//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Combine Alien Synth enemy // 9 sept 2021 BJ //=============================================================================// #include "cbase.h" #include "npcevent.h" #include "soundenvelope.h" #include "ai_hint.h" #include "ai_moveprobe.h" #include "ai_squad.h" #include "beam_shared.h" #include "globalstate.h" #include "soundent.h" #include "npc_citizen17.h" #include "gib.h" #include "spotlightend.h" #include "IEffects.h" #include "items.h" #include "ai_route.h" #include "player_pickup.h" #include "weapon_physcannon.h" #include "hl2_player.h" #include "npc_mortarsynth.h" #include "materialsystem/imaterialsystemhardwareconfig.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Singleton interfaces //----------------------------------------------------------------------------- extern IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig; //----------------------------------------------------------------------------- // Parameters for how the scanner relates to citizens. //----------------------------------------------------------------------------- #define MSYNTH_CIT_INSPECT_DELAY 10 // Check for citizens this often #define MSYNTH_CIT_INSPECT_GROUND_DIST 500 // How far to look for citizens to inspect #define MSYNTH_CIT_INSPECT_FLY_DIST 1500 // How far to look for citizens to inspect #define MSYNTH_CIT_INSPECT_LENGTH 5 // How long does the inspection last #define MSYNTH_HINT_INSPECT_LENGTH 5 // How long does the inspection last #define MSYNTH_SOUND_INSPECT_LENGTH 5 // How long does the inspection last #define MSYNTH_HINT_INSPECT_DELAY 15 // Check for hint nodes this often #define SPOTLIGHT_WIDTH 32 #define MSYNTH_SPOTLIGHT_NEAR_DIST 256 #define MSYNTH_SPOTLIGHT_FAR_DIST 356 #define MSYNTH_SPOTLIGHT_FLY_HEIGHT 72 #define MSYNTH_NOSPOTLIGHT_FLY_HEIGHT 72 #define MSYNTH_FLASH_MIN_DIST 900 // How far does flash effect enemy #define MSYNTH_FLASH_MAX_DIST 1200 // How far does flash effect enemy #define MSYNTH_FLASH_MAX_VALUE 240 // How bright is maximum flash #define MSYNTH_PHOTO_NEAR_DIST 256 #define MSYNTH_PHOTO_FAR_DIST 356 #define MSYNTH_FOLLOW_DIST 356 #define MSYNTH_NUM_GIBS 6 // Number of gibs in gib file // Strider Scout Scanners #define MSYNTH_SCOUT_MAX_SPEED 200 ConVar sk_mortarsynth_beam_damage("sk_mortarsynth_beam_damage", "0"); ConVar sk_mortarsynth_projectile_damage("sk_mortarsynth_projectile_damage", "0"); ConVar sk_mortarsynth_projectile_damage_calc_difficulty("sk_mortarsynth_projectile_damage_calc_difficulty", "0"); ConVar sk_mortarsynth_health("sk_mortarsynth_health", "0"); ConVar g_debug_mortarsynth("g_debug_mortarsynth", "0"); //added ConVar sk_mortarsynth_explode_damage("sk_mortarsynth_explode_damage", "0"); //----------------------------------------------------------------------------- // Private activities. //----------------------------------------------------------------------------- static int ACT_SCANNER_SMALL_FLINCH_ALERT = 0; static int ACT_SCANNER_SMALL_FLINCH_COMBAT = 0; static int ACT_SCANNER_INSPECT = 0; static int ACT_SCANNER_WALK_ALERT = 0; static int ACT_SCANNER_WALK_COMBAT = 0; static int ACT_SCANNER_FLARE = 0; static int ACT_SCANNER_RETRACT = 0; static int ACT_SCANNER_FLARE_PRONGS = 0; static int ACT_SCANNER_RETRACT_PRONGS = 0; static int ACT_SCANNER_FLARE_START = 0; //----------------------------------------------------------------------------- // Interactions //----------------------------------------------------------------------------- int g_interactionScannerInspect = 0; int g_interactionScannerInspectBegin = 0; int g_interactionScannerInspectHandsUp = 0; int g_interactionScannerInspectShowArmband = 0;//<>still to be completed int g_interactionScannerInspectDone = 0; int g_interactionScannerSupportEntity = 0; int g_interactionScannerSupportPosition = 0; //----------------------------------------------------------------------------- // Animation events //------------------------------------------------------------------------ int AE_MSYNTH_CLOSED; //----------------------------------------------------------------------------- // Attachment points //----------------------------------------------------------------------------- #define MSYNTH_ATTACHMENT_LIGHT "light" #define MSYNTH_ATTACHMENT_FLASH 1 #define MSYNTH_ATTACHMENT_LPRONG 2 #define MSYNTH_ATTACHMENT_RPRONG 3 //----------------------------------------------------------------------------- // Other defines. //----------------------------------------------------------------------------- #define SCANNER_MAX_BEAMS 4 BEGIN_DATADESC(CNPC_MSynth) DEFINE_SOUNDPATCH(m_pEngineSound), DEFINE_KEYFIELD(m_BeamArmed, FIELD_BOOLEAN, "HasBeamWeapon"), DEFINE_FIELD(m_pEnergyBeam, FIELD_CLASSPTR), DEFINE_ARRAY(m_pEnergyGlow, FIELD_CLASSPTR, 2), DEFINE_EMBEDDED(m_KilledInfo), DEFINE_FIELD(m_bGibbing, FIELD_INTEGER), DEFINE_FIELD(m_flGoalOverrideDistance, FIELD_FLOAT), DEFINE_FIELD(m_bPhotoTaken, FIELD_BOOLEAN), DEFINE_FIELD(m_vInspectPos, FIELD_VECTOR), DEFINE_FIELD(m_fInspectEndTime, FIELD_TIME), DEFINE_FIELD(m_fCheckCitizenTime, FIELD_TIME), DEFINE_FIELD(m_fCheckHintTime, FIELD_TIME), DEFINE_KEYFIELD(m_bShouldInspect, FIELD_BOOLEAN, "ShouldInspect"), DEFINE_KEYFIELD(m_bOnlyInspectPlayers, FIELD_BOOLEAN, "OnlyInspectPlayers"), DEFINE_KEYFIELD(m_bNeverInspectPlayers, FIELD_BOOLEAN, "NeverInspectPlayers"), DEFINE_FIELD(m_fNextPhotographTime, FIELD_TIME), // DEFINE_FIELD( m_pEyeFlash, FIELD_CLASSPTR ), DEFINE_FIELD(m_vSpotlightTargetPos, FIELD_POSITION_VECTOR), DEFINE_FIELD(m_vSpotlightCurrentPos, FIELD_POSITION_VECTOR), // don't save (recreated after restore/transition) // DEFINE_FIELD( m_hSpotlight, FIELD_EHANDLE ), // DEFINE_FIELD( m_hSpotlightTarget, FIELD_EHANDLE ), DEFINE_FIELD(m_vSpotlightDir, FIELD_VECTOR), DEFINE_FIELD(m_vSpotlightAngVelocity, FIELD_VECTOR), DEFINE_FIELD(m_flSpotlightCurLength, FIELD_FLOAT), DEFINE_FIELD(m_fNextSpotlightTime, FIELD_TIME), DEFINE_FIELD(m_nHaloSprite, FIELD_INTEGER), DEFINE_FIELD(m_fNextFlySoundTime, FIELD_TIME), DEFINE_FIELD(m_nFlyMode, FIELD_INTEGER), DEFINE_FIELD(m_nPoseTail, FIELD_INTEGER), DEFINE_FIELD(m_nPoseDynamo, FIELD_INTEGER), DEFINE_FIELD(m_nPoseFlare, FIELD_INTEGER), DEFINE_FIELD(m_nPoseFaceVert, FIELD_INTEGER), DEFINE_FIELD(m_nPoseFaceHoriz, FIELD_INTEGER), DEFINE_FIELD(m_bIsClawScanner, FIELD_BOOLEAN), DEFINE_FIELD(m_bIsOpen, FIELD_BOOLEAN), // DEFINE_FIELD( m_bHasSpoken, FIELD_BOOLEAN ), DEFINE_FIELD(m_pSmokeTrail, FIELD_CLASSPTR), DEFINE_FIELD(m_flFlyNoiseBase, FIELD_FLOAT), DEFINE_FIELD(m_flEngineStallTime, FIELD_TIME), DEFINE_FIELD(m_vecDiveBombDirection, FIELD_VECTOR), DEFINE_FIELD(m_flDiveBombRollForce, FIELD_FLOAT), DEFINE_KEYFIELD(m_flSpotlightMaxLength, FIELD_FLOAT, "SpotlightLength"), DEFINE_KEYFIELD(m_flSpotlightGoalWidth, FIELD_FLOAT, "SpotlightWidth"), // Physics Influence DEFINE_FIELD(m_hPhysicsAttacker, FIELD_EHANDLE), DEFINE_FIELD(m_flLastPhysicsInfluenceTime, FIELD_TIME), DEFINE_KEYFIELD(m_bNoLight, FIELD_BOOLEAN, "SpotlightDisabled"), DEFINE_INPUTFUNC(FIELD_VOID, "DisableSpotlight", InputDisableSpotlight), DEFINE_INPUTFUNC(FIELD_STRING, "InspectTargetPhoto", InputInspectTargetPhoto), DEFINE_INPUTFUNC(FIELD_STRING, "InspectTargetSpotlight", InputInspectTargetSpotlight), DEFINE_INPUTFUNC(FIELD_INTEGER, "InputShouldInspect", InputShouldInspect), DEFINE_INPUTFUNC(FIELD_STRING, "SetFollowTarget", InputSetFollowTarget), DEFINE_INPUTFUNC(FIELD_VOID, "ClearFollowTarget", InputClearFollowTarget), DEFINE_INPUTFUNC(FIELD_STRING, "DeployMine", InputDeployMine), DEFINE_INPUTFUNC(FIELD_STRING, "EquipMine", InputEquipMine), DEFINE_OUTPUT(m_OnPhotographPlayer, "OnPhotographPlayer"), DEFINE_OUTPUT(m_OnPhotographNPC, "OnPhotographNPC"), END_DATADESC() LINK_ENTITY_TO_CLASS(npc_mortarsynth, CNPC_MSynth); #ifndef CLIENT_DLL void TE_StickyBolt(IRecipientFilter& filter, float delay, Vector vecDirection, const Vector *origin); //----------------------------------------------------------------------------- // ShockRifle //----------------------------------------------------------------------------- class CMSynthProjectile : public CBaseCombatCharacter { DECLARE_CLASS(CMSynthProjectile, CBaseCombatCharacter); public: CMSynthProjectile() { }; ~CMSynthProjectile(); Class_T Classify(void) { return CLASS_COMBINE; } public: void Spawn(void); void Precache(void); void BubbleThink(void); // changed void BoltTouch(CBaseEntity *pOther); bool CreateVPhysics(void); unsigned int PhysicsSolidMaskForEntity() const; static CMSynthProjectile *BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CNPC_MSynth *pentOwner = NULL); protected: bool CreateSprites(void); CHandle m_pGlowSprite; int m_iDamage; private: CNPC_MSynth *pParent = NULL; const char *Particle1; const char *Particle2; DECLARE_DATADESC(); //DECLARE_SERVERCLASS(); }; LINK_ENTITY_TO_CLASS(msynth_projectile, CMSynthProjectile); BEGIN_DATADESC(CMSynthProjectile) DEFINE_FUNCTION(BubbleThink), DEFINE_FUNCTION(BoltTouch), DEFINE_FIELD(m_pGlowSprite, FIELD_EHANDLE), END_DATADESC() //IMPLEMENT_SERVERCLASS_ST(CMSynthProjectile, DT_MSynthProjectile) //END_SEND_TABLE() #define BOLT_MODEL_NPC cvar->FindVar("oc_weapon_ShockRifle_model")->GetString() //"models/ShockRifle.mdl" #define BOLT_SKIN_GLOW_NPC 1 CMSynthProjectile *CMSynthProjectile::BoltCreate(const Vector &vecOrigin, const QAngle &angAngles, int iDamage, CNPC_MSynth *pentOwner) { CMSynthProjectile *pProjectile = (CMSynthProjectile *)CreateEntityByName("msynth_projectile"); UTIL_SetOrigin(pProjectile, vecOrigin); pProjectile->SetAbsAngles(angAngles); pProjectile->Spawn(); pProjectile->SetOwnerEntity(pentOwner); pProjectile->pParent = pentOwner; pProjectile->Particle1 = pentOwner->MSYNTH_PARTICLE_TRAIL; pProjectile->Particle1 = pentOwner->MSYNTH_PARTICLE_GLOW; DispatchParticleEffect(pentOwner->MSYNTH_PARTICLE_TRAIL, PATTACH_ABSORIGIN_FOLLOW, pProjectile); DispatchParticleEffect(pentOwner->MSYNTH_PARTICLE_GLOW, PATTACH_ABSORIGIN_FOLLOW, pProjectile); pProjectile->m_iDamage = iDamage; return pProjectile; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMSynthProjectile::~CMSynthProjectile(void) { if (m_pGlowSprite) { UTIL_Remove(m_pGlowSprite); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CMSynthProjectile::CreateVPhysics(void) { VPhysicsInitNormal(SOLID_BBOX, FSOLID_NOT_STANDABLE, false); return true; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- unsigned int CMSynthProjectile::PhysicsSolidMaskForEntity() const { return (BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX) & ~CONTENTS_GRATE; } bool CMSynthProjectile::CreateSprites(void) { //DevMsg("%s \n", pParent->MSYNTH_PARTICLE_TRAIL); //DevMsg("%s \n", pParent->MSYNTH_PARTICLE_GLOW); //if (pParent) //{ //DispatchParticleEffect(Particle1, PATTACH_ABSORIGIN_FOLLOW, this); //DispatchParticleEffect(Particle2, PATTACH_ABSORIGIN_FOLLOW, this); //} return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMSynthProjectile::Spawn(void) { Precache(); SetModel(BOLT_MODEL_NPC); SetMoveType(MOVETYPE_FLY, MOVECOLLIDE_FLY_CUSTOM); UTIL_SetSize(this, -Vector(10, 10, 10), Vector(10, 10, 10)); SetSolid(SOLID_BBOX); SetModelScale(0.3f, 0.0f); CollisionProp()->SetSurroundingBoundsType(USE_HITBOXES); UpdateWaterState(); SetTouch(&CMSynthProjectile::BoltTouch); SetThink(&CMSynthProjectile::BubbleThink); SetNextThink(gpGlobals->curtime + 0.02f); AddEFlags(EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL | EFL_NO_PHYSCANNON_INTERACTION); CreateSprites(); m_nSkin = BOLT_SKIN_GLOW_NPC; } void CMSynthProjectile::Precache(void) { PrecacheModel(BOLT_MODEL_NPC); /*PrecacheParticleSystem(GetOwnerEntity()->MSYNTH_PARTICLE_TRAIL); PrecacheParticleSystem(MSYNTH_PARTICLE_GLOW); PrecacheParticleSystem(MSYNTH_PARTICLE_EXPLOSION);*/ BaseClass::Precache(); } //----------------------------------------------------------------------------- // Purpose: // Input : *pOther - //----------------------------------------------------------------------------- void CMSynthProjectile::BoltTouch(CBaseEntity *pOther) { if (!pOther->IsSolid() || pOther->IsSolidFlagSet(FSOLID_VOLUME_CONTENTS)) return; trace_t tr2; tr2 = BaseClass::GetTouchTrace(); RadiusDamage(CTakeDamageInfo(this, NULL, cvar->FindVar("sk_mortarsynth_projectile_damage")->GetFloat(), DMG_BLAST), GetAbsOrigin(), 130, CLASS_NONE, NULL); if (pParent) { //DevMsg("%s \n", pParent->MSYNTH_PARTICLE_EXPLOSION); DispatchParticleEffect(pParent->MSYNTH_PARTICLE_EXPLOSION, tr2.endpos, GetAbsAngles()); } if (pOther->m_takedamage != DAMAGE_NO) { trace_t tr; tr = BaseClass::GetTouchTrace(); Vector vecNormalizedVel = GetAbsVelocity(); ClearMultiDamage(); VectorNormalize(vecNormalizedVel); if (GetOwnerEntity() && GetOwnerEntity()->IsPlayer() && pOther->IsNPC()) { //CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_NEVERGIB ); CTakeDamageInfo dmgInfo(this, GetOwnerEntity(), cvar->FindVar("sk_mortarsynth_projectile_damage_calc_difficulty")->GetFloat(), DMG_SHOCK); dmgInfo.AdjustPlayerDamageInflictedForSkillLevel(); CalculateMeleeDamageForce(&dmgInfo, vecNormalizedVel, tr.endpos, 0.7f); dmgInfo.SetDamagePosition(tr.endpos); pOther->DispatchTraceAttack(dmgInfo, vecNormalizedVel, &tr); } else { //CTakeDamageInfo dmgInfo( this, GetOwnerEntity(), m_iDamage, DMG_BULLET | DMG_NEVERGIB ); CTakeDamageInfo dmgInfo(this, GetOwnerEntity(), cvar->FindVar("sk_mortarsynth_projectile_damage_calc_difficulty")->GetFloat(), DMG_NEVERGIB | DMG_SHOCK); CalculateMeleeDamageForce(&dmgInfo, vecNormalizedVel, tr.endpos, 0.7f); dmgInfo.SetDamagePosition(tr.endpos); pOther->DispatchTraceAttack(dmgInfo, vecNormalizedVel, &tr); } ApplyMultiDamage(); //Adrian: keep going through the glass. if (pOther->GetCollisionGroup() == COLLISION_GROUP_BREAKABLE_GLASS) return; SetAbsVelocity(Vector(0, 0, 0)); EmitSound(MSYNTH_SHOCK_IMPACT_SOUND); CEffectData data; data.m_vOrigin = tr.endpos + (tr.plane.normal * 1.0f); data.m_vNormal = tr.plane.normal; g_pEffects->Sparks(data.m_vOrigin); SetTouch(NULL); SetThink(NULL); UTIL_Remove(this); } else { trace_t tr; Vector vForward; AngleVectors(GetAbsAngles(), &vForward); VectorNormalize(vForward); UTIL_TraceLine(GetAbsOrigin(), GetAbsOrigin() + vForward * 128, MASK_OPAQUE, pOther, COLLISION_GROUP_NONE, &tr); EmitSound(MSYNTH_SHOCK_IMPACT_SOUND); CEffectData data; data.m_vOrigin = tr.endpos + (tr.plane.normal * 1.0f); data.m_vNormal = tr.plane.normal; g_pEffects->Sparks(data.m_vOrigin); UTIL_DecalTrace(&tr, "SmallScorch"); //"Scorch" ); UTIL_Remove(this); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMSynthProjectile::BubbleThink(void) { QAngle angNewAngles; VectorAngles(GetAbsVelocity(), angNewAngles); SetAbsAngles(angNewAngles); SetNextThink(gpGlobals->curtime + 0.02f); if (GetWaterLevel() == 0) return; //DispatchParticleEffect("Shockrifle_sparks", PATTACH_ABSORIGIN, NULL); UTIL_Remove(this); // BriJee OVR : Little trick to get entity killed in water //UTIL_BubbleTrail( GetAbsOrigin() - GetAbsVelocity() * 0.1f, GetAbsOrigin(), 5 ); } #endif //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CNPC_MSynth::CNPC_MSynth() { #ifdef _DEBUG m_vInspectPos.Init(); m_vSpotlightTargetPos.Init(); m_vSpotlightCurrentPos.Init(); m_vSpotlightDir.Init(); m_vSpotlightAngVelocity.Init(); #endif m_bShouldInspect = true; m_bOnlyInspectPlayers = false; m_bNeverInspectPlayers = false; m_flAttackNearDist = MSYNTH_ATTACK_NEAR_DIST; m_flAttackFarDist = MSYNTH_ATTACK_FAR_DIST; m_flAttackRange = MSYNTH_ATTACK_RANGE; //m_bGibbing = 0; char szMapName[256]; Q_strncpy(szMapName, STRING(gpGlobals->mapname), sizeof(szMapName)); Q_strlower(szMapName); /*if (!Q_strnicmp(szMapName, "d3_c17", 6)) { // Streetwar scanners are claw scanners m_bIsClawScanner = true; } else*/ //{ m_bIsClawScanner = false; //} } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::Spawn(void) { // Check for user error if (m_flSpotlightMaxLength <= 0) { DevMsg("CNPC_MSynth::Spawn: Invalid spotlight length <= 0, setting to 500\n"); m_flSpotlightMaxLength = 500; } if (m_flSpotlightGoalWidth <= 0) { DevMsg("CNPC_MSynth::Spawn: Invalid spotlight width <= 0, setting to 100\n"); m_flSpotlightGoalWidth = 100; } if (m_flSpotlightGoalWidth > MAX_BEAM_WIDTH) { DevMsg("CNPC_MSynth::Spawn: Invalid spotlight width %.1f (max %.1f).\n", m_flSpotlightGoalWidth, MAX_BEAM_WIDTH); m_flSpotlightGoalWidth = MAX_BEAM_WIDTH; } Precache(); SetModel("models/mortarsynth_over.mdl"); //m_bGibbing = random->RandomInt(0, 1); m_bGibbing = 1; // BJ For now ALWAYS explode /*if (m_bIsClawScanner) { SetModel("models/shield_scanner.mdl"); } else { SetModel("models/combine_scanner.mdl"); }*/ m_pEnergyBeam = NULL; m_iHealth = sk_mortarsynth_health.GetFloat(); m_iMaxHealth = m_iHealth; // ------------------------------------ // Init all class vars // ------------------------------------ m_vInspectPos = vec3_origin; m_fInspectEndTime = 0; m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY; m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY; m_fNextPhotographTime = 0; m_vSpotlightTargetPos = vec3_origin; m_vSpotlightCurrentPos = vec3_origin; m_hSpotlight = NULL; m_hSpotlightTarget = NULL; AngleVectors(GetLocalAngles(), &m_vSpotlightDir); m_vSpotlightAngVelocity = vec3_origin; m_pEyeFlash = 0; m_fNextSpotlightTime = 0; m_nFlyMode = MSYNTH_FLY_PATROL; m_vCurrentBanking = m_vSpotlightDir; m_flSpotlightCurLength = m_flSpotlightMaxLength; m_nPoseTail = LookupPoseParameter("tail_control"); m_nPoseDynamo = LookupPoseParameter("dynamo_wheel"); m_nPoseFlare = LookupPoseParameter("alert_control"); m_nPoseFaceVert = LookupPoseParameter("flex_vert"); m_nPoseFaceHoriz = LookupPoseParameter("flex_horz"); // -------------------------------------------- CapabilitiesAdd(bits_CAP_INNATE_MELEE_ATTACK1); m_bPhotoTaken = false; // BJ: Check model collision Vector vecSurroundingMins(-80, -80, 0); Vector vecSurroundingMaxs(80, 80, 214); CollisionProp()->SetSurroundingBoundsType(USE_SPECIFIED_BOUNDS, &vecSurroundingMins, &vecSurroundingMaxs); SetHullType(HULL_SMALL_CENTERED); // BJ: Perfect HULL type SetHullSizeNormal(); AddSolidFlags(FSOLID_NOT_STANDABLE); // player stand upper? SetSolid(SOLID_BBOX); //bj test //SetCollisionGroup(HL2COLLISION_GROUP_HEADCRAB); if (m_bIsClawScanner) DevMsg("Mortar - Claw type check \n"); BaseClass::Spawn(); // sethull was here // Watch for this error state if (m_bOnlyInspectPlayers && m_bNeverInspectPlayers) { Assert(0); Warning("ERROR: Mortarsynth set to never and always inspect players!\n"); } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void CNPC_MSynth::Activate() { BaseClass::Activate(); // Have to do this here because sprites do not go across level transitions m_pEyeFlash = CSprite::SpriteCreate("sprites/blueflare1.vmt", GetLocalOrigin(), FALSE); m_pEyeFlash->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation); m_pEyeFlash->SetAttachment(this, LookupAttachment(MSYNTH_ATTACHMENT_LIGHT)); m_pEyeFlash->SetBrightness(0); m_pEyeFlash->SetScale(1.4); } //------------------------------------------------------------------------------ // Purpose: Override to split in two when attacked //------------------------------------------------------------------------------ int CNPC_MSynth::OnTakeDamage_Alive(const CTakeDamageInfo &info) { // Turn off my spotlight when shot SpotlightDestroy(); m_fNextSpotlightTime = gpGlobals->curtime + 2.0f; return (BaseClass::OnTakeDamage_Alive(info)); } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::Gib(void) { if (IsMarkedForDeletion()) return; //DevMsg("Msynth GIB after_mark \n"); // BJ: ONLY RIGHT GIBS NO LESS /*CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib1.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib4.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib5.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/Shield_Scanner_Gib6.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/scanner_gib04.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib01.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib02.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib03.mdl"); CGib::SpawnSpecificGibs(this, 1, 500, 250, "models/gibs/manhack_gib04.mdl");*/ // Add a random chance of spawning a battery... if (!HasSpawnFlags(SF_NPC_NO_WEAPON_DROP) && random->RandomFloat(0.0f, 1.0f) < 0.3f) { CItem *pBattery = (CItem*)CreateEntityByName("item_battery"); if (pBattery) { pBattery->SetAbsOrigin(GetAbsOrigin()); pBattery->SetAbsVelocity(GetAbsVelocity()); pBattery->SetLocalAngularVelocity(GetLocalAngularVelocity()); pBattery->ActivateWhenAtRest(); pBattery->Spawn(); } } //DeployMine(); RadiusDamage(CTakeDamageInfo(this, this, sk_mortarsynth_explode_damage.GetFloat(), DMG_BLAST), GetAbsOrigin(), 256, CLASS_COMBINE, NULL); // BJ Self Explode damage BaseClass::Gib(); } //----------------------------------------------------------------------------- // Purpose: // Input : pInflictor - // pAttacker - // flDamage - // bitsDamageType - //----------------------------------------------------------------------------- void CNPC_MSynth::Event_Killed(const CTakeDamageInfo &info) { // Copy off the takedamage info that killed me, since we're not going to call // up into the base class's Event_Killed() until we gib. (gibbing is ultimate death) m_KilledInfo = info; //MikeD: We don't have mines //DeployMine(); if (m_pEnergyBeam) { UTIL_Remove(m_pEnergyBeam); m_pEnergyBeam = NULL; } for (int i = 0; i < 2; i++) { if (m_pEnergyGlow[i]) { UTIL_Remove(m_pEnergyGlow[i]); m_pEnergyGlow[i] = NULL; } } ClearInspectTarget(); // Interrupt whatever schedule I'm on SetCondition(COND_SCHEDULE_DONE); // Remove spotlight SpotlightDestroy(); // Remove sprite UTIL_Remove(m_pEyeFlash); m_pEyeFlash = NULL; // If I have an enemy and I'm up high, do a dive bomb (unless dissolved) if (!m_bIsClawScanner && GetEnemy() != NULL && (info.GetDamageType() & DMG_DISSOLVE) == false) { DevMsg("Mortarsynth PRE bomb mode attack \n"); Vector vecDelta = GetLocalOrigin() - GetEnemy()->GetLocalOrigin(); if ((vecDelta.z > 120) && (vecDelta.Length() > 360)) { DevMsg("Mortarsynth bomb mode attack \n"); // If I'm divebombing, don't take any more damage. It will make Event_Killed() be called again. // This is especially bad if someone machineguns the divebombing scanner. AttackDivebomb(); return; } } if (m_bGibbing == 1) Gib(); else BecomeRagdoll(m_KilledInfo, m_KilledInfo.GetDamageForce()); } bool CNPC_MSynth::BecomeRagdoll(const CTakeDamageInfo &info, const Vector &forceVector) { if (m_bGibbing == 1) return false; else return BaseClass::BecomeRagdoll(info, forceVector); } //----------------------------------------------------------------------------- // Purpose: Tells use whether or not the NPC cares about a given type of hint node. // Input : sHint - // Output : TRUE if the NPC is interested in this hint type, FALSE if not. //----------------------------------------------------------------------------- bool CNPC_MSynth::FValidateHintType(CAI_Hint *pHint) { return(pHint->HintType() == HINT_WORLD_WINDOW); } //----------------------------------------------------------------------------- // Purpose: // Input : Type - //----------------------------------------------------------------------------- int CNPC_MSynth::TranslateSchedule(int scheduleType) { switch (scheduleType) { case SCHED_IDLE_STAND: { return SCHED_SCANNER_PATROL; } case SCHED_SCANNER_PATROL: return SCHED_CSCANNER_PATROL; } return BaseClass::TranslateSchedule(scheduleType); } //----------------------------------------------------------------------------- // Purpose: // Input : idealActivity - // *pIdealWeaponActivity - // Output : int //----------------------------------------------------------------------------- Activity CNPC_MSynth::NPC_TranslateActivity(Activity eNewActivity) { if (!m_bIsClawScanner || GetActivity() == ACT_RANGE_ATTACK1) { return BaseClass::NPC_TranslateActivity(eNewActivity); } // The claw scanner came along a little late and doesn't have the activities // of the city scanner. So Just pick between these three if (eNewActivity == ACT_DISARM) { // Closing up. return eNewActivity; } if (m_bIsOpen) { return ACT_IDLE_ANGRY; } else { return ACT_IDLE; } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_MSynth::HandleAnimEvent(animevent_t *pEvent) { if (pEvent->event == AE_MSYNTH_CLOSED) { m_bIsOpen = false; SetActivity(ACT_IDLE); return; } BaseClass::HandleAnimEvent(pEvent); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- char *CNPC_MSynth::GetEngineSound(void) { //if (m_bIsClawScanner) //return MSYNTH_ENGINE_SOUND; return MSYNTH_ENGINE_SOUND; } //----------------------------------------------------------------------------- // Purpose: Plays the engine sound. //----------------------------------------------------------------------------- void CNPC_MSynth::NPCThink(void) { if (!IsAlive()) { SetActivity((Activity)ACT_SCANNER_RETRACT_PRONGS); StudioFrameAdvance(); SetNextThink(gpGlobals->curtime + 0.1f); } else { BaseClass::NPCThink(); SpotlightUpdate(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::Precache(void) { PrecacheModel("models/mortarsynth_over.mdl"); PrecacheParticleSystem("CombineGuard_Attack_Laser"); PrecacheParticleSystem("CombineGuard_Muzzle_Flash"); PrecacheParticleSystem("weapon_muzzle_flash_shock_npc"); PrecacheScriptSound(MSYNTH_IDLE_SOUND); PrecacheScriptSound(MSYNTH_IDLE2_SOUND); PrecacheScriptSound(MSYNTH_ATTACK_SOUND); PrecacheScriptSound(MSYNTH_SHOCK_IMPACT_SOUND); PrecacheScriptSound(MSYNTH_DEPLOY_SOUND); PrecacheScriptSound(MSYNTH_PREATTACK_SOUND); PrecacheScriptSound(MSYNTH_PAIN_SOUND); PrecacheScriptSound(MSYNTH_ALERT_SOUND); PrecacheScriptSound(MSYNTH_DIE_SOUND); PrecacheScriptSound(MSYNTH_ENGINE_SOUND); PrecacheScriptSound(MSYNTH_SPEECH1_SOUND); PrecacheScriptSound(MSYNTH_SPEECH2_SOUND); PrecacheScriptSound(MSYNTH_SPEECH3_SOUND); PrecacheScriptSound(MSYNTH_SPEECH4_SOUND); // BJ: ONLY RIGHT GIBS NO LESS // Model //if (m_bIsClawScanner) //{ //PrecacheModel("models/shield_scanner.mdl"); PrecacheModel("models/gibs/Shield_Scanner_Gib1.mdl"); //PrecacheModel("models/gibs/Shield_Scanner_Gib2.mdl"); //PrecacheModel("models/gibs/Shield_Scanner_Gib3.mdl"); PrecacheModel("models/gibs/Shield_Scanner_Gib4.mdl"); PrecacheModel("models/gibs/Shield_Scanner_Gib5.mdl"); PrecacheModel("models/gibs/Shield_Scanner_Gib6.mdl"); PrecacheModel("models/gibs/scanner_gib04.mdl"); PrecacheModel("models/gibs/manhack_gib01.mdl"); PrecacheModel("models/gibs/manhack_gib02.mdl"); PrecacheModel("models/gibs/manhack_gib03.mdl"); PrecacheModel("models/gibs/manhack_gib04.mdl"); /* PrecacheScriptSound("NPC_SScanner.Shoot"); PrecacheScriptSound("NPC_SScanner.Alert"); PrecacheScriptSound("NPC_SScanner.Die"); PrecacheScriptSound("NPC_SScanner.Combat"); PrecacheScriptSound("NPC_SScanner.Idle"); PrecacheScriptSound("NPC_SScanner.Pain"); PrecacheScriptSound("NPC_SScanner.TakePhoto"); PrecacheScriptSound("NPC_SScanner.AttackFlash"); PrecacheScriptSound("NPC_SScanner.DiveBombFlyby"); PrecacheScriptSound("NPC_SScanner.DiveBomb"); PrecacheScriptSound("NPC_SScanner.DeployMine"); */ //PrecacheScriptSound("NPC_SScanner.FlyLoop"); UTIL_PrecacheOther("combine_mine"); //} /*else { PrecacheModel("models/combine_scanner.mdl"); PrecacheModel("models/gibs/scanner_gib01.mdl"); PrecacheModel("models/gibs/scanner_gib02.mdl"); PrecacheModel("models/gibs/scanner_gib02.mdl"); PrecacheModel("models/gibs/scanner_gib04.mdl"); PrecacheModel("models/gibs/scanner_gib05.mdl"); PrecacheScriptSound("NPC_CScanner.Shoot"); PrecacheScriptSound("NPC_CScanner.Alert"); PrecacheScriptSound("NPC_CScanner.Die"); PrecacheScriptSound("NPC_CScanner.Combat"); PrecacheScriptSound("NPC_CScanner.Idle"); PrecacheScriptSound("NPC_CScanner.Pain"); PrecacheScriptSound("NPC_CScanner.TakePhoto"); PrecacheScriptSound("NPC_CScanner.AttackFlash"); PrecacheScriptSound("NPC_CScanner.DiveBombFlyby"); PrecacheScriptSound("NPC_CScanner.DiveBomb"); PrecacheScriptSound("NPC_CScanner.DeployMine"); PrecacheScriptSound("NPC_CScanner.FlyLoop"); }*/ // Sprites m_nHaloSprite = PrecacheModel("sprites/light_glow03.vmt"); PrecacheModel("sprites/glow_test02.vmt"); BaseClass::Precache(); } //------------------------------------------------------------------------------ // Purpose: Request help inspecting from other squad members //------------------------------------------------------------------------------ void CNPC_MSynth::RequestInspectSupport(void) { if (m_pSquad) { AISquadIter_t iter; for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember(&iter); pSquadMember; pSquadMember = m_pSquad->GetNextMember(&iter)) { if (pSquadMember != this) { if (GetTarget()) { pSquadMember->DispatchInteraction(g_interactionScannerSupportEntity, ((void *)((CBaseEntity*)GetTarget())), this); } else { pSquadMember->DispatchInteraction(g_interactionScannerSupportPosition, ((void *)m_vInspectPos.Base()), this); } } } } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ bool CNPC_MSynth::IsValidInspectTarget(CBaseEntity *pEntity) { // If a citizen, make sure he can be inspected again if (pEntity->Classify() == CLASS_CITIZEN_PASSIVE) { if (((CNPC_Citizen*)pEntity)->GetNextScannerInspectTime() > gpGlobals->curtime) { return false; } } // Make sure no other squad member has already chosen to // inspect this entity if (m_pSquad) { AISquadIter_t iter; for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember(&iter); pSquadMember; pSquadMember = m_pSquad->GetNextMember(&iter)) { if (pSquadMember->GetTarget() == pEntity) { return false; } } } // Do not inspect friendly targets if (IRelationType(pEntity) == D_LI) return false; return true; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ CBaseEntity* CNPC_MSynth::BestInspectTarget(void) { if (!m_bShouldInspect) return NULL; CBaseEntity* pBestEntity = NULL; float fBestDist = MAX_COORD_RANGE; float fTestDist; CBaseEntity *pEntity = NULL; // If I have a spotlight, search from the spotlight position // otherwise search from my position Vector vSearchOrigin; float fSearchDist; if (m_hSpotlightTarget != NULL) { vSearchOrigin = m_hSpotlightTarget->GetAbsOrigin(); fSearchDist = MSYNTH_CIT_INSPECT_GROUND_DIST; } else { vSearchOrigin = WorldSpaceCenter(); fSearchDist = MSYNTH_CIT_INSPECT_FLY_DIST; } if (m_bOnlyInspectPlayers) { CBasePlayer *pPlayer = AI_GetSinglePlayer(); if (!pPlayer) return NULL; if (!pPlayer->IsAlive() || (pPlayer->GetFlags() & FL_NOTARGET)) return NULL; return WorldSpaceCenter().DistToSqr(pPlayer->EyePosition()) <= (fSearchDist * fSearchDist) ? pPlayer : NULL; } CUtlVector candidates; float fSearchDistSq = fSearchDist * fSearchDist; int i; // Inspect players unless told otherwise if (m_bNeverInspectPlayers == false) { // Players for (i = 1; i <= gpGlobals->maxClients; i++) { CBaseEntity *pPlayer = UTIL_PlayerByIndex(i); if (pPlayer) { if (vSearchOrigin.DistToSqr(pPlayer->GetAbsOrigin()) < fSearchDistSq) { candidates.AddToTail(pPlayer); } } } } // NPCs CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); for (i = 0; i < g_AI_Manager.NumAIs(); i++) { if (ppAIs[i] != this && vSearchOrigin.DistToSqr(ppAIs[i]->GetAbsOrigin()) < fSearchDistSq) { candidates.AddToTail(ppAIs[i]); } } for (i = 0; i < candidates.Count(); i++) { pEntity = candidates[i]; Assert(pEntity != this && (pEntity->MyNPCPointer() || pEntity->IsPlayer())); CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); if ((pNPC && pNPC->Classify() == CLASS_CITIZEN_PASSIVE) || pEntity->IsPlayer()) { if (pEntity->GetFlags() & FL_NOTARGET) continue; if (pEntity->IsAlive() == false) continue; // Ensure it's within line of sight if (!FVisible(pEntity)) continue; fTestDist = (GetAbsOrigin() - pEntity->EyePosition()).Length(); if (fTestDist < fBestDist) { if (IsValidInspectTarget(pEntity)) { fBestDist = fTestDist; pBestEntity = pEntity; } } } } return pBestEntity; } //------------------------------------------------------------------------------ // Purpose: Clears any previous inspect target and set inspect target to // the given entity and set the durection of the inspection //------------------------------------------------------------------------------ void CNPC_MSynth::SetInspectTargetToEnt(CBaseEntity *pEntity, float fInspectDuration) { ClearInspectTarget(); SetTarget(pEntity); m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; } //------------------------------------------------------------------------------ // Purpose: Clears any previous inspect target and set inspect target to // the given hint node and set the durection of the inspection //------------------------------------------------------------------------------ void CNPC_MSynth::SetInspectTargetToHint(CAI_Hint *pHint, float fInspectDuration) { ClearInspectTarget(); float yaw = pHint->Yaw(); // -------------------------------------------- // Figure out the location that the hint hits // -------------------------------------------- Vector vHintDir = UTIL_YawToVector(yaw); Vector vHintOrigin; pHint->GetPosition(this, &vHintOrigin); Vector vHintEnd = vHintOrigin + (vHintDir * 512); trace_t tr; AI_TraceLine(vHintOrigin, vHintEnd, MASK_BLOCKLOS, this, COLLISION_GROUP_NONE, &tr); if (g_debug_mortarsynth.GetBool()) { NDebugOverlay::Line(vHintOrigin, tr.endpos, 255, 0, 0, true, 4.0f); NDebugOverlay::Cross3D(tr.endpos, -Vector(8, 8, 8), Vector(8, 8, 8), 255, 0, 0, true, 4.0f); } if (tr.fraction == 1.0f) { DevMsg("ERROR: Mortarsynth hint node not facing a surface!\n"); } else { SetHintNode(pHint); m_vInspectPos = tr.endpos; pHint->Lock(this); m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; } } //------------------------------------------------------------------------------ // Purpose: Clears any previous inspect target and set inspect target to // the given position and set the durection of the inspection // Input : // Output : //------------------------------------------------------------------------------ void CNPC_MSynth::SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration) { ClearInspectTarget(); m_vInspectPos = vInspectPos; m_fInspectEndTime = gpGlobals->curtime + fInspectDuration; } //------------------------------------------------------------------------------ // Purpose: Clears out any previous inspection targets //------------------------------------------------------------------------------ void CNPC_MSynth::ClearInspectTarget(void) { if (GetIdealState() != NPC_STATE_SCRIPT) { SetTarget(NULL); } ClearHintNode(MSYNTH_HINT_INSPECT_LENGTH); m_vInspectPos = vec3_origin; } //------------------------------------------------------------------------------ // Purpose: Returns true if there is a position to be inspected. //------------------------------------------------------------------------------ bool CNPC_MSynth::HaveInspectTarget(void) { if (GetTarget() != NULL) return true; if (m_vInspectPos != vec3_origin) return true; return false; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ Vector CNPC_MSynth::InspectTargetPosition(void) { // If we have a target, return an adjust position if (GetTarget() != NULL) { Vector vEyePos = GetTarget()->EyePosition(); // If in spotlight mode, aim for ground below target unless is client if (m_nFlyMode == MSYNTH_FLY_SPOT && !(GetTarget()->GetFlags() & FL_CLIENT)) { Vector vInspectPos; vInspectPos.x = vEyePos.x; vInspectPos.y = vEyePos.y; vInspectPos.z = GetFloorZ(vEyePos); // Let's take three-quarters between eyes and ground vInspectPos.z += (vEyePos.z - vInspectPos.z) * 0.75f; return vInspectPos; } else { // Otherwise aim for eyes return vEyePos; } } else if (m_vInspectPos != vec3_origin) { return m_vInspectPos; } else { DevMsg("InspectTargetPosition called with no target!\n"); return m_vInspectPos; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::InputShouldInspect(inputdata_t &inputdata) { m_bShouldInspect = (inputdata.value.Int() != 0); if (!m_bShouldInspect) { if (GetEnemy() == GetTarget()) SetEnemy(NULL); ClearInspectTarget(); SetTarget(NULL); SpotlightDestroy(); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_MSynth::DeployMine() { CBaseEntity *child; // iterate through all children for (child = FirstMoveChild(); child != NULL; child = child->NextMovePeer()) { if (FClassnameIs(child, "combine_mine")) { child->SetParent(NULL); child->SetAbsVelocity(GetAbsVelocity()); child->SetOwnerEntity(this); ScannerEmitSound("DeployMine"); IPhysicsObject *pPhysObj = child->VPhysicsGetObject(); if (pPhysObj) { // Make sure the mine's awake pPhysObj->Wake(); } if (m_bIsClawScanner) { // Fold up. SetActivity(ACT_DISARM); } return; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- float CNPC_MSynth::GetMaxSpeed() { if (IsStriderScout()) { return MSYNTH_SCOUT_MAX_SPEED; } return BaseClass::GetMaxSpeed(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::InputDeployMine(inputdata_t &inputdata) { DeployMine(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::InputEquipMine(inputdata_t &inputdata) { CBaseEntity *child; // iterate through all children for (child = FirstMoveChild(); child != NULL; child = child->NextMovePeer()) { if (FClassnameIs(child, "combine_mine")) { // Already have a mine! return; } } CBaseEntity *pEnt; pEnt = CreateEntityByName("combine_mine"); bool bPlacedMine = false; if (m_bIsClawScanner) { Vector vecOrigin; QAngle angles; int attachment; attachment = LookupAttachment("claw"); if (attachment > -1) { GetAttachment(attachment, vecOrigin, angles); pEnt->SetAbsOrigin(vecOrigin); pEnt->SetAbsAngles(angles); pEnt->SetOwnerEntity(this); pEnt->SetParent(this, attachment); m_bIsOpen = true; SetActivity(ACT_IDLE_ANGRY); bPlacedMine = true; } } if (!bPlacedMine) { Vector vecMineLocation = GetAbsOrigin(); vecMineLocation.z -= 32.0; pEnt->SetAbsOrigin(vecMineLocation); pEnt->SetAbsAngles(GetAbsAngles()); pEnt->SetOwnerEntity(this); pEnt->SetParent(this); } pEnt->Spawn(); } //----------------------------------------------------------------------------- // Purpose: Tells the scanner to go photograph an entity. // Input : String name or classname of the entity to inspect. //----------------------------------------------------------------------------- void CNPC_MSynth::InputInspectTargetPhoto(inputdata_t &inputdata) { m_vLastPatrolDir = vec3_origin; m_bPhotoTaken = false; InspectTarget(inputdata, MSYNTH_FLY_PHOTO); } //----------------------------------------------------------------------------- // Purpose: Tells the scanner to go spotlight an entity. // Input : String name or classname of the entity to inspect. //----------------------------------------------------------------------------- void CNPC_MSynth::InputInspectTargetSpotlight(inputdata_t &inputdata) { InspectTarget(inputdata, MSYNTH_FLY_SPOT); } //----------------------------------------------------------------------------- // Purpose: Tells the scanner to go photo or spotlight an entity. // Input : String name or classname of the entity to inspect. //----------------------------------------------------------------------------- void CNPC_MSynth::InspectTarget(inputdata_t &inputdata, MSynthFlyMode_t eFlyMode) { CBaseEntity *pEnt = gEntList.FindEntityGeneric(NULL, inputdata.value.String(), this, inputdata.pActivator); if (pEnt != NULL) { // Set and begin to inspect our target SetInspectTargetToEnt(pEnt, MSYNTH_CIT_INSPECT_LENGTH); m_nFlyMode = eFlyMode; SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET); // Stop us from any other navigation we were doing GetNavigator()->ClearGoal(); } else { DevMsg("InspectTarget: target %s not found!\n", inputdata.value.String()); } } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_MSynth::MovingToInspectTarget(void) { // If we're flying to a photograph target and the photo isn't yet taken, we're still moving to it if (m_nFlyMode == MSYNTH_FLY_PHOTO && m_bPhotoTaken == false) return true; // If we're still on a path, then we're still moving if (HaveInspectTarget() && GetNavigator()->IsGoalActive()) return true; return false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::GatherConditions(void) { BaseClass::GatherConditions(); // Clear out our old conditions ClearCondition(COND_CSCANNER_INSPECT_DONE); ClearCondition(COND_CSCANNER_HAVE_INSPECT_TARGET); ClearCondition(COND_CSCANNER_SPOT_ON_TARGET); ClearCondition(COND_CSCANNER_CAN_PHOTOGRAPH); // We don't do any of these checks if we have an enemy if (GetEnemy()) return; // -------------------------------------- // COND_CSCANNER_INSPECT_DONE // // If my inspection over // --------------------------------------------------------- // Refresh our timing if we're still moving to our inspection target if (MovingToInspectTarget()) { m_fInspectEndTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_LENGTH; } // Update our follow times if (HaveInspectTarget() && gpGlobals->curtime > m_fInspectEndTime && m_nFlyMode != MSYNTH_FLY_FOLLOW) { SetCondition(COND_CSCANNER_INSPECT_DONE); m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY; m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY; ClearInspectTarget(); } // ---------------------------------------------------------- // If I heard a sound and I don't have an enemy, inspect it // ---------------------------------------------------------- if ((HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER)) && m_nFlyMode != MSYNTH_FLY_FOLLOW) { CSound *pSound = GetBestSound(); if (pSound) { // Chase an owner if we can if (pSound->m_hOwner != NULL) { // Don't inspect sounds of things we like if (IRelationType(pSound->m_hOwner) != D_LI) { // Only bother if we can see it if (FVisible(pSound->m_hOwner)) { SetInspectTargetToEnt(pSound->m_hOwner, MSYNTH_SOUND_INSPECT_LENGTH); } } } else { // Otherwise chase the specific sound Vector vSoundPos = pSound->GetSoundOrigin(); SetInspectTargetToPos(vSoundPos, MSYNTH_SOUND_INSPECT_LENGTH); } m_nFlyMode = (random->RandomInt(0, 2) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO; } } // -------------------------------------- // COND_CSCANNER_HAVE_INSPECT_TARGET // // Look for a nearby citizen or player to hassle. // --------------------------------------------------------- // Check for citizens to inspect if (gpGlobals->curtime > m_fCheckCitizenTime && HaveInspectTarget() == false) { CBaseEntity *pBestEntity = BestInspectTarget(); if (pBestEntity != NULL) { SetInspectTargetToEnt(pBestEntity, MSYNTH_CIT_INSPECT_LENGTH); m_nFlyMode = (random->RandomInt(0, 3) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO; SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET); } } // Check for hints to inspect if (gpGlobals->curtime > m_fCheckHintTime && HaveInspectTarget() == false) { SetHintNode(CAI_HintManager::FindHint(this, HINT_WORLD_WINDOW, 0, MSYNTH_CIT_INSPECT_FLY_DIST)); if (GetHintNode()) { m_fCheckHintTime = gpGlobals->curtime + MSYNTH_HINT_INSPECT_DELAY; m_nFlyMode = (random->RandomInt(0, 2) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO; SetInspectTargetToHint(GetHintNode(), MSYNTH_HINT_INSPECT_LENGTH); SetCondition(COND_CSCANNER_HAVE_INSPECT_TARGET); } } // -------------------------------------- // COND_CSCANNER_SPOT_ON_TARGET // // True when spotlight is on target ent // -------------------------------------- if (m_hSpotlightTarget != NULL && HaveInspectTarget() && m_hSpotlightTarget->GetSmoothedVelocity().Length() < 25) { // If I have a target entity, check my spotlight against the // actual position of the entity if (GetTarget()) { float fInspectDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length(); if (fInspectDist < 100) { SetCondition(COND_CSCANNER_SPOT_ON_TARGET); } } // Otherwise just check by beam direction else { Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin(); VectorNormalize(vTargetDir); float dotpr = DotProduct(vTargetDir, m_vSpotlightDir); if (dotpr > 0.95) { SetCondition(COND_CSCANNER_SPOT_ON_TARGET); } } } // -------------------------------------------- // COND_CSCANNER_CAN_PHOTOGRAPH // // True when can photograph target ent // -------------------------------------------- ClearCondition(COND_CSCANNER_CAN_PHOTOGRAPH); if (m_nFlyMode == MSYNTH_FLY_PHOTO) { // Make sure I have something to photograph and I'm ready to photograph and I'm not moving to fast if (gpGlobals->curtime > m_fNextPhotographTime && HaveInspectTarget() && GetCurrentVelocity().LengthSqr() < (64 * 64)) { // Check that I'm in the right distance range float fInspectDist = (InspectTargetPosition() - GetAbsOrigin()).Length2D(); // See if we're within range if (fInspectDist > MSYNTH_PHOTO_NEAR_DIST && fInspectDist < MSYNTH_PHOTO_FAR_DIST) { // Make sure we're looking at the target if (UTIL_AngleDiff(GetAbsAngles().y, VecToYaw(InspectTargetPosition() - GetAbsOrigin())) < 4.0f) { trace_t tr; AI_TraceLine(GetAbsOrigin(), InspectTargetPosition(), MASK_BLOCKLOS, GetTarget(), COLLISION_GROUP_NONE, &tr); if (tr.fraction == 1.0f) { SetCondition(COND_CSCANNER_CAN_PHOTOGRAPH); } } } } } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::PrescheduleThink(void) { BaseClass::PrescheduleThink(); // Go back to idling if we're done if (GetIdealActivity() == ACT_SCANNER_FLARE_START) { if (IsSequenceFinished() && GetActivity() != ACT_RANGE_ATTACK1) { SetIdealActivity((Activity)ACT_IDLE); } } } Class_T CNPC_MSynth::Classify(void) { // return(CLASS_MORTAR_SYNTH); return CLASS_COMBINE; } //----------------------------------------------------------------------------- // Purpose: Overridden because if the player is a criminal, we hate them. // Input : pTarget - Entity with which to determine relationship. // Output : Returns relationship value. //----------------------------------------------------------------------------- Disposition_t CNPC_MSynth::IRelationType(CBaseEntity *pTarget) { // If it's the player and they are a criminal, we hates them if (pTarget && pTarget->Classify() == CLASS_PLAYER) { if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON) //DevMsg("Msynth Gordon pre criminal? \n"); return D_NU; } return BaseClass::IRelationType(pTarget); } //----------------------------------------------------------------------------- // Purpose: // Input : *pTask - //----------------------------------------------------------------------------- void CNPC_MSynth::RunTask(const Task_t *pTask) { switch (pTask->iTask) { case TASK_CSCANNER_PHOTOGRAPH: { if (IsWaitFinished()) { // If light was on turn it off if (m_pEyeFlash->GetBrightness() > 0) { m_pEyeFlash->SetBrightness(0); // I'm done with this target if (gpGlobals->curtime > m_fInspectEndTime) { ClearInspectTarget(); TaskComplete(); } // Otherwise take another picture else { SetWait(5.0f, 10.0f); } } // If light was off, take another picture else { TakePhoto(); SetWait(0.1f); } } break; } case TASK_CSCANNER_ATTACK_PRE_FLASH: { AttackPreFlash(); if (IsWaitFinished()) { TaskComplete(); } break; } case TASK_CSCANNER_ATTACK_FLASH: { if (IsWaitFinished()) { AttackFlashBlind(); TaskComplete(); } break; } default: { BaseClass::RunTask(pTask); } } } //----------------------------------------------------------------------------- // Purpose: Gets the appropriate next schedule based on current condition // bits. //----------------------------------------------------------------------------- int CNPC_MSynth::SelectSchedule(void) { // Turn our flash off in case we were interrupted while it was on. if (m_pEyeFlash) { m_pEyeFlash->SetBrightness(0); } // ---------------------------------------------------- // If I'm dead, go into a dive bomb // ---------------------------------------------------- if (m_iHealth <= 0) { m_flSpeed = MSYNTH_MAX_DIVE_BOMB_SPEED; return SCHED_SCANNER_ATTACK_DIVEBOMB; } // ------------------------------- // If I'm in a script sequence // ------------------------------- if (m_NPCState == NPC_STATE_SCRIPT) return(BaseClass::SelectSchedule()); // ------------------------------- // Flinch // ------------------------------- if (HasCondition(COND_LIGHT_DAMAGE) || HasCondition(COND_HEAVY_DAMAGE)) { if (IsHeldByPhyscannon()) return SCHED_SMALL_FLINCH; if (m_NPCState == NPC_STATE_IDLE) return SCHED_SMALL_FLINCH; if (m_NPCState == NPC_STATE_ALERT) { if (m_iHealth < (3 * sk_mortarsynth_health.GetFloat() / 4)) return SCHED_TAKE_COVER_FROM_ORIGIN; if (SelectWeightedSequence(ACT_SMALL_FLINCH) != -1) return SCHED_SMALL_FLINCH; } else { if (random->RandomInt(0, 10) < 4) return SCHED_SMALL_FLINCH; } } // I'm being held by the physcannon... struggle! if (IsHeldByPhyscannon()) return SCHED_SCANNER_HELD_BY_PHYSCANNON; // ---------------------------------------------------------- // If I have an enemy // ---------------------------------------------------------- if (GetEnemy() != NULL && GetEnemy()->IsAlive() && m_bShouldInspect) { // Always chase the enemy SetInspectTargetToEnt(GetEnemy(), 9999); // Patrol if the enemy has vanished if (HasCondition(COND_LOST_ENEMY)) return SCHED_SCANNER_PATROL; // Chase via route if we're directly blocked if (HasCondition(COND_SCANNER_FLY_BLOCKED)) return SCHED_SCANNER_CHASE_ENEMY; // Attack if it's time if (gpGlobals->curtime < m_flNextAttack) return SCHED_CSCANNER_SPOTLIGHT_HOVER; // Melee attack if possible if (HasCondition(COND_CAN_MELEE_ATTACK1)) { if (random->RandomInt(0, 1)) return SCHED_CSCANNER_ATTACK_FLASH; // TODO: a schedule where he makes an alarm sound? return SCHED_SCANNER_CHASE_ENEMY; } // If I'm far from the enemy, stay up high and approach in spotlight mode float fAttack2DDist = (GetEnemyLKP() - GetAbsOrigin()).Length2D(); if (fAttack2DDist > MSYNTH_ATTACK_FAR_DIST) return SCHED_CSCANNER_SPOTLIGHT_HOVER; // Otherwise fly in low for attack return SCHED_SCANNER_ATTACK_HOVER; } // ---------------------------------------------------------- // If I have something to inspect // ---------------------------------------------------------- if (HaveInspectTarget()) { // Pathfind to our goal if (HasCondition(COND_SCANNER_FLY_BLOCKED)) return SCHED_CSCANNER_MOVE_TO_INSPECT; // If I was chasing, pick with photographing or spotlighting if (m_nFlyMode == MSYNTH_FLY_CHASE) { m_nFlyMode = (random->RandomInt(0, 1) == 0) ? MSYNTH_FLY_SPOT : MSYNTH_FLY_PHOTO; } // Handle spotlight if (m_nFlyMode == MSYNTH_FLY_SPOT) { if (HasCondition(COND_CSCANNER_SPOT_ON_TARGET)) { if (GetTarget()) { RequestInspectSupport(); CAI_BaseNPC *pNPC = GetTarget()->MyNPCPointer(); // If I'm leading the inspection, so verbal inspection if (pNPC && pNPC->GetTarget() == this) { return SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT; } return SCHED_CSCANNER_SPOTLIGHT_HOVER; } return SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS; } return SCHED_CSCANNER_SPOTLIGHT_HOVER; } // Handle photographing if (m_nFlyMode == MSYNTH_FLY_PHOTO) { if (HasCondition(COND_CSCANNER_CAN_PHOTOGRAPH)) return SCHED_CSCANNER_PHOTOGRAPH; return SCHED_CSCANNER_PHOTOGRAPH_HOVER; } // Handle following after a target if (m_nFlyMode == MSYNTH_FLY_FOLLOW) { //TODO: Randomly make noise, photograph, etc return SCHED_SCANNER_FOLLOW_HOVER; } // Handle patrolling if ((m_nFlyMode == MSYNTH_FLY_PATROL) || (m_nFlyMode == MSYNTH_FLY_FAST)) return SCHED_SCANNER_PATROL; } // Default to patrolling around return SCHED_SCANNER_PATROL; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::SpotlightDestroy(void) { if (m_hSpotlight) { UTIL_Remove(m_hSpotlight); m_hSpotlight = NULL; UTIL_Remove(m_hSpotlightTarget); m_hSpotlightTarget = NULL; } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::SpotlightCreate(void) { // Make sure we don't already have one if (m_hSpotlight != NULL) return; // Can we create a spotlight yet? if (gpGlobals->curtime < m_fNextSpotlightTime) return; // If I have an enemy, start spotlight on my enemy if (GetEnemy() != NULL) { Vector vEnemyPos = GetEnemyLKP(); Vector vTargetPos = vEnemyPos; vTargetPos.z = GetFloorZ(vEnemyPos); m_vSpotlightDir = vTargetPos - GetLocalOrigin(); VectorNormalize(m_vSpotlightDir); } // If I have an target, start spotlight on my target else if (GetTarget() != NULL) { Vector vTargetPos = GetTarget()->GetLocalOrigin(); vTargetPos.z = GetFloorZ(GetTarget()->GetLocalOrigin()); m_vSpotlightDir = vTargetPos - GetLocalOrigin(); VectorNormalize(m_vSpotlightDir); } // Other wise just start looking downpa else { m_vSpotlightDir = Vector(0, 0, -1); } trace_t tr; AI_TraceLine(GetAbsOrigin(), GetAbsOrigin() + m_vSpotlightDir * 2024, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr); /*m_hSpotlightTarget = (CSpotlightEnd*)CreateEntityByName("spotlight_end"); m_hSpotlightTarget->Spawn(); m_hSpotlightTarget->SetLocalOrigin(tr.endpos); m_hSpotlightTarget->SetOwnerEntity(this); // YWB: Because the scanner only moves the target during think, make sure we interpolate over 0.1 sec instead of every tick!!! m_hSpotlightTarget->SetSimulatedEveryTick(false); // Using the same color as the beam... m_hSpotlightTarget->SetRenderColor(255, 255, 255); m_hSpotlightTarget->m_Radius = m_flSpotlightMaxLength; m_hSpotlight = CBeam::BeamCreate("sprites/glow_test02.vmt", SPOTLIGHT_WIDTH); // Set the temporary spawnflag on the beam so it doesn't save (we'll recreate it on restore) m_hSpotlight->AddSpawnFlags(SF_BEAM_TEMPORARY); m_hSpotlight->SetColor(255, 255, 255); m_hSpotlight->SetHaloTexture(m_nHaloSprite); m_hSpotlight->SetHaloScale(32); m_hSpotlight->SetEndWidth(m_hSpotlight->GetWidth()); m_hSpotlight->SetBeamFlags((FBEAM_SHADEOUT | FBEAM_NOTILE)); m_hSpotlight->SetBrightness(32); m_hSpotlight->SetNoise(0); m_hSpotlight->EntsInit(this, m_hSpotlightTarget); m_hSpotlight->SetHDRColorScale(0.75f); // Scale this back a bit on HDR maps // attach to light m_hSpotlight->SetStartAttachment(LookupAttachment(MSYNTH_ATTACHMENT_LIGHT));*/ m_vSpotlightAngVelocity = vec3_origin; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ Vector CNPC_MSynth::SpotlightTargetPos(void) { // ---------------------------------------------- // If I have an enemy // ---------------------------------------------- if (GetEnemy() != NULL) { // If I can see my enemy aim for him if (HasCondition(COND_SEE_ENEMY)) { // If its client aim for his eyes if (GetEnemy()->GetFlags() & FL_CLIENT) { m_vSpotlightTargetPos = GetEnemy()->EyePosition(); } // Otherwise same for his feet else { m_vSpotlightTargetPos = GetEnemy()->GetLocalOrigin(); m_vSpotlightTargetPos.z = GetFloorZ(GetEnemy()->GetLocalOrigin()); } } // Otherwise aim for last known position if I can see LKP else { Vector vLKP = GetEnemyLKP(); m_vSpotlightTargetPos.x = vLKP.x; m_vSpotlightTargetPos.y = vLKP.y; m_vSpotlightTargetPos.z = GetFloorZ(vLKP); } } // ---------------------------------------------- // If I have an inspect target // ---------------------------------------------- else if (HaveInspectTarget()) { m_vSpotlightTargetPos = InspectTargetPosition(); } else { // This creates a nice patrol spotlight sweep // in the direction that I'm travelling m_vSpotlightTargetPos = GetCurrentVelocity(); m_vSpotlightTargetPos.z = 0; VectorNormalize(m_vSpotlightTargetPos); m_vSpotlightTargetPos *= 5; float noiseScale = 2.5; const Vector &noiseMod = GetNoiseMod(); m_vSpotlightTargetPos.x += noiseScale*sin(noiseMod.x * gpGlobals->curtime + noiseMod.x); m_vSpotlightTargetPos.y += noiseScale*cos(noiseMod.y* gpGlobals->curtime + noiseMod.y); m_vSpotlightTargetPos.z -= fabs(noiseScale*cos(noiseMod.z* gpGlobals->curtime + noiseMod.z)); m_vSpotlightTargetPos = GetLocalOrigin() + m_vSpotlightTargetPos * 2024; } return m_vSpotlightTargetPos; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ Vector CNPC_MSynth::SpotlightCurrentPos(void) { Vector vTargetDir = SpotlightTargetPos() - GetLocalOrigin(); VectorNormalize(vTargetDir); if (!m_hSpotlight) { DevMsg("Spotlight pos. called w/o spotlight!\n"); return vec3_origin; } // ------------------------------------------------- // Beam has momentum relative to it's ground speed // so sclae the turn rate based on its distance // from the beam source // ------------------------------------------------- float fBeamDist = (m_hSpotlightTarget->GetLocalOrigin() - GetLocalOrigin()).Length(); float fBeamTurnRate = atan(50 / fBeamDist); Vector vNewAngVelocity = fBeamTurnRate * (vTargetDir - m_vSpotlightDir); float myDecay = 0.4; m_vSpotlightAngVelocity = (myDecay * m_vSpotlightAngVelocity + (1 - myDecay) * vNewAngVelocity); // ------------------------------ // Limit overall angular speed // ----------------------------- if (m_vSpotlightAngVelocity.Length() > 1) { Vector velDir = m_vSpotlightAngVelocity; VectorNormalize(velDir); m_vSpotlightAngVelocity = velDir * 1; } // ------------------------------ // Calculate new beam direction // ------------------------------ m_vSpotlightDir = m_vSpotlightDir + m_vSpotlightAngVelocity; m_vSpotlightDir = m_vSpotlightDir; VectorNormalize(m_vSpotlightDir); // --------------------------------------------- // Get beam end point. Only collide with // solid objects, not npcs // --------------------------------------------- trace_t tr; Vector vTraceEnd = GetAbsOrigin() + (m_vSpotlightDir * 2 * m_flSpotlightMaxLength); AI_TraceLine(GetAbsOrigin(), vTraceEnd, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr); return (tr.endpos); } //------------------------------------------------------------------------------ // Purpose: Update the direction and position of my spotlight //------------------------------------------------------------------------------ void CNPC_MSynth::SpotlightUpdate(void) { //FIXME: JDW - E3 Hack if (m_bNoLight) { if (m_hSpotlight) { SpotlightDestroy(); } return; } if ((m_nFlyMode != MSYNTH_FLY_SPOT) && (m_nFlyMode != MSYNTH_FLY_PATROL) && (m_nFlyMode != MSYNTH_FLY_FAST)) { if (m_hSpotlight) { SpotlightDestroy(); } return; } // If I don't have a spotlight attempt to create one if (m_hSpotlight == NULL) { SpotlightCreate(); if (m_hSpotlight == NULL) return; } // Calculate the new homing target position m_vSpotlightCurrentPos = SpotlightCurrentPos(); // ------------------------------------------------------------------ // If I'm not facing the spotlight turn it off // ------------------------------------------------------------------ Vector vSpotDir = m_vSpotlightCurrentPos - GetAbsOrigin(); VectorNormalize(vSpotDir); Vector vForward; AngleVectors(GetAbsAngles(), &vForward); float dotpr = DotProduct(vForward, vSpotDir); if (dotpr < 0.0) { // Leave spotlight off for a while m_fNextSpotlightTime = gpGlobals->curtime + 3.0f; SpotlightDestroy(); return; } // -------------------------------------------------------------- // Update spotlight target velocity // -------------------------------------------------------------- Vector vTargetDir = (m_vSpotlightCurrentPos - m_hSpotlightTarget->GetLocalOrigin()); float vTargetDist = vTargetDir.Length(); Vector vecNewVelocity = vTargetDir; VectorNormalize(vecNewVelocity); vecNewVelocity *= (10 * vTargetDist); // If a large move is requested, just jump to final spot as we // probably hit a discontinuity if (vecNewVelocity.Length() > 200) { VectorNormalize(vecNewVelocity); vecNewVelocity *= 200; m_hSpotlightTarget->SetLocalOrigin(m_vSpotlightCurrentPos); } m_hSpotlightTarget->SetAbsVelocity(vecNewVelocity); m_hSpotlightTarget->m_vSpotlightOrg = GetAbsOrigin(); // Avoid sudden change in where beam fades out when cross disconinuities m_hSpotlightTarget->m_vSpotlightDir = m_hSpotlightTarget->GetLocalOrigin() - m_hSpotlightTarget->m_vSpotlightOrg; float flBeamLength = VectorNormalize(m_hSpotlightTarget->m_vSpotlightDir); m_flSpotlightCurLength = (0.80*m_flSpotlightCurLength) + (0.2*flBeamLength); // Fade out spotlight end if past max length. if (m_flSpotlightCurLength > 2 * m_flSpotlightMaxLength) { m_hSpotlightTarget->SetRenderColorA(0); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else if (m_flSpotlightCurLength > m_flSpotlightMaxLength) { m_hSpotlightTarget->SetRenderColorA((1 - ((m_flSpotlightCurLength - m_flSpotlightMaxLength) / m_flSpotlightMaxLength))); m_hSpotlight->SetFadeLength(m_flSpotlightMaxLength); } else { m_hSpotlightTarget->SetRenderColorA(1.0); m_hSpotlight->SetFadeLength(m_flSpotlightCurLength); } // Adjust end width to keep beam width constant float flNewWidth = SPOTLIGHT_WIDTH * (flBeamLength / m_flSpotlightMaxLength); m_hSpotlight->SetWidth(flNewWidth); m_hSpotlight->SetEndWidth(flNewWidth); m_hSpotlightTarget->m_flLightScale = 0.0; } //----------------------------------------------------------------------------- // Purpose: Called just before we are deleted. //----------------------------------------------------------------------------- void CNPC_MSynth::UpdateOnRemove(void) { SpotlightDestroy(); BaseClass::UpdateOnRemove(); } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::TakePhoto(void) { /*ScannerEmitSound("TakePhoto"); m_pEyeFlash->SetScale(1.4); m_pEyeFlash->SetBrightness(255); m_pEyeFlash->SetColor(255, 255, 255); Vector vRawPos = InspectTargetPosition(); Vector vLightPos = vRawPos; // If taking picture of entity, aim at feet if (GetTarget()) { if (GetTarget()->IsPlayer()) { m_OnPhotographPlayer.FireOutput(GetTarget(), this); //BlindFlashTarget(GetTarget()); } if (GetTarget()->MyNPCPointer() != NULL) { m_OnPhotographNPC.FireOutput(GetTarget(), this); GetTarget()->MyNPCPointer()->DispatchInteraction(g_interactionScannerInspectBegin, NULL, this); } } SetIdealActivity((Activity)ACT_SCANNER_FLARE_START);*/ m_bPhotoTaken = true; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::AttackPreFlash(void) { //ScannerEmitSound(MSYNTH_PREATTACK_SOUND); SetActivity(ACT_RANGE_ATTACK1); // ------------------- // Beam beams // ------------------- if (m_pEnergyBeam == NULL) { ScannerEmitSound(MSYNTH_PREATTACK_SOUND); m_pEnergyBeam = CBeam::BeamCreate("sprites/physbeam.vmt", 2.0); m_pEnergyBeam->SetColor(0, 100, 255); m_pEnergyBeam->SetBrightness(100); m_pEnergyBeam->SetNoise(8); m_pEnergyBeam->EntsInit(this, this); m_pEnergyBeam->SetStartAttachment(LookupAttachment("0")); m_pEnergyBeam->SetEndAttachment(LookupAttachment("1")); } // ------------- // Glow // ------------- if (m_pEnergyGlow[0] == NULL) { m_pEnergyGlow[0] = CSprite::SpriteCreate("sprites/glow01.vmt", GetLocalOrigin(), FALSE); m_pEnergyGlow[0]->SetAttachment(this, LookupAttachment("0")); m_pEnergyGlow[0]->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation); m_pEnergyGlow[0]->SetBrightness(100); m_pEnergyGlow[0]->SetScale(0.3); } if (m_pEnergyGlow[1] == NULL) { m_pEnergyGlow[1] = CSprite::SpriteCreate("sprites/glow01.vmt", GetLocalOrigin(), FALSE); m_pEnergyGlow[1]->SetAttachment(this, LookupAttachment("1")); m_pEnergyGlow[1]->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation); m_pEnergyGlow[1]->SetBrightness(100); m_pEnergyGlow[1]->SetScale(0.3); } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::AttackFlash(void) { ScannerEmitSound(MSYNTH_ATTACK_SOUND); /*m_pEyeFlash->SetScale(1.8); m_pEyeFlash->SetBrightness(255); m_pEyeFlash->SetColor(255, 255, 255); if (GetEnemy() != NULL) { Vector pos = GetEnemyLKP(); CBroadcastRecipientFilter filter; te->DynamicLight(filter, 0.0, &pos, 200, 200, 255, 0, 300, 0.2, 50); if (GetEnemy()->IsPlayer()) { m_OnPhotographPlayer.FireOutput(GetTarget(), this); } else if (GetEnemy()->MyNPCPointer()) { m_OnPhotographNPC.FireOutput(GetTarget(), this); } }*/ if (m_BeamArmed) { Vector vecSrc, vecAim, vecAimCorpse; trace_t tr; Vector forward, right, up; AngleVectors(GetAbsAngles(), &forward, &right, &up); vecSrc = GetAbsOrigin() + up * 36; vecAim = GetShootEnemyDir(vecSrc); Vector vAttachPos; QAngle ang = GetAbsAngles(); GetAttachment(1, vAttachPos, ang); Vector End = vAttachPos + (up + forward * MAX_TRACE_LENGTH); vecAim = GetShootEnemyDir(vAttachPos, false); AI_TraceLine(vAttachPos, vecSrc + vecAim * MAX_TRACE_LENGTH, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr); DevMsg("Msynth BeamArmed CGattacklaser \n"); DispatchParticleEffect("CombineGuard_Attack_Laser", vAttachPos, tr.endpos, ang, this); if (tr.m_pEnt != NULL && m_takedamage) { ClearMultiDamage(); CTakeDamageInfo firedamage(this, this, sk_mortarsynth_beam_damage.GetFloat(), DMG_DISSOLVE); CalculateMeleeDamageForce(&firedamage, vecAim, tr.endpos); if (!cvar->FindVar("sk_mortarsynth_beam_damage")->GetFloat() == 0) // BJ Little Fixup for 0 value RadiusDamage(CTakeDamageInfo(this, this, 2, DMG_DISSOLVE), tr.endpos, 64.0f, CLASS_NONE, NULL); tr.m_pEnt->DispatchTraceAttack((firedamage), vecAim, &tr); } } else { Vector vecShootOrigin;// = Weapon_ShootPosition(); GetAttachment("light", 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); //EjectShell( vecShootOrigin - vecShootDir * 24, vecShellVelocity, GetAbsAngles().y, 0 ); //FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, m_iAmmoType ); // shoot +-5 degrees Vector src; GetAttachment("light", src, NULL); QAngle angAiming; VectorAngles(vecShootDir, angAiming); CMSynthProjectile *pProjectile = CMSynthProjectile::BoltCreate(src, angAiming, 0, this); if (this->GetWaterLevel() == 3) { //pShockRifleProjectile->Remove(); //->SetAbsVelocity( vecAiming * BOLT_WATER_VELOCITY ); CTakeDamageInfo hitself(this, this, 210, DMG_SHOCK); TakeDamage(hitself); } else { pProjectile->SetAbsVelocity(vecShootDir * 1500); } DevMsg("Msynth Beam NOT armed SHKmuzzleflash \n"); DispatchParticleEffect("weapon_muzzle_flash_shock_npc", PATTACH_POINT_FOLLOW, this, "light", false); } if (m_pEnergyBeam) { // Let beam kill itself m_pEnergyBeam->LiveForTime(0.2); m_pEnergyBeam = NULL; } } //----------------------------------------------------------------------------- // Purpose: // Input : *pTarget - //----------------------------------------------------------------------------- void CNPC_MSynth::BlindFlashTarget(CBaseEntity *pTarget) { // Tell all the striders this person is here! CAI_BaseNPC ** ppAIs = g_AI_Manager.AccessAIs(); int nAIs = g_AI_Manager.NumAIs(); if (IsStriderScout()) { for (int i = 0; i < nAIs; i++) { if (FClassnameIs(ppAIs[i], "npc_strider")) { ppAIs[i]->UpdateEnemyMemory(pTarget, pTarget->GetAbsOrigin(), this); } } } // Only bother with player if (pTarget->IsPlayer() == false) return; // Scale the flash value by how closely the player is looking at me Vector vFlashDir = GetAbsOrigin() - pTarget->EyePosition(); VectorNormalize(vFlashDir); Vector vFacing; AngleVectors(pTarget->EyeAngles(), &vFacing); float dotPr = DotProduct(vFlashDir, vFacing); // Not if behind us if (dotPr > 0.5f) { // Make sure nothing in the way trace_t tr; AI_TraceLine(GetAbsOrigin(), pTarget->EyePosition(), MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr); if (tr.startsolid == false && tr.fraction == 1.0) { color32 white = { 255, 255, 255, MSYNTH_FLASH_MAX_VALUE * dotPr }; if ((g_pMaterialSystemHardwareConfig != NULL) && (g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE)) { white.a = (byte)((float)white.a * 0.9f); } float flFadeTime = (IsX360()) ? 0.5f : 3.0f; UTIL_ScreenFade(pTarget, white, flFadeTime, 0.5, FFADE_IN); } } } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::AttackFlashBlind(void) { /*if (GetEnemy()) { BlindFlashTarget(GetEnemy()); }*/ m_pEyeFlash->SetBrightness(0); float fAttackDelay = random->RandomFloat(MSYNTH_ATTACK_MIN_DELAY, MSYNTH_ATTACK_MAX_DELAY); if (IsStriderScout()) { // Make strider scouts more snappy. fAttackDelay *= 0.5; } m_flNextAttack = gpGlobals->curtime + fAttackDelay; m_fNextSpotlightTime = gpGlobals->curtime + 1.0f; } //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ void CNPC_MSynth::AttackDivebomb(void) { if (m_hSpotlight) { SpotlightDestroy(); } BaseClass::AttackDivebomb(); } //----------------------------------------------------------------------------- // Purpose: // Input : pTask - //----------------------------------------------------------------------------- void CNPC_MSynth::StartTask(const Task_t *pTask) { switch (pTask->iTask) { case TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET: { // Must have somewhere to fly to if (HaveInspectTarget() == false) { TaskFail("No inspection target to fly to!\n"); return; } if (GetTarget()) { //FIXME: Tweak //Vector idealPos = IdealGoalForMovement( InspectTargetPosition(), GetAbsOrigin(), 128.0f, 128.0f ); AI_NavGoal_t goal(GOALTYPE_TARGETENT, vec3_origin); if (GetNavigator()->SetGoal(goal)) { TaskComplete(); return; } } else { AI_NavGoal_t goal(GOALTYPE_LOCATION, InspectTargetPosition()); if (GetNavigator()->SetGoal(goal)) { TaskComplete(); return; } } // Don't try and inspect this target again for a few seconds CNPC_Citizen *pCitizen = dynamic_cast(GetTarget()); if (pCitizen) { pCitizen->SetNextScannerInspectTime(gpGlobals->curtime + 5.0); } TaskFail("No route to inspection target!\n"); } break; case TASK_CSCANNER_SPOT_INSPECT_ON: { if (GetTarget() == NULL) { TaskFail(FAIL_NO_TARGET); } else { CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); if (!pNPC) { TaskFail(FAIL_NO_TARGET); } else { pNPC->DispatchInteraction(g_interactionScannerInspectBegin, NULL, this); // Now we need some time to inspect m_fInspectEndTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_LENGTH; TaskComplete(); } } break; } case TASK_CSCANNER_SPOT_INSPECT_WAIT: { if (GetTarget() == NULL) { TaskFail(FAIL_NO_TARGET); } else { CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); if (!pNPC) { SetTarget(NULL); TaskFail(FAIL_NO_TARGET); } else { //<>//<> armband too! pNPC->DispatchInteraction(g_interactionScannerInspectHandsUp, NULL, this); } TaskComplete(); } break; } case TASK_CSCANNER_SPOT_INSPECT_OFF: { if (GetTarget() == NULL) { TaskFail(FAIL_NO_TARGET); } else { CAI_BaseNPC* pNPC = GetTarget()->MyNPCPointer(); if (!pNPC) { TaskFail(FAIL_NO_TARGET); } else { pNPC->DispatchInteraction(g_interactionScannerInspectDone, NULL, this); // Clear target entity and don't inspect again for a while SetTarget(NULL); m_fCheckCitizenTime = gpGlobals->curtime + MSYNTH_CIT_INSPECT_DELAY; TaskComplete(); } } break; } case TASK_CSCANNER_CLEAR_INSPECT_TARGET: { ClearInspectTarget(); TaskComplete(); break; } case TASK_CSCANNER_SET_FLY_SPOT: { m_nFlyMode = MSYNTH_FLY_SPOT; TaskComplete(); break; } case TASK_CSCANNER_SET_FLY_PHOTO: { m_nFlyMode = MSYNTH_FLY_PHOTO; m_bPhotoTaken = false; // Leave spotlight off for a while m_fNextSpotlightTime = gpGlobals->curtime + 2.0; TaskComplete(); break; } case TASK_CSCANNER_PHOTOGRAPH: { TakePhoto(); SetWait(0.1); break; } case TASK_CSCANNER_ATTACK_PRE_FLASH: { if (IsStriderScout()) { Vector vecScare = GetEnemy()->EarPosition(); Vector vecDir = WorldSpaceCenter() - vecScare; VectorNormalize(vecDir); vecScare += vecDir * 64.0f; CSoundEnt::InsertSound(SOUND_DANGER, vecScare, 256, 1.0, this); } DevMsg("Msynth prebeam CGmuzzleflash \n"); DispatchParticleEffect("CombineGuard_Muzzle_Flash", PATTACH_POINT_FOLLOW, this, "light", false); if (m_pEyeFlash) { // If off turn on, if on turn off if (m_pEyeFlash->GetBrightness() == 0) { m_pEyeFlash->SetScale(0.5); m_pEyeFlash->SetBrightness(255); m_pEyeFlash->SetColor(255, 0, 0); } else { m_pEyeFlash->SetBrightness(0); } } else { TaskFail("No Flash TASK_CSCANNER_ATTACK_PRE_FLASH"); } AttackPreFlash(); // Flash red for a while SetWait(1.0f); break; } case TASK_CSCANNER_ATTACK_FLASH: { AttackFlash(); // Blinding occurs slightly later SetWait(0.05); break; } // Override to go to inspect target position whether or not is an entity case TASK_GET_PATH_TO_TARGET: { if (!HaveInspectTarget()) { TaskFail(FAIL_NO_TARGET); } else if (GetHintNode()) { Vector vNodePos; GetHintNode()->GetPosition(this, &vNodePos); GetNavigator()->SetGoal(vNodePos); } else { AI_NavGoal_t goal((const Vector &)InspectTargetPosition()); goal.pTarget = GetTarget(); GetNavigator()->SetGoal(goal); } break; } default: BaseClass::StartTask(pTask); break; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- /* char *CNPC_MSynth::GetScannerSoundPrefix(void) { if (m_bIsClawScanner) return "NPC_SScanner"; return "NPC_CScanner"; } */ //------------------------------------------------------------------------------ // Purpose: //------------------------------------------------------------------------------ float CNPC_MSynth::MinGroundDist(void) { if (m_nFlyMode == MSYNTH_FLY_SPOT && !GetHintNode()) { return MSYNTH_SPOTLIGHT_FLY_HEIGHT; } return MSYNTH_NOSPOTLIGHT_FLY_HEIGHT; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::AdjustScannerVelocity(void) { if (m_bIsClawScanner) { m_vCurrentVelocity *= (1 + sin((gpGlobals->curtime + m_flFlyNoiseBase) * 2.5f) * .1); } } //----------------------------------------------------------------------------- // Purpose: // Input : flInterval - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_MSynth::OverrideMove(float flInterval) { // ---------------------------------------------- // If dive bombing // ---------------------------------------------- if (m_nFlyMode == MSYNTH_FLY_DIVE) { MoveToDivebomb(flInterval); } else { Vector vMoveTargetPos(0, 0, 0); CBaseEntity *pMoveTarget = NULL; // The original line of code was, due to the accidental use of '|' instead of // '&', always true. Replacing with 'true' to suppress the warning without changing // the (long-standing) behavior. if (true) //!GetNavigator()->IsGoalActive() || ( GetNavigator()->GetCurWaypointFlags() | bits_WP_TO_PATHCORNER ) ) { // Select move target if (GetTarget() != NULL) { pMoveTarget = GetTarget(); } else if (GetEnemy() != NULL) { pMoveTarget = GetEnemy(); } // Select move target position if (HaveInspectTarget()) { vMoveTargetPos = InspectTargetPosition(); } else if (GetEnemy() != NULL) { vMoveTargetPos = GetEnemy()->GetAbsOrigin(); } } else { vMoveTargetPos = GetNavigator()->GetCurWaypointPos(); } ClearCondition(COND_SCANNER_FLY_CLEAR); ClearCondition(COND_SCANNER_FLY_BLOCKED); // See if we can fly there directly if (pMoveTarget || HaveInspectTarget()) { trace_t tr; AI_TraceHull(GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr); //AI_TraceHull(GetAbsOrigin(), vMoveTargetPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, GetCollisionGroup(), &tr); // bj test float fTargetDist = (1.0f - tr.fraction)*(GetAbsOrigin() - vMoveTargetPos).Length(); if ((tr.m_pEnt == pMoveTarget) || (fTargetDist < 50)) { if (g_debug_mortarsynth.GetBool()) { NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 0, 255, 0, true, 0); NDebugOverlay::Cross3D(tr.endpos, Vector(-5, -5, -5), Vector(5, 5, 5), 0, 255, 0, true, 0.1); } SetCondition(COND_SCANNER_FLY_CLEAR); } else { //HANDY DEBUG TOOL if (g_debug_mortarsynth.GetBool()) { NDebugOverlay::Line(GetLocalOrigin(), vMoveTargetPos, 255, 0, 0, true, 0); NDebugOverlay::Cross3D(tr.endpos, Vector(-5, -5, -5), Vector(5, 5, 5), 255, 0, 0, true, 0.1); } SetCondition(COND_SCANNER_FLY_BLOCKED); } } // If I have a route, keep it updated and move toward target if (GetNavigator()->IsGoalActive()) { if (OverridePathMove(pMoveTarget, flInterval)) { BlendPhyscannonLaunchSpeed(); return true; } } else if (m_nFlyMode == MSYNTH_FLY_SPOT) { MoveToSpotlight(flInterval); } // If photographing else if (m_nFlyMode == MSYNTH_FLY_PHOTO) { MoveToPhotograph(flInterval); } else if (m_nFlyMode == MSYNTH_FLY_FOLLOW) { MoveToSpotlight(flInterval); } // ---------------------------------------------- // If attacking // ---------------------------------------------- else if (m_nFlyMode == MSYNTH_FLY_ATTACK) { if (m_hSpotlight) { SpotlightDestroy(); } MoveToAttack(flInterval); } // ----------------------------------------------------------------- // If I don't have a route, just decelerate // ----------------------------------------------------------------- else if (!GetNavigator()->IsGoalActive()) { float myDecay = 9.5; Decelerate(flInterval, myDecay); } } MoveExecute_Alive(flInterval); return true; } //----------------------------------------------------------------------------- // Purpose: Accelerates toward a given position. // Input : flInterval - Time interval over which to move. // vecMoveTarget - Position to move toward. //----------------------------------------------------------------------------- void CNPC_MSynth::MoveToTarget(float flInterval, const Vector &vecMoveTarget) { // Don't move if stalling if (m_flEngineStallTime > gpGlobals->curtime) return; // Look at our inspection target if we have one if (GetEnemy() != NULL) { // Otherwise at our enemy TurnHeadToTarget(flInterval, GetEnemy()->EyePosition()); } else if (HaveInspectTarget()) { TurnHeadToTarget(flInterval, InspectTargetPosition()); } else { // Otherwise face our motion direction TurnHeadToTarget(flInterval, vecMoveTarget); } // ------------------------------------- // Move towards our target // ------------------------------------- float myAccel; float myZAccel = 400.0f; float myDecay = 0.15f; Vector vecCurrentDir; // Get the relationship between my current velocity and the way I want to be going. vecCurrentDir = GetCurrentVelocity(); VectorNormalize(vecCurrentDir); Vector targetDir = vecMoveTarget - GetAbsOrigin(); float flDist = VectorNormalize(targetDir); float flDot; flDot = DotProduct(targetDir, vecCurrentDir); if (flDot > 0.25) { // If my target is in front of me, my flight model is a bit more accurate. myAccel = 250; } else { // Have a harder time correcting my course if I'm currently flying away from my target. myAccel = 128; } if (myAccel > flDist / flInterval) { myAccel = flDist / flInterval; } if (myZAccel > flDist / flInterval) { myZAccel = flDist / flInterval; } MoveInDirection(flInterval, targetDir, myAccel, myZAccel, myDecay); // calc relative banking targets Vector forward, right, up; GetVectors(&forward, &right, &up); m_vCurrentBanking.x = targetDir.x; m_vCurrentBanking.z = 120.0f * DotProduct(right, targetDir); m_vCurrentBanking.y = 0; float speedPerc = SimpleSplineRemapVal(GetCurrentVelocity().Length(), 0.0f, GetMaxSpeed(), 0.0f, 1.0f); speedPerc = clamp(speedPerc, 0.0f, 1.0f); m_vCurrentBanking *= speedPerc; } //----------------------------------------------------------------------------- // Purpose: // Input : flInterval - //----------------------------------------------------------------------------- void CNPC_MSynth::MoveToSpotlight(float flInterval) { if (flInterval <= 0) return; Vector vTargetPos; if (HaveInspectTarget()) { vTargetPos = InspectTargetPosition(); } else if (GetEnemy() != NULL) { vTargetPos = GetEnemyLKP(); } else { return; } //float flDesiredDist = SCANNER_SPOTLIGHT_NEAR_DIST + ( ( SCANNER_SPOTLIGHT_FAR_DIST - SCANNER_SPOTLIGHT_NEAR_DIST ) / 2 ); float flIdealHeightDiff = MSYNTH_SPOTLIGHT_NEAR_DIST; if (IsEnemyPlayerInSuit()) { flIdealHeightDiff *= 0.5; } Vector idealPos = IdealGoalForMovement(vTargetPos, GetAbsOrigin(), GetGoalDistance(), flIdealHeightDiff); MoveToTarget(flInterval, idealPos); //TODO: Re-implement? /* // ------------------------------------------------ // Also keep my distance from other squad members // unless I'm inspecting // ------------------------------------------------ if (m_pSquad && gpGlobals->curtime > m_fInspectEndTime) { CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this); if (pNearest) { Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin()); if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST) { vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin(); VectorNormalize(vNearestDir); vFlyDirection -= 0.5*vNearestDir; } } } // --------------------------------------------------------- // Add evasion if I have taken damage recently // --------------------------------------------------------- if ((m_flLastDamageTime + SCANNER_EVADE_TIME) > gpGlobals->curtime) { vFlyDirection = vFlyDirection + VelocityToEvade(GetEnemyCombatCharacterPointer()); } */ } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CNPC_MSynth::GetGoalDistance(void) { if (m_flGoalOverrideDistance != 0.0f) return m_flGoalOverrideDistance; switch (m_nFlyMode) { case MSYNTH_FLY_PHOTO: return (MSYNTH_PHOTO_NEAR_DIST + ((MSYNTH_PHOTO_FAR_DIST - MSYNTH_PHOTO_NEAR_DIST))); break; case MSYNTH_FLY_SPOT: { float goalDist = (MSYNTH_SPOTLIGHT_NEAR_DIST + ((MSYNTH_SPOTLIGHT_FAR_DIST - MSYNTH_SPOTLIGHT_NEAR_DIST))); /*if (IsEnemyPlayerInSuit()) { goalDist *= 0.5; }*/ return goalDist; } break; case MSYNTH_FLY_FOLLOW: return (MSYNTH_FOLLOW_DIST); break; } return BaseClass::GetGoalDistance(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_MSynth::MoveToPhotograph(float flInterval) { if (HaveInspectTarget() == false) return; //float flDesiredDist = SCANNER_PHOTO_NEAR_DIST + ( ( SCANNER_PHOTO_FAR_DIST - SCANNER_PHOTO_NEAR_DIST ) / 2 ); Vector idealPos = IdealGoalForMovement(InspectTargetPosition(), GetAbsOrigin(), GetGoalDistance(), 32.0f); MoveToTarget(flInterval, idealPos); //FIXME: Re-implement? /* // ------------------------------------------------ // Also keep my distance from other squad members // unless I'm inspecting // ------------------------------------------------ if (m_pSquad && gpGlobals->curtime > m_fInspectEndTime) { CBaseEntity* pNearest = m_pSquad->NearestSquadMember(this); if (pNearest) { Vector vNearestDir = (pNearest->GetLocalOrigin() - GetLocalOrigin()); if (vNearestDir.Length() < SCANNER_SQUAD_FLY_DIST) { vNearestDir = pNearest->GetLocalOrigin() - GetLocalOrigin(); VectorNormalize(vNearestDir); vFlyDirection -= 0.5*vNearestDir; } } } */ } //----------------------------------------------------------------------------- // Purpose: This is a generic function (to be implemented by sub-classes) to // handle specific interactions between different types of characters // (For example the barnacle grabbing an NPC) // Input : Constant for the type of interaction // Output : true - if sub-class has a response for the interaction // false - if sub-class has no response //----------------------------------------------------------------------------- bool CNPC_MSynth::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* pSourceEnt) { // TODO:: - doing this by just an interrupt contition would be a lot better! if (interactionType == g_interactionScannerSupportEntity) { // Only accept help request if I'm not already busy if (GetEnemy() == NULL && !HaveInspectTarget()) { // Only accept if target is a reasonable distance away CBaseEntity* pTarget = (CBaseEntity*)data; float fTargetDist = (pTarget->GetLocalOrigin() - GetLocalOrigin()).Length(); if (fTargetDist < MSYNTH_SQUAD_HELP_DIST) { float fInspectTime = (((CNPC_MSynth*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime); SetInspectTargetToEnt(pTarget, fInspectTime); if (random->RandomInt(0, 2) == 0) { SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER); } else { SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER); } return true; } } } else if (interactionType == g_interactionScannerSupportPosition) { // Only accept help request if I'm not already busy if (GetEnemy() == NULL && !HaveInspectTarget()) { // Only accept if target is a reasonable distance away Vector vInspectPos; vInspectPos.x = ((Vector *)data)->x; vInspectPos.y = ((Vector *)data)->y; vInspectPos.z = ((Vector *)data)->z; float fTargetDist = (vInspectPos - GetLocalOrigin()).Length(); if (fTargetDist < MSYNTH_SQUAD_HELP_DIST) { float fInspectTime = (((CNPC_MSynth*)pSourceEnt)->m_fInspectEndTime - gpGlobals->curtime); SetInspectTargetToPos(vInspectPos, fInspectTime); if (random->RandomInt(0, 2) == 0) { SetSchedule(SCHED_CSCANNER_PHOTOGRAPH_HOVER); } else { SetSchedule(SCHED_CSCANNER_SPOTLIGHT_HOVER); } return true; } } } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CNPC_MSynth::InputDisableSpotlight(inputdata_t &inputdata) { m_bNoLight = true; } //----------------------------------------------------------------------------- // Purpose: // Output : float //----------------------------------------------------------------------------- float CNPC_MSynth::GetHeadTurnRate(void) { if (GetEnemy()) return 800.0f; if (HaveInspectTarget()) return 500.0f; return BaseClass::GetHeadTurnRate(); } //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CNPC_MSynth::InputSetFollowTarget(inputdata_t &inputdata) { InspectTarget(inputdata, MSYNTH_FLY_FOLLOW); } //----------------------------------------------------------------------------- // Purpose: // Input : &inputdata - //----------------------------------------------------------------------------- void CNPC_MSynth::InputClearFollowTarget(inputdata_t &inputdata) { SetInspectTargetToEnt(NULL, 0); m_nFlyMode = MSYNTH_FLY_PATROL; } //----------------------------------------------------------------------------- // // Schedules // //----------------------------------------------------------------------------- AI_BEGIN_CUSTOM_NPC(npc_mortarsynth, CNPC_MSynth) // BJ npc_cscanner name fix DECLARE_TASK(TASK_CSCANNER_SET_FLY_PHOTO) DECLARE_TASK(TASK_CSCANNER_SET_FLY_SPOT) DECLARE_TASK(TASK_CSCANNER_PHOTOGRAPH) DECLARE_TASK(TASK_CSCANNER_ATTACK_PRE_FLASH) DECLARE_TASK(TASK_CSCANNER_ATTACK_FLASH) DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_ON) DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_WAIT) DECLARE_TASK(TASK_CSCANNER_SPOT_INSPECT_OFF) DECLARE_TASK(TASK_CSCANNER_CLEAR_INSPECT_TARGET) DECLARE_TASK(TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET) DECLARE_CONDITION(COND_CSCANNER_HAVE_INSPECT_TARGET) DECLARE_CONDITION(COND_CSCANNER_INSPECT_DONE) DECLARE_CONDITION(COND_CSCANNER_CAN_PHOTOGRAPH) DECLARE_CONDITION(COND_CSCANNER_SPOT_ON_TARGET) DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_ALERT) DECLARE_ACTIVITY(ACT_SCANNER_SMALL_FLINCH_COMBAT) DECLARE_ACTIVITY(ACT_SCANNER_INSPECT) DECLARE_ACTIVITY(ACT_SCANNER_WALK_ALERT) DECLARE_ACTIVITY(ACT_SCANNER_WALK_COMBAT) DECLARE_ACTIVITY(ACT_SCANNER_FLARE) DECLARE_ACTIVITY(ACT_SCANNER_RETRACT) DECLARE_ACTIVITY(ACT_SCANNER_FLARE_PRONGS) DECLARE_ACTIVITY(ACT_SCANNER_RETRACT_PRONGS) DECLARE_ACTIVITY(ACT_SCANNER_FLARE_START) DECLARE_ANIMEVENT(AE_MSYNTH_CLOSED) DECLARE_INTERACTION(g_interactionScannerInspect) DECLARE_INTERACTION(g_interactionScannerInspectBegin) DECLARE_INTERACTION(g_interactionScannerInspectDone) DECLARE_INTERACTION(g_interactionScannerInspectHandsUp) DECLARE_INTERACTION(g_interactionScannerInspectShowArmband) DECLARE_INTERACTION(g_interactionScannerSupportEntity) DECLARE_INTERACTION(g_interactionScannerSupportPosition) //========================================================= // > SCHED_CSCANNER_PATROL //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_PATROL, " Tasks" " TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" " TASK_SCANNER_SET_FLY_PATROL 0" " TASK_SET_TOLERANCE_DISTANCE 32" " TASK_SET_ROUTE_SEARCH_TIME 5" // Spend 5 seconds trying to build a path if stuck " TASK_GET_PATH_TO_RANDOM_NODE 2000" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" "" " Interrupts" " COND_GIVE_WAY" " COND_NEW_ENEMY" " COND_SEE_ENEMY" " COND_SEE_FEAR" " COND_HEAR_COMBAT" " COND_HEAR_DANGER" " COND_HEAR_PLAYER" " COND_LIGHT_DAMAGE" " COND_HEAVY_DAMAGE" " COND_PROVOKED" " COND_CSCANNER_HAVE_INSPECT_TARGET" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_SPOTLIGHT_HOVER // // Hover above target entity, trying to get spotlight // on my target //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_SPOTLIGHT_HOVER, " Tasks" " TASK_CSCANNER_SET_FLY_SPOT 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_WALK " " TASK_WAIT 1" "" " Interrupts" " COND_CSCANNER_SPOT_ON_TARGET" " COND_CSCANNER_INSPECT_DONE" " COND_SCANNER_FLY_BLOCKED" " COND_NEW_ENEMY" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS // // Inspect a position once spotlight is on it //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_SPOTLIGHT_INSPECT_POS, " Tasks" " TASK_CSCANNER_SET_FLY_SPOT 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT" " TASK_SPEAK_SENTENCE 3" // Curious sound " TASK_WAIT 5" " TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" "" " Interrupts" " COND_CSCANNER_INSPECT_DONE" " COND_HEAR_DANGER" " COND_HEAR_COMBAT" " COND_NEW_ENEMY" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT // // Inspect a citizen once spotlight is on it //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_SPOTLIGHT_INSPECT_CIT, " Tasks" " TASK_CSCANNER_SET_FLY_SPOT 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_SCANNER_INSPECT" " TASK_SPEAK_SENTENCE 0" // Stop! " TASK_WAIT 1" " TASK_CSCANNER_SPOT_INSPECT_ON 0" " TASK_WAIT 2" " TASK_SPEAK_SENTENCE 1" // Hands on head or Show Armband! " TASK_WAIT 1" " TASK_CSCANNER_SPOT_INSPECT_WAIT 0" " TASK_WAIT 5" " TASK_SPEAK_SENTENCE 2" // Free to go! " TASK_WAIT 1" " TASK_CSCANNER_SPOT_INSPECT_OFF 0" " TASK_CSCANNER_CLEAR_INSPECT_TARGET 0" "" " Interrupts" " COND_NEW_ENEMY" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_PHOTOGRAPH_HOVER //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_PHOTOGRAPH_HOVER, " Tasks" " TASK_CSCANNER_SET_FLY_PHOTO 0" " TASK_WAIT 2" "" " Interrupts" " COND_CSCANNER_INSPECT_DONE" " COND_CSCANNER_CAN_PHOTOGRAPH" " COND_SCANNER_FLY_BLOCKED" " COND_NEW_ENEMY" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_PHOTOGRAPH //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_PHOTOGRAPH, " Tasks" " TASK_CSCANNER_SET_FLY_PHOTO 0" " TASK_CSCANNER_PHOTOGRAPH 0" "" " Interrupts" " COND_CSCANNER_INSPECT_DONE" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_ATTACK_FLASH //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_ATTACK_FLASH, " Tasks" " TASK_SCANNER_SET_FLY_ATTACK 0" " TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" " TASK_CSCANNER_ATTACK_PRE_FLASH 0 " " TASK_CSCANNER_ATTACK_FLASH 0" " TASK_WAIT 0.5" "" " Interrupts" " COND_NEW_ENEMY" " COND_ENEMY_DEAD" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) //========================================================= // > SCHED_CSCANNER_MOVE_TO_INSPECT //========================================================= DEFINE_SCHEDULE ( SCHED_CSCANNER_MOVE_TO_INSPECT, " Tasks" " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_SCANNER_PATROL" " TASK_SET_TOLERANCE_DISTANCE 128" " TASK_CSCANNER_GET_PATH_TO_INSPECT_TARGET 0" " TASK_RUN_PATH 0" " TASK_WAIT_FOR_MOVEMENT 0" "" " Interrupts" " COND_SCANNER_FLY_CLEAR" " COND_NEW_ENEMY" " COND_SCANNER_GRABBED_BY_PHYSCANNON" ) AI_END_CUSTOM_NPC() //----------------------------------------------------------------------------- // Claw Scanner // // Scanner that always spawns as a claw scanner //----------------------------------------------------------------------------- class CNPC_ClawMSynth : public CNPC_MSynth { DECLARE_CLASS(CNPC_ClawMSynth, CNPC_MSynth); public: CNPC_ClawMSynth(); DECLARE_DATADESC(); }; BEGIN_DATADESC(CNPC_ClawMSynth) END_DATADESC() LINK_ENTITY_TO_CLASS(npc_clawmsynth, CNPC_ClawMSynth); //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CNPC_ClawMSynth::CNPC_ClawMSynth() { // override our superclass's setting BecomeClawScanner(); }