object deployable_plasmamortar : deployable_base { void preinit(); void init(); void destroy(); void syncFields(); void ServerIdle(); void ServerAiming(); void ServerFiring(); void ClientIdle(); void ClientFiring(); void vTargetSetTarget( vector targetPos, entity targetEnt ); boolean vTargetGetValid( vector targetPos ); boolean vTargetPlayerEligible( entity p ); // boolean InRange( vector targetPos ); boolean InFiringRange( vector targetPos ) { return InRange( targetPos ); } boolean CalcTargetAngles( vector targetPos ); // utility funcs ( none of these should be blocking ) void Launch(); void FireMissile(); boolean PitchValid( float pitch ); void ReloadThread( float delay ); void OnFireSupportStateChanged(); float vGetFireSupportMode(); target_marker vCreateTargetMarker(); void ClearFiringDecal(); void CreateFiringDecal(); float recycle; float fireCount; float reloadWait; float fireSyncDelay; float nextFire; float jointMuzzle; float minPitch; float maxPitch; float minRange; float maxRange; float missileSpeed; float spread; float clientState; float targetYaw; float targetPitch; vector targetDiff; float barrelPitch; vector firingVelocity; boolean hasTarget; entity targetPlayerEnemy; float projectileIndex; vector targetLocation; boolean playingPitchSound; boolean playingYawSound; generic_target_marker firingDecal; entity firingMarker; }; void deployable_plasmamortar::syncFields() { syncBroadcast( "nextFire" ); syncBroadcast( "fireSupportState" ); syncCallback( "fireSupportState", "OnFireSupportStateChanged" ); } void deployable_plasmamortar::destroy() { delete fsStatus; } float deployable_plasmamortar::vGetFireSupportMode() { return TARGET_ROCKETS; } void deployable_plasmamortar::preinit() { vector barrel; float entityDeclIndex; float i; float muzzleHandle; vector pitchJointPos; 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; } jointMuzzle = getJointHandle( getKey( "missile_barrel" ) ); entityDeclIndex = sys.getDeclType( "entityDef" ); projectileIndex = sys.getDeclIndex( entityDeclIndex, getKey( "def_projectile" ) ); 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; } void deployable_plasmamortar::init() { vector angles; angles = getAngles(); if ( sys.isClient() ) { setState( "ClientIdle" ); } else { setState( "ServerIdle" ); } } void deployable_plasmamortar::OnFireSupportStateChanged() { if ( fireSupportState == MPS_FIRING ) { if ( clientState != ART_CS_FIRING ) { setState( "ClientFiring" ); } } else { if ( clientState != ART_CS_IDLE ) { setState( "ClientIdle" ); } } } // ========================================== // States // ========================================== void deployable_plasmamortar::ClientIdle() { clientState = ART_CS_IDLE; } void deployable_plasmamortar::ServerIdle() { hasTarget = false; while( true ) { if ( hasTarget ) { setState( "ServerAiming" ); } sys.waitFrame(); } } void deployable_plasmamortar::ReloadThread( float delay ) { fireSupportState = MPS_RELOADING; sys.wait( delay ); fireSupportState = MPS_READY; } void deployable_plasmamortar::ServerAiming() { setState( "ServerFiring" ); } void deployable_plasmamortar::ClientFiring() { float nextMiniFire = 0; float baseFireTime = 0; clientState = ART_CS_FIRING; while( true ) { if ( nextFire < sys.getTime() ) { baseFireTime = sys.getTime(); nextFire = baseFireTime + recycle; Launch(); } sys.waitFrame(); } } void deployable_plasmamortar::ServerFiring() { float count = 0; float nextMiniFire = 0; float baseFireTime = 0; fireSupportState = MPS_FIRING; nextFire = sys.getTime() + 1.f; while( true ) { if ( disabledState ) { break; } if ( nextFire < sys.getTime() ) { baseFireTime = sys.getTime(); nextFire = baseFireTime + recycle; count = count + 1; Launch(); if ( count >= fireCount ) { break; } } sys.waitFrame(); } nextFire = 0; thread ReloadThread( reloadWait ); setState( "ServerIdle" ); } // ========================================== // Utility Funcs // ========================================== void deployable_plasmamortar::FireMissile() { vector org = getJointPos( jointMuzzle ); launchMissileSimple( owner, self, targetPlayerEnemy, projectileIndex, -1, spread, org, firingVelocity ); playJointEffect( "fx_fire", jointMuzzle, 0 ); } void deployable_plasmamortar::Launch() { string anim; float channel; FireMissile(); anim = getKey( "launch_anim" ); if ( anim != "" ) { channel = getIntKey( "launch_channel" ); playAnim( channel, anim ); } } // ========================================== // ========================================== void deployable_plasmamortar::vTargetSetTarget( vector targetPos, entity targetEnt ) { float firingPitch; float t; vector gravity; vector velocity; vector temp; float i; if ( !CalcTargetAngles( targetPos ) ) { return; } gravity_x = 0; gravity_y = 0; gravity_z = -1066; // make target pitch more vertical targetPitch = targetPitch + ( 90 - targetPitch ) * 0.5; firingPitch = targetPitch; firingVelocity = targetDiff * ( sys.cos( targetPitch ) * missileSpeed ); firingVelocity_z = sys.sin( targetPitch ) * missileSpeed; targetPlayerEnemy = owner.getEnemy(); hasTarget = true; Base_TargetSetTarget( targetPos, targetEnt ); } boolean deployable_plasmamortar::vTargetGetValid( vector targetPos ) { if ( !InRange( targetPos ) ) { return 0.f; } return CalcTargetAngles( targetPos ); } boolean deployable_plasmamortar::CalcTargetAngles( vector targetPos ) { vector barrelOrg; vector temp; float diffY; float diffX; float rootA; float count; float root1; float root2; vector modelOffset = targetPos; barrelOrg = getJointPos( jointMuzzle ); targetDiff = modelOffset - 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 = ( 1066 * 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_plasmamortar::PitchValid( float pitch ) { return pitch < maxPitch && pitch > minPitch; } boolean deployable_plasmamortar::InRange( vector targetPos ) { return true; } boolean deployable_plasmamortar::vTargetPlayerEligible( entity p ) { if ( disabledState || !finishedDeploying ) { return 0.f; } return 1.f; } target_marker deployable_plasmamortar::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" ) ); if ( vGetFireSupportMode() == TARGET_ROCKETS ) { marker.SetLocalOnly( true ); } return marker; } void deployable_plasmamortar::ClearFiringDecal() { if ( firingDecal != $null_entity ) { delete firingDecal; } if ( firingMarker != $null_entity ) { thread G_DelayRemoveEntity( firingMarker, 5.f ); firingMarker = $null_entity; } } void deployable_plasmamortar::CreateFiringDecal() { if ( !sys.isClient() ) { firingMarker = G_CreateFiringMarker( self, firingMarker, targetLocation ); } if ( vGetFireSupportMode() == TARGET_ROCKETS ) { return; } entity p = sys.getLocalPlayer(); if ( p == $null_entity ) { return; } if ( getEntityAllegiance( p ) != TA_FRIEND ) { return; } if ( firingDecal == $null_entity ) { firingDecal = vCreateTargetMarker(); } if ( firingDecal != $null_entity ) { firingDecal.SetTargetPosition( targetLocation ); } }