object deployable_ssg : deployable_base { void preinit(); void init(); void destroy(); void syncFields(); void TurningThread(); void ServerIdle(); void ServerAiming(); void ServerFiring(); void ClientIdle(); void ClientFiring(); boolean vTargetPlayerEligible( entity p ); boolean vTargetGetValid( vector pos ); void vTargetSetTarget( vector targetPos, entity targetEnt ); void vDoneDeploy(); float vGetFireSupportMode(); void InitTargetting( vector targetPos ); void ClearFiringDecal(); void CreateFiringDecal(); void ReloadThread( float delay ); void OnMissileChanged(); void OnFireSupportStateChanged(); // void UpdateTurning(); boolean UpdateYaw(); boolean UpdatePitch(); void ResetAngles(); void TurnToward( float yaw, float pitch ); boolean PitchValid( float pitch ); boolean CalcTargetAngles( vector targetPos ); // utility funcs ( none of these should be blocking ) void Launch(); void FireMissile(); void StopRotationSounds(); void StopYawSound(); void StopPitchSound(); void PlayYawSound(); void PlayPitchSound(); void MissileCheck(); float recycle; float fireCount; float reloadWait; float fireSyncDelay; float nextFire; float jointYaw; float idealYaw; float currentYaw; float oldIdealYaw; float minYawRate; float maxYawRate; float jointPitch; float idealPitch; float currentPitch; float oldIdealPitch; float minPitchRate; float maxPitchRate; float jointBarrel; float minPitch; float maxPitch; float minRange; float maxRange; float missileSpeed; float spread; float clientState; float targetYaw; float targetPitch; vector targetDiff; float baseYaw; float barrelPitch; float barrelLength; vector firingOffset; vector firingVelocity; boolean hasTarget; float projectileIndex; entity targetCapturer; boolean playingPitchSound; boolean playingYawSound; boolean anglesLocked; entity missile; boolean missileActive; vector target; generic_target_marker firingDecal; entity firingMarker; } void deployable_ssg::syncFields() { syncBroadcast( "target" ); syncBroadcast( "nextFire" ); syncBroadcast( "fireSupportState" ); syncBroadcast( "idealYaw" ); syncBroadcast( "idealPitch" ); syncBroadcast( "oldIdealYaw" ); syncBroadcast( "oldIdealPitch" ); syncBroadcast( "missile" ); sync( "currentYaw" ); sync( "currentPitch" ); syncCallback( "missile", "OnMissileChanged" ); syncCallback( "fireSupportState", "OnFireSupportStateChanged" ); } void deployable_ssg::MissileCheck() { while ( true ) { if ( missile != $null_entity ) { missileActive = true; } else { if ( missileActive ) { missileActive = false; ClearFiringDecal(); } } sys.waitFrame(); } } void deployable_ssg::destroy() { delete fsStatus; ClearFiringDecal(); } void deployable_ssg::ReloadThread( float delay ) { fireSupportState = MPS_RELOADING; sys.wait( delay ); fireSupportState = MPS_READY; } void deployable_ssg::OnMissileChanged() { if ( missile != $null_entity ) { CreateFiringDecal(); } else { ClearFiringDecal(); } } void deployable_ssg::OnFireSupportStateChanged() { if ( fireSupportState == MPS_FIRING ) { if ( clientState != ART_CS_FIRING ) { setState( "ClientFiring" ); } } else { if ( clientState != ART_CS_IDLE ) { setState( "ClientIdle" ); } } } float deployable_ssg::vGetFireSupportMode() { return TARGET_SSM; } void deployable_ssg::preinit() { vector barrel; float entityDeclIndex; float i; float muzzleHandle; recycle = getFloatKey( "missile_recycle" ); fireCount = getFloatKey( "missile_firecount" ); reloadWait = getFloatKey( "missile_reload" ); fireSyncDelay = getFloatKey( "sync_delay" ); spread = getFloatKey( "spread" ); if ( fireCount <= 0 ) { fireCount = 6; } if ( reloadWait <= 0 ) { reloadWait = 30; } if ( fireSyncDelay <= 0 ) { fireSyncDelay = 0.5; } jointYaw = getJointHandle( getKey( "joint_yaw" ) ); jointPitch = getJointHandle( getKey( "joint_pitch" ) ); jointBarrel = getJointHandle( getKey( "joint_barrel" ) ); entityDeclIndex = sys.getDeclType( "entityDef" ); projectileIndex = sys.getDeclIndex( entityDeclIndex, getKey( "def_projectile" ) ); barrel = getLocalJointPos( jointBarrel ) - getLocalJointPos( jointPitch ); barrelPitch = sys.atan2( barrel_z, barrel_x ); barrelLength = sys.vecLength( barrel ); currentYaw = 0; idealYaw = 0; oldIdealYaw = 0; maxYawRate = getFloatKey( "max_yaw_turn" ); minYawRate = getFloatKey( "min_yaw_turn" ); currentPitch = 0; idealPitch = 0; oldIdealPitch = 0; maxPitchRate = getFloatKey( "max_pitch_turn" ); minPitchRate = getFloatKey( "min_pitch_turn" ); minPitch = getFloatKey( "min_pitch" ); maxPitch = getFloatKey( "max_pitch" ); minRange = getFloatKey( "range_min" ); maxRange = getFloatKey( "range_max" ); missileSpeed = getFloatKey( "missile_speed" ); hasTarget = false; nextFire = 0; fsStatus = new fireSupportStatus; thread TurningThread(); thread MissileCheck(); } void deployable_ssg::init() { if ( sys.isClient() ) { setState( "ClientIdle" ); } else { setState( "ServerIdle" ); } } // ========================================== // States // ========================================== void deployable_ssg::TurningThread() { while( true ) { sys.waitFrame(); if ( disabledState ) { StopRotationSounds(); } else { UpdateTurning(); } } } void deployable_ssg::ClientIdle() { clientState = ART_CS_IDLE; } void deployable_ssg::ServerIdle() { hasTarget = false; ResetAngles(); } void deployable_ssg::UpdateTurning() { boolean yawDone; boolean pitchDone; vector angles; yawDone = UpdateYaw(); pitchDone = UpdatePitch(); if ( !yawDone ) { angles_x = 0; angles_y = currentYaw; angles_z = 0; setJointAngle( jointYaw, JOINTMOD_LOCAL, angles ); PlayYawSound(); } else { StopYawSound(); } if ( !pitchDone ) { angles_x = currentPitch; angles_y = 0; angles_z = 0; setJointAngle( jointPitch, JOINTMOD_LOCAL, angles ); PlayPitchSound(); } else { StopPitchSound(); } if ( yawDone && pitchDone ) { anglesLocked = true; } else { anglesLocked = false; } } void deployable_ssg::ServerAiming() { fireSupportState = MPS_FIRING_PREPARE; while ( !anglesLocked ) { sys.waitFrame(); if ( disabledState ) { setState( "ServerIdle" ); } } setState( "ServerFiring" ); } void deployable_ssg::ClientFiring() { clientState = ART_CS_FIRING; while( true ) { if ( sys.getTime() > nextFire ) { Launch(); break; } sys.waitFrame(); } } void deployable_ssg::ServerFiring() { fireSupportState = MPS_FIRING; nextFire = sys.getTime() + 2.f; while( true ) { if ( disabledState ) { ClearFiringDecal(); break; } if ( sys.getTime() > nextFire ) { Launch(); break; } sys.waitFrame(); } sys.wait( 2.f ); nextFire = 0; thread ReloadThread( reloadWait ); setState( "ServerIdle" ); } // ========================================== // Utility Funcs // ========================================== void deployable_ssg::FireMissile() { vector org = getJointPos( jointPitch ) + firingOffset; missile = launchMissileSimple( owner, self, $null_entity, projectileIndex, -1, 0.f, org, firingVelocity ); } void deployable_ssg::Launch() { FireMissile(); string anim = getKey( "missile_anim" ); if ( anim != "" ) { playAnim( getIntKey( "missile_channel" ), anim ); } objManager.CPrintEvent( sys.localizeString( "game/ssg_fired" ), $null ); team_base team = getGameTeam(); objManager.PlaySound( team.getKey( "snd_ssg_fired" ), team ); } void deployable_ssg::StopYawSound() { if ( playingYawSound ) { playingYawSound = false; startSound( "snd_turret_stop", SND_DEPLOYABLE_YAW ); } } void deployable_ssg::StopPitchSound() { if ( playingPitchSound ) { playingPitchSound = false; startSound( "snd_barrel_stop", SND_DEPLOYABLE_PITCH ); } } void deployable_ssg::PlayYawSound() { if ( !playingYawSound ) { playingYawSound = true; startSound( "snd_turret_start", SND_DEPLOYABLE_YAW ); } } void deployable_ssg::PlayPitchSound() { if ( !playingPitchSound ) { playingPitchSound = true; startSound( "snd_barrel_start", SND_DEPLOYABLE_PITCH ); } } // ========================================== // ========================================== boolean deployable_ssg::UpdateYaw() { float ang; float maxTurn; float frac; if ( idealYaw == currentYaw ) { return true; } ang = sys.angleNormalize180( idealYaw - currentYaw ); frac = sys.sin( sys.fabs( ang / sys.angleNormalize180( idealYaw - oldIdealYaw ) ) * MATH_PI ); maxTurn = ( minYawRate + ( maxYawRate - minYawRate ) * frac ) * sys.getFrameTime(); if ( ang < -maxTurn ) { currentYaw = currentYaw - maxTurn; } else if ( ang > maxTurn ) { currentYaw = currentYaw + maxTurn; } else { currentYaw = idealYaw; } return false; } boolean deployable_ssg::UpdatePitch() { float ang; float maxTurn; float frac; if ( idealPitch == currentPitch ) { return true; } ang = sys.angleNormalize180( idealPitch - currentPitch ); frac = sys.sin( sys.fabs( ang / sys.angleNormalize180( idealPitch - oldIdealPitch ) ) * MATH_PI ); maxTurn = ( minPitchRate + ( maxPitchRate - minPitchRate ) * frac ) * sys.getFrameTime(); if ( ang < -maxTurn ) { currentPitch = currentPitch - maxTurn; } else if ( ang > maxTurn ) { currentPitch = currentPitch + maxTurn; } else { currentPitch = idealPitch; } return false; } void deployable_ssg::TurnToward( float yaw, float pitch ) { oldIdealYaw = currentYaw; oldIdealPitch = currentPitch; idealYaw = sys.angleNormalize180( yaw ); idealPitch = sys.angleNormalize180( pitch ); anglesLocked = false; } void deployable_ssg::ResetAngles() { TurnToward( 0, 0 ); } void deployable_ssg::InitTargetting( vector targetPos ) { float firingPitch; float t; vector gravity = '0 0 -1066'; vector velocity; vector temp; float i; if ( !CalcTargetAngles( targetPos ) ) { hasTarget = false; return; } if ( sys.doClientSideStuff() ) { CreateFiringDecal(); } firingPitch = targetPitch - barrelPitch; firingVelocity = targetDiff * ( sys.cos( targetPitch ) * missileSpeed ); firingVelocity_z = sys.sin( targetPitch ) * missileSpeed; t = barrelLength / missileSpeed; gravity = gravity * t * t * 0.5f; temp = firingVelocity * t; firingOffset = temp + gravity; firingVelocity = firingVelocity + ( gravity * t ); TurnToward( targetYaw - baseYaw, -firingPitch ); } boolean deployable_ssg::CalcTargetAngles( vector targetPos ) { vector barrelOrg; vector temp; float diffY; float diffX; float rootA; float count; float root1; float root2; barrelOrg = getJointPos( jointPitch ); targetDiff = targetPos - barrelOrg; targetYaw = sys.angleNormalize180( sys.atan2( targetDiff_y, targetDiff_x ) ); temp = targetDiff; diffY = temp_z; temp_z = 0.f; targetDiff = sys.vecNormalize( temp ); diffX = sys.vecLength( temp ); // FIXME: Expose default gravity rootA = ( 400 * diffX * diffX ) / ( 2 * missileSpeed * missileSpeed ); count = sys.solveRoots( rootA, -diffX, rootA + diffY ); if ( count == 0 ) { return false; } if ( count == 1 ) { targetPitch = sys.atan( sys.getRoot( 0 ) ); return PitchValid( targetPitch ); } targetPitch = sys.atan( sys.getRoot( 0 ) ); if ( PitchValid( targetPitch ) ) { return true; } targetPitch = sys.atan( sys.getRoot( 1 ) ); return PitchValid( targetPitch ); } boolean deployable_ssg::PitchValid( float pitch ) { return pitch < maxPitch && pitch > minPitch; } void deployable_ssg::StopRotationSounds() { StopYawSound(); StopPitchSound(); } boolean deployable_ssg::vTargetPlayerEligible( entity p ) { if ( disabledState || !finishedDeploying ) { return 0.f; } return 1.f; } boolean deployable_ssg::vTargetGetValid( vector pos ) { return CalcTargetAngles( pos ); } void deployable_ssg::vTargetSetTarget( vector targetPos, entity targetEnt ) { hasTarget = true; target = targetPos; InitTargetting( targetPos ); Base_TargetSetTarget( targetPos, targetEnt ); setState( "ServerAiming" ); } void deployable_ssg::vDoneDeploy() { vector angles = getAngles(); baseYaw = angles_y; SetDeployingFinished(); } target_marker deployable_ssg::vCreateTargetMarker() { string entityDef = getKey( "def_marker" ); if ( entityDef == "" ) { return $null; } generic_target_marker marker = new generic_target_marker; marker.Init( self, entityDef, getKey( "mtr_marker_cm" ), getFloatKey( "cm_marker_sort" ) ); return marker; } void deployable_ssg::ClearFiringDecal() { if ( firingDecal != $null_entity ) { delete firingDecal; } if ( !sys.isClient() ) { if ( firingMarker != $null_entity ) { thread G_DelayRemoveEntity( firingMarker, 5.f ); firingMarker = $null_entity; } } } void deployable_ssg::CreateFiringDecal() { if ( !sys.isClient() ) { firingMarker = G_CreateFiringMarker( self, firingMarker, target ); } entity p = sys.getLocalPlayer(); if ( p == $null_entity ) { return; } if ( getEntityAllegiance( p ) != TA_FRIEND ) { return; } if ( firingDecal == $null_entity ) { firingDecal = vCreateTargetMarker(); } firingDecal.SetTargetPosition( target ); }