Scripts/st_equipment.js |
"use strict";
this.name = "Synchronised_Torus_Controller";
this.version = "1.0"; // 2015-12-14
this.description = "equipment script for torus drive synchronisation";
this.author = "Fritz G.";
this.copyright = "© 2015 Fritz G.";
this.licence = "CC BY-NC-SA 4.0";
//------------------------------------------------------------------------------------------------------------
// Credits to Capt. Murphy for his idea to implement torus synchronisation in his Escort Contracts OXP.
//------------------------------------------------------------------------------------------------------------
// This OXP is based on an idea I had when escorting slow freighters. Because NPX ships have no torus drive,
// this can take very long, if you want to follow them all the way from witchpoint to station.
// Later I discovered Escort Contracts OXP, using "torus synchronisation" to speed up the game.
// This of course works only for the ship you have agreed to escort, but using the same basic technique
// (simulating torus drive by using a high velocity) I was able to make this available for all ships
// that are underway in a straight line to a distant destination. That could be traders, bounty hunters,
// vipers or even pirates or assassins on their way to the station, the witchpoint or the sun.
// In theory this should work with all ships regardless of role, including OXP ships. Only Thargoids are
// excluded but they usually attack and dont't fly to a distant destination.
//
// Because the player shouldn't be able to change the destination of a NPC ship, the direction of torus
// driving (and its speed) is defined by the NPC. The torus drive stops if the destination is reached or
// if either the player or the mother is mass locked.
//------------------------------------------------------------------------------------------------------------
// configuration (values taken from Escort Contracts v1.7.1)
this.$st_minDist = 500; // minimum distance to NPC ship in game meters
this.$st_maxDist = 2500; // maximum distance to NPC ship in game meters
this.$st_headingAlign = 0.98; // precision needed for heading alignment (1 is exact (impossible!), 0.9 is too easy and looks odd)
this.$st_timerInterval = 0.75; // The Escort Contracts AI checks once per second, but a little more often can't do damage.
//------------------------------------------------------------------------------------------------------------
this.$st_timerRunning = false;
this.$st_npc = null; // will hold the ship object of the npc
this.$st_nbEscorts = 0; // number of npc escorts
// for saving scanner properties of targeted npc ship
this.$st_scanClass = "";
this.$st_scanColour1 = null;
this.$st_scanColour2 = null;
this.$st_scanDescription = null;
// sound
this.$st_beep = null;
this.$st_beepSounds = []; // array for storing the different sounds
// Key "n" pressed for starting or stopping torus synchronisation.
this.activated = function ()
{
if (!this.$st_timerRunning) this.$st_init(); // initialize timer and sounds
if (worldScripts["Synchronised_Torus"].$st_torusEngaged)
{
// stop synchronised torus
this.$st_stopTorus(1);
this.$st_message("Synchronised torus stopped.", 2);
}
else
{
// try to start synchronised torus
// first check the conditions (NPC targeted, correct distance and heading, NPC destination, mass locking)
if (this.$st_checkConditions(1))
{
this.$st_message("Synchronised torus drive engaged.", 1);
// Because the core game doesn't feature torus drives for npc ships, we have to simulate
// it by a high velocity. The factor 32 is the same as used for the standard torus drive.
// Synchronised torus speed is defined by the npc ship because it is the leader.
// For exact synchronisation of velocities the timer interval is too long, so use frame callback.
this.$st_frameCallback = addFrameCallback(this.$st_matchVelocity.bind(this));
this.$st_npc.velocity = this.$st_npc.heading.multiply(this.$st_npc.maxSpeed * 32);
// velocity will be synchronised in this.$st_matchVelocity(), but if we dont't do it here, the npc
// will start up to 1/60s (frame rate) earlier, and this is visible because of the very high speed.
player.ship.velocity = this.$st_npc.velocity;
if (this.$st_nbEscorts > 0) this.$st_setEscortVelocity(this.$st_npc.velocity);
worldScripts["Synchronised_Torus"].$st_torusEngaged = true;
this.$st_timer.start();
}
else
{
// Initialisation failed, reset the npc scanner class.
this.$st_resetScanClass();
}
}
};
// Key "b" pressed, show distance to target waypoint.
this.mode = function ()
{
if (!this.$st_timerRunning) this.$st_init(); // initialize sounds (and timer)
var pst = player.ship.target;
if (!pst)
{
this.$st_message("No target selected.", 2);
}
else if (!pst.isPiloted || pst.isStation)
{
this.$st_message("Unsuitable target.", 2);
}
else
{
this.$st_message("Distance to waypoint: " + Math.round(pst.position.distanceTo(pst.destination)/1000) + " km.", 0);
}
};
// Check if conditions allow starting or continuing torus synchronisation.
// Function called on activation (n key) (1) and in timer function (2).
this.$st_checkConditions = function(calledBy)
{
var ps = player.ship;
if (calledBy === 1)
{
// Checks only needed when starting (conditions shouldn't change after starting).
// First we have to check if something is targeted,
if (!ps.target)
{
this.$st_message("No target selected.", 2);
return false;
}
this.$st_npc = ps.target;
this.$st_nbEscorts = this.$st_npc.escortGroup.count - 1;
// Exclude unsuitable "ships"
// An Escort Contracts mother is unsuitable because the results could be unpredictable...
// Usually the mother starts synchronised torus driving anyway if it is possible.
// Escape capsules are piloted but don't have a torus drive.
if (!this.$st_npc.isPiloted || this.$st_npc.isThargoid || this.$st_npc.isStation || this.$st_npc.primaryRole === "ec_mother" || this.$st_npc.primaryRole === "escape-capsule")
{
this.$st_message("Unsuitable target.", 2);
return false;
}
// Is the target an escort?
if (this.$st_npc.owner != null)
{
// We could automatically target the leader. I tried it and it works, but it seemed too confusing.
// this.$st_message("Target was an escort, targeting leader.",0);
// ps.target = this.$st_npc.owner;
this.$st_message("Target is an escort, please target the leader.", 2);
return false;
}
// Does the target move?
if (this.$st_npc.speed === 0)
{
this.$st_message("Target is stationary.", 2);
return false;
}
// Check distance between ships.
if(this.$st_npc.position.distanceTo(ps.position) > this.$st_maxDist)
{
this.$st_message("Distance to target must not exceed " + this.$st_maxDist + " m.", 2);
return false;
}
else if(this.$st_npc.position.distanceTo(ps.position) < this.$st_minDist)
{
this.$st_message("Distance to target must be at least " + this.$st_minDist + " m.", 2);
return false;
}
// Check if escort ships are aligned correctly.
if (this.$st_nbEscorts > 0 && !this.$st_escortAligned(this.$st_npc.heading))
{
this.$st_message("Escort not aligned properly.", 2);
return false;
}
// Check for planet and star mass locks
// The reason for doing it this way is that player.alertMassLocked can't be used at
// this stage, even if these lines would stand behind the change of npc scan class.
if (this.st_checkForPlanetMassLock(ps))
{
this.$st_message("Mass-locked by planet.", 2);
return false;
}
else if (this.st_checkForSunMassLock(ps))
{
this.$st_message("Mass-locked by sun.", 2);
return false;
}
// To prevent the player being mass-locked we have to give the npc the scan class "rock".
// $st_setScanClass() will also turn the scanner lollipop to flashing yellow and magenta (like in Escort Contracts).
// Side effects:
// - The player status will change from yellow to green if no other ships are present
// - The legal status will not be shown during torus driving when using the Scanner Targeting Enhancement.
// Escorts, if present, will be "rocked" too, but they don't get a different colour.
this.$st_setScanClass();
}
// The following conditions are checked continuously during torus driving
// Torus synchronisation makes only sense over larger distances, so the distance to
// destination of npc ship must be outside scanner range.
var distance = this.$st_npc.position.distanceTo(this.$st_npc.destination);
if (distance < ps.scannerRange)
{
if (calledBy === 1)
this.$st_message("Next waypoint too close (" + Math.round(distance/1000) + " km).", 2);
else
this.$st_message("Waypoint reached.", 2);
return false;
}
else if (calledBy === 2)
{
// show (remaining) distance in scanner targeting description
this.$st_npc.scanDescription = "waypoint: " + Math.round(distance/1000) + " km";
}
// The player and in some cases the npc can change heading (but not velocity)
// during torus driving, so this has to be checked continuously.
if(this.$st_npc.heading.dot(ps.heading) < this.$st_headingAlign)
{
this.$st_message("Heading mismatch.", 2);
return false;
}
// Check for player mass lock (check returns true when done immediately after changing the npc ship(s) to rock).
if (calledBy != 1 && player.alertMassLocked)
{
this.$st_message("Mass-locked.", 2);
return false;
}
// Check for npc mass lock.
var targets = system.filteredEntities(this, st_checkForMassLock, this.$st_npc, this.$st_npc.scannerRange);
if (targets.length > 0)
{
this.$st_message("Target mass-locked.", 2);
return false;
}
// Check for npc planet and star mass locks
if (this.st_checkForPlanetMassLock(this.$st_npc))
{
this.$st_message("Target mass-locked by planet.", 2);
return false;
}
else if (this.st_checkForSunMassLock(this.$st_npc))
{
this.$st_message("Target mass-locked by sun.", 2);
return false;
}
// If player engages normal torus drive or injectors, synchronisation will be stopped.
// Player will be mass locked by npc ship shortly after this.
// Note: Torus driving does _not_ change speed, but using injectors does!
if (ps.torusEngaged || ps.speed > ps.maxSpeed)
{
return false;
}
// Everything is (still) ok
return true;
};
// Check for entities other than the player mass-locking the npc.
this.st_checkForMassLock = function(entity)
{
return ((entity.scanClass === "CLASS_NEUTRAL" || entity.scanClass === "CLASS_MILITARY" || entity.scanClass === "CLASS_POLICE" || entity.scanClass === "CLASS_THARGOID" || entity.scanClass === "CLASS_STATION") && !entity.isPlayer);
};
// Check mass-lock distance for sun
this.st_checkForSunMassLock = function(ship)
{
return (ship.position.distanceTo(system.sun.position) < system.sun.radius * 1.4142136);
};
// Check mass-lock distance for planets
this.st_checkForPlanetMassLock = function(ship)
{
for (var i = 0; i < system.planets.length; i++)
{
if (ship.position.distanceTo(system.planets[i].position) < system.planets[i].radius + Math.max(system.planets[i].radius, 25600))
{
return true;
}
}
return false;
};
// Stop torus, called by pressing n key (1) or by timer function (2).
this.$st_stopTorus = function(calledBy)
{
this.$st_timer.stop();
worldScripts["Synchronised_Torus"].$st_torusEngaged = false;
// Stop velocity synchronisation.
this.$st_removeFrameCallback();
// Bring ships back to normal speed.
this.$st_npc.velocity = this.$st_npc.thrustVector;
player.ship.velocity = player.ship.thrustVector;
if (this.$st_nbEscorts > 0) this.$st_setEscortVelocity(null);
// Reset scan class and lollipop colours.
this.$st_resetScanClass();
};
// Set velocity of escort ships or restore to normal.
this.$st_setEscortVelocity = function(v)
{
for (var i = 0; i < this.$st_nbEscorts; i++)
{
if (v)
this.$st_npc.escorts[i].velocity = v;
else
this.$st_npc.escorts[i].velocity = this.$st_npc.escorts[i].thrustVector;
}
};
// Check if all escort ships are aligned correctly.
this.$st_escortAligned = function(heading)
{
for (var i = 0; i < this.$st_nbEscorts; i++)
{
if(this.$st_npc.escorts[i].heading.dot(heading) < this.$st_headingAlign)
return false;
}
return true;
};
// Set scan class to prevent mass locking.
this.$st_setScanClass = function()
{
if (this.$st_npc)
{
// Save for resetting. It can be assumed that escorts have the same class.
// We should save the colours too because these could have been changed by an OXP.
// Scan description will be used to show remaining distance to waypoint.
this.$st_scanClass = this.$st_npc.scanClass;
this.$st_scanColour1 = this.$st_npc.scannerDisplayColor1;
this.$st_scanColour2 = this.$st_npc.scannerDisplayColor2;
this.$st_scanDescription = this.$st_npc.scanDescription;
this.$st_npc.scanClass = "CLASS_ROCK";
this.$st_npc.scannerDisplayColor1 = "yellowColor"; // same colours are used in Escort Contracts
this.$st_npc.scannerDisplayColor2 = "magentaColor";
for (var i = 0; i < this.$st_nbEscorts; i++)
{
this.$st_npc.escorts[i].scanClass = "CLASS_ROCK";
// The escorts should not flash. Note: The saved colour values are usually null,
// so using them here will turn the escorts white, the default for scan class "rock".
if (this.$st_scanClass === "CLASS_POLICE" || this.$st_scanClass === "CLASS_MILITARY")
{
this.$st_npc.escorts[i].scannerDisplayColor1 = "purpleColor";
this.$st_npc.escorts[i].scannerDisplayColor2 = "purpleColor";
}
else
{
this.$st_npc.escorts[i].scannerDisplayColor1 = "yellowColor";
this.$st_npc.escorts[i].scannerDisplayColor2 = "yellowColor";
}
}
}
};
// Reset scan class after stopping torus driving or after a failed starting attempt.
this.$st_resetScanClass = function()
{
// only reset if it has been changed already
if (this.$st_npc && this.$st_scanClass != "")
{
this.$st_npc.scanClass = this.$st_scanClass;
this.$st_npc.scannerDisplayColor1 = this.$st_scanColour1;
this.$st_npc.scannerDisplayColor2 = this.$st_scanColour2;
this.$st_npc.scanDescription = this.$st_scanDescription;
for (var i = 0; i < this.$st_nbEscorts; i++)
{
this.$st_npc.escorts[i].scanClass = this.$st_scanClass;
this.$st_npc.escorts[i].scannerDisplayColor1 = this.$st_scanColour1;
this.$st_npc.escorts[i].scannerDisplayColor2 = this.$st_scanColour2;
}
this.$st_scanClass = "";
}
};
// Timer function, checks if conditions are still ok.
this.$st_torusDriving = function()
{
if (!this.$st_checkConditions(2))
{
this.$st_stopTorus(2);
}
else
{
// Velocity reduces slowly, so we have to keep it up.
this.$st_npc.velocity = this.$st_npc.heading.multiply(this.$st_npc.maxSpeed * 32);
player.ship.velocity = this.$st_npc.velocity;
if (this.$st_nbEscorts > 0) this.$st_setEscortVelocity(this.$st_npc.velocity);
}
};
// Frame callback function for adjusting velocities continuously.
this.$st_matchVelocity = function()
{
player.ship.velocity = this.$st_npc.velocity;
if (this.$st_nbEscorts > 0) this.$st_setEscortVelocity(this.$st_npc.velocity);
};
// Remove frame callback function.
this.$st_removeFrameCallback = function()
{
if (this.$st_frameCallback)
{
removeFrameCallback(this.$st_frameCallback);
delete this.$st_frameCallback;
}
};
// Console message with no sound (0), beep (1), or boop (2).
this.$st_message = function(msg, beep)
{
if (beep > 0)
{
this.$st_beep.sound = this.$st_beepSounds[beep];
this.$st_beep.play();
}
player.consoleMessage(msg);
};
// Initialise timer and sound.
this.$st_init = function()
{
// initialise timer
this.$st_timer = new Timer(this, this.$st_torusDriving, -1, this.$st_timerInterval);
this.$st_timerRunning = true;
// initialise beep sounds
this.$st_beepSounds[1] = Sound.load("beep.ogg"); // on
this.$st_beepSounds[2] = Sound.load("boop.ogg"); // off, interrupted, not possible
this.$st_beep = new SoundSource;
// this.$st_beep.sound = this.$st_beepSounds[1];
this.$st_beep.volume = 1;
this.$st_beep.loop = false;
};
|