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

1332 lines
34 KiB
C++

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Gargantua - Big monster from Xen
//
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
#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_Route.h"
#include "AI_Memory.h"
#include "overcharged/npc_gargantua.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 "shareddefs.h"
#include "particle_parse.h" // BJ: Fire / Stomp particles
#include "particle_system.h"
extern short g_sModelIndexFireball;
int gGargGibModel;
int gGargGibModel2;
int gGargGibModel3;
//=========================================================
// Gargantua Monster
//=========================================================
#define GARG_ATTACKDIST 120.0f
// Garg animation events
#define GARG_AE_SLASH_LEFT 1
//#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used
#define GARG_AE_LEFT_FOOT 3
#define GARG_AE_RIGHT_FOOT 4
#define GARG_AE_STOMP 5
#define GARG_AE_BREATHE 6
// Gargantua is immune to any damage but this
#define GARG_DAMAGE ( DMG_ENERGYBEAM | DMG_CRUSH | /*DMG_MISSILEDEFENSE |*/ DMG_BLAST ) // BriJee OVR: Remove unused flag
#define GARG_EYE_SPRITE_NAME "sprites/redglow3.vmt" //"sprites/gargeye1.vmt"
//#define GARG_BEAM_SPRITE_NAME "sprites/laserbeam.vmt" //"sprites/xbeam3.vmt" // BriJee OVR: No need old beam sprites, use particles now :)
//#define GARG_BEAM_SPRITE2 "sprites/laserbeam.vmt" //"sprites/xbeam3.vmt"
//#define GARG_STOMP_SPRITE_NAME "sprites/redglow2.vmt" //"sprites/gargeye1.vmt"
#define GARG_FLAME_LENGTH 330
#define GARG_GIB_MODEL "models/gibs/helicopter_brokenpiece_02.mdl" //"models/metalplategibs.mdl"
#define GARG_GIB_MODEL2 "models/gibs/helicopter_brokenpiece_03.mdl" // BriJee OVR: More gibs added
#define GARG_GIB_MODEL3 "models/gibs/Strider_gib1.mdl"
#define STOMP_SPRITE_COUNT 10
#define ATTACH_EYE 1
ConVar sk_gargantua_health("sk_gargantua_health", "800");
ConVar sk_gargantua_dmg_slash("sk_gargantua_dmg_slash", "10");
ConVar sk_gargantua_dmg_fire("sk_gargantua_dmg_fire", "3");
ConVar sk_gargantua_dmg_stomp("sk_gargantua_dmg_stomp", "50");
enum
{
TASK_SOUND_ATTACK = LAST_SHARED_TASK,
TASK_FLAME_SWEEP,
};
enum
{
SCHED_GARG_FLAME = LAST_SHARED_SCHEDULE,
SCHED_GARG_SWIPE,
SCHED_GARG_CHASE_ENEMY,
SCHED_GARG_CHASE_ENEMY_FAILED,
SCHED_GARG_DIE,
};
LINK_ENTITY_TO_CLASS(npc_gargantua, CNPC_Gargantua);
BEGIN_DATADESC(CNPC_Gargantua)
DEFINE_FIELD(m_pEyeGlow, FIELD_CLASSPTR),
DEFINE_FIELD(m_eyeBrightness, FIELD_INTEGER),
DEFINE_FIELD(m_seeTime, FIELD_TIME),
DEFINE_FIELD(m_flameTime, FIELD_TIME),
DEFINE_FIELD(m_streakTime, FIELD_TIME),
DEFINE_ARRAY(m_pFlame, FIELD_CLASSPTR, 4),
DEFINE_FIELD(m_flameX, FIELD_FLOAT),
DEFINE_FIELD(m_flameY, FIELD_FLOAT),
DEFINE_FIELD(m_flDmgTime, FIELD_TIME),
DEFINE_FIELD(m_painSoundTime, FIELD_TIME),
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;
}
}
class CStomp : public CBaseEntity
{
DECLARE_CLASS(CStomp, CBaseEntity);
public:
DECLARE_DATADESC();
virtual void Precache();
void Spawn(void);
void Think(void);
static CStomp *StompCreate(Vector &origin, Vector &end, float speed, CBaseEntity* pOwner);
private:
Vector m_vecMoveDir;
float m_flScale;
float m_flSpeed;
unsigned int m_uiFramerate;
float m_flDmgTime;
CBaseEntity* m_pOwner;
//float m_flDelayDispatch;
bool bSpawnParticleOnce; // BJ: Stomp particle
// UNDONE: re-use this sprite list instead of creating new ones all the time
// CSprite *m_pSprites[ STOMP_SPRITE_COUNT ];
};
BEGIN_DATADESC(CStomp)
DEFINE_FIELD(m_vecMoveDir, FIELD_VECTOR),
DEFINE_FIELD(m_flScale, FIELD_FLOAT),
DEFINE_FIELD(m_flSpeed, FIELD_FLOAT),
DEFINE_FIELD(m_uiFramerate, FIELD_INTEGER),
DEFINE_FIELD(m_flDmgTime, FIELD_TIME),
DEFINE_FIELD(m_pOwner, FIELD_CLASSPTR),
END_DATADESC()
LINK_ENTITY_TO_CLASS(garg_stomp, CStomp);
CStomp *CStomp::StompCreate(Vector &origin, Vector &end, float speed, CBaseEntity* pOwner)
{
CStomp *pStomp = (CStomp*)CreateEntityByName("garg_stomp");
pStomp->SetAbsOrigin(origin);
Vector dir = (end - origin);
// pStomp->m_flScale = dir.Length();
pStomp->m_flScale = 2048;
pStomp->m_vecMoveDir = dir;
VectorNormalize(pStomp->m_vecMoveDir);
pStomp->m_flSpeed = speed;
pStomp->m_pOwner = pOwner;
pStomp->Spawn();
return pStomp;
}
void CStomp::Precache()
{
BaseClass::Precache();
PrecacheScriptSound("gargantua.Stomp");
//PrecacheModel(GARG_STOMP_SPRITE_NAME);
PrecacheParticleSystem("gargantua_stomp1");
}
void CStomp::Spawn(void)
{
Precache();
SetNextThink(gpGlobals->curtime);
SetClassname("garg_stomp");
m_flDmgTime = gpGlobals->curtime;
m_uiFramerate = 30;
// pev->rendermode = kRenderTransTexture;
// SetBrightness( 0 );
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "gargantua.Stomp");
//m_flDelayDispatch = 0.0f;
}
#define STOMP_INTERVAL 0.025
void CStomp::Think(void)
{
trace_t tr;
SetNextThink(gpGlobals->curtime + 0.1);
// Do damage for this frame
Vector vecStart = GetAbsOrigin();
vecStart.z += 30;
Vector vecEnd = vecStart + (m_vecMoveDir * m_flSpeed * gpGlobals->frametime);
UTIL_TraceHull(vecStart, vecEnd, Vector(-32, -32, -32), Vector(32, 32, 32), MASK_SOLID, m_pOwner, COLLISION_GROUP_NONE, &tr);
// NDebugOverlay::Line( vecStart, vecEnd, 0, 255, 0, false, 10.0f );
if (tr.m_pEnt)
{
CBaseEntity *pEntity = tr.m_pEnt;
CTakeDamageInfo info(this, this, 50, DMG_SONIC /*| DMG_DISSOLVE*/);
CalculateMeleeDamageForce(&info, m_vecMoveDir, tr.endpos);
pEntity->TakeDamage(info);
}
// Accelerate the effect
m_flSpeed += (gpGlobals->frametime) * m_uiFramerate;
m_uiFramerate += (gpGlobals->frametime) * 2000;
// Move and spawn trails
if (gpGlobals->curtime - m_flDmgTime > 0.2f)
{
m_flDmgTime = gpGlobals->curtime - 0.2f;
}
while (gpGlobals->curtime - m_flDmgTime > STOMP_INTERVAL)
{
SetAbsOrigin(GetAbsOrigin() + m_vecMoveDir * m_flSpeed * STOMP_INTERVAL);
for (int i = 0; i < 2; i++)
{
CSprite *pSprite = CSprite::SpriteCreate(/*GARG_STOMP_SPRITE_NAME*/ NULL, GetAbsOrigin(), TRUE); // BriJee OVR: NULL Sprite should make spark effects
if (pSprite)
{
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - Vector(0, 0, 500), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
pSprite->SetAbsOrigin(tr.endpos);
// pSprite->pev->velocity = Vector(RandomFloat(-200,200),RandomFloat(-200,200),175);
pSprite->SetNextThink(gpGlobals->curtime + 0.3);
pSprite->SetThink(&CSprite::SUB_Remove);
pSprite->SetTransparency(kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast);
}
/*if (gpGlobals->curtime > m_flDelayDispatch) // BJ: Old method pre frame spawn-remove
{
DispatchParticleEffect("gargantua_stomp1", PATTACH_ABSORIGIN_FOLLOW, pSprite);
m_flDelayDispatch = gpGlobals->curtime + 0.05f;
}*/
if (!bSpawnParticleOnce)
{
bSpawnParticleOnce = true;
// BJ: Valve method attach effect with info_particle_system once
CParticleSystem *pParticleStomp = (CParticleSystem *)CreateEntityByName("info_particle_system");
if (pParticleStomp != NULL)
{
pParticleStomp->KeyValue("start_active", "1");
pParticleStomp->KeyValue("effect_name", "gargantua_stomp1");
pParticleStomp->SetParent(this);
//pParticle->SetParentAttachment("SetParentAttachment", "attach_glow2", true);
pParticleStomp->SetLocalOrigin(Vector(0, 0, -39)); // BJ: PREFECT VALUES !!!
//pParticleStomp->SetLocalOrigin(vec3_origin);
DispatchSpawn(pParticleStomp);
if (gpGlobals->curtime > 1.5f)
pParticleStomp->Activate();
DevMsg("Garg stomp particle created \n");
}// end
}
g_pEffects->EnergySplash(tr.endpos, tr.plane.normal);
}
m_flDmgTime += STOMP_INTERVAL;
// Scale has the "life" of this effect
m_flScale -= STOMP_INTERVAL * m_flSpeed;
if (m_flScale <= 0)
{
// Life has run out
UTIL_Remove(this);
CPASAttenuationFilter filter(this);
StopSound(entindex(), CHAN_STATIC, "gargantua.Stomp");
}
}
}
//=========================================================================
// Gargantua
//=========================================================================
//=========================================================
// Spawn
//=========================================================
void CNPC_Gargantua::Spawn()
{
Precache();
DispatchParticleOnce = false;
//IgniteNpcOnce = false;
SetModel("models/garg.mdl");
SetNavType(NAV_GROUND);
SetSolid(SOLID_BBOX);
AddSolidFlags(FSOLID_NOT_STANDABLE);
SetMoveType(MOVETYPE_STEP);
Vector vecSurroundingMins(-80, -80, 0);
Vector vecSurroundingMaxs(80, 80, 214);
CollisionProp()->SetSurroundingBoundsType(USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs);
m_bloodColor = BLOOD_COLOR_GREEN;
m_iHealth = sk_gargantua_health.GetFloat();
SetViewOffset(Vector(0, 0, 96));// taken from mdl file
m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result )
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);
SetHullType(HULL_LARGE);
SetHullSizeNormal();
m_pEyeGlow = CSprite::SpriteCreate(GARG_EYE_SPRITE_NAME, GetAbsOrigin(), FALSE);
m_pEyeGlow->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
m_pEyeGlow->SetAttachment(this, 1);
EyeOff();
m_seeTime = gpGlobals->curtime + 5;
m_flameTime = gpGlobals->curtime + 2;
NPCInit();
BaseClass::Spawn();
// Give garg a healthy free knowledge.
GetEnemies()->SetFreeKnowledgeDuration(59.0f);
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Gargantua::Precache()
{
PrecacheModel("models/garg.mdl");
PrecacheModel(GARG_EYE_SPRITE_NAME);
//PrecacheModel(GARG_BEAM_SPRITE_NAME);
//PrecacheModel(GARG_BEAM_SPRITE2);
//gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME );
gGargGibModel = PrecacheModel(GARG_GIB_MODEL);
gGargGibModel2 = PrecacheModel(GARG_GIB_MODEL2);
gGargGibModel3 = PrecacheModel(GARG_GIB_MODEL3);
PrecacheParticleSystem("flamethrower");
PrecacheParticleSystem("Explosion_2");
PrecacheScriptSound("gargantua.AttackHit");
PrecacheScriptSound("gargantua.AttackMiss");
PrecacheScriptSound("gargantua.Footstep");
PrecacheScriptSound("gargantua.Breath");
PrecacheScriptSound("gargantua.Attack");
PrecacheScriptSound("gargantua.Pain");
PrecacheScriptSound("gargantua.BeamAttackOn");
PrecacheScriptSound("gargantua.BeamAttackRun");
PrecacheScriptSound("gargantua.BeamAttackOff");
PrecacheScriptSound("gargantua.StompSound");
}
Class_T CNPC_Gargantua::Classify(void)
{
return CLASS_ALIENGRUNT; //CLASS_ZOMBIE; //CLASS_ALIEN_MONSTER; // BriJee OVR: Garg don't hurt zombies so use this class
}
void CNPC_Gargantua::PrescheduleThink(void)
{
if (!HasCondition(COND_SEE_ENEMY))
{
m_seeTime = gpGlobals->curtime + 5;
EyeOff();
}
else
{
EyeOn(200);
}
EyeUpdate();
}
float CNPC_Gargantua::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;
}
int CNPC_Gargantua::MeleeAttack1Conditions(float flDot, float flDist)
{
if (flDot >= 0.7)
{
if (flDist <= GARG_ATTACKDIST)
{
return COND_CAN_MELEE_ATTACK1;
}
}
return COND_NONE;
}
// Flame thrower madness!
int CNPC_Gargantua::MeleeAttack2Conditions(float flDot, float flDist)
{
if (gpGlobals->curtime > m_flameTime)
{
if (flDot >= 0.8)
{
if (flDist > GARG_ATTACKDIST)
{
if (flDist <= GARG_FLAME_LENGTH)
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.
//=========================================================
//
// Stomp attack
//
//=========================================================
int CNPC_Gargantua::RangeAttack1Conditions(float flDot, float flDist)
{
if (gpGlobals->curtime > m_seeTime)
{
if (flDot >= 0.7)
{
if (flDist > GARG_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 Gargantua because his swing starts lower as
// a percentage of his height (otherwise he swings over the
// players head)
//=========================================================
CBaseEntity* CNPC_Gargantua::GargantuaCheckTraceHullAttack(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_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr );
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_Gargantua::HandleAnimEvent(animevent_t *pEvent)
{
CPASAttenuationFilter filter(this);
switch (pEvent->event)
{
case GARG_AE_SLASH_LEFT:
{
// HACKHACK!!!
CBaseEntity *pHurt = GargantuaCheckTraceHullAttack(GARG_ATTACKDIST + 10.0, sk_gargantua_dmg_slash.GetFloat(), DMG_SLASH);
if (pHurt)
{
if (pHurt->GetFlags() & (FL_NPC | FL_CLIENT))
{
pHurt->ViewPunch(QAngle(-30, -30, 30));
Vector vRight;
AngleVectors(GetAbsAngles(), NULL, &vRight, NULL);
pHurt->SetAbsVelocity(pHurt->GetAbsVelocity() - vRight * 100);
}
EmitSound(filter, entindex(), "gargantua.AttackHit");
}
else // Play a random attack miss sound
{
EmitSound(filter, entindex(), "gargantua.AttackMiss");
}
}
break;
case GARG_AE_RIGHT_FOOT:
case GARG_AE_LEFT_FOOT:
UTIL_ScreenShake(GetAbsOrigin(), 4.0, 3.0, 1.0, 1500, SHAKE_START);
EmitSound(filter, entindex(), "gargantua.Footstep");
break;
case GARG_AE_STOMP:
StompAttack();
m_seeTime = gpGlobals->curtime + 12;
break;
case GARG_AE_BREATHE:
EmitSound(filter, entindex(), "gargantua.Breath");
break;
default:
BaseClass::HandleAnimEvent(pEvent);
break;
}
}
int CNPC_Gargantua::TranslateSchedule(int scheduleType)
{
//TEMP TEMP
if (FlameIsOn())
FlameDestroy();
//DevMsg("scheduleType %i", scheduleType);
if (GetHealth() <= 0)
return SCHED_GARG_DIE;
switch (scheduleType)
{
case SCHED_MELEE_ATTACK2:
return SCHED_GARG_FLAME;
case SCHED_MELEE_ATTACK1:
return SCHED_GARG_SWIPE;
case SCHED_CHASE_ENEMY:
return SCHED_GARG_CHASE_ENEMY;
case SCHED_CHASE_ENEMY_FAILED:
return SCHED_GARG_CHASE_ENEMY_FAILED;
case SCHED_ALERT_STAND:
return SCHED_CHASE_ENEMY;
break;
}
return BaseClass::TranslateSchedule(scheduleType);
}
void CNPC_Gargantua::StartTask(const Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_FLAME_SWEEP:
//TEMP TEMP
FlameCreate();
m_flWaitFinished = gpGlobals->curtime + pTask->flTaskData;
m_flameTime = gpGlobals->curtime + 6;
m_flameX = 0;
m_flameY = 0;
break;
case TASK_SOUND_ATTACK:
if (random->RandomInt(0, 100) < 30)
{
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "gargantua.Attack");
}
TaskComplete();
break;
case TASK_DIE:
m_flWaitFinished = gpGlobals->curtime + 1.6;
DeathEffect();
// FALL THROUGH
default:
BaseClass::StartTask(pTask);
break;
}
}
bool CNPC_Gargantua::ShouldGib(const CTakeDamageInfo &info)
{
return false;
}
//=========================================================
// RunTask
//=========================================================
void CNPC_Gargantua::RunTask(const Task_t *pTask)
{
switch (pTask->iTask)
{
case TASK_DIE:
if (gpGlobals->curtime > m_flWaitFinished)
{
//TEMP TEMP
m_nRenderFX = kRenderFxExplode;
SetRenderColor(255, 0, 0, 255);
StopAnimation();
SetNextThink(gpGlobals->curtime + 0.15);
SetThink(&CBaseEntity::SUB_Remove);
DispatchParticleEffect("Explosion_2", GetAbsOrigin(), QAngle(-90, 0, 0)); // BriJee OVR: Big explosion effect
int i;
int parts = modelinfo->GetModelFrameCount(modelinfo->GetModel(gGargGibModel));
int parts2 = modelinfo->GetModelFrameCount(modelinfo->GetModel(gGargGibModel2));
int parts3 = modelinfo->GetModelFrameCount(modelinfo->GetModel(gGargGibModel3));
for (i = 0; i < 10; i++)
{
CGib *pGib = CREATE_ENTITY(CGib, "gib");
if (random->RandomInt(1, 3) == 1)
{
pGib->Spawn(GARG_GIB_MODEL);
}
if (random->RandomInt(1, 3) == 1)
{
pGib->Spawn(GARG_GIB_MODEL2);
}
else
{
pGib->Spawn(GARG_GIB_MODEL3);
}
int bodyPart = 0;
if (parts > 1)
bodyPart = random->RandomInt(0, parts - 1);
if (parts2 > 1)
bodyPart = random->RandomInt(0, parts2 - 1);
if (parts3 > 1)
bodyPart = random->RandomInt(0, parts3 - 1);
pGib->SetBodygroup(0, bodyPart);
pGib->SetBloodColor(BLOOD_COLOR_YELLOW);
pGib->m_material = matNone;
pGib->SetAbsOrigin(GetAbsOrigin());
pGib->SetAbsVelocity(UTIL_RandomBloodVector() * random->RandomFloat(300, 500));
pGib->SetNextThink(gpGlobals->curtime + 1.25);
pGib->SetThink(&CBaseEntity::SUB_FadeOut);
}
Vector vecSize = Vector(200, 200, 128);
CPVSFilter filter(GetAbsOrigin());
te->BreakModel(filter, 0.0, GetAbsOrigin(), vec3_angle, vecSize, vec3_origin,
gGargGibModel, 200, 50, 3.0, BREAK_FLESH);
UTIL_Remove(this);
/*CTakeDamageInfo info;
info.AddDamageType(DMG_REMOVENORAGDOLL);
BaseClass::Event_Killed(info);*/
return;
}
else
BaseClass::RunTask(pTask);
break;
case TASK_FLAME_SWEEP:
if (gpGlobals->curtime > m_flWaitFinished)
{
//TEMP TEMP
FlameDestroy();
TaskComplete();
FlameControls(0, 0);
SetBoneController(0, 0);
SetBoneController(1, 0);
}
else
{
bool cancel = false;
QAngle angles = QAngle(0, 0, 0);
//TEMP TEMP
FlameUpdate();
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 -= 0.5;
}
//TEMP TEMP
//FlameControls( angles.x + 2 * sin(gpGlobals->curtime*8), angles.y + 28 * sin(gpGlobals->curtime*8.5) );
FlameControls(angles.x, angles.y);
}
break;
default:
BaseClass::RunTask(pTask);
break;
}
}
void CNPC_Gargantua::FlameCreate(void)
{
int i;
Vector posGun;
QAngle angleGun;
trace_t trace;
Vector vForward;
AngleVectors(GetAbsAngles(), &vForward);
for (i = 0; i < 4; i++)
{
/*if (i < 2)
m_pFlame[i] = CBeam::BeamCreate(GARG_BEAM_SPRITE_NAME, 24.0);
else
m_pFlame[i] = CBeam::BeamCreate(GARG_BEAM_SPRITE2, 14.0);*/ // BriJee OVR: No more sprites!
if (i < 2)
m_pFlame[i] = CBeam::BeamCreate(NULL, 24.0);
else
m_pFlame[i] = CBeam::BeamCreate(NULL, 14.0);
if (m_pFlame[i])
{
int attach = i % 2;
// attachment is 0 based in GetAttachment
GetAttachment(attach + 1, posGun, angleGun);
Vector vecEnd = (vForward * GARG_FLAME_LENGTH) + posGun;
//UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace );
UTIL_TraceLine(posGun, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
// NDebugOverlay::Line( posGun, vecEnd, 255, 255, 255, false, 10.0f );
m_pFlame[i]->PointEntInit(trace.endpos, this);
if (i < 2)
m_pFlame[i]->SetColor(255, 130, 90);
else
m_pFlame[i]->SetColor(0, 120, 255);
m_pFlame[i]->SetBrightness(190);
m_pFlame[i]->SetBeamFlags(FBEAM_SHADEIN);
m_pFlame[i]->SetScrollRate(20);
// attachment is 1 based in SetEndAttachment
m_pFlame[i]->SetEndAttachment(attach + 2);
CSoundEnt::InsertSound(SOUND_COMBAT, posGun, 384, 0.3);
}
}
CPASAttenuationFilter filter4(this);
EmitSound(filter4, entindex(), "gargantua.BeamAttackOn");
EmitSound(filter4, entindex(), "gargantua.BeamAttackRun");
}
void CNPC_Gargantua::FlameControls(float angleX, float angleY)
{
if (angleY < -180)
angleY += 360;
else if (angleY > 180)
angleY -= 360;
if (angleY < -45)
angleY = -45;
else if (angleY > 45)
angleY = 45;
m_flameX = UTIL_ApproachAngle(angleX, m_flameX, 4);
m_flameY = UTIL_ApproachAngle(angleY, m_flameY, 8);
SetBoneController(0, m_flameY);
SetBoneController(1, m_flameX);
}
void CNPC_Gargantua::FlameUpdate(void)
{
int i;
static float offset[2] = { 60, -60 };
trace_t trace;
Vector vecStart;
QAngle angleGun;
BOOL streaks = FALSE;
Vector vForward;
if (!DispatchParticleOnce) // BriJee OVR: Dispatch once.
{
DispatchParticleEffect("flamethrower", PATTACH_POINT_FOLLOW, this, "1", false); // BriJee OVR: Flame particles
DispatchParticleEffect("flamethrower", PATTACH_POINT_FOLLOW, this, "2", false);
DispatchParticleOnce = true;
}
for (i = 0; i < 2; i++)
{
if (m_pFlame[i])
{
QAngle vecAim = GetAbsAngles();
vecAim.x += -m_flameX;
vecAim.y += m_flameY;
AngleVectors(vecAim, &vForward);
GetAttachment(i + 2, vecStart, angleGun);
Vector vecEnd = vecStart + (vForward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right;
UTIL_TraceLine(vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
m_pFlame[i]->SetStartPos(trace.endpos);
m_pFlame[i + 2]->SetStartPos((vecStart * 0.6) + (trace.endpos * 0.4));
if (trace.fraction != 1.0 && gpGlobals->curtime > m_streakTime)
{
//g_pEffects->Sparks(trace.endpos, 1, 1, &trace.plane.normal); // BJ: Cleanup Sparks + Decals on flame / out of sync with particle
streaks = TRUE;
//UTIL_DecalTrace(&trace, "SmallScorch");
}
// RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN );
FlameDamage(vecStart, trace.endpos, this, this, sk_gargantua_dmg_fire.GetFloat(), CLASS_ZOMBIE, DMG_BURN);
CBroadcastRecipientFilter filter;
GetAttachment(i + 2, vecStart, angleGun);
te->DynamicLight(filter, 0.0, &vecStart, 255, 0, 0, 0, 48, 0.2, 150);
}
}
if (streaks)
m_streakTime = gpGlobals->curtime;
}
void CNPC_Gargantua::FlameDamage(Vector vecStart, Vector vecEnd, CBaseEntity *pevInflictor, CBaseEntity *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType)
{
CBaseEntity *pEntity = NULL;
trace_t tr;
float flAdjustedDamage;
Vector vecSpot;
Vector vecMid = (vecStart + vecEnd) * 0.5;
// float searchRadius = (vecStart - vecMid).Length();
float searchRadius = GARG_FLAME_LENGTH;
float maxDamageRadius = searchRadius / 2.0;
Vector vecAim = (vecEnd - vecStart);
VectorNormalize(vecAim);
// iterate on all entities in the vicinity.
while ((pEntity = gEntList.FindEntityInSphere(pEntity, GetAbsOrigin(), searchRadius)) != NULL)
{
if (pEntity->m_takedamage != DAMAGE_NO)
{
// UNDONE: this should check a damage mask, not an ignore
if (iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore)
{// houndeyes don't hurt other houndeyes with their attack
continue;
}
//DevMsg("Gard check for enemies 1 \n");
//if (DispatchParticleOnce) // BriJee OVR: Ignite enemy NPCs in radius.
//{
//CAI_BaseNPC *pNpcGet;
//pNpcGet = (CAI_BaseNPC*)GetEntityName(pEntity);
//DevMsg("Gard check for enemies 2 \n");
//CBaseCombatCharacter *pBCC;
//pBCC = pEntity->MyCombatCharacterPointer();
if (random->RandomInt(0, 3) == 3) // BJ: 1 to 3 randomize ignite a bit
{
if (FClassnameIs(pEntity, "npc_advisor_fighter") || FClassnameIs(pEntity, "npc_citizen") || FClassnameIs(pEntity, "npc_combine_s") || FClassnameIs(pEntity, "npc_crow") ||
FClassnameIs(pEntity, "npc_hunter") || FClassnameIs(pEntity, "npc_metropolice") || FClassnameIs(pEntity, "npc_stalker") || FClassnameIs(pEntity, "npc_pigeon") ||
FClassnameIs(pEntity, "npc_hgrunt") || FClassnameIs(pEntity, "npc_pitdrone") || FClassnameIs(pEntity, "npc_shocktrooper") || FClassnameIs(pEntity, "npc_seagull") ||
FClassnameIs(pEntity, "npc_voltigore") || FClassnameIs(pEntity, "npc_voltigore_baby") || FClassnameIs(pEntity, "npc_cremator") || FClassnameIs(pEntity, "npc_assassin") ||
FClassnameIs(pEntity, "npc_zombie") || FClassnameIs(pEntity, "npc_zombie_torso") || FClassnameIs(pEntity, "npc_zombine") || FClassnameIs(pEntity, "npc_fastzombie") ||
FClassnameIs(pEntity, "npc_fastzombie_torso") || FClassnameIs(pEntity, "npc_poisonzombie"))
{
CAI_BaseNPC *pNpc = static_cast<CAI_BaseNPC *>(pEntity);
pNpc->IgniteLifetime(20); // BJ: DONT USE Ignite, use IgniteLifetime. It ignore limitations for (combine_s, metropolice) only
//if (pBCC && !pBCC->IsOnFire())
//{
//pBCC->Ignite(20);
DevMsg("Garg ignite %s \n", pNpc->GetClassname());
//pEntity->GetEnemy->ignite(20);
//pEntity->Ignite(30);
//tr.m_pEnt->Ignite();
//pEntity->GetBaseEntity->Ignite(20);
//}
}
//}
}
vecSpot = pEntity->BodyTarget(vecMid);
float dist = DotProduct(vecAim, vecSpot - vecMid);
if (dist > searchRadius)
dist = searchRadius;
else if (dist < -searchRadius)
dist = searchRadius;
Vector vecSrc = vecMid + dist * vecAim;
UTIL_TraceLine(vecStart, vecSpot, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr);
// NDebugOverlay::Line( vecStart, vecSpot, 0, 255, 0, false, 10.0f );
if (tr.fraction == 1.0 || tr.m_pEnt == pEntity)
{// the explosion can 'see' this entity, so hurt them!
// decrease damage for an ent that's farther from the flame.
dist = (vecSrc - tr.endpos).Length();
if (dist > maxDamageRadius)
{
flAdjustedDamage = flDamage - (dist - maxDamageRadius) * 0.4;
if (flAdjustedDamage <= 0)
continue;
}
else
{
flAdjustedDamage = flDamage;
}
// ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) );
if (tr.fraction != 1.0)
{
ClearMultiDamage();
Vector vDir = (tr.endpos - vecSrc);
VectorNormalize(vDir);
CTakeDamageInfo info(pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType);
CalculateMeleeDamageForce(&info, vDir, tr.endpos);
pEntity->DispatchTraceAttack(info, vDir, &tr);
ApplyMultiDamage();
}
else
{
pEntity->TakeDamage(CTakeDamageInfo(pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType));
}
}
}
}
}
void CNPC_Gargantua::FlameDestroy(void)
{
StopParticleEffects(this); // BriJee OVR: Clear all particle effects
DispatchParticleOnce = false;
int i;
CPASAttenuationFilter filter4(this);
EmitSound(filter4, entindex(), "gargantua.BeamAttackOff");
for (i = 0; i < 4; i++)
{
if (m_pFlame[i])
{
UTIL_Remove(m_pFlame[i]);
m_pFlame[i] = NULL;
}
}
}
void CNPC_Gargantua::EyeOn(int level)
{
m_eyeBrightness = level;
}
void CNPC_Gargantua::EyeOff(void)
{
m_eyeBrightness = 0;
}
void CNPC_Gargantua::EyeUpdate(void)
{
if (m_pEyeGlow)
{
m_pEyeGlow->SetBrightness(UTIL_Approach(m_eyeBrightness, m_pEyeGlow->GetBrightness(), 26), 0.5f);
if (m_pEyeGlow->GetBrightness() == 0)
{
m_pEyeGlow->AddEffects(EF_NODRAW);
}
else
{
m_pEyeGlow->RemoveEffects(EF_NODRAW);
}
}
}
void CNPC_Gargantua::StompAttack(void)
{
trace_t trace;
Vector vecForward;
AngleVectors(GetAbsAngles(), &vecForward);
Vector vecStart = GetAbsOrigin() + Vector(0, 0, 60) + 35 * vecForward;
CBaseEntity* pPlayer = GetEnemy();
if (!pPlayer)
return;
Vector vecAim = pPlayer->GetAbsOrigin() - GetAbsOrigin();
VectorNormalize(vecAim);
Vector vecEnd = (vecAim * 1024) + vecStart;
UTIL_TraceLine(vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
// NDebugOverlay::Line( vecStart, vecEnd, 255, 0, 0, false, 10.0f );
CStomp::StompCreate(vecStart, trace.endpos, 0, this);
UTIL_ScreenShake(GetAbsOrigin(), 12.0, 100.0, 2.0, 1000, SHAKE_START);
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "gargantua.StompSound");
UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() - Vector(0, 0, 20), MASK_SOLID, this, COLLISION_GROUP_NONE, &trace);
if (trace.fraction < 1.0)
{
UTIL_DecalTrace(&trace, "SmallScorch");
}
}
void CNPC_Gargantua::DeathEffect(void)
{
int i;
Vector vForward;
AngleVectors(GetAbsAngles(), &vForward);
Vector deathPos = GetAbsOrigin() + vForward * 100;
Vector position = GetAbsOrigin();
position.z += 32;
CPASFilter filter(GetAbsOrigin());
for (i = 0; i < 7; i++)
{
te->Explosion(filter, i * 0.2, &GetAbsOrigin(), g_sModelIndexFireball, 10, 15, TE_EXPLFLAG_NONE, 100, 0);
position.z += 15;
}
UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10);
UTIL_ScreenShake(GetAbsOrigin(), 25.0, 100.0, 5.0, 1000, SHAKE_START);
}
bool CNPC_Gargantua::BecomeRagdoll(const CTakeDamageInfo &info, const Vector &forceVector)
{
return true;//BaseClass::BecomeRagdoll(info, forceVector);//because we dont have the model's ragdoll
}
void CNPC_Gargantua::Event_Killed(const CTakeDamageInfo &info)
{
EyeOff();
UTIL_Remove(m_pEyeGlow);
m_pEyeGlow = NULL;
m_NPCState = NPC_STATE_DEAD;
TranslateSchedule(-1);
//Task_t pTask;
//pTask.flTaskData = TASK_DIE;
//RunTask((const Task_t *)&pTask);
//BaseClass::Event_Killed(info);
m_takedamage = DAMAGE_NO;
//UTIL_Remove(this);
}
void CNPC_Gargantua::TraceAttack(const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator)
{
CTakeDamageInfo subInfo = info;
if (!IsAlive())
{
BaseClass::TraceAttack(subInfo, vecDir, ptr, pAccumulator);
return;
}
// UNDONE: Hit group specific damage?
if (subInfo.GetDamageType() & (GARG_DAMAGE | DMG_BLAST))
{
if (m_painSoundTime < gpGlobals->curtime)
{
CPASAttenuationFilter filter(this);
EmitSound(filter, entindex(), "gargantua.Pain");
m_painSoundTime = gpGlobals->curtime + random->RandomFloat(2.5, 4);
}
}
int bitsDamageType = subInfo.GetDamageType();
bitsDamageType &= GARG_DAMAGE;
subInfo.SetDamageType(bitsDamageType);
if (subInfo.GetDamageType() == 0)
{
if (m_flDmgTime != gpGlobals->curtime || (random->RandomInt(0, 100) < 20))
{
g_pEffects->Ricochet(ptr->endpos, -vecDir);
m_flDmgTime = gpGlobals->curtime;
}
subInfo.SetDamage(0);
}
BaseClass::TraceAttack(subInfo, vecDir, ptr, pAccumulator);
}
int CNPC_Gargantua::OnTakeDamage_Alive(const CTakeDamageInfo &info)
{
if (GetState() == NPC_STATE_SCRIPT)
{
// Invulnerable while scripted. This fixes the problem where garg wouldn't
// explode in C2A1 because for some reason he wouldn't die while scripted, he'd
// only freeze in place. Now he's just immune until he gets to the script and stops.
return 0;
}
CTakeDamageInfo subInfo = info;
float flDamage = subInfo.GetDamage();
if (IsAlive())
{
if (!(subInfo.GetDamageType() & GARG_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(monster_gargantua, CNPC_Gargantua)
DECLARE_TASK(TASK_SOUND_ATTACK)
DECLARE_TASK(TASK_FLAME_SWEEP)
//=========================================================
// > SCHED_GARG_FLAME
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GARG_FLAME,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_SOUND_ATTACK 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_MELEE_ATTACK2"
" TASK_FLAME_SWEEP 4.5"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
)
//=========================================================
// > SCHED_GARG_SWIPE
//=========================================================
DEFINE_SCHEDULE
(
SCHED_GARG_SWIPE,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_MELEE_ATTACK1 0"
" "
" Interrupts"
" COND_CAN_MELEE_ATTACK2"
)
DEFINE_SCHEDULE
(
SCHED_GARG_CHASE_ENEMY,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_GARG_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_GARG_CHASE_ENEMY_FAILED,
" Tasks"
" TASK_SET_ROUTE_SEARCH_TIME 2" // Spend 2 seconds trying 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"
);
DEFINE_SCHEDULE
(
SCHED_GARG_DIE,
" Tasks"
" TASK_DIE 1"
""
);
AI_END_CUSTOM_NPC()