Script:Files:script/deployables/ssg.script

From Mod Wiki
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.getLocalPlayer() != $null_entity ) {
        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 );
}