Files
HL2Overcharged/game/server/overcharged/npc_voltigore.cpp
2025-05-21 21:20:08 +03:00

1460 lines
39 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Voltigore & Voltigore baby op4 Remake
//
// 9 sept 2021
//=============================================================================//
#include "cbase.h"
#include "beam_shared.h"
#include "Sprite.h"
#include "ai_default.h"
#include "ai_task.h"
#include "ai_schedule.h"
#include "ai_node.h"
#include "ai_hull.h"
#include "ai_hint.h"
#include "ai_memory.h"
#include "ai_route.h"
#include "ai_motor.h"
#include "overcharged/npc_voltigore.h"
#include "soundent.h"
#include "game.h"
#include "npcevent.h"
#include "entitylist.h"
#include "activitylist.h"
#include "animation.h"
#include "basecombatweapon.h"
#include "IEffects.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "ammodef.h"
#include "shake.h"
#include "decals.h"
#include "particle_smokegrenade.h"
#include "gib.h"
#include "func_break.h"
#include "hl2_shareddefs.h" //added
#include "particle_parse.h"
#include "prop_combine_ball.h"
#include "physics_prop_ragdoll.h" //post delayed anim
#include "npc_antlion.h" //flip antlions in volt baby rare attack
//=========================================================
// voltigore
//=========================================================
#define volt_MELEE_ATTACKDIST 100.0f // melee attack distance //120 def // MELEE
#define volt_MELEE_ATTACKDIST_BABY 50.0f
// volt animation events
#define volt_AE_SLASH_LEFT 1
//#define volt_AE_BEAM_ATTACK_RIGHT 2 // unused
#define volt_AE_LEFT_FOOT 3
#define volt_AE_RIGHT_FOOT 4
#define volt_AE_voenergy 5
#define volt_AE_BREATHE 6
#define volt_AE_EXPLODE 7
// ALLOWED damage types
#define volt_DAMAGE ( DMG_ENERGYBEAM | DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BURN | DMG_VEHICLE | DMG_CLUB | DMG_SHOCK | DMG_ENERGYBEAM | DMG_AIRBOAT | DMG_BUCKSHOT | DMG_GAUSS | DMG_BLAST | DMG_NEVERGIB )
//nevergib - crossbow
// dont use this
// DMG_DISSOLVE | DMG_DISSOLVE_EGON | DMG_DISSOLVE_EGON_ELECTRICAL
#define volt_ELSPHERE_DIST 330 // BJ: SHOOT ELECTRIC SPHERE DIST
ConVar sk_voltigore_health ( "sk_voltigore_health", "0" );
ConVar sk_voltigore_dmg_slash( "sk_voltigore_dmg_slash", "0" );
ConVar sk_voltigore_dmg_electric_prepare("sk_voltigore_dmg_electric_prepare", "0"); //pre shot dmg inside volt class
ConVar sk_voltigore_dmg_electric_sphere( "sk_voltigore_dmg_electric_sphere", "0" );
ConVar sk_voltigore_dmg_electric_sphere_explosion("sk_voltigore_dmg_electric_sphere_explosion", "0");
ConVar sk_voltigore_dmg_electric_sphere_speed("sk_voltigore_dmg_electric_sphere_speed", "0");
ConVar sk_voltigore_dmg_bigexplode("sk_voltigore_dmg_bigexplode", "0");
ConVar sk_voltigore_allow_extra_electric_throw("sk_voltigore_allow_extra_electric_throw", "0", FCVAR_ARCHIVE);
ConVar sk_voltigore_baby_health("sk_voltigore_baby_health", "0");
ConVar sk_voltigore_baby_dmg_slash("sk_voltigore_baby_dmg_slash", "0");
ConVar sk_voltigore_baby_dmg_electric_prepare("sk_voltigore_baby_dmg_electric_prepare", "0");
ConVar sk_voltigore_baby_dmg_electric_beam("sk_voltigore_baby_dmg_electric_beam", "0");
enum
{
TASK_SOUNDS_ATTACK = LAST_SHARED_TASK,
TASK_ELECTRO_SHOT,
};
enum
{
SCHED_volt_ELECTRO = LAST_SHARED_SCHEDULE,
SCHED_volt_SWIPE,
SCHED_volt_CHASE_ENEMY,
SCHED_volt_CHASE_ENEMY_FAILED,
};
LINK_ENTITY_TO_CLASS( npc_voltigore, CNPC_voltigore );
LINK_ENTITY_TO_CLASS( npc_voltigore_baby, CNPC_voltigore); //BJ: baby class added
IMPLEMENT_SERVERCLASS_ST(CNPC_voltigore, DT_NPC_voltigore) // BJ: Bleed added
SendPropInt(SENDINFO(m_iBleedingLevel), 2, SPROP_UNSIGNED),
END_SEND_TABLE() //end
BEGIN_DATADESC( CNPC_voltigore )
DEFINE_FIELD( m_seeTime_vo, FIELD_TIME ),
DEFINE_FIELD( m_flameTime_vo, FIELD_TIME ),
DEFINE_FIELD( m_streakTime_vo, FIELD_TIME ),
DEFINE_FIELD( m_flDmgTime_vo, FIELD_TIME ),
DEFINE_FIELD( m_painSoundTime_vo, FIELD_TIME ),
DEFINE_FIELD( m_fIsBabyVolt, FIELD_BOOLEAN),
DEFINE_FIELD( m_PresetDamage, FIELD_FLOAT ),
DEFINE_FIELD( m_MeleeDistRecalc, FIELD_FLOAT ),
DEFINE_FIELD(m_iBleedingLevel, FIELD_CHARACTER), // BJ: Bleed added
END_DATADESC()
static void MoveToGround( Vector *position, CBaseEntity *ignore, const Vector &mins, const Vector &maxs )
{
trace_t tr;
// Find point on floor where enemy would stand at chasePosition
Vector floor = *position;
floor.z -= 1024;
UTIL_TraceHull( *position, floor, mins, maxs, MASK_NPCSOLID, ignore, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1 )
{
position->z = tr.endpos.z;
}
}
//============================================================================================================= electric sphere ent
class CVoltElectricSphere : public CBaseCombatCharacter
{
DECLARE_CLASS(CVoltElectricSphere, CBaseCombatCharacter);
public:
CVoltElectricSphere() { };
~CVoltElectricSphere();
Class_T Classify(void) { return CLASS_NONE; }
public:
void Spawn(void);
void Precache(void);
void BubbleThink(void);
void BoltTouch(CBaseEntity *pOther);
bool CreateVPhysics(void);
unsigned int PhysicsSolidMaskForEntity() const;
static CVoltElectricSphere *BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CAI_BaseNPC *pentOwner = NULL);
protected:
bool ShouldDrawWaterImpacts(const trace_t &shot_trace);
int m_nLightningSprite1;
bool CreateSprites(void);
int m_iDamage;
DECLARE_DATADESC();
DECLARE_SERVERCLASS();
};
LINK_ENTITY_TO_CLASS(VoltigoreElectricSphere, CVoltElectricSphere);
BEGIN_DATADESC(CVoltElectricSphere)
DEFINE_FUNCTION(BubbleThink),
DEFINE_FUNCTION(BoltTouch),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST(CVoltElectricSphere, DT_VoltElectricSphere)
END_SEND_TABLE()
#define VSPHERE_MODEL "models/blackout.mdl" // sphere model preset
CVoltElectricSphere *CVoltElectricSphere::BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CAI_BaseNPC *pentOwner)
{
CVoltElectricSphere *pVoltElectricSphere = (CVoltElectricSphere *)CreateEntityByName("VoltigoreElectricSphere");
UTIL_SetOrigin(pVoltElectricSphere, vecOrigin);
pVoltElectricSphere->SetAbsAngles(angAngles);
pVoltElectricSphere->Spawn();
pVoltElectricSphere->SetOwnerEntity(pentOwner);
pVoltElectricSphere->m_iDamage = iDamage;
return pVoltElectricSphere;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CVoltElectricSphere::~CVoltElectricSphere(void)
{
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CVoltElectricSphere::CreateVPhysics(void)
{
// Create the object in the physics system
VPhysicsInitNormal(SOLID_BBOX, FSOLID_NOT_STANDABLE, false);
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
unsigned int CVoltElectricSphere::PhysicsSolidMaskForEntity() const
{
return (BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX) & ~CONTENTS_GRATE;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CVoltElectricSphere::CreateSprites(void)
{
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CVoltElectricSphere::Spawn(void)
{
Precache();
SetModel(VSPHERE_MODEL);
SetMoveType(MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM);
UTIL_SetSize(this, -Vector(10, 10, 10), Vector(10, 10, 10));
SetSolid(SOLID_BBOX);
//AddEffects( EF_NODRAW );
AddEffects(EF_NOSHADOW);
UpdateWaterState();
SetTouch(&CVoltElectricSphere::BoltTouch);
SetThink(&CVoltElectricSphere::BubbleThink);
SetNextThink(gpGlobals->curtime + 0.1f);
CreateSprites();
// 2 particles. one line one electric
DispatchParticleEffect("volt_sphere_longfire", PATTACH_ABSORIGIN_FOLLOW, this);
DispatchParticleEffect("volt_sphere_particles", PATTACH_ABSORIGIN_FOLLOW, this);
}
void CVoltElectricSphere::Precache(void)
{
PrecacheModel(VSPHERE_MODEL);
// 2 particles precache
PrecacheParticleSystem("volt_sphere_longfire");
PrecacheParticleSystem("volt_sphere_particles");
PrecacheScriptSound("NPC_Voltigore.ElectricSphereExplode");
m_nLightningSprite1 = PrecacheModel("sprites/bluelaser1.vmt");
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CVoltElectricSphere::BoltTouch(CBaseEntity *pOther)
{
if (!pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS))
return;
if (pOther->m_takedamage != DAMAGE_NO)
{
trace_t tr, tr2;
tr = BaseClass::GetTouchTrace();
Vector vecNormalizedVel = GetAbsVelocity();
ShouldDrawWaterImpacts(tr);
ClearMultiDamage();
VectorNormalize(vecNormalizedVel);
CTakeDamageInfo dmgInfo(this, GetOwnerEntity(), sk_voltigore_dmg_electric_sphere.GetFloat(), DMG_SHOCK);
CalculateMeleeDamageForce(&dmgInfo, vecNormalizedVel, tr.endpos, 0.7f);
dmgInfo.SetDamagePosition(tr.endpos);
pOther->DispatchTraceAttack(dmgInfo, vecNormalizedVel, &tr);
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_dmg_electric_sphere_explosion.GetFloat(), DMG_SHOCK), GetAbsOrigin(), 256, CLASS_RACEX, NULL);
ApplyMultiDamage();
if (pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS)
return;
SetAbsVelocity(Vector(0, 0, 0));
EmitSound("NPC_Voltigore.ElectricSphereExplode");
SetTouch(NULL);
SetThink(NULL);
UTIL_Remove(this);
}
else
{
trace_t tr;
//tr = BaseClass::GetTouchTrace();
//UTIL_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + Vector ( 0, 0, 0 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr);
ShouldDrawWaterImpacts(tr);
Vector vForward;
AngleVectors(GetAbsAngles(), &vForward);
VectorNormalize(vForward);
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_OPAQUE, pOther, COLLISION_GROUP_NONE, &tr);
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_dmg_electric_sphere_explosion.GetFloat(), DMG_SHOCK), GetAbsOrigin(), 256, CLASS_RACEX, NULL);
EmitSound("NPC_Voltigore.ElectricSphereExplode");
UTIL_DecalTrace( &tr, "FadingScorch" );
UTIL_Remove(this);
}
}
//----------------------------------------------------------------------------------
// Purpose: Check for water
//----------------------------------------------------------------------------------
#define FSetBit(iBitVector, bits) ((iBitVector) |= (bits))
#define FBitSet(iBitVector, bit) ((iBitVector) & (bit))
#define TraceContents( vec ) ( enginetrace->GetPointContents( vec ) )
#define WaterContents( vec ) ( FBitSet( TraceContents( vec ), CONTENTS_WATER|CONTENTS_SLIME ) )
bool CVoltElectricSphere::ShouldDrawWaterImpacts(const trace_t &shot_trace)
{
// We must start outside the water
if (WaterContents(shot_trace.startpos))
return false;
// We must end inside of water
if (!WaterContents(shot_trace.endpos))
return false;
trace_t waterTrace;
UTIL_TraceLine(shot_trace.startpos, shot_trace.endpos, (CONTENTS_WATER | CONTENTS_SLIME), UTIL_GetLocalPlayer(), COLLISION_GROUP_NONE, &waterTrace);
if (waterTrace.fraction < 1.0f)
{
CEffectData data;
data.m_fFlags = 0;
data.m_vOrigin = waterTrace.endpos;
data.m_vNormal = waterTrace.plane.normal;
data.m_flScale = random->RandomFloat(2.0, 4.0f);
// See if we hit slime
if (FBitSet(waterTrace.contents, CONTENTS_SLIME))
{
FSetBit(data.m_fFlags, FX_WATER_IN_SLIME);
}
CPASFilter filter(data.m_vOrigin);
te->DispatchEffect(filter, 0.0, data.m_vOrigin, "watersplash", data);
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CVoltElectricSphere::BubbleThink(void)
{
QAngle angNewAngles;
VectorAngles(GetAbsVelocity() / 2.0f, angNewAngles);
SetAbsAngles(angNewAngles);
SetNextThink(gpGlobals->curtime + 0.1f);
if (GetWaterLevel() == 0)
return;
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_dmg_electric_sphere_explosion.GetFloat(), DMG_SHOCK), GetAbsOrigin(), 256, CLASS_RACEX, NULL);
EmitSound("NPC_Voltigore.ElectricSphereExplode");
UTIL_Remove(this); // remove ent on water hit
}
//============================================================================================================= volt npc
//=========================================================================
// voltigore
//=========================================================================
//=========================================================
// Spawn
//=========================================================
void CNPC_voltigore::Spawn()
{
Precache();
if (FClassnameIs(this, "npc_voltigore"))
{
m_fIsBabyVolt = false;
SetModel("models/RaceX/voltigore.mdl");
}
else if (FClassnameIs(this, "npc_voltigore_baby"))
{
m_fIsBabyVolt = true;
SetModel("models/RaceX/voltigore_baby.mdl");
}
else
{
// custom models here reserved
SetModel("models/RaceX/voltigore.mdl"); //temp enhance later
}
SetNavType(NAV_GROUND);
SetSolid(SOLID_BBOX);
AddSolidFlags(FSOLID_NOT_STANDABLE);
SetMoveType(MOVETYPE_STEP);
if (!IsBabyVolt()) // only bigger cannot be dissolved or grabbed by physcannon
{
AddEFlags(EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL); // BJ: NO DISSOLVE NO MEGAPHYS instakill
}
Vector vecSurroundingMins(-80, -80, 0);
Vector vecSurroundingMaxs(80, 80, 214);
CollisionProp()->SetSurroundingBoundsType(USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs);
m_bloodColor = BLOOD_COLOR_YELLOW;
if (IsBabyVolt())
{
m_iHealth = sk_voltigore_baby_health.GetFloat();
}
else
{
m_iHealth = sk_voltigore_health.GetFloat();
}
SetViewOffset( Vector ( 0, 0, 96 ) );
//m_flFieldOfView = -0.2;
m_flFieldOfView = -0.707; // 270 degrees
SetDistLook(1024); // BJ: Test view
m_NPCState = NPC_STATE_NONE;
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_INNATE_MELEE_ATTACK2 );
if (IsBabyVolt())
{
SetHullType(HULL_TINY);
}
else
{
SetHullType(HULL_LARGE);
}
SetHullSizeNormal();
m_seeTime_vo = gpGlobals->curtime + 5;
m_flameTime_vo = gpGlobals->curtime + 2;
NPCInit();
BaseClass::Spawn();
GetEnemies()->SetFreeKnowledgeDuration( 59.0f );
iNumDeathAnim = 0; // BJ: Reset death anim index
iCountTillExplode = 0; // BJ: Count events to DO regular volt explode, must be 3
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_voltigore::Precache()
{
PrecacheModel("models/RaceX/voltigore.mdl");
PrecacheModel("models/RaceX/voltigore_baby.mdl");
if (FClassnameIs(this, "npc_voltigore_baby")) // BJ: Baby first, all other regular sounds. Precache goes first so didnt use IsBabyVolt here.
{
PrecacheScriptSound("NPC_Voltigore_Baby.AttackHit");
PrecacheScriptSound("NPC_Voltigore_Baby.AttackMiss");
PrecacheScriptSound("NPC_Voltigore_Baby.Footstep");
PrecacheScriptSound("NPC_Voltigore_Baby.Communicate");
PrecacheScriptSound("NPC_Voltigore_Baby.Attack");
PrecacheScriptSound("NPC_Voltigore_Baby.Pain");
PrecacheScriptSound("NPC_Voltigore_Baby.AttackElectricBeam");
}
else //if (FClassnameIs(this, "npc_voltigore"))
{
PrecacheScriptSound("NPC_Voltigore.AttackHit");
PrecacheScriptSound("NPC_Voltigore.AttackMiss");
PrecacheScriptSound("NPC_Voltigore.Footstep");
PrecacheScriptSound("NPC_Voltigore.Communicate");
PrecacheScriptSound("NPC_Voltigore.Attack");
PrecacheScriptSound("NPC_Voltigore.Pain");
PrecacheScriptSound("NPC_Voltigore.AttackElectric");
PrecacheScriptSound("NPC_Voltigore.SelfExplode");
}
// paw charging effects
PrecacheParticleSystem("voltigore_baby_paw_charge_preshot");
PrecacheParticleSystem("voltigore_baby_middle_charge_preshot");
PrecacheParticleSystem("voltigore_baby_beam_closeattack");
//PrecacheParticleSystem("Weapon_Combine_Ion_Cannon"); //TEST Replace later with closeattack
PrecacheParticleSystem("voltigore_paw_charge_preshot");
PrecacheParticleSystem("voltigore_middle_charge_preshot");
PrecacheParticleSystem("voltigore_big_explode_pre");
PrecacheParticleSystem("voltigore_big_explode_post");
PrecacheParticleSystem("blood_antlionguard_injured_light"); // BJ: Bleed added
PrecacheParticleSystem("blood_antlionguard_injured_heavy");
}
Class_T CNPC_voltigore::Classify ( void )
{
return CLASS_RACEX; // BJ: RaceX Custom Class
}
void CNPC_voltigore::PrescheduleThink( void )
{
if ( !HasCondition( COND_SEE_ENEMY ) )
{
m_seeTime_vo = gpGlobals->curtime + 5;
}
else
{
//BJ: Nothing! Just nothing.
}
// BJ: Bleed added
m_iBleedingLevel = GetBleedingLevel();
}
float CNPC_voltigore::MaxYawSpeed ( void )
{
float ys = 60;
switch ( GetActivity() )
{
case ACT_IDLE:
ys = 60;
break;
case ACT_TURN_LEFT:
case ACT_TURN_RIGHT:
ys = 180;
break;
case ACT_WALK:
case ACT_RUN:
ys = 60;
break;
default:
ys = 60;
break;
}
return ys;
}
//=========================================================
// Baby Voltigore rare beam attack
//=========================================================
void CNPC_voltigore::ElectricBeam(void)
{
DevMsg("Volt Rare Electric Beam Attack \n");
/*
Vector forward;
GetVectors(&forward, NULL, NULL);
Vector vecSrc = GetAbsOrigin() + GetViewOffset();
Vector vecAim = GetShootEnemyDir(vecSrc, false);
if (GetEnemy())
{
Vector vecTarget = GetEnemy()->BodyTarget(vecSrc, false);
}
trace_t tr;
AI_TraceLine(vecSrc, vecSrc + (vecAim * InnateRange1MaxRange()), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_left", false);
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_right", false);
DispatchParticleEffect("voltigore_baby_beam_closeattack", GetAbsOrigin(), tr.endpos, vec3_angle, NULL );
//DispatchParticleEffect("Weapon_Combine_Ion_Cannon", GetAbsOrigin(), tr.endpos, vec3_angle, NULL ); // BJ: Replace later
*/
Vector vecSrc, vecAiming, vecShootOrigin, vecTarget;
vecShootOrigin = this->GetAbsOrigin(); //pOperator->Weapon_ShootPosition();
//GetAttachment(LookupAttachment("muzzle"), vecShootOrigin); //use attachment as start position
GetVectors(&vecAiming, NULL, NULL);
vecSrc = WorldSpaceCenter() + vecAiming * 64;
Vector impactPoint = vecSrc + (vecAiming * MAX_TRACE_LENGTH);
//CAI_BaseNPC *pNPC = GetOwner()->MyNPCPointer();
//vecTarget = pNPC->GetEnemy()->BodyTarget(vecSrc);
if (GetEnemy())
{
Vector vecTarget = GetEnemy()->BodyTarget(vecSrc, false);
}
//CSoundEnt::InsertSound(SOUND_COMBAT | SOUND_CONTEXT_GUNFIRE, pOperator->GetAbsOrigin(), SOUNDENT_VOLUME_PISTOL, 0.2, pOperator, SOUNDENT_CHANNEL_WEAPON, pOperator->GetEnemy());
//WeaponSound(SINGLE_NPC);
//pOperator->DoMuzzleFlash();
//m_iClip1 = m_iClip1 - 1;
trace_t tr;
Vector vecShootPos;
AI_TraceLine(vecShootOrigin, vecTarget, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr);
DispatchParticleEffect("voltigore_baby_beam_closeattack", vecShootOrigin, tr.endpos, vec3_angle, NULL);
// end
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_left", false);
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_right", false);
CBaseEntity *pEntity = tr.m_pEnt;
if (pEntity != NULL && m_takedamage)
{
CTakeDamageInfo dmgInfo(this, this, sk_voltigore_baby_dmg_electric_beam.GetFloat(), DMG_PARALYZE);
dmgInfo.SetDamagePosition(tr.endpos);
VectorNormalize(vecAiming);
dmgInfo.SetDamageForce(1 * 10 * 12 * vecAiming); //5 100 12 def
if (FClassnameIs(pEntity, "npc_antlion")) // BJ: Antlion flip specials
{
CNPC_Antlion *pAntlion = static_cast<CNPC_Antlion *>(pEntity);
pAntlion->Flip();
}
pEntity->DispatchTraceAttack(dmgInfo, vecAiming, &tr);
}
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.AttackElectricBeam");
}
void CNPC_voltigore::ElectricAttack(void)
{
DevMsg("Volt Electric Prepare near damage \n");
if (IsBabyVolt())
{
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_baby_dmg_electric_prepare.GetFloat(), DMG_SHOCK), GetAbsOrigin(), 128, CLASS_RACEX, NULL);
ElectricBeam();
}
else
{
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_dmg_electric_prepare.GetFloat(), DMG_SHOCK), GetAbsOrigin(), 256, CLASS_RACEX, NULL);
}
if (GetEnemy() == NULL)
return;
Vector vecShootOrigin;
GetAttachment("electric", vecShootOrigin, NULL);
Vector vecShootDir = GetShootEnemyDir(vecShootOrigin);
Vector forward, right, up;
AngleVectors(GetAbsAngles(), &forward, &right, &up);
Vector vecShellVelocity = right * random->RandomFloat(40, 90) + up * random->RandomFloat(75, 200) + forward * random->RandomFloat(-40, 40);
Vector src;
GetAttachment("electric", src, NULL);
QAngle angAiming;
VectorAngles(vecShootDir, angAiming);
if (!IsBabyVolt()) // only regular can throw electric sphere
{
CVoltElectricSphere *pVoltElectricSphere = CVoltElectricSphere::BoltCreate(src, angAiming, 0, this);
if (this->GetWaterLevel() == 3)
{
CTakeDamageInfo hitself(this, this, 200, DMG_SHOCK);
TakeDamage(hitself);
}
else
{
pVoltElectricSphere->SetAbsVelocity(vecShootDir * sk_voltigore_dmg_electric_sphere_speed.GetFloat()); // last adjust speed // * 600 ok
}
}
else // baby volt can only damage self if in water
{
if (this->GetWaterLevel() == 3)
{
CTakeDamageInfo hitself(this, this, 100, DMG_SHOCK);
TakeDamage(hitself);
}
}
// BJ: Both paw plus electric in the middle
if (IsBabyVolt())
{
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_left", false);
DispatchParticleEffect("voltigore_baby_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_right", false);
DispatchParticleEffect("voltigore_baby_middle_charge_preshot", PATTACH_POINT_FOLLOW, this, "electric", false);
}
else
{
DispatchParticleEffect("voltigore_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_left", false);
DispatchParticleEffect("voltigore_paw_charge_preshot", PATTACH_POINT_FOLLOW, this, "paw_right", false);
DispatchParticleEffect("voltigore_middle_charge_preshot", PATTACH_POINT_FOLLOW, this, "electric", false);
}
if (!IsBabyVolt()) // BJ Only Bigger play voice sound
{
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "NPC_Voltigore.AttackElectric");
}
DoMuzzleFlash();
m_cAmmoLoaded--;
SetAim(vecShootDir);
}
void CNPC_voltigore::PreBigExplode(void)
{
DevMsg("Volt pre big explode event \n");
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "NPC_Voltigore.AttackElectric");
DispatchParticleEffect("voltigore_big_explode_pre", PATTACH_POINT_FOLLOW, this, "electric", false); // BJ: Pre explosion fx on attachment
}
void CNPC_voltigore::BigExplode(void)
{
DevMsg("Volt big explode activated \n");
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "NPC_Voltigore.SelfExplode");
// BJ: ADD GIBS LATER IF NEEDS
DispatchParticleEffect("voltigore_big_explode_post", GetAbsOrigin(), QAngle(0, 0, 0)); //BJ: Gib explosion in middle of NPC // def -90 0 0
RadiusDamage(CTakeDamageInfo(this, this, sk_voltigore_dmg_bigexplode.GetFloat(), DMG_NERVEGAS), GetAbsOrigin(), 300, CLASS_RACEX, NULL);
UTIL_Remove(this);
}
int CNPC_voltigore::MeleeAttack1Conditions( float flDot, float flDist ) // paw punch attack
{
if (flDot >= 0.7)
{
if (IsBabyVolt())
{
if (flDist <= volt_MELEE_ATTACKDIST_BABY)
{
return COND_CAN_MELEE_ATTACK1;
}
}
else
{
if (flDist <= volt_MELEE_ATTACKDIST)
{
return COND_CAN_MELEE_ATTACK1;
}
}
}
return COND_NONE;
}
int CNPC_voltigore::MeleeAttack2Conditions( float flDot, float flDist ) // electro close combat attacks?
{
if ( gpGlobals->curtime > m_flameTime_vo )
{
if ( flDot >= 0.8 )
{
if (IsBabyVolt())
{
if (flDist > volt_MELEE_ATTACKDIST_BABY)
{
if ((flDist <= 100) && (random->RandomInt(0, 27) == 3)) // BJ: Allow to use unique baby shockline very rare
{
DevMsg("RAND Volt Baby electro special attack \n");
return COND_CAN_MELEE_ATTACK2;
}
}
}
else
{
if (flDist > volt_MELEE_ATTACKDIST)
{
if ((!cvar->FindVar("sk_voltigore_allow_extra_electric_throw")->GetFloat() == 0) || (g_pGameRules->IsSkillLevel(SKILL_HARD))) // BJ: Second electro sphere usage in movement.
{
if (random->RandomInt(0, 7) == 3) // BJ: IMPORTANT! Regulate chance to throw electric sphere yes = throw no = ignore, no allow
{
DevMsg("Volt Pre sphere \n");
if (flDist <= volt_ELSPHERE_DIST)
return COND_CAN_MELEE_ATTACK2;
}
}
}
}
}
}
return COND_NONE;
}
//=========================================================
// CheckRangeAttack1
// flDot is the cos of the angle of the cone within which
// the attack can occur.
//=========================================================
//
// voenergy attack
//
//=========================================================
int CNPC_voltigore::RangeAttack1Conditions( float flDot, float flDist ) // long distance electric sphere throw, rare usage
{
if ( gpGlobals->curtime > m_seeTime_vo )
{
if ( flDot >= 0.7 )
{
if (IsBabyVolt())
{
if (flDist > volt_MELEE_ATTACKDIST_BABY)
{
//return COND_CAN_RANGE_ATTACK1; // BJ: Useless, not needed for volt baby
}
}
else
{
if (flDist > volt_MELEE_ATTACKDIST)
{
return COND_CAN_RANGE_ATTACK1;
}
}
}
}
return COND_NONE;
}
//=========================================================
// 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.
// Overridden for voltigore because his swing starts lower as
// a percentage of his height (otherwise he swings over the
// players head)
//=========================================================
CBaseEntity* CNPC_voltigore::voltigoreCheckTraceHullAttack(float flDist, int iDamage, int iDmgType)
{
trace_t tr;
Vector vForward, vUp;
AngleVectors( GetAbsAngles(), &vForward, NULL, &vUp );
Vector vecStart = GetAbsOrigin();
vecStart.z += 64;
Vector vecEnd = vecStart + ( vForward * flDist) - ( vUp * flDist * 0.3);
UTIL_TraceEntity( this, GetAbsOrigin(), vecEnd, MASK_SOLID, &tr );
if ( tr.m_pEnt )
{
CBaseEntity *pEntity = tr.m_pEnt;
if ( iDamage > 0 )
{
CTakeDamageInfo info( this, this, iDamage, iDmgType );
CalculateMeleeDamageForce( &info, vForward, tr.endpos );
pEntity->TakeDamage( info );
}
return pEntity;
}
return NULL;
}
void CNPC_voltigore::HandleAnimEvent( animevent_t *pEvent )
{
CPASAttenuationFilter filter( this );
switch( pEvent->event )
{
case volt_AE_SLASH_LEFT: // BJ: Now left AND both paws attack, merged in QC
{
if (IsBabyVolt()) // BJ: Tricky damage info setting
{
m_PresetDamage = sk_voltigore_baby_dmg_slash.GetFloat();
}
else
{
m_PresetDamage = sk_voltigore_dmg_slash.GetFloat();
}
if (IsBabyVolt()) // BJ: Tricky re-calc distance for static functions
{
m_MeleeDistRecalc = volt_MELEE_ATTACKDIST_BABY;
}
else
{
m_MeleeDistRecalc = volt_MELEE_ATTACKDIST;
}
CBaseEntity *pHurt = voltigoreCheckTraceHullAttack(m_MeleeDistRecalc + 10.0, m_PresetDamage, DMG_SLASH);
if (pHurt)
{
if ( pHurt->GetFlags() & ( FL_NPC | FL_CLIENT ) )
{
if (IsBabyVolt())
{
if (random->RandomInt(0, 3) == 3) // more interesting to see
pHurt->ViewPunch(QAngle(10, 10, -10));
else
pHurt->ViewPunch(QAngle(-10, -10, 10)); // left punch
}
else
{
if (random->RandomInt(0, 3) == 3)
pHurt->ViewPunch(QAngle(30, 30, -30));
else
pHurt->ViewPunch(QAngle(-30, -30, 30));
}
Vector vRight;
AngleVectors( GetAbsAngles(), NULL, &vRight, NULL );
pHurt->SetAbsVelocity( pHurt->GetAbsVelocity() - vRight * 100 );
}
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.AttackHit");
}
else
{
EmitSound(filter, entindex(), "NPC_Voltigore.AttackHit");
}
}
else
{
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.AttackMiss");
}
else
{
EmitSound(filter, entindex(), "NPC_Voltigore.AttackMiss");
}
}
}
break;
case volt_AE_RIGHT_FOOT:
case volt_AE_LEFT_FOOT:
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.Footstep");
}
else
{
UTIL_ScreenShake(GetAbsOrigin(), 4.0, 3.0, 1.0, 1500, SHAKE_START); // BJ: Moved, only for regular
EmitSound(filter, entindex(), "NPC_Voltigore.Footstep");
}
break;
case volt_AE_voenergy: // electric attack event
if (!IsBabyVolt()) // BJ: Only big one can throw spheres on distance // double check
{
DevMsg("Volt volt_AE_voenergy throw electric \n");
ElectricAttack();
}
m_seeTime_vo = gpGlobals->curtime + 12; //def 12
break;
case volt_AE_BREATHE:
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.Communicate");
}
else
{
EmitSound(filter, entindex(), "NPC_Voltigore.Communicate");
}
break;
case volt_AE_EXPLODE: // BJ: Explode event, defined in QC
BigExplode();
break;
default:
BaseClass::HandleAnimEvent(pEvent);
break;
}
}
int CNPC_voltigore::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_MELEE_ATTACK2:
return SCHED_volt_ELECTRO;
case SCHED_MELEE_ATTACK1:
return SCHED_volt_SWIPE;
case SCHED_CHASE_ENEMY:
return SCHED_volt_CHASE_ENEMY;
case SCHED_CHASE_ENEMY_FAILED:
return SCHED_volt_CHASE_ENEMY_FAILED;
case SCHED_ALERT_STAND:
return SCHED_CHASE_ENEMY;
break;
}
return BaseClass::TranslateSchedule( scheduleType );
}
void CNPC_voltigore::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_ELECTRO_SHOT:
if (IsBabyVolt())
{
DevMsg("Volt TASK_ELECTRO_SHOT throw electric \n");
ElectricAttack(); // BJ: 1 function 2 different attacks for regular and baby HERE. Baby attack instant, Bigger with delay.
m_flWaitFinished = gpGlobals->curtime + 1; //pTask->flTaskData;
m_flameTime_vo = gpGlobals->curtime + 1; //def 6 //test
}
else
{
if ((!cvar->FindVar("sk_voltigore_allow_extra_electric_throw")->GetFloat() == 0) || (g_pGameRules->IsSkillLevel(SKILL_HARD)))
{
m_flWaitFinished = gpGlobals->curtime + 0.8; // BJ Bigger volt delay on hard
m_flameTime_vo = gpGlobals->curtime + 0.8;
// attack defined under!
}
else
{
m_flWaitFinished = gpGlobals->curtime + 0; // BJ Bigger volt 0 delay
m_flameTime_vo = gpGlobals->curtime + 0;
}
}
break;
case TASK_SOUNDS_ATTACK:
if ( random->RandomInt(0,100) < 30 )
{
CPASAttenuationFilter filter( this );
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.Attack");
}
else
{
EmitSound(filter, entindex(), "NPC_Voltigore.Attack");
}
}
TaskComplete();
break;
case TASK_DIE:
m_flWaitFinished = gpGlobals->curtime + 1.6;
default:
BaseClass::StartTask( pTask );
break;
}
}
bool CNPC_voltigore::ShouldGib( const CTakeDamageInfo &info )
{
return false;
}
//=========================================================
// RunTask
//=========================================================
void CNPC_voltigore::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_DIE:
if ( gpGlobals->curtime > m_flWaitFinished )
{
m_nRenderFX = kRenderFxExplode;
SetRenderColor( 255, 0, 0 , 255 );
StopAnimation();
SetNextThink( gpGlobals->curtime + 0.15 );
SetThink( &CBaseEntity::SUB_Remove );
return;
}
else
BaseClass::RunTask( pTask );
break;
case TASK_ELECTRO_SHOT:
if ( gpGlobals->curtime > m_flWaitFinished )
{
if ((!cvar->FindVar("sk_voltigore_allow_extra_electric_throw")->GetFloat() == 0) || (g_pGameRules->IsSkillLevel(SKILL_HARD))) // BJ Non zero OR high difficulity
{
DevMsg("Volt TASK_ELECTRO_SHOT Extra electric \n");
if (!IsBabyVolt()) // BJ: Moved Big volt sphere shot just like in original, 2 sec delay
{
ElectricAttack();
}
}
TaskComplete();
SetBoneController( 0, 0 );
SetBoneController( 1, 0 );
}
else
{
bool cancel = false;
QAngle angles = QAngle( 0, 0, 0 );
CBaseEntity *pEnemy = GetEnemy();
if ( pEnemy )
{
Vector org = GetAbsOrigin();
org.z += 64;
Vector dir = pEnemy->BodyTarget(org) - org;
VectorAngles( dir, angles );
angles.x = -angles.x;
angles.y -= GetAbsAngles().y;
if ( dir.Length() > 400 )
cancel = true;
}
if ( fabs(angles.y) > 60 )
cancel = true;
if ( cancel )
{
m_flWaitFinished -= 0.5;
m_flameTime_vo -= 0.5;
}
}
break;
default:
BaseClass::RunTask( pTask );
break;
}
}
//-----------------------------------------------------------------------------
// Don't become a ragdoll until we've finished our death anim
//-----------------------------------------------------------------------------
bool CNPC_voltigore::CanBecomeRagdoll()
{
//return false; // wtf? This broke up Baby volt ragdolls
if (IsBabyVolt()) // BJ: volt baby don't use special anims, original ragdoll is fine
return true;
if (iNumDeathAnim == 0)
{
if (GetActivity() == ACT_RUN)
iNumDeathAnim = 1;
if (GetActivity() == ACT_WALK)
iNumDeathAnim = 2;
}
DevMsg("Volt become ragdoll count \n");
iCountTillExplode = iCountTillExplode + 1; // BJ: Should be 3. Tricky method
if (iCountTillExplode == 3) // BJ: Next second after death event
PreBigExplode();
return IsCurSchedule(SCHED_DIE, false); // BJ: Only die event, no need touch other
}
//-----------------------------------------------------------------------------
// Determines the best type of death anim to play based on how we died.
//-----------------------------------------------------------------------------
Activity CNPC_voltigore::GetDeathActivity()
{
if (iNumDeathAnim == 1) // BJ: Simple system that should looks like in HL1
{
DevMsg("Volt set death activity FWD \n");
return ACT_DIEFORWARD;
}
if (iNumDeathAnim == 2)
{
DevMsg("Volt set death activity BCWRD \n");
return ACT_DIEBACKWARD;
}
DevMsg("Volt set death activity ORIG\n");
return ACT_DIESIMPLE;
}
void CNPC_voltigore::Event_Killed( const CTakeDamageInfo &info )
{
BaseClass::Event_Killed( info );
m_takedamage = DAMAGE_NO;
// BJ: Bleed added
m_iBleedingLevel = 0;
}
//-----------------------------------------------------------------------------
// Purpose: Return desired level for the continuous bleeding effect (not the
// individual blood spurts you see per bullet hit)
// Return 0 for don't bleed,
// 1 for mild bleeding
// 2 for severe bleeding
//-----------------------------------------------------------------------------
unsigned char CNPC_voltigore::GetBleedingLevel(void) const // BJ: Bleed added
{
if (m_iHealth > (m_iMaxHealth >> 1))
{ // more 50%
return 0;
}
else if (m_iHealth > (m_iMaxHealth >> 2))
{ // less 50% more 25% 26-49
return 1;
}
else
{
return 2;
}
if ((m_fIsBabyVolt == true) && (m_iHealth > (m_iMaxHealth >> 2))) // BJ: Volt baby options, don't bleed too much
{
return 1;
}
}
void CNPC_voltigore::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator )
{
CTakeDamageInfo subInfo = info;
if ( !IsAlive() )
{
BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator );
return;
}
if ( subInfo.GetDamageType() & ( volt_DAMAGE | DMG_BLAST ) )
{
if ( m_painSoundTime_vo < gpGlobals->curtime )
{
CPASAttenuationFilter filter( this );
if (IsBabyVolt())
{
EmitSound(filter, entindex(), "NPC_Voltigore_Baby.Pain");
}
else
{
EmitSound(filter, entindex(), "NPC_Voltigore.Pain");
}
m_painSoundTime_vo = gpGlobals->curtime + random->RandomFloat( 2.5, 4 );
}
}
BaseClass::TraceAttack( subInfo, vecDir, ptr, pAccumulator );
}
int CNPC_voltigore::TakeDamageFromCombineBall(const CTakeDamageInfo &info)
{
float damage = info.GetDamage();
if (UTIL_IsAR2CombineBall(info.GetInflictor()))
{
damage = 60; // dmg here!!!
}
if (info.GetAttacker() && info.GetAttacker()->IsPlayer())
{
damage = g_pGameRules->AdjustPlayerDamageInflicted(damage);
}
m_iHealth -= damage;
return damage;
}
int CNPC_voltigore::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
/*
if( GetState() == NPC_STATE_SCRIPT ) // No damage in scripted scenes. test.
{
return 0;
}
*/
CTakeDamageInfo subInfo = info;
// BJ : NO DISSOLVE, LESS DAMAGE from combine balls
if (UTIL_IsCombineBall(info.GetInflictor()))
return TakeDamageFromCombineBall(info);
float flDamage = subInfo.GetDamage();
// BJ: volt_DAMAGE is ALLOWED
if ( IsAlive() )
{
if ( !(subInfo.GetDamageType() & volt_DAMAGE) )
{
flDamage *= 0.01;
subInfo.SetDamage( flDamage );
}
if ( subInfo.GetDamageType() & DMG_BLAST )
{
SetCondition( COND_LIGHT_DAMAGE );
}
}
return BaseClass::OnTakeDamage_Alive( subInfo );
}
AI_BEGIN_CUSTOM_NPC( npc_voltigore, CNPC_voltigore )
DECLARE_TASK ( TASK_SOUNDS_ATTACK )
DECLARE_TASK ( TASK_ELECTRO_SHOT )
//=========================================================
// > SCHED_volt_ELECTRO
//=========================================================
DEFINE_SCHEDULE
(
SCHED_volt_ELECTRO,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_SOUNDS_ATTACK 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_MELEE_ATTACK2"
" TASK_ELECTRO_SHOT 4.5"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
)
//=========================================================
// > SCHED_volt_SWIPE
//=========================================================
DEFINE_SCHEDULE
(
SCHED_volt_SWIPE,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_MELEE_ATTACK1 0"
" "
" Interrupts"
" COND_CAN_MELEE_ATTACK2"
)
DEFINE_SCHEDULE
(
SCHED_volt_CHASE_ENEMY,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_volt_CHASE_ENEMY_FAILED"
" TASK_GET_CHASE_PATH_TO_ENEMY 300"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_FACE_ENEMY 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_ENEMY_UNREACHABLE"
" COND_CAN_RANGE_ATTACK1"
" COND_CAN_MELEE_ATTACK1"
" COND_CAN_RANGE_ATTACK2"
" COND_CAN_MELEE_ATTACK2"
" COND_TOO_CLOSE_TO_ATTACK"
" COND_LOST_ENEMY"
);
DEFINE_SCHEDULE
(
SCHED_volt_CHASE_ENEMY_FAILED,
" Tasks"
" TASK_SET_ROUTE_SEARCH_TIME 2" // 2 seconds to build a path if stuck
" TASK_GET_PATH_TO_RANDOM_NODE 180"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_CAN_RANGE_ATTACK1"
" COND_CAN_MELEE_ATTACK1"
" COND_CAN_RANGE_ATTACK2"
" COND_CAN_MELEE_ATTACK2"
);
AI_END_CUSTOM_NPC()