| Config/script.js | "use strict";
this.name        = "spacecrowds"; 
this.author      = "Astrobe"; 
this.copyright   = "2017 Creative Commons: attribution, non-commercial, sharealike."; 
this.description = "Creates random encounters when the Torus drive has been in use for a long time."; 
this.version     = "1.2.0";
// Configurability contributed by PHKB.
// Additionnal options to preserve derelicts and asteroids contributed by Dybal.
/* How to add your own encounter scenarios:
 * Create a function that spawns all the necessary stuff.
 * This function will be called by Space Crowds (SC):
 * - with one parameter which is around where to spawn the ships (typically you pass it to system.addGroup().
 *   This is a suggested position that is a few scanner ranges ahead of the player. But you can spawn your ships
 *   behind the player if you want. Just make sure the player has good chances to be masslocked as a result.
 *   The masslock doesn't have to be certain, for the player to be able to avoid trouble by careful eyeballing
 *   is considered "fair game" (let other OXPs like Bullet Drive deal with that if that's too easy for some players).
 * - this is bound to SC's script object, so you can use its facilities like $spawnRandomRoles
 * Then, call $pushScenario or $pushRareScenario with your name of the symbol of your function.
 * There are two scenario lists. The former is used 99% of the times, the latter 1% of the times.
 * Among those two lists the probabilities are even.
 * If you really want to give your scenario relatively more weight, just push it multiple times.
 *
 * $spawnRandomRoles(n, role, pos, radius)
 * $spawnRandomRoles is a utility function that let you spawn easily a group of ships in one role.
 * n is the maximum number of ships. The function performs n-1 times a coinflip to determine how many
 * ships to spawn. As a result, we get a bell curve-like distribution centered on n/2, meaning that
 * the function will spawn most often n/2 ships, and more rarely 1 or n. The function will spawn at least
 * one ship.
 * roles is a string indicating the role of the ships ("pirate" or "police" or "trader" etc.)
 * pos is the spawning position; typically you pass the suggested position you've received from SC.
 * radius is the radius of the spawning sphere. The bigger, the more the ships will be spread-out.
 * This parameter is optional and will default to 5Km if not given (scanner range is 25Km).
 *
 * How to test your scenarios
 * The main problem with testing is that scenarios are chosent randomly, so it can take a while
 * until you get your scenario.
 * Use $flushScenarios() to remove the other scenarios and then add your scenario, so your scenario
 * will be the only available one besides the rare scenarios. When you're done testing, remove the
 * the $flushScenarios() line and move your scenario to the $pushRareScenario() if needed.
 * You can use the $report() function to log whatever you need to.
 *
 * Possible errors:
 * Javascript exception "group is null" : role doesn't exist; could be a typo in the name (eg "hunters" instead of "hunter")
 * Javascript exception "commonEvents is undefined": scenario list is empty. Maybe you flushed (for testing purposes)
 * the scenarion list after you added your scenario? Can also be a typo in scenario name.
 */
