/* 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<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 ); }