From 9602f862c9056788e5386dc23b27bcc274471dfd Mon Sep 17 00:00:00 2001 From: Julian Date: Sun, 19 Jul 2020 12:31:52 -0300 Subject: [PATCH] Added monster_voltigore and monster_baby_voltigore. --- README.md | 4 +- cfg/monster_precache.cfg | 2 + cfg/monster_skill.cfg | 10 + src/dlls/Makefile | 1 + src/dlls/cmbasemonster.h | 106 +++ src/dlls/dllapi.cpp | 10 +- src/dlls/skill.cpp | 14 + src/dlls/skill.h | 7 + src/dlls/voltigore.cpp | 1326 ++++++++++++++++++++++++++++++++++++++ 9 files changed, 1477 insertions(+), 3 deletions(-) create mode 100644 src/dlls/voltigore.cpp diff --git a/README.md b/README.md index 5649c3e..3ddf889 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ If you are trying to use the compiled binary, you must know that it has been com To remedy this issue you have two options: -You can recompile the source code under g++ 4.8 and use the newly generated binary. Make sure to edit the Makefile so it points to that version of g++. Compilation is done by simply running `make` on the `src/dlls` folder +You can recompile the source code under g++ 4.8 and use the newly generated binary. Make sure to edit the Makefile so it points to that version of g++. Compilation is done by simply running `make` on the `src/dlls` folder. Alternatively, you can "remove" the outdated library to force HLDS to use the libstdc++ provided by the linux distro, which is generally more up to date. You might need to install GCC/G++ on the operating system if it doesn't work. @@ -83,7 +83,7 @@ Current milestones are separated by "Tiers", which are as follows: ### Tier 3 -- Implement *-almost-* all Opposing Force monsters. +- Implement *-almost-* all Opposing Force monsters. **[DONE]** - Implement *-almost-* all default Sven Co-op monsters. - Add configurations to change AI behaviour. diff --git a/cfg/monster_precache.cfg b/cfg/monster_precache.cfg index 38b242d..b2503bc 100755 --- a/cfg/monster_precache.cfg +++ b/cfg/monster_precache.cfg @@ -32,3 +32,5 @@ //monster_otis //monster_pitdrone //monster_shocktrooper +//monster_voltigore +//monster_baby_voltigore diff --git a/cfg/monster_skill.cfg b/cfg/monster_skill.cfg index 3d76339..43e27da 100755 --- a/cfg/monster_skill.cfg +++ b/cfg/monster_skill.cfg @@ -121,6 +121,16 @@ sk_shocktrooper_rchgspeed 1 sk_shock_dmg 15 sk_spore_dmg 50 +// Voltigore +sk_voltigore_health 320 +sk_voltigore_dmg_beam 50 +sk_voltigore_dmg_punch 40 + +// Baby Voltigore +sk_babyvoltigore_health 60 +sk_babyvoltigore_dmg_punch 15 + + // MONSTER WEAPON DAMAGE sk_9mm_bullet 5 sk_9mmAR_bullet 4 diff --git a/src/dlls/Makefile b/src/dlls/Makefile index ed95550..eec103d 100644 --- a/src/dlls/Makefile +++ b/src/dlls/Makefile @@ -50,6 +50,7 @@ OBJ = \ talkmonster.o \ turret.o \ util.o \ + voltigore.o \ weapons.o \ zombie.o diff --git a/src/dlls/cmbasemonster.h b/src/dlls/cmbasemonster.h index 7a5c0a6..7df7a04 100644 --- a/src/dlls/cmbasemonster.h +++ b/src/dlls/cmbasemonster.h @@ -1490,4 +1490,110 @@ public: static const char *pGruntSentences[]; }; +//========================================================= +// Voltigore's energy ball projectile +//========================================================= +#define VOLTIGORE_MAX_BEAMS 8 +class CMVoltigoreEnergyBall : public CMBaseEntity +{ +public: + void Spawn(void); + + static edict_t *Shoot(entvars_t *pevOwner, Vector vecStart, Vector vecVelocity); + void EXPORT BallTouch(edict_t *pOther); + void EXPORT FlyThink(void); + + void CreateBeams(); + void ClearBeams(); + void UpdateBeams(); + + CMBeam* m_pBeam[VOLTIGORE_MAX_BEAMS]; + int m_iBeams; + float m_timeToDie; + +protected: + + void CreateBeam(int nIndex, const Vector& vecPos, int width, int brightness); + void UpdateBeam(int nIndex, const Vector& vecPos, bool show); + void ClearBeam(int nIndex); +}; + +//========================================================= +// Voltigore +//========================================================= +class CMVoltigore : public CMBaseMonster +{ +public: + virtual void Spawn(void); + virtual void Precache(void); + void SetYawSpeed(void); + virtual int Classify(void); + virtual void HandleAnimEvent(MonsterEvent_t *pEvent); + virtual void IdleSound(void); + virtual void PainSound(void); + virtual void DeathSound(void); + virtual void AlertSound(void); + void AttackSound(void); + virtual void StartTask(Task_t *pTask); + virtual BOOL CheckMeleeAttack1(float flDot, float flDist); + virtual BOOL CheckRangeAttack1(float flDot, float flDist); + virtual void RunAI(void); + virtual void GibMonster(); + Schedule_t *GetSchedule(void); + Schedule_t *GetScheduleOfType(int Type); + virtual int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed(entvars_t *pevAttacker, int iGib); + + CUSTOM_SCHEDULES + + float m_flNextZapTime; // last time the voltigore used the spit attack. + BOOL m_fShouldUpdateBeam; + CMBeam* m_pBeam[3]; + CMSprite* m_pBeamGlow; + int m_glowBrightness; + + static const char* pAlertSounds[]; + static const char* pAttackMeleeSounds[]; + static const char* pMeleeHitSounds[]; + static const char* pMeleeMissSounds[]; + static const char* pComSounds[]; + static const char* pDeathSounds[]; + static const char* pFootstepSounds[]; + static const char* pIdleSounds[]; + static const char* pPainSounds[]; + static const char* pGruntSounds[]; + + void CreateBeams(); + void DestroyBeams(); + void UpdateBeams(); + + void CreateGlow(); + void DestroyGlow(); + void GlowUpdate(); + void GlowOff(void); + void GlowOn(int level); +protected: + void GibBeamDamage(); + void PrecacheImpl(char* modelName); + int m_beamTexture; +}; + +//========================================================= +// Baby Voltigore +//========================================================= +class CMBabyVoltigore : public CMVoltigore +{ +public: + void Spawn(void); + void Precache(void); + void HandleAnimEvent(MonsterEvent_t* pEvent); + BOOL CheckMeleeAttack1(float flDot, float flDist); + BOOL CheckRangeAttack1(float flDot, float flDist); + void StartTask(Task_t *pTask); + void Killed(entvars_t *pevAttacker, int iGib); + void GibMonster(); + Schedule_t* GetSchedule(); + Schedule_t* GetScheduleOfType(int Type); +}; + #endif // BASEMONSTER_H diff --git a/src/dlls/dllapi.cpp b/src/dlls/dllapi.cpp index 07c81f9..b0c7c36 100644 --- a/src/dlls/dllapi.cpp +++ b/src/dlls/dllapi.cpp @@ -158,6 +158,8 @@ monster_type_t monster_types[]= "monster_pitdrone", FALSE, "monster_shockroach", FALSE, "monster_shocktrooper", FALSE, + "monster_voltigore", FALSE, + "monster_baby_voltigore", FALSE, "info_node", FALSE, // Nodes "info_node_air", FALSE, "", FALSE @@ -545,7 +547,7 @@ void check_monster_info( edict_t *pPlayer ) MESSAGE_END(); // Delay till next scan - g_NextMessage[ ENTINDEX( pPlayer ) ] = gpGlobals->time + 0.875; + g_NextMessage[ ENTINDEX( pPlayer ) ] = gpGlobals->time + 0.8; } } } @@ -624,6 +626,8 @@ bool spawn_monster(int monster_type, Vector origin, Vector angles, int respawn_i case 21: monsters[monster_index].pMonster = CreateClassPtr((CMPitdrone *)NULL); break; case 22: monsters[monster_index].pMonster = CreateClassPtr((CMShockRoach *)NULL); break; case 23: monsters[monster_index].pMonster = CreateClassPtr((CMStrooper *)NULL); break; + case 24: monsters[monster_index].pMonster = CreateClassPtr((CMVoltigore *)NULL); break; + case 25: monsters[monster_index].pMonster = CreateClassPtr((CMBabyVoltigore *)NULL); break; } if (monsters[monster_index].pMonster == NULL) @@ -1307,6 +1311,8 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) CMPitdrone pitdrone; CMShockRoach shockroach; CMStrooper strooper; + CMVoltigore voltigore; + CMBabyVoltigore babyvoltigore; g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); @@ -1348,6 +1354,8 @@ void mmServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) case 21: pitdrone.Precache(); break; case 22: shockroach.Precache(); break; case 23: strooper.Precache(); break; + case 24: voltigore.Precache(); break; + case 25: babyvoltigore.Precache(); break; } } } diff --git a/src/dlls/skill.cpp b/src/dlls/skill.cpp index 985ed98..26100d6 100644 --- a/src/dlls/skill.cpp +++ b/src/dlls/skill.cpp @@ -89,6 +89,11 @@ skill_cfg_t skill_cfg[] = { {"sk_shocktrooper_kick", &gSkillData.strooperDmgKick}, {"sk_shocktrooper_maxcharge", &gSkillData.strooperMaxCharge}, {"sk_shocktrooper_rchgspeed", &gSkillData.strooperRchgSpeed}, + {"sk_voltigore_health", &gSkillData.voltigoreHealth}, + {"sk_voltigore_dmg_beam", &gSkillData.voltigoreDmgBeam}, + {"sk_voltigore_dmg_punch", &gSkillData.voltigoreDmgPunch}, + {"sk_babyvoltigore_health", &gSkillData.babyVoltigoreHealth}, + {"sk_babyvoltigore_dmg_punch", &gSkillData.babyVoltigoreDmgPunch}, {"sk_12mm_bullet", &gSkillData.monDmg9MM}, {"sk_9mmAR_bullet", &gSkillData.monDmgMP5}, {"sk_9mm_bullet", &gSkillData.monDmg12MM}, @@ -262,6 +267,15 @@ void monster_skill_init(void) gSkillData.strooperMaxCharge = 8.0f; gSkillData.strooperRchgSpeed = 1.0f; + // Voltigore + gSkillData.voltigoreHealth = 320.0f; + gSkillData.voltigoreDmgBeam = 50.0f; + gSkillData.voltigoreDmgPunch = 40.0f; + + // Baby Voltigore + gSkillData.babyVoltigoreHealth = 60.0f; + gSkillData.babyVoltigoreDmgPunch = 15.0f; + // MONSTER WEAPONS gSkillData.monDmg9MM = 5.0f; gSkillData.monDmgMP5 = 4.0f; diff --git a/src/dlls/skill.h b/src/dlls/skill.h index 0165b69..7ea6a78 100644 --- a/src/dlls/skill.h +++ b/src/dlls/skill.h @@ -110,6 +110,13 @@ struct skilldata_t float strooperMaxCharge; float strooperRchgSpeed; + float voltigoreHealth; + float voltigoreDmgBeam; + float voltigoreDmgPunch; + + float babyVoltigoreHealth; + float babyVoltigoreDmgPunch; + // weapons shared by monsters float monDmg9MM; float monDmgMP5; diff --git a/src/dlls/voltigore.cpp b/src/dlls/voltigore.cpp new file mode 100644 index 0000000..6736f0f --- /dev/null +++ b/src/dlls/voltigore.cpp @@ -0,0 +1,1326 @@ +// HUGE thanks to DrBeef for his hlsdk-xash3d-opfor repository! + +/*** +* +* Copyright (c) 1996-2001, 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// voltigore +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cmbase.h" +#include "cmbasemonster.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" +#include "weapons.h" + +#define VOLTIGORE_SPRINT_DIST 256 // how close the voltigore has to get before starting to sprint and refusing to swerve + +#define VOLTIGORE_MAX_BEAMS 8 + +#define VOLTIGORE_CLASSNAME "monster_alien_voltigore" +#define VOLTIGORE_BABY_CLASSNAME "monster_alien_babyvoltigore" + +#define VOLTIGORE_ZAP_RED 180 +#define VOLTIGORE_ZAP_GREEN 16 +#define VOLTIGORE_ZAP_BLUE 255 +#define VOLTIGORE_ZAP_BEAM "sprites/lgtning.spr" +#define VOLTIGORE_ZAP_NOISE 80 +#define VOLTIGORE_ZAP_WIDTH 30 +#define VOLTIGORE_ZAP_BRIGHTNESS 255 +#define VOLTIGORE_ZAP_DISTANCE 512 +#define VOLTIGORE_GLOW_SCALE 0.75f +#define VOLTIGORE_GIB_COUNT 9 +#define VOLTIGORE_GLOW_SPRITE "sprites/blueflare2.spr" + +//========================================================= +// monster-specific schedule types +//========================================================= + + +//========================================================= +// monster-specific tasks +//========================================================= + +enum { + TASK_VOLTIGORE_GET_PATH_TO_ENEMY_CORPSE = LAST_COMMON_TASK + 1 +}; + + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::Spawn(void) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING("voltigore_energy_ball"); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), VOLTIGORE_GLOW_SPRITE); + pev->frame = 0; + pev->scale = VOLTIGORE_GLOW_SCALE; + + UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + + m_iBeams = 0; +} + +//========================================================= +// Purpose: +//========================================================= +edict_t *CMVoltigoreEnergyBall::Shoot(entvars_t *pevOwner, Vector vecStart, Vector vecVelocity) +{ + CMVoltigoreEnergyBall *pEnergyBall = CreateClassPtr((CMVoltigoreEnergyBall *)NULL); + if (pEnergyBall == NULL) + return NULL; + + pEnergyBall->Spawn(); + + UTIL_SetOrigin(pEnergyBall->pev, vecStart); + pEnergyBall->pev->velocity = vecVelocity; + pEnergyBall->pev->owner = ENT(pevOwner); + + pEnergyBall->SetTouch(&CMVoltigoreEnergyBall::BallTouch); + pEnergyBall->SetThink(&CMVoltigoreEnergyBall::FlyThink); + pEnergyBall->pev->nextthink = gpGlobals->time + 0.1; + + return pEnergyBall->edict(); +} + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::BallTouch(edict_t *pOther) +{ + if (m_timeToDie) { + return; + } + TraceResult tr; + if (!pOther->v.takedamage) + { + + // make a splat on the wall + UTIL_TraceLine(pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT(pev), &tr); + UTIL_DecalTrace(&tr, DECAL_SCORCH1 + RANDOM_LONG(0, 1)); + } + else + { + if ( UTIL_IsPlayer( pOther ) ) + UTIL_TakeDamage( pOther, pev, VARS( pev->owner ), gSkillData.voltigoreDmgBeam, DMG_SHOCK|DMG_ALWAYSGIB ); + else if (pOther->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pOther)); + pMonster->TakeDamage( pev, VARS( pev->owner ), gSkillData.voltigoreDmgBeam, DMG_SHOCK|DMG_ALWAYSGIB ); + } + } + pev->velocity = Vector(0,0,0); + + m_timeToDie = gpGlobals->time + 0.3; + SetTouch( NULL ); +} + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::FlyThink(void) +{ + pev->nextthink = gpGlobals->time + 0.1; + if (m_timeToDie) + { + edict_t *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere(pEntity, pev->origin, 32)) != NULL) + { + if (pEntity->v.takedamage && !FClassnameIs(pEntity, "monster_alien_voltigore") && !FClassnameIs(pEntity, "monster_alien_babyvoltigore")) + { + if ( UTIL_IsPlayer( pEntity ) ) + UTIL_TakeDamage( pEntity, pev, pev, gSkillData.voltigoreDmgBeam/5, DMG_SHOCK ); + else if (pEntity->v.euser4 != NULL) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEntity)); + pMonster->TakeDamage( pev, pev, gSkillData.voltigoreDmgBeam/5, DMG_SHOCK ); + } + } + } + + if (m_timeToDie <= gpGlobals->time) + { + ClearBeams(); + SetThink(&CMVoltigoreEnergyBall::SUB_Remove); + pev->nextthink = gpGlobals->time; + } + } + else + { + if (m_iBeams) + UpdateBeams(); + else + CreateBeams(); + } +} + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::CreateBeam(int nIndex, const Vector& vecPos, int width, int brightness) +{ + m_pBeam[nIndex] = CMBeam::BeamCreate(VOLTIGORE_ZAP_BEAM, width); + if (!m_pBeam[nIndex]) + return; + + m_pBeam[nIndex]->PointEntInit(vecPos, entindex()); + m_pBeam[nIndex]->SetColor(VOLTIGORE_ZAP_RED, VOLTIGORE_ZAP_GREEN, VOLTIGORE_ZAP_BLUE); + m_pBeam[nIndex]->SetBrightness(brightness); + m_pBeam[nIndex]->SetNoise(VOLTIGORE_ZAP_NOISE); + //m_pBeam[nIndex]->SetFlags( SF_BEAM_SHADEIN ); +} + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::UpdateBeam(int nIndex, const Vector& vecPos, bool show) +{ + if (!m_pBeam[nIndex]) + return; + m_pBeam[nIndex]->SetBrightness(show ? VOLTIGORE_ZAP_BRIGHTNESS : 0); + m_pBeam[nIndex]->SetStartPos(vecPos); + m_pBeam[nIndex]->SetEndEntity(entindex()); + m_pBeam[nIndex]->RelinkBeam(); +} + +//========================================================= +// Purpose: +//========================================================= +void CMVoltigoreEnergyBall::ClearBeam(int nIndex) +{ + if (m_pBeam[nIndex]) + { + UTIL_Remove(m_pBeam[nIndex]->edict()); + m_pBeam[nIndex] = NULL; + } +} + +//========================================================= +// CreateBeams - create all beams +//========================================================= +void CMVoltigoreEnergyBall::CreateBeams() +{ + for (int i = 0; i < VOLTIGORE_MAX_BEAMS; ++i) + { + CreateBeam(i, pev->origin, VOLTIGORE_ZAP_WIDTH, VOLTIGORE_ZAP_BRIGHTNESS ); + } + m_iBeams = VOLTIGORE_MAX_BEAMS; +} + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CMVoltigoreEnergyBall::ClearBeams() +{ + for (int i = 0; i < VOLTIGORE_MAX_BEAMS; ++i) + { + ClearBeam( i ); + } + m_iBeams = 0; +} + + +void CMVoltigoreEnergyBall::UpdateBeams() +{ + int i, j; + + TraceResult tr; + const Vector vecSrc = pev->origin; + const Vector directionVector = pev->velocity.Normalize(); + const int baseDistance = VOLTIGORE_ZAP_DISTANCE; + for (i = 0; i < m_iBeams; ++i) + { + for (j = 0; j < 3; ++j) + { + const float randomX = RANDOM_FLOAT(-1, 0.1); + const float randomY = RANDOM_FLOAT(-1, 0.1); + //ALERT(at_console, "Randomize: %f %f\n", randomX, randomY); + Vector vecTarget = vecSrc + Vector( + directionVector.x * randomX, + directionVector.y * randomY, + RANDOM_LONG(0, 1) ? 1 : -1 + ) * baseDistance; + TraceResult tr1; + UTIL_TraceLine(vecSrc, vecTarget, ignore_monsters, ENT(pev), &tr1); + if (tr1.flFraction != 1.0f) { + tr = tr1; + break; + } + } + + // Update the target position of the beam. + UpdateBeam(i, tr.vecEndPos, tr.flFraction != 1.0f); + } +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define VOLTIGORE_AE_THROW ( 1 ) +#define VOLTIGORE_AE_PUNCH_BOTH ( 12 ) +#define VOLTIGORE_AE_PUNCH_SINGLE ( 13 ) +#define VOLTIGORE_AE_GIB ( 2002 ) + +const char* CMVoltigore::pAlertSounds[] = +{ + "voltigore/voltigore_alert1.wav", + "voltigore/voltigore_alert2.wav", + "voltigore/voltigore_alert3.wav", +}; + +const char* CMVoltigore::pAttackMeleeSounds[] = +{ + "voltigore/voltigore_attack_melee1.wav", + "voltigore/voltigore_attack_melee2.wav", +}; + +const char* CMVoltigore::pMeleeHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char* CMVoltigore::pMeleeMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char* CMVoltigore::pComSounds[] = +{ + "voltigore/voltigore_communicate1.wav", + "voltigore/voltigore_communicate2.wav", + "voltigore/voltigore_communicate3.wav", +}; + + +const char* CMVoltigore::pDeathSounds[] = +{ + "voltigore/voltigore_die1.wav", + "voltigore/voltigore_die2.wav", + "voltigore/voltigore_die3.wav", +}; + +const char* CMVoltigore::pFootstepSounds[] = +{ + "voltigore/voltigore_footstep1.wav", + "voltigore/voltigore_footstep2.wav", + "voltigore/voltigore_footstep3.wav", +}; + +const char* CMVoltigore::pIdleSounds[] = +{ + "voltigore/voltigore_idle1.wav", + "voltigore/voltigore_idle2.wav", + "voltigore/voltigore_idle3.wav", +}; + +const char* CMVoltigore::pPainSounds[] = +{ + "voltigore/voltigore_pain1.wav", + "voltigore/voltigore_pain2.wav", + "voltigore/voltigore_pain3.wav", + "voltigore/voltigore_pain4.wav", +}; + +const char* CMVoltigore::pGruntSounds[] = +{ + "voltigore/voltigore_run_grunt1.wav", + "voltigore/voltigore_run_grunt2.wav", +}; + +//========================================================= +// TakeDamage - overridden for voltigore so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CMVoltigore::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + float flDist; + Vector vecApex; + + // if the voltigore is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, + // it will swerve. (whew). + if (m_hEnemy != 0 && IsMoving() && pevAttacker == VARS(m_hEnemy)) + { + flDist = (pev->origin - m_hEnemy->v.origin).Length2D(); + + if (flDist > VOLTIGORE_SPRINT_DIST) + { + flDist = (pev->origin - m_Route[m_iRouteIndex].vecLocation).Length2D();// reusing flDist. + + if (FTriangulate(pev->origin, m_Route[m_iRouteIndex].vecLocation, flDist * 0.5, m_hEnemy, &vecApex)) + { + InsertWaypoint(vecApex, bits_MF_TO_DETOUR | bits_MF_DONT_SIMPLIFY); + } + } + } + + // Ain't something missing here? -Giegue + + return CMBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CMVoltigore::CheckRangeAttack1(float flDot, float flDist) +{ + if (IsMoving() && flDist >= 512) + { + // voltigore will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if (flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextZapTime) + { + if (m_hEnemy != 0) + { + if (fabs(pev->origin.z - m_hEnemy->v.origin.z) > 256) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if (IsMoving()) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextZapTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextZapTime = gpGlobals->time + 2; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +//========================================================= +void CMVoltigore::RunAI(void) +{ + CMBaseMonster::RunAI(); + + if (m_fShouldUpdateBeam) + { + UpdateBeams(); + } + + GlowUpdate(); +} + +void CMVoltigore::GibMonster() +{ + GibBeamDamage(); + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM ); + if( CVAR_GET_FLOAT( "violence_agibs" ) != 0 ) // Should never get here, but someone might call it directly + { + CMGib::SpawnRandomGibs( pev, VOLTIGORE_GIB_COUNT, "models/vgibs.mdl", 0 ); // Throw alien gibs + } + SetThink( &CMBaseEntity::SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// CheckMeleeAttack1 - voltigore is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CMVoltigore::CheckMeleeAttack1(float flDot, float flDist) +{ + if (flDist <= 120 && flDot >= 0.7) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMVoltigore::Classify(void) +{ + if ( m_iClassifyOverride == -1 ) // helper + return CLASS_NONE; + else if ( m_iClassifyOverride > 0 ) + return m_iClassifyOverride; // override + + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// IdleSound +//========================================================= +void CMVoltigore::IdleSound(void) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), 1, ATTN_NORM); +} + +//========================================================= +// PainSound +//========================================================= +void CMVoltigore::PainSound(void) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), 1, ATTN_NORM, 0, RANDOM_LONG(85, 120)); +} + +//========================================================= +// AlertSound +//========================================================= +void CMVoltigore::AlertSound(void) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), 1, ATTN_NORM, 0, RANDOM_LONG(140, 160)); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMVoltigore::SetYawSpeed(void) +{ + int ys; + + ys = 0; + + switch (m_Activity) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMVoltigore::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch (pEvent->event) + { + case VOLTIGORE_AE_THROW: + { + // SOUND HERE! + Vector vecSpitDir; + + UTIL_MakeVectors(pev->angles); + + Vector vecSpitOrigin, vecAngles; + GetAttachment(3, vecSpitOrigin, vecAngles); + vecSpitDir = ShootAtEnemy(vecSpitOrigin); + + // do stuff for this event. + //AttackSound(); + + CMVoltigoreEnergyBall::Shoot(pev, vecSpitOrigin, vecSpitDir * 1000); + + // turn the beam glow off. + DestroyBeams(); + + GlowOff(); + + m_fShouldUpdateBeam = FALSE; + } + break; + + + case VOLTIGORE_AE_PUNCH_SINGLE: + { + // SOUND HERE! + edict_t *pHurt = CheckTraceHullAttack(120, gSkillData.voltigoreDmgPunch, DMG_CLUB); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.z = -15; + pHurt->v.punchangle.x = 15; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * -150; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 100; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeHitSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood( vecArmPos, BloodColor(), 25 );// a little surface blood. + } + else + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeMissSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + } + } + break; + + case VOLTIGORE_AE_PUNCH_BOTH: + { + // SOUND HERE! + edict_t *pHurt = CheckTraceHullAttack(120, gSkillData.voltigoreDmgPunch, DMG_CLUB); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.x = 20; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_forward * 150; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 100; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeHitSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood( vecArmPos, BloodColor(), 25 );// a little surface blood. + } + else + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeMissSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + } + } + break; + + case VOLTIGORE_AE_GIB: + { + pev->health = 0; + GibMonster(); + } + break; + + default: + CMBaseMonster::HandleAnimEvent(pEvent); + } +} + +//========================================================= +// Spawn +//========================================================= +void CMVoltigore::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev), "models/voltigore.mdl"); + UTIL_SetSize(pev, Vector(-80, -80, 0), Vector(80, 80, 90)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.voltigoreHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextZapTime = gpGlobals->time; + + + m_fShouldUpdateBeam = FALSE; + m_pBeamGlow = NULL; + + GlowOff(); + + // Create glow. + CreateGlow(); + + MonsterInit(); + pev->view_ofs = Vector(0, 0, 84); + + pev->classname = MAKE_STRING("monster_alien_voltigore"); + if ( strlen( STRING( m_szMonsterName ) ) == 0 ) + { + // default name + m_szMonsterName = MAKE_STRING( "Voltigore" ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMVoltigore::Precache() +{ + PrecacheImpl("models/voltigore.mdl"); + PRECACHE_MODEL("models/vgibs.mdl"); +} + +void CMVoltigore::PrecacheImpl(char *modelName) +{ + PRECACHE_MODEL(modelName); + + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pAttackMeleeSounds); + PRECACHE_SOUND_ARRAY(pMeleeHitSounds); + PRECACHE_SOUND_ARRAY(pMeleeMissSounds); + PRECACHE_SOUND_ARRAY(pComSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pFootstepSounds); + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pGruntSounds); + + PRECACHE_SOUND("voltigore/voltigore_attack_shock.wav"); + PRECACHE_SOUND("voltigore/voltigore_eat.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_beamTexture = PRECACHE_MODEL(VOLTIGORE_ZAP_BEAM); + PRECACHE_MODEL(VOLTIGORE_GLOW_SPRITE); + + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_MODEL("sprites/blueflare2.spr"); +} + +//========================================================= +// DeathSound +//========================================================= +void CMVoltigore::DeathSound(void) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), 1, ATTN_NORM); +} + +//========================================================= +// AttackSound +//========================================================= +void CMVoltigore::AttackSound(void) +{ + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "voltigore/voltigore_attack_shock.wav", 1, ATTN_NORM); +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlVoltigoreRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slVoltigoreRangeAttack1[] = +{ + { + tlVoltigoreRangeAttack1, + ARRAYSIZE(tlVoltigoreRangeAttack1), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + // Attack animation is quite long, so it's better to not stop it when enemy hides + //bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Voltigore Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlVoltigoreChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slVoltigoreChaseEnemy[] = +{ + { + tlVoltigoreChaseEnemy1, + ARRAYSIZE(tlVoltigoreChaseEnemy1), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + 0, + "Voltigore Chase Enemy" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlVoltigoreVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_VOLTIGORE_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, +}; + +Schedule_t slVoltigoreVictoryDance[] = +{ + { + tlVoltigoreVictoryDance, + ARRAYSIZE( tlVoltigoreVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "VoltigoreVictoryDance" + }, +}; + +DEFINE_CUSTOM_SCHEDULES(CMVoltigore) +{ + slVoltigoreRangeAttack1, + slVoltigoreChaseEnemy, + slVoltigoreVictoryDance +}; + +IMPLEMENT_CUSTOM_SCHEDULES(CMVoltigore, CMBaseMonster) + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CMVoltigore::GetSchedule(void) +{ + switch (m_MonsterState) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if (HasConditions(bits_COND_ENEMY_DEAD)) + { + // call base class, all code to handle dead enemies is centralized there. + return CMBaseMonster::GetSchedule(); + } + + if (HasConditions(bits_COND_NEW_ENEMY)) + { + return GetScheduleOfType(SCHED_WAKE_ANGRY); + } + + if( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + return GetScheduleOfType(SCHED_CHASE_ENEMY); + } + + if (HasConditions(bits_COND_CAN_RANGE_ATTACK1)) + { + return GetScheduleOfType(SCHED_RANGE_ATTACK1); + } + + if (HasConditions(bits_COND_CAN_MELEE_ATTACK1)) + { + return GetScheduleOfType(SCHED_MELEE_ATTACK1); + } + + return GetScheduleOfType(SCHED_CHASE_ENEMY); + + break; + } + } + + return CMBaseMonster::GetSchedule(); +} + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CMVoltigore::GetScheduleOfType(int Type) +{ + switch (Type) + { + case SCHED_RANGE_ATTACK1: + return &slVoltigoreRangeAttack1[0]; + break; + case SCHED_CHASE_ENEMY: + return &slVoltigoreChaseEnemy[0]; + break; + case SCHED_VICTORY_DANCE: + return &slVoltigoreVictoryDance[0]; + break; + } + + return CMBaseMonster::GetScheduleOfType(Type); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for voltigore because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CMVoltigore::StartTask(Task_t *pTask) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + GlowOff(); + DestroyBeams(); + m_fShouldUpdateBeam = FALSE; + + switch (pTask->iTask) + { + case TASK_RANGE_ATTACK1: + { + CreateBeams(); + + GlowOn( 255 ); + m_fShouldUpdateBeam = TRUE; + + // Play the beam 'glow' sound. + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "debris/beamstart1.wav", 1, ATTN_NORM, 0, PITCH_HIGH); + + CMBaseMonster::StartTask(pTask); + } + break; + case TASK_GET_PATH_TO_ENEMY: + { + if (BuildRoute(m_hEnemy->v.origin, bits_MF_TO_ENEMY, m_hEnemy)) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT(at_aiconsole, "GetPathToEnemy failed!!\n"); + TaskFail(); + } + } + break; + case TASK_VOLTIGORE_GET_PATH_TO_ENEMY_CORPSE: + UTIL_MakeVectors( pev->angles ); + if( BuildRoute( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT( at_aiconsole, "VoltigoreGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + default: + CMBaseMonster::StartTask(pTask); + break; + } +} + +void CMVoltigore::Killed(entvars_t *pevAttacker, int iGib) +{ + DestroyBeams(); + DestroyGlow(); + + int iTimes = 0; + int iDrawn = 0; + const int iBeams = VOLTIGORE_MAX_BEAMS; + while( iDrawn < iBeams && iTimes < ( iBeams * 3 ) ) + { + TraceResult tr; + const Vector vecOrigin = Center(); + const Vector vecDest = VOLTIGORE_ZAP_DISTANCE * ( Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize() ); + UTIL_TraceLine( vecOrigin, vecOrigin + vecDest, ignore_monsters, ENT( pev ), &tr ); + if( tr.flFraction != 1.0 ) + { + // we hit something. + iDrawn++; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( vecOrigin.x ); + WRITE_COORD( vecOrigin.y ); + WRITE_COORD( vecOrigin.z ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( m_beamTexture ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( RANDOM_LONG( 8, 10 ) ); // life + WRITE_BYTE( VOLTIGORE_ZAP_WIDTH ); // width + WRITE_BYTE( VOLTIGORE_ZAP_NOISE ); // noise + WRITE_BYTE( VOLTIGORE_ZAP_RED ); // r, g, b + WRITE_BYTE( VOLTIGORE_ZAP_GREEN); // r, g, b + WRITE_BYTE( VOLTIGORE_ZAP_BLUE ); // r, g, b + WRITE_BYTE( VOLTIGORE_ZAP_BRIGHTNESS ); // brightness + WRITE_BYTE( 35 ); // speed + MESSAGE_END(); + } + iTimes++; + } + + CMBaseMonster::Killed(pevAttacker, iGib); +} + +void CMVoltigore::GibBeamDamage() +{ + edict_t *pEntity = NULL; + // iterate on all entities in the vicinity. + const float attackRadius = gSkillData.voltigoreDmgBeam * 10; + float flAdjustedDamage = gSkillData.voltigoreDmgBeam/2; + while( ( pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, attackRadius ) ) != NULL ) + { + if( pEntity->v.takedamage != DAMAGE_NO ) + { + if( pEntity->v.classname != pev->classname && !FClassnameIs( pEntity, "monster_alien_babyvoltigore" ) ) + { + // voltigores don't hurt other (baby) voltigores on death + const float flDist = ( UTIL_Center( pEntity ) - pev->origin ).Length(); + + flAdjustedDamage -= ( flDist / attackRadius ) * flAdjustedDamage; + + if( !UTIL_FVisible( pEntity, this->edict() ) ) + { + if( UTIL_IsPlayer( pEntity ) ) + { + // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still + // take the residual damage if they don't totally leave the voltigore's effective radius. We restrict it to clients + // so that monsters in other parts of the level don't take the damage and get pissed. + flAdjustedDamage *= 0.5; + } + else if( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) ) + { + // do not hurt nonclients through walls, but allow damage to be done to breakables + flAdjustedDamage = 0; + } + } + + if( flAdjustedDamage > 0 ) + { + if ( UTIL_IsPlayer( pEntity ) ) + UTIL_TakeDamage( pEntity, pev, pev, flAdjustedDamage, DMG_SHOCK ); + else if ( pEntity->v.euser4 != NULL ) + { + CMBaseMonster *pMonster = GetClassPtr((CMBaseMonster *)VARS(pEntity)); + pMonster->TakeDamage( pev, pev, flAdjustedDamage, DMG_SHOCK ); + } + } + } + } + } +} + +void CMVoltigore::CreateBeams() +{ + Vector vecStart, vecEnd, vecAngles; + GetAttachment(3, vecStart, vecAngles); + + for (int i = 0; i < 3; i++) + { + m_pBeam[i] = CMBeam::BeamCreate(VOLTIGORE_ZAP_BEAM, VOLTIGORE_ZAP_WIDTH); + if (!m_pBeam[i]) + return; + + GetAttachment(i, vecEnd, vecAngles); + + m_pBeam[i]->PointsInit(vecStart, vecEnd); + m_pBeam[i]->SetColor(VOLTIGORE_ZAP_RED, VOLTIGORE_ZAP_GREEN, VOLTIGORE_ZAP_BLUE); + m_pBeam[i]->SetBrightness(VOLTIGORE_ZAP_BRIGHTNESS); + m_pBeam[i]->SetNoise(VOLTIGORE_ZAP_NOISE); + } +} + +void CMVoltigore::DestroyBeams() +{ + for (int i = 0; i < 3; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove(m_pBeam[i]->edict()); + m_pBeam[i] = NULL; + } + } +} + +void CMVoltigore::UpdateBeams() +{ + Vector vecStart, vecEnd, vecAngles; + GetAttachment(3, vecStart, vecAngles); + + for (int i = 0; i < 3; i++) + { + if (!m_pBeam[i]) { + continue; + } + GetAttachment(i, vecEnd, vecAngles); + m_pBeam[i]->SetStartPos(vecStart); + m_pBeam[i]->SetEndPos(vecEnd); + m_pBeam[i]->RelinkBeam(); + } +} + +void CMVoltigore::CreateGlow() +{ + m_pBeamGlow = CMSprite::SpriteCreate(VOLTIGORE_GLOW_SPRITE, pev->origin, FALSE); + m_pBeamGlow->SetTransparency(kRenderTransAdd, 255, 255, 255, 0, kRenderFxNoDissipation); + m_pBeamGlow->SetAttachment(edict(), 4); + m_pBeamGlow->SetScale(VOLTIGORE_GLOW_SCALE); +} + +void CMVoltigore::DestroyGlow() +{ + if (m_pBeamGlow) + { + UTIL_Remove(m_pBeamGlow->edict()); + m_pBeamGlow = NULL; + } +} + +void CMVoltigore::GlowUpdate() +{ + if (m_pBeamGlow) + { + m_pBeamGlow->pev->renderamt = UTIL_Approach(m_glowBrightness, m_pBeamGlow->pev->renderamt, 100); + if (m_pBeamGlow->pev->renderamt == 0) + m_pBeamGlow->pev->effects |= EF_NODRAW; + else + m_pBeamGlow->pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin(m_pBeamGlow->pev, pev->origin); + } +} + +void CMVoltigore::GlowOff(void) +{ + m_glowBrightness = 0; +} + +void CMVoltigore::GlowOn(int level) +{ + m_glowBrightness = level; +} + + +//========================================================= +// CBabyAlienVoltigore +//========================================================= + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BABY_VOLTIGORE_AE_RUN ( 14 ) + +//========================================================= +// Spawn +//========================================================= +void CMBabyVoltigore::Spawn() +{ + Precache(); + + SET_MODEL(ENT(pev), "models/baby_voltigore.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.babyVoltigoreHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextZapTime = gpGlobals->time; + + MonsterInit(); + pev->view_ofs = Vector(0, 0, 32); + + pev->classname = MAKE_STRING("monster_alien_babyvoltigore"); + if ( strlen( STRING( m_szMonsterName ) ) == 0 ) + { + // default name + m_szMonsterName = MAKE_STRING( "Baby Voltigore" ); + } +} + +//========================================================= +//========================================================= +void CMBabyVoltigore::Precache(void) +{ + PrecacheImpl("models/baby_voltigore.mdl"); +} + +void CMBabyVoltigore::HandleAnimEvent(MonsterEvent_t* pEvent) +{ + switch (pEvent->event) + { + case BABY_VOLTIGORE_AE_RUN: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pFootstepSounds), RANDOM_FLOAT(0.5, 0.6), ATTN_NORM, 0, RANDOM_LONG(85, 120)); + break; + + case VOLTIGORE_AE_PUNCH_SINGLE: + { + edict_t *pHurt = CheckTraceHullAttack(70, gSkillData.babyVoltigoreDmgPunch, DMG_CLUB | DMG_ALWAYSGIB); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.z = -10; + pHurt->v.punchangle.x = 10; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_right * -100; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 50; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeHitSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood( vecArmPos, BloodColor(), 25 );// a little surface blood. + } + else + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeMissSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + } + } + break; + + case VOLTIGORE_AE_PUNCH_BOTH: + { + edict_t *pHurt = CheckTraceHullAttack(70, gSkillData.babyVoltigoreDmgPunch, DMG_CLUB | DMG_ALWAYSGIB); + if (pHurt) + { + if (FBitSet(pHurt->v.flags, FL_MONSTER|FL_CLIENT)) + { + pHurt->v.punchangle.x = 15; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_forward * 100; + pHurt->v.velocity = pHurt->v.velocity + gpGlobals->v_up * 50; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeHitSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood( vecArmPos, BloodColor(), 25 );// a little surface blood. + } + else + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pMeleeMissSounds), RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + } + } + break; + default: + CMVoltigore::HandleAnimEvent(pEvent); + break; + } +} + +BOOL CMBabyVoltigore::CheckMeleeAttack1(float flDot, float flDist) +{ + return CMBaseMonster::CheckMeleeAttack1(flDot, flDist); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for voltigore because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CMBabyVoltigore::StartTask(Task_t *pTask) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch (pTask->iTask) + { + case TASK_MELEE_ATTACK1: + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackMeleeSounds), RANDOM_FLOAT(0.5, 0.6), ATTN_NONE, 0, RANDOM_LONG(110, 120)); + CMBaseMonster::StartTask(pTask); + } + break; + default: + CMBaseMonster::StartTask(pTask); + break; + } +} + +void CMBabyVoltigore::Killed(entvars_t* pevAttacker, int iGib) +{ + DestroyBeams(); + CMBaseMonster::Killed(pevAttacker, iGib); +} + +void CMBabyVoltigore::GibMonster() +{ + CMBaseMonster::GibMonster(); +} + +BOOL CMBabyVoltigore::CheckRangeAttack1(float flDot, float flDist) +{ + return FALSE; +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CMBabyVoltigore::GetSchedule(void) +{ + switch (m_MonsterState) + { + case MONSTERSTATE_COMBAT: + { + // dead enemy + if (HasConditions(bits_COND_ENEMY_DEAD)) + { + // call base class, all code to handle dead enemies is centralized there. + return CMBaseMonster::GetSchedule(); + } + + if (HasConditions(bits_COND_NEW_ENEMY)) + { + return GetScheduleOfType(SCHED_WAKE_ANGRY); + } + + if (HasConditions(bits_COND_CAN_MELEE_ATTACK1)) + { + return GetScheduleOfType(SCHED_MELEE_ATTACK1); + } + + return GetScheduleOfType(SCHED_CHASE_ENEMY); + + break; + } + } + + return CMBaseMonster::GetSchedule(); +} + +Schedule_t *CMBabyVoltigore::GetScheduleOfType(int Type) +{ + switch (Type) { + // For some cryptic reason baby voltigore tries to start the range attack even though its model does not have sequence with range attack activity. + // This hack is for preventing baby voltigore to do this. + case SCHED_RANGE_ATTACK1: + return &slVoltigoreChaseEnemy[0]; + break; + default: + return CMVoltigore::GetScheduleOfType(Type); + break; + } +}