| Scripts/sniperlock_plus.js | "use strict";
this.name        = "sniperlock_plus";
this.author      = "Dybal";
this.copyright   = "Copyright 2020 Dybal";
this.license     = "CC BY-NC-SA 4.0"
this.description = "Sniperlock Plus script";
this.version     = "1.0.2";
this.$logging = true;
this.$debug = false;
this.$enabled = true; // Other OXPs can deactivate Sniper Lock Plus by assigning false to this
this.$slpAngleToLock = 0.002;  // How close to the target you have to move the crosshairs for SniperLock Plus to lock; default: 0.002
this.$slpAngleToUnlock = 0.06; // How far from the center of the crosshairs the target has to move between FCBs for SniperLock Plus to unlock; default: 0.06
this.$slpDuration = 12; // How long SniperLock keeps a target loked; default: 12
this.$slpOriginalPitch; // stores the player's ship original maxPitch, so we can detect maneuverability reductions and compensate
this.$slpState = "OFF";
this.$slpTarget; // target in last FCB call
this.$slpLastAngle; // angle to target in last FCB call
//
// Event Handlers
//
//--------------------------------------------------------------------------------------------//
this.startUp = function _slp_startUp() {
    this.$enabled = true; // reset
    this.$slpState = "OFF";
    this.$slpCounter = 0; // to control the time locked on target
    this.$slpTargetPosition_1 = null;// aim is 1 frame behind true target position
    this.$slpTargetPosition_2 = null;// aim is 2 frames behind true target position
    this.$slpTargetPosition_3 = null;// aim is 3 frames behind true target position
    this.$slpOriginalPitch = player.ship.maxPitch;
}  
//--------------------------------------------------------------------------------------------//
this.playerBoughtNewShip = this.playerReplacedShip = function _slp_newShip() {
    this.$slpOriginalPitch = player.ship.maxPitch;
}
//--------------------------------------------------------------------------------------------//
this.playerBoughtEquipment = function _slp_playerBoughtEquipment(equipmentKey) {      
    var _player = player;
    if (equipmentKey === ("EQ_SNIPERLOCK_PLUS_REMOVAL")) {
        let eqInfo = EquipmentInfo.infoForKey("EQ_SNIPERLOCK_PLUS");
        _player.ship.removeEquipment("EQ_SNIPERLOCK_PLUS");
        _player.ship.removeEquipment("EQ_SNIPERLOCK_REMOVAL");
        _player.credits += eqInfo.calculatedPrice * 0.5 / 10;
    } 
}
    
//--------------------------------------------------------------------------------------------//
this.equipmentRemoved = function _slp_equipmentRemoved(equipmentKey) {
    if (equipmentKey === "EQ_SNIPERLOCK_PLUS" && this.$FCB && isValidFrameCallback(this.$FCB))
        removeFrameCallback(this.$FCB);
}
//--------------------------------------------------------------------------------------------//
this.equipmentAdded = function _slp_equipmentAdded(equipmentKey) {
    var ship = player.ship;
    if (equipmentKey === "EQ_SNIPERLOCK_PLUS" && ship.status === "STATUS_IN_FLIGHT") {
        this.$slpState = "OFF";
        this.$FCB = addFrameCallback(this.$sniperLock.bind(this, ship));
    }
}
//--------------------------------------------------------------------------------------------//
this.shipWillLaunchFromStation = function _slp_shipWillLaunchFromStation()    {
    var ship = player.ship;
    if (ship.equipmentStatus("EQ_SNIPERLOCK_PLUS") === "EQUIPMENT_OK") {
        // obtain compensations for off-center weapons
        if (ship.forwardWeapon && ship.forwardWeapon.equipmentKey !== "EQ_WEAPON_NONE") {
            this.$forwardModelCorrection = this.$weaponModelPositionCorrection(ship.weaponPositionForward, "Forward");
            if (this.$logging) log(this.name, ship.displayName+": forward weapon correction: "+this.$forwardModelCorrection);
        } else
            this.$forwardModelCorrection = Vector3D(0,0,0); // player might have changed ship
        if (ship.aftWeapon && ship.aftWeapon.equipmentKey !== "EQ_WEAPON_NONE") {
            this.$aftModelCorrection = this.$weaponModelPositionCorrection(ship.weaponPositionAft, "Aft");
            if (this.$logging) log(this.name, ship.displayName+": aft weapon correction: "+this.$aftModelCorrection);
        } else
            this.$aftModelCorrection = Vector3D(0,0,0); // player might have changed ship
        this.$slpState = "OFF";
        this.$FCB = addFrameCallback(this.$sniperLock.bind(this, ship));
    }
}
//--------------------------------------------------------------------------------------------//
this.shipDockedWithStation = function _slp_shipDockedWithStation() {
    if (this.$FCB && isValidFrameCallback(this.$FCB)) removeFrameCallback(this.$FCB);
}
    
