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