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

Expansion Trails

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description Ships leaves condensation trails, rocks make gray vapour when move. Works without shaders but not recommended for old computers where FPS could be low when display many trails. No trails in Red Alert below 50 FPS if weapons are online. Ships leaves condensation trails, rocks make gray vapour when move. Works without shaders but not recommended for old computers where FPS could be low when display many trails. No trails in Red Alert below 50 FPS if weapons are online.
Identifier oolite.oxp.Norby.Trails oolite.oxp.Norby.Trails
Title Trails Trails
Category Ambience Ambience
Author Norby Norby
Version 1.11 1.11
Tags
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Trails n/a
Download URL https://wiki.alioth.net/img_auth.php/c/ca/Trails_1.11.oxz n/a
License CC BY-NC-SA 4 CC BY-NC-SA 4
File Size n/a
Upload date 1610873471

Documentation

Also read http://wiki.alioth.net/index.php/Trails

Trails_readme.txt

Trails

NPC ships leaves condensation trails. Rocks make gray vapour when move.

Player ship will not make trail to avoid disturbing the aft view.

Based on Zireael's Engine Trails but rewritten from scratch to avoid the "Universe is full" error.

The default lifetime of a trail element is 9 seconds, the maximal length is 300m and will be reduced automatically when the total number of entities step over 1500.

The lifetime and the number of used light sources will be reduced below 25 FPS which cause thicker trails.

No trails in Red Alert below 50 FPS if weapons are online for smooth combat.

Visual elements based on the fuel leak effect in CommonSenseOTB's CustomShields OXP.


Instructions:

Do not unzip the .oxz file, just move into the AddOns folder of your Oolite installation.

License:

This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License version 4.0.
If you are re-using any piece of this OXP, please let me know by sending an e-mail to norbylite@gmail.com.


Changelog:
 2016.05.04. v1.11 No trails after mini ships below 10t mass like Tie Fighters.
 2016.02.16. v1.9  No trails in Red Alert below 50 FPS if weapons are online for smooth combat.
                   Fixed trail on Gnat, thanks to phkb.
 2016.02.15. v1.8  Render trail elements in sight only, others moved far away.
                   Lifetime of elements start at 9 seconds and step down to 3 seconds in low-FPS.
 2016.02.14. v1.7  New low-FPS mode added.
 2016.02.13. v1.6  Time-based trail dissipation so trail length is depending on speed.
                   Less visible trails at long range.
                   Thinner trail on small ships.
                   Missiles are excluded.
 2016.02.03. v1.5  Sparks in Griff Alloys and Wreckage OXP are are excluded for explosions.
 2016.02.03. v1.4  Wreckages, alloys and cargopods are excluded for speed up explosions.
                   Increase quality back to normal over 50 FPS and not the double of MinFPS.
 2016.02.03. v1.3  Rocks make gray vapour when move.
                   Speedup for explosions: wreckages make rare trail only.
                   General speedup due to less flashers (half than before).
                   Another speedup on trails over 12km.
                   Default of MinFPS is increased to 25 FPS.
                   Fixed an oddity with Telescope.
 2015.05.01. v1.2  Escort ships on EscortDeck does not exhaust fumes anymore.
                   Fixed a gap after fast ships.
                   No more trails after ships without exhaust like Thargoids, Nyoka.
                   Steam trails after moving rocks.
                   Always use less flashers in trails over 10km.
                   Trail starts near the engine.
 2015.05.01. v1.1  Use less flashers in trails for better performance.
                   Visibility of trails increased up to 2x scanner range.
 2015.04.30. v1.0  First release.

Equipment

This expansion declares no equipment.

Ships

This expansion declares no ships.

Models

This expansion declares no models.

Scripts

Path
Scripts/trails.js
"use strict";
this.name	= "trails";
this.author	= "Norby";
this.copyright	= "2015 Norby";
this.description= "Ships leaves condensation trails";
this.licence	= "CC BY-NC-SA 4.0";

