From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- sp/src/game/server/hl2/func_tank.cpp | 4431 ++++++++++++++++++++++++++++++++++ 1 file changed, 4431 insertions(+) create mode 100644 sp/src/game/server/hl2/func_tank.cpp (limited to 'sp/src/game/server/hl2/func_tank.cpp') diff --git a/sp/src/game/server/hl2/func_tank.cpp b/sp/src/game/server/hl2/func_tank.cpp new file mode 100644 index 00000000..b41708e9 --- /dev/null +++ b/sp/src/game/server/hl2/func_tank.cpp @@ -0,0 +1,4431 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "cbase.h" +#include "func_tank.h" +#include "Sprite.h" +#include "EnvLaser.h" +#include "basecombatweapon.h" +#include "explode.h" +#include "eventqueue.h" +#include "gamerules.h" +#include "ammodef.h" +#include "in_buttons.h" +#include "soundent.h" +#include "ndebugoverlay.h" +#include "grenade_beam.h" +#include "vstdlib/random.h" +#include "engine/IEngineSound.h" +#include "physics_cannister.h" +#include "decals.h" +#include "shake.h" +#include "particle_smokegrenade.h" +#include "player.h" +#include "entitylist.h" +#include "IEffects.h" +#include "ai_basenpc.h" +#include "ai_behavior_functank.h" +#include "weapon_rpg.h" +#include "effects.h" +#include "iservervehicle.h" +#include "soundenvelope.h" +#include "effect_dispatch_data.h" +#include "te_effect_dispatch.h" +#include "props.h" +#include "rumble_shared.h" +#include "particle_parse.h" +// NVNT turret recoil +#include "haptics/haptic_utils.h" + +#ifdef HL2_DLL +#include "hl2_player.h" +#endif //HL2_DLL + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint); + +ConVar mortar_visualize("mortar_visualize", "0" ); + +BEGIN_DATADESC( CFuncTank ) + DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ), + DEFINE_KEYFIELD( m_yawRange, FIELD_FLOAT, "yawrange" ), + DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ), + DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ), + DEFINE_KEYFIELD( m_pitchRange, FIELD_FLOAT, "pitchrange" ), + DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ), + DEFINE_KEYFIELD( m_fireRate, FIELD_FLOAT, "firerate" ), + DEFINE_FIELD( m_fireTime, FIELD_TIME ), + DEFINE_KEYFIELD( m_persist, FIELD_FLOAT, "persistence" ), + DEFINE_KEYFIELD( m_persist2, FIELD_FLOAT, "persistence2" ), + DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ), + DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ), + DEFINE_FIELD( m_flMinRange2, FIELD_FLOAT ), + DEFINE_FIELD( m_flMaxRange2, FIELD_FLOAT ), + DEFINE_KEYFIELD( m_iAmmoCount, FIELD_INTEGER, "ammo_count" ), + DEFINE_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ), + DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ), + DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ), + DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ), + DEFINE_FIELD( m_nBulletCount, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ), + DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ), + DEFINE_KEYFIELD( m_iBulletDamageVsPlayer, FIELD_INTEGER, "bullet_damage_vs_player" ), + DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ), + +#ifdef HL2_EPISODIC + DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ), + DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), +#else + DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ), +#endif // HL2_EPISODIC + + DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ), + DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ), + DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ), + DEFINE_KEYFIELD( m_flPlayerGracePeriod, FIELD_FLOAT, "playergraceperiod" ), + DEFINE_KEYFIELD( m_flIgnoreGraceUpto, FIELD_FLOAT, "ignoregraceupto" ), + DEFINE_KEYFIELD( m_flPlayerLockTimeBeforeFire, FIELD_FLOAT, "playerlocktimebeforefire" ), + DEFINE_FIELD( m_flLastSawNonPlayer, FIELD_TIME ), + + DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( m_yawCenterWorld, FIELD_FLOAT ), + DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( m_pitchCenterWorld, FIELD_FLOAT ), + DEFINE_FIELD( m_fireLast, FIELD_TIME ), + DEFINE_FIELD( m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( m_sightOrigin, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_hFuncTankTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_hController, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( m_targetEntityName, FIELD_STRING ), + DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( m_vTargetPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecNPCIdleTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_persist2burst, FIELD_FLOAT), + //DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE + DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ), + DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ), + DEFINE_FIELD( m_flNextControllerSearch, FIELD_TIME ), + DEFINE_FIELD( m_bShouldFindNPCs, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bNPCInRoute, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_iszNPCManPoint, FIELD_STRING, "npc_man_point" ), + DEFINE_FIELD( m_bReadyToFire, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_bPerformLeading, FIELD_BOOLEAN, "LeadTarget" ), + DEFINE_FIELD( m_flStartLeadFactor, FIELD_FLOAT ), + DEFINE_FIELD( m_flStartLeadFactorTime, FIELD_TIME ), + DEFINE_FIELD( m_flNextLeadFactor, FIELD_FLOAT ), + DEFINE_FIELD( m_flNextLeadFactorTime, FIELD_TIME ), + + // Used for when the gun is attached to another entity + DEFINE_KEYFIELD( m_iszBaseAttachment, FIELD_STRING, "gun_base_attach" ), + DEFINE_KEYFIELD( m_iszBarrelAttachment, FIELD_STRING, "gun_barrel_attach" ), +// DEFINE_FIELD( m_nBarrelAttachment, FIELD_INTEGER ), + + // Used when the gun is actually a part of the parent entity, and pose params aim it + DEFINE_KEYFIELD( m_iszYawPoseParam, FIELD_STRING, "gun_yaw_pose_param" ), + DEFINE_KEYFIELD( m_iszPitchPoseParam, FIELD_STRING, "gun_pitch_pose_param" ), + DEFINE_KEYFIELD( m_flYawPoseCenter, FIELD_FLOAT, "gun_yaw_pose_center" ), + DEFINE_KEYFIELD( m_flPitchPoseCenter, FIELD_FLOAT, "gun_pitch_pose_center" ), + DEFINE_FIELD( m_bUsePoseParameters, FIELD_BOOLEAN ), + + DEFINE_KEYFIELD( m_iEffectHandling, FIELD_INTEGER, "effecthandling" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), + DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamage", InputSetDamage ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ), + DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetDir", InputSetTargetDir ), + DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ), + DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ), + DEFINE_INPUTFUNC( FIELD_VOID, "ClearTargetEntity", InputClearTargetEntity ), + DEFINE_INPUTFUNC( FIELD_STRING, "FindNPCToManTank", InputFindNPCToManTank ), + DEFINE_INPUTFUNC( FIELD_VOID, "StopFindingNPCs", InputStopFindingNPCs ), + DEFINE_INPUTFUNC( FIELD_VOID, "StartFindingNPCs", InputStartFindingNPCs ), + DEFINE_INPUTFUNC( FIELD_VOID, "ForceNPCOff", InputForceNPCOff ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxRange", InputSetMaxRange ), + + // Outputs + DEFINE_OUTPUT(m_OnFire, "OnFire"), + DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"), + DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"), + DEFINE_OUTPUT(m_OnAmmoDepleted, "OnAmmoDepleted"), + DEFINE_OUTPUT(m_OnGotController, "OnGotController"), + DEFINE_OUTPUT(m_OnLostController, "OnLostController"), + DEFINE_OUTPUT(m_OnGotPlayerController, "OnGotPlayerController"), + DEFINE_OUTPUT(m_OnLostPlayerController, "OnLostPlayerController"), + DEFINE_OUTPUT(m_OnReadyToFire, "OnReadyToFire"), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncTank::CFuncTank() +{ + m_nBulletCount = 0; + + m_bNPCInRoute = false; + m_flNextControllerSearch = 0; + m_bShouldFindNPCs = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CFuncTank::~CFuncTank( void ) +{ + if ( m_soundLoopRotate != NULL_STRING && ( m_spawnflags & SF_TANK_SOUNDON ) ) + { + StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +inline bool CFuncTank::CanFire( void ) +{ + float flTimeDelay = gpGlobals->curtime - m_lastSightTime; + + // Fire when can't see enemy if time is less that persistence time + if ( flTimeDelay <= m_persist ) + return true; + + // Fire when I'm in a persistence2 burst + if ( flTimeDelay <= m_persist2burst ) + return true; + + // If less than persistence2, occasionally do another burst + if ( flTimeDelay <= m_persist2 ) + { + if ( random->RandomInt( 0, 30 ) == 0 ) + { + m_persist2burst = flTimeDelay + 0.5f; + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------ +// Purpose: Input handler for activating the tank. +//------------------------------------------------------------------------------ +void CFuncTank::InputActivate( inputdata_t &inputdata ) +{ + TankActivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::TankActivate( void ) +{ + m_spawnflags |= SF_TANK_ACTIVE; + SetNextThink( gpGlobals->curtime + 0.1f ); + m_fireLast = gpGlobals->curtime; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for deactivating the tank. +//----------------------------------------------------------------------------- +void CFuncTank::InputDeactivate( inputdata_t &inputdata ) +{ + TankDeactivate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::TankDeactivate( void ) +{ + m_spawnflags &= ~SF_TANK_ACTIVE; + m_fireLast = 0; + StopRotSound(); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for changing the name of the tank's target entity. +//----------------------------------------------------------------------------- +void CFuncTank::InputSetTargetEntityName( inputdata_t &inputdata ) +{ + m_targetEntityName = inputdata.value.StringID(); + m_hTarget = FindTarget( m_targetEntityName, inputdata.pActivator ); + + // No longer aim at target position if have one + m_spawnflags &= ~SF_TANK_AIM_AT_POS; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting a new target entity by ehandle. +//----------------------------------------------------------------------------- +void CFuncTank::InputSetTargetEntity( inputdata_t &inputdata ) +{ + if ( inputdata.value.Entity() != NULL ) + { + m_targetEntityName = inputdata.value.Entity()->GetEntityName(); + } + else + { + m_targetEntityName = NULL_STRING; + } + m_hTarget = inputdata.value.Entity(); + + // No longer aim at target position if have one + m_spawnflags &= ~SF_TANK_AIM_AT_POS; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for clearing the tank's target entity +//----------------------------------------------------------------------------- +void CFuncTank::InputClearTargetEntity( inputdata_t &inputdata ) +{ + m_targetEntityName = NULL_STRING; + m_hTarget = NULL; + + // No longer aim at target position if have one + m_spawnflags &= ~SF_TANK_AIM_AT_POS; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the rate of fire in shots per second. +//----------------------------------------------------------------------------- +void CFuncTank::InputSetFireRate( inputdata_t &inputdata ) +{ + m_fireRate = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the damage +//----------------------------------------------------------------------------- +void CFuncTank::InputSetDamage( inputdata_t &inputdata ) +{ + m_iBulletDamage = inputdata.value.Int(); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the target as a position. +//----------------------------------------------------------------------------- +void CFuncTank::InputSetTargetPosition( inputdata_t &inputdata ) +{ + m_spawnflags |= SF_TANK_AIM_AT_POS; + m_hTarget = NULL; + + inputdata.value.Vector3D( m_vTargetPosition ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for setting the target as a position. +//----------------------------------------------------------------------------- +void CFuncTank::InputSetTargetDir( inputdata_t &inputdata ) +{ + m_spawnflags |= SF_TANK_AIM_AT_POS; + m_hTarget = NULL; + + Vector vecTargetDir; + inputdata.value.Vector3D( vecTargetDir ); + m_vTargetPosition = GetAbsOrigin() + m_barrelPos.LengthSqr() * vecTargetDir; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for telling the func_tank to find an NPC to man it. +//----------------------------------------------------------------------------- +void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) +{ + // Verify the func_tank is controllable and available. + if ( !IsNPCControllable() && !IsNPCSetController() ) + return; + + // If we have a controller already - don't look for one. + if ( HasController() ) + return; + + // NPC assigned to man the func_tank? + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() ); + if ( pEntity ) + { + CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); + if ( pNPC ) + { + // Verify the npc has the func_tank controller behavior. + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) ) + { + m_hController = pNPC; + pBehavior->SetFuncTank( this ); + NPC_SetInRoute( true ); + return; + } + } + } + + // No controller? Find a nearby NPC who can man this func_tank. + NPC_FindController(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncTank::InputStopFindingNPCs( inputdata_t &inputdata ) +{ + m_bShouldFindNPCs = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncTank::InputStartFindingNPCs( inputdata_t &inputdata ) +{ + m_bShouldFindNPCs = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncTank::InputForceNPCOff( inputdata_t &inputdata ) +{ + // Interrupt any npc in route (ally or not). + if ( NPC_InRoute() ) + { + // Interrupt the npc's route. + NPC_InterruptRoute(); + } + + // If we don't have a controller - then the gun should be free. + if ( !m_hController ) + return; + + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + if ( !pNPC ) + return; + + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) ) + { + pBehavior->Dismount(); + } + + m_hController = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &inputdata - +//----------------------------------------------------------------------------- +void CFuncTank::InputSetMaxRange( inputdata_t &inputdata ) +{ + m_maxRange = inputdata.value.Float(); + m_flMaxRange2 = m_maxRange * m_maxRange; +} + +//----------------------------------------------------------------------------- +// Purpose: Find the closest NPC with the func_tank behavior. +//----------------------------------------------------------------------------- +void CFuncTank::NPC_FindController( void ) +{ + // Not NPC controllable or controllable on by specified npc's return. + if ( !IsNPCControllable() || IsNPCSetController() ) + return; + + // Initialize for finding closest NPC. + CAI_BaseNPC *pClosestNPC = NULL; + float flClosestDist2 = ( FUNCTANK_DISTANCE_MAX * FUNCTANK_DISTANCE_MAX ); + float flMinDistToEnemy2 = ( FUNCTANK_DISTANCE_MIN_TO_ENEMY * FUNCTANK_DISTANCE_MIN_TO_ENEMY ); + CAI_FuncTankBehavior *pClosestBehavior = NULL; + + // Get the mount position. + Vector vecMountPos; + NPC_FindManPoint( vecMountPos ); + + // Search through the AI list for the closest NPC with the func_tank behavior. + CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); + int nAICount = g_AI_Manager.NumAIs(); + for ( int iAI = 0; iAI < nAICount; ++iAI ) + { + CAI_BaseNPC *pNPC = ppAIs[iAI]; + if ( !pNPC ) + continue; + + if ( !pNPC->IsAlive() ) + continue; + + if ( pNPC->IsInAScript() ) + continue; + + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) ) + { + // Don't mount the func_tank if your "enemy" is within X feet or it or the npc. + CBaseEntity *pEnemy = pNPC->GetEnemy(); + + if ( pEnemy ) + { + if ( !IsEntityInViewCone(pEnemy) ) + { + // Don't mount the tank if the tank can't be aimed at the enemy. + continue; + } + + float flDist2 = ( pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin() ).LengthSqr(); + if ( flDist2 < flMinDistToEnemy2 ) + continue; + + flDist2 = ( vecMountPos - pEnemy->GetAbsOrigin() ).LengthSqr(); + if ( flDist2 < flMinDistToEnemy2 ) + continue; + + if ( !pNPC->FVisible( vecMountPos + pNPC->GetViewOffset() ) ) + continue; + } + + trace_t tr; + UTIL_TraceEntity( pNPC, vecMountPos, vecMountPos, MASK_NPCSOLID, this, pNPC->GetCollisionGroup(), &tr ); + if( tr.startsolid || tr.fraction < 1.0 ) + { + // Don't mount the tank if someone/something is located on the control point. + continue; + } + + if ( !pBehavior->HasFuncTank() && !pBehavior->IsBusy() ) + { + float flDist2 = ( vecMountPos - pNPC->GetAbsOrigin() ).LengthSqr(); + if ( flDist2 < flClosestDist2 ) + { + pClosestNPC = pNPC; + pClosestBehavior = pBehavior; + flClosestDist2 = flDist2; + } + } + } + } + + // Set the closest NPC as controller. + if ( pClosestNPC ) + { + m_hController = pClosestNPC; + pClosestBehavior->SetFuncTank( this ); + NPC_SetInRoute( true ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CFuncTank::DrawDebugTextOverlays(void) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + // -------------- + // State + // -------------- + char tempstr[255]; + if (IsActive()) + { + Q_strncpy(tempstr,"State: Active",sizeof(tempstr)); + } + else + { + Q_strncpy(tempstr,"State: Inactive",sizeof(tempstr)); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + // ------------------- + // Print Firing Speed + // -------------------- + Q_snprintf(tempstr,sizeof(tempstr),"Fire Rate: %f",m_fireRate); + + EntityText(text_offset,tempstr,0); + text_offset++; + + // -------------- + // Print Target + // -------------- + if (m_hTarget!=NULL) + { + Q_snprintf(tempstr,sizeof(tempstr),"Target: %s",m_hTarget->GetDebugName()); + } + else + { + Q_snprintf(tempstr,sizeof(tempstr),"Target: - "); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + // -------------- + // Target Pos + // -------------- + if (m_spawnflags & SF_TANK_AIM_AT_POS) + { + Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: %3.0f %3.0f %3.0f",m_vTargetPosition.x,m_vTargetPosition.y,m_vTargetPosition.z); + } + else + { + Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: - "); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Override base class to add display of fly direction +// Input : +// Output : +//----------------------------------------------------------------------------- +void CFuncTank::DrawDebugGeometryOverlays(void) +{ + // Center + QAngle angCenter; + Vector vecForward; + angCenter = QAngle( 0, YawCenterWorld(), 0 ); + AngleVectors( angCenter, &vecForward ); + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 64), 255,255,255, true, 0.1); + + // Draw the yaw ranges + angCenter = QAngle( 0, YawCenterWorld() + m_yawRange, 0 ); + AngleVectors( angCenter, &vecForward ); + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1); + angCenter = QAngle( 0, YawCenterWorld() - m_yawRange, 0 ); + AngleVectors( angCenter, &vecForward ); + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1); + + // Draw the pitch ranges + angCenter = QAngle( PitchCenterWorld() + m_pitchRange, 0, 0 ); + AngleVectors( angCenter, &vecForward ); + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1); + angCenter = QAngle( PitchCenterWorld() - m_pitchRange, 0, 0 ); + AngleVectors( angCenter, &vecForward ); + NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1); + + BaseClass::DrawDebugGeometryOverlays(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pAttacker - +// flDamage - +// vecDir - +// ptr - +// bitsDamageType - +//----------------------------------------------------------------------------- +void CFuncTank::TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType) +{ + if (m_spawnflags & SF_TANK_DAMAGE_KICK) + { + // Deflect the func_tank + // Only adjust yaw for now + if (pAttacker) + { + Vector vFromAttacker = (pAttacker->EyePosition()-GetAbsOrigin()); + vFromAttacker.z = 0; + VectorNormalize(vFromAttacker); + + Vector vFromAttacker2 = (ptr->endpos-GetAbsOrigin()); + vFromAttacker2.z = 0; + VectorNormalize(vFromAttacker2); + + + Vector vCrossProduct; + CrossProduct(vFromAttacker,vFromAttacker2, vCrossProduct); + + QAngle angles; + angles = GetLocalAngles(); + if (vCrossProduct.z > 0) + { + angles.y += 10; + } + else + { + angles.y -= 10; + } + + // Limit against range in y + if ( angles.y > m_yawCenter + m_yawRange ) + { + angles.y = m_yawCenter + m_yawRange; + } + else if ( angles.y < (m_yawCenter - m_yawRange) ) + { + angles.y = (m_yawCenter - m_yawRange); + } + + SetLocalAngles( angles ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : targetName - +// pActivator - +//----------------------------------------------------------------------------- +CBaseEntity *CFuncTank::FindTarget( string_t targetName, CBaseEntity *pActivator ) +{ + return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Caches entity key values until spawn is called. +// Input : szKeyName - +// szValue - +// Output : +//----------------------------------------------------------------------------- +bool CFuncTank::KeyValue( const char *szKeyName, const char *szValue ) +{ + if (FStrEq(szKeyName, "barrel")) + { + m_barrelPos.x = atof(szValue); + return true; + } + + if (FStrEq(szKeyName, "barrely")) + { + m_barrelPos.y = atof(szValue); + return true; + } + + if (FStrEq(szKeyName, "barrelz")) + { + m_barrelPos.z = atof(szValue); + return true; + } + + return BaseClass::KeyValue( szKeyName, szValue ); +} + + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::Spawn( void ) +{ + Precache(); + +#ifdef HL2_EPISODIC + m_iAmmoType = GetAmmoDef()->Index( STRING( m_iszAmmoType ) ); +#else + m_iSmallAmmoType = GetAmmoDef()->Index("Pistol"); + m_iMediumAmmoType = GetAmmoDef()->Index("SMG1"); + m_iLargeAmmoType = GetAmmoDef()->Index("AR2"); +#endif // HL2_EPISODIC + + SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything + SetSolid( SOLID_VPHYSICS ); + SetModel( STRING( GetModelName() ) ); + AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); + + if ( HasSpawnFlags(SF_TANK_NOTSOLID) ) + { + AddSolidFlags( FSOLID_NOT_SOLID ); + } + + m_hControlVolume = NULL; + + if ( GetParent() && GetParent()->GetBaseAnimating() ) + { + CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); + if ( m_iszBaseAttachment != NULL_STRING ) + { + int nAttachment = pAnim->LookupAttachment( STRING( m_iszBaseAttachment ) ); + if ( nAttachment != 0 ) + { + SetParent( pAnim, nAttachment ); + SetLocalOrigin( vec3_origin ); + SetLocalAngles( vec3_angle ); + } + } + + m_bUsePoseParameters = (m_iszYawPoseParam != NULL_STRING) && (m_iszPitchPoseParam != NULL_STRING); + + if ( m_iszBarrelAttachment != NULL_STRING ) + { + if ( m_bUsePoseParameters ) + { + pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), 0 ); + pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), 0 ); + pAnim->InvalidateBoneCache(); + } + + m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); + + Vector vecWorldBarrelPos; + QAngle worldBarrelAngle; + pAnim->GetAttachment( m_nBarrelAttachment, vecWorldBarrelPos, worldBarrelAngle ); + VectorITransform( vecWorldBarrelPos, EntityToWorldTransform( ), m_barrelPos ); + } + + if ( m_bUsePoseParameters ) + { + // In this case, we're relying on the parent to have the gun model + AddEffects( EF_NODRAW ); + QAngle localAngles( m_flPitchPoseCenter, m_flYawPoseCenter, 0 ); + SetLocalAngles( localAngles ); + SetSolid( SOLID_NONE ); + SetMoveType( MOVETYPE_NOCLIP ); + + // If our parent is a prop_dynamic, make it use hitboxes for renderbox + CDynamicProp *pProp = dynamic_cast(GetParent()); + if ( pProp ) + { + pProp->m_bUseHitboxesForRenderBox = true; + } + } + } + + // For smoothing out leading + m_flStartLeadFactor = 1.0f; + m_flNextLeadFactor = 1.0f; + m_flStartLeadFactorTime = gpGlobals->curtime; + m_flNextLeadFactorTime = gpGlobals->curtime + 1.0f; + + m_yawCenter = GetLocalAngles().y; + m_yawCenterWorld = GetAbsAngles().y; + m_pitchCenter = GetLocalAngles().x; + m_pitchCenterWorld = GetAbsAngles().y; + m_vTargetPosition = vec3_origin; + + if ( IsActive() || (IsControllable() && !HasController()) ) + { + // Think to find controllers. + SetNextThink( gpGlobals->curtime + 1.0f ); + m_flNextControllerSearch = gpGlobals->curtime + 1.0f; + } + + UpdateMatrix(); + + m_sightOrigin = WorldBarrelPosition(); // Point at the end of the barrel + + if ( m_spread > MAX_FIRING_SPREADS ) + { + m_spread = 0; + } + + // No longer aim at target position if have one + m_spawnflags &= ~SF_TANK_AIM_AT_POS; + + if (m_spawnflags & SF_TANK_DAMAGE_KICK) + { + m_takedamage = DAMAGE_YES; + } + + // UNDONE: Do this? + //m_targetEntityName = m_target; + if ( GetSolid() != SOLID_NONE ) + { + CreateVPhysics(); + } + + // Setup squared min/max range. + m_flMinRange2 = m_minRange * m_minRange; + m_flMaxRange2 = m_maxRange * m_maxRange; + m_flIgnoreGraceUpto *= m_flIgnoreGraceUpto; + + m_flLastSawNonPlayer = 0; + + if( IsActive() ) + { + m_OnReadyToFire.FireOutput( this, this ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::Activate( void ) +{ + BaseClass::Activate(); + + // Necessary for save/load + if ( (m_iszBarrelAttachment != NULL_STRING) && (m_nBarrelAttachment == 0) ) + { + if ( GetParent() && GetParent()->GetBaseAnimating() ) + { + CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); + m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); + } + } +} + +bool CFuncTank::CreateVPhysics() +{ + VPhysicsInitShadow( false, false ); + return true; +} + + +void CFuncTank::Precache( void ) +{ + if ( m_iszSpriteSmoke != NULL_STRING ) + PrecacheModel( STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash != NULL_STRING ) + PrecacheModel( STRING(m_iszSpriteFlash) ); + + if ( m_soundStartRotate != NULL_STRING ) + PrecacheScriptSound( STRING(m_soundStartRotate) ); + if ( m_soundStopRotate != NULL_STRING ) + PrecacheScriptSound( STRING(m_soundStopRotate) ); + if ( m_soundLoopRotate != NULL_STRING ) + PrecacheScriptSound( STRING(m_soundLoopRotate) ); + + PrecacheScriptSound( "Func_Tank.BeginUse" ); + + // Precache the combine cannon + if ( m_iEffectHandling == EH_COMBINE_CANNON ) + { + PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" ); + } +} + +void CFuncTank::UpdateOnRemove( void ) +{ + if ( HasController() ) + { + StopControl(); + } + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Barrel position +//----------------------------------------------------------------------------- +void CFuncTank::UpdateMatrix( void ) +{ + m_parentMatrix.InitFromEntity( GetParent(), GetParentAttachment() ); +} + + +//----------------------------------------------------------------------------- +// Barrel position +//----------------------------------------------------------------------------- +Vector CFuncTank::WorldBarrelPosition( void ) +{ + if ( (m_nBarrelAttachment == 0) || !GetParent() ) + { + EntityMatrix tmp; + tmp.InitFromEntity( this ); + return tmp.LocalToWorld( m_barrelPos ); + } + + Vector vecOrigin; + QAngle vecAngles; + CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); + pAnim->GetAttachment( m_nBarrelAttachment, vecOrigin, vecAngles ); + return vecOrigin; +} + + +//----------------------------------------------------------------------------- +// Make the parent's pose parameters match the func_tank +//----------------------------------------------------------------------------- +void CFuncTank::PhysicsSimulate( void ) +{ + BaseClass::PhysicsSimulate(); + + if ( m_bUsePoseParameters && GetParent() ) + { + const QAngle &angles = GetLocalAngles(); + CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); + pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), angles.y ); + pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), angles.x ); + pAnim->StudioFrameAdvance(); + } +} + +//============================================================================= +// +// TANK CONTROLLING +// + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::OnControls( CBaseEntity *pTest ) +{ + // Is the tank controllable. + if ( !IsControllable() ) + return false; + + if ( !m_hControlVolume ) + { + // Find our control volume + if ( m_iszControlVolume != NULL_STRING ) + { + m_hControlVolume = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); + } + + if (( !m_hControlVolume ) && IsControllable() ) + { + Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); + return false; + } + } + + if ( m_hControlVolume->IsTouching( pTest ) ) + return true; + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::StartControl( CBaseCombatCharacter *pController ) +{ + // Check to see if we have a controller. + if ( HasController() && GetController() != pController ) + return false; + + // Team only or disabled? + if ( m_iszMaster != NULL_STRING ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + return false; + } + + // Set func_tank as manned by player/npc. + m_hController = pController; + if ( pController->IsPlayer() ) + { + m_spawnflags |= SF_TANK_PLAYER; + + CBasePlayer *pPlayer = static_cast( m_hController.Get() ); + pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; + } + else + { + m_spawnflags |= SF_TANK_NPC; + NPC_SetInRoute( false ); + } + + // Holster player/npc weapon + if ( m_hController->GetActiveWeapon() ) + { + m_hController->GetActiveWeapon()->Holster(); + } + + // Set the controller's position to be the use position. + m_vecControllerUsePos = m_hController->GetLocalOrigin(); + + EmitSound( "Func_Tank.BeginUse" ); + + SetNextThink( gpGlobals->curtime + 0.1f ); + + // Let the map maker know a controller has been found + if ( m_hController->IsPlayer() ) + { + m_OnGotPlayerController.FireOutput( this, this ); + } + else + { + m_OnGotController.FireOutput( this, this ); + } + + OnStartControlled(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// TODO: bring back the controllers current weapon +//----------------------------------------------------------------------------- +void CFuncTank::StopControl() +{ + // Do we have a controller? + if ( !m_hController ) + return; + + OnStopControlled(); + + // Arm player/npc weapon. + if ( m_hController->GetActiveWeapon() ) + { + m_hController->GetActiveWeapon()->Deploy(); + } + + if ( m_hController->IsPlayer() ) + { + CBasePlayer *pPlayer = static_cast( m_hController.Get() ); + pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; + } + + // Stop thinking. + SetNextThink( TICK_NEVER_THINK ); + + // Let the map maker know a controller has been lost. + if ( m_hController->IsPlayer() ) + { + m_OnLostPlayerController.FireOutput( this, this ); + } + else + { + m_OnLostController.FireOutput( this, this ); + } + + // Reset the func_tank as unmanned (player/npc). + if ( m_hController->IsPlayer() ) + { + m_spawnflags &= ~SF_TANK_PLAYER; + } + else + { + m_spawnflags &= ~SF_TANK_NPC; + } + + m_hController = NULL; + + // Set think, if the func_tank can think on its own. + if ( IsActive() || (IsControllable() && !HasController()) ) + { + // Delay the think to find controllers a bit +#ifdef FUNCTANK_AUTOUSE + m_flNextControllerSearch = gpGlobals->curtime + 1.0f; +#else + m_flNextControllerSearch = gpGlobals->curtime + 5.0f; +#endif//FUNCTANK_AUTOUSE + + SetNextThink( m_flNextControllerSearch ); + } + + SetLocalAngularVelocity( vec3_angle ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Called each frame by the player's ItemPostFrame +//----------------------------------------------------------------------------- + +// NVNT turret recoil +ConVar hap_turret_mag("hap_turret_mag", "5", 0); + +void CFuncTank::ControllerPostFrame( void ) +{ + // Make sure we have a contoller. + Assert( m_hController != NULL ); + + // Control the firing rate. + if ( gpGlobals->curtime < m_flNextAttack ) + return; + + if ( !IsPlayerManned() ) + return; + + CBasePlayer *pPlayer = static_cast( m_hController.Get() ); + if ( ( pPlayer->m_nButtons & IN_ATTACK ) == 0 ) + return; + + Vector forward; + AngleVectors( GetAbsAngles(), &forward ); + m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; + + if( HasSpawnFlags( SF_TANK_AIM_ASSISTANCE ) ) + { + // Trace out a hull and if it hits something, adjust the shot to hit that thing. + trace_t tr; + Vector start = WorldBarrelPosition(); + Vector dir = forward; + + UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO && (tr.m_pEnt->GetFlags() & FL_AIMTARGET) ) + { + forward = tr.m_pEnt->WorldSpaceCenter() - start; + VectorNormalize( forward ); + } + } + + Fire( bulletCount, WorldBarrelPosition(), forward, pPlayer, false ); + +#if defined( WIN32 ) && !defined( _X360 ) + // NVNT apply a punch on the player each time fired + HapticPunch(pPlayer,0,0,hap_turret_mag.GetFloat()); +#endif + // HACKHACK -- make some noise (that the AI can hear) + CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 ); + + if( m_iAmmoCount > -1 ) + { + if( !(m_iAmmoCount % 10) ) + { + Msg("Ammo Remaining: %d\n", m_iAmmoCount ); + } + + if( --m_iAmmoCount == 0 ) + { + // Kick the player off the gun, and make myself not usable. + m_spawnflags &= ~SF_TANK_CANCONTROL; + StopControl(); + return; + } + } + + SetNextAttack( gpGlobals->curtime + (1/m_fireRate) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFuncTank::HasController( void ) +{ + return (m_hController != NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : CBaseCombatCharacter +//----------------------------------------------------------------------------- +CBaseCombatCharacter *CFuncTank::GetController( void ) +{ + return m_hController; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::NPC_FindManPoint( Vector &vecPos ) +{ + if ( m_iszNPCManPoint != NULL_STRING ) + { + CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNPCManPoint ); + if ( pEntity ) + { + vecPos = pEntity->GetAbsOrigin(); + return true; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: The NPC manning this gun just saw a player for the first time since he left cover +//----------------------------------------------------------------------------- +void CFuncTank::NPC_JustSawPlayer( CBaseEntity *pTarget ) +{ + SetNextAttack( gpGlobals->curtime + m_flPlayerLockTimeBeforeFire ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::NPC_Fire( void ) +{ + // Control the firing rate. + if ( gpGlobals->curtime < m_flNextAttack ) + return; + + // Check for a valid npc controller. + if ( !m_hController ) + return; + + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + if ( !pNPC ) + return; + + // Setup for next round of firing. + if ( m_nBulletCount == 0 ) + { + m_nBulletCount = GetRandomBurst(); + m_fireTime = 1.0f; + } + + // m_fireLast looks like it is only needed for Active non-controlled func_tank. +// m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Vector vecBarrelEnd = WorldBarrelPosition(); + Vector vecForward; + AngleVectors( GetAbsAngles(), &vecForward ); + + if ( (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && pNPC->IsInSquad() ) + { + // Avoid shooting squadmates. + if ( pNPC->IsSquadmateInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) ) + { + return; + } + } + + if ( !HasSpawnFlags( SF_TANK_ALLOW_PLAYER_HITS ) && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) ) + { + // Avoid shooting player. + if ( pNPC->PlayerInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) ) + { + return; + } + } + + bool bIgnoreSpread = false; + + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if ( HasSpawnFlags( SF_TANK_HACKPLAYERHIT ) && pEnemy && pEnemy->IsPlayer() ) + { + // Every third shot should be fired directly at the player + if ( m_nBulletCount%2 == 0 ) + { + Vector vecBodyTarget = pEnemy->BodyTarget( vecBarrelEnd, false ); + vecForward = (vecBodyTarget - vecBarrelEnd); + VectorNormalize( vecForward ); + bIgnoreSpread = true; + } + } + + // Fire the bullet(s). + Fire( 1, vecBarrelEnd, vecForward, m_hController, bIgnoreSpread ); + --m_nBulletCount; + + // Check ammo counts and dismount when empty. + if( m_iAmmoCount > -1 ) + { + if( --m_iAmmoCount == 0 ) + { + // Disable the func_tank. + m_spawnflags &= ~SF_TANK_CANCONTROL; + + // Remove the npc. + StopControl(); + return; + } + } + + float flFireTime = GetRandomFireTime(); + if ( m_nBulletCount != 0 ) + { + m_fireTime -= flFireTime; + SetNextAttack( gpGlobals->curtime + flFireTime ); + } + else + { + SetNextAttack( gpGlobals->curtime + m_fireTime ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::NPC_HasEnemy( void ) +{ + if ( !IsNPCManned() ) + return false; + + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + Assert( pNPC ); + + return ( pNPC->GetEnemy() != NULL ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::NPC_InterruptRoute( void ) +{ + if ( !m_hController ) + return; + + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + if ( !pNPC ) + return; + + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) ) + { + pBehavior->SetFuncTank( NULL ); + } + + // Reset the npc controller. + m_hController = NULL; + + // No NPC's in route. + NPC_SetInRoute( false ); + + // Delay the think to find controllers a bit + m_flNextControllerSearch = gpGlobals->curtime + 5.0f; + + if ( !HasController() ) + { + // Start thinking to find controllers again + SetNextThink( m_flNextControllerSearch ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::NPC_InterruptController( void ) +{ + // If we don't have a controller - then the gun should be free. + if ( !m_hController ) + return true; + + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + if ( !pNPC || !pNPC->IsPlayerAlly() ) + return false; + + CAI_FuncTankBehavior *pBehavior; + if ( pNPC->GetBehavior( &pBehavior ) ) + { + pBehavior->Dismount(); + } + + m_hController = NULL; + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CFuncTank::GetRandomFireTime( void ) +{ + Assert( m_fireRate != 0 ); + float flOOFireRate = 1.0f / m_fireRate; + float flOOFireRateBy2 = flOOFireRate * 0.5f; + float flOOFireRateBy4 = flOOFireRate * 0.25f; + return random->RandomFloat( flOOFireRateBy4, flOOFireRateBy2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CFuncTank::GetRandomBurst( void ) +{ + return random->RandomInt( m_fireRate-2, m_fireRate+2 ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pActivator - +// *pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !IsControllable() ) + return; + + // player controlled turret + CBasePlayer *pPlayer = ToBasePlayer( pActivator ); + if ( !pPlayer ) + return; + + if ( value == 2 && useType == USE_SET ) + { + ControllerPostFrame(); + } + else if ( m_hController != pPlayer && useType != USE_OFF ) + { + // The player must be within the func_tank controls + if ( !m_hControlVolume ) + { + // Find our control volume + if ( m_iszControlVolume != NULL_STRING ) + { + m_hControlVolume = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); + } + + if (( !m_hControlVolume ) && IsControllable() ) + { + Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); + return; + } + } + + if ( !m_hControlVolume->IsTouching( pPlayer ) ) + return; + + // Interrupt any npc in route (ally or not). + if ( NPC_InRoute() ) + { + // Interrupt the npc's route. + NPC_InterruptRoute(); + } + + // Interrupt NPC - if possible (they must be allies). + if ( IsNPCControllable() && HasController() ) + { + if ( !NPC_InterruptController() ) + return; + } + + pPlayer->SetUseEntity( this ); + StartControl( pPlayer ); + } + else + { + StopControl(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : range - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CFuncTank::InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( (m_maxRange > 0) && (range > m_maxRange) ) + return FALSE; + + return TRUE; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::InRange2( float flRange2 ) +{ + if ( flRange2 < m_flMinRange2 ) + return false; + + if ( ( m_flMaxRange2 > 0.0f ) && ( flRange2 > m_flMaxRange2 ) ) + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::Think( void ) +{ + FuncTankPreThink(); + + m_hFuncTankTarget = NULL; + + // Look for a new controller? + if ( IsControllable() && !HasController() && (m_flNextControllerSearch <= gpGlobals->curtime) ) + { + if ( m_bShouldFindNPCs && gpGlobals->curtime > 5.0f ) + { + // Check for in route and timer. + if ( !NPC_InRoute() ) + { + NPC_FindController(); + } + } + +#ifdef FUNCTANK_AUTOUSE + CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); + bool bThinkFast = false; + + if( pPlayer ) + { + if ( !m_hControlVolume ) + { + // Find our control volume + if ( m_iszControlVolume != NULL_STRING ) + { + m_hControlVolume = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); + } + + if (( !m_hControlVolume ) && IsControllable() ) + { + Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); + return; + } + } + + if ( m_hControlVolume ) + { + if( m_hControlVolume->IsTouching( pPlayer ) && pPlayer->FInViewCone(WorldSpaceCenter()) ) + { + // If my control volume is touching a player that's facing the mounted gun, automatically use the gun. + // !!!BUGBUG - this only works in cases where the player can see the gun whilst standing in the control + // volume. (This works just fine for all func_tanks mounted on combine walls and small barriers) + variant_t emptyVariant; + AcceptInput( "Use", pPlayer, pPlayer, emptyVariant, USE_TOGGLE ); + } + else + { + // If the player is nearby, think faster for snappier response to XBox auto mounting + float flDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); + + if( flDistSqr <= Square(360) ) + { + bThinkFast = true; + } + } + } + } + + // Keep thinking, in case they turn NPC finding back on + if ( !HasController() ) + { + if( bThinkFast ) + { + SetNextThink( gpGlobals->curtime + 0.1f ); + } + else + { + SetNextThink( gpGlobals->curtime + 2.0f ); + } + } + + if( bThinkFast ) + { + m_flNextControllerSearch = gpGlobals->curtime + 0.1f; + } + else + { + m_flNextControllerSearch = gpGlobals->curtime + 2.0f; + } +#else + // Keep thinking, in case they turn NPC finding back on + if ( !HasController() ) + { + SetNextThink( gpGlobals->curtime + 2.0f ); + } + + m_flNextControllerSearch = gpGlobals->curtime + 2.0f; +#endif//FUNCTANK_AUTOUSE + } + + // refresh the matrix + UpdateMatrix(); + + SetLocalAngularVelocity( vec3_angle ); + TrackTarget(); + + if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 ) + { + StartRotSound(); + } + else + { + StopRotSound(); + } + + FuncTankPostThink(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Aim the offset barrel at a position in parent space +// Input : parentTarget - the position of the target in parent space +// Output : Vector - angles in local space +//----------------------------------------------------------------------------- +QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget ) +{ + Vector target = parentTarget - GetLocalOrigin(); + float quadTarget = target.LengthSqr(); + float quadTargetXY = target.x*target.x + target.y*target.y; + + // Target is too close! Can't aim at it + if ( quadTarget <= m_barrelPos.LengthSqr() ) + { + return GetLocalAngles(); + } + else + { + // We're trying to aim the offset barrel at an arbitrary point. + // To calculate this, I think of the target as being on a sphere with + // it's center at the origin of the gun. + // The rotation we need is the opposite of the rotation that moves the target + // along the surface of that sphere to intersect with the gun's shooting direction + // To calculate that rotation, we simply calculate the intersection of the ray + // coming out of the barrel with the target sphere (that's the new target position) + // and use atan2() to get angles + + // angles from target pos to center + float targetToCenterYaw = atan2( target.y, target.x ); + float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) ); + + float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) ); + float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) ); + return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Aim the tank at the player crosshair +//----------------------------------------------------------------------------- +void CFuncTank::CalcPlayerCrosshairTarget( Vector *pVecTarget ) +{ + // Get the player. + CBasePlayer *pPlayer = static_cast( m_hController.Get() ); + + // Tank aims at player's crosshair. + Vector vecStart, vecDir; + trace_t tr; + + vecStart = pPlayer->EyePosition(); + + if ( !IsX360() ) + { + vecDir = pPlayer->EyeDirection3D(); + } + else + { + // Use autoaim as the eye dir. + vecDir = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); + } + + // Make sure to start the trace outside of the player's bbox! + UTIL_TraceLine( vecStart + vecDir * 24, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr ); + + *pVecTarget = tr.endpos; +} + +//----------------------------------------------------------------------------- +// Aim the tank at the player crosshair +//----------------------------------------------------------------------------- +void CFuncTank::AimBarrelAtPlayerCrosshair( QAngle *pAngles ) +{ + Vector vecTarget; + CalcPlayerCrosshairTarget( &vecTarget ); + *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) ); +} + + +//----------------------------------------------------------------------------- +// Aim the tank at the NPC's enemy +//----------------------------------------------------------------------------- +void CFuncTank::CalcNPCEnemyTarget( Vector *pVecTarget ) +{ + Vector vecTarget; + CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); + + // Aim the barrel at the npc's enemy, or where the npc is looking. + CBaseEntity *pEnemy = pNPC->GetEnemy(); + if ( pEnemy ) + { + // Clear the idle target + *pVecTarget = pEnemy->BodyTarget( GetAbsOrigin(), false ); + m_vecNPCIdleTarget = *pVecTarget; + } + else + { + if ( m_vecNPCIdleTarget != vec3_origin ) + { + *pVecTarget = m_vecNPCIdleTarget; + } + else + { + Vector vecForward; + QAngle angCenter( 0, m_yawCenterWorld, 0 ); + AngleVectors( angCenter, &vecForward ); + trace_t tr; + Vector vecBarrel = GetAbsOrigin() + m_barrelPos; + UTIL_TraceLine( vecBarrel, vecBarrel + vecForward * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + *pVecTarget = tr.endpos; + } + } +} + + +//----------------------------------------------------------------------------- +// Aim the tank at the NPC's enemy +//----------------------------------------------------------------------------- +void CFuncTank::AimBarrelAtNPCEnemy( QAngle *pAngles ) +{ + Vector vecTarget; + CalcNPCEnemyTarget( &vecTarget ); + *pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) ); +} + +//----------------------------------------------------------------------------- +// Returns true if the desired angles are out of range +//----------------------------------------------------------------------------- +bool CFuncTank::RotateTankToAngles( const QAngle &angles, float *pDistX, float *pDistY ) +{ + bool bClamped = false; + + // Force the angles to be relative to the center position + float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter ); + float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter ); + + float flActualYaw = m_yawCenter + offsetY; + float flActualPitch = m_pitchCenter + offsetX; + + if ( ( fabs( offsetY ) > m_yawRange + m_yawTolerance ) || + ( fabs( offsetX ) > m_pitchRange + m_pitchTolerance ) ) + { + // Limit against range in x + flActualYaw = clamp( flActualYaw, m_yawCenter - m_yawRange, m_yawCenter + m_yawRange ); + flActualPitch = clamp( flActualPitch, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange ); + + bClamped = true; + } + + // Get at the angular vel + QAngle vecAngVel = GetLocalAngularVelocity(); + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( flActualYaw, GetLocalAngles().y ); + vecAngVel.y = distY * 10; + vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate ); + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( flActualPitch, GetLocalAngles().x ); + vecAngVel.x = distX * 10; + vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate ); + + // How exciting! We're done + SetLocalAngularVelocity( vecAngVel ); + + if ( pDistX && pDistY ) + { + *pDistX = distX; + *pDistY = distY; + } + + return bClamped; +} + + +//----------------------------------------------------------------------------- +// We lost our target! +//----------------------------------------------------------------------------- +void CFuncTank::LostTarget( void ) +{ + if (m_fireLast != 0) + { + m_OnLoseTarget.FireOutput(this, this); + m_fireLast = 0; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ) +{ + Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false ); + float flShotSpeed = GetShotSpeed(); + if ( flShotSpeed == 0 ) + { + *pLeadPosition = vecTarget; + return; + } + + Vector vecVelocity = pTarget->GetSmoothedVelocity(); + vecVelocity.z = 0.0f; + float flTargetSpeed = VectorNormalize( vecVelocity ); + + // Guesstimate... + if ( m_flNextLeadFactorTime < gpGlobals->curtime ) + { + m_flStartLeadFactor = m_flNextLeadFactor; + m_flStartLeadFactorTime = gpGlobals->curtime; + m_flNextLeadFactor = random->RandomFloat( 0.8f, 1.3f ); + m_flNextLeadFactorTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f ); + } + + float flFactor = (gpGlobals->curtime - m_flStartLeadFactorTime) / (m_flNextLeadFactorTime - m_flStartLeadFactorTime); + float flLeadFactor = SimpleSplineRemapVal( flFactor, 0.0f, 1.0f, m_flStartLeadFactor, m_flNextLeadFactor ); + flTargetSpeed *= flLeadFactor; + + Vector vecDelta; + VectorSubtract( vecShootPosition, vecTarget, vecDelta ); + float flTargetToShooter = VectorNormalize( vecDelta ); + float flCosTheta = DotProduct( vecDelta, vecVelocity ); + + // Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta + // where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time + // x = flTargetSpeed * predicted time + // y = flTargetToShooter + // solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a + float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed; + float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed; + float c = flTargetToShooter * flTargetToShooter; + + float flDiscrim = b*b - 4*a*c; + if (flDiscrim < 0) + { + *pLeadPosition = vecTarget; + return; + } + + flDiscrim = sqrt(flDiscrim); + float t = (-b + flDiscrim) / (2.0f * a); + float t2 = (-b - flDiscrim) / (2.0f * a); + if ( t < t2 ) + { + t = t2; + } + + if ( t <= 0.0f ) + { + *pLeadPosition = vecTarget; + return; + } + + VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::AimFuncTankAtTarget( void ) +{ + // Get world target position + CBaseEntity *pTarget = NULL; + trace_t tr; + QAngle angles; + bool bUpdateTime = false; + + CBaseEntity *pTargetVehicle = NULL; + Vector barrelEnd = WorldBarrelPosition(); + Vector worldTargetPosition; + if (m_spawnflags & SF_TANK_AIM_AT_POS) + { + worldTargetPosition = m_vTargetPosition; + } + else + { + CBaseEntity *pEntity = (CBaseEntity *)m_hTarget; + if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) ) + { + if( m_targetEntityName != NULL_STRING ) + { + m_hTarget = FindTarget( m_targetEntityName, NULL ); + } + + LostTarget(); + return; + } + + pTarget = pEntity; + + // Calculate angle needed to aim at target + worldTargetPosition = pEntity->EyePosition(); + if ( pEntity->IsPlayer() ) + { + CBasePlayer *pPlayer = assert_cast(pEntity); + pTargetVehicle = pPlayer->GetVehicleEntity(); + if ( pTargetVehicle ) + { + worldTargetPosition = pTargetVehicle->BodyTarget( GetAbsOrigin(), false ); + } + } + } + + float range2 = worldTargetPosition.DistToSqr( barrelEnd ); + if ( !InRange2( range2 ) ) + { + if ( m_hTarget ) + { + m_hTarget = NULL; + LostTarget(); + } + return; + } + + Vector vecAimOrigin = m_sightOrigin; + if (m_spawnflags & SF_TANK_AIM_AT_POS) + { + bUpdateTime = true; + m_sightOrigin = m_vTargetPosition; + vecAimOrigin = m_sightOrigin; + } + else + { + if ( m_spawnflags & SF_TANK_LINEOFSIGHT ) + { + AI_TraceLOS( barrelEnd, worldTargetPosition, this, &tr ); + } + else + { + tr.fraction = 1.0f; + tr.m_pEnt = pTarget; + } + + // No line of sight, don't track + if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget || (pTargetVehicle && (tr.m_pEnt == pTargetVehicle)) ) + { + if ( InRange2( range2 ) && pTarget && pTarget->IsAlive() ) + { + bUpdateTime = true; + + // Sight position is BodyTarget with no noise (so gun doesn't bob up and down) + CBaseEntity *pInstance = pTargetVehicle ? pTargetVehicle : pTarget; + m_hFuncTankTarget = pInstance; + + m_sightOrigin = pInstance->BodyTarget( GetAbsOrigin(), false ); + if ( m_bPerformLeading ) + { + ComputeLeadingPosition( barrelEnd, pInstance, &vecAimOrigin ); + } + else + { + vecAimOrigin = m_sightOrigin; + } + } + } + } + + // Convert targetPosition to parent + Vector vecLocalOrigin = m_parentMatrix.WorldToLocal( vecAimOrigin ); + angles = AimBarrelAt( vecLocalOrigin ); + + // FIXME: These need to be the clamped angles + float distX, distY; + bool bClamped = RotateTankToAngles( angles, &distX, &distY ); + if ( bClamped ) + { + bUpdateTime = false; + } + + if ( bUpdateTime ) + { + if( (gpGlobals->curtime - m_lastSightTime >= 1.0) && (gpGlobals->curtime > m_flNextAttack) ) + { + // Enemy was hidden for a while, and I COULD fire right now. Instead, tack a delay on. + m_flNextAttack = gpGlobals->curtime + 0.5; + } + + m_lastSightTime = gpGlobals->curtime; + m_persist2burst = 0; + } + + SetMoveDoneTime( 0.1 ); + + if ( CanFire() && ( ( (fabs(distX) <= m_pitchTolerance) && (fabs(distY) <= m_yawTolerance) ) || (m_spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + bool fire = false; + Vector forward; + AngleVectors( GetLocalAngles(), &forward ); + forward = m_parentMatrix.ApplyRotation( forward ); + + if ( m_spawnflags & SF_TANK_LINEOFSIGHT ) + { + AI_TraceLine( barrelEnd, pTarget->WorldSpaceCenter(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction == 1.0f || (tr.m_pEnt && tr.m_pEnt == pTarget) ) + { + fire = true; + } + } + else + { + fire = true; + } + + if ( fire ) + { + if (m_fireLast == 0) + { + m_OnAquireTarget.FireOutput(this, this); + } + FiringSequence( barrelEnd, forward, this ); + } + else + { + LostTarget(); + } + } + else + { + LostTarget(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::TrackTarget( void ) +{ + QAngle angles; + + if( !m_bReadyToFire && m_flNextAttack <= gpGlobals->curtime ) + { + m_OnReadyToFire.FireOutput( this, this ); + m_bReadyToFire = true; + } + + if ( IsPlayerManned() ) + { + AimBarrelAtPlayerCrosshair( &angles ); + RotateTankToAngles( angles ); + SetNextThink( gpGlobals->curtime + 0.05f ); + SetMoveDoneTime( 0.1 ); + return; + } + + if ( IsNPCManned() ) + { + AimBarrelAtNPCEnemy( &angles ); + RotateTankToAngles( angles ); + SetNextThink( gpGlobals->curtime + 0.05f ); + SetMoveDoneTime( 0.1 ); + return; + } + + if ( !IsActive() ) + { + // If we're not active, but we're controllable, we need to keep thinking + if ( IsControllable() && !HasController() ) + { + // Think to find controllers. + SetNextThink( m_flNextControllerSearch ); + } + return; + } + + // Clean room for unnecessarily complicated old code + SetNextThink( gpGlobals->curtime + 0.1f ); + AimFuncTankAtTarget(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Start of firing sequence. By default, just fire now. +// Input : &barrelEnd - +// &forward - +// *pAttacker - +//----------------------------------------------------------------------------- +void CFuncTank::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) +{ + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; + + if ( bulletCount > 0 ) + { + // NOTE: Set m_fireLast first so that Fire can adjust it + m_fireLast = gpGlobals->curtime; + Fire( bulletCount, barrelEnd, forward, pAttacker, false ); + } + } + else + { + m_fireLast = gpGlobals->curtime; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTank::DoMuzzleFlash( void ) +{ + // If we're parented to something, make it play the muzzleflash + if ( m_bUsePoseParameters && GetParent() ) + { + CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); + pAnim->DoMuzzleFlash(); + + // Do the AR2 muzzle flash + if ( m_iEffectHandling == EH_COMBINE_CANNON ) + { + CEffectData data; + data.m_nAttachmentIndex = m_nBarrelAttachment; + data.m_nEntIndex = pAnim->entindex(); + + // FIXME: Create a custom entry here! + DispatchEffect( "ChopperMuzzleFlash", data ); + } + else + { + CEffectData data; + data.m_nEntIndex = pAnim->entindex(); + data.m_nAttachmentIndex = m_nBarrelAttachment; + data.m_flScale = 1.0f; + data.m_fFlags = MUZZLEFLASH_COMBINE; + + DispatchEffect( "MuzzleFlash", data ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CFuncTank::GetTracerType( void ) +{ + switch( m_iEffectHandling ) + { + case EH_AR2: + return "AR2Tracer"; + + case EH_COMBINE_CANNON: + return "HelicopterTracer"; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Fire targets and spawn sprites. +// Input : bulletCount - +// barrelEnd - +// forward - +// pAttacker - +//----------------------------------------------------------------------------- +void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + // If we have a specific effect handler, apply it's effects + if ( m_iEffectHandling == EH_AR2 ) + { + DoMuzzleFlash(); + + // Play the AR2 sound + EmitSound( "Weapon_functank.Single" ); + } + else if ( m_iEffectHandling == EH_COMBINE_CANNON ) + { + DoMuzzleFlash(); + + // Play the cannon sound + EmitSound( "NPC_Combine_Cannon.FireBullet" ); + } + else + { + if ( m_iszSpriteSmoke != NULL_STRING ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( random->RandomFloat( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, m_clrRender->r, m_clrRender->g, m_clrRender->b, 255, kRenderFxNone ); + + Vector vecVelocity( 0, 0, random->RandomFloat(40, 80) ); + pSprite->SetAbsVelocity( vecVelocity ); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash != NULL_STRING ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 5 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + } + } + + if( pAttacker && pAttacker->IsPlayer() ) + { + if ( IsX360() ) + { + UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE ); + } + else + { + CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON ); + } + } + + + m_OnFire.FireOutput(this, this); + m_bReadyToFire = false; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr ) +{ + Vector forward, right, up; + + AngleVectors( GetAbsAngles(), &forward, &right, &up ); + // get circular gaussian spread + float x, y, z; + do { + x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + Vector vecDir = vecForward + + x * vecSpread.x * right + + y * vecSpread.y * up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * MAX_TRACE_LENGTH; + UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( m_spawnflags & SF_TANK_SOUNDON ) + return; + m_spawnflags |= SF_TANK_SOUNDON; + + if ( m_soundLoopRotate != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + filter.MakeReliable(); + + EmitSound_t ep; + ep.m_nChannel = CHAN_STATIC; + ep.m_pSoundName = (char*)STRING(m_soundLoopRotate); + ep.m_flVolume = 0.85; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + + if ( m_soundStartRotate != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_BODY; + ep.m_pSoundName = (char*)STRING(m_soundStartRotate); + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( m_spawnflags & SF_TANK_SOUNDON ) + { + if ( m_soundLoopRotate != NULL_STRING ) + { + StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) ); + } + if ( m_soundStopRotate != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_BODY; + ep.m_pSoundName = (char*)STRING(m_soundStopRotate); + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NORM; + + EmitSound( filter, entindex(), ep ); + } + } + m_spawnflags &= ~SF_TANK_SOUNDON; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CFuncTank::IsEntityInViewCone( CBaseEntity *pEntity ) +{ + // First check to see if the enemy is in range. + Vector vecBarrelEnd = WorldBarrelPosition(); + float flRange2 = ( pEntity->GetAbsOrigin() - vecBarrelEnd ).LengthSqr(); + + if( !(GetSpawnFlags() & SF_TANK_IGNORE_RANGE_IN_VIEWCONE) ) + { + if ( !InRange2( flRange2 ) ) + return false; + } + + // If we're trying to shoot at a player, and we've seen a non-player recently, check the grace period + if ( m_flPlayerGracePeriod && pEntity->IsPlayer() && (gpGlobals->curtime - m_flLastSawNonPlayer) < m_flPlayerGracePeriod ) + { + // Grace period is ignored under a certain distance + if ( flRange2 > m_flIgnoreGraceUpto ) + return false; + } + + // Check to see if the entity center lies within the yaw and pitch constraints. + // This isn't horribly accurate, but should do for now. + QAngle angGun; + angGun = AimBarrelAt( m_parentMatrix.WorldToLocal( pEntity->GetAbsOrigin() ) ); + + // Force the angles to be relative to the center position + float flOffsetY = UTIL_AngleDistance( angGun.y, m_yawCenter ); + float flOffsetX = UTIL_AngleDistance( angGun.x, m_pitchCenter ); + angGun.y = m_yawCenter + flOffsetY; + angGun.x = m_pitchCenter + flOffsetX; + + if ( ( fabs( flOffsetY ) > m_yawRange + m_yawTolerance ) || ( fabs( flOffsetX ) > m_pitchRange + m_pitchTolerance ) ) + return false; + + // Remember the last time we saw a non-player + if ( !pEntity->IsPlayer() ) + { + m_flLastSawNonPlayer = gpGlobals->curtime; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Return true if this func tank can see the enemy +//----------------------------------------------------------------------------- +bool CFuncTank::HasLOSTo( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return false; + + // Get the barrel position + Vector vecBarrelEnd = WorldBarrelPosition(); + Vector vecTarget = pEntity->BodyTarget( GetAbsOrigin(), false ); + trace_t tr; + + // Ignore the func_tank and any prop it's parented to + CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); + + // UNDONE: Should this hit BLOCKLOS brushes? + AI_TraceLine( vecBarrelEnd, vecTarget, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr ); + + CBaseEntity *pHitEntity = tr.m_pEnt; + + // Is entity in a vehicle? if so, verify vehicle is target and return if so (so npc shoots at vehicle) + CBaseCombatCharacter *pCCEntity = pEntity->MyCombatCharacterPointer(); + if ( pCCEntity != NULL && pCCEntity->IsInAVehicle() ) + { + // Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is + // Also, check to see if the owner of the entity is the vehicle, in which case it's valid too. + // This catches vehicles that use bone followers. + CBaseEntity *pVehicle = pCCEntity->GetVehicle()->GetVehicleEnt(); + if ( pHitEntity == pVehicle || ( pHitEntity != NULL && pHitEntity->GetOwnerEntity() == pVehicle ) ) + return true; + } + + return ( tr.fraction == 1.0 || tr.m_pEnt == pEntity ); +} + +// ############################################################################# +// CFuncTankGun +// ############################################################################# +class CFuncTankGun : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankGun, CFuncTank ); + + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + int i; + + FireBulletsInfo_t info; + info.m_iShots = 1; + info.m_vecSrc = barrelEnd; + info.m_vecDirShooting = forward; + if ( bIgnoreSpread ) + { + info.m_vecSpread = gTankSpread[0]; + } + else + { + info.m_vecSpread = gTankSpread[m_spread]; + } + + info.m_flDistance = MAX_TRACE_LENGTH; + info.m_iTracerFreq = 1; + info.m_flDamage = m_iBulletDamage; + info.m_iPlayerDamage = m_iBulletDamageVsPlayer; + info.m_pAttacker = pAttacker; + info.m_pAdditionalIgnoreEnt = GetParent(); + +#ifdef HL2_EPISODIC + if ( m_iAmmoType != -1 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + info.m_iAmmoType = m_iAmmoType; + FireBullets( info ); + } + } +#else + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_SMALL: + info.m_iAmmoType = m_iSmallAmmoType; + FireBullets( info ); + break; + + case TANK_BULLET_MEDIUM: + info.m_iAmmoType = m_iMediumAmmoType; + FireBullets( info ); + break; + + case TANK_BULLET_LARGE: + info.m_iAmmoType = m_iLargeAmmoType; + FireBullets( info ); + break; + + default: + case TANK_BULLET_NONE: + break; + } + } +#endif // HL2_EPISODIC + + CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker, bIgnoreSpread ); +} + +// ############################################################################# +// CFuncTankPulseLaser +// ############################################################################# +class CFuncTankPulseLaser : public CFuncTankGun +{ +public: + DECLARE_CLASS( CFuncTankPulseLaser, CFuncTankGun ); + DECLARE_DATADESC(); + + void Precache(); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + + float m_flPulseSpeed; + float m_flPulseWidth; + color32 m_flPulseColor; + float m_flPulseLife; + float m_flPulseLag; + string_t m_sPulseFireSound; +}; +LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser ); + +BEGIN_DATADESC( CFuncTankPulseLaser ) + + DEFINE_KEYFIELD( m_flPulseSpeed, FIELD_FLOAT, "PulseSpeed" ), + DEFINE_KEYFIELD( m_flPulseWidth, FIELD_FLOAT, "PulseWidth" ), + DEFINE_KEYFIELD( m_flPulseColor, FIELD_COLOR32, "PulseColor" ), + DEFINE_KEYFIELD( m_flPulseLife, FIELD_FLOAT, "PulseLife" ), + DEFINE_KEYFIELD( m_flPulseLag, FIELD_FLOAT, "PulseLag" ), + DEFINE_KEYFIELD( m_sPulseFireSound, FIELD_SOUNDNAME, "PulseFireSound" ), + +END_DATADESC() + +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CFuncTankPulseLaser::Precache(void) +{ + UTIL_PrecacheOther( "grenade_beam" ); + + if ( m_sPulseFireSound != NULL_STRING ) + { + PrecacheScriptSound( STRING(m_sPulseFireSound) ); + } + BaseClass::Precache(); +} +//------------------------------------------------------------------------------ +// Purpose : +// Input : +// Output : +//------------------------------------------------------------------------------ +void CFuncTankPulseLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + // -------------------------------------------------- + // Get direction vectors for spread + // -------------------------------------------------- + Vector vecUp = Vector(0,0,1); + Vector vecRight; + CrossProduct ( vecForward, vecUp, vecRight ); + CrossProduct ( vecForward, -vecRight, vecUp ); + + for ( int i = 0; i < bulletCount; i++ ) + { + // get circular gaussian spread + float x, y, z; + do { + x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + + Vector vecDir = vecForward + x * gTankSpread[m_spread].x * vecRight + y * gTankSpread[m_spread].y * vecUp; + + CGrenadeBeam *pPulse = CGrenadeBeam::Create( pAttacker, barrelEnd); + pPulse->Format(m_flPulseColor, m_flPulseWidth); + pPulse->Shoot(vecDir,m_flPulseSpeed,m_flPulseLife,m_flPulseLag,m_iBulletDamage); + + if ( m_sPulseFireSound != NULL_STRING ) + { + CPASAttenuationFilter filter( this, 0.6f ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_WEAPON; + ep.m_pSoundName = (char*)STRING(m_sPulseFireSound); + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_85dB; + + EmitSound( filter, entindex(), ep ); + } + + } + CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker, bIgnoreSpread ); +} + +// ############################################################################# +// CFuncTankLaser +// ############################################################################# +class CFuncTankLaser : public CFuncTank +{ + DECLARE_CLASS( CFuncTankLaser, CFuncTank ); +public: + void Activate( void ); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + void Think( void ); + CEnvLaser *GetLaser( void ); + + DECLARE_DATADESC(); + +private: + CEnvLaser *m_pLaser; + float m_laserTime; + string_t m_iszLaserName; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +BEGIN_DATADESC( CFuncTankLaser ) + + DEFINE_KEYFIELD( m_iszLaserName, FIELD_STRING, "laserentity" ), + + DEFINE_FIELD( m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( m_laserTime, FIELD_TIME ), + +END_DATADESC() + + +void CFuncTankLaser::Activate( void ) +{ + BaseClass::Activate(); + + if ( !GetLaser() ) + { + UTIL_Remove(this); + Warning( "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } +} + + +CEnvLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + CBaseEntity *pLaser = gEntList.FindEntityByName( NULL, m_iszLaserName ); + while ( pLaser ) + { + // Found the landmark + if ( FClassnameIs( pLaser, "env_laser" ) ) + { + m_pLaser = (CEnvLaser *)pLaser; + break; + } + else + { + pLaser = gEntList.FindEntityByName( pLaser, m_iszLaserName ); + } + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->curtime > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + int i; + trace_t tr; + + if ( GetLaser() ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->SetLocalOrigin( barrelEnd ); + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->curtime; + m_pLaser->TurnOn(); + m_pLaser->SetFireTime( gpGlobals->curtime - 1.0 ); + m_pLaser->FireAtPoint( tr ); + m_pLaser->SetNextThink( TICK_NEVER_THINK ); + } + CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankRocket, CFuncTank ); + + void Precache( void ); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + virtual float GetShotSpeed() { return m_flRocketSpeed; } + +protected: + float m_flRocketSpeed; + + DECLARE_DATADESC(); +}; + +BEGIN_DATADESC( CFuncTankRocket ) + + DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_missile" ); + CFuncTank::Precache(); +} + +void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + CMissile *pRocket = (CMissile *) CBaseEntity::Create( "rpg_missile", barrelEnd, GetAbsAngles(), this ); + + pRocket->DumbFire(); + pRocket->SetNextThink( gpGlobals->curtime + 0.1f ); + pRocket->SetAbsVelocity( forward * m_flRocketSpeed ); + if ( GetController() && GetController()->IsPlayer() ) + { + pRocket->SetDamage( m_iBulletDamage ); + } + else + { + pRocket->SetDamage( m_iBulletDamageVsPlayer ); + } + + CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); +} + + +//----------------------------------------------------------------------------- +// Airboat gun +//----------------------------------------------------------------------------- +class CFuncTankAirboatGun : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankAirboatGun, CFuncTank ); + DECLARE_DATADESC(); + + void Precache( void ); + virtual void Spawn(); + virtual void Activate(); + virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + virtual void ControllerPostFrame(); + virtual void OnStopControlled(); + virtual const char *GetTracerType( void ); + virtual Vector WorldBarrelPosition( void ); + virtual void DoImpactEffect( trace_t &tr, int nDamageType ); + +private: + void CreateSounds(); + void DestroySounds(); + void DoMuzzleFlash( ); + void StartFiring(); + void StopFiring(); + + CSoundPatch *m_pGunFiringSound; + float m_flNextHeavyShotTime; + bool m_bIsFiring; + + string_t m_iszAirboatGunModel; + CHandle m_hAirboatGunModel; + int m_nGunBarrelAttachment; + float m_flLastImpactEffectTime; +}; + + +//----------------------------------------------------------------------------- +// Save/load: +//----------------------------------------------------------------------------- +BEGIN_DATADESC( CFuncTankAirboatGun ) + + DEFINE_SOUNDPATCH( m_pGunFiringSound ), + DEFINE_FIELD( m_flNextHeavyShotTime, FIELD_TIME ), + DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ), + DEFINE_KEYFIELD( m_iszAirboatGunModel, FIELD_STRING, "airboat_gun_model" ), +// DEFINE_FIELD( m_hAirboatGunModel, FIELD_EHANDLE ), +// DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ), + DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_tankairboatgun, CFuncTankAirboatGun ); + + +//----------------------------------------------------------------------------- +// Precache: +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::Precache( void ) +{ + BaseClass::Precache(); + PrecacheScriptSound( "Airboat.FireGunLoop" ); + PrecacheScriptSound( "Airboat.FireGunRevDown"); + CreateSounds(); +} + + +//----------------------------------------------------------------------------- +// Precache: +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::Spawn( void ) +{ + BaseClass::Spawn(); + m_flNextHeavyShotTime = 0.0f; + m_bIsFiring = false; + m_flLastImpactEffectTime = -1; +} + + +//----------------------------------------------------------------------------- +// Attachment indices +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::Activate() +{ + BaseClass::Activate(); + + if ( m_iszAirboatGunModel != NULL_STRING ) + { + m_hAirboatGunModel = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszAirboatGunModel ) ); + if ( m_hAirboatGunModel ) + { + m_nGunBarrelAttachment = m_hAirboatGunModel->LookupAttachment( "muzzle" ); + } + } +} + + +//----------------------------------------------------------------------------- +// Create/destroy looping sounds +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::CreateSounds() +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + CPASAttenuationFilter filter( this ); + if (!m_pGunFiringSound) + { + m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" ); + controller.Play( m_pGunFiringSound, 0, 100 ); + } +} + +void CFuncTankAirboatGun::DestroySounds() +{ + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + + controller.SoundDestroy( m_pGunFiringSound ); + m_pGunFiringSound = NULL; +} + + +//----------------------------------------------------------------------------- +// Stop Firing +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::StartFiring() +{ + if ( !m_bIsFiring ) + { + CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController(); + float flVolume = pController->SoundGetVolume( m_pGunFiringSound ); + pController->SoundChangeVolume( m_pGunFiringSound, 1.0f, 0.1f * (1.0f - flVolume) ); + m_bIsFiring = true; + } +} + +void CFuncTankAirboatGun::StopFiring() +{ + if ( m_bIsFiring ) + { + CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController(); + float flVolume = pController->SoundGetVolume( m_pGunFiringSound ); + pController->SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f * flVolume ); + EmitSound( "Airboat.FireGunRevDown" ); + m_bIsFiring = false; + } +} + + +//----------------------------------------------------------------------------- +// Maintains airboat gun sounds +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::ControllerPostFrame( void ) +{ + if ( IsPlayerManned() ) + { + CBasePlayer *pPlayer = static_cast( GetController() ); + if ( pPlayer->m_nButtons & IN_ATTACK ) + { + StartFiring(); + } + else + { + StopFiring(); + } + } + + BaseClass::ControllerPostFrame(); +} + + +//----------------------------------------------------------------------------- +// Stop controlled +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::OnStopControlled() +{ + StopFiring(); + BaseClass::OnStopControlled(); +} + + +//----------------------------------------------------------------------------- +// Barrel position +//----------------------------------------------------------------------------- +Vector CFuncTankAirboatGun::WorldBarrelPosition( void ) +{ + if ( !m_hAirboatGunModel || (m_nGunBarrelAttachment == 0) ) + { + return BaseClass::WorldBarrelPosition(); + } + + Vector vecOrigin; + m_hAirboatGunModel->GetAttachment( m_nGunBarrelAttachment, vecOrigin ); + return vecOrigin; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CFuncTankAirboatGun::GetTracerType( void ) +{ + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + return "AirboatGunHeavyTracer"; + + return "AirboatGunTracer"; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::DoMuzzleFlash( void ) +{ + if ( m_hAirboatGunModel && (m_nGunBarrelAttachment != 0) ) + { + CEffectData data; + data.m_nEntIndex = m_hAirboatGunModel->entindex(); + data.m_nAttachmentIndex = m_nGunBarrelAttachment; + data.m_flScale = 1.0f; + DispatchEffect( "AirboatMuzzleFlash", data ); + } +} + + +//----------------------------------------------------------------------------- +// Allows the shooter to change the impact effect of his bullets +//----------------------------------------------------------------------------- +void CFuncTankAirboatGun::DoImpactEffect( trace_t &tr, int nDamageType ) +{ + // The airboat spits out so much crap that we need to do cheaper versions + // of the impact effects. Also, we need to do less of them. + if ( m_flLastImpactEffectTime == gpGlobals->curtime ) + return; + + m_flLastImpactEffectTime = gpGlobals->curtime; + UTIL_ImpactTrace( &tr, nDamageType, "AirboatGunImpact" ); +} + + +//----------------------------------------------------------------------------- +// Fires bullets +//----------------------------------------------------------------------------- +#define AIRBOAT_GUN_HEAVY_SHOT_INTERVAL 0.2f + +void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + CAmmoDef *pAmmoDef = GetAmmoDef(); + int ammoType = pAmmoDef->Index( "AirboatGun" ); + + FireBulletsInfo_t info; + info.m_vecSrc = barrelEnd; + info.m_vecDirShooting = forward; + info.m_flDistance = 4096; + info.m_iAmmoType = ammoType; + + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + { + info.m_iShots = 1; + info.m_vecSpread = VECTOR_CONE_PRECALCULATED; + info.m_flDamageForceScale = 1000.0f; + } + else + { + info.m_iShots = 2; + info.m_vecSpread = VECTOR_CONE_5DEGREES; + } + + FireBullets( info ); + + DoMuzzleFlash(); + + // NOTE: This must occur after FireBullets + if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) + { + m_flNextHeavyShotTime = gpGlobals->curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL; + } +} + + +//----------------------------------------------------------------------------- +// APC Rocket +//----------------------------------------------------------------------------- +#define DEATH_VOLLEY_MISSILE_COUNT 10 +#define DEATH_VOLLEY_MIN_FIRE_RATE 3 +#define DEATH_VOLLEY_MAX_FIRE_RATE 6 + +class CFuncTankAPCRocket : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankAPCRocket, CFuncTank ); + + void Precache( void ); + virtual void Spawn(); + virtual void UpdateOnRemove(); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + virtual void Think(); + virtual float GetShotSpeed() { return m_flRocketSpeed; } + +protected: + void InputDeathVolley( inputdata_t &inputdata ); + void FireDying( const Vector &barrelEnd ); + + EHANDLE m_hLaserDot; + float m_flRocketSpeed; + int m_nSide; + int m_nBurstCount; + bool m_bDying; + + DECLARE_DATADESC(); +}; + + +BEGIN_DATADESC( CFuncTankAPCRocket ) + + DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ), + DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ), + DEFINE_FIELD( m_nSide, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_nBurstCount, FIELD_INTEGER, "burstcount" ), + DEFINE_FIELD( m_bDying, FIELD_BOOLEAN ), + + DEFINE_INPUTFUNC( FIELD_VOID, "DeathVolley", InputDeathVolley ), + +END_DATADESC() + +LINK_ENTITY_TO_CLASS( func_tankapcrocket, CFuncTankAPCRocket ); + +void CFuncTankAPCRocket::Precache( void ) +{ + UTIL_PrecacheOther( "apc_missile" ); + + PrecacheScriptSound( "PropAPC.FireCannon" ); + + CFuncTank::Precache(); +} + +void CFuncTankAPCRocket::Spawn( void ) +{ + BaseClass::Spawn(); + AddEffects( EF_NODRAW ); + m_nSide = 0; + m_bDying = false; + m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false ); + m_nBulletCount = m_nBurstCount; + SetSolid( SOLID_NONE ); + SetLocalVelocity( vec3_origin ); +} + +void CFuncTankAPCRocket::UpdateOnRemove( void ) +{ + if ( m_hLaserDot ) + { + UTIL_Remove( m_hLaserDot ); + m_hLaserDot = NULL; + } + BaseClass::UpdateOnRemove(); +} + +void CFuncTankAPCRocket::FireDying( const Vector &barrelEnd ) +{ + Vector vecDir; + vecDir.Random( -1.0f, 1.0f ); + if ( vecDir.z < 0.0f ) + { + vecDir.z *= -1.0f; + } + + VectorNormalize( vecDir ); + + Vector vecVelocity; + VectorMultiply( vecDir, m_flRocketSpeed * random->RandomFloat( 0.75f, 1.25f ), vecVelocity ); + + QAngle angles; + VectorAngles( vecDir, angles ); + + CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this ); + float flDeathTime = random->RandomFloat( 0.3f, 0.5f ); + if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f ) + { + pRocket->ExplodeDelay( flDeathTime ); + } + else + { + pRocket->AugerDelay( flDeathTime ); + } + + // Make erratic firing + m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE ); + if ( --m_nBulletCount <= 0 ) + { + UTIL_Remove( this ); + } +} + +void CFuncTankAPCRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 }; + + Vector vecDir; + CrossProduct( Vector( 0, 0, 1 ), forward, vecDir ); + vecDir.z = 1.0f; + vecDir.x *= s_pSide[m_nSide]; + vecDir.y *= s_pSide[m_nSide]; + if ( ++m_nSide >= 6 ) + { + m_nSide = 0; + } + + VectorNormalize( vecDir ); + + Vector vecVelocity; + VectorMultiply( vecDir, m_flRocketSpeed, vecVelocity ); + + QAngle angles; + VectorAngles( vecDir, angles ); + + CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this ); + pRocket->IgniteDelay(); + + CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); + + if ( --m_nBulletCount <= 0 ) + { + m_nBulletCount = m_nBurstCount; + + // This will cause it to wait for a little while before shooting + m_fireLast += random->RandomFloat( 2.0f, 3.0f ); + } + EmitSound( "PropAPC.FireCannon" ); +} + +void CFuncTankAPCRocket::Think() +{ + // Inert if we're carried... + if ( GetMoveParent() && GetMoveParent()->GetMoveParent() ) + { + SetNextThink( gpGlobals->curtime + 0.5f ); + return; + } + + BaseClass::Think(); + m_hLaserDot->SetAbsOrigin( m_sightOrigin ); + SetLaserDotTarget( m_hLaserDot, m_hFuncTankTarget ); + EnableLaserDot( m_hLaserDot, m_hFuncTankTarget != NULL ); + + if ( m_bDying ) + { + FireDying( WorldBarrelPosition() ); + return; + } +} + + +void CFuncTankAPCRocket::InputDeathVolley( inputdata_t &inputdata ) +{ + if ( !m_bDying ) + { + m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE ); + SetNextAttack( gpGlobals->curtime + (1.0f / m_fireRate ) ); + m_nBulletCount = DEATH_VOLLEY_MISSILE_COUNT; + m_bDying = true; + } +} + + +//----------------------------------------------------------------------------- +// Mortar shell +//----------------------------------------------------------------------------- +class CMortarShell : public CBaseEntity +{ +public: + DECLARE_CLASS( CMortarShell, CBaseEntity ); + + static CMortarShell *Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ); + + void Spawn( void ); + void Precache( void ); + void Impact( void ); + void Warn( void ); + void FlyThink( void ); + void FadeThink( void ); + int UpdateTransmitState( void ); + +private: + + void FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal ); + + float m_flFadeTime; + float m_flImpactTime; + float m_flWarnTime; + float m_flNPCWarnTime; + string_t m_warnSound; + int m_iSpriteTexture; + bool m_bHasWarned; + Vector m_vecFiredFrom; + Vector m_vecFlyDir; + float m_flSpawnedTime; + + CHandle m_pBeamEffect[4]; + + CNetworkVar( float, m_flLifespan ); + CNetworkVar( float, m_flRadius ); + CNetworkVar( Vector, m_vecSurfaceNormal ); + + DECLARE_DATADESC(); + DECLARE_SERVERCLASS(); +}; + +LINK_ENTITY_TO_CLASS( mortarshell, CMortarShell ); + +BEGIN_DATADESC( CMortarShell ) + DEFINE_FIELD( m_flImpactTime, FIELD_TIME ), + DEFINE_FIELD( m_flFadeTime, FIELD_TIME ), + DEFINE_FIELD( m_flWarnTime, FIELD_TIME ), + DEFINE_FIELD( m_flNPCWarnTime, FIELD_TIME ), + DEFINE_FIELD( m_warnSound, FIELD_STRING ), + DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( m_bHasWarned, FIELD_BOOLEAN ), + DEFINE_FIELD( m_flLifespan, FIELD_FLOAT ), + DEFINE_FIELD( m_vecFiredFrom, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( m_vecFlyDir, FIELD_VECTOR ), + DEFINE_FIELD( m_flSpawnedTime, FIELD_TIME ), + DEFINE_AUTO_ARRAY( m_pBeamEffect, FIELD_EHANDLE), + DEFINE_FIELD( m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), + + DEFINE_FUNCTION( FlyThink ), + DEFINE_FUNCTION( FadeThink ), +END_DATADESC() + +IMPLEMENT_SERVERCLASS_ST( CMortarShell, DT_MortarShell ) + SendPropFloat( SENDINFO( m_flLifespan ), -1, SPROP_NOSCALE ), + SendPropFloat( SENDINFO( m_flRadius ), -1, SPROP_NOSCALE ), + SendPropVector( SENDINFO( m_vecSurfaceNormal ), 0, SPROP_NORMAL ), +END_SEND_TABLE() + +#define MORTAR_TEST_RADIUS 16.0f + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &initialPos - +// *endPos - +// *endNormal - +//----------------------------------------------------------------------------- +void CMortarShell::FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal ) +{ + Vector vecStartOffset; + + vecStartOffset = initialPos + ( initialNormal * 1.0f ); + + trace_t tr; + UTIL_TraceLine( vecStartOffset, vecStartOffset - Vector( 0, 0, 256 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); + + if ( tr.fraction < 1.0f ) + { + if ( endPos ) + { + *endPos = tr.endpos + ( initialNormal * 16.0f ); + } + + if ( endNormal ) + { + *endNormal = tr.plane.normal; + } + } + else + { + if ( endPos ) + { + *endPos = initialPos; + } + + if ( endNormal ) + { + *endNormal = initialNormal; + } + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +#define MORTAR_BLAST_DAMAGE 50 +#define MORTAR_BLAST_HEIGHT 7500 + +CMortarShell *CMortarShell::Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ) +{ + CMortarShell *pShell = (CMortarShell *)CreateEntityByName("mortarshell" ); + + // Place the mortar shell at the target location so that it can make the sound and explode. + trace_t tr; + UTIL_TraceLine( vecTarget, vecTarget + ( vecShotDir * 128.0f ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &tr ); + + Vector targetPos, targetNormal; + pShell->FixUpImpactPoint( tr.endpos, tr.plane.normal, &targetPos, &targetNormal ); + + UTIL_SetOrigin( pShell, targetPos ); + + Vector vecStartSkew, vecEndSkew; + + vecStartSkew = targetPos - vecStart; + vecStartSkew[2] = 0.0f; + float skewLength = VectorNormalize( vecStartSkew ); + + vecEndSkew = -vecStartSkew * ( skewLength * 0.25f ); + vecStartSkew *= skewLength * 0.1f; + + // Muzzleflash beam + pShell->m_pBeamEffect[0] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); + pShell->m_pBeamEffect[0]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) ); + pShell->m_pBeamEffect[0]->SetColor( 16, 16, 8 ); + pShell->m_pBeamEffect[0]->SetBrightness( 0 ); + pShell->m_pBeamEffect[0]->SetNoise( 0 ); + pShell->m_pBeamEffect[0]->SetBeamFlag( FBEAM_SHADEOUT ); + pShell->m_pBeamEffect[0]->SetWidth( 64.0f ); + pShell->m_pBeamEffect[0]->SetEndWidth( 64.0f ); + + pShell->m_pBeamEffect[1] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); + pShell->m_pBeamEffect[1]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) ); + pShell->m_pBeamEffect[1]->SetColor( 255, 255, 255 ); + pShell->m_pBeamEffect[1]->SetBrightness( 0 ); + pShell->m_pBeamEffect[1]->SetNoise( 0 ); + pShell->m_pBeamEffect[1]->SetBeamFlag( FBEAM_SHADEOUT ); + pShell->m_pBeamEffect[1]->SetWidth( 8.0f ); + pShell->m_pBeamEffect[1]->SetEndWidth( 8.0f ); + + trace_t skyTrace; + UTIL_TraceLine( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &skyTrace ); + + // We must touch the sky to make this beam + if ( skyTrace.fraction <= 1.0f && skyTrace.surface.flags & SURF_SKY ) + { + // Impact point beam + pShell->m_pBeamEffect[2] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); + pShell->m_pBeamEffect[2]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) ); + pShell->m_pBeamEffect[2]->SetColor( 16, 16, 8 ); + pShell->m_pBeamEffect[2]->SetBrightness( 0 ); + pShell->m_pBeamEffect[2]->SetNoise( 0 ); + pShell->m_pBeamEffect[2]->SetBeamFlag( FBEAM_SHADEOUT ); + pShell->m_pBeamEffect[2]->SetWidth( 32.0f ); + pShell->m_pBeamEffect[2]->SetEndWidth( 32.0f ); + + pShell->m_pBeamEffect[3] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); + pShell->m_pBeamEffect[3]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) ); + pShell->m_pBeamEffect[3]->SetColor( 255, 255, 255 ); + pShell->m_pBeamEffect[3]->SetBrightness( 0 ); + pShell->m_pBeamEffect[3]->SetNoise( 0 ); + pShell->m_pBeamEffect[3]->SetBeamFlag( FBEAM_SHADEOUT ); + pShell->m_pBeamEffect[3]->SetWidth( 4.0f ); + pShell->m_pBeamEffect[3]->SetEndWidth( 4.0f ); + } + else + { + // Mark these as not being used + pShell->m_pBeamEffect[2] = NULL; + pShell->m_pBeamEffect[3] = NULL; + } + + pShell->m_vecFiredFrom = vecStart; + pShell->m_flLifespan = flImpactDelay; + pShell->m_flImpactTime = gpGlobals->curtime + flImpactDelay; + pShell->m_flWarnTime = pShell->m_flImpactTime - flWarnDelay; + pShell->m_flNPCWarnTime = pShell->m_flWarnTime - 0.5; + pShell->m_warnSound = warnSound; + pShell->Spawn(); + + // Save off the impact normal + pShell->m_vecSurfaceNormal = targetNormal; + pShell->m_flRadius = MORTAR_BLAST_RADIUS; + + return pShell; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMortarShell::Precache() +{ + m_iSpriteTexture = PrecacheModel( "sprites/physbeam.vmt" ); + + PrecacheScriptSound( "Weapon_Mortar.Impact" ); + PrecacheMaterial( "effects/ar2ground2" ); + + if ( NULL_STRING != m_warnSound ) + { + PrecacheScriptSound( STRING( m_warnSound ) ); + } +} + +//------------------------------------------------------------------------------ +// Purpose : Send even though we don't have a model +//------------------------------------------------------------------------------ +int CMortarShell::UpdateTransmitState( void ) +{ + return SetTransmitState( FL_EDICT_PVSCHECK ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMortarShell::Spawn() +{ + Precache(); + + AddEffects( EF_NODRAW ); + AddSolidFlags( FSOLID_NOT_SOLID ); + + Vector mins( -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS ); + Vector maxs( MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS ); + + UTIL_SetSize( this, mins, maxs ); + + m_vecFlyDir = GetAbsOrigin() - m_vecFiredFrom; + VectorNormalize( m_vecFlyDir ); + + m_flSpawnedTime = gpGlobals->curtime; + + SetThink( &CMortarShell::FlyThink ); + SetNextThink( gpGlobals->curtime ); + + // No model but we still need to force this! + AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - +// steps - +// bias - +//----------------------------------------------------------------------------- +ConVar curve_bias( "curve_bias", "0.5" ); + +enum +{ + CURVE_BIAS, + CURVE_GAIN, + CURVE_SMOOTH, + CURVE_SMOOTH_TWEAK, +}; + +void UTIL_VisualizeCurve( int type, int steps, float bias ) +{ + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + Vector vForward, vRight, vUp; + + pPlayer->EyeVectors( &vForward, &vRight, &vUp ); + + Vector renderOrigin = pPlayer->EyePosition() + ( vForward * 512.0f ); + + float renderScale = 8.0f; + float lastPerc, perc; + + Vector renderOffs, lastRenderOffs = vec3_origin; + + for ( int i = 0; i < steps; i++ ) + { + perc = RemapValClamped( i, 0, steps-1, 0.0f, 1.0f ); + + switch( type ) + { + case CURVE_BIAS: + perc = Bias( perc, bias ); + break; + + case CURVE_GAIN: + perc = Gain( perc, bias ); + break; + + case CURVE_SMOOTH: + perc = SmoothCurve( perc ); + break; + + case CURVE_SMOOTH_TWEAK: + perc = SmoothCurve_Tweak( perc, bias, 0.9f ); + break; + } + + renderOffs = ( vRight * (-steps*0.5f) * renderScale ) + ( vUp * (renderScale*-(steps*0.5f)) )+ ( vRight * i * renderScale ) + ( vUp * perc * (renderScale*steps) ); + + NDebugOverlay::Cross3D( renderOrigin + renderOffs, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, true, 0.05f ); + + if ( i > 0 ) + { + NDebugOverlay::Line( renderOrigin + renderOffs, renderOrigin + lastRenderOffs, 255, 0, 0, true, 0.05f ); + } + + lastRenderOffs = renderOffs; + lastPerc = perc; + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMortarShell::FlyThink() +{ + SetNextThink( gpGlobals->curtime + 0.05 ); + + if ( gpGlobals->curtime > m_flNPCWarnTime ) + { + // Warn the AI. Make this radius a little larger than the explosion will be, and make the sound last a little longer. + CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), MORTAR_BLAST_RADIUS * 1.25, (m_flImpactTime - m_flNPCWarnTime) + 0.15 ); + m_flNPCWarnTime = FLT_MAX; + } + + //UTIL_VisualizeCurve( CURVE_GAIN, 64, curve_bias.GetFloat() ); + + float lifePerc = 1.0f - ( ( m_flImpactTime - gpGlobals->curtime ) / ( m_flImpactTime - m_flSpawnedTime ) ); + + lifePerc = clamp( lifePerc, 0.0f, 1.0f ); + + float curve1 = Bias( lifePerc, 0.75f ); + + // Beam updates START + + m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); + m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); + + m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); + m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); + + float curve2 = Bias( lifePerc, 0.1f ); + + if ( m_pBeamEffect[2] ) + { + m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); + m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); + } + + if ( m_pBeamEffect[3] ) + { + m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); + m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); + } + + // Beam updates END + + if( !m_bHasWarned && gpGlobals->curtime > m_flWarnTime ) + { + Warn(); + } + + if( gpGlobals->curtime > m_flImpactTime ) + { + Impact(); + } + +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMortarShell::Warn( void ) +{ + if ( m_warnSound != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_WEAPON; + ep.m_pSoundName = (char*)STRING(m_warnSound); + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NONE; + + EmitSound( filter, entindex(), ep ); + } + + m_bHasWarned = true; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CMortarShell::Impact( void ) +{ + // Fire the bullets + Vector vecSrc, vecShootDir; + + float flRadius = MORTAR_BLAST_RADIUS; + + trace_t tr; + UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 128 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); + + UTIL_DecalTrace( &tr, "Scorch" ); + + // Send the effect over + CEffectData data; + + // Do an extra effect if we struck the world + if ( tr.m_pEnt && tr.m_pEnt->IsWorld() ) + { + data.m_flRadius = flRadius * 0.5f; + data.m_vNormal = tr.plane.normal; + data.m_vOrigin = tr.endpos; + + DispatchEffect( "AR2Explosion", data ); + } + + //Shockring + CBroadcastRecipientFilter filter2; + te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin + 8.0f, //start radius + flRadius * 2, //end radius + m_iSpriteTexture, //texture + 0, //halo index + 0, //start frame + 2, //framerate + 0.2f, //life + 32, //width + 0, //spread + 0, //amplitude + 255, //r + 255, //g + 225, //b + 32, //a + 0, //speed + FBEAM_FADEOUT + ); + + //Shockring + te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin + 8.0f, //start radius + flRadius, //end radius + m_iSpriteTexture, //texture + 0, //halo index + 0, //start frame + 2, //framerate + 0.2f, //life + 64, //width + 0, //spread + 0, //amplitude + 255, //r + 255, //g + 225, //b + 64, //a + 0, //speed + FBEAM_FADEOUT + ); + + RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), MORTAR_BLAST_DAMAGE, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), MORTAR_BLAST_RADIUS, CLASS_NONE, NULL ); + + EmitSound( "Weapon_Mortar.Impact" ); + + UTIL_ScreenShake( GetAbsOrigin(), 10, 60, 1.0, 550, SHAKE_START, false ); + + //Fade the beams over time! + m_flFadeTime = gpGlobals->curtime; + + SetThink( &CMortarShell::FadeThink ); + SetNextThink( gpGlobals->curtime + 0.05f ); +} + +#define MORTAR_FADE_LENGTH 1.0f + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMortarShell::FadeThink( void ) +{ + SetNextThink( gpGlobals->curtime + 0.05f ); + + float lifePerc = 1.0f - ( ( gpGlobals->curtime - m_flFadeTime ) / MORTAR_FADE_LENGTH ); + + lifePerc = clamp( lifePerc, 0.0f, 1.0f ); + + float curve1 = Bias( lifePerc, 0.1f ); + + // Beam updates START + + m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); + m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); + + m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); + m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); + m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); + + float curve2 = Bias( lifePerc, 0.25f ); + + if ( m_pBeamEffect[2] ) + { + m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); + m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); + } + + if ( m_pBeamEffect[3] ) + { + m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); + m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); + m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); + } + + // Beam updates END + + if ( gpGlobals->curtime > ( m_flFadeTime + MORTAR_FADE_LENGTH ) ) + { + UTIL_Remove( m_pBeamEffect[0] ); + UTIL_Remove( m_pBeamEffect[1] ); + UTIL_Remove( m_pBeamEffect[2] ); + UTIL_Remove( m_pBeamEffect[3] ); + + SetThink(NULL); + UTIL_Remove( this ); + } +} + +//========================================================= +//========================================================= +class CFuncTankMortar : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankMortar, CFuncTank ); + + CFuncTankMortar() { m_fLastShotMissed = false; } + + void Precache( void ); + void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + void ShootGun(void); + void Spawn(); + void SetNextAttack( float flWait ); + + // Input handlers. + void InputShootGun( inputdata_t &inputdata ); + void InputFireAtWill( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + + int m_Magnitude; + float m_fireDelay; + string_t m_fireStartSound; + //string_t m_fireEndSound; + + string_t m_incomingSound; + float m_flWarningTime; + float m_flFireVariance; + + bool m_fLastShotMissed; + + // store future firing event + CBaseEntity *m_pAttacker; +}; + +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + +BEGIN_DATADESC( CFuncTankMortar ) + + DEFINE_KEYFIELD( m_Magnitude, FIELD_INTEGER, "iMagnitude" ), + DEFINE_KEYFIELD( m_fireDelay, FIELD_FLOAT, "firedelay" ), + DEFINE_KEYFIELD( m_fireStartSound, FIELD_STRING, "firestartsound" ), + //DEFINE_KEYFIELD( m_fireEndSound, FIELD_STRING, "fireendsound" ), + DEFINE_KEYFIELD( m_incomingSound, FIELD_STRING, "incomingsound" ), + DEFINE_KEYFIELD( m_flWarningTime, FIELD_TIME, "warningtime" ), + DEFINE_KEYFIELD( m_flFireVariance, FIELD_TIME, "firevariance" ), + + DEFINE_FIELD( m_fLastShotMissed, FIELD_BOOLEAN ), + + DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ), + DEFINE_INPUTFUNC( FIELD_VOID, "FireAtWill", InputFireAtWill ), +END_DATADESC() + + +void CFuncTankMortar::Spawn() +{ + BaseClass::Spawn(); + + m_takedamage = DAMAGE_NO; +} + +void CFuncTankMortar::Precache( void ) +{ + if ( m_fireStartSound != NULL_STRING ) + PrecacheScriptSound( STRING(m_fireStartSound) ); + //if ( m_fireEndSound != NULL_STRING ) + // PrecacheScriptSound( STRING(m_fireEndSound) ); + if ( m_incomingSound != NULL_STRING ) + PrecacheScriptSound( STRING(m_incomingSound) ); + BaseClass::Precache(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CFuncTankMortar::SetNextAttack( float flWait ) +{ + if ( m_flFireVariance > 0.09 ) + flWait += random->RandomFloat( -m_flFireVariance, m_flFireVariance ); + BaseClass::SetNextAttack( flWait ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler to make the tank shoot. +//----------------------------------------------------------------------------- +void CFuncTankMortar::InputShootGun( inputdata_t &inputdata ) +{ + ShootGun(); +} + +//----------------------------------------------------------------------------- +// This mortar can fire the next round as soon as it is ready. This is not a +// 'sticky' state, it just allows us to get the next shot off as soon as the +// tank is on target. great for scripted applications where you need a shot as +// soon as you can get it. +//----------------------------------------------------------------------------- +void CFuncTankMortar::InputFireAtWill( inputdata_t &inputdata ) +{ + SetNextAttack( gpGlobals->curtime ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankMortar::ShootGun( void ) +{ + Vector forward; + AngleVectors( GetLocalAngles(), &forward ); + UpdateMatrix(); + forward = m_parentMatrix.ApplyRotation( forward ); + + Fire( 1, WorldBarrelPosition(), forward, m_pAttacker, false ); +} + + +void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) +{ + if ( gpGlobals->curtime > GetNextAttack() ) + { + ShootGun(); + m_fireLast = gpGlobals->curtime; + SetNextAttack( gpGlobals->curtime + (1.0 / m_fireRate ) ); + } + else + { + m_fireLast = gpGlobals->curtime; + } +} + +void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + Vector vecProjectedPosition = vec3_invalid; + trace_t tr; + + if ( m_hTarget ) + { + float leadTime = (m_fireDelay * 1.1); + + if ( m_hTarget->IsNPC() ) // Give NPCs a little extra grace + leadTime = 1.25; + + Vector vLead = m_hTarget->GetSmoothedVelocity() * leadTime; + Vector vNoise; + + vecProjectedPosition = m_hTarget->WorldSpaceCenter() + vLead; + vNoise.AsVector2D().Random( -6*12, 6*12); + vNoise.z = 0; + + if( m_hTarget->Classify() != CLASS_BULLSEYE ) + { + // Don't apply noise when attacking a bullseye. + vecProjectedPosition += vNoise; + } + } + else if ( IsPlayerManned() ) + { + CalcPlayerCrosshairTarget( &vecProjectedPosition ); + } + else if ( IsNPCManned() ) + { + CalcNPCEnemyTarget( &vecProjectedPosition ); + //vecProjectedPosition += GetEnemy()->GetSmoothedVelocity() * (m_fireDelay * 1.1); + } + else + return; + + #define TARGET_SEARCH_DEPTH 100 + + // find something interesting to shoot at near the projected position. + Vector delta; + + // Make a really rough approximation of the last half of the mortar trajectory and trace it. + // Do this so that mortars fired into windows land on rooftops, and that targets projected + // inside buildings (or out of the world) clip to the world. (usually a building facade) + + // Find halfway between the mortar and the target. + Vector vecSpot = ( vecProjectedPosition + GetAbsOrigin() ) * 0.5; + vecSpot.z = GetAbsOrigin().z; + + // Trace up to find the fake 'apex' of the shell. The skybox or 1024 units, whichever comes first. + UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + vecSpot = tr.endpos; + + //NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 ); + + // Now trace from apex to target + UTIL_TraceLine( vecSpot, vecProjectedPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); + + if( mortar_visualize.GetBool() ) + { + NDebugOverlay::Line( tr.startpos, tr.endpos, 255,0,0, false, 5 ); + } + + if ( m_fireStartSound != NULL_STRING ) + { + CPASAttenuationFilter filter( this ); + + EmitSound_t ep; + ep.m_nChannel = CHAN_WEAPON; + ep.m_pSoundName = (char*)STRING(m_fireStartSound); + ep.m_flVolume = 1.0f; + ep.m_SoundLevel = SNDLVL_NONE; + + EmitSound( filter, entindex(), ep ); + } + + Vector vecFinalDir = tr.endpos - tr.startpos; + VectorNormalize( vecFinalDir ); + + CMortarShell::Create( barrelEnd, tr.endpos, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound ); + BaseClass::Fire( bulletCount, barrelEnd, vecForward, this, bIgnoreSpread ); +} + +//----------------------------------------------------------------------------- +// Purpose: Func tank that fires physics cannisters placed on it +//----------------------------------------------------------------------------- +class CFuncTankPhysCannister : public CFuncTank +{ +public: + DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank ); + DECLARE_DATADESC(); + + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + +protected: + string_t m_iszBarrelVolume; + CHandle m_hBarrelVolume; +}; + +LINK_ENTITY_TO_CLASS( func_tankphyscannister, CFuncTankPhysCannister ); + +BEGIN_DATADESC( CFuncTankPhysCannister ) + + DEFINE_KEYFIELD( m_iszBarrelVolume, FIELD_STRING, "barrel_volume" ), + DEFINE_FIELD( m_hBarrelVolume, FIELD_EHANDLE ), + +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CFuncTankPhysCannister::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + // Find our barrel volume + if ( !m_hBarrelVolume ) + { + if ( m_iszBarrelVolume != NULL_STRING ) + { + m_hBarrelVolume = dynamic_cast( gEntList.FindEntityByName( NULL, m_iszBarrelVolume ) ); + } + + if ( !m_hBarrelVolume ) + { + Msg("ERROR: Couldn't find barrel volume for func_tankphyscannister %s.\n", STRING(GetEntityName()) ); + return; + } + } + + // Do we have a cannister in our barrel volume? + CPhysicsCannister *pCannister = (CPhysicsCannister *)m_hBarrelVolume->GetTouchedEntityOfType( "physics_cannister" ); + if ( !pCannister ) + { + // Play a no-ammo sound + return; + } + + // Fire the cannister! + pCannister->CannisterFire( pAttacker ); +} + +//========================================================= +//========================================================= +static const char *s_pUpdateBeamThinkContext = "UpdateBeamThinkContext"; +#define COMBINE_CANNON_BEAM "effects/blueblacklargebeam.vmt" +//#define COMBINE_CANNON_BEAM "sprites/strider_bluebeam.vmt" + +class CFuncTankCombineCannon : public CFuncTankGun +{ + DECLARE_CLASS( CFuncTankCombineCannon, CFuncTankGun ); + + void Precache(); + void Spawn(); + void CreateBeam(); + void DestroyBeam(); + void FuncTankPostThink(); + void AdjustRateOfFire(); + void UpdateBeamThink( void ); + void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); + void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); + void TankDeactivate(); + + void InputSetTargetEntity( inputdata_t &inputdata ); + void InputClearTargetEntity( inputdata_t &inputdata ); + + void InputEnableHarrass( inputdata_t &inputdata ); + void InputDisableHarrass( inputdata_t &inputdata ); + + COutputEvent m_OnShotAtPlayer; + + CHandle m_hBeam; + + DECLARE_DATADESC(); + +private: + float m_originalFireRate; + float m_flTimeNextSweep; + float m_flTimeBeamOn; + Vector m_vecTrueForward; + bool m_bShouldHarrass; + bool m_bLastTargetWasNPC; // Tells whether the last entity we fired a shot at was an NPC (otherwise it was the player) +}; + +BEGIN_DATADESC( CFuncTankCombineCannon ) + DEFINE_FIELD( m_originalFireRate, FIELD_FLOAT ), + DEFINE_THINKFUNC( UpdateBeamThink ), + DEFINE_FIELD( m_flTimeNextSweep, FIELD_TIME ), + DEFINE_FIELD( m_flTimeBeamOn, FIELD_TIME ), + DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ), + DEFINE_FIELD( m_vecTrueForward, FIELD_VECTOR ), + DEFINE_FIELD( m_bShouldHarrass, FIELD_BOOLEAN ), + DEFINE_FIELD( m_bLastTargetWasNPC, FIELD_BOOLEAN ), + + DEFINE_INPUTFUNC( FIELD_VOID, "EnableHarrass", InputEnableHarrass ), + DEFINE_INPUTFUNC( FIELD_VOID, "DisableHarrass", InputDisableHarrass ), + + DEFINE_OUTPUT( m_OnShotAtPlayer, "OnShotAtPlayer" ), + +END_DATADESC() + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::Precache() +{ + m_originalFireRate = m_fireRate; + + PrecacheModel(COMBINE_CANNON_BEAM); + PrecacheParticleSystem( "Weapon_Combine_Ion_Cannon" ); + + BaseClass::Precache(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::Spawn() +{ + BaseClass::Spawn(); + m_flTimeBeamOn = gpGlobals->curtime; + CreateBeam(); + + m_bShouldHarrass = true; + + GetVectors( &m_vecTrueForward, NULL, NULL ); + m_bLastTargetWasNPC = false; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::CreateBeam() +{ + if (!m_hBeam && gpGlobals->curtime >= m_flTimeBeamOn ) + { + m_hBeam = CBeam::BeamCreate( COMBINE_CANNON_BEAM, 1.0f ); + m_hBeam->SetColor( 255, 255, 255 ); + SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime, s_pUpdateBeamThinkContext ); + } + else + { + // Beam seems to be on, or I'm not supposed to have it on at the moment. + return; + } + + Vector vecInitialAim; + + AngleVectors( GetAbsAngles(), &vecInitialAim, NULL, NULL ); + + m_hBeam->PointsInit( WorldBarrelPosition(), WorldBarrelPosition() + vecInitialAim ); + m_hBeam->SetBrightness( 255 ); + m_hBeam->SetNoise( 0 ); + m_hBeam->SetWidth( 3.0f ); + m_hBeam->SetEndWidth( 0 ); + m_hBeam->SetScrollRate( 0 ); + m_hBeam->SetFadeLength( 60 ); // five feet to fade out + //m_hBeam->SetHaloTexture( sHaloSprite ); + m_hBeam->SetHaloScale( 4.0f ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::DestroyBeam() +{ + if( m_hBeam ) + { + UTIL_Remove( m_hBeam ); + m_hBeam.Set(NULL); + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::AdjustRateOfFire() +{ + // Maintain 1.5 rounds per second rate of fire. + m_fireRate = 1.5; +/* + if( m_hTarget.Get() != NULL && m_hTarget->IsPlayer() ) + { + if( m_bLastTargetWasNPC ) + { + // Cheat, and be able to fire RIGHT NOW if the target is a player and the + // last target I fired at was an NPC. This prevents the player from running + // for it while the gun is busy dealing with NPCs + SetNextAttack( gpGlobals->curtime ); + } + } +*/ +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +#define COMBINE_CANNON_BEAM_MAX_DIST 1900.0f +void CFuncTankCombineCannon::UpdateBeamThink() +{ + SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime + 0.025, s_pUpdateBeamThinkContext ); + + // Always try to create the beam. + CreateBeam(); + + if( !m_hBeam ) + return; + + trace_t trBeam; + trace_t trShot; + trace_t trBlockLOS; + + Vector vecBarrel = WorldBarrelPosition(); + Vector vecAim; + AngleVectors( GetAbsAngles(), &vecAim, NULL, NULL ); + + AI_TraceLine( vecBarrel, vecBarrel + vecAim * COMBINE_CANNON_BEAM_MAX_DIST, MASK_SHOT, this, COLLISION_GROUP_NONE, &trBeam ); + + m_hBeam->SetStartPos( trBeam.startpos ); + m_hBeam->SetEndPos( trBeam.endpos ); + + if( !(m_spawnflags & SF_TANK_AIM_AT_POS) ) + { + SetTargetPosition( trBeam.endpos ); + } +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::FuncTankPostThink() +{ + AdjustRateOfFire(); + + if( m_hTarget.Get() == NULL ) + { + if( gpGlobals->curtime > m_flTimeNextSweep ) + { + AddSpawnFlags( SF_TANK_AIM_AT_POS ); + + Vector vecTargetPosition = GetTargetPosition(); + CBasePlayer *pPlayer = AI_GetSinglePlayer(); + Vector vecToPlayer = pPlayer->WorldSpaceCenter() - GetAbsOrigin(); + vecToPlayer.NormalizeInPlace(); + + bool bHarass = false; + float flDot = DotProduct( m_vecTrueForward, vecToPlayer ); + + if( flDot >= 0.9f && m_bShouldHarrass ) + { + //Msg("%s Harrassing player\n", GetDebugName() ); + vecTargetPosition = pPlayer->EyePosition(); + bHarass = true; + } + else + { + //Msg( "%s Bored\n", GetDebugName() ); + // Just point off in the distance, more or less directly ahead of me. + vecTargetPosition = GetAbsOrigin() + m_vecTrueForward * 1900.0f; + } + + int i; + Vector vecTest; + bool bFoundPoint = false; + for( i = 0 ; i < 5 ; i++ ) + { + vecTest = vecTargetPosition; + + if( bHarass ) + { + vecTest.x += random->RandomFloat( -48, 48 ); + vecTest.y += random->RandomFloat( -48, 48 ); + vecTest.z += random->RandomFloat( 16, 48 ); + } + else + { + vecTest.x += random->RandomFloat( -48, 48 ); + vecTest.y += random->RandomFloat( -48, 48 ); + vecTest.z += random->RandomFloat( -48, 48 ); + } + + // Get the barrel position + Vector vecBarrelEnd = WorldBarrelPosition(); + trace_t trLOS; + trace_t trShoot; + + // Ignore the func_tank and any prop it's parented to, and check line of sight to the point + // Trace to the point. If an opaque trace doesn't reach the point, that means the beam hit + // something closer, (including a blockLOS), so try again. + CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); + AI_TraceLine( vecBarrelEnd, vecTest, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &trLOS ); + AI_TraceLine( vecBarrelEnd, vecTest, MASK_SHOT, &traceFilter, &trShoot ); + + if( trLOS.fraction < trShoot.fraction ) + { + // Damn block LOS brushes. + continue; + } + + //Msg("Point is visible in %d tries\n", i); + bFoundPoint = true; + break; + } + + if( bFoundPoint ) + { + vecTargetPosition = vecTest; + SetTargetPosition( vecTargetPosition ); + //Msg("New place\n"); + } + + if( bHarass ) + { + m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.75f ); + } + else + { + m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 1, 3 ); + } + } + } + else + { + //Msg("%d engaging: %s\n", entindex(), m_hTarget->GetClassname() ); + RemoveSpawnFlags( SF_TANK_AIM_AT_POS ); + } +} + +//--------------------------------------------------------- +// A normal func_tank uses a method of aiming the gun that will +// always follow a fast-moving player. This is because the func_tank +// turns the weapon by applying angular velocities in the early +// phase of the func_tank's Think(). Because the bullet is fired +// later in the same think, it is fired before the game physics have +// updated the func_tank's angles using the newly-computed angular +// velocity, so the bullet always trails the target slightly. +// This is unacceptable for the Combine Cannon, as the cannon MUST +// strike a moving player with absolute certainty. As a quick +// remedy, this code allows the combine cannon to fire a bullet +// at a slightly different angle than the gun is aiming, to +// ensure a hit. Large discrepancies are ignored and we accept +// the miss instead of presenting a bullet fired at an obviously +// adjusted angle. +//--------------------------------------------------------- +void CFuncTankCombineCannon::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) +{ + // Specifically do NOT fire in aim at pos mode. This is just for show. + if( HasSpawnFlags(SF_TANK_AIM_AT_POS) ) + return; + + Vector vecAdjustedForward = forward; + + if( m_hTarget != NULL ) + { + Vector vecToTarget = m_hTarget->BodyTarget( barrelEnd, false ) - barrelEnd; + VectorNormalize( vecToTarget ); + + float flDot = DotProduct( vecToTarget, forward ); + + if( flDot >= 0.97 ) + { + vecAdjustedForward = vecToTarget; + } + + if( m_hTarget->IsNPC() ) + m_bLastTargetWasNPC = true; + else + m_bLastTargetWasNPC = false; + + if( m_hTarget->IsPlayer() ) + m_OnShotAtPlayer.FireOutput( this, this ); + } + + BaseClass::Fire( bulletCount, barrelEnd, vecAdjustedForward, pAttacker, bIgnoreSpread ); + + // Turn off the beam and tell it to stay off for a bit. We want it to look like the beam became the + // ion cannon 'rail gun' effect. + DestroyBeam(); + m_flTimeBeamOn = gpGlobals->curtime + 0.2f; + + m_flTimeNextSweep = gpGlobals->curtime + random->RandomInt( 1.0f, 2.0f ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ) +{ + // If the shot passed near the player, shake the screen. + if( AI_IsSinglePlayer() ) + { + Vector vecPlayer = AI_GetSinglePlayer()->EyePosition(); + + Vector vecNearestPoint = PointOnLineNearestPoint( vecTracerSrc, tr.endpos, vecPlayer ); + + float flDist = vecPlayer.DistTo( vecNearestPoint ); + + if( flDist >= 10.0f && flDist <= 120.0f ) + { + // Don't shake the screen if we're hit (within 10 inches), but do shake if a shot otherwise comes within 10 feet. + UTIL_ScreenShake( vecNearestPoint, 10, 60, 0.3, 120.0f, SHAKE_START, false ); + } + } + + // Send the railgun effect + DispatchParticleEffect( "Weapon_Combine_Ion_Cannon", vecTracerSrc, tr.endpos, vec3_angle, NULL ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::TankDeactivate() +{ + DestroyBeam(); + m_flTimeBeamOn = gpGlobals->curtime + 1.0f; + SetContextThink( NULL, 0, s_pUpdateBeamThinkContext ); + + BaseClass::TankDeactivate(); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::InputSetTargetEntity( inputdata_t &inputdata ) +{ + BaseClass::InputSetTargetEntity( inputdata ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::InputClearTargetEntity( inputdata_t &inputdata ) +{ +/* + m_targetEntityName = NULL_STRING; + m_hTarget = NULL; + + // No longer aim at target position if have one + m_spawnflags &= ~SF_TANK_AIM_AT_POS; +*/ + BaseClass::InputClearTargetEntity( inputdata ); +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::InputEnableHarrass( inputdata_t &inputdata ) +{ + m_bShouldHarrass = true; +} + +//--------------------------------------------------------- +//--------------------------------------------------------- +void CFuncTankCombineCannon::InputDisableHarrass( inputdata_t &inputdata ) +{ + m_bShouldHarrass = false; +} + + +LINK_ENTITY_TO_CLASS( func_tank_combine_cannon, CFuncTankCombineCannon ); -- cgit v1.2.3