mirror of
https://github.com/Gigaslav/HL2Overcharged.git
synced 2026-01-05 06:10:21 +03:00
1460 lines
39 KiB
C++
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()
|