2330 lines
58 KiB
C
2330 lines
58 KiB
C
// g_combat.c
|
|
|
|
#include "g_local.h"
|
|
|
|
#include "voice_bitch.h"
|
|
#include "voice_punk.h"
|
|
|
|
/*
|
|
============
|
|
CanDamage
|
|
|
|
Returns true if the inflictor can directly damage the target. Used for
|
|
explosions and melee attacks.
|
|
============
|
|
*/
|
|
|
|
qboolean CanDamageThroughAlpha (trace_t trace, edict_t *targ, edict_t *inflictor, vec3_t dest)
|
|
{
|
|
|
|
vec3_t dir;
|
|
vec3_t alpha_start;
|
|
trace_t tr;
|
|
|
|
VectorSubtract (dest, inflictor->s.origin, dir);
|
|
VectorNormalize (dir);
|
|
VectorCopy (trace.endpos, alpha_start);
|
|
|
|
if (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)
|
|
{
|
|
VectorMA (alpha_start, 4*3, dir, alpha_start);
|
|
}
|
|
else
|
|
{
|
|
VectorMA (alpha_start, 8, dir, alpha_start);
|
|
}
|
|
|
|
if (targ->client)
|
|
tr = gi.trace (alpha_start, NULL, NULL, dest, NULL, MASK_SHOT);
|
|
else
|
|
tr = gi.trace (alpha_start, NULL, NULL, dest, inflictor, MASK_SHOT);
|
|
|
|
if (tr.ent == targ)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
else if (((trace.ent) && (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)) || ((trace.contents & MASK_ALPHA)))
|
|
{
|
|
VectorCopy (targ->s.origin, dest);
|
|
if (CanDamageThroughAlpha (trace, targ, inflictor, dest))
|
|
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;
|
|
else if (((trace.ent) && (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)) || ((trace.contents & MASK_ALPHA)))
|
|
{
|
|
VectorCopy (targ->s.origin, dest);
|
|
if (CanDamageThroughAlpha (trace, targ, inflictor, dest))
|
|
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;
|
|
else if (((trace.ent) && (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)) || ((trace.contents & MASK_ALPHA)))
|
|
{
|
|
VectorCopy (targ->s.origin, dest);
|
|
if (CanDamageThroughAlpha (trace, targ, inflictor, dest))
|
|
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;
|
|
else if (((trace.ent) && (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)) || ((trace.contents & MASK_ALPHA)))
|
|
{
|
|
VectorCopy (targ->s.origin, dest);
|
|
if (CanDamageThroughAlpha (trace, targ, inflictor, dest))
|
|
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;
|
|
else if (((trace.ent) && (trace.ent->s.renderfx2 & RF2_SURF_ALPHA)) || ((trace.contents & MASK_ALPHA)))
|
|
{
|
|
VectorCopy (targ->s.origin, dest);
|
|
if (CanDamageThroughAlpha (trace, targ, inflictor, dest))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
Killed
|
|
============
|
|
*/
|
|
void Killed (edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mdx_part, int mdx_subobject)
|
|
{
|
|
if (targ->health < -999)
|
|
targ->health = -999;
|
|
|
|
targ->enemy = attacker;
|
|
|
|
// turn off Flamethrower
|
|
if (targ->client || (targ->svflags & SVF_MONSTER))
|
|
targ->s.renderfx2 &= ~RF2_FLAMETHROWER;
|
|
|
|
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
|
|
{
|
|
cast_memory_t *mem;
|
|
|
|
// Ridah 5-8-99, so we can shoot through dying characters
|
|
targ->maxs[2] = 0;
|
|
gi.linkentity( targ );
|
|
|
|
// JOSEPH 12-MAR-99
|
|
/*
|
|
if (targ->leader && targ->leader->client)
|
|
{
|
|
targ->leader->client->pers.friends--;
|
|
}
|
|
|
|
targ->leader = NULL;
|
|
*/
|
|
// END JOSEPH
|
|
|
|
{
|
|
edict_t *player;
|
|
cast_memory_t *cast_memory;
|
|
|
|
player = &g_edicts[1];
|
|
|
|
cast_memory = level.global_cast_memory[targ->character_index][player->character_index];
|
|
|
|
if (cast_memory && cast_memory->flags & MEMORY_HIRED)
|
|
player->client->pers.friends--;
|
|
|
|
}
|
|
|
|
targ->leader = NULL;
|
|
|
|
|
|
|
|
if (attacker->client && attacker->client->gun_noise) // a guy killed with the silencer should not wake the others up
|
|
{
|
|
// Share our list of enemies, so surrounding friends can seek revenge
|
|
mem = targ->cast_info.friend_memory;
|
|
while (mem)
|
|
{
|
|
if (mem->timestamp > (level.time - 3))
|
|
{
|
|
AI_ShareEnemies( targ, &g_edicts[mem->cast_ent] );
|
|
}
|
|
|
|
mem = mem->next;
|
|
}
|
|
}
|
|
|
|
// Simulate triggering a combattarget
|
|
if (targ->combattarget)
|
|
AI_Goto_CombatTarget( targ );
|
|
else if (targ->combat_goalent)
|
|
{ // trigger it in 2 seconds
|
|
char *savetarget;
|
|
edict_t *target;
|
|
|
|
target = targ->combat_goalent;
|
|
|
|
if (target->delay < 2)
|
|
target->delay = 2;
|
|
|
|
savetarget = target->target;
|
|
target->target = target->pathtarget;
|
|
G_UseTargets (target, targ);
|
|
target->target = savetarget;
|
|
}
|
|
}
|
|
|
|
// Remove them from the game
|
|
AI_UnloadCastMemory ( targ );
|
|
|
|
// if they were killed by an AI character, send them back to their start point
|
|
if (attacker->svflags & SVF_MONSTER && !attacker->goal_ent && attacker->start_ent
|
|
&& (!attacker->enemy || (attacker->enemy == targ)))
|
|
{
|
|
attacker->goal_ent = attacker->start_ent;
|
|
}
|
|
|
|
if (targ->movetype == MOVETYPE_PUSH || targ->movetype == MOVETYPE_STOP || targ->movetype == MOVETYPE_NONE)
|
|
{ // doors, triggers, etc
|
|
targ->die (targ, inflictor, attacker, damage, point, mdx_part, mdx_subobject);
|
|
return;
|
|
}
|
|
|
|
if ((targ->svflags & SVF_MONSTER) && (targ->deadflag != DEAD_DEAD))
|
|
{
|
|
targ->touch = NULL;
|
|
cast_death_use (targ);
|
|
}
|
|
|
|
if (targ->svflags & SVF_MONSTER)
|
|
EP_SpecialEventDeath (targ);
|
|
|
|
targ->die (targ, inflictor, attacker, damage, point, mdx_part, mdx_subobject);
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
SpawnDamage
|
|
================
|
|
*/
|
|
|
|
//int sp_blood1;
|
|
|
|
void SpawnDamage (edict_t *self, int type, vec3_t origin, vec3_t normal, int damage)
|
|
{
|
|
// int i, cnt;
|
|
// vec3_t vec;
|
|
// edict_t *sprent;
|
|
|
|
if ((cl_parental_lock->value && !cl_parental_override->value) && type == TE_BLOOD)
|
|
return;
|
|
|
|
if (damage > 127)
|
|
damage = 127;
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (type);
|
|
gi.WritePosition (origin);
|
|
if (type != TE_BLOOD)
|
|
gi.WriteDir (normal);
|
|
// JOSEPH 12-MAY-99-B
|
|
else
|
|
{
|
|
// As per Maxx request
|
|
if (damage > 1)
|
|
damage <<= 1;
|
|
gi.WriteByte( damage );
|
|
}
|
|
// END JOSEPH
|
|
gi.multicast (origin, MULTICAST_PVS);
|
|
|
|
/*
|
|
// FIXME: move these to the client-side TE_* effects
|
|
|
|
cnt = (int)(damage/2);
|
|
if (cnt > 4)
|
|
cnt = 4;
|
|
|
|
// Ridah, spawn some sprite effects
|
|
switch (type)
|
|
{
|
|
case TE_BLOOD:
|
|
for (i=0; i<cnt; i++)
|
|
{
|
|
sprent = G_Spawn();
|
|
VectorSet( sprent->s.origin,
|
|
origin[0] + crandom()*4,
|
|
origin[1] + crandom()*4,
|
|
origin[2] + crandom()*4 );
|
|
|
|
VectorClear( sprent->mins );
|
|
VectorClear( sprent->maxs );
|
|
|
|
VectorSubtract( origin, self->s.origin, vec );
|
|
vec[2] = 0;
|
|
VectorNormalize( vec );
|
|
|
|
VectorSet( sprent->velocity,
|
|
random()*40*vec[0],
|
|
random()*40*vec[1],
|
|
80 + 100*random() );
|
|
|
|
sprent->s.modelindex = sp_blood1;
|
|
sprent->s.frame = rand()%9;
|
|
sprent->movetype = MOVETYPE_BOUNCE;
|
|
sprent->clipmask = MASK_SOLID;
|
|
sprent->solid = SOLID_BBOX;
|
|
// sprent->s.effects |= EF_GIB;
|
|
sprent->owner = self;
|
|
sprent->think = G_FreeEdict;
|
|
sprent->nextthink = level.time + 1.0;
|
|
sprent->classname = "sp_blood1";
|
|
|
|
gi.linkentity( self );
|
|
}
|
|
break;
|
|
}
|
|
*/
|
|
}
|
|
|
|
|
|
/*
|
|
SpawnBloodPool
|
|
*/
|
|
void SpawnBloodPool (edict_t *self)
|
|
{
|
|
vec3_t origin;
|
|
vec3_t forward, right, up;
|
|
vec3_t end;
|
|
vec3_t vec;
|
|
trace_t tr;
|
|
int baseskin, currentskin;
|
|
|
|
if (self->onfireent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cl_parental_lock->value && !cl_parental_override->value)
|
|
return;
|
|
|
|
VectorCopy (self->s.origin, origin);
|
|
|
|
AngleVectors (self->s.angles, forward, right, up);
|
|
VectorMA (origin, -8192, up, end);
|
|
|
|
tr = gi.trace (origin, NULL, NULL, end, self, MASK_SHOT );
|
|
|
|
if (tr.contents & CONTENTS_SOLID)
|
|
{
|
|
|
|
baseskin = self->s.model_parts[PART_HEAD].baseskin;
|
|
currentskin = self->s.model_parts[PART_HEAD].skinnum[0];
|
|
|
|
if (baseskin != currentskin)
|
|
{
|
|
// VectorCopy (tr.endpos, vec);
|
|
// this will need to be computed for each mdx
|
|
// VectorMA (vec, -(32), forward, vec);
|
|
// VectorMA( vec, 0.1, tr.plane.normal, tr.endpos ); // lift it above the ground slightly
|
|
|
|
// Ridah, use object_bounds to get head position
|
|
if (self->s.model_parts[PART_HEAD].object_bounds[0] > 0)
|
|
{
|
|
vec3_t org, ang, mins, maxs;
|
|
vec3_t forward, right, up;
|
|
vec3_t rmins, rmaxs, pmins, pmaxs;
|
|
|
|
VectorCopy (self->s.origin, org);
|
|
VectorCopy (self->s.angles, ang);
|
|
|
|
VectorCopy (g_objbnds[-1+self->s.model_parts[PART_HEAD].object_bounds[0]][self->s.frame].mins, mins);
|
|
VectorCopy (g_objbnds[-1+self->s.model_parts[PART_HEAD].object_bounds[0]][self->s.frame].maxs, maxs);
|
|
|
|
AngleVectors (ang, forward, right, up);
|
|
|
|
VectorMA (org, ((mins[0] + maxs[0]) * 0.5), forward, org);
|
|
VectorMA (org, -((mins[1] + maxs[1]) * 0.5), right, org);
|
|
VectorMA (org, (mins[2] + maxs[2]) * 0.5, up, org);
|
|
|
|
// find rotated positions of mins/maxs, and then build the new min/max
|
|
VectorScale ( forward, mins[0], rmins);
|
|
VectorMA (rmins, -mins[1], right, rmins);
|
|
VectorMA (rmins, mins[2], up, rmins);
|
|
|
|
VectorScale ( forward, maxs[0], rmaxs);
|
|
VectorMA (rmaxs, -maxs[1], right, rmaxs);
|
|
VectorMA (rmaxs, maxs[2], up, rmaxs);
|
|
|
|
pmins[0] = (rmins[0] < rmaxs[0] ? rmins[0] : rmaxs[0]);
|
|
pmins[1] = (rmins[1] < rmaxs[1] ? rmins[1] : rmaxs[1]);
|
|
pmins[2] = (rmins[2] < rmaxs[2] ? rmins[2] : rmaxs[2]);
|
|
|
|
pmaxs[0] = (rmins[0] > rmaxs[0] ? rmins[0] : rmaxs[0]);
|
|
pmaxs[1] = (rmins[1] > rmaxs[1] ? rmins[1] : rmaxs[1]);
|
|
pmaxs[2] = (rmins[2] > rmaxs[2] ? rmins[2] : rmaxs[2]);
|
|
|
|
// now align the mins/maxs with the origin
|
|
mins[0] = pmins[0] - (0.5*(pmaxs[0] + pmins[0]));
|
|
mins[1] = pmins[1] - (0.5*(pmaxs[1] + pmins[1]));
|
|
mins[2] = pmins[2] - (0.5*(pmaxs[2] + pmins[2]));
|
|
maxs[0] = pmaxs[0] - (0.5*(pmaxs[0] + pmins[0]));
|
|
maxs[1] = pmaxs[1] - (0.5*(pmaxs[1] + pmins[1]));
|
|
maxs[2] = pmaxs[2] - (0.5*(pmaxs[2] + pmins[2]));
|
|
|
|
vec[0] = org[0] + (mins[0] + maxs[0]) / 2;
|
|
vec[1] = org[1] + (mins[1] + maxs[1]) / 2;
|
|
vec[2] = org[2] + (mins[2] + maxs[2]) / 2;
|
|
|
|
VectorMA( vec, -8192, up, end );
|
|
|
|
tr = gi.trace (vec, NULL, NULL, end, self, MASK_SHOT );
|
|
|
|
VectorMA( tr.endpos, 0.1, tr.plane.normal, tr.endpos ); // lift it above the ground slightly
|
|
}
|
|
}
|
|
else
|
|
VectorMA( tr.endpos, 0.1, tr.plane.normal, tr.endpos ); // lift it above the ground slightly
|
|
|
|
// JOSEPH 18-DEC-98
|
|
SurfaceSpriteEffect(SFX_SPRITE_SURF_BLOOD_POOL, 2, 2, tr.ent, tr.endpos, tr.plane.normal);
|
|
/*gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_BLOOD_POOL);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.multicast (origin, MULTICAST_PVS);*/
|
|
// END JOSEPH
|
|
}
|
|
else
|
|
{
|
|
// TBD
|
|
// the entity steped on the dead body so now we can leave a bloody trail
|
|
;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
============
|
|
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 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 (!damage)
|
|
return 0;
|
|
|
|
client = ent->client;
|
|
|
|
if (!client)
|
|
return 0;
|
|
/*
|
|
{
|
|
static int done=0;
|
|
// if (!done)
|
|
// gi.dprintf( "NOTE: Armour has been disabled (was preventing bullet damage)\n");
|
|
done = 1;
|
|
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 (ent, te_sparks, point, normal, save);
|
|
|
|
return save;
|
|
}
|
|
|
|
void M_ReactToDamage (edict_t *targ, edict_t *attacker, float damage)
|
|
{
|
|
cast_memory_t *cast_memory;
|
|
int i;
|
|
|
|
if (targ->health <= 0)
|
|
return;
|
|
|
|
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
|
|
return;
|
|
|
|
if (!(targ->client) && !(targ->svflags & SVF_MONSTER))
|
|
return;
|
|
|
|
|
|
// stop hiding, someone has hurt us
|
|
if ( (targ->cast_info.aiflags & AI_TAKE_COVER)
|
|
&& (!targ->combat_goalent || (VectorDistance(targ->s.origin, targ->combat_goalent->s.origin) < 48)))
|
|
{
|
|
targ->cast_info.aiflags &= ~AI_TAKE_COVER;
|
|
if (targ->combat_goalent)
|
|
targ->combat_goalent = NULL;
|
|
|
|
targ->dont_takecover_time = level.time + 2;
|
|
}
|
|
|
|
if (attacker == targ || attacker == targ->enemy)
|
|
return;
|
|
|
|
// friendly fire?
|
|
if ((targ->cast_group == attacker->cast_group) && (targ->cast_group == 1) && attacker->client)
|
|
{
|
|
if (damage > 40)
|
|
targ->missteam = 1;
|
|
|
|
if (!targ->enemy && (targ->missteam++ > 0) && level.global_cast_memory[targ->character_index][attacker->character_index])
|
|
{ // make them an enemy
|
|
|
|
// Ridah, 17-may-99, make sure other hiredguys's don't attack their leader
|
|
for (i=1; i<level.num_characters; i++)
|
|
{
|
|
if (level.characters[i] && (level.characters[i]->cast_group == 1) && (level.global_cast_memory[i][targ->character_index]) && (level.characters[i]->leader == attacker))
|
|
{
|
|
AI_RemoveFromMemory( level.characters[i], level.global_cast_memory[i][targ->character_index] );
|
|
AI_RemoveFromMemory( targ, level.global_cast_memory[targ->character_index][i] );
|
|
AI_AddToMemory( level.characters[i], level.global_cast_memory[i][targ->character_index], MEMORY_TYPE_ENEMY );
|
|
AI_AddToMemory( targ, level.global_cast_memory[targ->character_index][i], MEMORY_TYPE_ENEMY );
|
|
AI_MakeEnemy( level.characters[i], targ, 0 );
|
|
level.characters[i]->enemy = targ;
|
|
}
|
|
}
|
|
|
|
AI_RemoveFromMemory( targ, level.global_cast_memory[targ->character_index][attacker->character_index] );
|
|
AI_AddToMemory( targ, level.global_cast_memory[targ->character_index][attacker->character_index], MEMORY_TYPE_ENEMY );
|
|
targ->cast_group = 0;
|
|
targ->leader = NULL;
|
|
AI_MakeEnemy( targ, attacker, 0 );
|
|
targ->enemy = attacker;
|
|
}
|
|
else
|
|
{
|
|
if (targ->gender == GENDER_MALE)
|
|
Voice_Random( targ, attacker, friendlypain, NUM_FRIENDLYPAIN );
|
|
else if (targ->gender == GENDER_FEMALE)
|
|
// Ridah, 17-may-99, we can't ship with a todo message, so just play a pain sound
|
|
// gi.dprintf( "FIXME: Female \"Quit shootin me!\"" );
|
|
Voice_Random( targ, attacker, female_specific, 4 );
|
|
}
|
|
}
|
|
|
|
// they aren't on our side, are they a friend?
|
|
if (!(cast_memory = level.global_cast_memory[targ->character_index][attacker->character_index]))
|
|
{ // record this sighting, so we can start to attack them
|
|
|
|
AI_RecordSighting(targ, attacker, VectorDistance(targ->s.origin, attacker->s.origin) );
|
|
cast_memory = level.global_cast_memory[targ->character_index][attacker->character_index];
|
|
|
|
}
|
|
|
|
if (cast_memory->memory_type == MEMORY_TYPE_FRIEND)
|
|
{
|
|
// gi.dprintf("SOUND TODO: Are you done shooting me?\n");
|
|
return; // let them off easy
|
|
}
|
|
|
|
if ((cast_memory->memory_type != MEMORY_TYPE_ENEMY) || !(cast_memory->flags & MEMORY_HOSTILE_ENEMY))
|
|
{
|
|
// make them a hostile enemy
|
|
AI_MakeEnemy( targ, attacker, 0 );
|
|
}
|
|
|
|
// if we're attacking someone else, and this is a client, get mad at them!
|
|
if (attacker->cast_group != targ->cast_group && targ->enemy != attacker && attacker->client)
|
|
{
|
|
AI_StartAttack( targ, attacker );
|
|
}
|
|
|
|
}
|
|
|
|
// returns TRUE if attacker is on same team (can't damage)
|
|
qboolean CheckTeamDamage (edict_t *targ, edict_t *attacker)
|
|
{
|
|
if (targ == attacker) // we can always hurt ourselves
|
|
return false;
|
|
|
|
if (targ->client->pers.friendly_vulnerable)
|
|
return true;
|
|
|
|
if (teamplay->value && targ && attacker && attacker->client && targ->client && (targ->client->pers.team) && (targ->client->pers.team == attacker->client->pers.team))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
// Ridah, health_threshold targetting
|
|
void CheckHealthTarget( edict_t *targ, char *target )
|
|
{
|
|
edict_t *goal;
|
|
|
|
goal = G_Find( NULL, FOFS(targetname), target );
|
|
|
|
if (goal)
|
|
{
|
|
if (!strcmp(goal->classname, "misc_use_cutscene") || strstr(goal->classname, "trigger_"))
|
|
{ // trigger it
|
|
goal->use( goal, targ, targ );
|
|
}
|
|
else // walk to it
|
|
{
|
|
targ->goal_ent = goal;
|
|
targ->cast_info.aiflags |= AI_GOAL_IGNOREENEMY;
|
|
targ->cast_info.aiflags &= ~AI_TAKE_COVER;
|
|
|
|
targ->cast_info.currentmove = targ->cast_info.move_run;
|
|
targ->goal_ent->cast_info.aiflags |= AI_GOAL_RUN;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 te_sparks;
|
|
float dmg;
|
|
|
|
dmg = (float)(damage);
|
|
|
|
if (!targ->takedamage)
|
|
return;
|
|
|
|
if (targ->cast_info.aiflags & AI_IMMORTAL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// friendly fire avoidance
|
|
// if enabled you can't hurt teammates (but you can hurt yourself)
|
|
// knockback still occurs
|
|
if (!(dflags & DAMAGE_NO_PROTECTION) && (targ != attacker) && ((deathmatch->value && (teamplay->value || ((int)(dmflags->value) & (DF_MODELTEAMS/*| DF_SKINTEAMS*/))) /*|| coop->value*/)))
|
|
{
|
|
if ((OnSameTeam (targ, attacker)) && (!targ->client->pers.friendly_vulnerable))
|
|
{
|
|
if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
|
|
{
|
|
dmg = 0;
|
|
}
|
|
else
|
|
mod |= MOD_FRIENDLY_FIRE;
|
|
}
|
|
}
|
|
meansOfDeath = mod;
|
|
|
|
// easy mode takes half damage
|
|
if (deathmatch->value == 0 && targ->client)
|
|
{
|
|
if (skill->value == 0)
|
|
dmg *= 0.2; // Ridah, bumped it up a bit, since Rockets were only doing 2% health, 4% would be a bit more reasonable
|
|
else if (skill->value == 1)
|
|
dmg *= 0.35;
|
|
else if (skill->value == 2)
|
|
dmg *= 0.60;
|
|
else if (skill->value == 3)
|
|
dmg *= 0.80;
|
|
|
|
if (skill->value >= 2)
|
|
{
|
|
if (rand()%(2 + (int)skill->value * 2) > 4)
|
|
dmg *= 2; // randomized simulation of head shot
|
|
}
|
|
|
|
if (dmg < 1)
|
|
dmg = 1;
|
|
|
|
|
|
// gi.dprintf ("dmg: %5.2f\n", dmg);
|
|
}
|
|
else if (deathmatch->value && dm_realmode->value && attacker != targ && attacker->client)
|
|
{
|
|
dmg *= 4;
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
// JOSEPH 11-APR-99
|
|
if ((mod == MOD_BLACKJACK) || (mod == MOD_CROWBAR))
|
|
te_sparks = TE_SPARKS;
|
|
else if (dflags & DAMAGE_BULLET && mod != MOD_DOGBITE)
|
|
te_sparks = TE_BULLET_SPARKS;
|
|
else if (mod != MOD_DOGBITE)
|
|
te_sparks = TE_SPARKS;
|
|
else
|
|
te_sparks = TE_SPARKS;
|
|
// END JOSEPH
|
|
|
|
VectorNormalize(dir);
|
|
|
|
// bonus damage for suprising a monster
|
|
// if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0))
|
|
// dmg *= 2;
|
|
|
|
if (((dflags & DAMAGE_NO_KNOCKBACK)) || (targ->nokickbackflag))
|
|
knockback = 0;
|
|
|
|
|
|
// figure momentum add
|
|
// JOSEPH 7-MAR-99
|
|
if ((!(dflags & DAMAGE_NO_KNOCKBACK)) && (mod == MOD_BLOWBACK))
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
knockback *= 3;
|
|
|
|
if (knockback < 100)
|
|
knockback = 100;
|
|
else if (knockback > 150)
|
|
knockback = 150;
|
|
|
|
if (targ->mass < 50)
|
|
mass = 50;
|
|
else
|
|
mass = targ->mass;
|
|
|
|
if (targ->client)
|
|
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
|
else
|
|
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
|
|
|
VectorAdd (targ->velocity, kvel, targ->velocity);
|
|
}
|
|
else if (!(dflags & DAMAGE_NO_KNOCKBACK))
|
|
// END JOSEPH
|
|
{
|
|
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) && (targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
|
|
{
|
|
vec3_t kvel;
|
|
float mass;
|
|
|
|
if (targ->mass < 50)
|
|
mass = 50;
|
|
else
|
|
mass = targ->mass;
|
|
|
|
// JOSEPH 13-MAY-99
|
|
if (targ->client && (attacker == targ) && deathmatch->value)
|
|
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
|
else if (targ->client && (attacker == targ))
|
|
VectorScale (dir, 128.0 * (float)knockback / mass, kvel);
|
|
else
|
|
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
|
// END JOSEPH
|
|
|
|
VectorAdd (targ->velocity, kvel, targ->velocity);
|
|
}
|
|
}
|
|
|
|
take = dmg;
|
|
save = 0;
|
|
|
|
// JOSEPH 1-APR-99-B
|
|
if (targ->client && dmg && (!(dflags & DAMAGE_NO_ARMOR)))
|
|
{
|
|
int index;
|
|
gitem_t *item = NULL;
|
|
int random;
|
|
float takefactor;
|
|
|
|
// JOSEPH 21-MAY-99
|
|
if (mod == MOD_DOGBITE)
|
|
{
|
|
if (inflictor->s.origin[2] < targ->s.origin[2]) // legs
|
|
{
|
|
random = 2;
|
|
}
|
|
else if (inflictor->s.origin[2] > targ->s.origin[2] + targ->viewheight) // head
|
|
{
|
|
random = 1;
|
|
}
|
|
else // body
|
|
{
|
|
random = 0;
|
|
}
|
|
}
|
|
else if (dflags & DAMAGE_BULLET)
|
|
{
|
|
random = rand()%100;
|
|
|
|
if (random < 40) // legs
|
|
{
|
|
random = 2;
|
|
}
|
|
else if (random < 60) // head
|
|
{
|
|
random = 1;
|
|
}
|
|
else // body
|
|
{
|
|
random = 0;
|
|
}
|
|
}
|
|
else
|
|
random = rand()%3;
|
|
// END JOSEPH
|
|
|
|
|
|
// JOSEPH 5-APR-99
|
|
if (mod == MOD_FALLING)
|
|
{
|
|
item = FindItem ("Legs Armor");
|
|
takefactor = 0.5;
|
|
if (!(targ->client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Legs Armor Heavy");
|
|
takefactor = 0.25;
|
|
}
|
|
}
|
|
else if (random == 0)
|
|
// END JOSEPH
|
|
{
|
|
item = FindItem ("Jacket Armor");
|
|
takefactor = 0.3;
|
|
if (!(targ->client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Jacket Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
}
|
|
else if (random == 1)
|
|
{
|
|
item = FindItem ("Helmet Armor");
|
|
takefactor = 0.3;
|
|
if (!(targ->client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Helmet Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
item = FindItem ("Legs Armor");
|
|
takefactor = 0.3;
|
|
if (!(targ->client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Legs Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
}
|
|
|
|
if ((item) && (targ->client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
int takehealth;
|
|
int takeshield;
|
|
|
|
takehealth = take * takefactor;
|
|
takeshield = take - takehealth;
|
|
|
|
// Ridah, make Flamethrower burn armor away faster than normal
|
|
if (mod == MOD_FLAMETHROWER)
|
|
{
|
|
if (takeshield < 1)
|
|
takeshield = 1;
|
|
|
|
takeshield *= 3;
|
|
}
|
|
|
|
index = ITEM_INDEX (item);
|
|
|
|
// JOSEPH 5-APR-99
|
|
if (takeshield <= targ->client->pers.inventory[ index ])
|
|
{
|
|
targ->client->pers.inventory[ index ] -= takeshield;
|
|
take = takehealth;
|
|
if (!take)
|
|
take = 1;
|
|
}
|
|
else
|
|
{
|
|
takehealth = ((take - targ->client->pers.inventory[ index ])+
|
|
(takefactor*targ->client->pers.inventory[ index ]));
|
|
targ->client->pers.inventory[ index ] = 0;
|
|
take = takehealth;
|
|
if (!take)
|
|
take = 1;
|
|
}
|
|
// END JOSEPH
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
// check for godmode
|
|
if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{
|
|
take = 0;
|
|
save = dmg;
|
|
SpawnDamage (targ, te_sparks, point, normal, save);
|
|
}
|
|
|
|
// check for invincibility
|
|
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
|
{
|
|
if (targ->pain_debounce_time < level.time)
|
|
{
|
|
// JOSEPH 29-MAR-99
|
|
//gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
|
|
// END JOSEPH
|
|
targ->pain_debounce_time = level.time + 2;
|
|
}
|
|
take = 0;
|
|
save = dmg;
|
|
}
|
|
|
|
// JOSEPH 1-APR-99
|
|
//asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
|
|
//take -= asave;
|
|
|
|
//treat cheat/powerup savings the same as armor
|
|
asave = 0;
|
|
asave += save;
|
|
// END JOSEPH
|
|
|
|
// team damage avoidance
|
|
//if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
|
|
// return;
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
if (mod == MOD_FLAMETHROWER)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
SpawnDamage (targ, TE_BLOOD, point, normal, take);
|
|
else
|
|
SpawnDamage (targ, te_sparks, point, normal, take);
|
|
}
|
|
|
|
targ->health = targ->health - take;
|
|
|
|
|
|
// Ridah, trigger health threshold events
|
|
if (targ->health_target && (targ->health < targ->health_threshold))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target );
|
|
targ->health_target = NULL;
|
|
}
|
|
else if (targ->health_target2 && (targ->health < targ->health_threshold2))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target2 );
|
|
targ->health_target2 = NULL;
|
|
}
|
|
else if (targ->health_target3 && (targ->health < targ->health_threshold3))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target3 );
|
|
targ->health_target3 = NULL;
|
|
}
|
|
|
|
|
|
if (targ->health <= 0)
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
{
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
M_ReactToDamage (targ, attacker, take); // Ridah, so our friends seek vengence
|
|
}
|
|
|
|
// JOSEPH 3-MAR-99
|
|
if (targ->svflags & SVF_MONSTER)
|
|
{
|
|
// targ->solid = SOLID_NOT;
|
|
targ->svflags |= SVF_DEADMONSTER;
|
|
|
|
if (mod == MOD_ROCKET)
|
|
{
|
|
targ->s.renderfx2 &= ~RF2_DIR_LIGHTS;
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
// Ridah, removed so grenades and rockets can gib bodies
|
|
// if (!targ->deadflag)
|
|
Killed (targ, inflictor, attacker, take, point, 0, 0);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( (targ->svflags & SVF_MONSTER)
|
|
&& (targ->health > 0)) // Ridah, 31-may-99, possibly fixes Whore crouching death bug
|
|
{
|
|
M_ReactToDamage (targ, attacker, take);
|
|
if (take)
|
|
{
|
|
targ->pain (targ, attacker, knockback, take, 0, 0);
|
|
// nightmare mode monsters don't go into pain frames often
|
|
if (skill->value >= 3)
|
|
targ->pain_debounce_time = level.time + 5;
|
|
}
|
|
}
|
|
else if (client)
|
|
{
|
|
if (!(targ->flags & FL_GODMODE) && (take))
|
|
targ->pain (targ, attacker, knockback, take, 0, 0);
|
|
}
|
|
else if (take)
|
|
{
|
|
if (targ->pain)
|
|
targ->pain (targ, attacker, knockback, take, 0, 0);
|
|
}
|
|
|
|
// 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_armor += asave;
|
|
if (mod == MOD_FLAMETHROWER)
|
|
client->damage_flame += take*4;
|
|
else
|
|
client->damage_blood += take;
|
|
client->damage_knockback += knockback;
|
|
VectorCopy (point, client->damage_from);
|
|
}
|
|
}
|
|
|
|
void SpawnPartShotOff (edict_t *self, int mdx_part, vec3_t dir)
|
|
{
|
|
|
|
edict_t *dropped;
|
|
int skin, model;
|
|
vec3_t forward, angles;
|
|
int i;
|
|
|
|
if (mdx_part == PART_CIGAR)
|
|
{
|
|
self->count &= ~1;
|
|
return; // no cigar
|
|
}
|
|
|
|
else if (mdx_part == PART_HAT)
|
|
{
|
|
if (self->count & 2)
|
|
{
|
|
model = gi.modelindex("models/props/fedora/fedora.mdx");
|
|
// hat bug
|
|
self->count &= ~2;
|
|
}
|
|
else if (self->count & 4)
|
|
{
|
|
model = gi.modelindex("models/props/stetson/stetson.mdx");
|
|
// hat bug
|
|
self->count &= ~4;
|
|
}
|
|
else if (self->count & 8)
|
|
{
|
|
model = gi.modelindex("models/props/cap/cap.mdx");
|
|
// hat bug
|
|
self->count &= ~8;
|
|
}
|
|
}
|
|
|
|
skin = self->s.model_parts[mdx_part].baseskin;
|
|
|
|
dropped = G_Spawn();
|
|
|
|
VectorSet (dropped->mins, -4, -4, -2);
|
|
VectorSet (dropped->maxs, 4, 4, 2);
|
|
|
|
memset(&(dropped->s.model_parts[0]), 0, sizeof(model_part_t) * MAX_MODEL_PARTS);
|
|
|
|
dropped->s.num_parts++;
|
|
dropped->s.model_parts[PART_HEAD].modelindex = model;
|
|
for (i=0; i<MAX_MODELPART_OBJECTS; i++)
|
|
self->s.model_parts[PART_HEAD].baseskin = skin;
|
|
gi.GetObjectBounds( "models/actors/runt/fedora.mdx", &self->s.model_parts[PART_HEAD] );
|
|
|
|
dropped->movetype = MOVETYPE_BOUNCE;
|
|
dropped->clipmask = MASK_SHOT;
|
|
dropped->solid = SOLID_NOT;
|
|
dropped->s.renderfx2 |= RF2_NOSHADOW;
|
|
|
|
VectorCopy (self->s.origin, dropped->s.origin);
|
|
dropped->s.origin[2] += 24;
|
|
|
|
vectoangles (dir, angles);
|
|
AngleVectors (angles, forward, NULL, NULL);
|
|
|
|
VectorScale (forward, 100, dropped->velocity);
|
|
dropped->velocity [2] += 300;
|
|
|
|
gi.linkentity (dropped);
|
|
}
|
|
|
|
|
|
void gib_a_little_blood (edict_t *self)
|
|
{
|
|
|
|
trace_t tr;
|
|
vec3_t end;
|
|
float rnd;
|
|
|
|
VectorCopy (self->s.origin, end);
|
|
end[2] -= 8000;
|
|
|
|
tr = gi.trace (self->s.origin, NULL, NULL, end, self, MASK_SOLID);
|
|
|
|
rnd = (0.5 + 1.5*random());
|
|
SurfaceSpriteEffect(SFX_SPRITE_SURF_BLOOD1, (unsigned char)(rnd*SFX_BLOOD_WIDTH), (unsigned char)(rnd*SFX_BLOOD_HEIGHT),
|
|
tr.ent, tr.endpos, tr.plane.normal);
|
|
}
|
|
|
|
// JOSEPH 18-MAR-99
|
|
void Think_gib_fade (edict_t *ent)
|
|
{
|
|
if (!ent->misstime--)
|
|
{
|
|
ent->nextthink = 0;
|
|
G_FreeEdict(ent);
|
|
}
|
|
else
|
|
{
|
|
if (ent->misstime <= 20)
|
|
{
|
|
if (ent->misstime == 20)
|
|
{
|
|
ent->s.renderfx2 |= RF2_PASSALPHA;
|
|
ent->s.effects = 1; // this is full alpha now
|
|
}
|
|
|
|
ent->s.effects += (255/20);
|
|
|
|
if (ent->s.effects > 255)
|
|
ent->s.effects = 255;
|
|
}
|
|
|
|
if (rand()%100 > 90)
|
|
gib_a_little_blood (ent);
|
|
|
|
ent->nextthink = level.time + 0.1;
|
|
/*
|
|
// Ridah, optimized this to prevent SZ_GetSpace() overrun's
|
|
if (random() < 0.5 && ent->velocity[2])
|
|
{
|
|
static float last_blood;
|
|
vec3_t negvel;
|
|
|
|
// if (last_blood != level.time)
|
|
{
|
|
VectorScale( ent->velocity, -1, negvel );
|
|
VectorNormalize( negvel );
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_SPLASH);
|
|
gi.WriteByte (5);
|
|
gi.WritePosition (ent->s.origin);
|
|
gi.WriteDir (negvel);
|
|
gi.WriteByte (6);
|
|
gi.multicast (ent->s.origin, MULTICAST_PVS);
|
|
|
|
last_blood = level.time;
|
|
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
void gib_end_life (edict_t *self)
|
|
{
|
|
gib_a_little_blood (self);
|
|
|
|
// JOSEPH 18-MAR-99
|
|
self->think = Think_gib_fade;
|
|
self->nextthink = level.time + 0.1;
|
|
self->misstime = 80;
|
|
// END JOSEPH
|
|
}
|
|
|
|
void SpawnGib (edict_t *self, int mdx_part, int mdx_subobject, vec3_t dir)
|
|
{
|
|
edict_t *gib;
|
|
int i;
|
|
vec3_t forward, right, up, angles;
|
|
char gibname[1024];
|
|
float vel;
|
|
int max_rnd;
|
|
|
|
gi.sound(self, CHAN_VOICE, gi.soundindex("actors/player/bodyfalls/jibs.wav"), 1, ATTN_NORM, 0);
|
|
|
|
if (mdx_part == PART_BODY && mdx_subobject == 0) // Torso is big, so allow big chunks
|
|
{ // torso
|
|
max_rnd = 2;
|
|
Com_sprintf (gibname, sizeof(gibname), "models/props/gibs/gib%d.mdx", 3+(rand()%max_rnd) + 1);
|
|
}
|
|
else if (mdx_part == PART_LEGS)
|
|
{
|
|
max_rnd = 3;
|
|
Com_sprintf (gibname, sizeof(gibname), "models/props/gibs/gib%d.mdx", 1+(rand()%max_rnd) + 1);
|
|
}
|
|
else // arm/leg/head
|
|
{
|
|
max_rnd = 3;
|
|
Com_sprintf (gibname, sizeof(gibname), "models/props/gibs/gib%d.mdx", (rand()%max_rnd) + 1);
|
|
}
|
|
|
|
gib = G_Spawn();
|
|
|
|
memset(&(gib->s.model_parts[0]), 0, sizeof(model_part_t) * MAX_MODEL_PARTS);
|
|
|
|
gib->s.num_parts++;
|
|
gib->s.model_parts[PART_HEAD].modelindex = gi.modelindex (gibname);
|
|
for (i=0; i<MAX_MODELPART_OBJECTS; i++)
|
|
gib->s.model_parts[PART_HEAD].baseskin = 0;
|
|
|
|
// gib->movetype = MOVETYPE_TOSS;
|
|
gib->movetype = MOVETYPE_BOUNCE;
|
|
|
|
gib->solid = SOLID_NOT;
|
|
gib->s.renderfx2 |= RF2_NOSHADOW;
|
|
gib->s.renderfx2 |= RF2_DIR_LIGHTS;
|
|
|
|
gib->s.effects |= EF_GIB;
|
|
|
|
//gi.dprintf( "SpawnGib: shot ent: %i\n", (int)(self-g_edicts) );
|
|
|
|
VectorCopy (self->s.origin, gib->s.origin);
|
|
VectorCopy (self->s.angles, gib->s.angles);
|
|
|
|
gib->s.angles[YAW] += (rand()%32) - 16;
|
|
|
|
AngleVectors (gib->s.angles, forward, right, up);
|
|
|
|
VectorCopy (g_objbnds[-1+self->s.model_parts[mdx_part].object_bounds[mdx_subobject]][self->s.frame].mins, gib->mins);
|
|
VectorCopy (g_objbnds[-1+self->s.model_parts[mdx_part].object_bounds[mdx_subobject]][self->s.frame].maxs, gib->maxs);
|
|
|
|
VectorMA (gib->s.origin, ((gib->mins[0] + gib->maxs[0]) * 0.5), forward, gib->s.origin);
|
|
VectorMA (gib->s.origin, -((gib->mins[1] + gib->maxs[1]) * 0.5), right, gib->s.origin);
|
|
VectorMA (gib->s.origin, (gib->mins[2] + gib->maxs[2]) * 0.5, up, gib->s.origin);
|
|
|
|
|
|
if (deathmatch->value) // do client-side gibs in deathmatch
|
|
{
|
|
gib->s.origin[2] += 10;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_GIBS);
|
|
gi.WritePosition (gib->s.origin);
|
|
gi.WriteDir (vec3_origin);
|
|
gi.WriteByte ( 2 ); // number of gibs
|
|
gi.WriteByte ( 0 ); // scale of direction to add to velocity
|
|
gi.WriteByte ( 0 ); // random offset scale
|
|
gi.WriteByte ( 10 ); // random velocity scale
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
G_FreeEdict( gib );
|
|
|
|
return;
|
|
}
|
|
|
|
vel = (float) ((rand()%512) + 128);
|
|
|
|
vectoangles (dir, angles);
|
|
AngleVectors (angles, forward, NULL, NULL);
|
|
|
|
// Ridah, only fly off if not lying on ground
|
|
if (self->cast_info.currentmove && self->svflags & SVF_MONSTER && self->cast_info.currentmove->endfunc != AI_EndDeath)
|
|
{
|
|
VectorScale (forward, vel, gib->velocity);
|
|
gib->velocity[2] += (float)(rand()%256) + 120;
|
|
}
|
|
else
|
|
{
|
|
gib->velocity[0] = (float)(rand()%60) - 30;
|
|
gib->velocity[1] = (float)(rand()%60) - 30;
|
|
gib->velocity[2] += (float)(rand()%256) + 120;
|
|
}
|
|
|
|
VectorSet (gib->avelocity, 300*random(), 300*random(), 300*random());
|
|
|
|
VectorSet (gib->mins, -4, -4, -2);
|
|
VectorSet (gib->maxs, 4, 4, 2);
|
|
|
|
gib->think = gib_end_life;
|
|
gib->nextthink = level.time + 0.1;
|
|
|
|
gi.linkentity (gib);
|
|
|
|
// Ridah, optimized this to prevent SZ_GetSpace() overrun's
|
|
{
|
|
static float last_blood;
|
|
|
|
// JOSEPH 12-MAY-99-B
|
|
if (last_blood != level.time)
|
|
{
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_SPLASH);
|
|
gi.WriteByte (25);
|
|
gi.WritePosition (gib->s.origin);
|
|
gi.WriteDir (up);
|
|
gi.WriteByte (6);
|
|
gi.multicast (gib->s.origin, MULTICAST_PVS);
|
|
|
|
last_blood = level.time;
|
|
}
|
|
// END JOSEPH
|
|
}
|
|
|
|
// Spawn some blood on the wall close by
|
|
for (i=0; i<1; i++)
|
|
{
|
|
trace_t tr;
|
|
vec3_t end;
|
|
|
|
VectorCopy (gib->s.origin, end);
|
|
VectorAdd( end, tv((random()-0.5)*48, (random()-0.5)*48, -16 - (random())*16), end );
|
|
|
|
tr = gi.trace (gib->s.origin, NULL, NULL, end, gib, MASK_SOLID);
|
|
|
|
if ((tr.fraction < 1 && tr.ent == &g_edicts[0]) || !(tr.surface->flags & SURF_SKY))
|
|
{
|
|
float rnd;
|
|
|
|
rnd = (1.5 + 3.2*random());
|
|
SurfaceSpriteEffect(SFX_SPRITE_SURF_BLOOD1, (unsigned char)(SFX_BLOOD_WIDTH*rnd), (unsigned char)(SFX_BLOOD_HEIGHT*rnd),
|
|
tr.ent, tr.endpos, tr.plane.normal);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Rafael: this routine will need special handling if the jibed part is a helmet
|
|
void SpawnGibShotOff (edict_t *self, int mdx_part, int mdx_subobject, vec3_t dir)
|
|
{
|
|
#define GIBS_HEAD 4
|
|
#define GIBS_BODY 8
|
|
#define GIBS_ARMS 6
|
|
#define GIBS_LEGS 8
|
|
|
|
int i;
|
|
int numgibs;
|
|
|
|
if (self->gender != GENDER_MALE && self->gender != GENDER_FEMALE)
|
|
return;
|
|
|
|
if (cl_parental_lock->value && !cl_parental_override->value)
|
|
return;
|
|
|
|
//gi.dprintf( "shot ent: %i, part %i, object %i\n", (int)(self-g_edicts), mdx_part, mdx_subobject );
|
|
|
|
// ok the head got shot off so spawn in the standin
|
|
if (mdx_part == PART_HEAD)
|
|
{
|
|
numgibs = GIBS_HEAD;
|
|
|
|
if (deathmatch->value)
|
|
numgibs /= 2;
|
|
|
|
self->s.model_parts[mdx_part].invisible_objects = (1<<0 | 1<<1);
|
|
for (i=0; i<numgibs; i++)
|
|
SpawnGib (self, mdx_part, mdx_subobject, dir);
|
|
|
|
// Ridah, if the head goes, the hat and cigar must also go
|
|
if (self->s.model_parts[PART_CIGAR].modelindex)
|
|
{
|
|
self->s.model_parts[PART_CIGAR].invisible_objects = (1<<0 | 1<<1);
|
|
SpawnPartShotOff (self, PART_CIGAR, dir);
|
|
}
|
|
|
|
if (self->s.model_parts[PART_HAT].modelindex)
|
|
{
|
|
self->s.model_parts[PART_HAT].invisible_objects = (1<<0 | 1<<1);
|
|
SpawnPartShotOff (self, PART_HAT, dir);
|
|
}
|
|
|
|
// hat and cigar bug
|
|
self->count = 0;
|
|
|
|
}
|
|
else if (mdx_part == PART_LEGS || mdx_part == PART_BODY)
|
|
{
|
|
// sub object 0 cant be blown off
|
|
if (mdx_subobject == 0)
|
|
{
|
|
if ( self->s.model_parts[PART_BODY].invisible_objects & ((1<<1) | (1<<2) | (1<<3))
|
|
&& self->s.model_parts[PART_BODY].invisible_objects & ((1<<4) | (1<<5) | (1<<6))
|
|
&& self->s.model_parts[PART_LEGS].invisible_objects & ((1<<1) | (1<<2) | (1<<3))
|
|
&& self->s.model_parts[PART_LEGS].invisible_objects & ((1<<4) | (1<<5) | (1<<6)) )
|
|
{
|
|
numgibs = GIBS_BODY;
|
|
|
|
if (deathmatch->value)
|
|
numgibs /= 2;
|
|
|
|
for (i=0; i<numgibs; i++)
|
|
SpawnGib (self, mdx_part, mdx_subobject, dir);
|
|
|
|
// Ridah, couldn't free it since it has further processing in T_DamageMDX()
|
|
self->nextthink = -1;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->solid = SOLID_NOT;
|
|
gi.linkentity( self );
|
|
|
|
// self->s.model_parts[PART_BODY].invisible_objects |= (1<<0 | 1<<1);
|
|
// self->s.model_parts[PART_LEGS].invisible_objects |= (1<<0 | 1<<1);
|
|
// self->s.model_parts[PART_HEAD].invisible_objects |= (1<<0 | 1<<1);
|
|
//self->s.model_parts[PART_HEAD].invisible_objects |= (1<<0 | 1<<1);
|
|
//self->s.model_parts[PART_HEAD].invisible_objects |= (1<<0 | 1<<1);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
// the left side
|
|
else if (mdx_subobject <= 3)
|
|
{
|
|
self->s.model_parts[mdx_part].invisible_objects |= ((1<<1) | (1<<2) | (1<<3));
|
|
|
|
if (mdx_part == PART_LEGS)
|
|
numgibs = GIBS_LEGS;
|
|
else
|
|
numgibs = GIBS_ARMS;
|
|
|
|
|
|
if (deathmatch->value)
|
|
numgibs /= 2;
|
|
|
|
// Ridah, increased this, we really need lots of gibs, and since they disappear pretty quick, and
|
|
// the pody parts are harder to gib now, they shouldn't occur so often
|
|
for (i=0; i<numgibs; i++)
|
|
SpawnGib (self, mdx_part, 1 + i%3/*mdx_subobject*/, dir); // Ridah, spawn gibs at each subobject
|
|
|
|
// set health back up so next gib takes more shots
|
|
self->health = 0;
|
|
}
|
|
// the right side
|
|
else if (mdx_subobject <= 6)
|
|
{
|
|
self->s.model_parts[mdx_part].invisible_objects |= ((1<<4) | (1<<5) | (1<<6));
|
|
|
|
if (mdx_part == PART_LEGS)
|
|
numgibs = GIBS_LEGS;
|
|
else
|
|
numgibs = GIBS_ARMS;
|
|
|
|
if (deathmatch->value)
|
|
numgibs /= 2;
|
|
|
|
for (i=0; i<numgibs; i++)
|
|
SpawnGib (self, mdx_part, 4 + i%3/*mdx_subobject*/, dir); // Ridah, spawn gibs at each subobject
|
|
|
|
// set health back up so next gib takes more shots
|
|
self->health = 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void Think_Shard (edict_t *ent)
|
|
{
|
|
if (!ent->misstime--)
|
|
{
|
|
ent->nextthink = 0;
|
|
G_FreeEdict(ent);
|
|
}
|
|
else
|
|
{
|
|
if (ent->misstime <= 15)
|
|
{
|
|
if (ent->misstime == 15)
|
|
{
|
|
ent->s.renderfx2 |= RF2_PASSALPHA;
|
|
ent->s.effects = 1; // this is full alpha now
|
|
}
|
|
|
|
ent->s.effects += (255/15);
|
|
|
|
if (ent->s.effects > 255)
|
|
ent->s.effects = 255;
|
|
}
|
|
|
|
ent->nextthink = level.time + 0.1;
|
|
}
|
|
}
|
|
|
|
void shard_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point, int mdx_part, int mdx_subobject)
|
|
{
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
|
|
void ThrowShard (edict_t *self, char *modelname, float speed, vec3_t origin)
|
|
{
|
|
edict_t *chunk;
|
|
vec3_t v;
|
|
|
|
chunk = G_Spawn();
|
|
VectorCopy (origin, chunk->s.origin);
|
|
gi.setmodel (chunk, modelname);
|
|
v[0] = speed * crandom();
|
|
v[1] = speed * crandom();
|
|
v[2] = speed + 16 * crandom();
|
|
VectorMA (self->velocity, speed, v, chunk->velocity);
|
|
chunk->movetype = MOVETYPE_BOUNCE;
|
|
chunk->solid = SOLID_NOT;
|
|
chunk->avelocity[0] = random()*128;
|
|
chunk->avelocity[1] = random()*128;
|
|
chunk->avelocity[2] = random()*128;
|
|
chunk->think = G_FreeEdict;
|
|
chunk->nextthink = level.time + 5 + random()*5;
|
|
chunk->s.frame = 0;
|
|
chunk->flags = 0;
|
|
chunk->classname = "debris";
|
|
chunk->takedamage = DAMAGE_YES;
|
|
chunk->die = shard_die;
|
|
chunk->think = Think_Shard;
|
|
chunk->misstime = 20;
|
|
chunk->nextthink = level.time + 0.1;
|
|
chunk->s.renderfx2 |= RF2_NOSHADOW;
|
|
|
|
gi.linkentity (chunk);
|
|
}
|
|
|
|
|
|
// RAFAEL
|
|
void T_DamageMDX (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,
|
|
int mdx_part, int mdx_subobject)
|
|
{
|
|
gclient_t *client;
|
|
int take;
|
|
int save;
|
|
int asave;
|
|
int te_sparks;
|
|
qboolean hasarmor = false;
|
|
|
|
if (!targ->takedamage)
|
|
return;
|
|
|
|
if (targ->cast_info.aiflags & AI_IMMORTAL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// friendly fire avoidance
|
|
// if enabled you can't hurt teammates (but you can hurt yourself)
|
|
// knockback still occurs
|
|
if (!(dflags & DAMAGE_NO_PROTECTION) && (targ != attacker) && ((deathmatch->value && (teamplay->value || ((int)(dmflags->value) & (DF_MODELTEAMS/*| DF_SKINTEAMS*/))) /*|| coop->value*/)))
|
|
{
|
|
if ((OnSameTeam (targ, attacker)) && (!targ->client->pers.friendly_vulnerable))
|
|
{
|
|
if ((int)(dmflags->value) & DF_NO_FRIENDLY_FIRE)
|
|
{
|
|
damage = 0;
|
|
}
|
|
else
|
|
mod |= MOD_FRIENDLY_FIRE;
|
|
}
|
|
}
|
|
meansOfDeath = mod;
|
|
|
|
// easy mode takes half damage
|
|
if (deathmatch->value == 0 && targ->client)
|
|
{
|
|
if (skill->value == 0)
|
|
damage *= 0.1; // used to be 50%, so we have to make it 10% to be 1/5 of what it was before
|
|
else if (skill->value == 1)
|
|
damage *= 0.6;
|
|
|
|
if (!damage)
|
|
damage = 1;
|
|
}
|
|
else if (deathmatch->value && dm_realmode->value && attacker != targ && attacker->client)
|
|
{
|
|
damage *= 4;
|
|
}
|
|
|
|
client = targ->client;
|
|
|
|
// JOSEPH 4-MAY-99
|
|
if (mod == MOD_BLACKJACK)
|
|
te_sparks = TE_IMPACT_CONCUSSION;
|
|
else if (dflags & DAMAGE_BULLET)
|
|
te_sparks = TE_BULLET_SPARKS;
|
|
else
|
|
te_sparks = TE_SPARKS;
|
|
// END JOSEPH
|
|
|
|
VectorNormalize(dir);
|
|
|
|
// 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) || (targ->nokickbackflag))
|
|
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 kvel;
|
|
float mass;
|
|
|
|
if (targ->mass < 50)
|
|
mass = 50;
|
|
else
|
|
mass = targ->mass;
|
|
|
|
// JOSEPH 13-MAY-99
|
|
if ((targ->client && attacker == targ) && deathmatch->value)
|
|
VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack...
|
|
else if (targ->client && attacker == targ)
|
|
VectorScale (dir, 128.0 * (float)knockback / mass, kvel);
|
|
else
|
|
VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
|
|
// END JOSEPH
|
|
VectorAdd (targ->velocity, kvel, targ->velocity);
|
|
}
|
|
}
|
|
|
|
take = damage;
|
|
save = 0;
|
|
|
|
// check for godmode
|
|
if ( (targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) )
|
|
{
|
|
take = 0;
|
|
save = damage;
|
|
SpawnDamage (targ, te_sparks, point, normal, save);
|
|
}
|
|
|
|
// check for invincibility
|
|
if ((client && client->invincible_framenum > level.framenum ) && !(dflags & DAMAGE_NO_PROTECTION))
|
|
{
|
|
if (targ->pain_debounce_time < level.time)
|
|
{
|
|
// JOSEPH 29-MAR-99
|
|
//gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
|
|
// END JOSEPH
|
|
targ->pain_debounce_time = level.time + 2;
|
|
}
|
|
take = 0;
|
|
save = damage;
|
|
}
|
|
|
|
// JOSEPH 1-APR-99
|
|
//asave = CheckArmor (targ, point, normal, take, te_sparks, dflags);
|
|
//take -= asave;
|
|
|
|
//treat cheat/powerup savings the same as armor
|
|
asave = 0;
|
|
asave += save;
|
|
// END JOSEPH
|
|
|
|
// team damage avoidance
|
|
//if (!(dflags & DAMAGE_NO_PROTECTION) && CheckTeamDamage (targ, attacker))
|
|
// return;
|
|
|
|
// do the damage
|
|
if (take)
|
|
{
|
|
|
|
// JOSEPH 2-APR-99
|
|
if (!deathmatch->value || dm_locational_damage->value)
|
|
{
|
|
int index;
|
|
gitem_t *item = NULL;
|
|
float takefactor;
|
|
int takehealth = 0;
|
|
int takeshield = 0;
|
|
|
|
if (mdx_part == PART_HEAD)
|
|
{
|
|
if (client)
|
|
{
|
|
item = FindItem ("Helmet Armor");
|
|
takefactor = 0.3;
|
|
if (!(client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Helmet Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
|
|
takeshield = (take * (1.0 - takefactor));
|
|
if (deathmatch->value)
|
|
take *= 1.5;
|
|
else if (skill->value < 3) // FIXME? note to Rafael/Joeseph: why does it add more damage if lower skill level?
|
|
take *= 3;
|
|
takehealth = take * takefactor;
|
|
}
|
|
else
|
|
{
|
|
if (deathmatch->value)
|
|
take *= 1.5; // Ridah, scaled this down, since it gives people with low pings an un-fair advantage
|
|
else if (skill->value < 3)
|
|
take *= 3;
|
|
}
|
|
}
|
|
else if (mdx_part == PART_LEGS)
|
|
{
|
|
if (client)
|
|
{
|
|
item = FindItem ("Legs Armor");
|
|
takefactor = 0.3;
|
|
if (!(client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Legs Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
|
|
takeshield = (take * (1.0 - takefactor));
|
|
takehealth = take * takefactor;
|
|
}
|
|
else
|
|
{
|
|
if (deathmatch->value)
|
|
take *= 1.0;
|
|
else if (skill->value < 3)
|
|
take *= 1.25;
|
|
}
|
|
}
|
|
else if (mdx_part == PART_BODY)
|
|
{
|
|
if (client)
|
|
{
|
|
item = FindItem ("Jacket Armor");
|
|
takefactor = 0.3;
|
|
if (!(client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
item = FindItem ("Jacket Armor Heavy");
|
|
takefactor = 0.1;
|
|
}
|
|
|
|
|
|
takeshield = (take * (1.0 - takefactor));
|
|
if (deathmatch->value)
|
|
take *= 1.5;
|
|
else if (skill->value < 3)
|
|
take *= 2;
|
|
takehealth = take * takefactor;
|
|
}
|
|
else
|
|
{
|
|
if (deathmatch->value)
|
|
take *= 1.5;
|
|
else if (skill->value < 3)
|
|
take *= 2;
|
|
}
|
|
}
|
|
else if (mdx_part == PART_CIGAR || mdx_part == PART_HAT)
|
|
{
|
|
// we hit a hat or something
|
|
take = 0;
|
|
targ->s.model_parts[mdx_part].modelindex = 0;
|
|
|
|
// need to spawn the piece flying off
|
|
{
|
|
SpawnPartShotOff (targ, mdx_part, dir);
|
|
}
|
|
}
|
|
|
|
if ((client) && (item) && (client->pers.inventory[ITEM_INDEX(item)]))
|
|
{
|
|
index = ITEM_INDEX (item);
|
|
|
|
hasarmor = true;
|
|
|
|
if (takeshield <= client->pers.inventory[ index ])
|
|
{
|
|
client->pers.inventory[ index ] -= takeshield;
|
|
take = takehealth;
|
|
}
|
|
else
|
|
{
|
|
takehealth = ((take - client->pers.inventory[ index ])+
|
|
(takefactor*client->pers.inventory[ index ]));
|
|
client->pers.inventory[ index ] = 0;
|
|
take = takehealth;
|
|
}
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
if (mod == MOD_FLAMETHROWER)
|
|
{
|
|
}
|
|
else
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
if (hasarmor)
|
|
{
|
|
|
|
// Ridah, this is a major bandwidth hog, need to make client-side, like gibs
|
|
if (!deathmatch->value)
|
|
{
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 4, point);
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 4, point);
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 8, point);
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 8, point);
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 16, point);
|
|
ThrowShard (targ, "models/props/glass/glass2.md2", 16, point);
|
|
}
|
|
|
|
{
|
|
int rval;
|
|
rval = rand();
|
|
|
|
if (rval > 66)
|
|
gi.sound(targ, CHAN_VOICE, gi.soundindex("weapons/bullethit_armor1.wav"), 1, ATTN_NORM, 0);
|
|
else if (rval > 33)
|
|
gi.sound(targ, CHAN_VOICE, gi.soundindex("weapons/bullethit_armor2.wav"), 1, ATTN_NORM, 0);
|
|
else
|
|
gi.sound(targ, CHAN_VOICE, gi.soundindex("weapons/bullethit_armor3.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
|
|
}
|
|
else
|
|
SpawnDamage (targ, TE_BLOOD, point, normal, take);
|
|
else
|
|
SpawnDamage (targ, te_sparks, point, normal, take);
|
|
}
|
|
|
|
|
|
targ->health = targ->health - take;
|
|
|
|
// Ridah, trigger health threshold events
|
|
if (targ->health_target && (targ->health < targ->health_threshold))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target );
|
|
targ->health_target = NULL;
|
|
}
|
|
else if (targ->health_target2 && (targ->health < targ->health_threshold2))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target2 );
|
|
targ->health_target2 = NULL;
|
|
}
|
|
else if (targ->health_target3 && (targ->health < targ->health_threshold3))
|
|
{
|
|
CheckHealthTarget( targ, targ->health_target3 );
|
|
targ->health_target3 = NULL;
|
|
}
|
|
|
|
// gi.dprintf ("health %d\n", targ->health);
|
|
|
|
if ((mod != MOD_BLACKJACK) && (mod != MOD_CROWBAR))
|
|
{
|
|
if (targ->health <= targ->gib_health && targ->gender != GENDER_NONE && mdx_part <= PART_BODY)
|
|
{
|
|
if (!deathmatch->value)
|
|
{
|
|
SpawnGibShotOff (targ, mdx_part, mdx_subobject, dir);
|
|
}
|
|
else // in deathmatch, take some beating before a limb gibs
|
|
{
|
|
if (((int)targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] + take) < 255)
|
|
targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] += take;
|
|
else
|
|
targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] = 255;
|
|
|
|
if (targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] > 150)
|
|
{
|
|
SpawnGibShotOff (targ, mdx_part, mdx_subobject, dir);
|
|
}
|
|
}
|
|
}
|
|
|
|
else if (!targ->deadflag)
|
|
{
|
|
if (((int)targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] + take) < 255)
|
|
targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] += take;
|
|
else
|
|
targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] = 255;
|
|
|
|
// tweek to help them stay alive a bit longer
|
|
if (targ->s.model_parts[mdx_part].hitpoints[mdx_subobject] > ((rand()%150)+25) && targ->health < 50)
|
|
{
|
|
targ->health -= targ->max_health; // make sure they die
|
|
SpawnGibShotOff (targ, mdx_part, mdx_subobject, dir);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Ridah, don't show pain skins if hit by melee weapon
|
|
//if ((mod != MOD_BLACKJACK) && (mod != MOD_CROWBAR))
|
|
if (targ->art_skins && (!(cl_parental_lock->value) || cl_parental_override->value))
|
|
{
|
|
int baseskin;//, currentskin;
|
|
|
|
baseskin = targ->s.model_parts[mdx_part].baseskin;
|
|
//currentskin = targ->s.model_parts[mdx_part].skinnum[mdx_subobject];
|
|
|
|
if (targ->health < (targ->max_health * 0.5))
|
|
{
|
|
targ->s.model_parts[mdx_part].skinnum[mdx_subobject] = baseskin + 2;
|
|
}
|
|
else if (targ->health < (targ->max_health * 0.75))
|
|
{
|
|
targ->s.model_parts[mdx_part].skinnum[mdx_subobject] = baseskin + 1;
|
|
}
|
|
}
|
|
|
|
if (targ->health <= 0)
|
|
{
|
|
if ((targ->svflags & SVF_MONSTER) || (client))
|
|
{
|
|
targ->flags |= FL_NO_KNOCKBACK;
|
|
M_ReactToDamage (targ, attacker, take); // Ridah, so our friends seek vengence
|
|
}
|
|
|
|
// JOSEPH 3-MAR-99
|
|
if (targ->svflags & SVF_MONSTER)
|
|
{
|
|
// targ->solid = SOLID_NOT;
|
|
targ->svflags |= SVF_DEADMONSTER;
|
|
|
|
if (mod == MOD_ROCKET)
|
|
{
|
|
targ->s.renderfx2 &= ~RF2_DIR_LIGHTS;
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
// Ridah, removed so grenades and rockets can gib bodies
|
|
// if (!targ->deadflag)
|
|
Killed (targ, inflictor, attacker, take, point, mdx_part, mdx_subobject);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( (targ->svflags & SVF_MONSTER)
|
|
&& (targ->health > 0)) // Ridah, 31-may-99, possibly fixes Whore crouching death bug
|
|
{
|
|
M_ReactToDamage (targ, attacker, take);
|
|
if (take)
|
|
{
|
|
targ->pain (targ, attacker, knockback, take, mdx_part, mdx_subobject);
|
|
// nightmare mode monsters don't go into pain frames often
|
|
if (skill->value >= 3)
|
|
targ->pain_debounce_time = level.time + 5;
|
|
}
|
|
}
|
|
else if (client)
|
|
{
|
|
if (!(targ->flags & FL_GODMODE) && (take))
|
|
targ->pain (targ, attacker, knockback, take, mdx_part, mdx_subobject);
|
|
}
|
|
else if (take)
|
|
{
|
|
if (targ->pain)
|
|
targ->pain (targ, attacker, knockback, take, mdx_part, mdx_subobject);
|
|
}
|
|
|
|
// 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_armor += asave;
|
|
if (mod == MOD_FLAMETHROWER)
|
|
client->damage_flame += take*4;
|
|
else
|
|
client->damage_blood += take;
|
|
client->damage_knockback += knockback;
|
|
VectorCopy (point, client->damage_from);
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
T_RadiusDamage
|
|
============
|
|
*/
|
|
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;
|
|
// int mdx_part, mdx_subobject;
|
|
|
|
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);
|
|
points = damage * (1.0 - VectorLength (v)/radius); // Ridah, changed this
|
|
|
|
if (ent == attacker)
|
|
{
|
|
if (mod == MOD_FLAMETHROWER) // don't damage self from flames, only if really close to wall, in which case we do a T_Damage()
|
|
continue;
|
|
|
|
// points = points * 0.5;
|
|
}
|
|
|
|
// JOSEPH 26-MAY-99
|
|
if ((ent->svflags & SVF_MONSTER) && inflictor->classname &&
|
|
(!strcmp(inflictor->classname, "grenade")) &&
|
|
attacker && (attacker->svflags & SVF_MONSTER))
|
|
{
|
|
points *= 0.5;
|
|
}
|
|
// END JOSEPH
|
|
|
|
if (points > 0)
|
|
{
|
|
if (CanDamage (ent, inflictor))
|
|
{
|
|
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
|
|
|
|
if (mod == MOD_FLAMETHROWER && ent->health > 0 && (ent->svflags & SVF_MONSTER || ent->client))
|
|
{
|
|
|
|
// JOSEPH 29-MAY-99
|
|
if (ent->onfiretime < -20)
|
|
{
|
|
ent->onfiretime = 1;
|
|
|
|
if (ent->svflags & SVF_MONSTER)
|
|
ent->cast_info.aiflags &= ~AI_NO_TALK;
|
|
|
|
if (ent->gender == GENDER_FEMALE)
|
|
{
|
|
if (ent->health > 80)
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 8);
|
|
}
|
|
else if (ent->health > 40)
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 7);
|
|
}
|
|
else
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 6);
|
|
}
|
|
}
|
|
else if (ent->gender == GENDER_MALE)
|
|
{
|
|
if (ent->health > 80)
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 12);
|
|
}
|
|
else if (ent->health > 40)
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 11);
|
|
}
|
|
else
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 10);
|
|
}
|
|
}
|
|
|
|
if (ent->svflags & SVF_MONSTER)
|
|
ent->cast_info.aiflags |= AI_NO_TALK;
|
|
// if (deathmatch->value)
|
|
{ // play the sound on different channels so it's less likely to get over-written
|
|
if (ent->last_talk_time == level.time && ent->last_voice->soundindex)
|
|
{
|
|
gi.sound( ent, CHAN_SPECIAL | CHAN_RELIABLE, ent->last_voice->soundindex-1, 1.0, 2, 0 );
|
|
// gi.sound( ent, CHAN_BODY | CHAN_RELIABLE, ent->last_voice->soundindex-1, 1.0, 2, 0 );
|
|
}
|
|
}
|
|
|
|
ent->pain_debounce_time = level.time + 5;
|
|
|
|
if (ent->svflags & SVF_MONSTER)
|
|
ent->cast_info.aiflags |= AI_NO_TALK; // can't talk anymore, burning
|
|
}
|
|
|
|
if (ent->onfiretime > 0)
|
|
{
|
|
if (attacker->client || attacker->svflags & SVF_MONSTER)
|
|
ent->onfireent = attacker;
|
|
else
|
|
ent->onfireent = NULL;
|
|
|
|
if (deathmatch->value && (ent->pain_debounce_time < (level.time+3)))
|
|
{
|
|
if (ent->gender == GENDER_FEMALE)
|
|
{
|
|
if (ent->health > 80)
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 8);
|
|
}
|
|
else if (ent->health > 40)
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 7);
|
|
}
|
|
else
|
|
{
|
|
Voice_Specific (ent, attacker, female_specific, 6);
|
|
}
|
|
}
|
|
else if (ent->gender == GENDER_MALE)
|
|
{
|
|
if (ent->health > 80)
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 12);
|
|
}
|
|
else if (ent->health > 40)
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 11);
|
|
}
|
|
else
|
|
{
|
|
Voice_Specific (ent, attacker, male_specific, 10);
|
|
}
|
|
}
|
|
|
|
if (deathmatch->value)
|
|
{ // play the sound on different channels so it's less likely to get over-written
|
|
if (ent->last_talk_time == level.time && ent->last_voice->soundindex)
|
|
{
|
|
gi.sound( ent, CHAN_SPECIAL | CHAN_RELIABLE, ent->last_voice->soundindex-1, 1.0, 2, 0 );
|
|
// gi.sound( ent, CHAN_ITEM | CHAN_RELIABLE, ent->last_voice->soundindex-1, 1.0, 2, 0 );
|
|
// gi.sound( ent, CHAN_BODY | CHAN_RELIABLE, ent->last_voice->soundindex-1, 1.0, 2, 0 );
|
|
}
|
|
}
|
|
|
|
ent->pain_debounce_time = level.time + 5;
|
|
}
|
|
// END JOSEPH
|
|
|
|
ent->onfiretime = 50;
|
|
|
|
if (ent->client)
|
|
{
|
|
ent->onfiretime = 25;
|
|
|
|
// Ridah, in Deathmatch, you should be rewarded for flaming them, not so much just setting them on fire and waiting for them to burn to death
|
|
if (deathmatch->value)
|
|
{
|
|
ent->onfiretime = 10;
|
|
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
}
|
|
|
|
if (ent->cast_info.catch_fire)
|
|
{
|
|
ent->cast_info.catch_fire( ent, attacker );
|
|
}
|
|
|
|
if (!ent->client)
|
|
ent->s.renderfx2 &= ~RF2_DIR_LIGHTS;
|
|
}
|
|
else
|
|
{
|
|
ent->onfiretime -= (int) (3.0 * (1.0 - VectorLength (v)/radius) * (float)((deathmatch->value!=0)*2 + 1));
|
|
}
|
|
}
|
|
else // do the damage as normal
|
|
{
|
|
T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// JOSEPH 21-APR-99
|
|
/*
|
|
============
|
|
T_RadiusDamage
|
|
============
|
|
*/
|
|
void T_RadiusDamage_Fire (edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius)
|
|
{
|
|
float points;
|
|
edict_t *ent = NULL;
|
|
vec3_t v;
|
|
vec3_t dir;
|
|
int mod = MOD_FLAMETHROWER;
|
|
|
|
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 * (1.0 - VectorLength (v)/radius);
|
|
//points = damage;
|
|
|
|
if (points > 0)
|
|
{
|
|
if (CanDamage (ent, inflictor))
|
|
{
|
|
VectorSubtract (ent->s.origin, inflictor->s.origin, dir);
|
|
|
|
if (ent->health > 0 && (ent->svflags & SVF_MONSTER || ent->client))
|
|
{
|
|
T_Damage (ent, inflictor, attacker, vec3_origin, ent->s.origin, vec3_origin, points, points, 0, MOD_FLAMETHROWER);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// END JOSEPH
|
|
|
|
edict_t *SpawnTheWeapon (edict_t *self, char *var_itemname)
|
|
{
|
|
edict_t *drop = NULL;
|
|
int model;
|
|
qboolean e_weapon = false;
|
|
char itemname[MAX_QPATH];
|
|
|
|
strcpy( itemname, var_itemname );
|
|
|
|
if (!(strcmp (itemname, "weapon_pistol_e")))
|
|
{
|
|
strcpy (itemname, "weapon_pistol");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_pistol/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_shotgun_e")))
|
|
{
|
|
strcpy (itemname, "weapon_shotgun");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_shotgun/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_heavymachinegun_e")))
|
|
{
|
|
strcpy (itemname, "weapon_heavymachinegun");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_hmg/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_bazooka_e")))
|
|
{
|
|
strcpy (itemname, "weapon_bazooka");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_rocket_launcher/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_flamethrower_e")))
|
|
{
|
|
strcpy (itemname, "weapon_flamethrower");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_flamegun/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_grenadelauncher_e")))
|
|
{
|
|
strcpy (itemname, "weapon_grenadelauncher");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_grenade_launcher/tris.md2");
|
|
}
|
|
else if (!(strcmp (itemname, "weapon_tommygun_e")))
|
|
{
|
|
strcpy (itemname, "weapon_tommygun");
|
|
e_weapon = true;
|
|
model = gi.modelindex("models/weapons/e_tomgun/tris.md2");
|
|
}
|
|
|
|
drop = Drop_Item (self, FindItemByClassname (itemname));
|
|
|
|
if (e_weapon)
|
|
drop->s.modelindex = model;
|
|
|
|
if (drop)
|
|
drop->spawnflags &= ~DROPPED_ITEM;
|
|
|
|
return drop;
|
|
}
|