/*** * * Copyright (c) 1999, 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. * ****/ #include "extdll.h" #include "util.h" #include "cbase.h" #include "animation.h" #include "effects.h" #define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" #define XEN_PLANT_HIDE_TIME 5 class CActAnimating : public CBaseAnimating { public: void SetActivity( Activity act ); inline Activity GetActivity( void ) { return m_Activity; } virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; private: Activity m_Activity; }; TYPEDESCRIPTION CActAnimating::m_SaveData[] = { DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), }; IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); void CActAnimating :: SetActivity( Activity act ) { int sequence = LookupActivity( act ); if ( sequence != ACTIVITY_NOT_AVAILABLE ) { pev->sequence = sequence; m_Activity = act; pev->frame = 0; ResetSequenceInfo( ); } } class CXenPLight : public CActAnimating { public: void Spawn( void ); void Precache( void ); void Touch( CBaseEntity *pOther ); void Think( void ); void LightOn( void ); void LightOff( void ); virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; private: CSprite *m_pGlow; }; LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); TYPEDESCRIPTION CXenPLight::m_SaveData[] = { DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), }; IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); void CXenPLight :: Spawn( void ) { Precache(); SET_MODEL( ENT(pev), "models/light.mdl" ); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_TRIGGER; UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); SetActivity( ACT_IDLE ); pev->nextthink = gpGlobals->time + 0.1; pev->frame = RANDOM_FLOAT(0,255); m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector((float)0, (float)0, (float)((pev->mins.z+pev->maxs.z)*0.5)), FALSE ); m_pGlow->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); m_pGlow->SetAttachment( edict(), 1 ); } void CXenPLight :: Precache( void ) { PRECACHE_MODEL( "models/light.mdl" ); PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); } void CXenPLight :: Think( void ) { StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; switch( GetActivity() ) { case ACT_CROUCH: if ( m_fSequenceFinished ) { SetActivity( ACT_CROUCHIDLE ); LightOff(); } break; case ACT_CROUCHIDLE: if ( gpGlobals->time > pev->dmgtime ) { SetActivity( ACT_STAND ); LightOn(); } break; case ACT_STAND: if ( m_fSequenceFinished ) SetActivity( ACT_IDLE ); break; case ACT_IDLE: default: break; } } void CXenPLight :: Touch( CBaseEntity *pOther ) { if ( pOther->IsPlayer() ) { pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND ) { SetActivity( ACT_CROUCH ); } } } void CXenPLight :: LightOn( void ) { SUB_UseTargets( this, USE_ON, 0 ); if ( m_pGlow ) m_pGlow->pev->effects &= ~EF_NODRAW; } void CXenPLight :: LightOff( void ) { SUB_UseTargets( this, USE_OFF, 0 ); if ( m_pGlow ) m_pGlow->pev->effects |= EF_NODRAW; } class CXenHair : public CActAnimating { public: void Spawn( void ); void Precache( void ); void Think( void ); }; LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); #define SF_HAIR_SYNC 0x0001 void CXenHair::Spawn( void ) { Precache(); SET_MODEL( edict(), "models/hair.mdl" ); UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); pev->sequence = 0; if ( !(pev->spawnflags & SF_HAIR_SYNC) ) { pev->frame = RANDOM_FLOAT(0,255); pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); } ResetSequenceInfo( ); pev->solid = SOLID_NOT; pev->movetype = MOVETYPE_NONE; pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit } void CXenHair::Think( void ) { StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.5; } void CXenHair::Precache( void ) { PRECACHE_MODEL( "models/hair.mdl" ); } class CXenTreeTrigger : public CBaseEntity { public: void Touch( CBaseEntity *pOther ); static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); }; LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) { CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); pTrigger->pev->origin = position; pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); pTrigger->pev->solid = SOLID_TRIGGER; pTrigger->pev->movetype = MOVETYPE_NONE; pTrigger->pev->owner = pOwner; return pTrigger; } void CXenTreeTrigger::Touch( CBaseEntity *pOther ) { if ( pev->owner ) { CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); pEntity->Touch( pOther ); } } #define TREE_AE_ATTACK 1 class CXenTree : public CActAnimating { public: void Spawn( void ); void Precache( void ); void Touch( CBaseEntity *pOther ); void Think( void ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } void HandleAnimEvent( MonsterEvent_t *pEvent ); void Attack( void ); int Classify( void ) { return CLASS_BARNACLE; } virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; static const char *pAttackHitSounds[]; static const char *pAttackMissSounds[]; private: CXenTreeTrigger *m_pTrigger; }; LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); TYPEDESCRIPTION CXenTree::m_SaveData[] = { DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), }; IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); void CXenTree :: Spawn( void ) { Precache(); SET_MODEL( ENT(pev), "models/tree.mdl" ); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); SetActivity( ACT_IDLE ); pev->nextthink = gpGlobals->time + 0.1; pev->frame = RANDOM_FLOAT(0,255); pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); Vector triggerPosition; UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); triggerPosition = pev->origin + (triggerPosition * 64); // Create the trigger m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); } const char *CXenTree::pAttackHitSounds[] = { "zombie/claw_strike1.wav", "zombie/claw_strike2.wav", "zombie/claw_strike3.wav", }; const char *CXenTree::pAttackMissSounds[] = { "zombie/claw_miss1.wav", "zombie/claw_miss2.wav", }; void CXenTree :: Precache( void ) { PRECACHE_MODEL( "models/tree.mdl" ); PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); PRECACHE_SOUND_ARRAY( pAttackHitSounds ); PRECACHE_SOUND_ARRAY( pAttackMissSounds ); } void CXenTree :: Touch( CBaseEntity *pOther ) { if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) return; Attack(); } void CXenTree :: Attack( void ) { if ( GetActivity() == ACT_IDLE ) { SetActivity( ACT_MELEE_ATTACK1 ); pev->framerate = RANDOM_FLOAT( 1.0, 1.4 ); EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackMissSounds ); } } void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case TREE_AE_ATTACK: { CBaseEntity *pList[8]; BOOL sound = FALSE; int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); Vector forward; UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); for ( int i = 0; i < count; i++ ) { if ( pList[i] != this ) { if ( pList[i]->pev->owner != edict() ) { sound = TRUE; pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); pList[i]->pev->punchangle.x = 15; pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; } } } if ( sound ) { EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); } } return; } CActAnimating::HandleAnimEvent( pEvent ); } void CXenTree :: Think( void ) { float flInterval = StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; DispatchAnimEvents( flInterval ); switch( GetActivity() ) { case ACT_MELEE_ATTACK1: if ( m_fSequenceFinished ) { SetActivity( ACT_IDLE ); pev->framerate = RANDOM_FLOAT( 0.6, 1.4 ); } break; default: case ACT_IDLE: break; } } // UNDONE: These need to smoke somehow when they take damage // Touch behavior? // Cause damage in smoke area // // Spores // class CXenSpore : public CActAnimating { public: void Spawn( void ); void Precache( void ); void Touch( CBaseEntity *pOther ); void Think( void ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } // void HandleAnimEvent( MonsterEvent_t *pEvent ); void Attack( void ) {} static const char *pModelNames[]; }; class CXenSporeSmall : public CXenSpore { void Spawn( void ); }; class CXenSporeMed : public CXenSpore { void Spawn( void ); }; class CXenSporeLarge : public CXenSpore { void Spawn( void ); static const Vector m_hullSizes[]; }; // Fake collision box for big spores class CXenHull : public CPointEntity { public: static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); int Classify( void ) { return CLASS_BARNACLE; } }; CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) { CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); UTIL_SetOrigin( pHull->pev, source->pev->origin + offset ); SET_MODEL( pHull->edict(), STRING(source->pev->model) ); pHull->pev->solid = SOLID_BBOX; pHull->pev->classname = MAKE_STRING("xen_hull"); pHull->pev->movetype = MOVETYPE_NONE; pHull->pev->owner = source->edict(); UTIL_SetSize( pHull->pev, mins, maxs ); pHull->pev->renderamt = 0; pHull->pev->rendermode = kRenderTransTexture; // pHull->pev->effects = EF_NODRAW; return pHull; } LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); void CXenSporeSmall::Spawn( void ) { pev->skin = 0; CXenSpore::Spawn(); UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); } void CXenSporeMed::Spawn( void ) { pev->skin = 1; CXenSpore::Spawn(); UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); } // I just eyeballed these -- fill in hulls for the legs const Vector CXenSporeLarge::m_hullSizes[] = { Vector( 90, -25, 0 ), Vector( 25, 75, 0 ), Vector( -15, -100, 0 ), Vector( -90, -35, 0 ), Vector( -90, 60, 0 ), }; void CXenSporeLarge::Spawn( void ) { pev->skin = 2; CXenSpore::Spawn(); UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); Vector forward, right; UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); // Rotate the leg hulls into position for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ ) CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); } void CXenSpore :: Spawn( void ) { Precache(); SET_MODEL( ENT(pev), pModelNames[pev->skin] ); pev->movetype = MOVETYPE_NONE; pev->solid = SOLID_BBOX; pev->takedamage = DAMAGE_YES; // SetActivity( ACT_IDLE ); pev->sequence = 0; pev->frame = RANDOM_FLOAT(0,255); pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); ResetSequenceInfo( ); pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.1, 0.4 ); // Load balance these a bit } const char *CXenSpore::pModelNames[] = { "models/fungus(small).mdl", "models/fungus.mdl", "models/fungus(large).mdl", }; void CXenSpore :: Precache( void ) { PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); } void CXenSpore :: Touch( CBaseEntity *pOther ) { } void CXenSpore :: Think( void ) { float flInterval = StudioFrameAdvance(); pev->nextthink = gpGlobals->time + 0.1; #if 0 DispatchAnimEvents( flInterval ); switch( GetActivity() ) { default: case ACT_IDLE: break; } #endif }