| 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"); }
	}; |