You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1720 lines
44 KiB
1720 lines
44 KiB
/*** |
|
* |
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved. |
|
* |
|
* This product contains software technology licensed from Id |
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. |
|
* All Rights Reserved. |
|
* |
|
* Use, distribution, and modification of this source code and/or resulting |
|
* object code is restricted to non-commercial enhancements to products from |
|
* Valve LLC. All other use, distribution, or modification is prohibited |
|
* without written permission from Valve LLC. |
|
* |
|
****/ |
|
/* |
|
|
|
===== combat.cpp ======================================================== |
|
|
|
functions dealing with damage infliction & death |
|
|
|
*/ |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "monsters.h" |
|
#include "soundent.h" |
|
#include "decals.h" |
|
#include "animation.h" |
|
#include "weapons.h" |
|
#include "func_break.h" |
|
#include "player.h" |
|
|
|
extern DLL_GLOBAL Vector g_vecAttackDir; |
|
extern DLL_GLOBAL int g_iSkillLevel; |
|
|
|
extern Vector VecBModelOrigin( entvars_t *pevBModel ); |
|
extern entvars_t *g_pevLastInflictor; |
|
|
|
#define GERMAN_GIB_COUNT 4 |
|
#define HUMAN_GIB_COUNT 6 |
|
#define ALIEN_GIB_COUNT 4 |
|
|
|
|
|
// HACKHACK -- The gib velocity equations don't work |
|
void CGib::LimitVelocity( void ) |
|
{ |
|
float length = pev->velocity.Length(); |
|
|
|
// ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it |
|
// in 3 separate places again, I'll just limit it here. |
|
if( length > 1500.0 ) |
|
pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something |
|
} |
|
|
|
|
|
void CGib::SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) |
|
{ |
|
int i; |
|
|
|
if( g_Language == LANGUAGE_GERMAN ) |
|
{ |
|
// no sticky gibs in germany right now! |
|
return; |
|
} |
|
|
|
for( i = 0; i < cGibs; i++ ) |
|
{ |
|
CGib *pGib = GetClassPtr( (CGib *)NULL ); |
|
|
|
pGib->Spawn( "models/stickygib.mdl" ); |
|
pGib->pev->body = RANDOM_LONG( 0, 2 ); |
|
|
|
if( pevVictim ) |
|
{ |
|
pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); |
|
pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); |
|
pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); |
|
|
|
/* |
|
pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * ( RANDOM_FLOAT( 0, 1 ) ); |
|
pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * ( RANDOM_FLOAT( 0, 1 ) ); |
|
pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * ( RANDOM_FLOAT( 0, 1 ) ); |
|
*/ |
|
|
|
// make the gib fly away from the attack vector |
|
pGib->pev->velocity = g_vecAttackDir * -1; |
|
|
|
// mix in some noise |
|
pGib->pev->velocity.x += RANDOM_FLOAT( -0.15, 0.15 ); |
|
pGib->pev->velocity.y += RANDOM_FLOAT( -0.15, 0.15 ); |
|
pGib->pev->velocity.z += RANDOM_FLOAT( -0.15, 0.15 ); |
|
|
|
pGib->pev->velocity = pGib->pev->velocity * 900; |
|
|
|
pGib->pev->avelocity.x = RANDOM_FLOAT( 250, 400 ); |
|
pGib->pev->avelocity.y = RANDOM_FLOAT( 250, 400 ); |
|
|
|
// copy owner's blood color |
|
pGib->m_bloodColor = ( CBaseEntity::Instance( pevVictim ) )->BloodColor(); |
|
|
|
if( pevVictim->health > -50 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 0.7; |
|
} |
|
else if( pevVictim->health > -200 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 2; |
|
} |
|
else |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 4; |
|
} |
|
|
|
pGib->pev->movetype = MOVETYPE_TOSS; |
|
pGib->pev->solid = SOLID_BBOX; |
|
UTIL_SetSize( pGib->pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); |
|
pGib->SetTouch( &CGib::StickyGibTouch ); |
|
pGib->SetThink( NULL ); |
|
} |
|
pGib->LimitVelocity(); |
|
} |
|
} |
|
|
|
void CGib::SpawnHeadGib( entvars_t *pevVictim ) |
|
{ |
|
CGib *pGib = GetClassPtr( (CGib *)NULL ); |
|
|
|
if( g_Language == LANGUAGE_GERMAN ) |
|
{ |
|
pGib->Spawn( "models/germangibs.mdl" );// throw one head |
|
pGib->pev->body = 0; |
|
} |
|
else |
|
{ |
|
pGib->Spawn( "models/hgibs.mdl" );// throw one head |
|
pGib->pev->body = 0; |
|
} |
|
|
|
if( pevVictim ) |
|
{ |
|
pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; |
|
|
|
edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); |
|
|
|
if( RANDOM_LONG( 0, 100 ) <= 5 && pentPlayer ) |
|
{ |
|
// 5% chance head will be thrown at player's face. |
|
entvars_t *pevPlayer; |
|
|
|
pevPlayer = VARS( pentPlayer ); |
|
pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; |
|
pGib->pev->velocity.z += 100; |
|
} |
|
else |
|
{ |
|
pGib->pev->velocity = Vector( RANDOM_FLOAT( -100, 100 ), RANDOM_FLOAT( -100, 100 ), RANDOM_FLOAT( 200, 300 ) ); |
|
} |
|
|
|
pGib->pev->avelocity.x = RANDOM_FLOAT( 100, 200 ); |
|
pGib->pev->avelocity.y = RANDOM_FLOAT( 100, 300 ); |
|
|
|
// copy owner's blood color |
|
pGib->m_bloodColor = ( CBaseEntity::Instance( pevVictim ) )->BloodColor(); |
|
|
|
if( pevVictim->health > -50 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 0.7; |
|
} |
|
else if( pevVictim->health > -200 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 2; |
|
} |
|
else |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 4; |
|
} |
|
} |
|
pGib->LimitVelocity(); |
|
} |
|
|
|
void CGib::SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) |
|
{ |
|
int cSplat; |
|
|
|
for( cSplat = 0; cSplat < cGibs; cSplat++ ) |
|
{ |
|
CGib *pGib = GetClassPtr( (CGib *)NULL ); |
|
|
|
if( g_Language == LANGUAGE_GERMAN ) |
|
{ |
|
pGib->Spawn( "models/germangibs.mdl" ); |
|
pGib->pev->body = RANDOM_LONG( 0, GERMAN_GIB_COUNT - 1 ); |
|
} |
|
else |
|
{ |
|
if( human ) |
|
{ |
|
// human pieces |
|
pGib->Spawn( "models/hgibs.mdl" ); |
|
pGib->pev->body = RANDOM_LONG( 1, HUMAN_GIB_COUNT - 1 );// start at one to avoid throwing random amounts of skulls (0th gib) |
|
} |
|
else |
|
{ |
|
// aliens |
|
pGib->Spawn( "models/agibs.mdl" ); |
|
pGib->pev->body = RANDOM_LONG( 0, ALIEN_GIB_COUNT - 1 ); |
|
} |
|
} |
|
|
|
if( pevVictim ) |
|
{ |
|
// spawn the gib somewhere in the monster's bounding volume |
|
pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); |
|
pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); |
|
pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box |
|
|
|
// make the gib fly away from the attack vector |
|
pGib->pev->velocity = g_vecAttackDir * -1; |
|
|
|
// mix in some noise |
|
pGib->pev->velocity.x += RANDOM_FLOAT( -0.25, 0.25 ); |
|
pGib->pev->velocity.y += RANDOM_FLOAT( -0.25, 0.25 ); |
|
pGib->pev->velocity.z += RANDOM_FLOAT( -0.25, 0.25 ); |
|
|
|
pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT( 300, 400 ); |
|
|
|
pGib->pev->avelocity.x = RANDOM_FLOAT( 100, 200 ); |
|
pGib->pev->avelocity.y = RANDOM_FLOAT( 100, 300 ); |
|
|
|
// copy owner's blood color |
|
pGib->m_bloodColor = ( CBaseEntity::Instance( pevVictim ) )->BloodColor(); |
|
|
|
if( pevVictim->health > -50 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 0.7; |
|
} |
|
else if( pevVictim->health > -200 ) |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 2; |
|
} |
|
else |
|
{ |
|
pGib->pev->velocity = pGib->pev->velocity * 4; |
|
} |
|
|
|
pGib->pev->solid = SOLID_BBOX; |
|
UTIL_SetSize( pGib->pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); |
|
} |
|
pGib->LimitVelocity(); |
|
} |
|
} |
|
|
|
BOOL CBaseMonster::HasHumanGibs( void ) |
|
{ |
|
int myClass = Classify(); |
|
|
|
if( myClass == CLASS_HUMAN_MILITARY || |
|
myClass == CLASS_PLAYER_ALLY || |
|
myClass == CLASS_HUMAN_PASSIVE || |
|
myClass == CLASS_PLAYER ) |
|
|
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
BOOL CBaseMonster::HasAlienGibs( void ) |
|
{ |
|
int myClass = Classify(); |
|
|
|
if( myClass == CLASS_ALIEN_MILITARY || |
|
myClass == CLASS_ALIEN_MONSTER || |
|
myClass == CLASS_ALIEN_PASSIVE || |
|
myClass == CLASS_INSECT || |
|
myClass == CLASS_ALIEN_PREDATOR || |
|
myClass == CLASS_ALIEN_PREY ) |
|
|
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
void CBaseMonster::FadeMonster( void ) |
|
{ |
|
StopAnimation(); |
|
pev->velocity = g_vecZero; |
|
pev->movetype = MOVETYPE_NONE; |
|
pev->avelocity = g_vecZero; |
|
pev->animtime = gpGlobals->time; |
|
pev->effects |= EF_NOINTERP; |
|
SUB_StartFadeOut(); |
|
} |
|
|
|
//========================================================= |
|
// GibMonster - create some gore and get rid of a monster's |
|
// model. |
|
//========================================================= |
|
void CBaseMonster::GibMonster( void ) |
|
{ |
|
TraceResult tr; |
|
BOOL gibbed = FALSE; |
|
|
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM ); |
|
|
|
// only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs |
|
if( HasHumanGibs() ) |
|
{ |
|
if( CVAR_GET_FLOAT( "violence_hgibs" ) != 0 ) // Only the player will ever get here |
|
{ |
|
CGib::SpawnHeadGib( pev ); |
|
CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. |
|
} |
|
gibbed = TRUE; |
|
} |
|
else if( HasAlienGibs() ) |
|
{ |
|
if( CVAR_GET_FLOAT( "violence_agibs" ) != 0 ) // Should never get here, but someone might call it directly |
|
{ |
|
CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs |
|
} |
|
gibbed = TRUE; |
|
} |
|
|
|
if( !IsPlayer() ) |
|
{ |
|
if( gibbed ) |
|
{ |
|
// don't remove players! |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time; |
|
} |
|
else |
|
{ |
|
FadeMonster(); |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// GetDeathActivity - determines the best type of death |
|
// anim to play. |
|
//========================================================= |
|
Activity CBaseMonster::GetDeathActivity( void ) |
|
{ |
|
Activity deathActivity; |
|
BOOL fTriedDirection; |
|
float flDot; |
|
TraceResult tr; |
|
Vector vecSrc; |
|
|
|
if( pev->deadflag != DEAD_NO ) |
|
{ |
|
// don't run this while dying. |
|
return m_IdealActivity; |
|
} |
|
|
|
vecSrc = Center(); |
|
|
|
fTriedDirection = FALSE; |
|
deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. |
|
|
|
UTIL_MakeVectors( pev->angles ); |
|
flDot = DotProduct( gpGlobals->v_forward, g_vecAttackDir * -1 ); |
|
|
|
switch( m_LastHitGroup ) |
|
{ |
|
// try to pick a region-specific death. |
|
case HITGROUP_HEAD: |
|
deathActivity = ACT_DIE_HEADSHOT; |
|
break; |
|
case HITGROUP_STOMACH: |
|
deathActivity = ACT_DIE_GUTSHOT; |
|
break; |
|
case HITGROUP_GENERIC: |
|
// try to pick a death based on attack direction |
|
fTriedDirection = TRUE; |
|
if( flDot > 0.3 ) |
|
{ |
|
deathActivity = ACT_DIEFORWARD; |
|
} |
|
else if( flDot <= -0.3 ) |
|
{ |
|
deathActivity = ACT_DIEBACKWARD; |
|
} |
|
break; |
|
default: |
|
// try to pick a death based on attack direction |
|
fTriedDirection = TRUE; |
|
|
|
if( flDot > 0.3 ) |
|
{ |
|
deathActivity = ACT_DIEFORWARD; |
|
} |
|
else if( flDot <= -0.3 ) |
|
{ |
|
deathActivity = ACT_DIEBACKWARD; |
|
} |
|
break; |
|
} |
|
|
|
// can we perform the prescribed death? |
|
if( LookupActivity( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
// no! did we fail to perform a directional death? |
|
if( fTriedDirection ) |
|
{ |
|
// if yes, we're out of options. Go simple. |
|
deathActivity = ACT_DIESIMPLE; |
|
} |
|
else |
|
{ |
|
// cannot perform the ideal region-specific death, so try a direction. |
|
if( flDot > 0.3 ) |
|
{ |
|
deathActivity = ACT_DIEFORWARD; |
|
} |
|
else if( flDot <= -0.3 ) |
|
{ |
|
deathActivity = ACT_DIEBACKWARD; |
|
} |
|
} |
|
} |
|
|
|
if( LookupActivity( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
// if we're still invalid, simple is our only option. |
|
deathActivity = ACT_DIESIMPLE; |
|
} |
|
|
|
if( deathActivity == ACT_DIEFORWARD ) |
|
{ |
|
// make sure there's room to fall forward |
|
UTIL_TraceHull( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); |
|
|
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
deathActivity = ACT_DIESIMPLE; |
|
} |
|
} |
|
|
|
if( deathActivity == ACT_DIEBACKWARD ) |
|
{ |
|
// make sure there's room to fall backward |
|
UTIL_TraceHull( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); |
|
|
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
deathActivity = ACT_DIESIMPLE; |
|
} |
|
} |
|
|
|
return deathActivity; |
|
} |
|
|
|
//========================================================= |
|
// GetSmallFlinchActivity - determines the best type of flinch |
|
// anim to play. |
|
//========================================================= |
|
Activity CBaseMonster::GetSmallFlinchActivity( void ) |
|
{ |
|
Activity flinchActivity; |
|
BOOL fTriedDirection; |
|
float flDot; |
|
|
|
fTriedDirection = FALSE; |
|
UTIL_MakeVectors( pev->angles ); |
|
flDot = DotProduct( gpGlobals->v_forward, g_vecAttackDir * -1 ); |
|
|
|
switch( m_LastHitGroup ) |
|
{ |
|
// pick a region-specific flinch |
|
case HITGROUP_HEAD: |
|
flinchActivity = ACT_FLINCH_HEAD; |
|
break; |
|
case HITGROUP_STOMACH: |
|
flinchActivity = ACT_FLINCH_STOMACH; |
|
break; |
|
case HITGROUP_LEFTARM: |
|
flinchActivity = ACT_FLINCH_LEFTARM; |
|
break; |
|
case HITGROUP_RIGHTARM: |
|
flinchActivity = ACT_FLINCH_RIGHTARM; |
|
break; |
|
case HITGROUP_LEFTLEG: |
|
flinchActivity = ACT_FLINCH_LEFTLEG; |
|
break; |
|
case HITGROUP_RIGHTLEG: |
|
flinchActivity = ACT_FLINCH_RIGHTLEG; |
|
break; |
|
case HITGROUP_GENERIC: |
|
default: |
|
// just get a generic flinch. |
|
flinchActivity = ACT_SMALL_FLINCH; |
|
break; |
|
} |
|
|
|
// do we have a sequence for the ideal activity? |
|
if( LookupActivity( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
flinchActivity = ACT_SMALL_FLINCH; |
|
} |
|
|
|
return flinchActivity; |
|
} |
|
|
|
|
|
void CBaseMonster::BecomeDead( void ) |
|
{ |
|
pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. |
|
|
|
// give the corpse half of the monster's original maximum health. |
|
pev->health = pev->max_health / 2; |
|
pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. |
|
|
|
// make the corpse fly away from the attack vector |
|
pev->movetype = MOVETYPE_TOSS; |
|
//pev->flags &= ~FL_ONGROUND; |
|
//pev->origin.z += 2; |
|
//pev->velocity = g_vecAttackDir * -1; |
|
//pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); |
|
} |
|
|
|
BOOL CBaseMonster::ShouldGibMonster( int iGib ) |
|
{ |
|
if( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) |
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
void CBaseMonster::CallGibMonster( void ) |
|
{ |
|
BOOL fade = FALSE; |
|
|
|
if( HasHumanGibs() ) |
|
{ |
|
if( CVAR_GET_FLOAT( "violence_hgibs" ) == 0 ) |
|
fade = TRUE; |
|
} |
|
else if( HasAlienGibs() ) |
|
{ |
|
if( CVAR_GET_FLOAT( "violence_agibs" ) == 0 ) |
|
fade = TRUE; |
|
} |
|
|
|
pev->takedamage = DAMAGE_NO; |
|
pev->solid = SOLID_NOT;// do something with the body. while monster blows up |
|
|
|
if( fade ) |
|
{ |
|
FadeMonster(); |
|
} |
|
else |
|
{ |
|
pev->effects = EF_NODRAW; // make the model invisible. |
|
GibMonster(); |
|
} |
|
|
|
pev->deadflag = DEAD_DEAD; |
|
FCheckAITrigger(); |
|
|
|
// don't let the status bar glitch for players.with <0 health. |
|
if( pev->health < -99 ) |
|
{ |
|
pev->health = 0; |
|
} |
|
|
|
if( ShouldFadeOnDeath() && !fade ) |
|
UTIL_Remove( this ); |
|
} |
|
|
|
/* |
|
============ |
|
Killed |
|
============ |
|
*/ |
|
void CBaseMonster::Killed( entvars_t *pevAttacker, int iGib ) |
|
{ |
|
unsigned int cCount = 0; |
|
BOOL fDone = FALSE; |
|
|
|
if( HasMemory( bits_MEMORY_KILLED ) ) |
|
{ |
|
if( ShouldGibMonster( iGib ) ) |
|
CallGibMonster(); |
|
return; |
|
} |
|
|
|
Remember( bits_MEMORY_KILLED ); |
|
|
|
// clear the deceased's sound channels.(may have been firing or reloading when killed) |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM ); |
|
m_IdealMonsterState = MONSTERSTATE_DEAD; |
|
// Make sure this condition is fired too (TakeDamage breaks out before this happens on death) |
|
SetConditions( bits_COND_LIGHT_DAMAGE ); |
|
|
|
// tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. |
|
CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); |
|
if( pOwner ) |
|
{ |
|
pOwner->DeathNotice( pev ); |
|
} |
|
|
|
if( ShouldGibMonster( iGib ) ) |
|
{ |
|
CallGibMonster(); |
|
return; |
|
} |
|
else if( pev->flags & FL_MONSTER ) |
|
{ |
|
SetTouch( NULL ); |
|
BecomeDead(); |
|
} |
|
|
|
// don't let the status bar glitch for players.with <0 health. |
|
if( pev->health < -99 ) |
|
{ |
|
pev->health = 0; |
|
} |
|
|
|
//pev->enemy = ENT( pevAttacker );//why? (sjb) |
|
|
|
m_IdealMonsterState = MONSTERSTATE_DEAD; |
|
} |
|
|
|
// |
|
// fade out - slowly fades a entity out, then removes it. |
|
// |
|
// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! |
|
// SET A FUTURE THINK AND A RENDERMODE!! |
|
void CBaseEntity::SUB_StartFadeOut( void ) |
|
{ |
|
if( pev->rendermode == kRenderNormal ) |
|
{ |
|
pev->renderamt = 255; |
|
pev->rendermode = kRenderTransTexture; |
|
} |
|
|
|
pev->solid = SOLID_NOT; |
|
pev->avelocity = g_vecZero; |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
SetThink( &CBaseEntity::SUB_FadeOut ); |
|
} |
|
|
|
void CBaseEntity::SUB_FadeOut( void ) |
|
{ |
|
if( pev->renderamt > 7 ) |
|
{ |
|
pev->renderamt -= 7; |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
else |
|
{ |
|
pev->renderamt = 0; |
|
pev->nextthink = gpGlobals->time + 0.2; |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// WaitTillLand - in order to emit their meaty scent from |
|
// the proper location, gibs should wait until they stop |
|
// bouncing to emit their scent. That's what this function |
|
// does. |
|
//========================================================= |
|
void CGib::WaitTillLand( void ) |
|
{ |
|
if( !IsInWorld() ) |
|
{ |
|
UTIL_Remove( this ); |
|
return; |
|
} |
|
|
|
if( pev->velocity == g_vecZero ) |
|
{ |
|
SetThink( &CBaseEntity::SUB_StartFadeOut ); |
|
pev->nextthink = gpGlobals->time + m_lifeTime; |
|
|
|
// If you bleed, you stink! |
|
if( m_bloodColor != DONT_BLEED ) |
|
{ |
|
// ok, start stinkin! |
|
CSoundEnt::InsertSound( bits_SOUND_MEAT, pev->origin, 384, 25 ); |
|
} |
|
} |
|
else |
|
{ |
|
// wait and check again in another half second. |
|
pev->nextthink = gpGlobals->time + 0.5; |
|
} |
|
} |
|
|
|
// |
|
// Gib bounces on the ground or wall, sponges some blood down, too! |
|
// |
|
void CGib::BounceGibTouch( CBaseEntity *pOther ) |
|
{ |
|
Vector vecSpot; |
|
TraceResult tr; |
|
|
|
//if( RANDOM_LONG( 0, 1 ) ) |
|
// return;// don't bleed everytime |
|
|
|
if( pev->flags & FL_ONGROUND ) |
|
{ |
|
pev->velocity = pev->velocity * 0.9; |
|
pev->angles.x = 0; |
|
pev->angles.z = 0; |
|
pev->avelocity.x = 0; |
|
pev->avelocity.z = 0; |
|
} |
|
else |
|
{ |
|
if( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) |
|
{ |
|
vecSpot = pev->origin + Vector( 0, 0, 8 );//move up a bit, and trace down. |
|
UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -24 ), ignore_monsters, ENT( pev ), &tr ); |
|
|
|
UTIL_BloodDecalTrace( &tr, m_bloodColor ); |
|
|
|
m_cBloodDecals--; |
|
} |
|
|
|
if( m_material != matNone && RANDOM_LONG( 0, 2 ) == 0 ) |
|
{ |
|
float volume; |
|
float zvel = fabs( pev->velocity.z ); |
|
|
|
volume = 0.8 * min( 1.0, ( (float)zvel ) / 450.0 ); |
|
|
|
CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); |
|
} |
|
} |
|
} |
|
|
|
// |
|
// Sticky gib puts blood on the wall and stays put. |
|
// |
|
void CGib::StickyGibTouch( CBaseEntity *pOther ) |
|
{ |
|
Vector vecSpot; |
|
TraceResult tr; |
|
|
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + 10; |
|
|
|
if( !FClassnameIs( pOther->pev, "worldspawn" ) ) |
|
{ |
|
pev->nextthink = gpGlobals->time; |
|
return; |
|
} |
|
|
|
UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT( pev ), &tr ); |
|
|
|
UTIL_BloodDecalTrace( &tr, m_bloodColor ); |
|
|
|
pev->velocity = tr.vecPlaneNormal * -1; |
|
pev->angles = UTIL_VecToAngles( pev->velocity ); |
|
pev->velocity = g_vecZero; |
|
pev->avelocity = g_vecZero; |
|
pev->movetype = MOVETYPE_NONE; |
|
} |
|
|
|
// |
|
// Throw a chunk |
|
// |
|
void CGib::Spawn( const char *szGibModel ) |
|
{ |
|
pev->movetype = MOVETYPE_BOUNCE; |
|
pev->friction = 0.55; // deading the bounce a bit |
|
|
|
// sometimes an entity inherits the edict from a former piece of glass, |
|
// and will spawn using the same render FX or rendermode! bad! |
|
pev->renderamt = 255; |
|
pev->rendermode = kRenderNormal; |
|
pev->renderfx = kRenderFxNone; |
|
pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap |
|
pev->classname = MAKE_STRING( "gib" ); |
|
|
|
SET_MODEL( ENT( pev ), szGibModel ); |
|
UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); |
|
|
|
pev->nextthink = gpGlobals->time + 4; |
|
m_lifeTime = 25; |
|
SetThink( &CGib::WaitTillLand ); |
|
SetTouch( &CGib::BounceGibTouch ); |
|
|
|
m_material = matNone; |
|
m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). |
|
} |
|
|
|
// take health |
|
int CBaseMonster::TakeHealth( float flHealth, int bitsDamageType ) |
|
{ |
|
if( !pev->takedamage ) |
|
return 0; |
|
|
|
// clear out any damage types we healed. |
|
// UNDONE: generic health should not heal any |
|
// UNDONE: time-based damage |
|
|
|
m_bitsDamageType &= ~( bitsDamageType & ~DMG_TIMEBASED ); |
|
|
|
return CBaseEntity::TakeHealth( flHealth, bitsDamageType ); |
|
} |
|
|
|
/* |
|
============ |
|
TakeDamage |
|
|
|
The damage is coming from inflictor, but get mad at attacker |
|
This should be the only function that ever reduces health. |
|
bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK |
|
|
|
Time-based damage: only occurs while the monster is within the trigger_hurt. |
|
When a monster is poisoned via an arrow etc it takes all the poison damage at once. |
|
|
|
GLOBALS ASSUMED SET: g_iSkillLevel |
|
============ |
|
*/ |
|
int CBaseMonster::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) |
|
{ |
|
float flTake; |
|
Vector vecDir; |
|
|
|
if( !pev->takedamage ) |
|
return 0; |
|
|
|
if( !IsAlive() ) |
|
{ |
|
return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); |
|
} |
|
|
|
if( pev->deadflag == DEAD_NO ) |
|
{ |
|
// no pain sound during death animation. |
|
PainSound();// "Ouch!" |
|
} |
|
|
|
//!!!LATER - make armor consideration here! |
|
flTake = flDamage; |
|
|
|
// set damage type sustained |
|
m_bitsDamageType |= bitsDamageType; |
|
|
|
// grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). |
|
vecDir = Vector( 0, 0, 0 ); |
|
if( !FNullEnt( pevInflictor ) ) |
|
{ |
|
CBaseEntity *pInflictor = CBaseEntity::Instance( pevInflictor ); |
|
if( pInflictor ) |
|
{ |
|
vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); |
|
vecDir = g_vecAttackDir = vecDir.Normalize(); |
|
} |
|
} |
|
|
|
// add to the damage total for clients, which will be sent as a single |
|
// message at the end of the frame |
|
// todo: remove after combining shotgun blasts? |
|
if( IsPlayer() ) |
|
{ |
|
if( pevInflictor ) |
|
pev->dmg_inflictor = ENT( pevInflictor ); |
|
|
|
pev->dmg_take += flTake; |
|
|
|
// check for godmode or invincibility |
|
if( pev->flags & FL_GODMODE ) |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
// if this is a player, move him around! |
|
if( ( !FNullEnt( pevInflictor ) ) && ( pev->movetype == MOVETYPE_WALK ) && ( !pevAttacker || pevAttacker->solid != SOLID_TRIGGER ) ) |
|
{ |
|
pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); |
|
} |
|
|
|
// do the damage |
|
pev->health -= flTake; |
|
|
|
// HACKHACK Don't kill monsters in a script. Let them break their scripts first |
|
if( m_MonsterState == MONSTERSTATE_SCRIPT ) |
|
{ |
|
SetConditions( bits_COND_LIGHT_DAMAGE ); |
|
return 0; |
|
} |
|
|
|
if( pev->health <= 0 ) |
|
{ |
|
g_pevLastInflictor = pevInflictor; |
|
|
|
if( bitsDamageType & DMG_ALWAYSGIB ) |
|
{ |
|
Killed( pevAttacker, GIB_ALWAYS ); |
|
} |
|
else if( bitsDamageType & DMG_NEVERGIB ) |
|
{ |
|
Killed( pevAttacker, GIB_NEVER ); |
|
} |
|
else |
|
{ |
|
Killed( pevAttacker, GIB_NORMAL ); |
|
} |
|
|
|
g_pevLastInflictor = NULL; |
|
|
|
return 0; |
|
} |
|
|
|
// react to the damage (get mad) |
|
if( ( pev->flags & FL_MONSTER ) && !FNullEnt( pevAttacker ) ) |
|
{ |
|
if( pevAttacker->flags & ( FL_MONSTER | FL_CLIENT ) ) |
|
{ |
|
// only if the attack was a monster or client! |
|
// enemy's last known position is somewhere down the vector that the attack came from. |
|
if( pevInflictor ) |
|
{ |
|
if( m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions( bits_COND_SEE_ENEMY ) ) |
|
{ |
|
m_vecEnemyLKP = pevInflictor->origin; |
|
} |
|
} |
|
else |
|
{ |
|
m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); |
|
} |
|
|
|
MakeIdealYaw( m_vecEnemyLKP ); |
|
|
|
// add pain to the conditions |
|
// !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and |
|
// heavy damage per monster class? |
|
if( flDamage > 0 ) |
|
{ |
|
SetConditions( bits_COND_LIGHT_DAMAGE ); |
|
} |
|
|
|
if( flDamage >= 20 ) |
|
{ |
|
SetConditions( bits_COND_HEAVY_DAMAGE ); |
|
} |
|
} |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
//========================================================= |
|
// DeadTakeDamage - takedamage function called when a monster's |
|
// corpse is damaged. |
|
//========================================================= |
|
int CBaseMonster::DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) |
|
{ |
|
Vector vecDir; |
|
|
|
// grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). |
|
vecDir = Vector( 0, 0, 0 ); |
|
if( !FNullEnt( pevInflictor ) ) |
|
{ |
|
CBaseEntity *pInflictor = CBaseEntity::Instance( pevInflictor ); |
|
if( pInflictor ) |
|
{ |
|
vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); |
|
vecDir = g_vecAttackDir = vecDir.Normalize(); |
|
} |
|
} |
|
|
|
#if 0// turn this back on when the bounding box issues are resolved. |
|
|
|
pev->flags &= ~FL_ONGROUND; |
|
pev->origin.z += 1; |
|
|
|
// let the damage scoot the corpse around a bit. |
|
if( !FNullEnt( pevInflictor ) && ( pevAttacker->solid != SOLID_TRIGGER ) ) |
|
{ |
|
pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); |
|
} |
|
#endif |
|
// kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. |
|
if( bitsDamageType & DMG_GIB_CORPSE ) |
|
{ |
|
if( pev->health <= flDamage ) |
|
{ |
|
pev->health = -50; |
|
Killed( pevAttacker, GIB_ALWAYS ); |
|
return 0; |
|
} |
|
// Accumulate corpse gibbing damage, so you can gib with multiple hits |
|
pev->health -= flDamage * 0.1; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
float CBaseMonster::DamageForce( float damage ) |
|
{ |
|
float force = damage * ( ( 32 * 32 * 72.0 ) / ( pev->size.x * pev->size.y * pev->size.z ) ) * 5; |
|
|
|
if( force > 1000.0 ) |
|
{ |
|
force = 1000.0; |
|
} |
|
|
|
return force; |
|
} |
|
|
|
// |
|
// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. |
|
// |
|
// only damage ents that can clearly be seen by the explosion! |
|
void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
TraceResult tr; |
|
float flAdjustedDamage, falloff; |
|
Vector vecSpot; |
|
|
|
if( flRadius ) |
|
falloff = flDamage / flRadius; |
|
else |
|
falloff = 1.0; |
|
|
|
int bInWater = ( UTIL_PointContents( vecSrc ) == CONTENTS_WATER ); |
|
|
|
vecSrc.z += 1;// in case grenade is lying on the ground |
|
|
|
if( !pevAttacker ) |
|
pevAttacker = pevInflictor; |
|
|
|
// iterate on all entities in the vicinity. |
|
while( ( pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius ) ) != NULL ) |
|
{ |
|
if( pEntity->pev->takedamage != DAMAGE_NO ) |
|
{ |
|
// UNDONE: this should check a damage mask, not an ignore |
|
if( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) |
|
{ |
|
// houndeyes don't hurt other houndeyes with their attack |
|
continue; |
|
} |
|
|
|
// blast's don't tavel into or out of water |
|
if( bInWater && pEntity->pev->waterlevel == 0 ) |
|
continue; |
|
if( !bInWater && pEntity->pev->waterlevel == 3 ) |
|
continue; |
|
|
|
vecSpot = pEntity->BodyTarget( vecSrc ); |
|
|
|
UTIL_TraceLine( vecSrc, vecSpot, dont_ignore_monsters, ENT( pevInflictor ), &tr ); |
|
|
|
if( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) |
|
{ |
|
// the explosion can 'see' this entity, so hurt them! |
|
if( tr.fStartSolid ) |
|
{ |
|
// if we're stuck inside them, fixup the position and distance |
|
tr.vecEndPos = vecSrc; |
|
tr.flFraction = 0.0; |
|
} |
|
|
|
// decrease damage for an ent that's farther from the bomb. |
|
flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; |
|
flAdjustedDamage = flDamage - flAdjustedDamage; |
|
|
|
if( flAdjustedDamage < 0 ) |
|
{ |
|
flAdjustedDamage = 0; |
|
} |
|
|
|
// ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); |
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
ClearMultiDamage(); |
|
pEntity->TraceAttack( pevInflictor, flAdjustedDamage, ( tr.vecEndPos - vecSrc ).Normalize(), &tr, bitsDamageType ); |
|
ApplyMultiDamage( pevInflictor, pevAttacker ); |
|
} |
|
else |
|
{ |
|
pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CBaseMonster::RadiusDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) |
|
{ |
|
::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); |
|
} |
|
|
|
void CBaseMonster::RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) |
|
{ |
|
::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); |
|
} |
|
|
|
//========================================================= |
|
// CheckTraceHullAttack - expects a length to trace, amount |
|
// of damage to do, and damage type. Returns a pointer to |
|
// the damaged entity in case the monster wishes to do |
|
// other stuff to the victim (punchangle, etc) |
|
// |
|
// Used for many contact-range melee attacks. Bites, claws, etc. |
|
//========================================================= |
|
CBaseEntity* CBaseMonster::CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) |
|
{ |
|
TraceResult tr; |
|
|
|
if( IsPlayer() ) |
|
UTIL_MakeVectors( pev->angles ); |
|
else |
|
UTIL_MakeAimVectors( pev->angles ); |
|
|
|
Vector vecStart = pev->origin; |
|
vecStart.z += pev->size.z * 0.5; |
|
Vector vecEnd = vecStart + ( gpGlobals->v_forward * flDist ); |
|
|
|
UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); |
|
|
|
if( tr.pHit ) |
|
{ |
|
CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); |
|
|
|
if( iDamage > 0 ) |
|
{ |
|
pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); |
|
} |
|
|
|
return pEntity; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//========================================================= |
|
// FInViewCone - returns true is the passed ent is in |
|
// the caller's forward view cone. The dot product is performed |
|
// in 2d, making the view cone infinitely tall. |
|
//========================================================= |
|
BOOL CBaseMonster::FInViewCone( CBaseEntity *pEntity ) |
|
{ |
|
Vector2D vec2LOS; |
|
float flDot; |
|
|
|
UTIL_MakeVectors( pev->angles ); |
|
|
|
vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); |
|
vec2LOS = vec2LOS.Normalize(); |
|
|
|
flDot = DotProduct( vec2LOS, gpGlobals->v_forward.Make2D() ); |
|
|
|
if( flDot > m_flFieldOfView ) |
|
{ |
|
return TRUE; |
|
} |
|
else |
|
{ |
|
return FALSE; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// FInViewCone - returns true is the passed vector is in |
|
// the caller's forward view cone. The dot product is performed |
|
// in 2d, making the view cone infinitely tall. |
|
//========================================================= |
|
BOOL CBaseMonster::FInViewCone( Vector *pOrigin ) |
|
{ |
|
Vector2D vec2LOS; |
|
float flDot; |
|
|
|
UTIL_MakeVectors( pev->angles ); |
|
|
|
vec2LOS = ( *pOrigin - pev->origin ).Make2D(); |
|
vec2LOS = vec2LOS.Normalize(); |
|
|
|
flDot = DotProduct( vec2LOS, gpGlobals->v_forward.Make2D() ); |
|
|
|
if( flDot > m_flFieldOfView ) |
|
{ |
|
return TRUE; |
|
} |
|
else |
|
{ |
|
return FALSE; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// FVisible - returns true if a line can be traced from |
|
// the caller's eyes to the target |
|
//========================================================= |
|
BOOL CBaseEntity::FVisible( CBaseEntity *pEntity ) |
|
{ |
|
TraceResult tr; |
|
Vector vecLookerOrigin; |
|
Vector vecTargetOrigin; |
|
|
|
if( !pEntity ) |
|
return FALSE; |
|
if( !pEntity->pev ) |
|
return FALSE; |
|
|
|
if( FBitSet( pEntity->pev->flags, FL_NOTARGET ) ) |
|
return FALSE; |
|
|
|
// don't look through water |
|
if( ( pev->waterlevel != 3 && pEntity->pev->waterlevel == 3 ) |
|
|| ( pev->waterlevel == 3 && pEntity->pev->waterlevel == 0 ) ) |
|
return FALSE; |
|
|
|
vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' |
|
vecTargetOrigin = pEntity->EyePosition(); |
|
|
|
UTIL_TraceLine( vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT( pev )/*pentIgnore*/, &tr ); |
|
|
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
return FALSE;// Line of sight is not established |
|
} |
|
else |
|
{ |
|
return TRUE;// line of sight is valid. |
|
} |
|
} |
|
|
|
//========================================================= |
|
// FVisible - returns true if a line can be traced from |
|
// the caller's eyes to the target vector |
|
//========================================================= |
|
BOOL CBaseEntity::FVisible( const Vector &vecOrigin ) |
|
{ |
|
TraceResult tr; |
|
Vector vecLookerOrigin; |
|
|
|
vecLookerOrigin = EyePosition();//look through the caller's 'eyes' |
|
|
|
UTIL_TraceLine( vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT( pev )/*pentIgnore*/, &tr ); |
|
|
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
return FALSE;// Line of sight is not established |
|
} |
|
else |
|
{ |
|
return TRUE;// line of sight is valid. |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
TraceAttack |
|
================ |
|
*/ |
|
void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) |
|
{ |
|
Vector vecOrigin = ptr->vecEndPos - vecDir * 4; |
|
|
|
if( pev->takedamage ) |
|
{ |
|
AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); |
|
|
|
int blood = BloodColor(); |
|
|
|
if( blood != DONT_BLEED ) |
|
{ |
|
SpawnBlood( vecOrigin, blood, flDamage );// a little surface blood. |
|
TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
//========================================================= |
|
// TraceAttack |
|
//========================================================= |
|
void CBaseMonster::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) |
|
{ |
|
Vector vecOrigin = ptr->vecEndPos - vecDir * 4; |
|
|
|
ALERT( at_console, "%d\n", ptr->iHitgroup ); |
|
|
|
if( pev->takedamage ) |
|
{ |
|
AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); |
|
|
|
int blood = BloodColor(); |
|
|
|
if( blood != DONT_BLEED ) |
|
{ |
|
SpawnBlood( vecOrigin, blood, flDamage );// a little surface blood. |
|
} |
|
} |
|
} |
|
*/ |
|
|
|
//========================================================= |
|
// TraceAttack |
|
//========================================================= |
|
void CBaseMonster::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) |
|
{ |
|
if( pev->takedamage ) |
|
{ |
|
m_LastHitGroup = ptr->iHitgroup; |
|
|
|
switch( ptr->iHitgroup ) |
|
{ |
|
case HITGROUP_GENERIC: |
|
break; |
|
case HITGROUP_HEAD: |
|
flDamage *= gSkillData.monHead; |
|
break; |
|
case HITGROUP_CHEST: |
|
flDamage *= gSkillData.monChest; |
|
break; |
|
case HITGROUP_STOMACH: |
|
flDamage *= gSkillData.monStomach; |
|
break; |
|
case HITGROUP_LEFTARM: |
|
case HITGROUP_RIGHTARM: |
|
flDamage *= gSkillData.monArm; |
|
break; |
|
case HITGROUP_LEFTLEG: |
|
case HITGROUP_RIGHTLEG: |
|
flDamage *= gSkillData.monLeg; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
SpawnBlood( ptr->vecEndPos, BloodColor(), flDamage );// a little surface blood. |
|
TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); |
|
AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
FireBullets |
|
|
|
Go to the trouble of combining multiple pellets into a single damage call. |
|
|
|
This version is used by Monsters. |
|
================ |
|
*/ |
|
void CBaseEntity::FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) |
|
{ |
|
static int tracerCount; |
|
int tracer; |
|
TraceResult tr; |
|
Vector vecRight = gpGlobals->v_right; |
|
Vector vecUp = gpGlobals->v_up; |
|
|
|
if( pevAttacker == NULL ) |
|
pevAttacker = pev; // the default attacker is ourselves |
|
|
|
ClearMultiDamage(); |
|
gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; |
|
|
|
for( ULONG iShot = 1; iShot <= cShots; iShot++ ) |
|
{ |
|
// get circular gaussian spread |
|
float x, y, z; |
|
do { |
|
x = RANDOM_FLOAT( -0.5, 0.5 ) + RANDOM_FLOAT( -0.5, 0.5 ); |
|
y = RANDOM_FLOAT( -0.5, 0.5 ) + RANDOM_FLOAT( -0.5, 0.5 ); |
|
z = x * x + y * y; |
|
} while (z > 1); |
|
|
|
Vector vecDir = vecDirShooting + |
|
x * vecSpread.x * vecRight + |
|
y * vecSpread.y * vecUp; |
|
Vector vecEnd; |
|
|
|
vecEnd = vecSrc + vecDir * flDistance; |
|
UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( pev )/*pentIgnore*/, &tr ); |
|
|
|
tracer = 0; |
|
if( iTracerFreq != 0 && ( tracerCount++ % iTracerFreq ) == 0 ) |
|
{ |
|
Vector vecTracerSrc; |
|
|
|
if( IsPlayer() ) |
|
{ |
|
// adjust tracer position for player |
|
vecTracerSrc = vecSrc + Vector( 0, 0, -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; |
|
} |
|
else |
|
{ |
|
vecTracerSrc = vecSrc; |
|
} |
|
|
|
if( iTracerFreq != 1 ) // guns that always trace also always decal |
|
tracer = 1; |
|
switch( iBulletType ) |
|
{ |
|
case BULLET_MONSTER_MP5: |
|
case BULLET_MONSTER_9MM: |
|
case BULLET_MONSTER_12MM: |
|
default: |
|
MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecTracerSrc ); |
|
WRITE_BYTE( TE_TRACER ); |
|
WRITE_COORD( vecTracerSrc.x ); |
|
WRITE_COORD( vecTracerSrc.y ); |
|
WRITE_COORD( vecTracerSrc.z ); |
|
WRITE_COORD( tr.vecEndPos.x ); |
|
WRITE_COORD( tr.vecEndPos.y ); |
|
WRITE_COORD( tr.vecEndPos.z ); |
|
MESSAGE_END(); |
|
break; |
|
} |
|
} |
|
// do damage, paint decals |
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); |
|
|
|
if( iDamage ) |
|
{ |
|
pEntity->TraceAttack( pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ( ( iDamage > 16 ) ? DMG_ALWAYSGIB : DMG_NEVERGIB ) ); |
|
|
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
DecalGunshot( &tr, iBulletType ); |
|
} |
|
else switch( iBulletType ) |
|
{ |
|
default: |
|
case BULLET_MONSTER_9MM: |
|
pEntity->TraceAttack( pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET ); |
|
|
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
DecalGunshot( &tr, iBulletType ); |
|
break; |
|
case BULLET_MONSTER_MP5: |
|
pEntity->TraceAttack( pevAttacker, gSkillData.monDmgMP5, vecDir, &tr, DMG_BULLET ); |
|
|
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
DecalGunshot( &tr, iBulletType ); |
|
break; |
|
case BULLET_MONSTER_12MM: |
|
pEntity->TraceAttack( pevAttacker, gSkillData.monDmg12MM, vecDir, &tr, DMG_BULLET ); |
|
if( !tracer ) |
|
{ |
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
DecalGunshot( &tr, iBulletType ); |
|
} |
|
break; |
|
case BULLET_NONE: // FIX |
|
pEntity->TraceAttack( pevAttacker, 50, vecDir, &tr, DMG_CLUB ); |
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
// only decal glass |
|
if( !FNullEnt( tr.pHit ) && VARS( tr.pHit )->rendermode != 0 ) |
|
{ |
|
UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG( 0, 2 ) ); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
// make bullet trails |
|
UTIL_BubbleTrail( vecSrc, tr.vecEndPos, ( flDistance * tr.flFraction ) / 64.0 ); |
|
} |
|
ApplyMultiDamage( pev, pevAttacker ); |
|
} |
|
|
|
/* |
|
================ |
|
FireBullets |
|
|
|
Go to the trouble of combining multiple pellets into a single damage call. |
|
|
|
This version is used by Players, uses the random seed generator to sync client and server side shots. |
|
================ |
|
*/ |
|
Vector CBaseEntity::FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker, int shared_rand ) |
|
{ |
|
static int tracerCount; |
|
TraceResult tr; |
|
Vector vecRight = gpGlobals->v_right; |
|
Vector vecUp = gpGlobals->v_up; |
|
float x, y, z; |
|
int tracer; |
|
|
|
if( pevAttacker == NULL ) |
|
pevAttacker = pev; // the default attacker is ourselves |
|
|
|
CBasePlayer *pThisPlayer = NULL; |
|
if( IsPlayer() ) |
|
pThisPlayer = (CBasePlayer *)this; |
|
|
|
ClearMultiDamage(); |
|
gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; |
|
|
|
for( ULONG iShot = 1; iShot <= cShots; iShot++ ) |
|
{ |
|
//Use player's random seed. |
|
// get circular gaussian spread |
|
x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); |
|
y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); |
|
z = x * x + y * y; |
|
|
|
Vector vecDir = vecDirShooting + |
|
x * vecSpread.x * vecRight + |
|
y * vecSpread.y * vecUp; |
|
Vector vecEnd; |
|
|
|
vecEnd = vecSrc + vecDir * flDistance; |
|
UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( pev )/*pentIgnore*/, &tr ); |
|
|
|
tracer = 0; |
|
if( iTracerFreq != 0 && ( tracerCount++ % iTracerFreq ) == 0 ) |
|
{ |
|
Vector vecTracerSrc; |
|
|
|
if( IsPlayer() ) |
|
{ |
|
// adjust tracer position for player |
|
vecTracerSrc = vecSrc + Vector( 0, 0, -4 ) + gpGlobals->v_right * 2 + gpGlobals->v_forward * 16; |
|
} |
|
else |
|
{ |
|
vecTracerSrc = vecSrc; |
|
} |
|
|
|
if( iTracerFreq != 1 ) // guns that always trace also always decal |
|
tracer = 1; |
|
switch( iBulletType ) |
|
{ |
|
case BULLET_MONSTER_MP5: |
|
case BULLET_MONSTER_9MM: |
|
case BULLET_MONSTER_12MM: |
|
default: |
|
MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecTracerSrc ); |
|
WRITE_BYTE( TE_TRACER ); |
|
WRITE_COORD( vecTracerSrc.x ); |
|
WRITE_COORD( vecTracerSrc.y ); |
|
WRITE_COORD( vecTracerSrc.z ); |
|
WRITE_COORD( tr.vecEndPos.x ); |
|
WRITE_COORD( tr.vecEndPos.y ); |
|
WRITE_COORD( tr.vecEndPos.z ); |
|
MESSAGE_END(); |
|
break; |
|
} |
|
} |
|
|
|
// do damage, paint decals |
|
if( tr.flFraction != 1.0 ) |
|
{ |
|
CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); |
|
|
|
if( iDamage ) |
|
{ |
|
pEntity->TraceAttack( pevAttacker, iDamage, vecDir, &tr, DMG_BULLET | ( ( iDamage > 16 ) ? DMG_ALWAYSGIB : DMG_NEVERGIB ) ); |
|
|
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
DecalGunshot( &tr, iBulletType ); |
|
} |
|
else switch( iBulletType ) |
|
{ |
|
default: |
|
case BULLET_PLAYER_9MM: |
|
pEntity->TraceAttack( pevAttacker, gSkillData.plrDmg9MM, vecDir, &tr, DMG_BULLET ); |
|
break; |
|
case BULLET_PLAYER_MP5: |
|
pEntity->TraceAttack( pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET ); |
|
break; |
|
case BULLET_PLAYER_BUCKSHOT: |
|
// make distance based! |
|
pEntity->TraceAttack( pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET ); |
|
break; |
|
case BULLET_PLAYER_357: |
|
if( pThisPlayer->m_RuneFlags == RUNE_357 ) |
|
pEntity->TraceAttack( pevAttacker, gSkillData.plrDmg357 * 2.5, vecDir, &tr, DMG_BULLET ); |
|
else |
|
pEntity->TraceAttack( pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET ); |
|
break; |
|
case BULLET_NONE: // FIX |
|
pEntity->TraceAttack( pevAttacker, 50, vecDir, &tr, DMG_CLUB ); |
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); |
|
// only decal glass |
|
if( !FNullEnt( tr.pHit ) && VARS( tr.pHit )->rendermode != 0 ) |
|
{ |
|
UTIL_DecalTrace( &tr, DECAL_GLASSBREAK1 + RANDOM_LONG( 0, 2 ) ); |
|
} |
|
|
|
break; |
|
} |
|
} |
|
// make bullet trails |
|
UTIL_BubbleTrail( vecSrc, tr.vecEndPos, ( flDistance * tr.flFraction ) / 64.0 ); |
|
} |
|
ApplyMultiDamage( pev, pevAttacker ); |
|
|
|
return Vector( x * vecSpread.x, y * vecSpread.y, 0.0 ); |
|
} |
|
|
|
void CBaseEntity::TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) |
|
{ |
|
if( BloodColor() == DONT_BLEED ) |
|
return; |
|
|
|
if( flDamage == 0 ) |
|
return; |
|
|
|
if( !( bitsDamageType & ( DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR ) ) ) |
|
return; |
|
|
|
// make blood decal on the wall! |
|
TraceResult Bloodtr; |
|
Vector vecTraceDir; |
|
float flNoise; |
|
int cCount; |
|
int i; |
|
|
|
/* |
|
if( !IsAlive() ) |
|
{ |
|
// dealing with a dead monster. |
|
if( pev->max_health <= 0 ) |
|
{ |
|
// no blood decal for a monster that has already decalled its limit. |
|
return; |
|
} |
|
else |
|
{ |
|
pev->max_health--; |
|
} |
|
} |
|
*/ |
|
if( flDamage < 10 ) |
|
{ |
|
flNoise = 0.1; |
|
cCount = 1; |
|
} |
|
else if( flDamage < 25 ) |
|
{ |
|
flNoise = 0.2; |
|
cCount = 2; |
|
} |
|
else |
|
{ |
|
flNoise = 0.3; |
|
cCount = 4; |
|
} |
|
|
|
for( i = 0; i < cCount; i++ ) |
|
{ |
|
vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) |
|
|
|
vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); |
|
vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); |
|
vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); |
|
|
|
UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT( pev ), &Bloodtr ); |
|
|
|
if( Bloodtr.flFraction != 1.0 ) |
|
{ |
|
UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CBaseMonster::MakeDamageBloodDecal( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) |
|
{ |
|
// make blood decal on the wall! |
|
TraceResult Bloodtr; |
|
Vector vecTraceDir; |
|
int i; |
|
|
|
if( !IsAlive() ) |
|
{ |
|
// dealing with a dead monster. |
|
if( pev->max_health <= 0 ) |
|
{ |
|
// no blood decal for a monster that has already decalled its limit. |
|
return; |
|
} |
|
else |
|
{ |
|
pev->max_health--; |
|
} |
|
} |
|
|
|
for( i = 0; i < cCount; i++ ) |
|
{ |
|
vecTraceDir = vecDir; |
|
|
|
vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); |
|
vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); |
|
vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); |
|
|
|
UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT( pev ), &Bloodtr ); |
|
|
|
/* |
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); |
|
WRITE_BYTE( TE_SHOWLINE); |
|
WRITE_COORD( ptr->vecEndPos.x ); |
|
WRITE_COORD( ptr->vecEndPos.y ); |
|
WRITE_COORD( ptr->vecEndPos.z ); |
|
|
|
WRITE_COORD( Bloodtr.vecEndPos.x ); |
|
WRITE_COORD( Bloodtr.vecEndPos.y ); |
|
WRITE_COORD( Bloodtr.vecEndPos.z ); |
|
MESSAGE_END(); |
|
*/ |
|
|
|
if( Bloodtr.flFraction != 1.0 ) |
|
{ |
|
UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); |
|
} |
|
} |
|
}
|
|
|