forked from FWGS/Paranoia2
1969 lines
50 KiB
C++
1969 lines
50 KiB
C++
/***
|
|
*
|
|
* 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 "studio.h" //LRC
|
|
#include "material.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
|
|
|
|
MULTIDAMAGE gMultiDamage;
|
|
|
|
/*
|
|
==============================================================================
|
|
|
|
MULTI-DAMAGE
|
|
|
|
Collects multiple small damages into a single damage
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
//
|
|
// ClearMultiDamage - resets the global multi damage accumulator
|
|
//
|
|
void ClearMultiDamage(void)
|
|
{
|
|
gMultiDamage.pEntity = NULL;
|
|
gMultiDamage.amount = 0;
|
|
gMultiDamage.type = 0;
|
|
}
|
|
|
|
|
|
//
|
|
// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity
|
|
//
|
|
// GLOBALS USED:
|
|
// gMultiDamage
|
|
|
|
void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker )
|
|
{
|
|
Vector vecSpot1;//where blood comes from
|
|
Vector vecDir;//direction blood should go
|
|
TraceResult tr;
|
|
|
|
if ( !gMultiDamage.pEntity )
|
|
return;
|
|
|
|
gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type );
|
|
}
|
|
|
|
|
|
// GLOBALS USED:
|
|
// gMultiDamage
|
|
|
|
void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType)
|
|
{
|
|
if ( !pEntity )
|
|
return;
|
|
|
|
gMultiDamage.type |= bitsDamageType;
|
|
|
|
if ( pEntity != gMultiDamage.pEntity )
|
|
{
|
|
ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker!
|
|
gMultiDamage.pEntity = pEntity;
|
|
gMultiDamage.amount = 0;
|
|
}
|
|
|
|
gMultiDamage.amount += flDamage;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SpawnBlood
|
|
================
|
|
*/
|
|
void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage)
|
|
{
|
|
UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage );
|
|
}
|
|
|
|
|
|
const char *DamageDecal( CBaseEntity *pEntity, int bitsDamageType )
|
|
{
|
|
if( !pEntity )
|
|
return "shot";
|
|
|
|
return pEntity->DamageDecal( bitsDamageType );
|
|
}
|
|
|
|
void SpawnPartEffect( TraceResult *pTrace, matdef_t *pMat )
|
|
{
|
|
if( !pMat || pTrace->flFraction == 1.0f )
|
|
return;
|
|
|
|
// fire effects
|
|
for( int i = 0; pMat->impact_parts[i] != NULL; i++ )
|
|
{
|
|
MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, pTrace->vecEndPos );
|
|
WRITE_COORD( pTrace->vecEndPos.x );
|
|
WRITE_COORD( pTrace->vecEndPos.y );
|
|
WRITE_COORD( pTrace->vecEndPos.z );
|
|
WRITE_COORD( pTrace->vecPlaneNormal.x * 8192 );
|
|
WRITE_COORD( pTrace->vecPlaneNormal.y * 8192 );
|
|
WRITE_COORD( pTrace->vecPlaneNormal.z * 8192 );
|
|
WRITE_STRING( pMat->impact_parts[i] );
|
|
MESSAGE_END();
|
|
}
|
|
}
|
|
|
|
void DecalGunshot( TraceResult *pTrace, int iBulletType, const Vector &vecSrc, bool fromPlayer )
|
|
{
|
|
// buz
|
|
if (pTrace->fInWater )
|
|
return;
|
|
|
|
// Is the entity valid
|
|
if ( !UTIL_IsValidEntity( pTrace->pHit ) )
|
|
return;
|
|
|
|
CBaseEntity *pHit = CBaseEntity :: Instance( pTrace->pHit );
|
|
|
|
matdef_t *pMat = NULL;
|
|
|
|
if( FBitSet( pHit->pev->flags, ( FL_MONSTER|FL_CLIENT )))
|
|
{
|
|
switch( iBulletType )
|
|
{
|
|
case BULLET_NORMAL:
|
|
case BULLET_BUCKSHOT:
|
|
default:
|
|
UTIL_StudioDecalTrace( pTrace, DamageDecal( pHit, DMG_BULLET ));
|
|
break;
|
|
case BULLET_STAB:
|
|
UTIL_StudioDecalTrace( pTrace, DamageDecal( pHit, DMG_CLUB ));
|
|
break;
|
|
}
|
|
}
|
|
else if( !fromPlayer )
|
|
{
|
|
if( pHit->pev->solid == SOLID_BSP || pHit->pev->movetype == MOVETYPE_PUSHSTEP )
|
|
{
|
|
pMat = COM_MatDefFromSurface( TRACE_SURFACE( pTrace->pHit, vecSrc, pTrace->vecEndPos ), pTrace->vecEndPos );
|
|
if ( pMat ) UTIL_TraceCustomDecal( pTrace, pMat->impact_decal, RANDOM_FLOAT( 0.0f, 360.0f ));
|
|
}
|
|
else if( pHit->pev->solid == SOLID_CUSTOM && pTrace->pMat )
|
|
{
|
|
pMat = pTrace->pMat->effects;
|
|
if ( pMat ) UTIL_StudioDecalTrace( pTrace, pMat->impact_decal );
|
|
}
|
|
}
|
|
|
|
SpawnPartEffect( pTrace, pMat );
|
|
}
|
|
|
|
//
|
|
// EjectBrass - tosses a brass shell from passed origin at passed velocity
|
|
//
|
|
void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype )
|
|
{
|
|
// FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see.
|
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin );
|
|
WRITE_BYTE( TE_MODEL);
|
|
WRITE_COORD( vecOrigin.x);
|
|
WRITE_COORD( vecOrigin.y);
|
|
WRITE_COORD( vecOrigin.z);
|
|
WRITE_COORD( vecVelocity.x);
|
|
WRITE_COORD( vecVelocity.y);
|
|
WRITE_COORD( vecVelocity.z);
|
|
WRITE_ANGLE( rotation );
|
|
WRITE_SHORT( model );
|
|
WRITE_BYTE ( soundtype);
|
|
WRITE_BYTE ( 100 );// 10 seconds
|
|
MESSAGE_END();
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
if ( g_Language == LANGUAGE_GERMAN )
|
|
SpawnHeadGib(pevVictim, "models/germangibs.mdl" );// throw one head
|
|
else
|
|
SpawnHeadGib(pevVictim, "models/hgibs.mdl" );
|
|
}
|
|
|
|
void CGib :: SpawnHeadGib( entvars_t *pevVictim, const char* szGibModel )
|
|
{
|
|
CGib *pGib = GetClassPtr( (CGib *)NULL );
|
|
|
|
pGib->Spawn( szGibModel );// 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 )
|
|
{
|
|
if ( g_Language == LANGUAGE_GERMAN )
|
|
SpawnRandomGibs(pevVictim, cGibs, 1, "models/germangibs.mdl");
|
|
else if (human)
|
|
SpawnRandomGibs(pevVictim, cGibs, 1, "models/hgibs.mdl");
|
|
else
|
|
SpawnRandomGibs(pevVictim, cGibs, 0, "models/agibs.mdl");
|
|
}
|
|
|
|
//LRC - changed signature, to support custom gib models
|
|
void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int notfirst, const char *szGibModel )
|
|
{
|
|
if (cGibs == 0) return; // spawn nothing!
|
|
|
|
CGib *pGib = GetClassPtr( (CGib *)NULL );
|
|
pGib->Spawn( szGibModel );
|
|
|
|
//LRC - check the model itself to find out how many gibs are available
|
|
studiohdr_t *pstudiohdr = (studiohdr_t *)(GET_MODEL_PTR( ENT(pGib->pev) ));
|
|
if (! pstudiohdr)
|
|
return;
|
|
|
|
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex);
|
|
//ALERT(at_console, "read %d bodyparts, canonical is %d\n", pbodypart->nummodels, HUMAN_GIB_COUNT);
|
|
|
|
for (int cSplat = 0 ; cSplat < cGibs ; cSplat++ )
|
|
{
|
|
if (pGib == NULL) // first time through, we set pGib before the loop started
|
|
{
|
|
pGib = GetClassPtr( (CGib *)NULL );
|
|
pGib->Spawn( szGibModel );
|
|
}
|
|
|
|
if (notfirst)
|
|
pGib->pev->body = RANDOM_LONG(1, pbodypart->nummodels - 1);// start at one to avoid throwing random amounts of skulls (0th gib)
|
|
else
|
|
pGib->pev->body = RANDOM_LONG(0, pbodypart->nummodels - 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();
|
|
pGib = NULL; //LRC
|
|
}
|
|
}
|
|
|
|
// automatically set collision box
|
|
void CBaseMonster :: AutoSetSize( void )
|
|
{
|
|
studiohdr_t *pstudiohdr;
|
|
pstudiohdr = (studiohdr_t *)GET_MODEL_PTR( edict() );
|
|
|
|
if( pstudiohdr == NULL )
|
|
{
|
|
UTIL_SetSize( pev, Vector( -10, -10, -10 ), Vector( 10, 10, 10 ));
|
|
ALERT( at_error, "env_static: unable to fetch model pointer!\n" );
|
|
return;
|
|
}
|
|
|
|
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex);
|
|
UTIL_SetSize( pev, pseqdesc[pev->sequence].bbmin, pseqdesc[pev->sequence].bbmax );
|
|
}
|
|
|
|
//LRC - work out gibs from blood colour, instead of from class.
|
|
BOOL CBaseMonster :: HasHumanGibs( void )
|
|
{
|
|
int myClass = Classify();
|
|
|
|
// these types of monster don't use gibs
|
|
if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE ||
|
|
myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return (this->m_bloodColor == BLOOD_COLOR_RED);
|
|
}
|
|
}
|
|
|
|
//LRC - work out gibs from blood colour, instead.
|
|
BOOL CBaseMonster :: HasAlienGibs( void )
|
|
{
|
|
int myClass = Classify();
|
|
|
|
// these types of monster don't use gibs
|
|
if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE ||
|
|
myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON)
|
|
{
|
|
return FALSE;
|
|
}
|
|
else
|
|
{
|
|
return (this->m_bloodColor == BLOOD_COLOR_GREEN);
|
|
}
|
|
}
|
|
|
|
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;
|
|
int iszCustomGibs;
|
|
|
|
EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM);
|
|
|
|
if ( iszCustomGibs = HasCustomGibs() ) //LRC - monster_generic can have a custom gibset
|
|
{
|
|
if ( CVAR_GET_FLOAT("violence_hgibs") != 0 )
|
|
{
|
|
CGib::SpawnHeadGib( pev, STRING(iszCustomGibs) );
|
|
CGib::SpawnRandomGibs( pev, 4, 1, STRING(iszCustomGibs) );
|
|
}
|
|
gibbed = TRUE;
|
|
}
|
|
// only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs
|
|
else if ( HasHumanGibs() )
|
|
{
|
|
if ( CVAR_GET_FLOAT("violence_hgibs") != 0 )// Only the player will ever fail this test
|
|
{
|
|
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 fail this test, 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(&CBaseMonster :: SUB_Remove );
|
|
SetNextThink( 0 );
|
|
}
|
|
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:
|
|
|
|
// buz: play brains squish sound
|
|
// ALERT(at_console, "HEADSHOT SOUND\n");
|
|
UTIL_EmitAmbientSound(ENT(0), vecSrc, "common/headshot.wav", 1, ATTN_NORM, 0, 100);
|
|
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->solid = SOLID_NOT;
|
|
//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);
|
|
}
|
|
|
|
const char *CBaseMonster::DamageDecal( int bitsDamageType )
|
|
{
|
|
if( bitsDamageType & ( DMG_BULLET|DMG_CLUB ))
|
|
{
|
|
if( BloodColor() == BLOOD_COLOR_RED )
|
|
return "bodyhurt_red";
|
|
else if( BloodColor() == BLOOD_COLOR_YELLOW )
|
|
return "bodyhurt_yellow";
|
|
}
|
|
|
|
// Assume no blood is vehicles or furniture
|
|
return "shot";
|
|
}
|
|
|
|
/*
|
|
============
|
|
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;
|
|
|
|
SetNextThink( 0.1 );
|
|
SetThink(&CBaseEntity :: SUB_FadeOut );
|
|
}
|
|
|
|
void CBaseEntity :: SUB_FadeOut ( void )
|
|
{
|
|
if ( pev->renderamt > 7 )
|
|
{
|
|
pev->renderamt -= 7;
|
|
SetNextThink( 0.1 );
|
|
}
|
|
else
|
|
{
|
|
pev->renderamt = 0;
|
|
SetNextThink( 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(&CGib ::SUB_StartFadeOut);
|
|
SetNextThink( 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.
|
|
SetNextThink( 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(&CGib :: SUB_Remove );
|
|
SetNextThink( 10 );
|
|
|
|
if ( !FClassnameIs( pOther->pev, "worldspawn" ) )
|
|
{
|
|
SetNextThink( 0 );
|
|
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_TRIGGER; //LRC - so that they don't get in each other's way when we fire lots
|
|
// 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));
|
|
|
|
SetNextThink( 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 );
|
|
}
|
|
// Wargon: Никогда не гибать если тип дамаги - DMG_SLASH. (1.1)
|
|
else if ((bitsDamageType & DMG_NEVERGIB) || (bitsDamageType & DMG_SLASH))
|
|
{
|
|
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) )
|
|
{
|
|
//LRC - new behaviours, for m_iPlayerReact.
|
|
if (pevAttacker->flags & FL_CLIENT)
|
|
{
|
|
if (m_iPlayerReact == 2)
|
|
{
|
|
// just get angry.
|
|
Remember( bits_MEMORY_PROVOKED );
|
|
}
|
|
else if (m_iPlayerReact == 3)
|
|
{
|
|
// try to decide whether it was deliberate... if I have an enemy, assume it was just crossfire.
|
|
if ( m_hEnemy == NULL )
|
|
{
|
|
if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || UTIL_IsFacing( pevAttacker, pev->origin ) )
|
|
Remember( bits_MEMORY_PROVOKED );
|
|
else
|
|
Remember( bits_MEMORY_SUSPICIOUS );
|
|
}
|
|
}
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
SetIdealYawToTargetAndUpdate( 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();
|
|
}
|
|
}
|
|
|
|
pev->frame = RANDOM_FLOAT ( 220, 225 );
|
|
//pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage );
|
|
ResetSequenceInfo( );
|
|
|
|
#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 )
|
|
{
|
|
// buz: skip grenade damage from player's grenades to invincible monsters
|
|
if ( pEntity->pev->spawnflags & SF_MONSTER_INVINCIBLE )
|
|
{
|
|
CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker );
|
|
if (pEnt->IsPlayer())
|
|
continue;
|
|
}
|
|
|
|
// 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 (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 && tr.pHit != ENT(pEntity->pev)) //LRC - added so that monsters can "see" some bsp objects
|
|
{
|
|
// ALERT(at_console, "can't see \"%s\"\n", STRING(pEntity->pev->classname));
|
|
return FALSE;// Line of sight is not established
|
|
}
|
|
else
|
|
{
|
|
// ALERT(at_console, "Seen ok\n");
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
BOOL CBaseMonster :: AllowDecal( entvars_t *pevAttacker )
|
|
{
|
|
if ( pev->takedamage )
|
|
{
|
|
if ( pev->spawnflags & SF_MONSTER_INVINCIBLE )
|
|
{
|
|
// check for player
|
|
CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker );
|
|
if (pEnt->IsPlayer())
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// check for owner (it may be grenade, thrown by player)
|
|
if (pevAttacker->owner)
|
|
{
|
|
pEnt = CBaseEntity::Instance( pevAttacker->owner );
|
|
if (pEnt->IsPlayer())
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//=========================================================
|
|
// TraceAttack
|
|
//=========================================================
|
|
void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType)
|
|
{
|
|
if ( pev->takedamage )
|
|
{
|
|
if ( pev->spawnflags & SF_MONSTER_INVINCIBLE )
|
|
{
|
|
// ALERT(at_console, "yes im incvincible\n");
|
|
// check for player
|
|
CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker );
|
|
if (pEnt->IsPlayer())
|
|
{
|
|
// ALERT(at_console, "ent is player\n");
|
|
return;
|
|
}
|
|
|
|
// check for owner (it may be grenade, thrown by player)
|
|
if (pevAttacker->owner)
|
|
{
|
|
pEnt = CBaseEntity::Instance( pevAttacker->owner );
|
|
if (pEnt->IsPlayer())
|
|
{
|
|
// ALERT(at_console, "owner is player\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_LastHitGroup = ptr->iHitgroup;
|
|
TraceBleed( flDamage, vecDir, ptr, bitsDamageType );
|
|
|
|
TraceResult btr; // Wargon: Переменная для трейса декали мозгов от хедшотов.
|
|
|
|
switch ( ptr->iHitgroup )
|
|
{
|
|
case HITGROUP_GENERIC:
|
|
break;
|
|
case HITGROUP_HEAD:
|
|
// Wargon: Декаль мозгов от хедшотов.
|
|
UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecDir * 172, ignore_monsters, ENT(pev), &btr );
|
|
UTIL_TraceCustomDecal( &btr, "brains", RANDOM_FLOAT( 0.0f, 360.0f ));
|
|
|
|
// buz: more blood in the head!!! let the brains fly out and smash on ground, muahahaha!!
|
|
SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 4);
|
|
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 * 2); // Wargon: Побольше крови.
|
|
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, float flDamage, entvars_t *pevAttacker )
|
|
{
|
|
Vector vecRight = gpGlobals->v_right;
|
|
Vector vecUp = gpGlobals->v_up;
|
|
TraceResult tr;
|
|
|
|
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.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f );
|
|
y = RANDOM_FLOAT( -0.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f );
|
|
z = x * x + y * y;
|
|
} while( z > 1.0f );
|
|
|
|
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 ), &tr );
|
|
|
|
// do damage, paint decals
|
|
if ( tr.flFraction != 1.0f )
|
|
{
|
|
CBaseEntity *pEntity = CBaseEntity :: Instance( tr.pHit );
|
|
|
|
switch( iBulletType )
|
|
{
|
|
case BULLET_NORMAL:
|
|
case BULLET_BUCKSHOT:
|
|
pEntity->TraceAttack( pevAttacker, flDamage, vecDir, &tr, DMG_BULLET|DMG_NEVERGIB );
|
|
TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType );
|
|
if( pEntity->AllowDecal( pevAttacker ))
|
|
DecalGunshot( &tr, iBulletType, vecSrc );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// make bullet trails
|
|
UTIL_BubbleTrail( vecSrc, tr.vecEndPos, ( flDistance * tr.flFraction ) / 64.0f );
|
|
}
|
|
|
|
ApplyMultiDamage( pev, pevAttacker );
|
|
}
|
|
|
|
//Water splash
|
|
extern int gmsgWaterSplash;
|
|
|
|
void CBaseEntity::FireBulletsWater( Vector vecSrc, Vector vecEnd, float ScaleSplash1, float ScaleSplash2 )
|
|
{
|
|
if( !( POINT_CONTENTS( vecEnd ) == CONTENTS_WATER && POINT_CONTENTS( vecSrc ) != CONTENTS_WATER ) )
|
|
return;
|
|
|
|
float x = vecEnd.x - vecSrc.x;
|
|
float y = vecEnd.y - vecSrc.y;
|
|
float z = vecEnd.z - vecSrc.z;
|
|
float len = Vector( vecEnd - vecSrc).Length();
|
|
|
|
Vector vecTemp = ( vecEnd + vecSrc ) / 2;
|
|
while( len >= 1 )
|
|
{
|
|
if( POINT_CONTENTS( vecTemp ) == CONTENTS_WATER )
|
|
vecEnd = vecTemp;
|
|
else
|
|
vecSrc = vecTemp;
|
|
vecTemp = ( vecEnd + vecSrc ) / 2;
|
|
len = Vector( vecEnd - vecSrc).Length();
|
|
}
|
|
|
|
if( len <= 1 )
|
|
{
|
|
MESSAGE_BEGIN(MSG_ALL, gmsgWaterSplash);
|
|
WRITE_COORD( vecTemp.x );
|
|
WRITE_COORD( vecTemp.y );
|
|
WRITE_COORD( vecTemp.z );
|
|
WRITE_COORD( ScaleSplash1 );
|
|
WRITE_COORD( ScaleSplash2 );
|
|
MESSAGE_END();
|
|
|
|
switch( RANDOM_LONG( 1, 3 ) )
|
|
{
|
|
case 1:
|
|
UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash1.wav", 1, ATTN_NORM, 0, 100 );
|
|
break;
|
|
case 2:
|
|
UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash2.wav", 1, ATTN_NORM, 0, 100 );
|
|
break;
|
|
case 3:
|
|
UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash3.wav", 1, ATTN_NORM, 0, 100 );
|
|
break;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if( POINT_CONTENTS( vecTemp ) == CONTENTS_WATER )
|
|
FireBulletsWater( vecSrc, vecTemp, ScaleSplash1, ScaleSplash2 );
|
|
else
|
|
FireBulletsWater( vecTemp, vecEnd, ScaleSplash1, ScaleSplash2 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
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, float flDamage, entvars_t *pevAttacker, int shared_rand )
|
|
{
|
|
TraceResult tr;
|
|
Vector vecRight = gpGlobals->v_right;
|
|
Vector vecUp = gpGlobals->v_up;
|
|
float x, y, z;
|
|
|
|
if ( pevAttacker == NULL )
|
|
pevAttacker = pev; // the default attacker is ourselves
|
|
|
|
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), &tr );
|
|
|
|
// do damage, paint decals
|
|
if( tr.flFraction != 1.0 )
|
|
{
|
|
CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit );
|
|
pEntity->TraceAttack( pevAttacker, flDamage, vecDir, &tr, DMG_BULLET );
|
|
if( pEntity->AllowDecal( pevAttacker ))
|
|
DecalGunshot( &tr, BULLET_NORMAL, vecSrc, true );
|
|
}
|
|
|
|
// make bullet trails
|
|
UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 );
|
|
FireBulletsWater( vecSrc, tr.vecEndPos, 0.15f, 0.15f );
|
|
}
|
|
|
|
ApplyMultiDamage( pev, pevAttacker );
|
|
|
|
return Vector( x * vecSpread.x, y * vecSpread.y, 0.0f );
|
|
}
|
|
|
|
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() );
|
|
}
|
|
}
|
|
}
|