diff --git a/sp/src/game/server/hl2/proto_sniper.cpp b/sp/src/game/server/hl2/proto_sniper.cpp index cd7c309e76..0343a51635 100644 --- a/sp/src/game/server/hl2/proto_sniper.cpp +++ b/sp/src/game/server/hl2/proto_sniper.cpp @@ -58,6 +58,26 @@ ConVar showsniperdist("showsniperdist", "0" ); ConVar sniperspeak( "sniperspeak", "0" ); ConVar sniper_xbox_delay( "sniper_xbox_delay", "1" ); +#ifdef MAPBASE +// ConVars for configuring Unhidden Snipers +ConVar npc_sniper_riflemodel("npc_sniper_riflemodel", "models/weapons/w_irifle.mdl"); +ConVar npc_sniper_rifle_offset_x("npc_sniper_rifle_offset_x", "10.5"); +ConVar npc_sniper_rifle_offset_y("npc_sniper_rifle_offset_y", "-2.5"); +ConVar npc_sniper_rifle_offset_z("npc_sniper_rifle_offset_z", "45.0"); +ConVar npc_sniper_rifle_despawn_t("npc_sniper_rifle_despawn_t", "5"); +ConVar npc_sniper_rifle_use_attachment("npc_sniper_rifle_use_attachment", "1"); +ConVar npc_sniper_rifle_attachment_point("npc_sniper_rifle_attachment_point", "lefthand"); + +ConVar npc_sniper_rifle_offset_x_attachment("npc_sniper_rifle_offset_x_attachment", "-3.5"); +ConVar npc_sniper_rifle_offset_y_attachment("npc_sniper_rifle_offset_y_attachment", "-7.5"); +ConVar npc_sniper_rifle_offset_z_attachment("npc_sniper_rifle_offset_z_attachment", "0"); + +ConVar npc_sniper_use_aiming_anims("npc_sniper_use_aiming_anims", "1"); +ConVar npc_sniper_should_rotate_body("npc_sniper_should_rotate_body", "1"); + +ConVar sk_npc_sniper_easy_delay("sk_npc_sniper_easy_delay", "5.0"); +#endif + // Moved to HL2_SharedGameRules because these are referenced by shared AmmoDef functions extern ConVar sk_dmg_sniper_penetrate_plr; extern ConVar sk_dmg_sniper_penetrate_npc; @@ -71,6 +91,7 @@ extern ConVar sk_dmg_sniper_penetrate_npc; #define SF_SNIPER_NOSWEEP (1 << 21) ///< This sniper doesn't sweep to the target or use decoys. #ifdef MAPBASE #define SF_SNIPER_DIE_ON_FIRE (1 << 22) // This sniper dies on fire. +#define SF_SNIPER_DELAY_FIRE_ON_EASY (1 << 23) // This sniper will delay fire by 5 seconds on Easy. #endif // If the last time I fired at someone was between 0 and this many seconds, draw @@ -407,6 +428,10 @@ class CProtoSniper : public CAI_BaseNPC #endif COutputEvent m_OnShotFired; + +#ifdef MAPBASE + CBaseEntity* m_FakeRifle; +#endif DEFINE_CUSTOM_AI; @@ -755,7 +780,31 @@ void CProtoSniper::LaserOn( const Vector &vecTarget, const Vector &vecDeviance ) // The beam is backwards, sortof. The endpoint is the sniper. This is // so that the beam can be tapered to very thin where it emits from the sniper. - m_pBeam->PointsInit( vecInitialAim, GetBulletOrigin() ); +#ifndef MAPBASE + m_pBeam->PointsInit(vecInitialAim, GetBulletOrigin()); +#endif +#ifdef MAPBASE + if (HasSpawnFlags(SF_SNIPER_HIDDEN)) + { + m_pBeam->PointsInit(vecInitialAim, GetBulletOrigin()); + } + else + { + // Try to make the laser emit from the rifle or the NPC origin + if (GetNavigator() != NULL && GetNavigator()->IsGoalActive()) + { + vecInitialAim = vecTarget; + } + if (m_FakeRifle != NULL) + { + m_pBeam->PointsInit(vecInitialAim, m_FakeRifle->GetAbsOrigin()); + } + else + { + m_pBeam->PointsInit(vecInitialAim, GetAbsOrigin()); + } + } +#endif m_pBeam->SetBrightness( 255 ); m_pBeam->SetNoise( 0 ); m_pBeam->SetWidth( 1.0f ); @@ -1107,6 +1156,57 @@ void CProtoSniper::Spawn( void ) AddEffects( EF_NODRAW ); AddSolidFlags( FSOLID_NOT_SOLID ); } +#ifdef MAPBASE + else + { + m_FakeRifle = CreateEntityByName("prop_dynamic_ornament"); + if (m_FakeRifle) + { + bool shouldUseAimingVariables = false; + Vector offset; + if (npc_sniper_rifle_use_attachment.GetBool()) + { + int attachment = LookupAttachment(npc_sniper_rifle_attachment_point.GetString()); + { + if (attachment) + { + offset.Init(npc_sniper_rifle_offset_x_attachment.GetFloat(), npc_sniper_rifle_offset_y_attachment.GetFloat(), npc_sniper_rifle_offset_z_attachment.GetFloat()); + m_FakeRifle->SetParent(this, attachment); + shouldUseAimingVariables = true; + } + else + { + Warning("Failed to attach to attachment, attachment doesn't exist.\n"); + offset.Init(npc_sniper_rifle_offset_x.GetFloat(), npc_sniper_rifle_offset_y.GetFloat(), npc_sniper_rifle_offset_z.GetFloat()); + m_FakeRifle->SetParent(this); + } + } + } + else + { + offset.Init(npc_sniper_rifle_offset_x.GetFloat(), npc_sniper_rifle_offset_y.GetFloat(), npc_sniper_rifle_offset_z.GetFloat()); + m_FakeRifle->SetParent(this); + } + m_FakeRifle->SetLocalOrigin(offset); + m_FakeRifle->SetModel(npc_sniper_riflemodel.GetString()); + m_FakeRifle->SetOwnerEntity(this); + m_FakeRifle->Spawn(); + m_FakeRifle->Activate(); + + m_bloodColor = BLOOD_COLOR_RED; + SetMoveType(MOVETYPE_STEP); + CapabilitiesAdd(bits_CAP_ANIMATEDFACE); + CapabilitiesAdd(bits_CAP_DOORS_GROUP); + CapabilitiesAdd(bits_CAP_FRIENDLY_DMG_IMMUNE); + CapabilitiesAdd(bits_CAP_MOVE_GROUND); + if (npc_sniper_use_aiming_anims.GetBool()) + { + CapabilitiesAdd(bits_CAP_AIM_GUN); + } + CapabilitiesAdd(bits_CAP_TURN_HEAD); + } + } +#endif // Point the cursor straight ahead so that the sniper's // first sweep of the laser doesn't look weird. @@ -1420,6 +1520,14 @@ int CProtoSniper::OnTakeDamage_Alive( const CTakeDamageInfo &info ) if ( info.GetDamageType() == DMG_GENERIC && info.GetInflictor() == this ) return CAI_BaseNPC::OnTakeDamage_Alive( newInfo ); +#ifdef MAPBASE + // Allow unhidden snipers to be damaged by normal means + if (!HasSpawnFlags(SF_SNIPER_HIDDEN)) + { + return CAI_BaseNPC::OnTakeDamage_Alive(newInfo); + } +#endif + if( !(info.GetDamageType() & (DMG_BLAST|DMG_BURN) ) ) { // Only blasts and burning hurt @@ -1461,6 +1569,52 @@ void CProtoSniper::Event_Killed( const CTakeDamageInfo &info ) { if( !(m_spawnflags & SF_SNIPER_NOCORPSE) ) { +#ifdef MAPBASE + // If we're not hidden, spawn a physics version of our rifle + if (!HasSpawnFlags(SF_SNIPER_HIDDEN)) + { + // Clear the rifle + if (m_FakeRifle != NULL) + { + m_FakeRifle->SetParent(NULL); + UTIL_Remove(m_FakeRifle); + m_FakeRifle = NULL; + } + + CBaseEntity* pGunProp = CreateEntityByName("prop_physics"); + if (pGunProp) + { + Vector offset; + offset.Init(npc_sniper_rifle_offset_x.GetFloat(), npc_sniper_rifle_offset_y.GetFloat(), npc_sniper_rifle_offset_z.GetFloat()); + pGunProp->SetModel(npc_sniper_riflemodel.GetString()); + pGunProp->SetAbsOrigin(GetAbsOrigin() + offset); + pGunProp->SetAbsAngles(GetAbsAngles()); + pGunProp->SetCollisionGroup(COLLISION_GROUP_WEAPON); + pGunProp->Spawn(); + pGunProp->Activate(); + + variant_t variant; + variant.SetFloat(npc_sniper_rifle_despawn_t.GetFloat()); + pGunProp->AcceptInput("KillWhenNotVisible", NULL, NULL, variant, -1); + + IPhysicsObject* pPhys = pGunProp->VPhysicsGetObject(); + if (pPhys) + { + // Add an extra push in a random direction + Vector vel = RandomVector(-64.0f, 64.0f); + AngularImpulse angImp = RandomAngularImpulse(-300.0f, 300.0f); + vel[2] = 0.0f; + pPhys->AddVelocity(&vel, &angImp); + } + else + { + // taken from DropItem code + pGunProp->ApplyAbsVelocityImpulse(GetAbsVelocity()); + pGunProp->ApplyLocalAngularVelocityImpulse(AngularImpulse(0, random->RandomFloat(0, 100), 0)); + } + } +#endif + } #ifdef MAPBASE Vector vecForce; @@ -2056,9 +2210,21 @@ Activity CProtoSniper::NPC_TranslateActivity( Activity eNewActivity ) { // ACT_IDLE is now just the soldier's unarmed idle animation. // Use a gun-holding animation like what unhidden snipers were using before. - if (!HasSpawnFlags( SF_SNIPER_HIDDEN ) && eNewActivity == ACT_IDLE) + if (!HasSpawnFlags( SF_SNIPER_HIDDEN )) { - eNewActivity = ACT_IDLE_SMG1; + if (eNewActivity == ACT_IDLE) + { + // Now uses aiming animations for snipers that don't have Hidden spawnflag + eNewActivity = ACT_IDLE_ANGRY_SMG1; // ACT_IDLE_SMG1 + } + else if (eNewActivity == ACT_WALK) + { + eNewActivity = ACT_WALK_AIM_RIFLE; + } + else if (eNewActivity == ACT_RUN) + { + eNewActivity = ACT_RUN_AIM_RIFLE; + } } return BaseClass::NPC_TranslateActivity( eNewActivity ); @@ -2246,6 +2412,14 @@ void CProtoSniper::StartTask( const Task_t *pTask ) delay += sniper_xbox_delay.GetFloat(); #endif +#ifdef MAPBASE + // Add a delay on easy difficulty + if (g_pGameRules->IsSkillLevel(SKILL_EASY) && !HasSpawnFlags(SF_SNIPER_HIDDEN) && HasSpawnFlags(SF_SNIPER_DELAY_FIRE_ON_EASY)) + { + delay += sk_npc_sniper_easy_delay.GetFloat(); + } +#endif + if( gpGlobals->curtime - m_flTimeLastAttackedPlayer <= SNIPER_FASTER_ATTACK_PERIOD ) { SetWait( SNIPER_SUBSEQUENT_PAINT_TIME + delay ); @@ -2605,6 +2779,16 @@ void CProtoSniper::PrescheduleThink( void ) // If the enemy has been out of sight for a full second, mark him eluded. if( GetEnemy() != NULL ) { +#ifdef MAPBASE + if (npc_sniper_should_rotate_body.GetBool() && !HasSpawnFlags(SF_SNIPER_HIDDEN)) + { + // Rotate and face the target + Vector posToLookAt = GetEnemy()->GetAbsOrigin() - GetAbsOrigin(); + QAngle angles; + VectorAngles(posToLookAt, angles); + SetAbsAngles(QAngle(0, angles.y, 0)); + } +#endif if( gpGlobals->curtime - GetEnemies()->LastTimeSeen( GetEnemy() ) > 30 ) { // Stop pestering enemies after 30 seconds of frustration.