this.$commonEvents=[];
this.$rareEvents=[];
this.$spawnedShips=[];
this.$chance = 0.02;
this.$rare_chance = 0.01;
this.preserve_derelicts = false;
this.preserve_asteroids = false;
this.libSettings = {Name:this.name, Display:"Config", Alias:"Space Crowds", Alive:"libSettings",
Bool:{
    B0:{Name:"$preserve_derelicts", Def:false, Desc:"Preserve derelicts"},
    B1:{Name:"$preserve_asteroids", Def:false, Desc:"Perserve asteroids"},
    Info:"true:Preserve, false:Remove when out of player's sight"},
SInt:{
	S0:{Name:"$chance", Def:0.02, Min:0.0001, Max:1, Desc:"Chance of encounter", Float:true},
	S1:{Name:"$rare_chance", Def:0.01, Min:0.0001, Max:1, Desc:"Rare chance", Float:true},
	Info:"0 - Chance of having an encounter\n1 - Chance of encounter being a rare one"},
};
// utilities
this.$spawnRandomRoles=function(n, roles, pos, radius)
{
	radius = (typeof radius !== 'undefined') ?  radius : 8E3;
	var random=Math.random;
	var count=1; // at least one
	for(var i=0; i<n-1; i++)
	{
		if(random()<0.5) continue;
		count++;
	}
    var group=system.addGroup(roles, count, pos, radius);
	group.ships.forEach(function(ship) {ship.fuel=random()*7; this.$spawnedShips.push(ship); }, this);
    this.$report("Adding " + count + " " + roles);
	return group;
}
// basic scenarios
this.$small_pirate_pack=function(pos) {this.$spawnRandomRoles(5, "pirate", pos);}
this.$small_hunter_pack=function(pos) {this.$spawnRandomRoles(5, "hunter", pos);}
this.$small_police_patrol=function(pos) {this.$spawnRandomRoles(5, "police", pos);}
this.$small_traders_pack=function(pos) {this.$spawnRandomRoles(5, "trader", pos);}
this.$adaptative=function(pos)
{
	this.$report("Adaptative");
	var s=system.government;
	if(s<=4) this.$spawnRandomRoles(7-s, "pirate", pos);
	else this.$spawnRandomRoles(s, "police", pos);
}
this.$pirates_vs_police=function(pos)
{
	this.$report("Pirates vs Police");
	this.$small_pirate_pack(pos);
	this.$small_police_patrol(pos);
}
this.$pirates_vs_hunters=function(pos)
{
	this.$report("Pirates vs Hunters");
	this.$small_pirate_pack(pos);
	this.$small_hunter_pack(pos);
}
this.$pirates_vs_traders=function(pos)
{
	this.$report("Pirates vs Traders");
	this.$small_pirate_pack(pos);
	this.$small_traders_pack(pos);
}
this.$boulder_field=function(pos)
{
	// The "popping" with asteroids is too obvious, so we use boulders.
	// However, they seem to have a low "visibility range", so typically
	// some of them are almost immediately removed.
	// We should give those boulder some speed so as to deter miner-players
	// from setting a way point here in order to come back later; they won't find
	// anything because those boulders will be removed as soon as they get out
	// of sight.
	this.$report("Boulders");
	var g=this.$spawnRandomRoles(20, "boulder", pos, 2E3);
	g.ships.forEach(function(ship) { ship.temperature=0; }, this);
}
this.$empty=function(pos)
{
	this.$report("Empty");
}
this.$cargo=function(pos)
{
	this.$report("Cargo");
	if(Math.random()<0.5) this.$spawnRandomRoles(1, "cargopod", pos);
}
this.$traders_vs_thargoids=function(pos) // rare
{
	this.$report("Traders vs Thargoids");
	this.$small_traders_pack(pos);
	this.$spawnRandomRoles(5, "thargoid", pos);
}
this.$police_vs_thargoids=function(pos) // rare
{
	this.$report("Police vs Thargoids");
	this.$small_police_patrol(pos);
	this.$spawnRandomRoles(6, "thargoid", pos);
}
this.$thargoid_fleet=function(pos) // rare
{
	// Around 5 Thargoid ships, expect no support.
	this.$report("Tharmageddon!");
	this.$spawnRandomRoles(10, "thargoid", pos);
}
this.$pirateNet=function(pos) // rare
{
	// A large group of pirates widely spread in order to catch lone ships
	this.$report("Pirate net");
	this.$spawnRandomRoles(10, "pirate", pos, 15E3);
}
this.$pushScenario = function (proc)
{
	this.$commonEvents.push(proc);
}
this.$pushRareScenario = function (proc)
{
	this.$rareEvents.push(proc);
}
this.$flushScenarios = function (proc)
{
	this.$commonEvents.length=0;
}
this.$report = function (msg)
{
	log(this.name, msg);
}
this.startUp = function ()
{
	this.dsGroup = new ShipGroup("DeepSpaceShips"); // start with an empty group
	//this.$pushScenario(this.$small_pirate_pack);
	//this.$pushScenario(this.$small_police_patrol);
	this.$pushScenario(this.$small_traders_pack);
	this.$pushScenario(this.$small_hunter_pack);
	this.$pushScenario(this.$empty);
	this.$pushScenario(this.$pirates_vs_police);
	this.$pushScenario(this.$pirates_vs_hunters);
	this.$pushScenario(this.$pirates_vs_traders);
	this.$pushScenario(this.$boulder_field);
	this.$pushScenario(this.$cargo);
	this.$pushScenario(this.$adaptative);
	this.$pushRareScenario(this.$traders_vs_thargoids);
	this.$pushRareScenario(this.$police_vs_thargoids);
	this.$pushRareScenario(this.$thargoid_fleet);
	this.$pushRareScenario(this.$pirateNet);
}
this.startUpComplete = function() {
	if (missionVariables.SpaceCrowds_Chance) this.$chance = parseFloat(missionVariables.SpaceCrowds_Chance);
	if (missionVariables.SpaceCrowds_Rare) this.$rare_chance = parseFloat(missionVariables.SpaceCrowds_Rare);
    if (missionVariables.SpaceCrowds_Preserve_Derelicts && missionVariables.SpaceCrowds_Preserve_Derelicts == 1)
        this.$preserve_derelicts = true;
    else
        this.$preserve_derelicts = false;
    if (missionVariables.SpaceCrowds_Preserve_Asteroids && missionVariables.SpaceCrowds_Preserve_Asteroids == 1)
        this.$preserve_asteroids = true;
    else
        this.$preserve_asteroids = false;
	// register our settings, if Lib_Config is present
	if (worldScripts.Lib_Config) worldScripts.Lib_Config._registerSet(this.libSettings);
}
this.playerWillSaveGame = function() {
	missionVariables.SpaceCrowds_Chance = this.$chance;
	missionVariables.SpaceCrowds_Rare = this.$rare_chance;
    if (this.$preserve_derelicts) 
        missionVariables.SpaceCrowds_Preserve_Derelicts = 1;
    else
        missionVariables.SpaceCrowds_Preserve_Derelicts = 0;
    if (this.$preserve_asteroids) 
        missionVariables.SpaceCrowds_Preserve_Asteroids = 1;
    else
        missionVariables.SpaceCrowds_Preserve_Asteroids = 0;
        
}
this.shipLaunchedFromStation = function ()
{
    if (this.checkTimer)
        this.checkTimer.start()
    else
        this.checkTimer = new Timer(this, this.$check, 5, 1000/(player.ship.maxSpeed*3)); // about every 1.3 second for Adder (maxSpeed=240).
}
this.$check = function ()
{
	if(player.ship.torusEngaged
			&& Math.random()>(1 - this.$chance)
			&& !system.isInterstellarSpace) 
	{
		this.$report("Chance!");
		this.$createEncounter();
	}
	else
	{
		this.$removeFarShips();
	}
}
this.$createEncounter = function ()
{
	var ourShip=player.ship;
	if(system.countShipsWithRole("station", ourShip, 240E3) > 0)
	{
		this.$report("Station nearby");
		return;
	}
	var pos = ourShip.position.add(ourShip.heading.multiply(48E3)).add(Vector3D.random(15E3));
	if(Math.random()<this.$rare_chance)
	{
		var n=Math.floor(Math.random()*this.$rareEvents.length);
		this.$rareEvents[n].call(this, pos);
	}
	else
	{
		var n=Math.floor(Math.random()*this.$commonEvents.length);
		this.$commonEvents[n].call(this, pos);
	}
}
this.$removeFarShips=function()
{
	var spawnedShips=this.$spawnedShips;
	var count=0;
	var e;
	for(var i=0; i<spawnedShips.length; i++)
	{
		// ship might actually be long dead, but Oolite seems to handle that case well.
		try
		{
			if(!spawnedShips[i].position.distanceTo(player.ship.position)>100E3)
			{
				if(spawnedShips[i].isShip && 
						!(this.preserve_derelicts && spawnedShips[i].isDerelict) &&
						!(this.preserve_asteroids && spawnedShips[i].isBoulder) )
				{
					spawnedShips[i].remove();
					spawnedShips[i]=null; // for cleanup
					count++;
				}
			}
		}
		catch(e)
		{
			spawnedShips[i]=null;
		}
	}
	//cleanup
	if(count)
	{
		this.$spawnedShips=spawnedShips.filter(function(e) { return e!=null; });
		this.$report("Removed "+count+" ships");
	}
}
 |