Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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.

601 lines
14 KiB

/*
Copyright (c) 1999, Cold Ice Modification.
This code has been written by SlimShady ( darcuri@optonline.net )
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 and from the Cold Ice team.
Please if you use this code in any public form, please give us credit.
*/
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "soundent.h"
#include "gamerules.h"
enum w_chumtoad_e {
WCHUM_IDLE1 = 0,
WCHUM_IDLE2,
WCHUM_IDLE3,
WCHUM_FIDGET,
WCHUM_FIDGET2,
WCHUM_HOP,
WCHUM_HOP2,
WCHUM_SWIM,
WCHUM_DIE,
WCHUM_DEADWIGGLE,
WCHUM_PLAYDEAD,
WCHUM_DEADWIGGLE2,
WCHUM_PLAYDEAD2,
};
enum chumtoad_e {
CHUM_IDLE1 = 0,
CHUM_FIDGETFIT,
CHUM_FIDGETNIP,
CHUM_DOWN,
CHUM_UP,
CHUM_THROW
};
class CChumtoadGrenade : public CGrenade
{
void Spawn( void );
void Precache( void );
int Classify( void );
void EXPORT SuperBounceTouch( CBaseEntity *pOther );
void EXPORT HuntThink( void );
int BloodColor( void ) { return BLOOD_COLOR_YELLOW; }
void Killed( entvars_t *pevAttacker, int iGib );
void GibMonster( void );
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
static float m_flNextBounceSoundTime;
// CBaseEntity *m_pTarget;
float m_flDie;
Vector m_vecTarget;
float m_flNextHunt;
float m_flNextHit;
Vector m_posPrev;
EHANDLE m_hOwner;
int m_iMyClass;
};
float CChumtoadGrenade::m_flNextBounceSoundTime = 0;
LINK_ENTITY_TO_CLASS( monster_chumtoad, CChumtoadGrenade );
TYPEDESCRIPTION CChumtoadGrenade::m_SaveData[] =
{
DEFINE_FIELD( CChumtoadGrenade, m_flDie, FIELD_TIME ),
DEFINE_FIELD( CChumtoadGrenade, m_vecTarget, FIELD_VECTOR ),
DEFINE_FIELD( CChumtoadGrenade, m_flNextHunt, FIELD_TIME ),
DEFINE_FIELD( CChumtoadGrenade, m_flNextHit, FIELD_TIME ),
DEFINE_FIELD( CChumtoadGrenade, m_posPrev, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( CChumtoadGrenade, m_hOwner, FIELD_EHANDLE ),
};
IMPLEMENT_SAVERESTORE( CChumtoadGrenade, CGrenade );
#define CHUMTOAD_DETONATE_DELAY 15.0
int CChumtoadGrenade :: Classify ( void )
{
if (m_iMyClass != 0)
return m_iMyClass; // protect against recursion
if (m_hEnemy != NULL)
{
m_iMyClass = CLASS_INSECT; // no one cares about it
switch( m_hEnemy->Classify( ) )
{
case CLASS_PLAYER:
case CLASS_HUMAN_PASSIVE:
case CLASS_HUMAN_MILITARY:
m_iMyClass = 0;
return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it
}
m_iMyClass = 0;
}
return CLASS_ALIEN_BIOWEAPON;
}
void CChumtoadGrenade :: Spawn( void )
{
Precache( );
// motor
pev->movetype = MOVETYPE_BOUNCE;
pev->solid = SOLID_BBOX;
SET_MODEL(ENT(pev), "models/chumtoad/chumtoad.mdl");
UTIL_SetSize(pev, Vector( -4, -4, 0), Vector(4, 4, 8));
UTIL_SetOrigin( pev, pev->origin );
SetTouch( SuperBounceTouch );
SetThink( HuntThink );
pev->nextthink = gpGlobals->time + 0.1;
m_flNextHunt = gpGlobals->time + 1E6;
pev->flags |= FL_MONSTER;
pev->takedamage = DAMAGE_AIM;
pev->health = 20;
pev->gravity = 0.5;
pev->friction = 0.5;
pev->dmg = 100;
m_flDie = gpGlobals->time + CHUMTOAD_DETONATE_DELAY;
m_flFieldOfView = 0; // 180 degrees
if ( pev->owner )
m_hOwner = Instance( pev->owner );
m_flNextBounceSoundTime = gpGlobals->time;// reset each time a snark is spawned.
pev->sequence = WCHUM_HOP;
ResetSequenceInfo( );
}
void CChumtoadGrenade::Precache( void )
{
PRECACHE_MODEL("models/chumtoad/chumtoad.mdl");
PRECACHE_SOUND("squeek/sqk_blast1.wav");
PRECACHE_SOUND("common/bodysplat.wav");
//PRECACHE_SOUND("chumtoad/sqk_die1.wav");
PRECACHE_SOUND("chumtoad/hunt1.wav");
PRECACHE_SOUND("chumtoad/hunt2.wav");
PRECACHE_SOUND("chumtoad/hunt3.wav");
PRECACHE_SOUND("chumtoad/bite.wav");
}
void CChumtoadGrenade :: Killed( entvars_t *pevAttacker, int iGib )
{
pev->model = iStringNull;// make invisible
SetThink( SUB_Remove );
SetTouch( NULL );
pev->nextthink = gpGlobals->time + 0.1;
// since squeak grenades never leave a body behind, clear out their takedamage now.
// Squeaks do a bit of radius damage when they pop, and that radius damage will
// continue to call this function unless we acknowledge the Squeak's death now. (sjb)
pev->takedamage = DAMAGE_NO;
// play squeek blast
EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "squeek/sqk_blast1.wav", 1, 0.5, 0, PITCH_NORM);
CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 );
UTIL_BloodDrips( pev->origin, g_vecZero, BloodColor(), 80 );
if (m_hOwner != NULL)
RadiusDamage ( pev, m_hOwner->pev, pev->dmg, CLASS_NONE, DMG_BLAST );
else
RadiusDamage ( pev, pev, pev->dmg, CLASS_NONE, DMG_BLAST );
// reset owner so death message happens
if (m_hOwner != NULL)
pev->owner = m_hOwner->edict();
CBaseMonster :: Killed( pevAttacker, GIB_ALWAYS );
}
void CChumtoadGrenade :: GibMonster( void )
{
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200);
}
void CChumtoadGrenade::HuntThink( void )
{
// ALERT( at_console, "think\n" );
if (!IsInWorld())
{
SetTouch( NULL );
UTIL_Remove( this );
return;
}
StudioFrameAdvance( );
pev->nextthink = gpGlobals->time + 0.1;
// explode when ready
if (gpGlobals->time >= m_flDie)
{
g_vecAttackDir = pev->velocity.Normalize( );
pev->health = -1;
Killed( pev, 0 );
return;
}
// float
if (pev->waterlevel != 0)
{
if (pev->movetype == MOVETYPE_BOUNCE)
{
pev->movetype = MOVETYPE_FLY;
}
pev->sequence = WCHUM_SWIM;
pev->velocity = pev->velocity * 0.9;
pev->velocity.z += 8.0;
}
else if (pev->movetype = MOVETYPE_FLY)
{
pev->movetype = MOVETYPE_BOUNCE;
}
// return if not time to hunt
if (m_flNextHunt > gpGlobals->time)
return;
m_flNextHunt = gpGlobals->time + 2.0;
CBaseEntity *pOther = NULL;
Vector vecDir;
TraceResult tr;
Vector vecFlat = pev->velocity;
vecFlat.z = 0;
vecFlat = vecFlat.Normalize( );
UTIL_MakeVectors( pev->angles );
if (m_hEnemy == NULL || !m_hEnemy->IsAlive())
{
// find target, bounce a bit towards it.
Look( 512 );
m_hEnemy = BestVisibleEnemy( );
pev->sequence = WCHUM_HOP2;
}
// squeek if it's about time blow up
if ((m_flDie - gpGlobals->time <= 0.5) && (m_flDie - gpGlobals->time >= 0.3))
{
//EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_die1.wav", 1, ATTN_NORM, 0, 100 + RANDOM_LONG(0,0x3F));
CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 );
}
// higher pitch as squeeker gets closer to detonation time
float flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / CHUMTOAD_DETONATE_DELAY);
if (flpitch < 80)
flpitch = 80;
if (m_hEnemy != NULL)
{
if (FVisible( m_hEnemy ))
{
vecDir = m_hEnemy->EyePosition() - pev->origin;
m_vecTarget = vecDir.Normalize( );
}
float flVel = pev->velocity.Length();
float flAdj = 50.0 / (flVel + 10.0);
if (flAdj > 1.2)
flAdj = 1.2;
// ALERT( at_console, "think : enemy\n");
// ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z );
pev->velocity = pev->velocity * flAdj + m_vecTarget * 300;
}
if (pev->flags & FL_ONGROUND)
{
pev->avelocity = Vector( 0, 0, 0 );
}
else
{
if (pev->avelocity == Vector( 0, 0, 0))
{
pev->avelocity.x = RANDOM_FLOAT( -100, 100 );
pev->avelocity.z = RANDOM_FLOAT( -100, 100 );
}
}
if ((pev->origin - m_posPrev).Length() < 1.0)
{
pev->velocity.x = RANDOM_FLOAT( -100, 100 );
pev->velocity.y = RANDOM_FLOAT( -100, 100 );
}
m_posPrev = pev->origin;
pev->angles = UTIL_VecToAngles( pev->velocity );
pev->angles.z = 0;
pev->angles.x = 0;
}
void CChumtoadGrenade::SuperBounceTouch( CBaseEntity *pOther )
{
float flpitch;
TraceResult tr = UTIL_GetGlobalTrace( );
// don't hit the guy that launched this grenade
if ( pev->owner && pOther->edict() == pev->owner )
return;
// at least until we've bounced once
pev->owner = NULL;
pev->angles.x = 0;
pev->angles.z = 0;
// avoid bouncing too much
if (m_flNextHit > gpGlobals->time)
return;
// higher pitch as squeeker gets closer to detonation time
flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / CHUMTOAD_DETONATE_DELAY);
if ( pOther->pev->takedamage && m_flNextAttack < gpGlobals->time)
{
// attack!
// make sure it's me who has touched them
if (tr.pHit == pOther->edict())
{
// and it's not another squeakgrenade
if (tr.pHit->v.modelindex != pev->modelindex)
{
// ALERT( at_console, "hit enemy\n");
ClearMultiDamage( );
pOther->TraceAttack(pev, 20, gpGlobals->v_forward, &tr, DMG_SLASH );
if (m_hOwner != NULL)
ApplyMultiDamage( pev, m_hOwner->pev );
else
ApplyMultiDamage( pev, pev );
pev->dmg += 40; // add more explosion damage
// make bite sound
EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "chumtoad/bite.wav", 1.0, ATTN_NORM, 0, (int)flpitch);
m_flNextAttack = gpGlobals->time + 0.5;
}
}
else
{
// ALERT( at_console, "been hit\n");
}
}
m_flNextHit = gpGlobals->time + 0.1;
m_flNextHunt = gpGlobals->time;
if ( g_pGameRules->IsMultiplayer() )
{
// in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows.
if ( gpGlobals->time < m_flNextBounceSoundTime )
{
// too soon!
return;
}
}
if (!(pev->flags & FL_ONGROUND))
{
// play bounce sound
float flRndSound = RANDOM_FLOAT ( 0 , 1 );
if ( flRndSound <= 0.33 )
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt1.wav", 1, ATTN_NORM, 0, (int)flpitch);
else if (flRndSound <= 0.66)
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt2.wav", 1, ATTN_NORM, 0, (int)flpitch);
else
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt3.wav", 1, ATTN_NORM, 0, (int)flpitch);
CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 );
}
else
{
// skittering sound
CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 100, 0.1 );
}
m_flNextBounceSoundTime = gpGlobals->time + 0.5;// half second.
}
class CChumtoad : public CBasePlayerWeapon
{
public:
void Spawn( void );
void Precache( void );
int iItemSlot( void ) { return 5; }
int GetItemInfo(ItemInfo *p);
void PrimaryAttack( void );
BOOL Deploy( void );
void Holster( void );
void WeaponIdle( void );
int m_fJustThrown;
};
LINK_ENTITY_TO_CLASS( weapon_chumtoad, CChumtoad );
LINK_ENTITY_TO_CLASS( weapon_snark, CChumtoad );
void CChumtoad::Spawn( )
{
pev->classname = MAKE_STRING("weapon_chumtoad");
Precache( );
m_iId = WEAPON_CHUMTOAD;
SET_MODEL(ENT(pev), "models/chumtoad/chumtoad.mdl");
FallInit();
m_iDefaultAmmo = CHUMTOAD_DEFAULT_GIVE;
pev->sequence = 1;
pev->animtime = gpGlobals->time;
pev->framerate = 10.0;
}
void CChumtoad::Precache( void )
{
PRECACHE_MODEL("models/chumtoad/chumtoad.mdl");
PRECACHE_MODEL("models/vmodels/v_chumtoad.mdl");
PRECACHE_MODEL("models/pmodels/p_chumtoad.mdl");
PRECACHE_SOUND("chumtoad/hunt2.wav");
PRECACHE_SOUND("chumtoad/hunt3.wav");
UTIL_PrecacheOther("monster_chumtoad");
}
int CChumtoad::GetItemInfo(ItemInfo *p)
{
p->pszName = STRING(pev->classname);
p->pszAmmo1 = "Chumtoad";
p->iMaxAmmo1 = CHUMTOAD_MAX_CARRY;
p->pszAmmo2 = NULL;
p->iMaxAmmo2 = -1;
p->iMaxClip = WEAPON_NOCLIP;
p->iSlot = 4;
p->iPosition = 1;
p->iId = m_iId = WEAPON_CHUMTOAD;
p->iWeight = CHUMTOAD_WEIGHT;
p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE;
return 1;
}
BOOL CChumtoad::Deploy( )
{
// play hunt sound
float flRndSound = RANDOM_FLOAT ( 0 , 1 );
if ( flRndSound <= 0.5 )
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt2.wav", 1, ATTN_NORM, 0, 100);
else
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt3.wav", 1, ATTN_NORM, 0, 100);
m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME;
return DefaultDeploy( "models/vmodels/v_chumtoad.mdl", "models/pmodels/p_chumtoad.mdl", CHUM_UP, "squeak" );
}
void CChumtoad::Holster( )
{
m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5;
if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
{
m_pPlayer->pev->weapons &= ~(1<<WEAPON_CHUMTOAD);
SetThink( DestroyItem );
pev->nextthink = gpGlobals->time + 0.1;
return;
}
SendWeaponAnim( CHUM_DOWN );
EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM);
}
void CChumtoad::PrimaryAttack()
{
if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType])
{
UTIL_MakeVectors( m_pPlayer->pev->v_angle );
TraceResult tr;
// find place to toss monster
UTIL_TraceLine( m_pPlayer->pev->origin + gpGlobals->v_forward * 16, m_pPlayer->pev->origin + gpGlobals->v_forward * 64, dont_ignore_monsters, NULL, &tr );
if (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.25)
{
m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;
SendWeaponAnim( CHUM_THROW );
// player "shoot" animation
m_pPlayer->SetAnimation( PLAYER_ATTACK1 );
CBaseEntity *pSqueak = CBaseEntity::Create( "monster_chumtoad", tr.vecEndPos, m_pPlayer->pev->v_angle, m_pPlayer->edict() );
pSqueak->pev->velocity = gpGlobals->v_forward * 200 + m_pPlayer->pev->velocity;
// play hunt sound
float flRndSound = RANDOM_FLOAT ( 0 , 1 );
if ( flRndSound <= 0.5 )
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt2.wav", 1, ATTN_NORM, 0, 105);
else
EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chumtoad/hunt3.wav", 1, ATTN_NORM, 0, 105);
m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME;
m_fJustThrown = 1;
m_flNextPrimaryAttack = gpGlobals->time + 0.3;
m_flTimeWeaponIdle = gpGlobals->time + 1.0;
}
}
}
void CChumtoad::WeaponIdle( void )
{
if (m_flTimeWeaponIdle > gpGlobals->time)
return;
if (m_fJustThrown)
{
m_fJustThrown = 0;
if ( !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] )
{
RetireWeapon();
return;
}
SendWeaponAnim( CHUM_UP );
m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );
return;
}
int iAnim;
float flRand = RANDOM_FLOAT(0, 1);
if (flRand <= 0.75)
{
iAnim = CHUM_IDLE1;
m_flTimeWeaponIdle = gpGlobals->time + 30.0 / 16 * (2);
}
else if (flRand <= 0.875)
{
iAnim = CHUM_FIDGETFIT;
m_flTimeWeaponIdle = gpGlobals->time + 70.0 / 16.0;
}
else
{
iAnim = CHUM_FIDGETNIP;
m_flTimeWeaponIdle = gpGlobals->time + 80.0 / 16.0;
}
SendWeaponAnim( iAnim );
}