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

Expansion Swarm

Content

Manifest

from Expansion Manager's OXP list from Expansion Manifest
Description A new variant on the Thargoid mothership and Thargons, introducing a swarming mentality. Somewhat more of a challenge than the vanilla versions. A new variant on the Thargoid mothership and Thargons, introducing a swarming mentality. Somewhat more of a challenge than the vanilla versions.
Identifier oolite.oxp.Thargoid.Swarm oolite.oxp.Thargoid.Swarm
Title Swarm Swarm
Category Ships Ships
Author Thargoid Thargoid
Version 1.03 1.03
Tags ships ships
Required Oolite Version
Maximum Oolite Version
Required Expansions
Optional Expansions
Conflict Expansions
Information URL http://wiki.alioth.net/index.php/Swarm_OXP n/a
Download URL https://wiki.alioth.net/img_auth.php/a/ac/Swarm_1.03.oxz n/a
License Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license with clauses - see readme file Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license with clauses - see readme file
File Size n/a
Upload date 1610873408

Documentation

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

Swarm v1.03 Readme & License.txt

Swarm OXP by Thargoid.

Urgent Notification to Civilian Commanders

Recent military intelligence and combat encounters have identified a new and potentially worrying evolution in Thargoid technology It appears that the recent appearance of space-borne "wasp-like" creatures within Galcop space (see the report by K. Wolf et al - Galcop Science Network #359-1701) has not gone unnoticed by our alien adversaries, who have also seemingly encountered them and their hive "stations".

As a result of this, a new type of Thargoid combat craft has now been sighted which has been nicknamed the Swarmer. Similar in design to their familiar octagonal motherships, these new vessels are distinguished byexternal protrusions used to carry small drone craft. These secondary vehicles seem to have replaced the conventional "Thargon" robot fighters, but if anything are more of a threat. Whilst they appear slightly weaker in individual weapon power, they are far more numerous with reports of up to 16 being released by a single mother vessel. But the most worrying aspect is that the craft appear to have taken the wasp swarming mentality, with co-ordinated attacks on individual vessels within a target fleet being reported. 

One notable encounter with three of the mother vessels took all of the skill, bravery and cutting edge military technology of an entire wing of specialised Raptor anti-Thargoid destroyers to counter and repulse. Sadly this was not without losses of several of our brave heroes, leading to the decision to issue this wideband warning to civilian ships.

The increased numbers of the drone craft combined with their small size and faster speed has led to this new type of mother vessel being classification as an extreme threat to civilian and all but the most powerful of Galcop craft. We strongly suggest that discretion is the better part of valour in case of an encounter, and a wise Commander should consider using his fuel injectors rather than his weaponry to survive. However if combat is undertaken, notification to the local Galcop Intelligence directorate at the system main station should be made to assist in countering this terrible new threat.

Fleet Major J deLance.
Galcop Intelligence.

--------------------------------------------------------------
Authors note:

Currently there is a minor glitch in trunk v1.76 (and earlier) which stops thargons triggering a ship script event when it is fired. To avoid this the swarming Thargons are currently scanClass CLASS_MISSILE. This gives a false alert to certain OXPs which automatically try and deal with missiles (and may spam the screen when the ships launch their drones).

The fix for this has been scheduled for inclusion into trunk 1.76.1, at which point I'll adjust the OXP to suit and raise the minimum requirements.

For tough guys, try spawning role "swarm_squadron", or for a real challenge, "swarm_invasion" 

--------------------------------------------------------------

License:

This OXP is released under the Creative Commons Attribution - Non-Commercial - Share Alike 3.0 license with the following clauses:

* Whilst you are free (and encouraged) to re-use any of the scripting, models or texturing in this OXP, the usage must be distinct from that within this OXP. Unique identifiers such as (but not limited to) unique shipdata.plist entity keys, mission variables, script names (this.name), equipment identity strings (EQ_), description list arrays and entity roles must not be re-used without prior agreement.
* rebundling of this OXP within another distribution is permitted as long as it is unchanged. The following derivates however are permitted and except from the above:
	* the conversion of files between XML and openStep.
	* the merging of files with other files of the same type from other OXPs.
* Even though it is not compulsory, if you are re-using any sizable or recognisable piece of this OXP, please let me know :)