this.$GoodFPS = 50; //if FPS is over this and quality is reduced then increase back
this.$LowFPS = 25; //if fall below then reduce quality using smaller number in dataKey
this.$Zoom = false; //increase brightness over 12km to detect far ships, disabled in ambience pack

//internal properties, should not touch
this.$Delta = 0; //delta counter for FCB
this.$Delta2 = 0; //store first delta in the last second for FCB
this.$Distance = 60; //distance between trail elements based on effectada.plist
this.$ED = null; //store a pointer to escortdeck worldscript
this.$FC = 0; //frame counter for calculating FPS
this.$FP = null; //FCB pointer
this.$FPS = 60; //lastly measured FPS
this.$Length = this.$MaxLength; //actual length to avoid "Universe is full"
this.$MaxAngle = 0.7; //max. angle of sight in radian (0.7 for FOV80), move far away if out of sight
this.$MaxKey = 3; //max. used dataKey from effectdata.plist ("trails3")
this.$Key = this.$MaxKey; //start at the max. available dataKey
this.$MaxLength = 50; //how many elements are in a trail (60m each) based on effectdata.plist
this.$MaxSec = 3 + 2*this.$MaxKey; //max timeout of trail elements in seconds (9)
this.$Old = false; //old version before Oolite v1.82
this.$Red = false; //No trails in red alert below 50 FPS if weapons are online
this.$S = [];	//tracked ships and visualEffects
		//$S[i][0]: the ship object,
		//$S[i][1]: last position of the ship where a visualEffect is created - unused
		//$S[i][2]: lastly used "k" indexes
                //$S[i][k] where k >= 3: visualEffects
this.$Sec = this.$MaxSec; //timeout of trail elements in seconds (3-6)
this.$Shift = 10000000000; //x coord. shift if the trail element is not in sight
this.$T = [];	//timestamps of the creation of the trail elements
//this.$V = [];	//storage of all visualEffects

//worldscript events
this.startUp = function() {
	this.$ED = worldScripts.escortdeck;
	this.$Clear(this);
	this.$FC = 0; //frame counter
	this.$Delta = 0; //delta counter
	if (0 < oolite.compareVersion("1.82")) {
		this.$Old = true;
/*		
		//pre-creating visualEffects
		var key = "trails";
		var pos = Vector3D(100000000,0,0);
		for( var i = 0; i < 100; i++ ) {
			this.$V.push(system.addVisualEffect( key, pos ));
		}
*/		
	} else { //from Oolite v1.82 flashers in subentities are usable, like in "trails+"
		this.$Old = false;
	}
	if( !isValidFrameCallback( this.$FP ) )
                this.$FP = addFrameCallback( this.$FCB.bind(this) );
}

this.shipWillEnterWitchspace = function() {
	this.$Clear(this);
}

//local methods
this.$Clear = function(w) {  //start a new array, free up memory
	w.$Length = w.$MaxLength;
        w.$RemoveAllTrails(w);
	delete w.$S;
	w.$S = [];
	w.$S[0] = [];
	w.$S[0][0] = player.ship; //the first ship
        delete w.$T;
        w.$T = [];
        w.$T[0] = [];
        w.$Red = false;
}

