Script:Files:script/deployables/artillery.script

From Mod Wiki
object deployable_artillery : deployable_base {
    void    preinit();
    void    init();
    void    destroy();
    void    syncFields();

    void    vDoneDeploy();

    void    TurningThread();

    void    ServerIdle();
    void    ServerAiming();
    void    ServerFiring();

    void    ClientIdle();
    void    ClientFiring();

    void    StopRotationSounds();

    void    vTargetSetTarget( vector targetPos, entity targetEnt );
    boolean    vTargetGetValid( vector targetPos );
    boolean    vTargetPlayerEligible( entity p );

    float    vGetFireSupportMode();

    //
    boolean    InRange( vector targetPos );
    boolean    InFiringRange( vector targetPos ) { return InRange( targetPos ); }
    void    UpdateTurning();
    boolean    UpdateYaw();
    boolean UpdatePitch();
    void    ResetAngles();
    void    TurnToward( float yaw, float pitch );
    boolean PitchValid( float pitch );
    boolean    CalcTargetAngles( vector targetPos );
    string    MuzzleForMissile( float missileIndex );

    // utility funcs ( none of these should be blocking )
    void    Launch( float missileIndex );
    void    FireMissile( float missileIndex );
    void    LaunchForEffect();

    float    GetMissileBarrelLength( float index );
    void    SetMissileBarrelLength( float index, float value );
    float    GetMissileSideOffset( float index );
    void    SetMissileSideOffset( float index, float value );

    void    OnFireSupportStateChanged();

    target_marker vCreateTargetMarker();

    void    ClearFiringDecal();    
    void    CreateFiringDecal();

    void    ReloadThread( float delay );

    float    recycle;
    float    fireCount;
    float    reloadWait;
    float    numMissiles;
    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;
    vector    firingOffset;
    vector    firingVelocity;
    boolean    hasTarget;
    entity    targetPlayerEnemy;
    vector    targetLocation;
    float    projectileIndex;
    float    projectileForEffectIndex;    

    generic_target_marker        firingDecal;
    entity                        firingMarker;

    boolean anglesLocked;
}

float deployable_artillery::GetMissileBarrelLength( float index ) {
    return getFloatKey( "missile" + ( index + 1 ) + "_barrel_length" );
}

void deployable_artillery::SetMissileBarrelLength( float index, float value ) {
    setKey( "missile" + ( index + 1 ) + "_barrel_length", value );
}

float deployable_artillery::GetMissileSideOffset( float index ) {
    return getFloatKey( "missile" + ( index + 1 ) + "_side_offset" );
}

void deployable_artillery::SetMissileSideOffset( float index, float value ) {
    setKey( "missile" + ( index + 1 ) + "_side_offset", value );
}

void deployable_artillery::OnFireSupportStateChanged() {
    if ( fireSupportState == MPS_FIRING ) {
        if ( clientState != ART_CS_FIRING ) {
            setState( "ClientFiring" );
        }
    } else {
        if ( clientState != ART_CS_IDLE ) {
            setState( "ClientIdle" );
        }
    }
}

void deployable_artillery::syncFields() {
    syncBroadcast( "nextFire" );
    syncBroadcast( "targetLocation" );
    syncBroadcast( "fireSupportState" );
    syncBroadcast( "idealYaw" );
    syncBroadcast( "idealPitch" );
    syncBroadcast( "oldIdealYaw" );
    syncBroadcast( "oldIdealPitch" );

    syncCallback( "fireSupportState", "OnFireSupportStateChanged" );

    sync( "currentYaw" );
    sync( "currentPitch" );
}

void deployable_artillery::destroy() {
    ClearFiringDecal();
    delete fsStatus;
}

float deployable_artillery::vGetFireSupportMode() {
    return TARGET_ARTILLERY;
}

void deployable_artillery::preinit() {
    recycle            = getFloatKey( "missile_recycle" );
    fireCount        = getFloatKey( "missile_firecount" );
    reloadWait        = getFloatKey( "missile_reload" );
    numMissiles        = getFloatKey( "num_missiles" );
    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" ) );

    projectileIndex    = GetEntityDef( getKey( "def_projectile" ) );
    projectileForEffectIndex = GetEntityDef( getKey( "def_projectile_foreffect" ) );

    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();
}

void deployable_artillery::vDoneDeploy() {
    vector barrel;
    float i;
    float muzzleHandle;
    vector pitchJointPos;
    float sideOffset;
    float barrelLength;

    pitchJointPos    = getLocalJointPos( jointPitch );
    barrel            = getLocalJointPos( jointBarrel ) - pitchJointPos;
    barrelPitch        = sys.atan2( barrel_z, barrel_x );

    for ( i = 0; i < numMissiles; i++ ) {
        muzzleHandle = getJointHandle( MuzzleForMissile( i ) );
        barrel = getLocalJointPos( muzzleHandle ) - pitchJointPos;
        sideOffset = barrel_y;
        barrel_y = 0;
        barrelLength = sys.vecLength( barrel );

        SetMissileSideOffset( i, sideOffset );
        SetMissileBarrelLength( i, barrelLength );
    }

    vector angles = getAngles();
    baseYaw = angles_y;

    SetDeployingFinished();
}