--------------------------------------------------------------

Instructions:

Unzip the file, and then move the folder "Swarm 1.03.oxp" to the AddOns directory of your Oolite installation, and also the enclosed zip file of bigShips oxp on which this OXP relies. Then start the game up and the ships should be added. 

Version history:

11/01/2012 (v1.00) - First release, version 1.00
11/01/2012 (v1.01) - Two minor small script tweaks.
22/01/2012 (v1.02) - Hard-coded this.weaveX and this.weaveY scaling values in script, due to NaN problem for Switeck.
29/01/2012 (v1.03) - Added correct isNaN check.

--------------------------------------------------------------

Acknowledgements:

This OXP came about from a pub chat with ClymAngus (probably an event that should be banned under the Geneva Convention). It also re-uses some code originally supplied to Killer Wolf for his Wasps OXP.

With thanks also to Ironfist, Switeck and Cmdr. Maegil for test pilot duties.

Equipment

Name Visible Cost [deci-credits] Tech-Level
Thargon yes 1000 101+

Ships

Name
swarm_dummyThargon
swarm_invasion
Unidentified Sphere
swarm_squadron
Thargoid Mothership
Thargoid Swarm

Models

This expansion declares no models.

Scripts

Path
Scripts/swarm_seeker.js
this.name           = "swarm_seeker";
this.author         = "Thargoid";
this.copyright		= "Creative Commons: attribution, non-commercial, sharealike with clauses - see readme.txt";
this.description    = "Ship script for the Thargoid swarm seeker";
this.version        = "1.03";
"use strict";

this.shipSpawned = function()
	{ 	
	this.spawnedTime = clock.absoluteSeconds;
	function targetShips(entity) {return entity.isShip && entity.isValid && entity.isCloaked && !entity.hasRole("thargoid") && entity.scanClass !== "CLASS_THARGOID" && !entity.isThargoid}; 
	this.cloakedShips = system.filteredEntities(this, targetShips, this.ship, 25600); 
	
	if(this.cloakedShips.length === 0)
		{ 
		this.ship.explode();
		return;
		}
	
	this.victim = this.cloakedShips[0];
	this.callbackID = addFrameCallback(this.trackVictim.bind(this)); 
	};
	
this.trackVictim = function()
	{
	if(!this.victim || !this.victim.isValid || (clock.absoluteSeconds - this.spawnedTime > 120))
		{
		this.stopCallback();
		this.ship.explode();
		return;
		}
		
	var targetVector = this.victim.position.subtract(this.ship.position).direction();
	var angle = this.ship.heading.angleTo(targetVector);
	var cross = this.ship.heading.cross(targetVector).direction();
	this.ship.orientation = this.ship.orientation.rotate(cross, -angle);	
	this.ship.desiredSpeed = this.ship.maxSpeed;
	
	if(this.ship && this.victim && (this.ship.position.distanceTo(this.victim.position) < 25))
		{
		if(this.victim && this.victim.isValid && this.victim.equipmentStatus("EQ_CLOAKING_DEVICE", "EQUIPMENT_OK"))
			{
			if(Math.random() > 0.9)
				{ this.victim.setEquipmentStatus("EQ_CLOAKING_DEVICE", "EQUIPMENT_DAMAGED"); }
			else
				{
				this.victim.removeEquipment("EQ_CLOAKING_DEVICE");
				this.victim.awardEquipment("EQ_CLOAKING_DEVICE");
				}
			}	
		this.stopCallback(); 
		}
	};

