Back to Index Page generated: May 8, 2024, 6:16:03 AM

Expansion Synchronised Torus

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Enables torus drive synchronisation with NPC ships escorted by the player. Enables torus drive synchronisation with NPC ships escorted by the player.
Identifier oolite.oxp.FritzG.Synchronised_Torus oolite.oxp.FritzG.Synchronised_Torus
Title Synchronised Torus Synchronised Torus
Category Equipment Equipment
Author Fritz G. Fritz G.
Version 1.0 1.0
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Synchronised_Torus_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/e/ec/Synchronised_Torus_1.0.oxz n/a
License CC BY-NC-SA 4 CC BY-NC-SA 4
File Size n/a
Upload date 1610873506

Documentation

Also read http://wiki.alioth.net/index.php/Synchronised%20Torus

Equipment

Name Visible Cost [deci-credits] Tech-Level
Torus synchronisation no 1 1+

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
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;  
};	
Scripts/st_world.js
"use strict";
this.name	     = "Synchronised_Torus";
this.version     = "1.0";    // 2015-12-14
this.description = "World script for awarding torus drive synchronisation equipment at start-up";
this.author	     = "Fritz G.";   
this.copyright	 = "© 2015 Fritz G.";
this.licence	 = "CC BY-NC-SA 4.0";

//--------------------------------------------------------------------------------------------------------------------
// For information and configuration see st_equipment.js
//--------------------------------------------------------------------------------------------------------------------
this.$st_torusEngaged = false;  // this can be used read only (!) by other OXPs (similar to player.ship.torusEngaged)    
//--------------------------------------------------------------------------------------------------------------------

// Award primable equipment at startup.
// It is assumed that every ship can do torus synchronisation without the player having to buy 
// additional equipment, because every ship can do it in Escort Contracts OXP too. The equipment 
// is only needed for starting and stopping the synchronisation by pressing a key.
this.startUp = function() 
{
	if(!player.ship.awardEquipment("EQ_SYNC_TORUS")) 
	{	
		log(this.name, "Equipment EQ_SYNC_TORUS already present or failed to award");
	}
	else
	{
		log(this.name, "Equipment EQ_SYNC_TORUS successfully awarded");
	}
};