void deployable_artillery::init() {
    fadeSound( SND_DEPLOYABLE_YAW, -60.f, 0.f );
    fadeSound( SND_DEPLOYABLE_PITCH, -60.f, 0.f );
    startSound( "snd_turret_yaw", SND_DEPLOYABLE_YAW );
    startSound( "snd_turret_pitch", SND_DEPLOYABLE_PITCH );

    if ( sys.isClient() ) {
        setState( "ClientIdle" );
    } else {
        setState( "ServerIdle" );
    }
}

// ==========================================
// States
// ==========================================

void deployable_artillery::TurningThread() {
    while( true ) {
        sys.waitFrame();

        if ( disabledState ) {
            StopRotationSounds();
        } else {
            UpdateTurning();
        }
    }
}

void deployable_artillery::ClientIdle() {
    clientState = ART_CS_IDLE;

    ClearFiringDecal();
}

void deployable_artillery::ServerIdle() {
    hasTarget = false;
    ResetAngles();

    ClearFiringDecal();
}

void deployable_artillery::ReloadThread( float delay ) {
    fireSupportState = MPS_RELOADING;

    sys.wait( delay );

    fireSupportState = MPS_READY;
}

void deployable_artillery::StopRotationSounds() {
    DEPLOYABLE_STOP_YAW_SOUND
    DEPLOYABLE_STOP_PITCH_SOUND
}

void deployable_artillery::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 );

        DEPLOYABLE_PLAY_YAW_SOUND
    } else {
        DEPLOYABLE_STOP_YAW_SOUND
    }

    if ( !pitchDone ) {
        angles_x = currentPitch;
        angles_y = 0;
        angles_z = 0;
        setJointAngle( jointPitch, JOINTMOD_LOCAL, angles );

        DEPLOYABLE_PLAY_PITCH_SOUND
    } else {
        DEPLOYABLE_STOP_PITCH_SOUND
    }

    if ( yawDone && pitchDone ) {
        anglesLocked = true;
    } else {
        anglesLocked = false;
    }
}

void deployable_artillery::ServerAiming() {
    fireSupportState = MPS_FIRING_PREPARE;

    while ( !anglesLocked ) {
        sys.waitFrame();

        if ( disabledState ) {
            fireSupportState = MPS_READY;

            setState( "ServerIdle" );
        }
    }

    setState( "ServerFiring" );
}

void deployable_artillery::ClientFiring() {
    float nextMiniFire    = 0;
    float nextMiniCount = 0;
    float baseFireTime    = 0;

    CreateFiringDecal();

    clientState = ART_CS_FIRING;

    if ( projectileForEffectIndex >= 0 ) {
        LaunchForEffect();        
        sys.wait( getFloatKey( "foreffect_wait" ) );
    }

    while( true ) {
        if ( nextFire < sys.getTime() ) {
            baseFireTime = sys.getTime();
            nextFire = baseFireTime + recycle;

            nextMiniCount = 0;
            nextMiniFire = baseFireTime + getFloatKey( "missile" + ( nextMiniCount + 1 ) + "_delay" );
        }

        if ( nextMiniCount < numMissiles ) {
            if( nextMiniFire < sys.getTime() ) {

                Launch( nextMiniCount );

                nextMiniCount++;
                nextMiniFire = baseFireTime + getFloatKey( "missile" + ( nextMiniCount + 1 ) + "_delay" );
            }
        }

        sys.waitFrame();
    }
}

void deployable_artillery::ServerFiring() {
    float count            = fireCount;
    float nextMiniFire    = 0;
    float nextMiniCount = 0;
    float baseFireTime    = 0;

    fireSupportState = MPS_FIRING;

    nextFire = sys.getTime() + 1.f;

    if ( projectileForEffectIndex >= 0 ) {
        LaunchForEffect();
        sys.wait( getFloatKey( "foreffect_wait" ) );
    }

    objManager.PlaySoundForPlayer( getKey( "snd_firing" ), owner );

    while( true ) {
        if ( disabledState ) {
            break;
        }

        if ( nextFire < sys.getTime() ) {
            baseFireTime = sys.getTime();
            nextFire = baseFireTime + recycle;
            count = count - 1;
            if ( count < 0 ) {
                break;
            }

            nextMiniCount = 0;
            nextMiniFire = baseFireTime + getFloatKey( "missile" + ( nextMiniCount + 1 ) + "_delay" );
        }

        if ( nextMiniCount < numMissiles ) {
            if ( nextMiniFire != 0 && nextMiniFire < sys.getTime() ) {

                Launch( nextMiniCount );

                nextMiniCount++;
                nextMiniFire = baseFireTime + getFloatKey( "missile" + ( nextMiniCount + 1 ) + "_delay" );
            }
        }

        sys.waitFrame();
    }

    nextFire = 0;

    thread ReloadThread( reloadWait );

    setState( "ServerIdle" );
}