this.shipDied = this.stopCallback = function()
	{
	if(this.callbackID && isValidFrameCallback(this.callbackID)) 
		{ removeFrameCallback(this.callbackID); } 
	delete this.callbackID;
	};
	
this.shipHitByECM = function(pulse)
	{
	if(Math.random() < ((pulse+1)/100))
		{ 
		this.stopCallback();
		this.ship.explode();
		}
	};
Scripts/swarm_thargoid.js
this.name           = "swarm_thargoid";
this.author         = "Thargoid";
this.copyright		= "Creative Commons: attribution, non-commercial, sharealike with clauses - see readme.txt";
this.description    = "Ship script for the Thargoid swarm mothership";
this.version        = "1.03";
"use strict";

this.spawnedAsEscort = function()
	{ this.ship.switchAI("swarm_thargoidAI.plist"); };

this.shipSpawned = function()
	{
	if (player.score > 512)
		{ this.ship.awardEquipment("EQ_SHIELD_BOOSTER"); }
	if (player.score > 1024)
		{ this.ship.awardEquipment("EQ_ENERGY_UNIT"); }
	if (player.score > 2560)
		{ this.ship.awardEquipment("EQ_SHIELD_ENHANCER"); }
	if (player.score > 4000)
		{ this.ship.awardEquipment("EQ_NAVAL_ENERGY_UNIT"); }
	if (player.score > 6399)
		{ this.ship.awardEquipment("EQ_NAVAL_SHIELD_BOOSTER"); }
	
	if(this.ship.scriptInfo.missileRole) // missileRole should be defined in shipdata.plist
		{ this.missileRole = this.ship.scriptInfo.missileRole; }
	else
		{ this.missileRole = "EQ_THARGON"; } // default to standard thargon if not
	
	this.ship.awardEquipment("EQ_MISSILE_REMOVAL"); // remove all spawning missiles and restock with selected ones.
	var addCounter = 0; 
	for(addCounter = 0; addCounter < this.ship.missileCapacity;addCounter++)
		{ this.ship.awardEquipment(this.missileRole); }
	
	this.swarmGroup = new ShipGroup("Alien Swarm", this.ship);
	this.ship.group = this.swarmGroup;
	this.swarmGroup.leader = this.ship;
	this.lastBlink = 0;
	this.lastSeek = 0;
	this.seekerCount = 3;
	};
	
this.shipFiredMissile = function(missile, target)
	{	
	if(!this.ship.isValid || this.ship.subEntities.length === 0) { return; } // if we've run out of sub-ents before we run out of missiles, or the ship has been destroyed
	
	var subCounter = this.ship.subEntities.length - 1; // Set counter to number of sub-ents minus 1 (as entity array goes up from zero)
	for(subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--)
		{
		if(this.ship.subEntities[subCounter].hasRole(missile.primaryRole)) // if the sub-ent is the same as the missile being fired
			{
			missile.position = this.localToGlobal(this.ship.subEntities[subCounter].position); // move the fired missile to the sub-ent position
			missile.orientation = this.ship.subEntities[subCounter].orientation.multiply(this.ship.orientation); // point the missile in the right direction
			missile.desiredSpeed = missile.maxSpeed;
			missile.group = this.swarmGroup;
			this.ship.subEntities[subCounter].remove(); // remove the sub-ent version of the missile
			break; // come out of the loop, as we've done our swap
			}
		}
	};
	
this.localToGlobal = function(position)
	{ // sub-ent position is relative to mother, but for swapping we need the absolute global position
	let orientation = this.ship.orientation;
	return this.ship.position.add(position.rotateBy(orientation));
	};

