Scripts/stardestroyer-fighter.js |
"use strict";
this.name = "stardestroyer-fighter";
this.author = "Norby";
this.copyright = "2016 Norbert Nagy";
this.licence = "CC BY-NC-SA 4.0";
this.description = "support functions for fighters";
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 = false; //cache of this.ship.scriptInfo.npc_shields
this.$OwnerScoopPos = null; //store the mothership's scoop position for landing
this.$TimerLandNow = null; //trigger _landNow() after some time for sure landing
this.$ws = worldScripts.stardestroyer;
//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.shipTakingDamage = function(amount, whom, type) {
this.$FighterHull -= amount; //workaround recharge when no shields
if( !this.$FighterShields ) {
if(this.$FighterHull < 0) {
delete this.shipTakingDamage;//prevent infinite loop
this.ship.explode();
}
}
}
this.shipSpawned = function() {
if( this.ship && this.ship.scriptInfo && this.ship.scriptInfo.npc_shields == "no" ) {
this.$FighterShields = false;
} else this.$FighterShields = true;
}
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 == player.ship) return; //exit if worldScript, do in ship script only
//var w = worldScripts.escortdeck;
//increase steering and acceleration in proportion with the smaller mass on deck
//w.$EscortDeck_SetMaxs( player.ship, w );
//var pad = w.$EscortDeckShip.indexOf(this.ship);
//if( pad > -1 ) w.$EscortDeckShipData[pad] = null; //prevent recreation in dock
if( whom && whom.isValid && whom != player.ship && this.$FighterOwner == player.ship ) {
player.commsMessage("A "+this.ship.name+" is lost.", 5);
this.$ws._MFD(); //update numbers in MFD
}
log(this.name, this.ship.displayName+" terminated by "+whom+" "+why);
}
//fighter functions
this._escortPosition = function() {this._escortPosition2(this.ship);} //for stardestroyer-fighterAI.plist
this._escortPosition2 = function(ship) {
var sc = ship.script;
if( !sc || !sc.$FighterNumber || !sc.$FighterOwner || !sc.$FighterOwner.isValid
|| ship == player.ship ) return; //for sure
ship.scanClass = "CLASS_BUOY";//for scoop deactivation at stopped landing and no masslock
if( sc.$FighterOwner == player.ship && player.alertCondition == 1 ) { //green alert
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) {
var sc = ship.script;
if( !sc || ship == player.ship ) return; //for sure
ship.scanClass = "CLASS_CARGO";//for scoop activation
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(200).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
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/stardestroyer.js |
"use strict";
this.name = "stardestroyer";
this.author = "GGShinobi, Norby";
this.copyright = "© 2013 GGShinobi, Creative Commons: attribution, non-commercial, sharealike.";
this.description = "some special actions for the stardestroyer.";
// CHANGELOG:
// changes from 0.0.2 to 0.0.3: - added player ship handler, fighters and ILS support by Norby
// changes from 0.0.1 to 0.0.2: - added "throw_sparks = yes;" to wrecked stardestroyer in shipdata.plist
// changes from 0.0.0 to 0.0.1: - fixed bug that orientation of doomedStarDestroyer didn't match the orientation
// of the original stardestroyer. Thanx to cim!
//this script works as NPC ship script and also worldScript for the player
//settings
this.$FighterCost = [1000, 2000]; //cost of TIE Fighter and Interceptor, equipment.plist also!
this.$LaunchDelay = 1; //delay between launch of fighter groups in seconds
this.$MaxFighters = 72; //capacity of the internal fighter hangar
this.$MFDName = "Star Destroyer 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.$Fighters = []; //Active, launched fighters
this.$FightersOfPlayer = 0; //actually how many figters are in player's star destroyer
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.$simulator = false; //detect if combat simulator is running
this.$stardestroyershuttle = null; //player docking support ship
this.$Targets = []; //Ships once targeted by fighters, indexed by entityPersonality
this.$TimerDelay = null; //for fighter launch delay
//event handlers
this.startUp = function() {
this.$FightersOfPlayer = missionVariables.$StarDestroyerFightersOfPlayerPlusOne;
if( !this.$FightersOfPlayer ) this.$FightersOfPlayer = this.$MaxFighters;
else this.$FightersOfPlayer--; //missionVariables store one more to avoid 0
var h = worldScripts.hudselector;
if( h && h.$HUDSelectorAddMFD ) h.$HUDSelectorAddMFD(this.name, this.$MFDName);
var e, p = player.ship;
e="EQ_DTADAM"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
e="EQ_DTADER"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
e="EQ_DTAEMP"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
e="EQ_DTAMIN"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
e="EQ_DTAUSA"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
e="EQ_DTAWEA"; if(p.equipmentStatus(e)==="EQUIPMENT_OK") p.removeEquipment(e);//fix for towbar
}
this.startUpComplete = function() {
var t = worldScripts.trophy_col;
if(t) { //remove bogus trophy entry
var n = "Star Destroyer [reactor breach detected]";
for (var j = t.$trophyArray.length - 1; j >= 0; j--) {
if (t.$trophyArray[j][0] === n) t.$trophyArray.splice(j,1);
}
for (var j = t.$trophyLog.length - 1; j >= 0; j--) {
if (t.$trophyLog[j][0] === n) t.$trophyLog.splice(j,1);
}
}
}
this.alertConditionChanged = function(newCondition, oldCondition) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( !this._playerInStarDestroyer(ship) || player.docked ) return;
this._MFD();
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 == 2 || newCondition == 3 && player.alertHostiles ) {
this._fightersStopLanding(); //cancel landing in yellow or red alert
this._reconsiderTargets(p, p.target); //update targets of launched fighters
}
}
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) {
//6 groups in hexagon form like the Imperial Logo, for 6*4=24 fighters
//the ring of groups always show the xz plane of system due to zero second coordinates
var positions = [new Vector3D(0,0,1), new Vector3D(0,0,-1),
new Vector3D(-0.8,0,0.5), new Vector3D(0.8,0,0.5),
new Vector3D(-0.8,0,-0.5), new Vector3D(0.8,0,-0.5)];
var space = 100; //m space between escorts in the same group
var zoom = 3000; //m, radius of group positions around the main ship
//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 24 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.helpRequestReceived = function(ally, enemy) {
this._launchFighters(enemy);
}
this.playerBoughtEquipment = function(equipmentKey) {
if(equipmentKey === "EQ_TIE_F" || equipmentKey === "EQ_TIE_I") {
player.ship.removeEquipment(equipmentKey);
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, hangar is full.";
player.consoleMessage("You have "+this.$FightersOfPlayer+f, 5);
}
}
this.playerCancelledJumpCountdown = function() { //stop landing
if(!this._playerInStarDestroyer(player.ship)) return;
this._fightersFollow();
}
this.playerJumpFailed = function(reason) {
if(!this._playerInStarDestroyer(player.ship)) return;
this._fightersFollow();
}
this.playerStartedJumpCountdown = function(type, seconds) { //landing fighters
if(!this._playerInStarDestroyer(player.ship)) return;
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) {
missionVariables.$StarDestroyerFightersOfPlayerPlusOne = 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.shipCollided = function(otherShip) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( !this._playerInStarDestroyer(ship) ) return;
if( this._isStation(otherShip) ) {
//check if facing to a station - must exclude non-facig to skip collisions at launch
var angle = ship.heading.angleTo( otherShip.position.subtract(ship.position) );
otherShip.commsMessage("Docking sequence initiated", player.ship); //angle); //debug
if( angle < 1.5 ) otherShip.dockPlayer(); //this help needed by ILS OXP to dock Star Destroyer
}
}
// sometimes, the reactor core of the stardestroyer breaches, resulting in a gigantic explosion
// of a destructive force equivalent to that of a q-bomb! Beware!!
this.shipDied = function(whom, why) {
// if (Math.random() > 0.77) { // chance for reactor breach: 33 %
// this.ship.spawn("stardestroyer_reactor_breach", 1);
if(!this.ship || !this.ship.position) return;
if(this.ship.primaryRole == "pirate-aegis-raider" //aegis raider should not blow up the station
|| this.ship.dataKey == "stardestroyer-pirate2") //no black reactor breach ship in shipdata
return;
var doomedStarDestroyer = system.addShips("stardestroyer_reactor_breach", 1, this.ship.position, 0)[0];
doomedStarDestroyer.orientation = this.ship.orientation; // change orientation to that of the original ship
doomedStarDestroyer.velocity = this.ship.velocity;
doomedStarDestroyer.script.$startSubDetonations(doomedStarDestroyer);
// }
}
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._playerInStarDestroyer(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 ) {
if( !this._playerInStarDestroyer(ship) ) return;
this._MFD();
this._reconsiderTargets(ship, t); //update targets of launched fighters
}
if( this._isStation2( t, ship ) //launch shuttle if station is targeted
//player also must have no undamaged ILS equipment on board (NPC SD always send Shuttle)
&& ( ship != p || p.equipmentStatus("EQ_ILS") != "EQUIPMENT_OK" ) ) {
var s = this.$stardestroyershuttle;
if( s && s.isValid && s.target != t ) s.target = t;
if( !s && ( ship != p || !p.missilesOnline ) ) {
s = ship.spawnOne("[stardestroyershuttle]");
if( s && s.isValid ) {
this.$stardestroyershuttle = s;
s.target = t;
var msg = "";
if( !s.target ) {
msg = "Your Shuttle is not launched due to no friendly station nearby";
if(ship == p) player.consoleMessage(msg, 4.5);
s.remove(true);
} else {
msg = "Your Shuttle is going to "+s.target.name;
if(ship == p) player.consoleMessage(msg, 10);
s.position = ship.position.add(ship.vectorUp.multiply(-150));
s.orientation = ship.orientation;
if( s.injectorSpeedFactor > 0 )
s.desiredSpeed = s.injectorSpeedFactor * s.maxSpeed;
else s.desiredSpeed = 7 * s.maxSpeed; //for Oolite 1.80
s.velocity = ship.velocity.add(ship.vectorForward.multiply(700)); //initial push
if(!s.script) s.script = "stardestroyer-shuttle.js"; //for sure
if(s.script) s.script.$FighterOwner = ship;//store owner for ship script
ship.target = s; //lock to the shuttle: player can see it, NPC avoid to dock by ILS
}
}
}
}
}
this.shipTargetLost = function(t) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( this._playerInStarDestroyer(ship) ) {
this._MFD();
}
}
this.shipWillDockWithStation = function(station) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( this._playerInStarDestroyer(ship) ) {
this.$stardestroyershuttle = null; //player docking support ship
// if this was a simulator run, reset the variable and return - no docking fee in this case
if (this.$simulator == true) {
this.$simulator = false;
} else if( ship.equipmentStatus("EQ_TIE_FF") != "EQUIPMENT_OK"
&& ship.equipmentStatus("EQ_TIE_IF") != "EQUIPMENT_OK" ) {
var s = ""; if( this.$FightersOfPlayer > 1 ) s = "s"; //current fighters in plural
player.addMessageToArrivalReport(expandDescription("[stardestroyer-factorydamaged]",
{fightersofplayer: this.$FightersOfPlayer, plural:s, maxfighters:this.$MaxFighters}));
} else {
var itc = 0; //index of TIE Fighter's cost in this.$FighterCost array
if( ship.equipmentStatus("EQ_TIE_IF") == "EQUIPMENT_OK" )
itc = 1; //index of TIE Interceptor's cost when ride Imperial variant of SD
var fee = this.$FighterCost[itc];
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 fi.remove(true); //do not leave fighters outside of the station
}
var bf = 0; //bought fighters
var lf = 0; //lost fighters
for(var i = this.$FightersOfPlayer; i < this.$MaxFighters; i++) {
lf++;
if (player.credits > fee) { // make sure the player has enough credits
player.credits -= fee; //buy a fighter
this.$FightersOfPlayer++;
bf++;
}
}
var cbf = fee * bf; //cost of bougth fighters
var s = ""; if( lf > 1 ) s = "s"; //lost fighters in plural
var s2 = ""; if( this.$FightersOfPlayer > 1 ) s2 = "s"; //current fighters in plural
if( !(lf > 0) ) {
//no lost fighters, have a nice trip
var s = " is"; if( this.$MaxFighters > 1 ) s = "s are"; //fighters in plural
player.addMessageToArrivalReport(expandDescription("[stardestroyer-allfighters]",
{maxfighters:this.$MaxFighters, plural:s}));
} else if( !station || !station.isValid || !station.hasShipyard ) {
//no shipyard, no replace
player.addMessageToArrivalReport(expandDescription("[stardestroyer-noshipyard]",
{sdlostfighters:lf, plural:s, maxfighters:this.$MaxFighters}));
} else if( bf == lf ) {
//bought all missing fighters
player.addMessageToArrivalReport(expandDescription("[stardestroyer-boughtfighters]",
{sdlostfighters:lf, plural:s, costofbougthfighters:cbf,
fightersofplayer: this.$FightersOfPlayer, plural2:s2}));
} else if( bf > 0 ) {
//bought some but not all missing fighters
player.addMessageToArrivalReport(expandDescription("[stardestroyer-notenoughmoney]",
{sdlostfighters:lf, plural:s, costofbougthfighters:cbf, numberofbougthfighters:bf,
fightersofplayer: this.$FightersOfPlayer, plural2:s2}));
} else if( lf >= this.$MaxFighters && bf == 0 ) {
//all fighters are missing but none bought
player.addMessageToArrivalReport(expandDescription("[stardestroyer-emptyhangar]",
{sdlostfighters:lf, plural:s, fightercost: fee}));
} else if( bf == 0 ) {
//at least one fighter is missing but none bought
player.addMessageToArrivalReport(expandDescription("[stardestroyer-nomoneyforone]",
{sdlostfighters:lf, plural:s, fightersofplayer: this.$FightersOfPlayer, plural2:s2}));
} else {
//not a real chance to arrive here, just display the current fighter status for sure
player.addMessageToArrivalReport(expandDescription("[stardestroyer-noshipyard]",
{sdlostfighters:lf, plural:s, 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
if( this._playerInStarDestroyer(ship) ) {
this.$stardestroyershuttle = null; //player docking support ship
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, p); //use the last chance to land before left behind
} 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);
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) {
var p = player.ship;
var ship = this.ship;
if( !ship ) ship = p; //called in worldScript
if( this._playerInStarDestroyer(ship) ) {
p.position = p.position.add(p.vectorForward.multiply(700)); //prevent collisions at launch
this._MFD(); //show mfd as early as other MFDs
}
}
//local functions
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 fighterai = "stardestroyer-fighterAI.plist";
if(fi.AI != fighterai ) fi.switchAI(fighterai);
fi.target = null; //clear target to stop attack
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._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) {
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._playerInStarDestroyer(ship)) return;
//continue if NPC or player in Star Destroyer
if( !this._isValidFighterTarget(target, ship) ) return;//skip invalids like own fighters
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
&& ( !ship.withinStationAegis || player.alertCondition == 3 ) ) ) {
//&& this._isNewTargetOrNeedMoreFighters(ship, target) ) {
//launch more fighters, must far below to avoid kill by collision at injector speed
var pos = ship.position.add(ship.vectorUp.multiply( -200 ));
var tiekey = "tiefighter";
if( ship == player.ship && ship.equipmentStatus("EQ_TIE_IF") != "EQUIPMENT_UNAVAILABLE"
|| ship != player.ship && ship.dataKey.indexOf("stardestroyer-pirate") == 0 ) //NPC ISD
tiekey = "tieinterceptor"; //only in Imperial-class Star Destroyers
else if( ship.dataKey.indexOf("IST_destroyer") == 0 )
tiekey = "IST_tiefighter"; //naval, asked by UK_Eliter for Interstellar Tweaks OXP
var ties = Math.min(this.$MaxFighters-this.$LaunchedFighters,4); //launch 4 fighters at once
var ships = system.addShips(tiekey, ties, pos, 50);
var oldlf = this.$LaunchedFighters;
if(ships) for(var i = 0; i < ships.length; i++) {
var si = ships[i];
if(si) {
this.$Fighters.push(si);
this.$LaunchedFighters++;
this.$LaunchInProgress = true;
si.displayName = si.name+" #"+this.$LaunchedFighters;
if(!si.script) si.script = "stardestroyer-fighter.js"; //for sure
if(si.script) {
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;
si.target = target;
si.performAttack();
if( ship == player.ship ) {
si.bounty = 0; //remove bounty from fighter given in shipdata.plist
//si.setBounty(0, "player's stardestroyer launched a fighter");
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.displayName; //not owned by the player
if(ship.dataKey.indexOf("stardestroyer-pirate2") == 0 )
si.scannerDisplayColor1 = [0, 0, 0.4]; //darkblue
}
}
}
if( this.$LaunchInProgress && !this.$TimerDelay ) {//disable future launches until delay
this.$TimerDelay = new Timer(this, this._TimedDelay.bind(this), this.$LaunchDelay);
}
if( ship == player.ship && this.$LaunchedFighters > oldlf ) {
var newf = this.$LaunchedFighters - oldlf;
var remain = this.$FightersOfPlayer - this.$LaunchedFighters;
player.consoleMessage(newf+" fighters launched to "+target.name+", "+remain+" remain", 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._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._playerInStarDestroyer = function(ship) {
if( ship && ship.isValid && ship == player.ship ) {
if( ship.dataKey.indexOf("stardestroyer-player") == 0 )
return true; //detect both stardestroyer-player and stardestroyer-player2 ships
else this.$FightersOfPlayer = 0; //no TIE fighters on other ship types
}
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);
}
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 owner's tagret?
if( !fi.target && ownertarget && ownertarget != fi.target
&& ownertarget != fi //do not target itself
//only the half of fighters should attack the mother's target
//&& ( i - invalidfighters ) * 2 < f.length - invalidfighters
) {
fi.target = ownertarget; //attack the mother's target (except friends)
}
//only the half of fighters attack the mother's target
//if( fi.target && fi.target.isValid && fi.target == ownertarget ) {
// if( oldtargetistargetofowner*2 > f.length - invalidfighters
// && target && target.isValid ) {
// fi.target = null;
// }
//}
//if still no target then aim the nearest defensive target?
if( owner && owner.isValid && ( !fi.target || !fi.target.isValid ) ) {
var d = owner.defenseTargets;
if( d && d.length > 0 ) {
var mindist = fi.scannerRange;
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];
}
}
//aim the new target?
if( target && target != fi && ( !fi.target || !fi.target.isValid
//replace any target if the given new target is more than 10km nearer
|| fi.target.position.distanceTo(fi.position) - 10000
> target.position.distanceTo(fi.position) ) ) {
fi.target = target; //lock on the newest aggressor
}
if(fi.target) { //attack the target
if(fi.AI == "stardestroyer-fighterAI.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 == "stardestroyer-fighterAI.plist" ) 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._MFD = function() { //update Star Destroyer 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;
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
if( this.$TimerDelay ) {
this.$TimerDelay.stop();
delete this.$TimerDelay;
}
} |