// ==========================================
// Utility Funcs
// ==========================================

string deployable_artillery::MuzzleForMissile( float missileIndex ) {
    return getKey( "missile" + ( missileIndex + 1 ) + "_barrel" );
}

void deployable_artillery::FireMissile( float missileIndex ) {
    vector org;

    org = getJointPos( jointPitch ) + firingOffset + ( getWorldAxis( 1 ) * GetMissileSideOffset( missileIndex ) );

    launchMissileSimple( owner, self, targetPlayerEnemy, projectileIndex, -1, spread, org, firingVelocity );

    playEffect( "fx_fire", MuzzleForMissile( missileIndex ), 0 );
}

void deployable_artillery::Launch( float missileIndex ) {
    string anim;
    float channel;

    FireMissile( missileIndex );

    anim = getKey( "missile" + ( missileIndex + 1 ) + "_anim" );
    if ( anim != "" ) {
        channel = getIntKey( "missile" + ( missileIndex + 1 ) + "_channel" );

        playAnim( channel, anim );
    }
}

void deployable_artillery::LaunchForEffect() {
    string anim;
    float channel;
    vector org;

    org = getJointPos( jointPitch ) + firingOffset + ( getWorldAxis( 1 ) * GetMissileSideOffset( 0 ) );

    launchMissileSimple( owner, self, $null_entity, projectileForEffectIndex, -1, 0.0, org, firingVelocity );

//	objManager.PlaySoundForPlayer( getKey( "snd_effect" ), owner );

    playEffect( "fx_fire", MuzzleForMissile( 0 ), 0 );

    anim = getKey( "missile1_anim" );
    if ( anim != "" ) {
        channel = getIntKey( "missile1_channel" );
        playAnim( channel, anim );
    }
}

// ==========================================
// ==========================================

boolean deployable_artillery::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 ) ) * 180.f );

    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_artillery::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 ) ) * 180.f );

    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_artillery::TurnToward( float yaw, float pitch ) {
    oldIdealYaw        = currentYaw;
    oldIdealPitch    = currentPitch;
    idealYaw        = sys.angleNormalize180( yaw );
    idealPitch        = sys.angleNormalize180( pitch );
    anglesLocked    = false;
}

void deployable_artillery::ResetAngles() {
    TurnToward( 0, 0 );
}

void deployable_artillery::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;

    firingPitch = targetPitch - barrelPitch;

    for ( i = 0; i < numMissiles; i++ ) {

        firingVelocity = targetDiff * ( sys.cos( targetPitch ) * missileSpeed );
        firingVelocity_z = sys.sin( targetPitch ) * missileSpeed;

        t = GetMissileBarrelLength( i ) / missileSpeed;

        gravity = gravity * t * t * 0.5f;

        temp = firingVelocity * t;

        firingOffset = temp + gravity;
        firingVelocity = firingVelocity + ( gravity * t );
    }

    TurnToward( targetYaw - baseYaw, -firingPitch );

    targetPlayerEnemy = owner.getEnemy();
    owner.lastValidTarget = targetPos;
    targetLocation = targetPos;

    Base_TargetSetTarget( targetPos, targetEnt );

    CreateFiringDecal();

    setState( "ServerAiming" );
}

boolean deployable_artillery::vTargetGetValid( vector targetPos ) {
    if ( !InRange( targetPos ) ) {
        return 0;
    }

    return CalcTargetAngles( targetPos );
}

boolean deployable_artillery::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 = ( 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_artillery::PitchValid( float pitch ) {
    return pitch < maxPitch && pitch > minPitch;
}

boolean    deployable_artillery::InRange( vector targetPos ) {    
    return true;
}

boolean deployable_artillery::vTargetPlayerEligible( entity p ) {
    if ( disabledState || !finishedDeploying ) {
        return 0.f;
    }

    return 1.f;
}

target_marker deployable_artillery::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_artillery::ClearFiringDecal() {
    if ( firingDecal != $null_entity ) {
        delete firingDecal;
    }

    if ( firingMarker != $null_entity ) {
        thread G_DelayRemoveEntity( firingMarker, 5.f );
        firingMarker = $null_entity;
    }
}

void deployable_artillery::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 );
    }
}


object deployable_rockets : deployable_artillery {
    float    vGetFireSupportMode();
}

float deployable_rockets::vGetFireSupportMode() {
    return TARGET_ROCKETS;
}