Back to Index Page generated: May 8, 2024, 6:16:03 AM

Expansion SniperLockPlus

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description SniperLock Plus software keeps you on target, at ranges longer than 5km, with off-center weapon compensation SniperLock Plus software keeps you on target, at ranges longer than 5km, with off-center weapon compensation
Identifier oolite.oxp.dybal.SniperLockPlus oolite.oxp.dybal.SniperLockPlus
Title SniperLockPlus SniperLockPlus
Category Equipment Equipment
Author Dybal Dybal
Version 1.0.2 1.0.2
Tags Equipment Equipment
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/SniperLockPlus n/a
Download URL https://wiki.alioth.net/img_auth.php/f/f4/SniperLockPlus-1.0.2.oxz n/a
License CC BY-NC-SA 4.0 CC BY-NC-SA 4.0
File Size n/a
Upload date 1610873513

Documentation

Also read http://wiki.alioth.net/index.php/SniperLockPlus

Readme.txt

SniperLock Plus
---------------
v1.0.2, by Dybal

SniperLock Plus is an upgraded version of SniperLock.

Like SniperLock, it makes small corrections to your ship steering to keep you on-target if you are in Forward or Aft views and your target is at least 5km away, but unlike its cousin it is able to compensate for of-center weapons.

It doesn't change the ship's optical pickups' position, so if the ship has an off-center weapon, when SniperLock Plus is active the target will be locked aligned with the weapon and will appear off-center in the crosshairs.


Aknowledgements
---------------

This OXP is based on CommonSenseOTB's SniperLock OXP.

I would rather have updated the otiginal OXP, but since SniperLock OXP's license doesn't allow for modifications of the original OXP, I wrote instead an OXP that adds to it, without trying to completely supersed it: SniperLock Plus equipment costs significantly more and only brings a real benefit for those ships with weapons way off the longitudinal axis... for the vast majority of ships, whose weapons are not that off-center, SniperLock still offers better value.


Compatibility
-------------

This OXP requires Oolite version 1.89 or above.


License
-------

This work is release under the Creative Commons Attribution - Non-Commercial - Share Alike 4.0 (CC BY-NC-SA 4.0)
(https://creativecommons.org/licenses/by-nc-sa/4.0/)


Version History
---------------

Version 1.0.2 (december/2020):

* Fixes bug when calculating ship model weapon position offset.


Version 1.0.1 (december/2020):

* Fixes some issues with the plist format on Mac.


Version 1.0 (october/2020):

* Initial version

Equipment

Name Visible Cost [deci-credits] Tech-Level
SniperLock Plus Software & Control Interface yes 15000 13+
Sell SniperLock Plus yes 0 12+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
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;
}