//--------------------------------------------------------------------------------------------//
this.shipWillEnterWitchspace = function _slp_shipWillEnterWitchspace() {
    if (this.$FCB && isValidFrameCallback(this.$FCB)) removeFrameCallback(this.$FCB);
}
    
//--------------------------------------------------------------------------------------------//
this.shipExitedWitchspace = function _slp_shipExitedWitchspace() {
    var ship = player.ship;
 
    if (ship.equipmentStatus("EQ_SNIPERLOCK_PLUS") === "EQUIPMENT_OK") {
        this.$slpState = "OFF";
        this.$FCB = addFrameCallback(this.$sniperLock.bind(this, ship));
    }   
}
    
//--------------------------------------------------------------------------------------------//
//this.shipTargetAcquired = function _slp_shipTargetAcquired() {
//    if (this.$enabled) {
//        this.$slpTargetPosition_1 = null;
//        this.$slpTargetPosition_2 = null;
//        this.$slpTargetPosition_3 = null;
//        this.$slpState = "READY";
//        this.$slpCounter = 0;
//    }
//}
//--------------------------------------------------------------------------------------------//
this.shipDied = function _slp_shipDied() {
    if (this.$FCB && isValidFrameCallback(this.$FCB)) removeFrameCallback(this.$FCB);
}      
//
// Internal Functions
//
//--------------------------------------------------------------------------------------------//
this.$sniperLock = function _slp_sniperLock(ship) {
    var that = _slp_sniperLock;
    var fwdModelCorrection = this.$forwardModelCorrection;
    var aftModelCorrection = this.$aftModelCorrection;
    var applyFwdCorrection = (fwdModelCorrection != Vector3D(0,0,0));
    var applyAftCorrection = (aftModelCorrection != Vector3D(0,0,0));
    var viewDirection = ship.viewDirection;
    var modelCorrection = (viewDirection === "VIEW_FORWARD" ? fwdModelCorrection : aftModelCorrection);
    var applyCorrection = (viewDirection === "VIEW_FORWARD" ? applyFwdCorrection : applyAftCorrection);
    var debug = this.$debug && ship.weaponsOnline;
    var logging = this.$logging;
    // compensate for reduced maneuverability in the unlock angle
    var angleToUnlock = this.$slpAngleToUnlock * (ship.maxPitch / this.$slpOriginalPitch);
    var angleToLock = this.$slpAngleToLock;
    var angleChange;
    if (angleToUnlock <= angleToLock) 
        // if maxPitch is reduced too much, angleToUnlock would become less tha angleToLock, making it impossble to keep a lock
        angleToUnlock = 1.1 * angleToLock;
    if (!ship.target || !this.$enabled || 
        (ship.target && ship.speed > ship.maxSpeed && ship.target.dataKey === "telescopemarker") ||
        ship.equipmentStatus("EQ_SNIPERLOCK_PLUS") !== "EQUIPMENT_OK" ||
        ship.equipmentStatus("EQ_SCANNER_SHOW_MISSILE_TARGET") !== "EQUIPMENT_OK" ||
        (viewDirection !== "VIEW_FORWARD" && viewDirection !== "VIEW_AFT")) {
        if (this.$slpState !== "OFF") {
            this.$slpTargetPosition_1 = null;
            this.$slpTargetPosition_2 = null;
            this.$slpTargetPosition_3 = null;
            this.$slpState = "OFF";
            this.$slpCounter = 0;
            this.$slpTarget = null;
            this.$slpLastAngle = null;
        }
        return;
    }
    var sh =(viewDirection === "VIEW_FORWARD" ? ship.heading : ship.heading.multiply(-1));
    var tp, tv, ta, tp_aim;
    if (ship.target.isValid) {
        tp = ship.target.position;
        this.$slpDistance = ((tp.distanceTo(ship) - ship.target.collisionRadius) / 1000);
    } else {
        this.$slpDistance = 0;
    }
    if (this.$slpDistance > 5.0 && this.$slpDistance < 25.6) {
        if (!this.$slpTarget || this.$slpTarget != ship.target) {
            // new target, reset everything
            if (debug) log(this.name, "Target changed from "+(this.$slpTarget ? this.$slpTarget.displayName : "None")+" to "+ship.target.displayName);
            this.$slpTargetPosition_1 = null;
            this.$slpTargetPosition_2 = null;
            this.$slpTargetPosition_3 = null;
            this.$slpState = "READY";
            this.$slpCounter = 0;
            this.$slpTarget = ship.target;
        }
        tv = tp.subtract(ship).direction(); // direction to target
        ta = sh.angleTo(tv); // angle to target
        angleChange = (this.$slpLastAngle != null ? Math.abs(this.$slpLastAngle - ta) : null); 
        if (this.$slpState === "READY") {
            this.$slpLastAngle = ta;
            if (debug && (angleChange == null || angleChange > 0.000001)) log(this.name, "target: "+ship.target.displayName+", angle to target: "+ta.toFixed(4)+" lock if <= "+angleToLock.toFixed(6)+", angle change: "+(angleChange?angleChange.toFixed(7):"None"));
            if (ta <= this.$slpAngleToLock) {
                // close enough, Lock!
                if (debug) log(this.name, "target: "+ship.target.displayName+", angle to target: "+ta.toFixed(4)+", less than "+this.angleToLock.toFixed(6)+", LOCKED");
                this.$slpTargetPosition_1 = tp;
                this.$slpTargetPosition_2 = tp;
                this.$slpTargetPosition_3 = tp;
                this.$slpLastAngle = null;
                this.$slpState = "ON";
            }
        }
        if (this.$slpState === "ON") {
            this.$slpCounter += 1;
            // if angle to target didn't change from last iteration, weapon is still aligned and nothing to do
            if (angleChange && angleChange < 0.000001) return;
            this.$slpLastAngle = ta;
            if ((angleChange && angleChange > angleToUnlock) || this.$slpCounter > this.$slpDuration) {
            if (debug) log(this.name, "target: "+ship.target.displayName+", angle to target: "+ta.toFixed(4)+", changed: "+angleChange.toFixed(7)+" threshold to unlock "+angleToUnlock.toFixed(6)+", counter: "+this.$slpCounter+", UNLOCKED");
                this.$slpTargetPosition_1 = null;
                this.$slpTargetPosition_2 = null;
                this.$slpTargetPosition_3 = null;
                this.$slpState = "READY";
                this.$slpCounter = 0;
                return;
            }
            if (debug) log(this.name, "target: "+ship.target.displayName+", angle to target: "+ta.toFixed(4)+", changed: "+angleChange.toFixed(7)+" less than "+angleToUnlock.toFixed(6)+", KEPT LOCKED");
                
            if (applyCorrection) {
                var correction = this.$weaponPositionCorrection(ship, modelCorrection);
                tp_aim = this.$slpTargetPosition_3.add(correction);
                if (debug) log(this.name, ship.displayName+" target position: "+this.$slpTargetPosition_3+", aiming point:" + tp_aim);
            } else
                tp_aim = this.$slpTargetPosition_3;
            let tvc = tp_aim.subtract(ship).direction();
            let tac = sh.angleTo(tvc);
            let cr = sh.cross(tvc).direction();
            let new_orientation = ship.orientation.rotate(cr,-tac);
            let dot = new_orientation.dot(ship.orientation); 
            if (dot < 1) {
                if (debug) log(this.name, "Orientation changed, dot="+dot.toFixed(4));
                ship.orientation = ship.orientation.rotate(cr,-ta);
            }
            this.$slpTargetPosition_3 = this.$slpTargetPosition_2;
            this.$slpTargetPosition_2 = this.$slpTargetPosition_1;
            this.$slpTargetPosition_1 = tp;
            this.$slpTarget = ship.target;
        } 
    }
    if (this.$slpState === "ON" && this.$slpDistance <= 5.0) {
        // target closer than minimun distacne, reset but keep target
        this.$slpTargetPosition_1 = null;
        this.$slpTargetPosition_2 = null;
        this.$slpTargetPosition_3 = null;
        this.$slpState = "READY";
        this.$slpCounter = 0;
    }
}
    
