Scripts/fighters-ship.js |
"use strict";
this.name = "fighters-ship";
this.author = "Norby";
this.copyright = "2016 Norbert Nagy";
this.licence = "CC BY-NC-SA 4.0";
this.description = "ship script of fighters";
this.$FighterKey = ""; //store equipmentKey of this fighter to award back when landed
this.$FighterHull = this.ship.maxEnergy; //must repair in hangar, avoid recharge when no shields
this.$FighterNumber = 0; //order of this fighter in escort formation
this.$FighterOwner = null; //store the mothership
this.$FighterShields = true; //cache of this.ship.scriptInfo.npc_shields
this.$ForcedLanding = false; //forced due to damage, do not stop landing with others
this.$OwnerScoopPos = null; //store the mothership's scoop position for landing
this.$SniperLock = false; //cache of shipdata.plist script_info fighters-sniperlock field
this.$TimerLandNow = null; //trigger _landNow() after some time for sure landing
this.$ws = worldScripts.fighters;
//ship script events
this.shipAttackedOther = function(other) {
if( other && other.isDerelict && other == this.ship.target )
this.ship.target = null; //do not destroy derelicts, choose another target instead
}
this.shipBountyChanged = function(delta,reason) {
//prevent orange scanner flag on player's fighters with ShipVersion OXP
if(this.$FighterOwner == player.ship && this.ship.bounty > 0 ) {
this.ship.bounty = 0; //Warning: this change trigger this event again!
} //So must check before that bounty is greater than 0
} //else this is an infinite loop which make Crash To Desktop!
this.shipCollided = this.shipWasScooped = function(otherShip) {
if(otherShip == this.$FighterOwner) {
this.ship.energy = this.ship.maxEnergy; //sometimes prevent destruction
if(this.ship.AIState == "LANDING") //put into the hangar
this._landNow(this.ship, this.$FighterOwner);
}
}
this.shipLaunchedEscapePod = function(escapepod, passengers) {
this.ship.target = null;
this.ship.performHold();
this.ship.switchAI("nullAI.plist");
}
this.shipTakingDamage = function(amount, whom, type) {
this.$FighterHull -= amount; //workaround recharge when no shields
if( !this.$FighterShields ) { //light fighter without shields
if(this.$FighterHull < 0) { //out of hull
delete this.shipTakingDamage;//prevent infinite loop
if(this.ship && this.ship.isValid) {
this.ship.abandonShip(); //eject pilot
this.ship.explode(); //destroy the defeated fighter
}
} else if(this.$FighterHull < this.ship.maxEnergy * 0.4) { //damaged
var fi = this.ship;
var fai = "fightersAI.plist";
if(fi.AI != fai ) fi.switchAI(fai);
fi.target = null; //clear target to stop attack
fi.AIState = "LANDING";
this.$ForcedLanding = true;
}
} else { //make sure damages on heavy fighters are persistent by set ECM to damaged
if(this.ship && this.ship.energy < 0.4 * this.ship.maxEnergy ) {
if( this.ship.equipmentStatus("EQ_ECM") == "EQUIPMENT_OK" ) {
this.ship.setEquipmentStatus("EQ_ECM", "EQUIPMENT_DAMAGED");
if(this.ship.dataKey === "fighters-raider") { //lose a laser
var su = this.ship.subEntities;
if(su && su[1]) su[1].remove();//no true within to display message
}
}
}
}
}
this.shipSpawned = function() {
if( !this.ship ) return;
var si = this.ship.scriptInfo;
if( si && si.npc_shields == "no" //Swarm has regenerating armor without CustiomShields
&& this.ship.primaryRole != "EQ_FIGHTER_SWARM" ) {
this.$FighterShields = false;
} else this.$FighterShields = true;
if( si && si.fighters_sniperlock ) this.$SniperLock = true;
if( this.ship.primaryRole === "EQ_FIGHTER_RAIDER" //stealth fighters in cloaked era
&& ( player.ship.equipmentStatus("EQ_CLOAKING_DEVICE") != "EQUIPMENT_UNAVAILABLE"
|| missionVariables.cloak === "COMPLETE" ) ) {
this.ship.awardEquipment("EQ_CLOAKING_DEVICE");
}
}
this.spawnedAsEscort = function(mother) { //for NPC mothers
if(mother && mother.script) {
if(!mother.script.$LaunchedFighters) mother.script.$LaunchedFighters = 1;
else mother.script.$LaunchedFighters++;
this.$FighterNumber = mother.script.$LaunchedFighters;
if(!mother.script.$Fighters) mother.script.$Fighters = [];
mother.script.$Fighters.push(this.ship);
this.ship.displayName = this.ship.name+" #"+this.$FighterNumber;
}
this.$FighterOwner = mother;
}
this.shipTargetDestroyed = function(target) {
if(this.ship == player.ship || this.$FighterOwner != player.ship )
return; //exit if worldScript, do in ship script only
if(target.primaryRole == "constrictor" && missionVariables.conhunt && missionVariables.conhunt == "STAGE_1") {
// just in case an escort kills the constrictor, let's not break the mission for the player...
missionVariables.conhunt = "CONSTRICTOR_DESTROYED";
}
if(target.isRock || target.isBoulder || target.isCargo || target.isWeapon)
return;
// player.score += 1; - score should remain personal
var b = "";
if(target.bounty > 0) {
b = " : " + target.bounty + "₢ awarded.";
player.credits += target.bounty;
}
player.consoleMessage(this.ship.displayName+" killed "+target.name+b, 5);
log(this.name, this.ship.displayName+" killed " + target.name + " : " + target.bounty +"₢");
}
this.shipDied = function(whom,why) {
if( !this.ship || this.ship == player.ship ) return; //exit if worldScript, do in ship script only
if( this.ship.dataKey === "fighters-swarm" ) {
var ai = system.addShips("fighters-alienitem", 1, this.ship.position, 20);
if( ai && ai[0] && this.ship.velocity) ai[0].velocity = this.ship.velocity;
var e = this.ship.subEntities;
var l = 0;
if( e ) l = e.length;
if( l > 0 ) { //there are subents so not all swarm fighters are lost
var sw = system.addShips("fighters-swarm", 1, this.ship.position, 20)[0];
if(sw) {
sw.target = this.ship.target;
if(this.ship.target) sw.addDefenseTarget(this.ship.target);
if(sw.script) {
sw.script.$FighterKey = this.$FighterKey;
sw.script.$FighterNumber = this.$FighterNumber;
sw.script.$FighterOwner = this.$FighterOwner;
sw.script.$TrailsDisabled = 1; //reduce load with Trails OXP
}
this.$FighterOwner.escortGroup.addShip(sw);
var f;
if(this.$FighterOwner === player.ship) f = this.$ws.$Fighters;
else f = this.$FighterOwner.script.$Fighters;
var fi = f.indexOf(this.ship);
if( fi > -1 ) {
if(this.$FighterOwner === player.ship) this.$ws.$Fighters[fi] = sw;
else this.$FighterOwner.script.$Fighters = sw;
}
sw.velocity = this.ship.velocity;
sw.orientation = this.ship.orientation;
sw.displayName = this.ship.displayName;
if( this.$FighterOwner === player.ship ) {
sw.bounty = 0; //remove bounty from fighter given in shipdata.plist
sw.scannerDisplayColor1 = [0, 0.4, 0]; //green, mean frendly
//si.scannerDisplayColor2 = [0, 0.4, 0]; //green, mean frendly
} else {
if( ship.bounty == 0 ) sw.bounty = 0; //remove bounty from fighter given in shipdata.plist
}
var su = sw.subEntities;
if(su && su[1]) {
su[1].remove(true); //remove a swarm fighter
if( l == 1 ) su[0].remove(true); //remove another also
}
}
}
}
if( this.ship.dataKey === "fighters-shark" ) {
var a = Math.ceil(this.$ws._alienItems("EQ_FIGHTER_SHARK")/2);
var ai = system.addShips("fighters-alienitem", a, this.ship.position, 100);
if( ai && this.ship.velocity )
for( var i = 0; i < ai.length; i++ )
if(ai[i]) ai[i].velocity = this.ship.velocity;
}
if( this.ship.dataKey === "fighters-king" ) {
var a = Math.ceil(this.$ws._alienItems("EQ_FIGHTER_KING")/2);
var ai = system.addShips("fighters-alienitem", a, this.ship.position, 100);
if( ai && this.ship.velocity )
for( var i = 0; i < ai.length; i++ )
if(ai[i]) ai[i].velocity = this.ship.velocity;
}
if( this.$FighterOwner == player.ship ) {
var d = " lost";
if( whom && whom.isValid && whom != player.ship ) {
d = " defeated by "+whom.displayName;
}
player.commsMessage(this.ship.displayName + d, 10);
this.$ws._MFD(); //update numbers in MFD
}
log(this.name, this.ship.displayName+" terminated by "+whom+" "+why); //log NPC fighters also
}
//fighter functions
this._escortPosition = function() {this._escortPosition2(this.ship);} //for fighterAI.plist
this._escortPosition2 = function(ship) {
if( ship && ship.isDerelict ) {
ship.target = null;
ship.performHold();
ship.switchAI("nullAI.plist");
return;
}
var sc = ship.script;
if( !sc || !sc.$FighterNumber || !sc.$FighterOwner || !sc.$FighterOwner.isValid
|| ship == player.ship ) return; //for sure
if( sc.$FighterOwner == player.ship && //green alert or jump countdown
( player.alertCondition == 1 || this.$ws.$ForcedLanding ) ) {
ship.AIState = "LANDING";
this.$ws._MFD(); //update if owner is the player
return;
}
if( ship.target && ship.target.hasHostileTarget ) {
ship.setAI("interceptAI.plist");
return;
}
var epos = sc.$ws.coordinatesForEscortPosition(sc.$FighterNumber);
var pos = sc.$FighterOwner.position.add(epos);
ship.savedCoordinates = pos;
if( ship.injectorSpeedFactor > 0 )
ship.desiredSpeed = ship.injectorSpeedFactor * ship.maxSpeed;
else ship.desiredSpeed = 7 * ship.maxSpeed; //for Oolite 1.80
ship.desiredRange = 0; //go to the escort position in the formation
ship.performFlyToRangeFromDestination();
ship.reactToAIMessage("OWNER_FAR");//go back to owner
//var dist = ship.position.distanceTo(sc.$FighterOwner.position);
//if(dist > 10000) ship.reactToAIMessage("OWNER_FAR");//go back to owner
//else if(dist > 5000) ship.reactToAIMessage("OWNER_NEAR");//go back to owner
//else ship.reactToAIMessage("OWNER_NEXT");//keep formation
}
this._landing = function() {this._landing2(this.ship);}
this._landing2 = function(ship) {
if( ship && ship.isDerelict ) {
ship.target = null;
ship.performHold();
ship.switchAI("nullAI.plist");
return;
}
var sc = ship.script;
if( !sc || ship == player.ship ) return; //for sure
var owner = sc.$FighterOwner;
if( !owner || !owner.isValid ) return;
ship.target = null; //clear target to stop attack
var landingdist = 50; //m from the scoop position of owner where put into hangar
//make a waypoint below the owner's scoop position
var scooppos = owner.position;
var sp = this.$OwnerScoopPos;
if( !sp ) { //first time read scoop pos from shipdata.plist
var shipdata = Ship.shipDataForKey(owner.dataKey);
if( shipdata ) {
sp = shipdata["scoop_position"];
if(sp && sp != "undefined") {
sp = sp.split(" ",3);
if( sp && sp.length == 3 ) {
this.$OwnerScoopPos = sp;
//log(this.name, owner.name+" scoop_position:"+sp); //debug
}
}
}
if( !this.$OwnerScoopPos ) { //no scoop pos in shipdata so make one
this.$OwnerScoopPos = [];
this.$OwnerScoopPos[0] = 0;
this.$OwnerScoopPos[1] = -20;
this.$OwnerScoopPos[2] = 0;
sp = this.$OwnerScoopPos;
}
}
scooppos = scooppos.add(owner.vectorRight.multiply(sp[0]))
.add(owner.vectorUp.multiply(sp[1]))
.add(owner.heading.multiply(sp[2]));
var down = 1.5*owner.collisionRadius; //AI never enter into collisionRadius
var downpos = scooppos.add(owner.vectorUp.multiply(-down));
ship.savedCoordinates = downpos.add(owner.velocity.multiply(6)); //go below the owner
// also must use setDestinationFromCoordinates in AI.plist
var d = ship.position.distanceTo(scooppos);
var d2 = ship.position.distanceTo(downpos);
//log(this.name, d);
if( this.$ws.$TractorBeam ) {
if( d < landingdist ) { //enough near?: add to the hangar
this._landNow(ship, owner);
return;
}
if( d2 < down ) { //arrived below the owner?
//if( owner.addCollisionException ) { //need Oolite v1.81
// owner.addCollisionException(ship); //must go near
//log(this.name, "coll.ex.len:"+owner.collisionExceptions.length);//debug
//}
//go up to the owner's center point from below into the dock
ship.savedCoordinates = scooppos.add(owner.velocity.multiply(2));
// also must use setDestinationFromCoordinates in AI.plist
//apply "tractor beam" to pull this fighter to the scoop
var v = scooppos.subtract(ship.position);
ship.velocity = v.direction().multiply(80).add(v.multiply(0.4))
.add(owner.velocity);
//make sure the fighter will land after 10 seconds
if( !this.$TimerLandNow )
this.$TimerLandNow = new Timer(this, this._TimedLandNow.bind(this), 10);
ship.desiredSpeed = 0; //must set before "OWNER_NEXT"
ship.reactToAIMessage("OWNER_NEXT"); //go straight
return;
}
} else { //land without tractor beam
if( d < landingdist ) {
//must be triggered outside collisionRadius of owner
this._landNow(ship, owner); //add to the hangar
return;
} else if( d2 < down ) { //surely land after 10 seconds
if( !this.$TimerLandNow )
this.$TimerLandNow = new Timer(this, this._TimedLandNow.bind(this), 10);
//go up to the owner's center point from below into the dock
ship.savedCoordinates = scooppos.add(owner.velocity);
ship.desiredSpeed = ship.maxSpeed; //must set before "OWNER_NEXT"
ship.reactToAIMessage("OWNER_NEXT"); //go straight
return;
}
}
if( d < 2000 ) ship.reactToAIMessage("OWNER_NEAR"); //approach owner at normal speed
else ship.reactToAIMessage("OWNER_FAR"); //use injectors in approach if available
}
this._landNow = function(ship, owner) { //put back into the internal hangar
if(!ship || !owner || !owner.isValid) return; //for sure
var f = [], f2 = []; //remove from $Fighters array
this.$ws._awardFighter(ship, owner); //put back into the owner's equipment list
if( owner == player.ship ) {
if( this.$ws.$LaunchedFighters > 0 ) this.$ws.$LaunchedFighters--;
f = this.$ws.$Fighters;
} else if( owner.script ) {
if( owner.script.$LaunchedFighters > 0 ) owner.script.$LaunchedFighters--;
f = owner.script.$Fighters;
}
var out = 0;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi != ship ) {
f2.push(fi);
if( fi && fi.isValid ) out++;
}
}
if( owner == player.ship ) {
this.$ws.$Fighters = f2;
this.$ws._MFD(); //update numbers in MFD
if( out == 0 ) {
player.commsMessage("All fighters landed", 10);
player.consoleMessage(" ", 10); //make room to the countdown
}
} else if( owner.script ) owner.script.$Fighters = f2;
//prevent double landing if both shipCollided and shipWasScooped is fired
var sc = ship.script;
if( sc ) sc.$FighterOwner = null;
//if( owner.removeCollisionException ) { //need Oolite v1.81
// owner.removeCollisionException(ship); //must go near
//log(this.name, "coll.ex.len:"+owner.collisionExceptions.length);//debug
//}
ship.remove(true); //landed
}
this._newTarget = function() {this._newTarget2(this.ship);} //AI in plist located new target
this._newTarget2 = function() { //AI in plist located new target
var sc = ship.script;
if( !sc || ship == player.ship ) return; //for sure
var owner = sc.$FighterOwner;
if( !owner || !owner.isValid || owner != player.ship ) return; //player's fighters only
var t = ship.target;
if( !this.$ws._isValidFighterTarget(t, owner) ) return;
player.consoleMessage(ship.name+" #"+this.$FighterNumber+" aim "+t.displayName, 4.5);
this.$ws._MFD(); //update numbers in MFD
}
this._TimedLandNow = function() {
if( this.$TimerLandNow ) {
this.$TimerLandNow.stop();
delete this.$TimerLandNow;
}
this._landNow(this.ship, this.$FighterOwner);
}
|
Scripts/fighters.js |
"use strict";
this.name = "fighters";
this.author = "Norby";
this.copyright = "© 2016 Norby, Creative Commons: attribution, non-commercial, sharealike.";
this.description = "worldscript for fighters";
//settings
this.$AutoRepair = true; //pay for fighter repairs right at arrival screen
this.$FireDist = 12000; //where fighters accept new target, best within Pulse Laser range (12.5km)
this.$LaunchDelay = 1; //delay between launch of fighters in seconds
this.$MaxFightersIfNoBay = 8; //how many fighters you can control without Fighter Bay
this.$MFDName = "Fighters"; //name appear in HUD and MFD Selector Interface
this.$TractorBeam = true; //pull fighters to the scoop position of owner at landing
//internal variables, should not touch
this.$EscortPositions = []; //store positions for escort formation
this.$FCB = null; //FrameCallBack for sniperlock of Kings and Sharks
this.$FighterCost = []; //cost of fighters, read from equipment.plist in startUp
this.$FighterKeys = []; //["EQ_FIGHTER_LIGHT","EQ_FIGHTER_RAIDER"]; //in equipment.plist
this.$Fighters = []; //Active, launched fighters
this.$FightersOfPlayer = 0; //actually how many figters owned by the player
this.$ForcedLanding = false; //must land when countdown active
this.$LastForced = false; //store forced status for next fighter launch
this.$LastTarget = null; //store target for next fighter launch
this.$LaunchedFighters = 0; //how many fighters launched from the internal hangar
this.$LaunchInProgress = false; //do not launch fighters too often
this.$LeftBehindFighters = 0; //set in shipWillEnterWitchspace, read in shipExitedWormhole
this.$LeftBehindSystem = ""; //set in shipWillEnterWitchspace, read in shipExitedWormhole
this.$MaxFighters = this.$MaxFightersIfNoBay; //how many fighters you can control currently
this.$simulator = false; //detect if combat simulator is running
this.$Targets = []; //Ships once targeted by fighters, indexed by entityPersonality
this.$TimerDelay = null; //for fighter launch delay
//event handlers
this.startUp = function() {
this.$MaxFighters = this._maxFighters(player.ship);
this.$FightersOfPlayer = missionVariables.$FightersOfPlayerPlusOne;
if( !this.$FightersOfPlayer ) this.$FightersOfPlayer = 0;
else this.$FightersOfPlayer--; //missionVariables store one more to avoid 0
var j = 0;
var alleq = EquipmentInfo.allEquipment;
for(var i = 0; i < alleq.length; i++) { //read fighter costs into local array
var eq = alleq[i];
if( eq ) {
var k = eq.equipmentKey;
if( k && k.indexOf("EQ_FIGHTER_") == 0 ) {
this.$FighterKeys[j] = k;
//bubble sort keep smaller fighters first to ensure launch of heaviest first
var sp = EquipmentInfo.infoForKey( k ).requiredCargoSpace;
for( var x = j; x > 0 &&
sp < EquipmentInfo.infoForKey(this.$FighterKeys[x-1]).requiredCargoSpace;
x-- ) { //exchange the two fighters until smaller than the other
var tmpkey = this.$FighterKeys[x];
this.$FighterKeys[x] = this.$FighterKeys[x-1];
this.$FighterKeys[x-1] = tmpkey;
}
j++;
}
}
}
for(var i = 0; i < this.$FighterKeys.length; i++) { //read fighter costs into local array
var k = this.$FighterKeys[i];
this.$FighterCost[i] = EquipmentInfo.infoForKey( k ).price/10;
}
var feq = this._fighterEqsOf(player.ship);
if( feq != this.$FightersOfPlayer ) {
log(this.name, "Using "+feq+" fighter equipments instead of "
+this.$FightersOfPlayer+" from missionVariables");
this.$FightersOfPlayer = feq; //override by the number of counted fighter equipments
}
var h = worldScripts.hudselector;
if( h && h.$HUDSelectorAddMFD ) h.$HUDSelectorAddMFD(this.name, this.$MFDName);
if( !isValidFrameCallback(this.$FCB) ) {
this.$FCB = addFrameCallback( this._FCB.bind(this) ); //sniperlock of Kings and Sharks
}
}
this.shipSpawned = function(ship) { //NPC with fighters
if(this === worldScripts.fighters) return; //do nothing in worldscript, just in NPC ship script
this.$MaxFighters = this._maxFighters(ship);
if( !isValidFrameCallback(this.$FCB) ) {
this.$FCB = addFrameCallback( this._FCB.bind(this) ); //sniperlock of Kings and Sharks
}
}
this.alertConditionChanged = function(newCondition, oldCondition) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
this._MFD(); //update if player, even if not use fighters
if( !this._playerHasFighters(ship) || player.docked ) return;
if( newCondition == 1 ) { //green alert in space so fighters should go back to hangar
var out = this._fightersLanding();
if( out > 0 ) {
var s = out+" fighters are";
if( out == 1 ) s = "A fighter is";
player.consoleMessage(s+" landing", 4.5);
}
} else if( newCondition == 3 && player.alertHostiles ) {
this._fightersStopLanding(); //cancel landing in red alert
this._launchFighters(ship.target, true); //forced launch even if no valid target
}
}
this.coordinatesForEscortPosition = function(index) {
var pos = this.$EscortPositions[index]; //cache of positions
if( !pos ) pos = this.$EscortPositions[index] = this.coordinatesForEscortPosition2(index);
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( ship && ship.isValid ) pos = pos.add(ship.velocity.multiply(4)); //adjusted for movement
return(pos);
}
this.coordinatesForEscortPosition2 = function(index) {
//4 groups in square form, 4*4=16 fighters
//always show the xz plane of system due to zero second coordinates
var positions = [new Vector3D(1,0,1), new Vector3D(-1,0,1),
new Vector3D(1,0,-1), new Vector3D(-1,0,-1)];
var space = 100; //m space between escorts in the same group
var zoom = 1500; //m, scale group positions around the mothership
//small adjustment in x position to form horizontal lines within groups
var layer = 1+Math.floor(index/positions.length);
var xdir = 1; //spread one left, one right, starting from center to outside
var i = layer/2;
var fi = Math.floor(i);
if( fi == i ) xdir = -1;
var x = xdir * space * ( 0.5 + (fi % 4) ); //x positions within a group: -50 50 -150 150
//small adjustment in y position for more than 16 fighters
var y = space * ( -0.5 + Math.floor(index/(4*positions.length)));
var group = index % positions.length;
return positions[group].multiply(zoom).add([x,y,0]);
}
this.distressMessageReceived = function(aggressor, sender) {
this._launchFighters(aggressor);
}
this.equipmentDamaged = this.equipmentRepaired = this.equipmentRemoved = this.equipmentAdded = function(equipment) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
this.$MaxFighters = this._maxFighters(ship);
}
this.helpRequestReceived = function(ally, enemy) {
this._launchFighters(enemy);
}
this.playerBoughtEquipment = function(equipmentKey) {
if(equipmentKey === "EQ_FIGHTERSREMOVE") {
player.ship.removeEquipment(equipmentKey);
var ai = 0, refund = 0, k, eqcost;
for(var i = 0; i < this.$FighterKeys.length; i++) { //remove all fighter types
k = this.$FighterKeys[i];
var e = this._eqStats(player.ship, k);
eqcost = this.$FighterCost[i];
refund += eqcost * e.ok + eqcost/2 * e.dmg; //half price for damaged fighters
ai += (e.ok+e.dmg) * this._alienItems(k); //regain all built-in alien items
//one less alien items in each damaged swarm fighter group
if( k.indexOf("EQ_FIGHTER_SWARM") == 0 ) ai -= e.dmg;
for(var j = 0; j < e.ok + e.dmg; j++) { //remove all fighters
player.ship.removeEquipment(k);//remove fighter
}
}
var f = "", fb = "", fcc = "";
if(this.$FightersOfPlayer > 1) f = this.$FightersOfPlayer+" fighters";
else if(this.$FightersOfPlayer == 1 ) f = "a fighter";
k = "EQ_FIGHTERBAY";
if( player.ship.equipmentStatus(k) != "EQUIPMENT_UNAVAILABLE" ) {
eqcost = EquipmentInfo.infoForKey( k ).price/10;
if(player.ship.equipmentStatus(k) == "EQUIPMENT_OK") refund += eqcost;
else refund += eqcost/2; //half money if damaged
player.ship.removeEquipment(k);
fb = "Fighter Bay";
if(f.length > 0) f = " and "+f;
}
k = "EQ_FIGHTERSUPPORT";
if( player.ship.equipmentStatus(k) != "EQUIPMENT_UNAVAILABLE" ) {
eqcost = EquipmentInfo.infoForKey( k ).price/10;
if(player.ship.equipmentStatus(k) == "EQUIPMENT_OK") refund += eqcost;
else refund += eqcost/2; //half money if damaged
player.ship.removeEquipment(k);
if(f.length > 0 && fb.length > 0) fcc = ", ";
else if(fb.length > 0) fcc = " and ";
else if(f.length > 0) f = " and "+f;
fcc += "Fighter Support Center";
}
k = "EQ_FIGHTERHANGAR";
if( player.ship.equipmentStatus(k) != "EQUIPMENT_UNAVAILABLE" ) {
eqcost = EquipmentInfo.infoForKey( k ).price/10;
if(player.ship.equipmentStatus(k) == "EQUIPMENT_OK") refund += eqcost;
else refund += eqcost/2; //half money if damaged
player.ship.removeEquipment(k);
if(f.length > 0 && fb.length > 0) fcc = ", ";
else if(fb.length > 0) fcc = " and ";
else if(f.length > 0) f = " and "+f;
fcc += "Fighter Hangar";
}
var ais = "";
if( ai > 0 ) {
player.ship.manifest.alien_items += ai;
ais=" and "+ai+" Alien Items";
}
player.credits += refund;
player.consoleMessage(refund+" credits"+ais+" refunded for "+fb+fcc+f, 10);
this.$FightersOfPlayer = this._fighterEqsOf(player.ship); //should be 0
this.$MaxFighters = this._maxFighters(player.ship);
}
if(equipmentKey === "EQ_FIGHTERBAY"
|| equipmentKey === "EQ_FIGHTERSUPPORT"
|| equipmentKey === "EQ_FIGHTERHANGAR" ) {
this.$MaxFighters = this._maxFighters(player.ship);
player.consoleMessage("You can control "+this.$MaxFighters+" fighters from now", 10);
}
if(equipmentKey.indexOf("REFUND_EQ_FIGHTER_") == 0 ) {
player.ship.removeEquipment(equipmentKey);
var refund = 0;
var k = equipmentKey.substring(7); //chop REFUND_ to get the eqkey of fighter
if(player.ship.equipmentStatus(k) == "EQUIPMENT_OK") {
var eqcost = EquipmentInfo.infoForKey( k ).price/10;
refund += eqcost;
player.ship.removeEquipment(k);//remove fighter
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
//if( this.$FightersOfPlayer > 0 ) this.$FightersOfPlayer--;
player.credits += refund;
var ai = "", a = this._alienItems(k);
if( a > 0 ) {
player.ship.manifest.alien_items += a;
ai = " and "+a+" Alien Items";
}
var n = "";
if(equipmentKey.indexOf("REFUND_EQ_FIGHTER_I") == 0) n = "n"; //an Iron King
player.consoleMessage(refund+" credits"+ai+" refunded for a"+n+" "
+EquipmentInfo.infoForKey( k ).name, 10);
}
} else if(equipmentKey.indexOf("EQ_FIGHTER_") == 0 ) {
//if there is a damaged then this event is called with the same eqkey at repair, must exclude!
if( this._eqStats(player.ship, equipmentKey).dmg == 0 ) { //all ok
var a = this._alienItems(equipmentKey);
if( a > 0 ) { //need some Alien Items to buy this fighter
if(!(player.ship.manifest.alien_items >= a)) {
player.ship.removeEquipment(equipmentKey);//remove fighter if not enough alien items
player.consoleMessage("You need "+a+" Alien Items to buy this fighter", 5);
return false;
}
player.ship.manifest.alien_items -= a;
}
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
//if( this.$FightersOfPlayer < this.$MaxFighters ) this.$FightersOfPlayer++;
var f = "";
if( this.$FightersOfPlayer < this.$MaxFighters )
f = " of "+this.$MaxFighters+" fighters, buy again for more.";
else f = " fighters, this is the maximum.";
player.consoleMessage("You have "+this.$FightersOfPlayer+f, 5);
} else { //handle repair
if( equipmentKey.indexOf("EQ_FIGHTER_SWARM") == 0 ) {
var ki = this.$FighterKeys.indexOf("EQ_FIGHTER_SWARM");
if( this._eqStats(player.ship, equipmentKey).dmg == 1 ) {//exactly 1 damamged
if( player.ship.manifest.alien_items >= 1 ) {
player.ship.manifest.alien_items -= 1;
player.consoleMessage("Repair of Swarm Fighter Group consumed an Alien Item", 10);
} else {
player.consoleMessage("Fixing need an Alien Item or 2 damaged groups", 10);
player.ship.removeEquipment(equipmentKey);//remove the gift awarded by the core
player.credits += this.$FighterCost[ki] / 2; //refund the cost, nothing happened
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
return;
}
} else { //make an ungamaged group from 2 damaged groups
player.consoleMessage("Two gappy Swarm Fighter Groups merged to a full group", 10);
player.credits += this.$FighterCost[ki] / 2; //give back the repair cost
if(player.ship.equipmentStatus(equipmentKey) == "EQUIPMENT_OK") {
//there is an undamaged swarm fighter group
//so set a damaged group to ok due to the removeEq below will remove a good one
player.ship.setEquipmentStatus(equipmentKey, "EQUIPMENT_OK");
}
player.ship.removeEquipment(equipmentKey);
}
}
player.ship.removeEquipment(equipmentKey);//remove the gift awarded by the core
player.ship.setEquipmentStatus(equipmentKey, "EQUIPMENT_OK");//fix one fighter only
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
}
}
}
this.playerCancelledJumpCountdown = function() { //stop landing
if(!this._playerHasFighters(player.ship)) return;
this.$ForcedLanding = false;
this._fightersFollow();
}
this.playerJumpFailed = function(reason) {
if(!this._playerHasFighters(player.ship)) return;
this.$ForcedLanding = false;
this._fightersFollow();
}
this.playerStartedJumpCountdown = function(type, seconds) { //landing fighters
if(!this._playerHasFighters(player.ship)) return;
this.$ForcedLanding = true;
var out = this._fightersLanding();
if( out > 0 ) {
var s = out+" fighters";
if( out == 1 ) s = " a fighter";
player.consoleMessage("Warning: "+s+" need landing!", 10);
player.consoleMessage(" ", 10); //make room to the countdown
}
}
this.playerWillSaveGame = function(message) {
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
missionVariables.$FightersOfPlayerPlusOne = this.$FightersOfPlayer + 1;
}
this.shipAttackedOther = function(whom) {
this._launchFighters(whom);
}
this.shipAttackedWithMissile = function(missile, whom) {
this._launchFighters(whom);
}
this.shipBeingAttacked = function(whom) {
this._launchFighters(whom);
}
this.shipWillExitWitchspace = function() {
var lbf = this.$LeftBehindFighters;
if( lbf > 0 ) {
var s = lbf+" fighters";
if( lbf == 1 ) s = "a fighter";
var msg = "You left behind "+s+" in "+this.$LeftBehindSystem;
player.commsMessage(msg, 10);
log(this.name, msg);
this.$LeftBehindFighters = 0;
}
}
this.shipFiredMissile = function(missile, target) {
this._launchFighters(target);
}
// check when the player launches if this is a simulator run
this.shipLaunchedFromStation = function(station) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( !this._playerHasFighters(ship) ) return;
var w = worldScripts["Combat Simulator"];
if (w && w.$checkFight && w.$checkFight.isRunning) this.$simulator = true;
else this.$simulator = false;
}
this.shipTakingDamage = function(amount, whom, type) {
this._launchFighters(whom);
}
this.shipTargetAcquired = function(t) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( ship == p ) {
this._MFD(); //update if player, even if not use fighters
if( !this._playerHasFighters(ship) ) return;
this._reconsiderTargets(ship, t); //update targets of launched fighters
}
}
this.shipTargetLost = function(t) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( ship == p ) {
this._MFD(); //update if player, even if not use fighters
}
}
this.shipWillDockWithStation = function(station) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
// if this was a simulator run, reset the variable and return - no arrival report in this case
if (this.$simulator == true) {
this.$simulator = false;
} else {
if( ship.equipmentStatus("EQ_FIGHTERBAY") == "EQUIPMENT_DAMAGED"
|| ship.equipmentStatus("EQ_FIGHTERSUPPORT") == "EQUIPMENT_DAMAGED"
|| ship.equipmentStatus("EQ_FIGHTERHANGAR") == "EQUIPMENT_DAMAGED" ) {
var b = "", l = "";
if( ship.equipmentStatus("EQ_FIGHTERBAY") == "EQUIPMENT_DAMAGED" ) {
b = EquipmentInfo.infoForKey( "EQ_FIGHTERBAY" ).name;
l = " Without working "+b+" only light fighters can launch from your ship.";
}
if( ship.equipmentStatus("EQ_FIGHTERSUPPORT") == "EQUIPMENT_DAMAGED" ) {
if(b.length > 0) b += " and ";
b += EquipmentInfo.infoForKey( "EQ_FIGHTERSUPPORT" ).name;
}
if( ship.equipmentStatus("EQ_FIGHTERHANGAR") == "EQUIPMENT_DAMAGED" ) {
if(b.length > 0) b += " and ";
b += EquipmentInfo.infoForKey( "EQ_FIGHTERHANGAR" ).name;
}
var s = ""; if( this.$FightersOfPlayer > 1 ) s = "s"; //current fighters in plural
player.addMessageToArrivalReport(expandDescription("[fighters-baydamaged]",
{bay:b, fightersofplayer:this.$FightersOfPlayer,
plural:s, maxfighters:this.$MaxFighters, light:l}));
}
if( this._playerHasFighters(ship)
|| ship.equipmentStatus("EQ_FIGHTERBAY") != "EQUIPMENT_UNAVAILABLE"
|| ship.equipmentStatus("EQ_FIGHTERSUPPORT") != "EQUIPMENT_UNAVAILABLE"
|| ship.equipmentStatus("EQ_FIGHTERHANGAR") != "EQUIPMENT_UNAVAILABLE" ) {
var f = this.$Fighters;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( !fi || !fi.isValid ) this.$FightersOfPlayer--; //reduce total with lost ones
else {
this._awardFighter(fi, ship);
fi.remove(true); //do not leave fighters outside of the station
}
}
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
var d = this._damagedFighter(player.ship); //how many damaged fighters are on board
var s2 = ""; if( this.$FightersOfPlayer > 1 ) s2 = "s"; //current fighters in plural
if( !d || !d[0] || d[0][0] === 0 ) { //no damaged fighters
if( this.$FightersOfPlayer === this.$MaxFighters ) {
var s = " is"; if( this.$MaxFighters > 1 ) s = "s are"; //fighters in plural
player.addMessageToArrivalReport(expandDescription("[fighters-allfighters]",
{maxfighters:this.$MaxFighters, plural:s}));
} else if( !station || !station.isValid || !station.hasShipyard ) {
//no shipyard, no replace
player.addMessageToArrivalReport(expandDescription("[fighters-noshipyard]",
{fightersofplayer:this.$FightersOfPlayer, plural:s2,
maxfighters:this.$MaxFighters}));
} else if( this.$FightersOfPlayer === 0 ) { //no fighters on board
player.addMessageToArrivalReport(expandDescription("[fighters-emptybay]"));
} else { //less than maxFighters are on board
var s = " is"; if( this.$MaxFighters > 1 ) s = "s are"; //fighters in plural
player.addMessageToArrivalReport(expandDescription("[fighters-lessfighters]",
{fightersofplayer:this.$FightersOfPlayer, plural:s2,
maxfighters:this.$MaxFighters}));
}
} else { //there are damaged fighters
var dm = d[0][0];//number of damaged fighters
var dc = d[0][1];//credits needed for repair all damaged fighters
var s = ""; if( dm > 1 ) s = "s"; //damaged fighters in plural
if( !station || !station.isValid || !station.hasShipyard ) {
//no shipyard, no repair
player.addMessageToArrivalReport(expandDescription("[fighters-noshipyard]",
{fightersofplayer:this.$FightersOfPlayer, plural:s2,
maxfighters:this.$MaxFighters}));
} else if( player.credits < dc ) { //not enough money for all repairs
player.addMessageToArrivalReport(expandDescription("[fighters-notenoughmoney]",
{damagedfighters:dm, plural:s}));
} else if( !this.$AutoRepair ) { //repair disabled
player.addMessageToArrivalReport(expandDescription("[fighters-damaged]",
{damagedfighters:dm, plural:s}));
} else { //repair damaged fighters
var dc2 = 0; //must count again to exclude cost of damaged swarm groups
var dm2 = 0; //must count again to exclude number of damaged swarm groups
for(var i = 1; d[i]; i++) {
var k = d[i][0];
if( k && k.indexOf("EQ_FIGHTER_SWARM") == 0 ) {
continue; //do not repair swarm groups
}
for(var j = 0; j < d[i][1]; j++) {
player.ship.setEquipmentStatus(k, "EQUIPMENT_OK");
player.credits -= d[i][2];
dc2 += d[i][2];
dm2++;
log(this.name, "Repair of "+k+" cost "+d[i][2]+" credits");
}
}
if( dm2 === 0 ) { //only swarm damages without repair
player.addMessageToArrivalReport(expandDescription("[fighters-damaged]",
{damagedfighters:dm, plural:s}));
} else { //repaired fighters
s = ""; if( dm2 > 1 ) s = "s"; //damaged fighters in plural
player.addMessageToArrivalReport(expandDescription("[fighters-repaired]",
{cost:dc2, repaired:dm2, plural:s,
fightersofplayer:this.$FightersOfPlayer, plural2:s2,
maxfighters:this.$MaxFighters}));
}
}
}
}
}
//free up arrays both in NPC ship scripts and player's array in worldScript
delete this.$Fighters;
delete this.$Targets;
this.$Fighters = [];
this.$Targets = [];
this.$LaunchedFighters = 0; //how many fighters launched from the internal hangar
}
this.shipWillEnterWitchspace = function() {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
this.$ForcedLanding = false;
if( this._playerHasFighters(ship) ) {
var bay = false;
//Fighter Bays help to do not lose fighters which see the wormhole in its scanner
if( ship.equipmentStatus("EQ_FIGHTERBAY") != "EQUIPMENT_UNAVAILABLE"
|| ship.equipmentStatus("EQ_FIGHTERSUPPORT") != "EQUIPMENT_UNAVAILABLE"
|| ship.equipmentStatus("EQ_FIGHTERHANGAR") != "EQUIPMENT_UNAVAILABLE" )
bay = true;
this.$LeftBehindFighters = 0; //will be displayed in shipExitedWormhole event
this.$LeftBehindSystem = system.name;
var f = this.$Fighters;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( !fi || !fi.isValid ) this.$FightersOfPlayer--; //reduce total with lost ones
else if( fi.script && fi.script.$TimerLandNow ) {
this.$TimerLandNow.stop();
delete this.$TimerLandNow;
fi.script._landNow(fi, ship); //use the last moment to land before left behind
} else if( bay && fi.position.distanceTo(ship.position) < fi.scannerRange ) {
//using the fighter's (and not mother's) scannerRange for King's 50km scanner
fi.script._landNow(fi, ship); //Fighter bay help land fighters
} else {
this.$FightersOfPlayer--; //reduce total with left behind ones
this.$LeftBehindFighters++; //increase left behind fighters
}
}
//log(this.name,"Left behind "+this.$LeftBehindFighters+" fighters in "+this.$LeftBehindSystem);
this.$FightersOfPlayer = this._fighterEqsOf(player.ship);
delete this.$Fighters;
delete this.$Targets;
this.$Fighters = [];
this.$Targets = [];
this.$LaunchedFighters = 0; //how many fighters launched from the internal hangar
}
}
this.shipWillLaunchFromStation = function(station) {
this.$ForcedLanding = false;
this._MFD(); //show mfd as early as other MFDs
}
//local functions
this._alienItems = function(k) { //how many alien items needed to buy this fighter equipment
if( !k || !(k.length > 0)) return(0); //for sure
var a = 0;
if( k.indexOf("EQ_FIGHTER_SWARM") == 0 ) a = 3;
if( k.indexOf("EQ_FIGHTER_SHARK") == 0 ) a = 5;
if( k.indexOf("EQ_FIGHTER_KING") == 0 ) a = 10;
return(a);
}
this._awardFighter = function(ship, owner) { //put back into the owner's equipment list when landed
if(ship && owner && owner.isValid) { //must not require ship.isValid!
var sc = ship.script;
if( sc && sc.$FighterKey ) {
var damaged = false;
if( sc.$FighterKey == "EQ_FIGHTER_SWARM" ) {
//for Swarm only - do not set King fighter to damaged when a subent is lost
if(!ship.subEntities || ship.subEntities.length < ship.subEntityCapacity ) {
if( this._eqStats(owner, sc.$FighterKey).dmg > 0 ) {
//there is a damamged group on board so fix that instead of award eq
owner.setEquipmentStatus(sc.$FighterKey, "EQUIPMENT_OK");
if( owner === player.ship )
player.consoleMessage("Damaged Swarm Fighter Groups are merged", 4.5);
return; //used up to fix another damaged group, so prevent award it
} else damaged = true; //at least one ship is lost from the group
}
} else if( sc.$FighterShields ) { //heavy fighters, including King
var eq = ship.equipment;
var i=-1;
if(eq) while(eq[++i]) {
if(ship.equipmentStatus(eq[i].equipmentKey) == "EQUIPMENT_DAMAGED") {
damaged = true; //if an equipment like ECM is damaged in shielded fighters
break; //then set the fighter eq in owner to damaged
}
}
} else { //if no shields nor swarm fighter group then check hull damage
if( sc.$FighterHull < ship.maxEnergy * 0.4 ) //damaged
damaged = true;
}
owner.awardEquipment(sc.$FighterKey);
if(damaged) owner.setEquipmentStatus(sc.$FighterKey, "EQUIPMENT_DAMAGED");
}
}
}
this._damagedFighter = function(owner) { //how many damaged fighters are on board
var damaged = [];
damaged[0] = [0,0];//damaged fighters and needed credits for repair
if(owner && owner.isValid) {
for(var i = 0; i < this.$FighterKeys.length; i++) {
var k = this.$FighterKeys[i];
var d = this._eqStats(owner, k).dmg;
if(d > 0) {
damaged[0][0] += d;
var c = this.$FighterCost[i]/2; //fixing cost of a fighter
damaged[0][1] += c;
damaged.push([k, d, c]); //key, damaged fighters and needed credits
}
}
}
if(damaged[0][0] > 0) return(damaged);
else return(false);
}
this._eqStats = function(ship, eqKey) { //count damaged and ok eqs
var ret = {ok:0, dmg:0};
if(!ship || !ship.isValid
|| ship.equipmentStatus(eqKey) != "EQUIPMENT_DAMAGED"
&& ship.equipmentStatus(eqKey) != "EQUIPMENT_OK")
return(ret); //for sure
if (0 < oolite.compareVersion("1.81")) { //for Oolite v1.80
var e = ship.equipment;
var len = e.length;
if(e) for(var i=0; i < len; i++) {
if(e[i] && e[i].equipmentKey === eqKey) {
var s = ship.equipmentStatus(eqKey);//report the best status
if( s === "EQUIPMENT_OK" ) {
ret.ok++;
ship.removeEquipment(eqKey); //must remove to uncover damaged ones
e = ship.equipment; //reread the shorter equipment array
i--; //check again this item due to the remove it was the next
len = e.length; //the equipment array is shorter due to the remove
} else if( s === "EQUIPMENT_DAMAGED" ) {
ret.dmg++;
}
}
}
for(var i=0; i < ret.ok; i++) { //award back the removed ok ones
ship.awardEquipment(eqKey);
}
} else { //from Oolite v1.81
ret.ok = ship.equipmentStatus(eqKey, true).EQUIPMENT_OK;
ret.dmg = ship.equipmentStatus(eqKey, true).EQUIPMENT_DAMAGED;
}
return(ret);
}
this._fightersAIState = function(state, stoplanding) { //set AI state of fighters to one defined in plist
var p = player.ship; //AI is not applicable on player, must check to prevent error in log
var f = this.$Fighters;
var out = 0;
for(var i = 0; i < f.length ; i++) {
var fi = f[i];
if( fi && fi.isValid && fi != p ) {
var fai = "fightersAI.plist";
if(fi.AI != fai ) fi.switchAI(fai);
fi.target = null; //clear target to stop attack
if( fi.script && fi.script.$ForcedLanding ) {
fi.AIState = "LANDING";
} else if( !stoplanding || fi.AIState == "LANDING" ) {
fi.AIState = state; //FOLLOW or LANDING
}
out++;
}
}
if( out > 0 ) this._MFD(); //refresh
return(out); //report how many fighters affected
}
this._fightersFollow = function() { //set AI of all valid fighters to follow
return(this._fightersAIState("FOLLOW"));
}
this._fightersLanding = function() { //start landing
return(this._fightersAIState("LANDING"));
}
this._fightersStopLanding = function() { //stop landing only, others untouched
return(this._fightersAIState("FOLLOW", true));
}
this._fighterEqsOf = function(ship) { //count all equipments represents fighters on the given ship
var e, f = 0, k;
for(var i = 0; i < this.$FighterKeys.length; i++) {
k = this.$FighterKeys[i];
e = this._eqStats(ship, k);
f += e.dmg;
f += e.ok;
}
return(f);
}
this._isNewTargetOrNeedMoreFighters_unused = function(ship, target) {
//deprecated, unused function
if( !target || !target.isValid ) return false;
//count nearby escorting fighters
var ships = ship.checkScanner(true);
//max. 32 ships are returned so this way allow to launch more than the limit if really needed
//due to some escorts will not be in the returned 32 so leave space to launch another
var escorts = 0;
for( var i = 0; i < ships.length; i++ ) {
var nearbyship = ships[i];
if( nearbyship && nearbyship.isValid ) {
if( this.$Fighters.indexOf(nearbyship) > -1 ) {
escorts++;
if( escorts > 24 ) return false; //there are enough fighters nearby
} else {
if( ship.escortGroup.containsShip(nearbyship) )
ship.escortGroup.removeShip(nearbyship); //make room for sure
}
}
}
return true; //too few fighters escorting so launch more
}
this._isNewTargetOrNeedMoreFighters_old = function(ship, target) {
//deprecated function, unused due to can not provide proper limit of launched fighters
minTargeters = 3; //how many active fighters must keep target lock to avoid additional launch
if( !target || !target.isValid || target.script && target.script.$FighterOwner == ship ) return false;
if( this.$Targets[target.entityPersonality] != target ) {
this.$Targets[target.entityPersonality] = target;
return true; //new target, launch a fighter group
}
var targeters = 0; //how many active fighters has target lock on this attacker
var f = this.$Fighters;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi && fi.isValid && fi.target == target ) {
targeters++;
if( targeters >= minTargeters ) return false; //there are enough fighters on this target
}
}
return true; //too few fighters follow this target so launch more
}
this._isStation = function(entity) {
return this._isStation2(entity, player.ship);
}
this._isStation2 = function(entity, ship) {
return (entity && entity.isValid && entity.isStation
&& entity.target != ship
&& entity.dataKey.indexOf("carrier") == -1 );
}
this._isValidFighterTarget = function(target, owner) {
if( target && target.isValid && !target.isDerelict
//check hasHostileTarget but this is never set for player
&& ( target.hasHostileTarget || target == player.ship )
&& ( !owner || !owner.isValid || //next conditions need valid owner
target != owner //do not target its own mothership
//do not target friendly figters
&& ( !target.script || target.script.$FighterOwner != owner )
//distance from owner is not too much
&& target.position.distanceTo(owner.position) < owner.scannerRange
) ) {
return(target); //valid target for fighters
} else return(false);
}
this._launchFighters = function(target, forced) {
var ship = this.ship;
if( !ship ) ship = player.ship; //called in worldScript
if( !target || !target.isValid || target.isDerelict || target === ship //for sure
|| ship === player.ship && !this._playerHasFighters(ship)) return;
//continue if NPC or player use fighters
if( !forced && !this._isValidFighterTarget(target, ship) ) //forced mean launch without valid target
return;//skip invalids like own fighters
this.$LastForced = forced;//for timed launches in red alert
if(target) {
if(target.maxSpeed > 0) //exclude stations but include mobile dockables
ship.addDefenseTarget(target); //turrets and thargoid lasers will fire
this._reconsiderTargets(ship, target); //update targets of launched fighters
}
if( !this.$LaunchInProgress && this.$LaunchedFighters < this.$MaxFighters
&& ( ship != player.ship || this.$LaunchedFighters < this.$FightersOfPlayer
&& ( forced || !ship.withinStationAegis || player.alertCondition === 3 ) ) ) {
//launch more fighters, must place far below the owner to avoid kill by collision at injector speed
var scoop = true, bay = true;
if( ship.equipmentStatus("EQ_FIGHTERBAY") != "EQUIPMENT_OK"
&& ship.equipmentStatus("EQ_FIGHTERSUPPORT") != "EQUIPMENT_OK"
&& ship.equipmentStatus("EQ_FIGHTERHANGAR") != "EQUIPMENT_OK" )
bay = false;
if( !bay && ship.equipmentStatus("EQ_FUEL_SCOOPS") != "EQUIPMENT_OK"
&& ship.equipmentStatus("EQ_CARGO_SCOOPS") != "EQUIPMENT_OK" )
scoop = false;
var heavy, key, ships;
var pos = ship.position.add(ship.vectorUp.multiply( -ship.collisionRadius ));
for(var i = this.$FighterKeys.length-1; i >= 0; i--) { //launch heavy fighters first
key = this.$FighterKeys[i];
var eqst = ship.equipmentStatus(key);
if( eqst === "EQUIPMENT_OK" ) { //prevent launch of damaged fighters
if( EquipmentInfo.infoForKey( key ).requiredCargoSpace >= 10 ) { //heavy
if( !bay ) continue; //will not launch heavy fighters without undamaged fighter bay
else heavy = true; //from 10t it is a heavy fighter, will get ECM
} else {
if( !scoop ) continue; //will not launch light fighters without scoop or fighter bay
else heavy = false; //below 10t it is a light fighter
}
ships = system.addShips(key, 1, pos, 0); //eq key must be in roles in shipdata.plist
if(ships && ships[0]) break;
}
}
var name = "Fighter";
var oldlf = this.$LaunchedFighters;
if(ships && ships[0]) for(var i = 0; i < ships.length; i++) {
var si = ships[i];
if(si) {
ship.removeEquipment(key); //reduce fighter number in F5 screen
this.$Fighters.push(si);
this.$LaunchedFighters++;
this.$LaunchInProgress = true;
if(target) {
this.$LastTarget = target;
si.target = target;
si.addDefenseTarget(target);
}
si.displayName = si.name+" #"+this.$LaunchedFighters;
name = si.name;
if(!si.script) si.script = "fighters-ship.js"; //for sure
if(si.script) {
si.script.$FighterKey = key;
si.script.$FighterNumber = this.$LaunchedFighters;
si.script.$FighterOwner = ship; //prevent fighters targeting each other
si.script.$TrailsDisabled = 1; //reduce load with Trails OXP
}
ship.escortGroup.addShip(si);
//if(si.AI == "escortAI.plist") si.AIState = "NEXT_TARGET";
si.velocity = ship.velocity.add(ship.vectorForward.multiply(500)); //initial push
//horizontal line formation with 50m space between ships at -75, -25, 25 and 75m
//si.position = pos.add(ship.vectorRight.multiply(50*(i-(ships.length-1)/2)));
si.orientation = ship.orientation;
if(key != "EQ_FIGHTER_SWARM") si.awardEquipment("EQ_ESCAPE_POD"); //swarm is unpiloted
if(heavy) {
si.awardEquipment("EQ_ECM");
si.awardEquipment("EQ_HEAT_SHIELD");
}
si.performAttack();
if( ship == player.ship ) {
si.bounty = 0; //remove bounty from fighter given in shipdata.plist
si.scannerDisplayColor1 = [0, 0.4, 0]; //green, mean frendly
//si.scannerDisplayColor2 = [0, 0.4, 0]; //green, mean frendly
} else {
if( ship.bounty == 0 ) si.bounty = 0; //remove bounty from fighter given in shipdata.plist
si.displayName += " of "+ship.name; //not owned by the player
}
}
}
if( this.$LaunchInProgress && !this.$TimerDelay ) {//disable future launches until delay
this.$TimerDelay = new Timer(this, this._TimedDelay.bind(this), this.$LaunchDelay, this.$LaunchDelay);
}
if( ship === player.ship && this.$LaunchedFighters > oldlf ) {
var newf = this.$LaunchedFighters - oldlf;
var remain = this.$FightersOfPlayer - this.$LaunchedFighters;
var t = "";
if(target) t = " to "+target.name;
player.consoleMessage(name+" launched"+t, 4.5);
}
}
}
this._mass = function(mass) { //format ship mass to display it in MFD
if(mass > 0) {
if(mass > 1000000000) return(Math.floor(mass/1000000000)+"Mt");
if(mass > 1000000) return(Math.floor(mass/1000000)+"kt");
return(Math.ceil(mass/1000)+"t"); //ceil needed by EscortDeck and ships under 1t
} else return("");
}
this._maxFighters = function(ship) {
var m = this.$MaxFightersIfNoBay; //base value
if( ship.equipmentStatus("EQ_FIGHTERHANGAR") == "EQUIPMENT_OK" ) m = 80; //in equipment.plist also
else if( ship.equipmentStatus("EQ_FIGHTERSUPPORT") == "EQUIPMENT_OK" ) m = 40; //in equipment.plist also
else if( ship.equipmentStatus("EQ_FIGHTERBAY") == "EQUIPMENT_OK" ) m = 16; //in equipment.plist also
return(m);
}
this._nameWithMass = function(t) { //format ship name and mass to display it in MFD
if( !t || !t.isValid ) return("");
return( t.displayName + " (" + this._mass(t.mass)+")" );
}
this._playerHasFighters = function(ship) { //check if this ship is a player ship and hold at least one fighter
if( ship && ship.isValid && ship == player.ship ) {
if( this.$FightersOfPlayer > 0 ) return true;
}
return false;
}
this._reconsiderTargets = function(owner, target) { //all fighters pick targets
target = this._isValidFighterTarget(target, owner);
var f = this.$Fighters;
var invalidfighters = 0;
var oldtargetistargetofowner = 0; //how many fighters attacked the owner's target before reconsider
var ownertarget;
if( owner ) {
if( owner == player.ship && player.alertCondition == 1 ) { //green alert
this.alertConditionChanged(1, 1); //keep up landing, do not target anything
this._MFD(); //update if owner is the player
return;
}
ownertarget = this._isValidFighterTarget(owner.target, owner);
}
var firedist = this.$FireDist; //fighters accept new target within this distance
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi && fi.isValid ) {
var oldtarget = fi.target; //call performAttack at the end if change
if( !owner && fi.script ) {
owner = fi.script.$FighterOwner; //for sure
if( owner ) ownertarget = this._isValidFighterTarget(owner.target, owner);
}
if( owner && owner.isValid ) {
if( fi.target && fi.target.isValid && fi.target == ownertarget ) {
oldtargetistargetofowner++;
}
//forget current target?
if( fi.position.distanceTo(owner.position) > 20000 //go back to owner
|| !this._isValidFighterTarget(fi.target, owner) ) {
fi.target = null;
}
}
//if no target then aim the nearest defensive target?
var mindist = fi.scannerRange;
if( owner && owner.isValid && ( !fi.target || !fi.target.isValid ) ) {
var d = owner.defenseTargets;
if( d && d.length > 0 ) {
var dmin = -1; //index of nearest defensive target
for(var j = 0; j < d.length; j++) {
var dj = d[j]; //need valid non-stationary target
if( this._isValidFighterTarget(dj, owner) && dj.maxSpeed > 0 ) {
var df = dj.position.distanceTo(fi.position);
if( df < mindist ) {
mindist = df; //nearest to this fighter
dmin = j;
}
}
}
if( dmin > -1 ) {
fi.target = d[dmin];
fi.addDefenseTarget(d[dmin]);
}
}
}
//if still no target or owner's tagret is nearer and within firedist then aim it
if( ownertarget && ownertarget != fi.target && ownertarget != fi ) {
//only the half of fighters attack the mother's target - removed
//&& ( i - invalidfighters ) * 2 < f.length - invalidfighters
var aim = false;
if( !fi.target || !fi.target.isValid ) aim = true;
else {
var otd = ownertarget.position.distanceTo(fi.position);
if( otd < firedist && otd < mindist ) {
fi.target = ownertarget; //attack the mother's target (except friends)
fi.addDefenseTarget(ownertarget);
}
}
}
//only the half of fighters attack the mother's target - removed
//if( fi.target && fi.target.isValid && fi.target == ownertarget ) {
// if( oldtargetistargetofowner*2 > f.length - invalidfighters
// && target && target.isValid ) {
// fi.target = null;
// }
//}
//aim the new target?
if( target && target != fi && target != fi.target ) {
var aim = false;
if( !fi.target || !fi.target.isValid ) aim = true;
else {
var ftd = fi.target.position.distanceTo(fi.position);
var td = target.position.distanceTo(fi.position);
//replace any target if the given new target is more than 5km nearer
if( ftd - 5000 > td ) aim = true;
//also if new target is within Pulse Laser range and old is almost outside
else if( ftd < firedist && td > firedist ) aim = true;
}
if( aim ) {
fi.target = target; //lock on the newest aggressor
fi.addDefenseTarget(target);
}
}
if(fi.target) { //attack the target
if(fi.AI == "fightersAI.plist" ) fi.setAI("interceptAI.plist");
if(fi.target != oldtarget) fi.performAttack();
} else { //follow the owner at last resort
if( fi.AI == "interceptAI.plist" ) fi.exitAI(); //step back to fighterAI
if( fi.AI == "fightersAI.plist" && fi.AIState != "LANDING" ) fi.AIState = "FOLLOW";
}
} else invalidfighters++; //count lost fighters to exclude from total when calculate half of group
}
if( owner && owner == player.ship ) { //tell the result of reconsider to the player
if( owner.target && owner.target.isValid ) {
//count how many fighters target the same ship than owner
var currenttargetistargetofowner = 0;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi && fi.isValid && fi.target == owner.target) {
currenttargetistargetofowner++;
}
}
if( currenttargetistargetofowner > 0
&& currenttargetistargetofowner != oldtargetistargetofowner ) {
var s = "";
if( currenttargetistargetofowner > 1 ) s = "s";
player.consoleMessage( currenttargetistargetofowner
+" fighter"+s+" aim "+owner.target.name, 3 );
}
}
this._MFD(); //update if owner is the player
}
}
this._FCB = function( delta ) { //FrameCallBack for sniperlock of Kings and Sharks
var f = this.$Fighters;
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi && fi.isValid && fi.target && fi.target.isValid
&& fi.script && fi.script.$SniperLock && fi.AIState != "LANDING"
&& fi.AIState != "FLEE" && fi.AIState != "FLEE_FOR_CLOAKED"
&& fi.AI != "fleeQMineAI.plist" && fi.energy > fi.maxEnergy/2
&& fi.target.position.distanceTo(fi.position) < 6000 ) { //exclude far targets
var v = fi.target.position.subtract( fi.position ).direction();
var angle = fi.heading.angleTo(v);
var r = Math.min(fi.maxPitch*delta, angle);//rotation speed is limited by maxPitch
var c = fi.heading.cross(v).direction(); //set the plane where we should rotate in
fi.orientation = fi.orientation.rotate( c, -r );
//log(this.name, fi.displayName+" rotated by "+r);
}
}
}
this._MFD = function() { //update Fighters MFD
var p = player.ship; if( !p || !p.isValid ) return;
var f = this.$Fighters;
var t = []; //targets
var n = []; //number of fighters on each targets
var free = 0; //fighters without target
var lost = 0; //lost fighters since last dock
for(var i = 0; i < f.length; i++) {
var fi = f[i];
if( fi && fi.isValid ) {
if( fi.target && fi.target.isValid
//&& fi.target.position.distanceTo(p.position) < 25600 //exclude far targets
) {
var j = t.indexOf(fi.target);
if( j == -1 ) {
j = t.length;
t.push(fi.target);
}
if( n[j] > 0 ) n[j]++; //one more fighter lock on this target
else n[j] = 1; //this is the first fighter who lock on this target
} else free++; //no target
} else lost++; //launched but destroyed
}
var mfd = "Fighters: ";
if( free > 0 ) mfd += free+" free, ";
var stay = this.$FightersOfPlayer - this.$LaunchedFighters;
var d = this._damagedFighter(p);
if( d ) {
mfd += (stay-d[0][0])+"+"+d[0][0]; //damaged ones after +
} else if( this.$FightersOfPlayer > 0 ) {
mfd += stay;
//if(this.$LaunchedFighters == 0) mfd += " stay";
} else mfd += "no one"; // none on Cobra Mark III
mfd += " in hangar";//+p.name;
if( lost > 0 ) mfd += ", "+lost+" lost";
mfd += "\n";
var pti = -1; //player target's index in t array
var pt = p.target;
if( pt && pt.isValid && (!pt.script || pt.script.$FighterOwner != p) ) {
pti = t.indexOf(pt);
var ptn = "None";
if( pti > -1 ) ptn = n[pti];
var ne = "";
if( pt.isDerelict ) ne = "derelict ";
else if( !pt.hasHostileTarget ) ne = "neutral ";
mfd += ptn+" aim your "+ne+"target "+this._nameWithMass(pt)+"\n";
}
var max = 9; //max. 9 lines left for targets in MFD
for(var i = 0; i < t.length && i < max; i++) {
if( i != pti ) { //exclude player's target due to already displayed as first target
mfd += n[i]+" aim "+this._nameWithMass(t[i])+"\n";
}
}
//while( i++ < max) mfd += "\n"; //scroll down to the last line
if( p.setMultiFunctionText ) p.setMultiFunctionText(this.$MFDName, mfd, true);
}
this._MFDAlign = function(left, right) { //insert as many spaces between as fill the line
var width = 14.2;
var space = " ";
left += space;
var t = left + right;
while( defaultFont.measureString(t) < width ) {
left += space;
t = left + right;
}
//fine tune with hair spaces - from spara's marketObserver
var hairSpace = String.fromCharCode(31);
while ( defaultFont.measureString(t + hairSpace) < width ) {
left += hairSpace;
t = left + right;
}
return(t);
}
this._MFDFrom = function(whomposition) { //direction mark
var ship = player.ship;
if( !ship || !ship.isValid || !whomposition || !whomposition.dot ) return ("");
var v = ship.position.subtract(whomposition);
var fw = ship.vectorForward.angleTo(v);
var ri = ship.vectorRight.angleTo(v);
var up = ship.vectorUp.angleTo(v);
var s = ""; // refine if out of front 1 degree cone
if( ri < 1.56 ) s += "<";
if( up > 1.58 ) s += "^";
else if( up < 1.56 ) s += "v";
if( ri > 1.58 ) s += ">";
return( s );
}
this._TimedDelay = function() {
this.$LaunchInProgress = false; //enable fighter launch again
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( ship == p && //player in red alert of forced launch
( this.$LastForced || player.alertCondition == 3 && player.alertHostiles )
|| ship.target && ship.target.hasHostileTarget ) {//NPC in attack mode
this._launchFighters(this.$LastTarget, this.$LastForced); //launch next fighter
} else {
this.$LastTarget = null;
if( this.$TimerDelay ) {
this.$TimerDelay.stop();
delete this.$TimerDelay;
}
}
} |