this.$FCB = function( delta ) {
	var w = this; //worldscript
	var p = player.ship;
	var pp = false;
	if( p && p.isValid ) pp = p.position;
	w.$FC++; //frame counter for calculating FPS
	w.$Delta += delta;
	if( w.$Delta > w.$Delta2 + 1 && pp ) { //like an 1s timer, search for new ships
		w.$FPS = w.$FC; //save measured fps for the trail length reducer code below
		w.$FC = 0; //reset frame counter
		w.$Delta2 = w.$Delta; //save delta
		
		//No trails in red alert below 50 FPS if weapons are online
                if( player.alertCondition > 2 && p.weaponsOnline ) {
                        if( !w.$Red && w.$FPS < w.$GoodFPS ) {
                                //first time after red alert started
                                w.$RemoveAllTrails(w);
                                w.$Red = true;
                        } //restore trails in red alert over 100FPS only
                        if( w.$Red && w.$FPS < 2 * w.$GoodFPS ) return;
                }
                w.$Red = false;
		
		var ships = [];
		if( !p.docked ) ships = p.checkScanner(false);//faster than filteredEntities
				//must use checkScanner with false to add the "green" escorts
				//and a good side effect boulders get steam tails
				//moreover true return the full list in Oolite 1.80 (fixed in 1.81)
		for( var i = 1; i < w.$S.length; i++ ) { //remove trails of far ships
			var o = w.$S[i]; //data of an old ship
			var x = -2;
			if( o && o[0] && o[0].isValid ) {
				x = ships.indexOf( o[0] );
				if( w.$ED ) { //disable escort trails on deck, enable if launched
					if( !o[0].script ) //for sure
						o[0].setScript("oolite-default-ship-script.js");
					var pad = w.$ED.$EscortDeckShip.indexOf( o[0] );
					if( pad != -1 ) { //change on ships used by escortdeck only
                                                if( w.$ED.$EscortDeckShipPos[pad] )
                                                        o[0].script.$TrailsDisabled = true;
                                                else o[0].script.$TrailsDisabled = false;
					}
				}
			}
			//if invalid then docked, jumped or destroyed, leave to run remove below
			if( x == -1 ) { //not in the scanner anymore?
				//must check distance due to many ships near exhibitions
				//can leave out important ones from the limited checkScanner list
				if( pp.distanceTo( o[0].position ) > 2 * p.scannerRange ) {
					for( var k = 3; k <= w.$Length; k++ ) {
						var v = o[k]; //remove visualEffects
						if( v && v.isValid ) v.remove();
					}
					w.$S[i] = false; //remove this ship from the tracked array
				}
			} else if( x >= 0 ) ships[x] = false; //don't add into $S if already in

		}
		
		if( p.docked ) {
			if( w.$S[1] ) w.$Clear(w); //start a new array, free up memory
		} else for( var i = 0; i < ships.length; i++ ) { //add new ships into $S
			var ship = ships[i];
			if( ship && ship.isValid && ship.maxSpeed > 0 //do not add buoy, etc.
				&& ( ship.exhausts && ship.exhausts[0] //need valid exhaust
					|| ship.scanClass == "CLASS_ROCK" )
                                && !ship.isMissile
                                && ship.primaryRole != "wreckage" //exclude for explosions
                                && ship.primaryRole != "oolite-wreckage-chunk"
                                && ship.primaryRole != "alloy"
                                && ship.primaryRole != "cargopod"
                                && ship.primaryRole != "griffspark" //explosion in griff's wreckages
				&& ship.dataKey != "telescopemarker" //exclude for Telescope
                                && ship.mass > 10000 //exclude TIE fighters and too small ships
                                && ship.scanClass != "CLASS_NO_DRAW" ) { //exclude for sure
				//do not add escorts landed on EscortDeck
				var add = true;
				if( w.$ED ) {
					var pad = w.$ED.$EscortDeckShip.indexOf( ship );
					if( pad != -1 && w.$ED.$EscortDeckShipPos[pad] ) {
						add = false;
					}
				}
				if( add ) {
					var l = w.$S.length; //add after the last ship
					w.$S[l] = [];
					w.$S[l][0] = ship;
                                        w.$T[l] = [];
				}
			}
		}
	}
	if( w.$Red ) return; //No trails in red alert below 50 FPS if weapons are online

	//reduce the number of flashers if FPS is low
        var newkey = false;
        if( w.$FPS < w.$LowFPS ) {
		if( w.$Key > 1 ) {
			w.$Key--;
                        newkey = true;
//                      player.consoleMessage("FPS:"+w.$FPS+" key:"+w.$Key); //debug
		}
                w.$FPS = w.$LowFPS; //do not reduce again until the next measue
	} else if( w.$FPS > w.$GoodFPS ) { //extend up to maxkey
                if( w.$Key < w.$MaxKey ) {
                        w.$Key++;
                        newkey = true;
//                      player.consoleMessage("FPS:"+w.$FPS+" key:"+w.$Key); //debug
		}
                w.$FPS = w.$GoodFPS; //do not extend again until the next measue
	}
	var key = "trails"+w.$Key;
        if( newkey ) w.$Sec = w.$MaxSec - 2 * w.$MaxKey + 2 * w.$Key;

	//reduce length over 1500 entity to prevent "Universe is full" error at 2047
	var a = system.allShips.length + system.allVisualEffects.length;
        var r = w.$Length;
	if( a > 1500 && w.$Length > 10 ) { //too much entity
		r = Math.ceil( w.$Length/2 ); //reduced length
                w.$RemoveAllTrails(w); //restart trails
		w.$Length = r;
	}
	
	var zoom = w.$Zoom;
	
	//create new trail elements
	var dist = w.$Distance;
        var shift = w.$Shift;
        var ma = w.$MaxAngle;
	for( var i = 1; i < w.$S.length; i++ ) { //start from 0 and player ship will make trail also
		var t = w.$S[i];
		if( t ) {
			var ship = t[0];
			if( ship && ship.isValid && !ship.isDerelict
				&& !ship.script.$TrailsDisabled ) {
				var sp = ship.position;
				var sc = 1;
				
                                if( ship.scanClass == "CLASS_ROCK" ) key = "trails-rock";
                                else {
                                        key = "trails"+w.$Key;//need if the previous ship was over 12km
				        if( pp ) { //create less flashers over 12km
					        var pd = sp.distanceTo( pp ) / 1000;
					        if( pd > 12 ) {
						        key = "trails0";
						        if( zoom ) //increase size in 12-20km for brightness
                                                            sc = 1.5+Math.max(0,Math.min(2,pd/5-2));
					        } //below the same formula is used again, keep these equal
				        }
                                }

				var c = 0; //safety counter to surely exit from the next cycle soon
				//check distance of the last position where a trail is created
				//need to do in cycle if travelled more in the last frame than
				//the length of a visualEffect element in effectdata.plist (20m)
				//which is happen often with more than 1x TAF
				var k = t[2];
                                var bp = sp;//.add(ship.heading.multiply(w.$Distance));//start earlier
                                var ez = 1; //default for ships without engine like rocks
                                var e = ship.exhausts; //use engine exhaust z distance from ship center
                                if( e && e[0] && e[0].position && e[0].position.z )
                                        ez = 1-Math.min(0, e[0].position.z); //ez must >=1 for Gnat
                                var kp = bp;
                                if(t[k] && t[k].isValid) {
                                        kp = t[k].position;
                                        if( kp.x > 0.9*shift ) {//moved far away
                                                kp = Vector3D(kp.x-shift, kp.y, kp.z);
//                                              if(p.target == ship) log(w.name, ship.name+" far i:"+i); //debug
                                        }
                                }
                                var kv = t[k];
				while( c < 50 && ( !k || !kv || !kv.isValid
					|| bp.distanceTo(kp) > dist/2+ez )) {
					c++;
					if( !w.$S[i][2] ) w.$S[i][2] = 3; //new trail
					k = w.$S[i][2];
					if( !k ) k = 3;
					var a = w.$S[i][k]; //actual (lastly created) visualEffect
					var pos = bp;
					var q = ship.orientation; //for the first element
					if( a && a.isValid ) { //from the second element
                                                var ap = a.position;
                                                if( ap.x > 0.9*shift ) //moved far away
                                                        ap = Vector3D(ap.x-shift, ap.y, ap.z);
						var u = bp.subtract(ap).direction();
						pos = ap.add(u.multiply(dist));
						if( a && a.isVisualEffect ) {
							//facing from the previous trail element
							q = a.orientation;
							var z=pos.subtract(ap).direction();
							var ah = a.heading;
							var angle = ah.angleTo(z);
							//set the plane where we should rotate in
							var cr = ah.cross(z).direction();
							q = q.rotate( cr, -angle );
						}
					}
//					w.$S[i][1] = pos; //save the new position - unused

					k++; //index of the next item
					if( k > w.$Length ) k = 3; //restart over $Length
					w.$S[i][2] = k; //save the new index
					
/*                                      //make the end of the trail darker but prevent blue end on gray dust
					if( ship.scanClass != "CLASS_ROCK" ) {
					        var pk = k+1; //previous k points to the oldest visualEffect
					        if( pk > w.$Length ) pk = 3;
					        var pv = w.$S[i][pk]; //the oldest visualEffect
					        if( pv && pv.isVisualEffect ) {
						        var nv = system.addVisualEffect(
							        "trails-end", pv.position );
						        if( nv ) {
							        nv.orientation = pv.orientation;
                                                                nv.scaleX = nv.scaleY = 0.5; //shrink
							        pv.remove();
							        w.$S[i][pk] = nv; //save the new item
                                                        }
						}
					}
*/
					//move or replace old visual item
					var ov = w.$S[i][k];
                                        if( ov && ov.isVisualEffect && ov.dataKey == key ) {
                                                ov.position = pos;
                                                ov.orientation = q;
                                        } else {
                                                if( ov && ov.isVisualEffect ) {
                                                        ov.remove();
                                                }
                                                var v = system.addVisualEffect( key, pos );
                                                if( v ) {
                                                        v.orientation = q;
                                                        w.$S[i][k] = v; //save the new element
                                                }
                                        }

                                        w.$T[i][k] = w.$Delta; //Timestamp of creation
                                        kv = w.$S[i][k];
                                        if( kv && kv.isValid ) {
                                                kp = kv.position;
                                                if( kp.x > 0.9*shift ) //moved far away
                                                        kp = Vector3D(kp.x-shift, kp.y, kp.z);
                                        } else kp = bp;
				}
//				if(c > 40) log(w.name, ship.name+" new i:"+i+" c:"+c); //debug
				//increase flasher size with the distance
				if( sc > 1 ) for( var j = 3; j <= w.$Length; j++ ) {
					var v = w.$S[i][j];
					if( v && v.isValid ) v.scaleX = v.scaleY = sc;
				}				//but not scaleZ!
			} else { //ship docked, jumped or destroyed
				var k = t[2]; //actual item index - old code without timestamp
				if( k ) {
					k++;// += 0.5; //slower count for slower remove
					if( k > w.$Length ) k = 3; //restart over $Length
					w.$S[i][2] = k; //save the new index
					if( k == Math.floor(k) ) {
						if( w.$S[i][k] == -1 ) { //all visualEffect removed
							//so remove this ship from the tracked array
							for( var k = 3; k <= w.$Length; k++ ) {
								var v = t[k]; //remove again for sure
								if( v && v.isValid ) v.remove();
							}
							w.$S[i] = false; 
						} else {
							var ov = t[k]; //remove old visual item
							if( ov && ov.isVisualEffect ) ov.remove();
							w.$S[i][k] = -1; //flag for remove ship
						}
					}
				}
			}
			
                        if( pp && ship && ship.isValid && ship.scanClass != "CLASS_ROCK" ) {
                                var max = 1;
                                var sd = ship.position.distanceTo( pp );
                                if( !zoom ) {
                                        if( ship.mass < 20000 ) max = 0.2;
                                        else if( ship.mass < 30000 ) max = 0.4;
                                        else if( ship.mass < 130000 ) max = 0.8;
                                } else {
                                        if( sd > 12000 ) //zoom in 12-20km, same formula as above
                                                max = 1.5+Math.max(0,Math.min(2,sd/5000-2));
                                        else if( ship.mass < 20000 ) max = 0.4;
                                        else if( ship.mass < 30000 ) max = 0.8;
                                }
//                              var c = 0;
                                for( var j = 3; j <= w.$Length; j++ ) {
                                        var v = w.$S[i][j];
                                        if( v && v.isValid && w.$T[i][j] ) {
//                                              c++;
                                                if( w.$Delta > w.$T[i][j] + w.$Sec ) { //timeout
                                                        v.remove();
                                                        w.$S[i][j] = null;
                                                        w.$T[i][j] = null;
                                                } else {
                                                        var vp = v.position;
                                                        var ph = p.heading;
                                                        switch( p.viewDirection ) {
                                                            case "VIEW_AFT":
                                                                    ph = ph.multiply(-1);
                                                                    break;
                                                            case "VIEW_PORT":
                                                                    ph = p.orientation.vectorRight()
                                                                            .multiply(-1);
                                                                    break;
                                                            case "VIEW_STARBOARD":
                                                                    ph = p.orientation.vectorRight();
                                                                    break;
                                                        }

/*                                                      //reduce flashers if far or near parallel - cause jams
                                                        var key2 = key;
                                                        if( w.$Key == 1 ) {
                                                                var an = vp.subtract(pp).angleTo(v.heading);
                                                                if( vp.distanceTo( pp )>12000 || an<0.3 ) {
                                                                        key2 = "trails0";
                                                                //improve if not enough paralell
                                                                } else key2 = "trails1";
                                                                if(p.target==ship) log(w.name, //debug
                                                                    an+" i:"+i+" j:"+j+" x:"+vp.x+" "+key2);
                                                        }*/
                                                        if( w.$Key < 2 && v.dataKey != key ) {
                                                                //immediately reduce quality in lowest mode
                                                                z = system.addVisualEffect(key, v.position);
                                                                if( z ) {
                                                                        z.orientation = v.orientation;
                                                                        v.remove();
                                                                        w.$S[i][j] = z;
                                                                        v = z;
                                                                }
                                                        }

                                                        //time-based trail dissipation
                                                        var s = 4;
                                                        if( !zoom || sd < 12000 )
                                                                s = 2.01-((w.$Delta-w.$T[i][j])/w.$Sec)*2;
                                                        //s must be always greater than 0 for scale!
                                                        v.scaleX = v.scaleY = Math.min(max, s);//shrink

                                                        //performance boost:
                                                        //move far away if not in sight or over 15km
                                                        if( vp.x < 0.9*shift ) {
                                                                var ang = ph.angleTo(vp.subtract(pp));
                                                                if( ang >= ma || !zoom && sd > 15000 ) {
                                                                        v.position = Vector3D(
                                                                        vp.x + shift, vp.y, vp.z );
                                                                }
                                                        } else { //move back if in sight again
                                                                vp = Vector3D(vp.x-shift, vp.y, vp.z);
                                                                if( ph.angleTo(vp.subtract(pp)) < ma
                                                                        && ( zoom || sd <= 15000 ) ) {
                                                                        v.position = vp;
                                                                }
                                                        }
                                                }
                                        }
                                }
                                //if(c > 45) log(w.name, ship.name+" end i:"+i+" c:"+c); //debug
			}
		}
	}
}

this.$RemoveAllTrails = function(w) {  
        var i, k, t, v;
        for( i = 0; i < w.$S.length; i++ ) {
                t = w.$S[i];
                if( t ) {
                        //remove the whole long trail
                        for( k = 3; k <= w.$Length; k++ ) {
                                v = t[k];
                                if( v && v.isValid ) v.remove();
                        }
                        w.$S[i][2] = 3;//reset visualEffect index
                }
        }
}