//--------------------------------------------------------------------------------------------//
this.$weaponModelPositionCorrection = function _slp_weaponModelPositionCorrection(weaponPosition, str) {
    var avgX, avgY , avgZ;
    avgX = avgY = avgZ = 0;
    var i = weaponPosition.length;
    while (i--) {
        avgX += weaponPosition[i].x;
        avgY += weaponPosition[i].y;
        avgZ += weaponPosition[i].z;
    }
    avgX /= weaponPosition.length;
    avgY /= weaponPosition.length;
    var avg_wp_pos = Vector3D(avgX, avgY, avgZ);
    var z_wp_pos = Vector3D(0, 0, avgZ);
    var correction = z_wp_pos.subtract(avg_wp_pos);
    if (this.$debug) log(this.name, str+": average position: "+avg_wp_pos+", centerline position: "+z_wp_pos+", correction: "+correction);
    return correction
}
//--------------------------------------------------------------------------------------------//
this.$weaponPositionCorrection = function _slp_weaponPositionCorrection(ship, model_correction) {
    var world_correction = model_correction.rotateBy(ship.orientation);
    var debug = this.$debug && player.ship.weaponsOnline;
    if (this.$debug) log(this.name, ship.displayName+" model_correction: "+model_correction+", world: "+world_correction+", ship position: "+ship.position);
    return world_correction;
}
 | 
                
                    | Scripts/sniperlock_plus_conditions.js | "use strict";
this.name        = "sniperlock_plus_conditions";
this.author      = "Dybal";
this.copyright   = "Copyright 2020 Dybal";
this.license     = "CC BY-NC-SA 4.0"
this.description = "Sniperlock Plus conditions script";
this.version     = "1.0";
//--------------------------------------------------------------------------------------------//
this.updateEquipmentPrice = function _slp_updateEquipmentPrice(equipmentKey, currentPrice) {
    var ws = worldScripts.sniperlock_plus;
    var slStatus = player.ship.equipmentStatus("EQ_SNIPERLOCK");
    var slPrice = EquipmentInfo.infoForKey("EQ_SNIPERLOCK").price;
    if (slStatus === "EQUIPMENT_OK") {
        var price = currentPrice - slPrice;
        if (ws.$debug) log(this.name, "Rebating price of EQ_SNIPERLOCK ("+formatCredits(slPrice/10, true, true)+") from "+equipmentKey+" price ("+formatCredits(currentPrice/10,true,true)+"): "+formatCredits(price/10, true, true));
        return price;
    }
    if (ws.$debug) log(this.name, equipmentKey+" price: "+formatCredits(currentPrice/10, true, true));
    return currentPrice;
}
 |