this.shipTakingDamage = function(amount, fromEntity, damageType)
	{
	if(this.ship.missiles.length === 0 && this.ship.subEntities.length === 0) // if we're all out of missiles and any sub-entities, bail out.
		{ return; }
	
	this.missileSubs = 0;
	var subCounter = this.ship.subEntities.length - 1; // Set counter to number of sub-ents minus 1 (as entity array goes up from zero)
	for(subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--)
		{
		if(this.ship.subEntities[subCounter].hasRole(this.missileRole)) // if the sub-ent is a missile, count it
			{ this.missileSubs++; }
		}
	
	if(this.missileSubs === 0 && this.ship.subEntities.length === 0) // if we're all out of missiles and missile sub-entities, bail out.
		{ return; }
	
	if(this.missileSubs < this.ship.missiles.length) // if we've got more missiles than sub-entity missiles
		{
		this.ship.awardEquipment("EQ_MISSILE_REMOVAL"); // get rid of all missiles
		if(this.missileSubs > 0)
			{
			var missileCounter = 0; 
			for(missileCounter = 0;missileCounter<this.missileSubs;missileCounter++) // restock with the correct number of selected missile
				{ this.ship.awardEquipment(this.missileRole); }
			}
		return;	
		}
		
	if(this.missileSubs > this.ship.missiles.length) // if we've got less missiles than sub-entity missiles
		{
		this.difference = this.missileSubs - this.ship.missiles.length;
		var removeCounter = 0; 
		for(removeCounter = 0;removeCounter<this.difference;removeCounter++) // loop through however many subs we need to remove
				{
				var subCounter = this.ship.subEntities.length - 1; // Set counter to number of sub-ents minus 1 (as entity array goes up from zero)
				for(subCounter = this.ship.subEntities.length - 1; subCounter >= 0; subCounter--)
					{
					if(this.ship.subEntities[subCounter].hasRole(this.missileRole)) // if the sub-ent is a missile, remove it
						{ 
						this.ship.subEntities[subCounter].remove(); 
						break;
						}
					}
				}
		return;	
		}	
	};	
	
this.shipEnergyIsLow = function()
	{ 
	this.starburst();	
	this.blink();	
	};
	
this.shipDied = function()
	{ this.ship.commsMessage(expandDescription("[thargoid_curses]")); };

this.starburst = function()
	{
	if(this.ship.missiles.length === 0) { return; }
	
	var missileCounter = this.ship.missiles.length; 
	for(missileCounter = this.ship.missiles.length; missileCounter > 0; missileCounter--) // restock with the correct number of selected missile
		{ this.ship.fireMissile(); }	
	};
	
this.blink = function()
	{
	if(clock.absoluteSeconds - this.lastBlink < 30) { return; }
	this.lastBlink = clock.absoluteSeconds;
	var xDistance = ((Math.random() * 2) - 1) * 5000;
	var yDistance = ((Math.random() * 2) - 1) * 5000;
	var zDistance = ((Math.random() * 2) - 1) * 5000;	
	this.ship.position = this.ship.position.add([xDistance, yDistance, zDistance]);	
	};
	
this.validateTarget = function()
	{
	if(this.ship.target && !this.ship.target.isThargoid && !this.ship.target.hasRole("thargoid") && !this.ship.target.hasRole("EQ_SWARM_MISSILE"))
		{ this.ship.reactToAIMessage("TARGET_VALID"); }
	else
		{ this.ship.reactToAIMessage("TARGET_INVALID"); }
	};	
	
