//========= 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(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()