Back to Index Page generated: Dec 20, 2024, 7:22:09 AM

Expansion Sniper Gun

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Sniper Gun is a strong long range laser placed outside of hull for the best cooling. Ships must hold a clearly visible big gun mount to use it: you find Asp SG, Boa SG, Cobra Mark III SG, Fer-de-Lance SG, Krait SG and Viper SG in high-tech shipyards. Press 'i' for more info about Heavy Sniper Gun and Sapper mode of this weapon. Heavy Sniper Gun has 2x power but need large ship like Python HSG, Python BattleCruiser or Prototype Boa. Sapper is the overdrive mode of Sniper Gun: deliver 5x more damage at point blank range than at max. range but use 5x energy and make double heat. The 5x hit is within 500m, 2x at 2km, 1x at 20km. Note the base damage/second value is only the half than in Sniper mode, so at and within 500m provide 2.5x more damage than a Sniper Gun in the same time. Cause some damage when a shot narrowly misses the target and can mine asteroids. You can set auto or manual switching between Sniper Gun and Sapper using Sapper Mode equipment. Anti-Sapper equipments and Hard Shields almost negate the effect. Can not be separated and break your cloak when you fire. Overheat can damage Sapper and reduce it to Sniper Gun, in this case you must repair Sapper Mode. Sniper Gun is a strong long range laser placed outside of hull for the best cooling. Ships must hold a clearly visible big gun mount to use it: you find Asp SG, Boa SG, Cobra Mark III SG, Fer-de-Lance SG, Krait SG and Viper SG in high-tech shipyards. Press 'i' for more info about Heavy Sniper Gun and Sapper mode of this weapon. Heavy Sniper Gun has 2x power but need large ship like Python HSG, Python BattleCruiser or Prototype Boa. Sapper is the overdrive mode of Sniper Gun: deliver 5x more damage at point blank range than at max. range but use 5x energy and make double heat. The 5x hit is within 500m, 2x at 2km, 1x at 20km. Note the base damage/second value is only the half than in Sniper mode, so at and within 500m provide 2.5x more damage than a Sniper Gun in the same time. Cause some damage when a shot narrowly misses the target and can mine asteroids. You can set auto or manual switching between Sniper Gun and Sapper using Sapper Mode equipment. Anti-Sapper equipments and Hard Shields almost negate the effect. Can not be separated and break your cloak when you fire. Overheat can damage Sapper and reduce it to Sniper Gun, in this case you must repair Sapper Mode.
Identifier oolite.oxp.Norby.Sniper_Gun oolite.oxp.Norby.Sniper_Gun
Title Sniper Gun Sniper Gun
Category Weapons Weapons
Author Norby Norby
Version 1.3 1.3
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
  • oolite.oxp.Norby.Separated_Lasers:1.0
  • oolite.oxp.Norby.Separated_Lasers:1.0
  • Conflict Expansions
    Information URL http://wiki.alioth.net/index.php/Sniper_Gun n/a
    Download URL https://wiki.alioth.net/img_auth.php/e/e9/Sniper_Gun_1.3.oxz n/a
    License CC BY-NC-SA 4 CC BY-NC-SA 4
    File Size n/a
    Upload date 1610873504

    Documentation

    Also read http://wiki.alioth.net/index.php/Sniper%20Gun

    Equipment

    Name Visible Cost [deci-credits] Tech-Level
    Anti-Sapper Equipment yes 200000 14+
    Sapper Mode yes 60000 1+
    Heavy Sapper yes 2060000 12+
    Heavy Sniper Gun yes 2000000 12+
    Sapper yes 260000 12+
    Sniper Gun yes 200000 12+

    Ships

    Name
    Asp SG
    asp-sg-player
    Boa SG
    boa-sg-player
    Cobra Mark III SG
    cobra3-sg-player
    Constrictor SG
    constrictor-sg-player
    Fer-de-Lance SG
    ferdelance-sg-player
    Krait SG
    krait-sg-player
    Python BattleCruiser
    python-battlecruiser-player
    Python HSG
    python-hsg-player
    python-sg
    python-sg-player
    Sniper Gun
    snipergun-dummy
    snipergun-dummy-npc
    Heavy Sniper Gun
    Heavy Sniper Gun
    Heavy Sniper Gun
    Heavy Sniper Gun L
    snipergun-heavy-l-npc
    snipergun-heavy-npc
    Heavy Sniper Gun R
    snipergun-heavy-r-npc
    Heavy Sniper Gun 2
    Heavy Sniper Gun 2 L
    snipergun-heavy2-l-npc
    snipergun-heavy2-npc
    Heavy Sniper Gun 2 R
    snipergun-heavy2-r-npc
    Sniper Gun L
    snipergun-l-npc
    snipergun-npc
    Sniper Gun R
    snipergun-r-npc
    Viper SG
    viper-sg-player

    Models

    This expansion declares no models.

    Scripts

    Path
    Scripts/sappermode.js
    "use strict";
    this.name	= "sappermode";
    this.author	= "Norby";
    this.copyright	= "2016 Norby";
    this.description= "Sapper Mode equipment script";
    this.licence	= "CC BY-NC-SA 4.0";
    
    this._w = worldScripts.snipergun;
    
    //equipment events for Sapper Mode equipment
    this.activated = function() {
            var w = this._w;
            w._sapperSwitchPlayer( w );
    }
    
    this.mode = function() {
            var w = this._w;
            w.$SapperMode++;
            if( w.$SapperMode > 8 ) w.$SapperMode = 1;
            var near = w._setSapperDist( player.ship, w );
            var far = player.ship.script.$SapperFarDistance;
            switch( w.$SapperMode ) {
                    case 1: //auto mode
                            player.consoleMessage( "Sapper in auto mode ("+near+"-"+far
                                    +" km distance and heat).", 4.5 );
                            break;
                    case 2: //preset mode (1-2 km)
                            player.consoleMessage( "Sapper in preset mode ("+near+"-"+far
                                    +" km distance and heat).", 4.5 );
                            break;
                    case 3: //0. distance mode (5-6 km)
                    case 4: //1. distance mode (2-3 km)
                    case 5: //2. distance mode (1-2 km)
                    case 6: //3. distance mode (0.5-1 km)
                            player.consoleMessage( "Sapper in target distance mode ("+near+"-"+far
                                    +" km).", 4.5 );
                            break;
                    case 7: //heat mode
                            player.consoleMessage( "Sapper in laser heat mode.", 4.5 );
                            break;
                    case 8: //manual mode
                    default:
                            var n = String.fromCharCode( oolite.gameSettings.keyConfig.key_activate_equipment );
                            player.consoleMessage( "Sapper in manual mode, press '"+n+"' to activate.", 4.5 );
                            break;
            }
    }
    
    Scripts/snipergun-conditions.js
    "use strict";
    this.name	= "snipergun-conditions";
    this.author	= "Norby";
    this.copyright	= "2016 Norby";
    this.description= "Sniper Gun condition script";
    this.licence	= "CC BY-NC-SA 4.0";
    
    //equipment condition
    this.allowAwardEquipment = function(equipment, ship, context)
    {
            // OXP hook to allow stations to forbid specific equipment
            if (context == "purchase" && player.ship.dockedStation
                    && player.ship.dockedStation.scriptInfo["oolite-barred-equipment"])
            {
                    if (player.ship.dockedStation.scriptInfo["oolite-barred-equipment"].indexOf(equipment) != -1)
                    {
                            return false;
                    }
            }
    
            // OXP hook to allow ships to forbid specific "available to all" equipment
            if (ship.scriptInfo && ship.scriptInfo["oolite-barred-equipment"]
                    && ship.scriptInfo["oolite-barred-equipment"].indexOf(equipment) != -1)
            {
                    return false;
            }
    
    //      player.consoleMessage( eqKey+" "+ship+" "+context );//debug
    
            if( equipment == "EQ_ANTISAPPER" ) {
                    if( ship.mass < 30000 ) return false;
                    return true;
            }
    
            var w = worldScripts.snipergun;
            return w._allowAwardSniperGun(equipment, ship, context, w);
    }
    
    Scripts/snipergun.js
    "use strict";
    this.name	= "snipergun";
    this.author	= "Norby";
    this.copyright	= "2016 Norby";
    this.description= "Sniper Gun in subentity";
    this.licence	= "CC BY-NC-SA 4.0";
    //Sniper Guns are usable only for ships over certain mass and with a big gun subentity.
    
    //customizable properties
    this.$DmgCh = 0.3; //chance in % when overheat cause damage in Sapper (0:never, 1:always)
    this.$Hot = 0.81; //Sapper will switch back to Sniper over this heat, red zone start at 0.8
    this.$ManualOverride = 0.25; //seconds after manual sapper activation without auto change back
    this.$SapperActivationVolume = 1.0; //loudness of sapper activation sound, adjust between 0 and 1
    this.$SapperDistance = 2; //km, in preset mode sapper auto switch on within this target distance
    this.$SapperFarDistance = 5; //km, in preset mode sapper switch back to sniper over this
    this.$SapperHitVolume = 0.9; //loudness of sapper hit sounds, adjust between 0 and 1
    this.$SapperMinEnergy = 64; //Sapper need at least this much energy in banks to fire
    
    //internal properties, should not touch
    this.$AC = false; //store a pointer to worldScripts.["Auto Crosshairs"] if installed
    this.$AConTargetCrosshairs = false; //store original crosshairs of autocrosshairs
    this.$Cwp = null; //currentWeapon when a switch between sapper and sniper is initiated
    this.$Delay = 2; //switch between sapper and sniper gun need this many seconds
    this.$HS = false; //store a pointer to worldScripts.hardships if installed
    this.$OverHeat = 0.85; //Sapper has $DmgCh chance to damage over this heat, laser cutoff at 0.85
    this.$SapperDistances = [5, 2, 1, 0.5, 0.5, 0.5]; //sapper auto distances for multiple lasers
    this.$SapperMode = 1; //sapper mode: 1=auto, 2=preset, 3-8=distances, 9=laser heat, 10=manual
    this.$SL = false; //store a pointer to worldScripts.separatedlasers if installed
    this.$SLReplace = false; //flag for allowAwardSniperGun with separated lasers
    this.$Sound = null; //soundsource of partial and far sapper hit on player's target
    this.$Sound0 = null; //soundsource of sapper hit on player's target
    this.$Sound1 = null; //additional soundsource of sapper hit on player's target for more volume
    this.$Sound2 = null; //soundsource of partial sapper hit on the player
    this.$Sound3 = null; //soundsource of sapper activation
    this.$Sound4 = null; //soundsource of sapper deactivation
    this.$Sound5 = null; //soundsource of sapper break due to overheat
    this.$Timer = null; //timer for check and auto switch between sapper and sniper
    this.$TCr = -1; //store crosshairs in timer during switching animation
    this.$TOHeat = false; //flag to check heat once only each time when exceed $OverHeat limit
    this.$TSapper = 0; //countdown in timer for changes between sniper and sapper
    this.$VDir = null; //current viewDirection when a switch between sapper and sniper is initiated
    this.$XH = false; //store a pointer to worldScripts.XenonHUD if installed
    
    //worldScript events
    
    this.startUp = function() {
            var a = worldScripts["Auto Crosshairs"];
            if( a ) {
                    this.$AC = a;
                    //make sure SG's autoCrosshairs run after AC's shipTargetAcquired
                    a.$SG = this;//worldScripts.snipergun;
                    a._shipTargetAcquired2 = a.shipTargetAcquired;
                    a.shipTargetAcquired = function( t ) {
                        a._shipTargetAcquired2( t );
                        a.$SG.autoCrosshairs( a.$SG ); //update autocrosshairs
                    }
            }
            var s = worldScripts.separatedlasers;
            if( s ) this.$SL = s;
            var h = worldScripts.hardships;
            if( h ) this.$HS = h;
    
            this.$Sound = new SoundSource;  //soundsource of partial and far sapper hit on player's target
            this.$Sound.sound = "sapperhits.ogg";
            this.$Sound.loop = false;
            this.$Sound.repeatCount = 1;
            this.$Sound.volume = this.$SapperHitVolume;
    
            this.$Sound0 = new SoundSource;  //soundsource of sapper hit on player's target
            this.$Sound0.sound = "sapperhits.ogg";
            this.$Sound0.loop = false;
            this.$Sound0.repeatCount = 1;
            this.$Sound0.volume = this.$SapperHitVolume;
    
            this.$Sound1 = new SoundSource;  //additional soundsource of sapper hit on player's target
            this.$Sound1.sound = "sapperhits.ogg";
            this.$Sound1.loop = false;
            this.$Sound1.repeatCount = 1;
            this.$Sound1.volume = this.$SapperHitVolume;
    
            this.$Sound2 = new SoundSource;  //soundsource of sapper hit on the player
            this.$Sound2.sound = "sapperhits.ogg";
            this.$Sound2.loop = false;
            this.$Sound2.repeatCount = 1;
            this.$Sound2.volume = this.$SapperHitVolume;
    
            this.$Sound3 = new SoundSource;  //soundsource of sapper activation
            this.$Sound3.sound = "sapper-on.ogg";
            this.$Sound3.loop = false;
            this.$Sound3.repeatCount = 1;
            this.$Sound3.volume = this.$SapperActivationVolume;
    
            this.$Sound4 = new SoundSource;  //soundsource of sapper deactivation
            this.$Sound4.sound = "sapper-off.ogg";
            this.$Sound4.loop = false;
            this.$Sound4.repeatCount = 1;
            this.$Sound4.volume = this.$SapperActivationVolume;
    
            this.$Sound5 = new SoundSource;  //soundsource of sapper break due to overheat
            this.$Sound5.sound = "sapperbreak.ogg";
            this.$Sound5.loop = false;
            this.$Sound5.repeatCount = 1;
            this.$Sound5.volume = 1;
    
            var m = missionVariables.$SapperMode;
            if( m ) this.$SapperMode = m;
            this._setSapperDist( player.ship, this ); //player sapper distance based on $SapperMode
    
            var h = worldScripts.XenonHUD;
            if( h ) {
                    this.$XH = h;
                    var sg = "xenon_crosshairs_alt4.png"; //cross and many stuff
                    var sp = "xenon_crosshairs_alt2.png"; //double circle
                    var hsg = "xenon_crosshairs_alt5.png"; //thick blue cross
                    var hsp = "xenon_crosshairs_alt3.png"; //oval and dot
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SNIPER_GUN", filename:sg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SNIPER_GUN_2", filename:sg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SNIPER_GUN_3", filename:sg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SNIPER_GUN_4", filename:sg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SNIPER_GUN_5", filename:sg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_SAPPER", filename:sp});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN", filename:hsg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN_2", filename:hsg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN_3", filename:hsg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN_4", filename:hsg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN_5", filename:hsg});
                    h.$customCrosshairs({laser:"EQ_WEAPON_CANNON_HEAVY_SAPPER", filename:hsp});
            }
    
            this.playerBoughtNewShip(); //validate sniper guns
    }
    
    this.alertConditionChanged = function(newCondition, oldCondition) {
            if( newCondition < 2 && this._isSapper( player.ship.currentWeapon, this ) )
                    this._startSwitchToSniper( player.ship, this );
    }
    
    this.playerBoughtEquipment = function(equipmentKey) {
            this._validate(equipmentKey, this);
            this._setSapperDist( player.ship, this ); //player sapper distance based on $SapperMode
    }
    
    this.playerBoughtNewShip = function() {
            //search for sniper guns and validate
            this._validate("EQ_WEAPON_CANNON_SNIPER_GUN", this);
            this._validate("EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN", this);
            this._validate("EQ_WEAPON_CANNON_SAPPER", this);
            this._validate("EQ_WEAPON_CANNON_HEAVY_SAPPER", this);
    }
    
    this.playerWillSaveGame = function(message) {
            missionVariables.$SapperMode = this.$SapperMode;
    }
    
    this.shipAttackedOther = function(other) {
            var p = player.ship; //replace player.ship to this.ship in ship scripts
            var w = this;
            if( other != player.ship ) { //must exclude player when called from ship scripts
                    var dmg = w._sapper( p, other, 1, w );
                    if( dmg > 0 ) w._sapperBreak( p, w ); //check overheat damage
                    w._checkSapperMode( p, other, w ); //switch between sniper and sapper in auto modes
            }
    }
    
    this.shipBeingAttacked = function(whom) {
            //do not use shipBeingAttacked in ship scripts due to if attacker is cloaked then no source ship
            //but must use here for sure at least for the player due to injecting into the ship script
            //is not enough if ship script is changed after spawned like in escortdeck
            var p = player.ship; //replace player.ship to this.ship in ship scripts
            var w = this;
            var dmg = w._sapper( whom, p, 1, w );
            if( dmg > 0 ) w._sapperBreak( whom, w ); //check overheat damage
    }
    
    this.shipBeingAttackedUnsuccessfully = function(whom) {
            var p = player.ship; //replace player.ship to this.ship in ship scripts
            var w = this;
            var dmg = w._sapper( whom, p, 0.5, w ); //half damage only
            if( dmg > 0 ) {
                    w._sapperBreak( whom, w ); //check overheat damage
                    w._switchToSniper( whom, w ); //NPC switch back to sniper when missed
                    w.$Sound2.play(); //notify the player about the hit on his ship
            }
    }
    
    this.shipLaunchedFromStation = function(station) {
            this.autoCrosshairs( this ); //update autocrosshairs first time
    }
    
    this.shipSpawned = function(ship) {
            //Inject the following functions into all ship scripts
            //except player.ship which use these in snipergun worldscript
            if( ship == player.ship ) return; //exclude player for sure
            var w = this;
    
            if( w._checkSubEnt(ship, "snipergun", w) >= 0 ) { //find snipergun subentity
                    //in HardShips AntiSapper requires Hard Shield X so award this first
                    if( w.$HS ) ship.awardEquipment("EQ_HARDSHIELD_10");
                    ship.awardEquipment("EQ_ANTISAPPER"); //default on all SG ships
                    ship.awardEquipment("EQ_SAPPERMODE"); //must to Sapper be able to damaged by overheat
                    var d = w._setSapperDist( ship, w ); //set default sapper distance for multiple lasers
    //              log(w.name, ship.name+" SapperDistance set to "+d+" km."); //debug
            }
    
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
            var s = ship.script;
            s.$wsg = w; //worldScripts.snipergun
    
            var sa = s.shipAttackedOther;
            if( sa ) eval("s._SniperGun_shipAttackedOther_Orig = "+sa);
            //must exclude player from the next function when called from ship scripts
            //due to player is handled in shipBeingAttacked in snipergun worldScript
            //and comments are pulled out from the eval string
            eval("s.shipAttackedOther = function(other) { \
                    var p = this.ship; \
                    var w = this.$wsg; \
                    var dmg = 0; \
                    if( other != player.ship ) dmg = w._sapper( p, other, 1, w ); \
                    if( dmg > 0 ) w._sapperBreak( p, w ); \
                    w._checkSapperMode( p, other, w ); \
                    if( this._SniperGun_shipAttackedOther_Orig ) \
                            this._SniperGun_shipAttackedOther_Orig(other); \
            }");
    
            var sb = s.shipBeingAttackedUnsuccessfully;
            if( sb ) eval("s._SniperGun_shipBeingAttackedUnsuccessfully_Orig = "+sb);
            //0.5 in _sapper() mean half damage only
            //if(whom != player.ship) mean NPC switch back from sapper to sniper gun
            //else player switch back to sniper in auto mode if needed
            //_sapperBreak() check overheat damage
            eval("s.shipBeingAttackedUnsuccessfully = function(whom) { \
                    var w = this.$wsg; \
                    var dmg = w._sapper(whom, this.ship, 0.5, w); \
                    if( dmg > 0 ) { \
                            if(whom != player.ship) \
                                    w._switchToSniper( whom, w ); \
                            else { \
                                    w._checkSapperMode( whom, this.ship, w ); \
                            } \
                            w._sapperBreak(whom, w); \
                    } \
                    if( this._SniperGun_shipBeingAttackedUnsuccessfully_Orig ) \
                            this._SniperGun_shipBeingAttackedUnsuccessfully_Orig(whom); \
            }");
    
            var st = s.shipTargetAcquired;
            if( st ) eval("s._SniperGun_shipTargetAcquired_Orig = "+st);
            //switch between sniper and sapper
            eval("s.shipTargetAcquired = function(t) { \
                    var w = this.$wsg; \
                    w._checkSapperMode( this.ship, t, w ); \
                    if( this._SniperGun_shipTargetAcquired_Orig ) \
                            this._SniperGun_shipTargetAcquired_Orig(t); \
            }");
    }
    
    this.shipTargetAcquired = function(t) {
            this._checkSapperMode( player.ship, t, this ); //switch between sniper and sapper
            this.autoCrosshairs( this ); //update autocrosshairs
    }
    
    this.viewDirectionChanged = function(viewString) {
            //sapper in auto mode could set different distances for aft and forward sappers
            if( this.$SapperMode == 1 ) this._setSapperDist( player.ship, this );
            this.autoCrosshairs( this ); //update autocrosshairs
    }
    
    
    //snipergun functions
    
    this._allowAwardSniperGun = function(equipment, ship, context, w) {
            //called from snipergun-condition.js and separatedlasers.js also
            if( !w ) w = worldScripts.snipergun;
    
            //Sniper Gun can not fit into very small ships below 30t mass
            if( ship.mass < 30000 ) return false;
    
            var datakey = "snipergun"; //begin of dataKeys in shipdata.plist
    
            //Heavy Sniper Gun need 250t mass like Python, need indexOf instead of == for separated lasers
            if( equipment.indexOf("EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN") > -1
                    || equipment.indexOf("EQ_WEAPON_CANNON_HEAVY_SAPPER") > -1 ) {
                    if( ship.mass < 250000 ) return false;
                    datakey = "snipergun-heavy"; //dataKey of Heavy Sniper Gun in shipdata.plist
            }
    
    
            //check if there is a proper subentity or snipergun_dummy on the ship
            var sub = w._checkSubEnt(ship, datakey, w);
    //      log(w.name, ship.name+" "+equipment+" "+context+" sub="+sub); //debug
    
            if( sub >= 0 ) {  //found subentity
                    //sapper gun and sapper mode equipment is purchasable
                    if( w.$SLReplace //do not use context != "purchase" due to a core bug
                                     //which set context to purchase even if called from script
                            || !w._isSniper(equipment, this) 
                            //must exclude when player is not docked else switch back from sapper
                            //during fligth will not work
                            || ship != player.ship || !player.ship.docked ) return true;
                    //sniper purchase need no sapper on board to prevent free cash after mode switching
                    if( !w._isSapper(ship.forwardWeapon, this)
                        && !w._isSapper(ship.aftWeapon, this)
                        && !w._isSapper(ship.portWeapon, this)
                        && !w._isSapper(ship.starboardWeapon, this) ) return true;
                    return false;
            } else if( context == "scripted") {
                    log(w.name, "Missing snipergun subentity on "+(ship.name)+" for "+
                            equipment+" but awarded due to context is scripted");
                    return true;
            }
    
            return false; //refused
    }
    
    this._aft = function(o) { //check if o is oriented aft
            var v = Vector3D(0, 0, -1); //aft direction
            if( o.vectorForward().direction().distanceTo(v) == 0 ) return true; //exactly aft
            return false; //not exactly aft
    }
    
    this.autoCrosshairs = function ( w ) { //update autocrosshairs
            if( !w ) w = worldScripts.snipergun;
            var p = player.ship;
            if( !w.$AC || !p || !p.isValid
                    || w.$AC.$crosshairs && w.$AC.$crosshairs[p.hud] ) { //skip custom hud crosshairs
                    return;
            }
            var t = p.target;
            var k = w._isSniperOrSapper( p.currentWeapon );
            if( k == 1  ) { //Sniper Gun with 20km range
                    if( t && t.isValid && p.position.distanceTo(t.position) - t.collisionRadius < 20000 )
                            w.autoCrosshairsLock( w );
                    else {
                            w.$AConTargetCrosshairs = w.$AC.$onTargetCrosshairs; //save original
                            w.$AC.$onTargetCrosshairs = "crosshairs.plist";
                    }
            } else if( k == 2  ) { //Heavy Sniper Gun with 25km range
                    if( t && t.isValid && p.position.distanceTo(t.position) - t.collisionRadius < 25000 )
                            w.autoCrosshairsLock( w );
                    else {
                            w.$AConTargetCrosshairs = w.$AC.$onTargetCrosshairs; //save original
                            w.$AC.$onTargetCrosshairs = "crosshairs.plist";
                    }
            } else if( k == 3 || k == 4 ) { //Sapper with 500m optimal range
                    if( t && t.isValid && p.position.distanceTo(t.position) - t.collisionRadius < 500 )
                            w.autoCrosshairsLock( w );
                    else {
                            w.$AConTargetCrosshairs = w.$AC.$onTargetCrosshairs; //save original
                            w.$AC.$onTargetCrosshairs = "crosshairs.plist";
                    }
            } else {
                    if( w.$AConTargetCrosshairs ) {
                            w.$AC.$onTargetCrosshairs = w.$AConTargetCrosshairs; //restore original
                            w.$AConTargetCrosshairs = false;
                    }
            }
            w.$AC.$currentState = null; //force crosshairs replace in the next frame
    }
    
    this.autoCrosshairsLock = function ( w ) { //lock with autocrosshairs
            w.$AConTargetCrosshairs = w.$AC.$onTargetCrosshairs; //save original
            if( w.$XH && w.$XH.$xenonHUDActive() ) //original autocrs looks better with Xenon HUD
                    w.$AC.$onTargetCrosshairs = "auto-crosshairs-on-target.plist";
            else w.$AC.$onTargetCrosshairs = "snipergun-crosshairs-box.plist";
    }
    
    this._checkAftSubEnt = function(ship, datakey, w) {
            if( !w ) w = worldScripts.snipergun;
            return w._checkSubEnt2(ship, datakey, w, 2);
    }
    
    this._checkForwardSubEnt = function(ship, datakey, w) {
            if( !w ) w = worldScripts.snipergun;
            return w._checkSubEnt2(ship, datakey, w, 1);
    }
    
    this._checkSapperMode = function( ship, other, w ) {
            var p = player.ship;
            if( !w ) w = worldScripts.snipergun;
            if( ship.energy < w.$SapperMinEnergy && w._isSapper( ship.currentWeapon, w ) ) {
                    if( ship == player.ship )
                            player.consoleMessage( "Not enough energy for Sapper" );
                    w._startSwitchToSniper( ship, w );
                    return;
            }
            if( ship.equipmentStatus("EQ_SAPPERMODE") != "EQUIPMENT_OK" //don't work if damaged
                    || ship == p && w.$SapperMode == 8 ) return; //or player in manual mode
            if( !other || !other.isValid ) {
                    //overheat should be checked even if no target
                    if( w._isSapper( ship.currentWeapon, w ) &&
                        w._isHot( ship, w ) && ( ship != p  //NPC or
                                //player not in target distance modes
                                || w.$SapperMode <= 2 || w.$SapperMode == 7 ) )
                        w._startSwitchToSniper( ship, w );
            } else if( w._isSapper( ship.currentWeapon, w ) ) { //check if sapper should switch to sg
                    if( other.script && other.script.$hasAntiSapper ) { //back to sg if has AntiSapper
                            w._startSwitchToSniper( ship, w );
                    } else if( ship != p || w.$SapperMode == 1 || w.$SapperMode == 2 ) {
                            //NPC, auto or preset mode need both condition
                            if( w._isFar( ship, other, w ) || w._isHot( ship, w ) )
                                    w._startSwitchToSniper( ship, w );
                    } else if( w.$SapperMode > 2 && w.$SapperMode < 7 //target distance modes
                                    && w._isFar( ship, other, w ) //target is too far
                            || w.$SapperMode == 7 && w._isHot( ship, w ) ) { //laser is in red heat
                                    w._startSwitchToSniper( ship, w );
                    }
            } else if( w._isSniper( ship.currentWeapon, w )  //check if sg should switch to sapper
                    && (!other.script || !other.script.$hasAntiSapper )) { //do not against AntiSapper
                    if( ship != p || w.$SapperMode == 1 || w.$SapperMode == 2 ) {
                            //NPC, auto or preset mode need both condition
                            if( w._isNear( ship, other, w ) && !w._isHot( ship, w ) )
                                    w._startSwitchToSapper( ship, w );
                    } else if( w.$SapperMode > 2 && w.$SapperMode < 7 //target distance modes
                                    && w._isNear( ship, other, w ) //target is enough near
                            || w.$SapperMode == 7 && !w._isHot( ship, w ) ) { //laser is not in red heat
                                    w._startSwitchToSapper( ship, w );
                    }
            }
    }
    
    this._checkSubEnt = function(ship, datakey, w) {
            if( !w ) w = worldScripts.snipergun;
            return w._checkSubEnt2(ship, datakey, w, 0);
    }
    
    this._checkSubEnt2 = function(ship, datakey, w, flag) {
            if( !w ) w = worldScripts.snipergun;
            //check if there is a proper subentity or snipergun-dummy on the ship
            var sub = -1;
            var s = ship.subEntities;
            if( s && s.length > 0 ) for(var i=0; i < s.length; i++) {
                    if( s[i].dataKey.indexOf(datakey) == 0 ) { //matching at the first character
                            if( flag == 2 && w._aft(s[i].orientation) ) sub = i; //aft found
                            if( flag == 1 && !w._aft(s[i].orientation) ) sub = i; //forward found
                            if( flag == 0 ) sub = i; //either fw or aft found for allowAwardEquipment
    //                      if(sub == i) log(w.name, s[i].dataKey+" subentity with orientation "
    //                                +s[i].orientation+" flag:"+flag+(w._aft(s[i].orientation))); //debug
                    }
            }
            return sub; //return the found index in subentity array or -1 if not found
    }
    
    this._getMulti = function(ship, w) { //how many multiple lasers are on this ship in current mount
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( ship.currentWeapon == ship.aftWeapon ) return w._getMultiAft(ship, w);
            if( ship.currentWeapon == ship.portWeapon ) return w._getMultiPort(ship, w);
            if( ship.currentWeapon == ship.starboardWeapon ) return w._getMultiSb(ship, w);
            return w._getMultiFw(ship, w);  //forward mount
    }
    
    this._getMultiAft = function(ship, w) {  //how many aft multiple lasers are on this ship
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
            var m = ship.script._cachedAftWeapons; //read cached variable
            if( !m ) {
                    m = w._getMultiSide(ship, 2); //read aft multiple weapons length
                    ship.script._cachedAftWeapons = m; //store into a cache variable
            }
            return( m );
    }
    
    this._getMultiFw = function(ship, w) {  //how many forward multiple lasers are on this ship
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
            var m = ship.script._cachedFwWeapons; //read cached variable
            if( !m ) {
                    m = w._getMultiSide(ship, 1); //read forward multiple weapons length
                    ship.script._cachedFwWeapons = m; //store into a cache variable
            }
            return( m );
    }
    
    this._getMultiPort = function(ship, w) {  //how many port multiple lasers are on this ship
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
            var m = ship.script._cachedPortWeapons; //read cached variable
            if( !m ) {
                    m = w._getMultiSide(ship, 3); //read port multiple weapons length
                    ship.script._cachedPortWeapons = m; //store into a cache variable
            }
            return( m );
    }
    
    this._getMultiSb = function(ship, w) {  //how many starboard multiple lasers are on this ship
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
            var m = ship.script._cachedSbWeapons; //read cached variable
            if( !m ) {
                    m = w._getMultiSide(ship, 4); //read starboard multiple weapons length
                    ship.script._cachedSbWeapons = m; //store into a cache variable
            }
            return( m );
    }
    
    this._getMultiSide = function(ship, side) {  //how many multiple lasers are on this ship
            var length = 1;  //default is a single laser
            if( 0 < oolite.compareVersion("1.83") ) length = 1; //no multiple before Oolite 1.83
            else {
                    var shipdata = Ship.shipDataForKey(ship.dataKey);
                    if( shipdata && shipdata["weapon_mount_mode"] == "multiply" ) {
                            if( side == 2 ) length = ship.weaponPositionAft.length;
                            else if( side == 3 ) length = ship.weaponPositionPort.length;
                            else if( side == 4 ) length = ship.weaponPositionStarboard.length;
                            else length = ship.weaponPositionForward.length; //forward is the default
                    }
            }
            return (length);
    }
    
    this._isFar = function(ship, other, w) { //target is too far to use sapper?
            if( !w ) w = worldScripts.snipergun;
            if( ship && ship.isValid && other && other.isValid ) {
                    var d = ship.position.distanceTo(other.position);
                    if( ship.script && ship.script.$SapperFarDistance ) { //set in _setSapperDist()
                            if( d > ship.script.$SapperFarDistance * 1000 ) return true;
                    } else { //fallback to preset far distance
                            if( d > w.$SapperFarDistance * 1000 ) return true;
                    }
            }
            return false;
    }
    
    this._isHeavy = function( weapon, w ) { //weapon is a heavy sniper gun or a heavy sapper?
            if( !weapon ) return false;
            if( !w ) w = worldScripts.snipergun;
            var k = w._isSniperOrSapper( weapon );
            if( k == 2 || k == 4 ) return( k );
            return false;
    }
    
    this._isHot = function(ship, w) { //current laser is too hot so in red heat zone?
            if( ship && ship.isValid ) { //multiple sappers make multiple heat with a shot
                    var h = w.$Hot - ( w._getMulti(ship, w) - 1 ) * 0.039; // 10/256=3.9% heat
                    if( ship.laserHeatLevel > h ) return true;
            }
            return false;
    }
    
    this._isNear = function(ship, other, w) { //target is enough near to switch to sapper?
            if( !w ) w = worldScripts.snipergun;
            if( ship && ship.isValid && other && other.isValid ) {
                    var d = ship.position.distanceTo(other.position);
                    if( ship.script && ship.script.$SapperDistance ) { //set in _setSapperDist()
                            if( d < ship.script.$SapperDistance * 1000 ) return true;
                    } else { //fallback to preset near distance
                            if( d < w.$SapperDistance * 1000 ) return true;
                    }
            }
            return false;
    }
    
    this._isSapper = function( weapon, w ) { //weapon is a sapper?
            if( !weapon ) return false;
            if( !w ) w = worldScripts.snipergun;
            var k = w._isSniperOrSapper( weapon );
            if( k == 3 || k == 4 ) return( k );
            return false;
    }
    
    this._isSniper = function( weapon, w ) { //weapon is a sniper gun (and not a sapper)?
            if( !weapon ) return false;
            if( !w ) w = worldScripts.snipergun;
            var k = w._isSniperOrSapper( weapon );
            if( k == 1 || k == 2 ) return( k );
            return false;
    }
    
    this._isSniperOrSapper = function( weapon ) {
            var eq;
            if( weapon && weapon.equipmentKey ) eq = weapon.equipmentKey;
            else eq = weapon; //the key is directly in the parameter
            if( !eq || !eq.length ) return false;
            //indexOf check separated guns also
            if( eq.indexOf("EQ_WEAPON_CANNON_SNIPER_GUN") > -1 ) return 1;
            if( eq.indexOf("EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN" ) > -1 ) return 2;
            if( eq.indexOf("EQ_WEAPON_CANNON_SAPPER") > -1 ) return 3;
            if( eq.indexOf("EQ_WEAPON_CANNON_HEAVY_SAPPER" ) > -1 ) return 4;
            return false;
    }
    
    this._refund = function(eq, length) { //refund weapon price, length is the size of multiple weapons array
            var f = 1;
            if( player.ship && player.ship.dockedStation ) {
                    var e = player.ship.dockedStation.equipmentPriceFactor;
                    if( e > 0 ) f = e;
            }
            if( 0 < oolite.compareVersion("1.83") ) length = 1; //no multiple weapons before Oolite 1.83
            var refund = 0, n = "Laser";
            var k = EquipmentInfo.infoForKey( eq );
            if( k ) {
                    refund = f * length * k.price/10;
                    player.credits += refund;
                    n = k.name;
            }
            var m = n+" removed from invalid mount, "+refund+" credits refunded.";
            player.consoleMessage(m, 10);
            log(this.name, m);
            return( refund );
    }
    
    this._replaceLasers = function(ship, w) { //call replace in separated lasers OXP
            if( w.$SL ) {
                    w.$SLReplace = true; //needed to allowAwardSniperGun allow replace
                    w.$SL._Replace_Lasers(ship, w.$SL, false); //replace without refund
                    w.$SLReplace = false; //do not allow at purchase
            }
    }
    
    this._sapper = function(source, target, x, w) { //apply sapper damage
            if( !w ) w = worldScripts.snipergun;
            var dmg = 0;
            if( source && source.currentWeapon && target && target.isValid ) {
                    var d = 0;
                    var k = w._isSapper( source.currentWeapon, w );
                    if ( k == 3 ) d = 10; //base damage of a Sapper, must be equal with damage in eq plist
                    else if ( k == 4 ) d = 20; //base damage of a Heavy Sapper, equal with damage in eq plist
                    if( d > 0 ) {
                            if( !x ) x = 1;
                            x = Math.min( 1, Math.max( 0, x ) ); //x must be min. 0 and max. 1
                            d = d * x;
                            if( source.isCloaked ) source.isCloaked = false; //break cloak
                            dmg = d * w._sapperBonus( source, target, w );
                            dmg = Math.min( target.energy - 1, dmg ); //do not destroy the ship
                            var e, name, p = player.ship;
                            if( target == p ) {
                                    if( p.forwardShield > dmg/2 && p.aftShield > dmg/2 ) {
                                            p.forwardShield -= dmg/2; //both shields are affected
                                            p.aftShield -= dmg/2;
                                    } else p.energy -= dmg;
                                    e = Math.round( p.forwardShield ) + "+" +
                                            + Math.round( p.aftShield ) + " shield and "
                                            + Math.round( p.energy );
                                    name = "Player";
                            } else { //NPC
                                    target.energy -= dmg;
                                    e = Math.round(target.energy);
                                    name = target.name + " " + target.entityPersonality;
                                    if( source == p ) {
                                            if( dmg < 2*d || x < 1 ) { //partial or far sapper hit
                                                    w.$Sound.play();
                                            } else if( dmg < d * 4 ) { //average sapper hit (2x volume)
                                                    w.$Sound0.play();
                                                    w.$Sound1.play();
                                            } else { //maximal sapper hit (3x volume)
                                                    w.$Sound.play();
                                                    w.$Sound0.play();
                                                    w.$Sound1.play();
                                            }
                                    }
                            }
                            if( source == p || target == p ) { //do not log NPC-NPC hits
                                    var dm = "Sapper damage: "+Math.round(dmg);
                                    if( x < 1 ) dm = "partial ("+x+"x) " + dm;
                                    log( this.name, name+" taking "+dm+", "+e+" energy left");
                            }
                    }
            }
            return( dmg );
    }
    
    this._sapperBonus = function( source, target, w ) { //calculate sapper multiplier
            if( !source || !source.isValid || !target || !target.isValid ) return( 0 );
            if( !w ) w = worldScripts.snipergun;
            if( target.equipmentStatus("EQ_SAPPERNULLIFIER") == "EQUIPMENT_OK" ) {
                    if( target.script ) target.script.$hasAntiSapper = true;
                    if( source == player.ship )
                            player.consoleMessage( target.name+" has Sapper Nullifier equipment" );
                    return( 0 );
            }
            var dist = source.position.distanceTo(target.position) - target.collisionRadius; //as on target box
            var s = 1; //1x damage is given by the core, s contain the bonus over core so s=1 mean 2x in total
            //2x damage at 2km, 4x at 1km, 5x within 500m, but only single damage over 20km
            if( dist < 2000 ) {
                    s = Math.max( 1, 4 - Math.max(0, dist - 500)/500 ); //fast gain within 2km
            } else s = (20000 - dist)/18000; //slow dissipation over 2km down to no bonus at 20km
    //      var m = w._getMulti(source, w);//multiple laser - commented out due to handled by the core well
    //      if( m > 1 ) s = s * Math.min( m, 3 ); //max. 3x, let's say other lasers are too far from center
            if( w.$HS ) s = s - w.$HS.$HardShips_GetHardShieldLevel( target ) / 3;
            if( target.equipmentStatus("EQ_ANTISAPPER") == "EQUIPMENT_OK" ) {
                    if( target.script ) target.script.$hasAntiSapper = true;
                    if( source == player.ship )
                            player.consoleMessage( target.name+" has Anti-Sapper equipment" );
                    s = s / 2; //half damage
            }
            return( Math.max( 0, s ) ); //exclude negative values
    } //return the bonus damage over the base value (which is applied by the core), so 4 mean 5x dmg.
    
    this._sapperBreak = function(ship, w) { //check if sapper is break in overheat
            if( !w ) w = worldScripts.snipergun;
            //log(w.name, ship.name+" Heat:"+ship.laserHeatLevel);//debug
            var brk = false;
            if( ship.laserHeatLevelForward > w.$OverHeat && w._isSapper( ship.forwardWeapon, w ) ) {
                    if( Math.random() < w.$DmgCh ) brk = true;
            }
            if( ship.laserHeatLevelAft > w.$OverHeat && w._isSapper( ship.aftWeapon, w ) ) {
                    if( Math.random() < w.$DmgCh ) brk = true;
            }
            if( ship.laserHeatLevelPort > w.$OverHeat && w._isSapper( ship.portWeapon, w ) ) {
                    if( Math.random() < w.$DmgCh ) brk = true;
            }
            if( ship.laserHeatLevelStarboard > w.$OverHeat
                    && w._isSapper( ship.starboardWeapon, w ) ) {
                    if( Math.random() < w.$DmgCh ) brk = true;
            }
            if( brk ) {
                    if( ship.equipmentStatus("EQ_SAPPERMODE") == "EQUIPMENT_OK" ) {
                            ship.setEquipmentStatus("EQ_SAPPERMODE", "EQUIPMENT_DAMAGED");
                            var n;
                            var m = "Sapper Mode damaged by overheat, reduced to Sniper Gun";
                            if( ship.currentWeapon != ship.forwardWeapon )
                                    ship.forwardWeapon = w._sapperToSniper( ship.forwardWeapon );
                            if( ship.currentWeapon != ship.aftWeapon )
                                    ship.aftWeapon = w._sapperToSniper( ship.aftWeapon );
                            if( ship.currentWeapon != ship.portWeapon )
                                    ship.portWeapon = w._sapperToSniper( ship.portWeapon );
                            if( ship.currentWeapon != ship.starboardWeapon )
                                    ship.starboardWeapon = w._sapperToSniper( ship.starboardWeapon );
                            if( ship == player.ship ) {
                                    n = "Player's ";
                                    player.consoleMessage( m, 10 );
                                    w.$Sound5.play(); //sound of sapper break due to overheat
                                    w._startSwitchToSniper( ship, w );
                            } else {
                                    n = ship.name+" "+ship.entityPersonality;
                                    ship.currentWeapon = w._sapperToSniper( ship.currentWeapon );
                            }
                            log(w.name, n+" "+m);//debug
                            //Overheat damaged Sapper Mode, reduce all Sappers to Sniper Gun
                    }
                    //replace to separated lasers without refund
                    w._replaceLasers(ship, w);
            }
            return( brk );
    }
    
    this._sapperSwitchPlayer = function( w ) { //called from equipment script when activated
            if( !w ) w = worldScripts.snipergun;
            var p = player.ship;
            if( !p || !p.isValid ) return;
            if( p.equipmentStatus("EQ_SAPPERMODE") == "EQUIPMENT_DAMAGED" ) {
                    player.consoleMessage( "Sapper Mode is damaged, can not switch." );
                    return;
            } else if( p.equipmentStatus("EQ_SAPPERMODE") != "EQUIPMENT_OK" ) return;
    
            w._sapperSwitch( p, w );
            w.$TManual = w.$ManualOverride * 4;  //no auto change right after manual keypress
    }
    
    this._sapperSwitch = function( ship, w ) {
            if( !w ) w = worldScripts.snipergun;
            if( w._isSniper( ship.currentWeapon, w ) ) {
                    w._startSwitchToSapper( ship, w );
            } else if( w._isSapper( ship.currentWeapon, w ) ) {
                    w._startSwitchToSniper( ship, w );
            }
    }
    
    this._sapperToSniper = function(weapon) {
            if( !weapon || !weapon.equipmentKey ) return ("");
            if( weapon.equipmentKey.indexOf("EQ_WEAPON_CANNON_SAPPER") > -1 )
                    return ("EQ_WEAPON_CANNON_SNIPER_GUN");
            if( weapon.equipmentKey.indexOf("EQ_WEAPON_CANNON_HEAVY_SAPPER") > -1 )
                    return ("EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN");
            return (weapon.equipmentKey); //no change
    }
    
    this._setSapperDist = function( ship, w ) { //set Sapper Distance, will switch to Sniper Gun outside
            if( !w ) w = worldScripts.snipergun;
            if( !ship || !ship.isValid ) return 0;
            if( !ship.script ) ship.setScript("oolite-default-ship-script.js"); //for sure
    
            var d = 2;//default distance
            if( ship == player.ship && w.$SapperMode == 2 ) { //player's preset mode
                    ship.script.$SapperDistance = d = w.$SapperDistance;
                    ship.script.$SapperFarDistance = w.$SapperFarDistance;
            } else {
                    if( ship == player.ship ) {
                            if( w.$SapperMode == 1 ) { //auto mode, different for forward and aft mounts
                                    var m = Math.min( 5, Math.max( 0, w._getMulti(ship, w) ) );
                                    d = w.$SapperDistances[ m ]; //select optimal distance for multiple lasers
                                    ship.script.$SapperDistance = d; //used in _isNear()
                            } else { //distance modes (3-6)
                                    d = w.$SapperDistances[ w.$SapperMode - 3 ]; //0-5 is valid in array
                                    if( d > 0 ) ship.script.$SapperDistance = d; //set a valid distance
                                    //fallback to preset mode in invalid mode for sure
                                    else ship.script.$SapperDistance = d = w.$SapperDistance;
                            }
                    } else { //NPC always use forward mount to determine optimal distance for multiple lasers
                            var m = Math.min( 5, Math.max( 0, w._getMultiFw(ship, w) ) );
                            d = w.$SapperDistances[ m ];
                            ship.script.$SapperDistance = d; //used in _isNear()
                    }
                    //if switch to sapper within 0.5km then back to sniper over 1km
                    if( d < 1 ) ship.script.$SapperFarDistance = d + 0.5; //used in _isFar()
                    else ship.script.$SapperFarDistance = d + 1; //1km hysteresis before switch back to sg
            }
            return( d );
    }
    
    this._sniperToSapper = function(weapon) {
            if( !weapon || !weapon.equipmentKey ) return ("");
            if( weapon.equipmentKey.indexOf("EQ_WEAPON_CANNON_SNIPER_GUN") > -1 )
                    return ("EQ_WEAPON_CANNON_SAPPER");
            if( weapon.equipmentKey.indexOf("EQ_WEAPON_CANNON_HEAVY_SNIPER_GUN") > -1 )
                    return ("EQ_WEAPON_CANNON_HEAVY_SAPPER");
            return (weapon.equipmentKey); //no change
    }
    
    this._startSwitchToSniper = function( ship, w ) {
            if( !w ) w = worldScripts.snipergun;
            if( ship == player.ship ) { //start delay
                    if( w.$TSapper == 0 ) { //make sure no changing in progress
                            //positive  mean sapper to sniper in timer
                            w.$TSapper = Math.max( 1, Math.ceil(w.$Delay*4) );
                            w.$VDir = player.ship.viewDirection;
                            if( w.$VDir != "VIEW_AFT" && w.$VDir != "VIEW_PORT"
                                    && w.$VDir != "VIEW_STARBOARD" ) {
                                    w.$VDir = "VIEW_FORWARD";  //in custom or gui view also
                                    w.$Cwp = ship.forwardWeapon;
                                    ship.forwardWeapon = null;
                            } else {
                                    w.$Cwp = ship.currentWeapon;
                                    ship.currentWeapon = null;
                            }
                            if( w.$XH  && w.$XH.$xenonHUDActive() ) 
                                    w.$XH.viewDirectionChanged( w.$VDir ); //update crosshairs
                            else {
                                    if( w.$TCr == -1 ) w.$TCr = player.ship.crosshairs;
                                    var h = "";
                                    if( w._isHeavy( w.$Cwp, w ) ) h = "heavy";
                                    player.ship.crosshairs = "snipergun-crosshairs-7"+h+".plist";
                            }
                            w.$Sound4.play(); //sapper off
                    }
            } else w._switchToSniper( ship, w ); //NPC switch instantly
    }
    
    this._startSwitchToSapper = function( ship, w ) {
            if( !w ) w = worldScripts.snipergun;
            if( ship.energy < w.$SapperMinEnergy ) {
                    if( ship == player.ship )
                            player.consoleMessage( "Not enough energy for Sapper" );
                    return;
            }
            if( ship == player.ship ) { //start delay
                    if( w.$TSapper == 0 ) { //make sure no changing in progress
                            //negative mean sniper to sapper in timer
                            w.$TSapper = Math.min( -1, -Math.ceil(w.$Delay*4) );
                            w.$VDir = player.ship.viewDirection;
                            if( w.$VDir != "VIEW_AFT" && w.$VDir != "VIEW_PORT"
                                    && w.$VDir != "VIEW_STARBOARD" ) {
                                    w.$VDir = "VIEW_FORWARD";  //in custom or gui view also
                                    w.$Cwp = ship.forwardWeapon;
                                    ship.forwardWeapon = null;
                            } else {
                                    w.$Cwp = ship.currentWeapon;
                                    ship.currentWeapon = null;
                            }
                            if( w.$XH && w.$XH.$xenonHUDActive() )
                                    w.$XH.viewDirectionChanged( w.$VDir ); //update crosshairs
                            else {
                                    if( w.$TCr == -1 ) w.$TCr = player.ship.crosshairs;
                                    var h = "";
                                    if( w._isHeavy( w.$Cwp, w ) ) h = "heavy";
                                    player.ship.crosshairs = "snipergun-crosshairs-1"+h+".plist";
                            }
                            w.$Sound3.play(); //sapper on
                    }
            } else w._switchToSapper( ship, w ); //NPC switch instantly
    }
    
    this._switchToSapper = function( ship, w ) { //instant switch weapons
            if( !w ) w = worldScripts.snipergun;
            var vd = player.ship.viewDirection;
            if( ship == player.ship && w.$VDir ) {
                    var nw = w._sniperToSapper( w.$Cwp ); //new weapon
                    if( w.$VDir == "VIEW_AFT" ) ship.aftWeapon = nw;
                    else if( w.$VDir == "VIEW_PORT" ) ship.portWeapon = nw;
                    else if( w.$VDir == "VIEW_STARBOARD" ) ship.starboardWeapon = nw;
                    else ship.forwardWeapon = nw;
                    w.$Cwp = null;
                    w.$VDir = null;
            } else ship.currentWeapon = w._sniperToSapper( ship.currentWeapon ); //NPC
            if( ship == player.ship ) {
                    if( w.$XH ) w.$XH.viewDirectionChanged( vd ); //update Xenon HUD crosshairs
                    w.autoCrosshairs( w ); //update autocrosshairs
    //                if( ship.currentWeapon )
    //                        player.consoleMessage( ship.currentWeapon.name+" activated" );
            }
            //separated laser replace is not needed due to sappers are not separable weapons
    }
    
    this._switchToSniper = function( ship, w ) { //instant switch weapons
            if( !w ) w = worldScripts.snipergun;
            var vd = player.ship.viewDirection;
            if( ship == player.ship && w.$VDir ) {
                    var nw = w._sapperToSniper( w.$Cwp ); //new weapon
                    if( w.$VDir == "VIEW_AFT" ) ship.aftWeapon = nw;
                    else if( w.$VDir == "VIEW_PORT" ) ship.portWeapon = nw;
                    else if( w.$VDir == "VIEW_STARBOARD" ) ship.starboardWeapon = nw;
                    else ship.forwardWeapon = nw;
                    w.$Cwp = null;
                    w.$VDir = null;
            } else ship.currentWeapon = w._sapperToSniper( ship.currentWeapon ); //NPC
            if( ship == player.ship ) {
                    if( w.$XH ) w.$XH.viewDirectionChanged( vd ); //update crosshairs
                    w.autoCrosshairs( w ); //update autocrosshairs
    //                if( ship.currentWeapon )
    //                        player.consoleMessage( ship.currentWeapon.name+" activated" );
            }
            w._replaceLasers(ship, w); //separated lasers
    }
    
    this._timed = function() { //regular target distance check and delayed sapper-sniper changes
            var p = player.ship;
            var w = this;
            if( !p || !p.isValid ) return;
            if( p.laserHeatLevel > w.$OverHeat ) {
                    if( !w.$TOHeat ) {  //check overheat damage
                            w.$TOHeat = true; //do not check again until not below $OverHeat
                            w._sapperBreak( p, w );
                    }
            } else w.$TOHeat = false;
            if( w.$TSapper == 0 ) { //make sure no changing in progress
                    if( w.$TManual > 0 ) w.$TManual--; //no auto change right after manual keypress
                    else w._checkSapperMode( p, p.target, w );
            } else { //changing in progress
                    if( w.$TSapper > 0 ) { //sapper to sniper
                            if( w.$TSapper > 1 ) {
                                    w.$TSapper--; //countdown to 1
                                    if( !w.$XH || !w.$XH.$xenonHUDActive() ) {
                                            if( w.$TCr == -1 ) w.$TCr = player.ship.crosshairs;
                                            var h = "";
                                            if( w._isHeavy( w.$Cwp, w ) ) h = "heavy";
                                            var tc = Math.min( 7, w.$TSapper ); //animation in max. 7 steps
                                            player.ship.crosshairs = "snipergun-crosshairs-"+tc+h+".plist";
                                            //player.consoleMessage(player.ship.crosshairs, 1);//debug
                                    }
                            } else { //change at 1
                                    w.$TSapper = 0;
                                    if( !w.$XH || !w.$XH.$xenonHUDActive() ) {
                                            if( !w.$TCr ) w.$TCr = "crosshairs.plist"; //never set to null!
                                            player.ship.crosshairs = w.$TCr; //end of animation
                                            //player.consoleMessage(player.ship.crosshairs, 1);//debug
                                            w.$TCr = -1;
                                    }
                                    w._switchToSniper( p, w );
                            }
                    } else  { //sniper to sapper
                            if( w.$TSapper < -1 ) {
                                    w.$TSapper++; //count up to -1
                                    if( !w.$XH || !w.$XH.$xenonHUDActive() ) {
                                            if( w.$TCr == -1 ) w.$TCr = player.ship.crosshairs;
                                            var h = "";
                                            if( w._isHeavy( w.$Cwp, w ) ) h = "heavy";
                                            var tc = Math.max( 1, w.$TSapper+8 ); //animation in max. 7 steps
                                            player.ship.crosshairs = "snipergun-crosshairs-"+tc+h+".plist";
                                            //player.consoleMessage(player.ship.crosshairs, 1);//debug
                                    }
                            } else { //change at -1
                                    w.$TSapper = 0;
                                    if( !w.$XH || !w.$XH.$xenonHUDActive() ) {
                                            if( !w.$TCr ) w.$TCr = "crosshairs.plist"; //never set to null!
                                            player.ship.crosshairs = w.$TCr; //end of animation
                                            //player.consoleMessage(player.ship.crosshairs, 1);//debug
                                            w.$TCr = -1;
                                    }
                                    w._switchToSapper( p, w );
                            }
                    }
            }
            if( w.$TCr == -1 ) w.autoCrosshairs( w ); //update autocrosshairs
    }
    
    this._validate = function(equipmentKey, w) {
        //called from playerBoughtEquipment, playerBoughtNewShip and startUp
        if( !w ) w = worldScripts.snipergun;
        var valid = false;
        var datakey = false; //dataKey of snipergun subentity
        var sg = w._isSniperOrSapper( equipmentKey );
        if( sg == 1 || sg == 3 ) datakey = "snipergun"; //sniper gun or sapper
        else if( sg == 2 || sg == 4 ) datakey = "snipergun-heavy"; //heavy sniper or heavy sapper
        if( datakey ) { //player bought a sniper gun
                var p = player.ship;
    //          log(w.name, equipmentKey+" "+datakey+" "+p.forwardWeapon+" "+p.aftWeapon+" "+p.portWeapon+" "+p.starboardWeapon); //debug
    
                //check if there is a sniper gun in the ship
                //can not put sniper guns into side mounts
                if( p.portWeapon && p.portWeapon.equipmentKey //match at start for separated lasers
                        && p.portWeapon.equipmentKey.indexOf( equipmentKey ) == 0 ) {
                        w._refund( p.portWeapon.equipmentKey, w._getMultiPort(p, w) );
                        p.portWeapon = null;
                }
    
                if( p.starboardWeapon && p.starboardWeapon.equipmentKey
                        && p.starboardWeapon.equipmentKey.indexOf( equipmentKey ) == 0 )  {
                        w._refund( p.starboardWeapon.equipmentKey, w._getMultiSb(p, w)  );
                        p.starboardWeapon = null;
                }
    
                //check if there is a needed subentity in the ship
                //aft mount need aft orientation
                if( p.aftWeapon && p.aftWeapon.equipmentKey
                        && p.aftWeapon.equipmentKey.indexOf( equipmentKey ) == 0 ) {
                        if( !(w._checkAftSubEnt(p, datakey, w) >= 0) ) {
                                w._refund( p.aftWeapon.equipmentKey, w._getMultiAft(p, w) );
                                p.aftWeapon = null;
                        } else valid = true;
                }
    
                //forward mount need subentity with non-aft orientation
                if( p.forwardWeapon && p.forwardWeapon.equipmentKey
                        && p.forwardWeapon.equipmentKey.indexOf( equipmentKey ) == 0 ) {
                        if( !(w._checkForwardSubEnt(p, datakey, w) >= 0) ) {
                                w._refund( p.forwardWeapon.equipmentKey, w._getMultiFw(p, w) );
                                p.forwardWeapon = null;
                        } else valid = true;
                }
    
                if( valid ) {
                        if( sg == 3 || sg == 4 ) p.awardEquipment("EQ_SAPPERMODE");
                        if( !w.$Timer ) w.$Timer = new Timer(w, w._timed.bind(this), 0.25, 0.25);
                }
    
        }
        return( valid );
    }