this.scanForTarget = function()
	{
	if(this.ship.target && this.ship.target.isValid && this.ship.AIState === "ATTACK_SHIP" && Math.random() > 0.1) { return; }
	
	function targetShips(entity) {return entity.isShip && !entity.isCloaked && !entity.hasRole("thargoid") && entity.scanClass !== "CLASS_THARGOID" && !entity.isThargoid}; 
	function primaryShips(entity) {return !entity.isStation && entity.isPiloted}; 
	function secondaryShips(entity) {return !entity.isPiloted || entity.isStation }; 
	let allShips = system.filteredEntities(this, targetShips, this.ship, 25600); 
	this.primaryArray = allShips.filter(primaryShips);
	this.secondaryArray = allShips.filter(secondaryShips);
	
	if(this.primaryArray.length > 0)
		{
		this.targetNumber = Math.floor(Math.random() * this.primaryArray.length);
		if(this.targetNumber > this.primaryArray.length)
			{ this.targetNumber = 0; }
		this.ship.target = this.primaryArray[this.targetNumber];
        this.ship.reactToAIMessage("TARGET_FOUND");
		}
	else
		{
		if(this.secondaryArray.length > 0)
			{
			this.targetNumber = Math.floor(Math.random() * this.secondaryArray.length);
			if(this.targetNumber > this.secondaryArray.length)
				{ this.targetNumber = 0; }
			this.ship.target = this.secondaryArray[this.targetNumber];
			this.ship.reactToAIMessage("TARGET_FOUND");
			}
		else
			{ this.ship.reactToAIMessage("NOTHING_FOUND"); }
		}
	};
	
this.startWeave = function()
	{
	if(this.callbackID) { return; }
	
	this.weaveTime = clock.absoluteSeconds;
	this.phase = Math.random() * 2 * Math.PI;
	this.callbackID = addFrameCallback(this.performWeave.bind(this));
	};
	
this.stopWeave = function()
	{ 
	if(this.callbackID && isValidFrameCallback(this.callbackID)) 
		{ removeFrameCallback(this.callbackID); } 
	delete this.callbackID;
	};
	
this.performWeave = function()
	{
	if(!this.ship.isValid || this.ship.AIState !== "ATTACK_SHIP") 
		{ 
		this.stopWeave(); 
		return;
		}
	
	var xOffset = (2.5 * this.ship.speed/this.ship.maxSpeed) * Math.sin(clock.absoluteSeconds - this.weaveTime);
	var yOffset = (2.5 * this.ship.speed/this.ship.maxSpeed) * Math.sin(this.phase + clock.absoluteSeconds - this.weaveTime);

	if(isNaN(xOffset)) 
		{ 
		log(this.name, "Swarm OXP - xOffset is NaN. Please report");
		xOffset = (2.5 * ((2 * Math.random()) - 1));
		};
		
	if(isNaN(yOffset)) 
		{ 
		log(this.name, "Swarm OXP - yOffset is NaN. Please report");
		yOffset = (2.5 * ((2 * Math.random()) - 1));
		};	
	
	this.ship.position = this.ship.position.add([xOffset, yOffset, 0]);	
	};
	
this.shipBeingAttackedByCloaked = function()
	{
	if(this.seekerCount === 0 || (clock.absoluteSeconds - this.lastSeek < 10)) { return; }
	
	this.lastSeek = clock.absoluteSeconds;
	this.seekerCount--;
	this.ship.spawnOne("swarm_seeker");
	};
Scripts/swarm_thargon.js
this.name           = "swarm_tharglet";
this.author         = "Thargoid";
this.copyright		= "Creative Commons: attribution, non-commercial, sharealike with clauses - see readme.txt";
this.description    = "Ship script for the Thargoid swarm tharglet";
this.version        = "1.03";
"use strict";

this.shipSpawned = function()
	{
	if (player.score > 512)
		{ this.ship.awardEquipment("EQ_SHIELD_BOOSTER"); }
	if (player.score > 2560)
		{ this.ship.awardEquipment("EQ_ENERGY_UNIT"); }
	if (player.score > 6399)
		{ this.ship.awardEquipment("EQ_SHIELD_ENHANCER"); }
	};
	
this.validateTarget = function()
	{
	if(this.ship.target && this.ship.target.scanClass != "CLASS_THARGOID" && !this.ship.target.hasRole("thargoid") && !this.ship.target.hasRole("EQ_SWARM_MISSILE"))
		{ this.ship.reactToAIMessage("TARGET_VALID"); }
	else
		{ this.ship.reactToAIMessage("TARGET_INVALID"); }
	};