///////////////////////GENERIC EXPLOSION GENERATOR AND AOE SIMULATION
///////////////////////REQUIRES LAZYLIB
package data.scripts;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.OnHitEffectPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import java.util.List;
import java.util.Iterator;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.combat.*;
public class ExplosiveOnHitEffectWithAOE implements OnHitEffectPlugin {
public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target,
Vector2f point, boolean shieldHit, CombatEngineAPI engine) {
float emp = projectile.getEmpAmount();
float dam = projectile.getDamageAmount();
WeaponAPI weapon = projectile.getWeapon();
DamageType damType = weapon.getDamageType();
float aoeToCheck = Math.max(dam / 10f, 20f);//Checks an area equal to damage / 10, with a floor of 20 (anything lower than that isn't worth doing, imo, but you may want to put an upper cap on really huge-damage weapons); can be adjusted to whatever you want
float aoeMult = 0.5f;//This is our multiplier against damage for distant targets, for balance purposes.
//AOE CODE
//This code searches first for nearby Ships, then for nearby non-ship entities, and applies damage to them over the AOE.
//When it checks for nearby ships, if it finds any it gets a valid collision (or not) by doing a ray-test...
//Which keeps things easy (don't thank me, thank Alex / LazyWizard)
//We don't need that (fairly expensive) check for nearby missiles, so we don't bother.
//If the checks come up with anything, then it gets damaged, in this case by half of the base damage.
List nearbyEnemies = CombatUtils.getShipsWithinRange(point, aoeToCheck, true);
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext();)
{
CombatEntityAPI targEnt = (CombatEntityAPI) iterMe.next();
if(targEnt != target && (targEnt.getOwner() != ((CombatEntityAPI) projectile.getSource()).getOwner()))//No double hits or friendly fire.
{
Vector2f targLoc = targEnt.getLocation();
//Try to get a valid collision point between our explosion's point source and the Entity.
Vector2f colPoint = CollisionUtils.getCollisionPoint(point, targLoc, targEnt);
//If we can't get a good collision point, use the center of the target Entity. This is potentially a balance issue (hits all going to one armor cell are pretty OP lol), but this case mainly covers little teeny drones and suchlike that should be registering hits from giant explosions nearby, but often don't, for whatever reason. Bigger things rarely fail, so it usually works out.
if(colPoint == null) colPoint = targLoc;
if(colPoint != null)//Must check this, getCollisionPoint returns null fairly frequently and that's a wrap
{
engine.applyDamage(
targEnt, //enemy Entity
colPoint, //Our 2D vector to the exact world-position of the collision
dam * aoeMult, //DPS modified by the damage multiplier
damType, //Using the damage type here, so that Kinetic / Explosive / Fragmentation AOE works.
emp * aoeMult, //EMP
false, //Does not bypass shields.
false, //Does not do Soft Flux damage (unless you want it to for some strange reason)
projectile.getSource() //Who owns this projectile?
);
//This is just for testing purposes- shows a blue dot if, when and where hits occur. Handy for testing balance.
/*
for(int i = 0; i < 6; i++)
{
engine.addHitParticle(colPoint, new Vector2f(MathUtils.getRandomNumberInRange(-5,5), MathUtils.getRandomNumberInRange(-5,5)), MathUtils.getRandomNumberInRange(25,50), 1f, 100f, new Color(0,0,255,255));
}*/
}
}
}
//Simpler task for missiles- we don't need an intersection with Hulls to get a valid armor location.
nearbyEnemies = CombatUtils.getMissilesWithinRange(point, aoeToCheck, true);
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext();)
{
CombatEntityAPI targEnt = (CombatEntityAPI) iterMe.next();
if(targEnt != target && (targEnt.getOwner() != ((CombatEntityAPI) projectile.getSource()).getOwner()))//No double hits or friendly fire.
{
Vector2f targLoc = targEnt.getLocation();
engine.applyDamage(
targEnt, //enemy Entity
targLoc, //Our 2D vector to the exact world-position of the collision
dam * aoeMult, //DPS modified by the damage multiplier
damType, //Using the damage type here, so that Kinetic / Explosive / Fragmentation AOE works.
emp * aoeMult, //EMP
false, //Does not bypass shields.
false, //Does not do Soft Flux damage (unless you want it to for some strange reason)
projectile.getSource() //Who owns this projectile?
);
}
}
//EXPLOSION BITZ
//This code auto-magically builds a reasonably-cool generic explosion, based on weapon damage.
//There isn't much code that's expensive here and the game does auto-cull particles, so this can be made more complex if you want. Just be careful about how many particles you create per event, since that's a linear cost (i.e., if you stick this on a Vulcan-type weapon, keep the total count low, low or re-write that part).
int repeat = Math.max(5,(int) ((dam / 60f)));
int repeatTwo = repeat / 3;
//This bit makes backdrop glows show up fairly briefly behind our main explosion.
//Just one particle per repeat, but it costs a lot of operations to built it.
for(int i = 0; i < repeatTwo; i++)
{
Vector2f randPoint = MathUtils.getRandomPointInCircle(point,dam/35f);
float randSize = MathUtils.getRandomNumberInRange(dam/25f,Math.max(dam / 20f,15f));
Vector2f randVec = new Vector2f(MathUtils.getRandomNumberInRange(-dam / 50f ,dam / 50f),MathUtils.getRandomNumberInRange(-dam / 50f ,dam / 50f));
float randDur = 0.1f + MathUtils.getRandomNumberInRange(0.0f,0.3f);
int yelVal = (int) (Math.random() * 200f + 32f);
int randTrans = (int) MathUtils.getRandomNumberInRange(64f,128f);
engine.addSmoothParticle(randPoint, randVec, randSize, 1f, randDur, new Color(255,yelVal,0,randTrans));
}
//Main explosion, fast & slow fires and smoke at the end.
//Six particles per repeat, min 30.
for(int i = 0; i < repeat; i++)
{
int yelVal = (int) (Math.random() * 128f + 64f);
Vector2f randPoint = MathUtils.getRandomPointInCircle(point,dam/100f);
//Random Vectors, in order of speed; generally, we need smoke and central poof to be slower than others
Vector2f randVecFast = new Vector2f(MathUtils.getRandomNumberInRange(-30f - dam / 2f ,30f + dam / 2f),MathUtils.getRandomNumberInRange(-30f - dam / 2f ,30f + dam / 2f));
Vector2f randVecFastTwo = new Vector2f(MathUtils.getRandomNumberInRange(-30f - dam / 2f ,30f + dam / 2f),MathUtils.getRandomNumberInRange(-30f - dam / 2f ,30f + dam / 2f));
Vector2f randVec = new Vector2f(MathUtils.getRandomNumberInRange(-dam / 25f ,dam / 25f),MathUtils.getRandomNumberInRange(-dam / 25f ,dam / 25f));
Vector2f randVecTwo = new Vector2f(MathUtils.getRandomNumberInRange(-dam / 35f ,dam / 35f),MathUtils.getRandomNumberInRange(-dam / 35f,dam / 35f));
Vector2f randVecThree = new Vector2f(MathUtils.getRandomNumberInRange(-dam / 40f ,dam / 40f),MathUtils.getRandomNumberInRange(-dam / 40f,dam / 40f));
float randSize = MathUtils.getRandomNumberInRange(10f,Math.max(dam / 50f,15f));
float randSizeTwo = MathUtils.getRandomNumberInRange(5f,10f);
float randDur = 1f + MathUtils.getRandomNumberInRange(-0.5f,3f);
float randDurTwo = MathUtils.getRandomNumberInRange(0.5f,1f);
int randTrans = (int) MathUtils.getRandomNumberInRange(32f,200f);
int randGray = (int) MathUtils.getRandomNumberInRange(32f,64f);
engine.addHitParticle(point, randVecFast, randSizeTwo, 1f, randDurTwo * 0.5f, new Color(255,yelVal + 64,0,randTrans + 55));
engine.addHitParticle(point, randVecFastTwo, randSizeTwo, 1f, randDurTwo * 0.65f, new Color(255,yelVal + 64,0,randTrans + 55));
engine.addHitParticle(point, randVec, randSize, 1f, randDurTwo, new Color(255,yelVal-64,0,randTrans));
engine.addHitParticle(point, randVecTwo, randSize, 1f, randDurTwo * 0.75f, new Color(255,yelVal-32,0,randTrans));
engine.addHitParticle(point, randVecThree, randSize, 1f, randDurTwo * 0.5f, new Color(255,yelVal,0,randTrans));
engine.addSmokeParticle(point, randVecThree, MathUtils.getRandomNumberInRange(dam / 100f,dam / 50f), MathUtils.getRandomNumberInRange(0.5f,1f), randDur, new Color(randGray,randGray,randGray,randTrans));
}
}
}
package data.scripts;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.OnHitEffectPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
public class HellboreOnHitEffect implements OnHitEffectPlugin {
public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target,
Vector2f point, boolean shieldHit, CombatEngineAPI engine) {
if (target instanceof ShipAPI) {
float emp = projectile.getEmpAmount() * 0.1f;
float dam = projectile.getDamageAmount() * 0.1f;
WeaponAPI weapon = projectile.getWeapon();
DamageType damType = weapon.getDamageType();
int heatMe = (int) Math.max(6f,dam / 150f);
Vector2f targLoc = target.getLocation();
for(int i = 0; i < heatMe; i++)
{
//Gets a point far, far away and uses it as our ray-test point. Long distances work better than shorter ones.
Vector2f cloneLoc = MathUtils.getRandomPointOnCircumference(targLoc, 1000000f);
//Try to get a valid collision point between our explosion's point source and the Entity.
Vector2f colPoint = CollisionUtils.getCollisionPoint(cloneLoc, targLoc, target);
//If we can't get a good collision point, use the center of the target Entity. This is potentially a balance issue (hits all going to one armor cell are pretty OP lol), but this case mainly covers little teeny drones and suchlike that should be registering hits from giant explosions nearby, but often don't, for whatever reason. Bigger things rarely fail, so it usually works out.
if(colPoint == null) colPoint = targLoc;
if(colPoint != null)//Must check this, getCollisionPoint returns null fairly frequently and that's a wrap
{
engine.applyDamage(
target, //enemy Entity
colPoint, //Our 2D vector to the exact world-position of the collision
dam, //DPS modified by the damage multiplier
damType, //Using the damage type here, so that Kinetic / Explosive / Fragmentation AOE works.
emp, //EMP (if any)
false, //Does not bypass shields.
false, //Does not do Soft Flux damage (unless you want it to for some strange reason)
projectile.getSource() //Who owns this projectile?
);
}
}
}
}
}
//Gets all ships;
List ships = engine.getAllShips();
Iterator it = ships.iterator();
while (it.hasNext())
{
ShipAPI ship = (ShipAPI) it.next();
if (ship.isFrigate())
{
ship.setCollisionClass(CollisionClass.FIGHTER);
continue;
}
if (ship.isFighter() || ship.isShuttlePod() || ship.isFrigate() || ship.isDrone()) continue;
//Gets the ship's velocity, expressed as a vector.
Vector2f shipVec = ship.getVelocity();
shipVec = (Vector2f) shipVec.scale(0.999f);
float shipSpeed = (float) Math.sqrt(Math.abs(shipVec.getX() * shipVec.getX()) + Math.abs(shipVec.getY() * shipVec.getY()));
MutableShipStatsAPI stats = ship.getMutableStats();
float maxSpeed = (float) stats.getMaxSpeed().getBaseValue();
float speedPenalty = Math.min(50f,(shipSpeed / maxSpeed) * 100.0f);
String id = ship.getFleetMemberId();
stats.getMaxTurnRate().unmodify(id);
stats.getTurnAcceleration().unmodify(id);
stats.getMaxTurnRate().modifyPercent(id,-speedPenalty);
stats.getTurnAcceleration().modifyPercent(id,-speedPenalty);
//public MutableStat getMaxTurnRate();
//public MutableStat getTurnAcceleration();
//Converts the player's heading (in degrees) back to unit vectors.
Vector2f rotVec = new Vector2f ((float) Math.cos(ship.getFacing() * 3.14159f / 180f), (float) Math.sin(ship.getFacing() * 3.14159f / 180f));
//Gets the "right" vector, i.e. perpendicular to the forward axis
Vector2f rightVec = new Vector2f(rotVec.getY() * -1.0f,rotVec.getX());
//Dot products of the two vectors
float dotForward = Vector2f.dot(shipVec, rotVec);
float dotRight = Vector2f.dot(shipVec, rightVec);
//Gives us the forward and rightward vectors of current velocity
Vector2f forwardVelocity = new Vector2f(rotVec.getX() * dotForward, rotVec.getY() * dotForward);
Vector2f rightVelocity = new Vector2f(rightVec.getX() * dotRight, rightVec.getY() * dotRight);
//Final conversion, drops the rightward component's magnitude while preserving the forward component.
//The multiplier 0.02f is our tuning variable; try different amounts to play with the "friction".
Vector2f.add(shipVec,new Vector2f(rightVelocity.getX() * -0.02f, rightVelocity.getY() * -0.02f),shipVec);
//This is an alternative behavior; it keeps forward velocity (towards the forward vector) constant but lowers...
//"right" velocity, resulting in a ship with an "inertialess" drive. It might be useful for various behaviors.
//Commented out, because the above behavior is closer to classic land / sea movement.
//shipVec = shipVec.add(forwardVelocity,new Vector2f(rightVelocity.getX() * -0.02f, rightVelocity.getY() * -0.02f),shipVec);
}
package data.missions.paragone;
import com.fs.starfarer.api.campaign.CargoAPI.CrewXPLevel;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.fleet.FleetGoal;
import com.fs.starfarer.api.fleet.FleetMemberType;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.mission.MissionDefinitionAPI;
import com.fs.starfarer.api.mission.MissionDefinitionPlugin;
public class MissionDefinition implements MissionDefinitionPlugin {
public void defineMission(MissionDefinitionAPI api) {
// Set up the fleets
api.initFleet(FleetSide.PLAYER, "TTS", FleetGoal.DEFEND, false, 30);
api.initFleet(FleetSide.ENEMY, "LI", FleetGoal.ATTACK, true, 30);
// Set a blurb for each fleet
api.setFleetTagline(FleetSide.PLAYER, "Emergency Force Delta");
api.setFleetTagline(FleetSide.ENEMY, "The Rogue L.I. / TTS Decisive");
// These show up as items in the bulleted list under
// "Tactical Objectives" on the mission detail screen
api.addBriefingItem("The TTS Decisive must be defeated. The Rook must survive.");
// Set up the player's fleet
api.addToFleet(FleetSide.PLAYER, "aurora_Balanced", FleetMemberType.SHIP, "TTS Rook", true, CrewXPLevel.ELITE);
api.addToFleet(FleetSide.PLAYER, "silent_raven_Standard", FleetMemberType.SHIP, "TTS Dark Wing", true, CrewXPLevel.ELITE);
api.addToFleet(FleetSide.PLAYER, "silent_raven_Standard", FleetMemberType.SHIP, "TTS Ebon Claw", true, CrewXPLevel.ELITE);
//api.addToFleet(FleetSide.PLAYER, "onslaught_Standard", FleetMemberType.SHIP, "TTS Invincible", true, CrewXPLevel.ELITE);
// Mark player flagship as essential.
api.defeatOnShipLoss("TTS Rook");
// Set up the enemy fleet
api.addToFleet(FleetSide.ENEMY, "paragon_Elite", FleetMemberType.SHIP, "TTS Decisive", true, CrewXPLevel.ELITE);
// Set up the map.
float width = 9000f;
float height = 9000f;
api.initMap((float)-width/2f, (float)width/2f, (float)-height/2f, (float)height/2f);
float minX = -width/2;
float minY = -height/2;
for (int i = 0; i < 15; i++) {
float x = (float) Math.random() * width - width/2;
float y = (float) Math.random() * height - height/2;
float radius = 100f + (float) Math.random() * 900f;
api.addNebula(x, y, radius);
}
// Add an asteroid field going diagonally across the
// battlefield, 2000 pixels wide, with a maximum of
// 100 asteroids in it.
// 20-70 is the range of asteroid speeds.
api.addAsteroidField(0f, 0f, (float) Math.random() * 360f, width,
20f, 70f, 100);
api.addPlugin(new RandomJunkScript(width, height));
}
}
package data.missions.paragone;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.util.IntervalUtil;
import java.util.List;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.DamageType;
import java.awt.Color;
public class RandomJunkScript implements EveryFrameCombatPlugin
{
private IntervalUtil goInterval = new IntervalUtil(0.1f, 0.1f);
// Whether one-time events have been triggered yet
private boolean hasSpawned = false;
// The combat engine object used by this battle
private CombatEngineAPI engine;
private float height;
private float width;
public RandomJunkScript(float width, float height)
{
// We're just passing the battlefield's height and width in here
this.height = height;
this.width = width;
}
private void buildShip(String type)
{
float x = (float) Math.random() * width - width/2;
float y = (float) Math.random() * height - height/2;
Vector2f loc = new Vector2f();
loc.x = x;
loc.y = y;
float facing = (float) Math.random() * 360f;
//String variantID = "brawler_Assault"; //Safety code here
//Give the enemy fleet this ship... temporarily...
ShipAPI target = engine.getFleetManager(FleetSide.ENEMY).spawnShipOrWing(type, loc, facing);
//re-use loc here, use the new position of the ship we've built
loc = target.getLocation();
//move the ship waaaaay off-screen
loc.x += 100000f;
loc.y += 100000f;
//Blow up the new ship.
engine.spawnEmpArc(target, loc, (CombatEntityAPI) target, (CombatEntityAPI) target,
DamageType.ENERGY,
1000000f,
0f, // emp
10000000f, // max range
"tachyon_lance_emp_impact",
0.0001f, // thickness
new Color(0,0,0,0),
new Color(0,0,0,0)
);
//Move the ship back to its spawn position.
loc.x -= 100000f;
loc.y -= 100000f;
}
private void buildFriendlyShip(String type)
{
float x = (float) Math.random() * width - width/2;
float y = (float) (Math.random() * (height / 4)) - height / 8 - 2000f;
Vector2f loc = new Vector2f();
loc.x = x;
loc.y = y;
float facing = (float) Math.random() * 360f;
//Give our fleet the ship.
ShipAPI target = engine.getFleetManager(FleetSide.PLAYER).spawnShipOrWing(type, loc, facing);
}
@Override
public void advance(float amount, List events)
{
// If the game is paused or we've already done the one-time events, do nothing
if (engine.isPaused())
{
return;
}
// One time setup at battle start
goInterval.advance(amount);
if (goInterval.intervalElapsed() && !hasSpawned)
{
int randomNum = (int) (Math.random() * 6f + 1f);
for (int i = 0; i < randomNum; i++) {
buildShip("afflictor_Strike");
}
randomNum = (int) (Math.random() * 6f + 1f);
for (int i = 0; i < randomNum; i++) {
buildShip("aurora_Balanced");
}
randomNum = (int) (Math.random() * 6f + 1f);
for (int i = 0; i < randomNum; i++) {
buildShip("buffalo_Standard");
}
randomNum = (int) (Math.random() * 6f + 1f);
for (int i = 0; i < randomNum; i++) {
buildShip("odyssey_Balanced");
}
randomNum = (int) (Math.random() * 6f + 1f);
for (int i = 0; i < randomNum; i++) {
buildFriendlyShip("aurora_Balanced");
}
hasSpawned = true;
}
}
@Override
public void init(CombatEngineAPI engine)
{
this.engine = engine;
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import java.util.List;
import java.util.ListIterator;
import org.lwjgl.util.vector.Vector2f;
/*
* Code by: xenoargh
*/
public class PhysicsFun implements EveryFrameCombatPlugin
{
private CombatEngineAPI engine;
@Override
public void init(CombatEngineAPI engine)
{
this.engine = engine;
}//INIT
@Override
public void advance(float amount, List events)
{
if (engine.isPaused()) return;
ListIterator allships = engine.getShips().listIterator();
if(allships.hasNext())
{
while(allships.hasNext())
{
ShipAPI ship = (ShipAPI) allships.next();
if (ship.isFrigate())
{
ship.setCollisionClass(CollisionClass.FIGHTER);
continue;
}//IF
if (ship.isFighter() || ship.isShuttlePod() || ship.isFrigate() || ship.isDrone())
continue;//IF
//Gets the ship's velocity, expressed as a vector.
Vector2f shipVec = ship.getVelocity();
shipVec = (Vector2f) shipVec.scale(0.999f);
float shipSpeed = (float) Math.sqrt(Math.abs(shipVec.getX() * shipVec.getX()) + Math.abs(shipVec.getY() * shipVec.getY()));
MutableShipStatsAPI stats = ship.getMutableStats();
float maxSpeed = (float) stats.getMaxSpeed().getBaseValue();
float speedPenalty = Math.min(50f,(shipSpeed / maxSpeed) * 100.0f);
String id = ship.getFleetMemberId();
stats.getMaxTurnRate().unmodify(id);
stats.getTurnAcceleration().unmodify(id);
stats.getMaxTurnRate().modifyPercent(id,-speedPenalty);
stats.getTurnAcceleration().modifyPercent(id,-speedPenalty);
//public MutableStat getMaxTurnRate();
//public MutableStat getTurnAcceleration();
//Converts the player's heading (in degrees) back to unit vectors.
Vector2f rotVec = new Vector2f ((float) Math.cos(ship.getFacing() * 3.14159f / 180f), (float) Math.sin(ship.getFacing() * 3.14159f / 180f));
//Gets the "right" vector, i.e. perpendicular to the forward axis
Vector2f rightVec = new Vector2f(rotVec.getY() * -1.0f,rotVec.getX());
//Dot products of the two vectors
float dotForward = Vector2f.dot(shipVec, rotVec);
float dotRight = Vector2f.dot(shipVec, rightVec);
//Gives us the forward and rightward vectors of current velocity
Vector2f forwardVelocity = new Vector2f(rotVec.getX() * dotForward, rotVec.getY() * dotForward);
Vector2f rightVelocity = new Vector2f(rightVec.getX() * dotRight, rightVec.getY() * dotRight);
//Final conversion, drops the rightward component's magnitude while preserving the forward component.
//The multiplier 0.02f is our tuning variable; try different amounts to play with the "friction".
Vector2f.add(shipVec,new Vector2f(rightVelocity.getX() * -0.02f, rightVelocity.getY() * -0.02f),shipVec);
//This is an alternative behavior; it keeps forward velocity (towards the forward vector) constant but lowers...
//"right" velocity, resulting in a ship with an "inertialess" drive. It might be useful for various behaviors.
//Commented out, because the above behavior is closer to classic land / sea movement.
//shipVec = shipVec.add(forwardVelocity,new Vector2f(rightVelocity.getX() * -0.02f, rightVelocity.getY() * -0.02f),shipVec);
}//WHILE
}//IF
}//ADVANCE
}//CLASS
public class [name_of_the_script] implements EveryFrameCombatPlugin
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MutableStat.StatMod;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipSystemAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Random;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
/*
* Multiple scripts from the following authors:
* xangle13 for hes: http://fractalsoftworks.com/forum/index.php?topic=5819 and http://fractalsoftworks.com/forum/index.php?topic=6003.0
* Psiyon for hes Original Crit System Script;
* LazyWizard for hes IncendiaryAmmoPlugin, ArmorPiercePlugin and LazyLib 1.5;
* Alex for helping me with a issue;
* @author eXist3nZ
*/
public class [name_of_the_script] implements EveryFrameCombatPlugin
{
// Combat Engine ofc
private CombatEngineAPI engine;
// Change this value for hull mods for example, original value is 5% Crit Chance
public float PercCritChance = 0.05f;
// CritDamage is 2 times the normal damage.
public float CritDamage = 2f;
// Sound to play while piercing a target's shield - Depending on size
private static final Map SOUND_IDS_PENETRATION_SHIELD = new HashMap();
static
{
SOUND_IDS_PENETRATION_SHIELD.put("Small", "explosion_ship");
SOUND_IDS_PENETRATION_SHIELD.put("Medium", "explosion_ship");
SOUND_IDS_PENETRATION_SHIELD.put("Large", "explosion_ship");
}
// Sound to play while piercing a target's armor - Depending on size (should be loopable!)
private static final Map SOUND_IDS_PENETRATION_ARMOR = new HashMap();
static
{
SOUND_IDS_PENETRATION_ARMOR.put("Small", "explosion_ship");
SOUND_IDS_PENETRATION_ARMOR.put("Medium", "explosion_ship");
SOUND_IDS_PENETRATION_ARMOR.put("Large", "explosion_ship");
}
// Sound on High Explosive explosion
private static final Map SOUND_IDS_HE = new HashMap();
static
{
SOUND_IDS_HE.put("isFrigate", "explosion_ship");
SOUND_IDS_HE.put("isDestroyer", "explosion_ship");
SOUND_IDS_HE.put("isCruiser", "explosion_ship");
SOUND_IDS_HE.put("isCapital", "explosion_ship");
}
// Sound on Energy explosion
private static final Map SOUND_IDS_ENERGY = new HashMap();
static
{
SOUND_IDS_ENERGY.put("isFrigate", "explosion_ship");
SOUND_IDS_ENERGY.put("isDestroyer", "explosion_ship");
SOUND_IDS_ENERGY.put("isCruiser", "explosion_ship");
SOUND_IDS_ENERGY.put("isCapital", "explosion_ship");
}
// Projectile ID (String), pierces shields (boolean)
private static final List PROJ_IDS = new ArrayList();
static
{
//add all the projectile ids that ignore shield
PROJ_IDS.add("lw_impaler_shot");
}
@Override
public void init(CombatEngineAPI engine) {
this.engine = engine;
}
@Override
public void advance(float amount, List events)
{
// Obvious exploit is obvious
if (engine.isPaused())
{
return;
}
DamagingProjectileAPI proj;
CombatEntityAPI entity;
String spec;
// Scan all shots on the map for armor piercing projectiles
for (Iterator itproj = engine.getProjectiles().iterator(); itproj.hasNext();)
{
//Grab a projectile in the battlefield
proj = (DamagingProjectileAPI) itproj.next();
//Get its ID (the one in the *.proj file)
spec = proj.getProjectileSpecId();
// We'll do collision checks manually
//proj.setCollisionClass(CollisionClass.NONE);
// Find nearby ships, missiles and asteroids
List toCheck = CombatUtils.getShipsWithinRange(proj.getLocation(),
proj.getCollisionRadius() + 5f);
toCheck.addAll(CombatUtils.getMissilesWithinRange(proj.getLocation(),
proj.getCollisionRadius() + 5f));
toCheck.addAll(CombatUtils.getAsteroidsWithinRange(proj.getLocation(),
proj.getCollisionRadius() + 5f));
// Don't include the ship that fired this projectile!
toCheck.remove(proj.getSource());
if(proj != null)
{
for (Iterator iter2 = toCheck.iterator(); iter2.hasNext();)
{
entity = (CombatEntityAPI) iter2.next();
// Check for an active phase cloak
if (entity instanceof ShipAPI)
{
ShipSystemAPI cloak = ((ShipAPI) entity).getPhaseCloak();
if (cloak != null && cloak.isActive())
{
continue;
}//IF
}//IF
// Check for a shield hit
if ((entity.getShield() != null
&& entity.getShield().isOn()
&& entity.getShield().isWithinArc(proj.getLocation())))
{
ListIterator projlist = PROJ_IDS.listIterator();
while(projlist.hasNext())
{
if(projlist.next().equals(spec))
{
// Save the CollisionClass
CollisionClass projCollision = proj.getCollisionClass();
// If we hit a shield, disable collision
proj.setCollisionClass(CollisionClass.NONE);
// Stop the projectile (ensures a hit for fast projectiles)
proj.getVelocity().set(entity.getVelocity());
// Then move the projectile inside the ship's shield bounds
Vector2f.add((Vector2f) MathUtils.getDirectionalVector(proj,entity).scale(5f), proj.getLocation(), proj.getLocation());
// Give back its original Collision
proj.setCollisionClass(projCollision);
// Play piercing sound (depends on the weapon size)
Global.getSoundPlayer().playSound(SOUND_IDS_PENETRATION_SHIELD.get(proj.getWeapon().getSize().getDisplayName()).toString(),
1f,
1f,
proj.getLocation(),
proj.getVelocity());
}
}
}
// Check if the projectile is inside the entity's bounds
else if (CollisionUtils.isPointWithinBounds(proj.getLocation(), entity))
{
//Is this a ship or a asteroid (dont forget asteroids can only be a CombatEntity)
if(critSucessfull(proj) && entity instanceof ShipAPI)
{
shipHitEffect ((ShipAPI) entity, proj);
}//IF
else
{
continue;
}
}//IF
}//FOR
}//IF
}//FOR
}//advance
public boolean critSucessfull(DamagingProjectileAPI damagingproj)
{
Random r = new Random();
WeaponAPI weapon = damagingproj.getWeapon();
float crewMaxXP = 0f;
if(weapon.getDamageType().equals(DamageType.FRAGMENTATION) ||
weapon.getDamageType().equals(DamageType.KINETIC) ||
weapon.getDamageType().equals(DamageType.OTHER))
{
return false;
}
ShipAPI ship = damagingproj.getSource();
float weaponRoF = weapon.getDerivedStats().getRoF();
StatMod crewBonus = ship.getMutableStats().getAutofireAimAccuracy().getFlatStatMod("crew skill bonus");
// Green crew don't grant a bonus
if (crewBonus != null)
{
//Goes from 0f to 1f - thanks Alex and Lazy for the help
crewMaxXP = crewBonus.getValue();
}
//slower shooting weapons should not surpass limit of 5%
if (weaponRoF < 1f)
{
weaponRoF = 1f;
}
float CritChance = r.nextFloat();
//This means that, if the crew is elite its (0.05f * 1) \ 1 that means 5%,
//if the crew is actually lower skilled and the weapon's RoF is higher the chances get lower.
float MAXCritChance = (PercCritChance * crewMaxXP) / weaponRoF;
if(CritChance <= MAXCritChance)
{
return true;
}
else
{
return false;
}
}//critSucessful
public void shipHitEffect (ShipAPI ship, DamagingProjectileAPI proj)
{
WeaponAPI weapon = proj.getWeapon();
float explosionSize = 0f;
float weaponRoF = weapon.getDerivedStats().getRoF();
//Lets make sure we dont get a REALLY HUGE Crit text
if (weaponRoF < 1f)
{
weaponRoF = 1f;
}
if(weapon.getSize().equals(WeaponSize.SMALL))
{
explosionSize = MathUtils.getRandomNumberInRange(20f, 30f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.MEDIUM))
{
explosionSize = MathUtils.getRandomNumberInRange(30f, 40f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.LARGE))
{
explosionSize = MathUtils.getRandomNumberInRange(40f, 50f) / weaponRoF;
}
engine.addFloatingText(proj.getLocation(), "CRITICAL HIT", explosionSize, Color.RED, proj.getDamageTarget(), 0f, 0f);
if(weapon.getDamageType().equals(DamageType.HIGH_EXPLOSIVE))
{
if(ship.isFrigate()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isFrigate").toString(),
explosionSize);
}
else if(ship.isDestroyer()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isDestroyer").toString(),
explosionSize);
}
else if(ship.isCruiser()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCruiser").toString(),
explosionSize);
}
else if(ship.isCapital()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCapital").toString(),
explosionSize);
}
}
else if(weapon.getDamageType().equals(DamageType.ENERGY))
{
if(ship.isFrigate()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isFrigate").toString(),
explosionSize);
}
else if(ship.isDestroyer()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isDestroyer").toString(),
explosionSize);
}
else if(ship.isCruiser()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCruiser").toString(),
explosionSize);
}
else if(ship.isCapital()) {
explosionEffects(ship,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCapital").toString(),
explosionSize);
}
}
}//shipHitEffect
public void explosionEffects (ShipAPI ship, DamagingProjectileAPI proj, DamageType dmgtype, String ExplosionSoundID, float explosionSize)
{
if(dmgtype.equals(DamageType.HIGH_EXPLOSIVE))
{
float damage = proj.getDamageAmount() * CritDamage;
float emp = proj.getEmpAmount() * CritDamage;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(ship,
proj.getLocation(), damage, proj.getDamageType(),
emp, true, true, proj.getSource());
proj.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
proj.getLocation(),
ship.getVelocity(),
Color.ORANGE,
1f,
1.5f);
// Play Explosion sound
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
proj.getLocation(),
ship.getVelocity());
if(!ship.isHulk())
{
engine.addSmokeParticle(ship.getLocation(), // Location
ship.getVelocity(), // Velocity
explosionSize, // Size
MathUtils.getRandomNumberInRange(.5f, .75f), // Brightness
10f, Color.DARK_GRAY); // Duration, color
}
}
else if(dmgtype.equals(DamageType.ENERGY))
{
float damage = proj.getDamageAmount() * CritDamage;
float emp = proj.getEmpAmount() * CritDamage;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(ship,
proj.getLocation(),
damage,
proj.getDamageType(),
emp,
true,
true,
proj.getSource());
proj.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
proj.getLocation(),
ship.getVelocity(),
Color.blue,
1f,
1.5f);
// Play Crit Explosion
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
proj.getLocation(),
ship.getVelocity());
if(!ship.isHulk())
{
engine.addSmokeParticle(ship.getLocation(), // Location
ship.getVelocity(), // Velocity
explosionSize, // Size
MathUtils.getRandomNumberInRange(.5f, .75f), // Brightness
10f, Color.DARK_GRAY); // Duration, color
}
}
}//explosionEffects
}//MAIN CLASS
package data.scripts;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
public class FreezeRayEffect implements BeamEffectPlugin {
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
CombatEntityAPI target = beam.getDamageTarget();
//Do we have a valid Entity to effect?
if (target != null)
{
//Yes! Is it in range, and the beam's on?
if (beam.getBrightness() >= 1f)
{
//Get the velocity, and drop it- bigger ships drop a lot less than small ones.
//This might not be balanced for your mod. The accumulative change in velocity per-frame is really huge!
Vector2f targVel = target.getVelocity();
if(target instanceof ShipAPI)
{
ShipAPI ship = (ShipAPI) target;
if(ship.isDrone())
{
targVel.x *= 0.75f;
targVel.y *= 0.75f;
} else if (ship.isFighter())
{
targVel.x *= 0.95f;
targVel.y *= 0.95f;
} else if (ship.isFrigate())
{
targVel.x *= 0.97f;
targVel.y *= 0.97f;
} else if (ship.isDestroyer())
{
targVel.x *= 0.99f;
targVel.y *= 0.99f;
} else if (ship.isCruiser())
{
targVel.x *= 0.995f;
targVel.y *= 0.995f;
} else if (ship.isCapital())
{
targVel.x *= 0.999f;
targVel.y *= 0.999f;
}
} else {
//If not a ship, slow down a lot. Instant slowdown for bombs, takes just a few frames for most missiles.
targVel.x *= 0.75f;
targVel.y *= 0.75f;
}
}
}
}
}
//Silentstormpt's Critical Hit system, v. 2.0, xenoargh build
//Credits to Silentstormpt, xangle13, Psyion, LazyWizard, xenoargh, eXist3nz, Alex
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MutableStat.StatMod;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipSystemAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Random;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
public class CriticalHits implements EveryFrameCombatPlugin
{
private CombatEngineAPI engine;
// Sound to play while piercing a target's armor (should be loopable!)
private static final String PIERCE_SOUND = "explosion_ship"; // TEMPORARY
public void init(CombatEngineAPI engine) {
this.engine = engine;
}
public void advance(float amount, List events)
{
// Obvious exploit is obvious
if (engine.isPaused())
{
return;
}
DamagingProjectileAPI proj;
CombatEntityAPI entity;
String spec;
// Scan all shots on the map for armor piercing projectiles
for (Iterator itproj = engine.getProjectiles().iterator(); itproj.hasNext();)
{
//Grab a projectile in the battlefield
proj = (DamagingProjectileAPI) itproj.next();
//Did this projectile do damage to an Entity?
if(proj.didDamage() && proj.getDamageTarget() != null)
{
//Get the entity hit
entity = (CombatEntityAPI) proj.getDamageTarget();
// Check to make sure we've hit a ship, and if so, for a shield hit; if we hit a ship's shield or a non-ship, stop!
if(entity instanceof ShipAPI && entity.getShield() == null || entity instanceof ShipAPI && !entity.getShield().isOn() || entity instanceof ShipAPI && !entity.getShield().isWithinArc(proj.getLocation()))
{
spec = proj.getProjectileSpecId();
if(critSucessful(proj))
{
//If Shuttle, stop right now!
if(((ShipAPI) entity).isShuttlePod())
{
continue;
}
//Random damage from crits
amount = MathUtils.getRandomNumberInRange(2f,5f);
float damage = (proj.getDamageAmount() * amount);
float emp = (proj.getEmpAmount() * amount);
Vector2f projVel = proj.getVelocity();
Vector2f projPos = proj.getLocation();
engine.applyDamage(entity,
projPos, damage, proj.getDamageType(),
emp, true, true, proj.getSource());
engine.addFloatingText(projPos, "CRIT!", Math.min(Math.max(20f,damage/200f),50f), Color.RED, entity, 0f, 0f);
//Create particle bursts only for big crits
if(damage > 1500f)
{
for(int i = 0; i < 30; i++)
{
//Invert the vector and randomize a little bit
float randX = MathUtils.getRandomNumberInRange(-60f,60f);
float randY = MathUtils.getRandomNumberInRange(-60f,60f);
Vector2f newVel = new Vector2f (randX - projVel.x / 3f, randY - projVel.y / 3f);
int randYel = (int) MathUtils.getRandomNumberInRange(64f,200f);
engine.addHitParticle(projPos, newVel, MathUtils.getRandomNumberInRange(5f,8f), 1f, MathUtils.getRandomNumberInRange(3f,7f), new Color(255,randYel,0,127));
}
}
}//IF
}//IF
}//FOR
}//FOR
}//advance
public boolean critSucessful(DamagingProjectileAPI damagingproj)
{
Random r = new Random();
WeaponAPI weapon = damagingproj.getWeapon();
float MAXCritChance = 0.05f; // change this value for hull mods for example, original value is 5%
float crewMaxXP = 0f;
//Safety code, catches null pointer exception
if(weapon == null || weapon.getDamageType() == null) return false;
//If the weapon can't crit, it stops here
if(weapon.getDamageType().equals(DamageType.FRAGMENTATION) ||
weapon.getDamageType().equals(DamageType.ENERGY) ||
weapon.getDamageType().equals(DamageType.OTHER))
{
return false; //No crit for u!
}
//Where did this shot come from?
ShipAPI ship = damagingproj.getSource();
float weaponRoF = weapon.getDerivedStats().getRoF() * 4f;//Balance here; crits should be really rare for high-RoF weapons
StatMod crewBonus = ship.getMutableStats().getAutofireAimAccuracy().getFlatStatMod("crew skill bonus");
// Green crew don't grant a bonus
if (crewBonus != null)
{
crewMaxXP = crewBonus.getValue(); //goes from 0f to 1f - thanks Alex and Lazy for the help
}
//Get our random number
float CritChance = r.nextFloat();
//Crit chance never goes above the max / min values here.
if(CritChance <= Math.min(Math.max((MAXCritChance * crewMaxXP) / weaponRoF,0.001f),0.05f))
{
return true; //We got a crit, send it back!
} else {
return false; //Failed...
}
}//critSucessful
}//MAIN CLASS
The Freeze Ray. Slows down Entities that are hit. With enough of them, you can immobilize anything.
Never see a pesky Frigate run away again! Muahahahahaha... er, I mean, it works, and stuff. Can't wait to release my mod now, these are just a few of the toys ;)SpoilerCodepackage data.scripts;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
public class FreezeRayEffect implements BeamEffectPlugin {
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
CombatEntityAPI target = beam.getDamageTarget();
//Do we have a valid Entity to effect?
if (target != null)
{
//Yes! Is it in range, and the beam's on?
if (beam.getBrightness() >= 1f)
{
//Get the velocity, and drop it- bigger ships drop a lot less than small ones.
//This might not be balanced for your mod. The accumulative change in velocity per-frame is really huge!
Vector2f targVel = target.getVelocity();
if(target instanceof ShipAPI)
{
ShipAPI ship = (ShipAPI) target;
if(ship.isDrone())
{
targVel.x *= 0.75f;
targVel.y *= 0.75f;
} else if (ship.isFighter())
{
targVel.x *= 0.95f;
targVel.y *= 0.95f;
} else if (ship.isFrigate())
{
targVel.x *= 0.97f;
targVel.y *= 0.97f;
} else if (ship.isDestroyer())
{
targVel.x *= 0.99f;
targVel.y *= 0.99f;
} else if (ship.isCruiser())
{
targVel.x *= 0.995f;
targVel.y *= 0.995f;
} else if (ship.isCapital())
{
targVel.x *= 0.999f;
targVel.y *= 0.999f;
}
} else {
//If not a ship, slow down a lot. Instant slowdown for bombs, takes just a few frames for most missiles.
targVel.x *= 0.75f;
targVel.y *= 0.75f;
}
}
}
}
}[close]
I've got a stupid question. How would one attach the critical hit system to a weapon? :-[
Or does this script enable critical hits for every weapon in the mod?
Edit: After testing I see that this script gives all weapons this ability. Would it be possible to limit it to a specific weapon? :)
"onHitEffect":"data.scripts.plugins.HiiHeatCannonEffect",
29903 [Thread-6] ERROR com.fs.starfarer.combat.D - java.lang.RuntimeException: Error compiling [data.scripts.plugins.HiiHeatCannonEffect]
java.lang.RuntimeException: Error compiling [data.scripts.plugins.HiiHeatCannonEffect]
at com.fs.starfarer.loading.scripts.ScriptStore$1.run(Unknown Source)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.ClassNotFoundException: Parsing compilation unit "com.fs.starfarer.loading.A$1@1f217ec"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:180)
at org.codehaus.janino.IClassLoader.loadIClass(IClassLoader.java:158)
at org.codehaus.janino.JavaSourceClassLoader.generateBytecodes(JavaSourceClassLoader.java:199)
at org.codehaus.janino.JavaSourceClassLoader.findClass(JavaSourceClassLoader.java:164)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
... 2 more
Caused by: org.codehaus.commons.compiler.CompileException: Source file "data/scripts/plugins/HiiHeatCannonEffect.java" does not declare class "data.scripts.plugins.HiiHeatCannonEffect"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:165)
... 7 more
package data.scripts;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.OnHitEffectPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
public class HiiHeatCannonEffect implements OnHitEffectPlugin {
public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target,
Vector2f point, boolean shieldHit, CombatEngineAPI engine) {
if (target instanceof ShipAPI) {
float emp = projectile.getEmpAmount() * 0.1f;
float dam = projectile.getDamageAmount() * 0.1f;
WeaponAPI weapon = projectile.getWeapon();
DamageType damType = weapon.getDamageType();
int heatMe = (int) Math.max(6f,dam / 150f);
Vector2f targLoc = target.getLocation();
for(int i = 0; i < heatMe; i++)
{
//Gets a point far, far away and uses it as our ray-test point. Long distances work better than shorter ones.
Vector2f cloneLoc = MathUtils.getRandomPointOnCircumference(targLoc, 1000000f);
//Try to get a valid collision point between our explosion's point source and the Entity.
Vector2f colPoint = CollisionUtils.getCollisionPoint(cloneLoc, targLoc, target);
//If we can't get a good collision point, use the center of the target Entity. This is potentially a balance issue (hits all going to one armor cell are pretty OP lol), but this case mainly covers little teeny drones and suchlike that should be registering hits from giant explosions nearby, but often don't, for whatever reason. Bigger things rarely fail, so it usually works out.
if(colPoint == null) colPoint = targLoc;
if(colPoint != null)//Must check this, getCollisionPoint returns null fairly frequently and that's a wrap
{
engine.applyDamage(
target, //enemy Entity
colPoint, //Our 2D vector to the exact world-position of the collision
dam, //DPS modified by the damage multiplier
damType, //Using the damage type here, so that Kinetic / Explosive / Fragmentation AOE works.
emp, //EMP (if any)
false, //Does not bypass shields.
false, //Does not do Soft Flux damage (unless you want it to for some strange reason)
projectile.getSource() //Who owns this projectile?
);
}
}
}
}
}
Would anyone know how to properly implement the Heat Special Projectile?
package data.scripts;
package data.scripts.plugins;
Dont forget the new version of Lazylib decrypted some methods used on that crit System, ill need to check what needs to be changed.
Might change the script to be used as a OnHit so its not so heavy in resources on huge fights, this means it needs to be added to every weapon tho.
package data.scripts.onhiteffects;
import java.awt.Color;
import java.util.Random;
import com.fs.starfarer.api.combat.*;
import org.lwjgl.util.vector.Vector2f;
public class Lightningstorm implements OnHitEffectPlugin {
public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target,
Vector2f point, boolean shieldHit, CombatEngineAPI engine) {
if (target instanceof ShipAPI && !shieldHit) { //only if hits hull
do {
double rnd1 = .5 - Math.random(); //see below for details
float rn1 = (float)rnd1;
double rnd2 = .5 - Math.random();
float rn2 = (float)rnd2; // +(float)rnd*2 to create a ring outside of ship
float px = target.getLocation().getX(); //get center of cloud
float py = target.getLocation().getY(); //get center of cloud
float sr = target.getCollisionRadius(); //replace to change to manual area or effect
Vector2f pa = new Vector2f(px + sr*rn1, py + sr*rn2); //replace "sr" to change to manual area or effect
float emp = projectile.getEmpAmount();
float dam = projectile.getDamageAmount();
engine.spawnEmpArc(projectile.getSource(), pa, target, target,
DamageType.ENERGY,
dam,
emp, // emp
sr, // max range prevent bolds hitting wrong side of the ship
"tachyon_lance_emp_impact",
20f, // thickness
new Color(255,10,15,255),
new Color(255,255,255,255)
);
}while ((float) Math.random() > 0.6f && target instanceof ShipAPI); //duration
}
else if (target instanceof ShipAPI && shieldHit) { //only if hits shield
do {
double rnd1 = .5 - Math.random(); //generate a random double with -vs component, range -.5 to +.5
float rn1 = (float)rnd1; // convert to float
double rnd2 = .5 - Math.random();
float rn2 = (float)rnd2; // +(float)rnd*2 to create a ring (implemented later in a better way)
float px = target.getLocation().getX(); //get center of cloud
float py = target.getLocation().getY();
float hx = point.getX(); //get center of cloud
float hy = point.getY(); //get center of cloud
float sr = target.getCollisionRadius(); //replace to change to manual area or effect
Vector2f pa = new Vector2f(hx + 1/2*(hx-px) + sr*rn1, hy + 1/2*(hy-py) + sr*rn2); //cause bolt spawn center to be just outside of ship radius,
//at the point where it was hit, within a radius bounded by max range(5/3*sr) and (1/2*sr) around point
float emp = projectile.getEmpAmount();
float dam = projectile.getDamageAmount();
engine.spawnEmpArc(projectile.getSource(), pa, target, target,
DamageType.ENERGY,
2/4*dam, //make it less shield destroying
emp, // emp
5/3*sr, // max range make bolts hit hull closest
"tachyon_lance_emp_impact",
20f, // thickness
new Color(255,10,15,255),
new Color(255,255,255,255)
);
if ((float) Math.random() > 0.85f) { //generate additional bolts around ship 15% chance per loop
double rnd1b = .25 - .5*Math.random();
float rn1b = (float)rnd1b+(float)rnd1b*5;
double rnd2b = .25 - .5*Math.random();
float rn2b = (float)rnd2b+(float)rnd2b*5; // +(float)rnd*2 to create a ring outside of ship
float pxb = target.getLocation().getX(); //get center of cloud
float pyb = target.getLocation().getY(); //get center of cloud
Vector2f pab = new Vector2f(pxb + sr*rn1b, pyb + sr*rn2b); //replace "sr" to change to manual area or effect
engine.spawnEmpArc(projectile.getSource(), pab, target, target,
DamageType.ENERGY,
dam,
emp, // emp
10000f, // max range
"tachyon_lance_emp_impact",
20f, // thickness
new Color(255,10,15,255),
new Color(255,255,255,255)
);
}
}while ((float) Math.random() > 0.6f && target instanceof ShipAPI); //duration
}
}
}
//less scarry math
//Vector2f pa = new Vector2f(px + sr*rn1, py + sr*rn2); //replace "sr" to change to manual area or effect
//creates a sphere of lightning around target ship
// +(float)rnd*2 to create a ring (implemented later in a better way):
//double rndx1 = rnd1/Math.abs(rnd1)*sr; //to generate exclusion zone of size sr
//projectile.getWeapon().getLocation()
// for arc from gun
//projectile.getDamageTarget().getLocation()
//long form for "point" Vector2f
if (MathUtils.getDistance(
add(target.getLocation(), target.getVelocity(), null), //blow up the enemy ship
add(missile.getLocation(), multV2f(missile.getVelocity(),3), null))
< 200
&& MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ship
missile.getSource().getLocation())
>missile.getSource().getCollisionRadius()+5)
{
CombatEngine engine = CombatEngine.getInstance(); //engine
String MBRC_p = "MBRC2"; //dummy weapon
{
{
int counts = 1;
do {
float angRAND= (float) ((25*(.5-Math.random()))); //angle of spread
float velRAND= (float) (1+.5*(.5-Math.random())); //variance of projectile speeds
float splashVEL = 255f*velRAND; //speed of bullets launched
float misFFACE = missile.getFacing()-angRAND;
float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(null, null,MBRC_p,
missile.getLocation(), //Vector2f firing point
misFFACE, //float angle of spread
add(missile.getVelocity(), vecFIRE, null)
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
counts++;
}while (counts<30); //30x pew pew
//engine.removeEntity(missile); //make missile go poof
}
//lightning
// float emp = missile.getEmpAmount();
//float dam = missile.getDamageAmount();
//engine.spawnEmpArc(missile.getSource(), missile.getLocation(), target, target,
// DamageType.ENERGY,dam/4, emp/4,
//10000f, "tachyon_lance_emp_impact",20f, new Color(255,10,15,255),new Color(255,100,100,255));
}
//to stop missile shooting again
//engine.removeEntity(missile); //make missile go poof
missile.flameOut(); //make missile flame out
return;
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
//more code
public static Vector2f multRanV2f(Vector2f Vector1, float Multiplier, float Random)
{
double rxd = .5 - Math.random(); //generate a random double with -vs component, range -.5 to +.5
float rx = (float)rxd*2*Random; // convert to float
double ryd = .5 - Math.random();
float ry = (float)ryd*2*Random;
float v1x = Vector1.getX()*Multiplier*rx;
float v1y = Vector1.getY()*Multiplier*ry;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
}
//create variables
//capitalized can be changed
//insert targeter code here (anything goes, you may need to change it so T gives tx/ty a float value
//see at bottom for possible code
CombatEntityAPI T = target;//end point of " " (enemy api/point in space/ect) for targeter
MissileAPI P = missile; //start point of " " for weapon
float px = P.getLocation().getX(); //get location og p (for center of dakakakaka)
float py = P.getLocation().getY();
float mvx = missile.getVelocity().getX();
float mvy = missile.getVelocity().getY();
float tx = px+mvx; //get location of t (for center of pew pew)
float ty = py+mvy;
double rxd = .5 - Math.random(); //generate a random double with -vs component, range -.5 to +.5
float rx = (float)rxd*2; // convert to float
double ryd = .5 - Math.random();
float ry = (float)ryd*2;
double rpd = Math.random(); //+ve random
float rp = (float)rpd;
float Tvar = 50f; //radius of t (sets mirv spread (angular))
float Pvar = 1f; //variable for vectors can use random float
double prany = py/Math.abs(py)*Pvar;
double pranx = px/Math.abs(px)*Pvar;
float prx = (float)pranx;
float pry = (float)prany;
float Rvar = 5f; //how much the velocities of projectiles can vary
double rvd = .5 - Math.random(); //generate a random double with -vs component, range -.5 to +.5
float rv = (float)rxd*Rvar;
//math
float Fx = (tx * rx * Tvar); //create randomized point field to shoot at
float Fy = (ty * ry * Tvar);
float Vx = (Fx * px * prx); //create vectors for pewpews to follow
float Vy = (Fy * py * pry);
double Smoothv = (Math.sqrt(((tx-px)*(tx-px))+((ty-py)*(ty-py)))/Math.sqrt((Vx*Vx)+(Vy*Vy)))*rv; //smoothes out ragged shot
float Sv = (float) Smoothv;
Vector2f Pjv = new Vector2f(Vx*Sv, Vy*Sv); //make the actual vector
//projectile spawn code here
engine.removeEntity(missile); //make missile go poof
}
//for canister of flashcetes behaviour
//CombatEntityAPI T = target;//end point of " " (enemy api/point in space/ect) for targeter
//MissileAPI P = missile; //start point of " " for weapon
//float px = P.getLocation().getX(); //get location og p (for center of dakakakaka)
//float py = P.getLocation().getY();
//float mvx = missile.getVelocity().getX();
//float mvy = missile.getVelocity().getY();
//float tx = px+mvx; //get location of t (for center of pew pew)
//float ty = py+mvy;
{
//create variables
//capitalized can be changed
//insert targeter code here (anything goes, you may need to change it so T gives tx/ty a float value
//see at bottom for possible code
CombatEntityAPI T = target;//end point of " " (enemy api/point in space/ect) for targeter
MissileAPI P = missile; //start point of " " for weapon
float px = P.getLocation().getX(); //get location og p (for center of dakakakaka)
float py = P.getLocation().getY();
//float mvx = missile.getVelocity().getX();
//float mvy = missile.getVelocity().getY();
float tx = T.getLocation().getX(); //get location of t (for center of pew pew)
float ty = T.getLocation().getY();
double rxd = .5 - Math.random(); //generate a random double with -vs component, range -.5 to +.5
float rx = (float)rxd*2; // convert to float
double ryd = .5 - Math.random();
float ry = (float)ryd*2;
double rpd = Math.random(); //+ve random
float rp = (float)rpd;
float Tvar = 50f; //radius of t (sets mirv spread (angular))
float Pvar = 1f; //variable for vectors can use random float
double prany = py/Math.abs(py)*Pvar;
double pranx = px/Math.abs(px)*Pvar;
float prx = (float)pranx;
float pry = (float)prany;
float Rvar = 5f; //how much the velocities of projectiles can vary
double rvd = .5 - Math.random(); //generate random velocity multiplier for smooth function (to arr roughness back in
float rv = (float)rvd*Rvar;
//math
float Fx = (tx * rx * Tvar); //create randomized point field to shoot at
float Fy = (ty * ry * Tvar);
float Vx = (Fx * px * prx); //create vectors for pewpews to follow
float Vy = (Fy * py * pry);
double Smoothv = (Math.sqrt(((tx-px)*(tx-px))+((ty-py)*(ty-py)))/Math.sqrt((Vx*Vx)+(Vy*Vy)))*rv; //smoothes out ragged shot
float Sv = (float) Smoothv;
Vector2f Pjv = new Vector2f(Vx*Sv, Vy*Sv); //make the actual vector
engine.spawnProjectile(null, null,
SBKR1,
missile.getLocation(), 0,Pjv);
engine.removeEntity(missile); //make missile go poof
}
int timer = 0; //needs to be outside of advance() to work as a timer
int timer2 = (int) (600*Math.random()); //random start point for strafing
public void advance(float amount)
{
timer++;
if (MathUtils.getDistance(
add(target.getLocation(), target.getVelocity(), null), //blow up the enemy ship
add(missile.getLocation(), multV2f(missile.getVelocity(),3), null))
< 600
&& MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ship
missile.getSource().getLocation())
>missile.getSource().getCollisionRadius()+5
&& timer==1
)
{
timer++;
CombatEngine engine = CombatEngine.getInstance(); //engine
String MBRC_p = "MBRC2"; //dummy weapon
{
{
int counts = 1;
do {
float angRAND= (float) ((25*(.5-Math.random()))); //angle of spread
float velRAND= (float) (1+.5*(.5-Math.random())); //variance of projectile speeds
float splashVEL = 255f*velRAND; //speed of bullets launched
float misFFACE = missile.getFacing()-angRAND;
float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(null, null,MBRC_p,
missile.getLocation(), //Vector2f firing point
misFFACE, //float angle of spread
add(missile.getVelocity(), vecFIRE, null)
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
counts++;
}while (counts<2); //2x pew pew
//engine.removeEntity(missile); //make missile go poof
}
//lightning
// float emp = missile.getEmpAmount();
//float dam = missile.getDamageAmount();
//engine.spawnEmpArc(missile.getSource(), missile.getLocation(), target, target,
// DamageType.ENERGY,dam/4, emp/4,
//10000f, "tachyon_lance_emp_impact",20f, new Color(255,10,15,255),new Color(255,100,100,255));
}
//to stop missile shooting again
//engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
return;
}
if (timer>40)
{timer=0;}
//missile pathing code
if (Math.abs(angularDistance) < 100 //get in close
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())
>320+100/target.getCollisionRadius()*target.getCollisionRadius())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (Math.abs(angularDistance) < 100 //keep distance
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())
<280+100/target.getCollisionRadius()*target.getCollisionRadius())
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
}
if (Math.abs(angularDistance) < 100 //strafe
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())<400+target.getCollisionRadius())
{
missile.giveCommand(timer2 > 300 ? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
timer2++;
if (timer2>600)
{
timer2=0;
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.combat.CombatEngine;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
import static org.lwjgl.util.vector.Vector2f.add;
public class CustomMINEMissileAI implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
public CustomMINEMissileAI(MissileAPI missile)
{
this.missile = missile;
}
float range1 = 0;
float range2 = 0;
int trigger = 0;
int deathcode = 0;
double minetimer = 0;
ShipAPI enemy=null;
@Override
public void advance(float amount)
{
CombatEngine engine = CombatEngine.getInstance(); //engine
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
minetimer++;
//intelligent mine distribution code
MissileAPI near = AIUtils.getNearestMissile(missile);
if (near != null //check if there are any nearby mines
&& near.getProjectileSpecId().equals(missile.getProjectileSpecId())
&& MathUtils.getDistance(near, missile) < 400)
{
//if there are move away from them
float angularDistance = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),
near.getLocation()));
if (Math.abs(angularDistance) < 175)
{
missile.giveCommand(angularDistance < 0f
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
if (Math.abs(angularDistance) > 135)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (Math.abs(angularDistance) < 45)
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
}
}
else
{
missile.giveCommand(ShipCommand.DECELERATE); //mine dose not move
}
//enemy tracking code
if (Math.tanh(minetimer)>.2) //run once every 3 frames to prevent fun stuff
{
if (enemy==null && AIUtils.getNearestEnemy(missile)!=null && AIUtils.getNearestEnemy(missile).isAlive()
)
{
float targetfinder = MathUtils.getDistance(AIUtils.getNearestEnemy(missile).getLocation(), missile.getLocation())
-AIUtils.getNearestEnemy(missile).getCollisionRadius();
if (targetfinder < 300)
{
enemy=AIUtils.getNearestEnemy(missile); //remember that enemy
}
if (targetfinder > 300)
{
return;
}
}
if (enemy != null) //check if there is an enemy all these need it
{
if (enemy!=AIUtils.getNearestEnemy(missile))
{
deathcode++; //if a second enemy comes close
trigger++;
}
if (!enemy.isAlive() | enemy.isHulk())
{
enemy=null; //if tracking enemy is dead, don't explode
}
if (deathcode==0 && range1==0
&& MathUtils.getDistance( //find out if there are enemy in range
enemy.getLocation(),
missile.getLocation())-enemy.getCollisionRadius()
< 200)
{
range1 = MathUtils.getDistance( //find get the range to target
enemy.getLocation(),
missile.getLocation())-enemy.getCollisionRadius();
}
}
if (deathcode==0 && range1!=0) //for all grouped in here
{
if (range1<100) //when enemy is close enough go boom
{
trigger++; //make it go boom
deathcode++; //stop tracking
}
{
range2 = MathUtils.getDistance(enemy.getLocation(), //find get the range to target now, 1 tick later
missile.getLocation())-enemy.getCollisionRadius();
}
if (range1>range2)
{
float range3 = MathUtils.getDistance( //find get the range to target now, 1 tick later
Vector2f.add(enemy.getLocation(), multV2f(enemy.getVelocity(), .5f), null), //analyze its velocity
missile.getLocation())-enemy.getCollisionRadius();
if (range1<range3)
{
trigger++;
deathcode++;
}
}
if (range1>range2)
{
range1=range2; //store new target range, if it got closer
}
if (range1<range2)
{
trigger++; //make it go boom
deathcode++; //stop tracking
}
if (missile.getMaxHitpoints()*0.9>missile.getHitpoints())
{
trigger++; //if damaged below 90%hp while in range, detonate
deathcode++;
}
}
//explosive code
if (trigger != 0
&&MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ships
missile.getSource().getLocation())
>AIUtils.getNearestAlly(missile).getCollisionRadius()+210)
{
String MBRC_p = "MBRC2"; //dummy weapon
{
{
int counts = 1;
do {
float angRAND= (float) ((360*(.5-Math.random()))); //angle of spread
float velRAND= (float) (.6+.5*(.5-Math.random())); //variance of projectile speeds
float splashVEL = 155f*velRAND; //speed of bullets launched
float misFFACE = missile.getFacing()-angRAND;
float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(null, null,MBRC_p,
missile.getLocation(), //Vector2f firing point
misFFACE, //float angle of spread
add(missile.getVelocity(), vecFIRE, null)
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
counts++;
}while (counts<100); //30x pew pew
//engine.removeEntity(missile); //make missile go poof
}
//lightning
// float emp = missile.getEmpAmount();
//float dam = missile.getDamageAmount();
//engine.spawnEmpArc(missile.getSource(), missile.getLocation(), target, target,
// DamageType.ENERGY,dam/4, emp/4,
//10000f, "tachyon_lance_emp_impact",20f, new Color(255,10,15,255),new Color(255,100,100,255));
}
//to stop missile shooting again
engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
return;
}
}
}
//in next lazylib
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//multiply vectors scale can not be used in static methods...
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import java.util.Iterator;
import java.util.List;
import com.fs.starfarer.combat.CombatEngine;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import static org.lazywizard.lazylib.combat.CombatUtils.getShipsWithinRange;
import static org.lwjgl.util.vector.Vector2f.add;
public class CustomMINEMissileAI implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
public CustomMINEMissileAI(MissileAPI missile)
{
this.missile = missile;
}
//mine
@Override
public void advance(float amount)
{
CombatEngine engine = CombatEngine.getInstance(); //engine
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
if (AIUtils.getNearestEnemy(missile) != null //check if there is an enemy
&& MathUtils.getDistance( //find out if there are enemy in range
AIUtils.getNearestEnemy(missile).getLocation(),
missile.getLocation()
)< 200+AIUtils.getNearestEnemy(missile).getCollisionRadius()
&& MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ship
missile.getSource().getLocation())
>missile.getSource().getCollisionRadius()+5)
{
String MBRC_p = "MBRC2"; //dummy weapon
{
{
int counts = 1;
do {
float angRAND= (float) ((360*(.5-Math.random()))); //angle of spread
float velRAND= (float) (.6+.5*(.5-Math.random())); //variance of projectile speeds
float splashVEL = 155f*velRAND; //speed of bullets launched
float misFFACE = missile.getFacing()-angRAND;
float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(null, null,MBRC_p,
missile.getLocation(), //Vector2f firing point
misFFACE, //float angle of spread
add(missile.getVelocity(), vecFIRE, null)
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
counts++;
}while (counts<100); //30x pew pew
//engine.removeEntity(missile); //make missile go poof
}
//lightning
// float emp = missile.getEmpAmount();
//float dam = missile.getDamageAmount();
//engine.spawnEmpArc(missile.getSource(), missile.getLocation(), target, target,
// DamageType.ENERGY,dam/4, emp/4,
//10000f, "tachyon_lance_emp_impact",20f, new Color(255,10,15,255),new Color(255,100,100,255));
}
//to stop missile shooting again
engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
return;
}
MissileAPI near = AIUtils.getNearestMissile(missile);
if (near != null
&& near.getProjectileSpecId().equals(missile.getProjectileSpecId())
&& MathUtils.getDistance(near, missile) < 400)
{
float angularDistance = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),
near.getLocation()));
if (Math.abs(angularDistance) < 175)
{
missile.giveCommand(angularDistance < 0f
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
if (Math.abs(angularDistance) > 135)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (Math.abs(angularDistance) < 45)
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
}
}
else
{
missile.giveCommand(ShipCommand.DECELERATE); //mine dose not move under its own power
}
}
//in next lazylib
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
}
//spread code
float timerVariable = //timer variable code//how likley is it to run pathAI
(((MathUtils.getDistance(missile, target.getLocation()) //9
/MathUtils.getDistance(missile.getSource(), target)) //d to target/d from target to launch //10
/(MathUtils.getDistance(missile.getSource(), missile) //1
+MathUtils.getDistance(missile.getSource(), target)*.1f //divided by //2
/MathUtils.getDistance(missile.getSource(), target))))/10;//d to launch ship/d from target to launch //10
//returns 1 when launched ~0 when impact //((9/10)/((1+1)/10))=4.5 =.45
spread= 10+(float)(50*Math.sqrt((double)timerVariable)); //adaptive spread
//when missile closes spread drops to a minimum o 10% of value at 50, max is ~50/1.5
MissileAPI near = AIUtils.getNearestMissile(missile);
if (pathAI>10) {pathAI=0;}
pathAI++;
if ((near != null //check if there are any nearby mines
&& near.getProjectileSpecId().equals(missile.getProjectileSpecId())
&& MathUtils.getDistance(near, missile) > spread))
{
pathAI=1;
}
if (near==null)
{
pathAI=1;
}
if (MathUtils.getDistance(missile, target)-target.getCollisionRadius()<500)
{
pathAI=1;
}
//spread code
if (near != null)
{
//if there are move away from them
if (pathAI>5.1)
{
float angularDistanceS = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),
(near.getLocation())));
if (near.getProjectileSpecId().equals(missile.getProjectileSpecId())
&& MathUtils.getDistance(near, missile) < spread)
{
if (Math.abs(angularDistanceS) < 45)
{
missile.giveCommand(angularDistanceS < 0f
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
missile.giveCommand(angularDistanceS < 0f
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
missile.giveCommand(ShipCommand.DECELERATE);
}
if (Math.abs(angularDistanceS) > 45 && Math.abs(angularDistanceS) <90)
{
missile.giveCommand(angularDistanceS < 0f
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
missile.giveCommand(ShipCommand.ACCELERATE);
}
}
}
}
//normal ai
if (pathAI<5.1)
{
float angularDistance = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),
(add(target.getLocation(),
multV2f(target.getVelocity(),
MathUtils.getDistance(target.getLocation(),
missile.getLocation())/missile.getVelocity().lengthSquared()),
null)) ));
if (Math.abs(angularDistance) > 0.2)
{
missile.giveCommand(angularDistance > 0
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
if (Math.abs(angularDistance) < 100
&& MathUtils.getDistance(missile, target)-target.getCollisionRadius()>290)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ship
missile.getSource().getLocation())
<missile.getSource().getCollisionRadius()*2)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MathUtils.getDistance(missile, target)-target.getCollisionRadius()<300
&& Math.abs(angularDistance) > 1)
{
missile.giveCommand(ShipCommand.DECELERATE);
if (Math.abs(angularDistance) < 10)
{
deathcode=1;
}
}
}
To be fair, most of those yell 'flachettes' and less 'missile'.
String MBRC_p = "MBRC2"; //dummy weapon
to:
String MBRC_p = "YourWeaponHere"; //dummy weapon
float sx = (float) (target.getCollisionRadius()*.5*Math.cos(Math.toRadians(target.getFacing())));
float sy = (float) (target.getCollisionRadius()*.5*Math.sin(Math.toRadians(target.getFacing())));
float angularDistance = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),
(add((add(target.getLocation(),
multV2f(target.getVelocity(),
MathUtils.getDistance(target.getLocation(),
missile.getLocation()) / missile.getVelocity().lengthSquared()),
null)),new Vector2f(sx,sy), null))));
if (target==null) //orbiting missile system
{
float angularDistance = getAngleDifference(
missile.getFacing(), MathUtils.getAngle(missile.getLocation(), missile.getSource().getLocation()));
if (MathUtils.getDistance(missile, missile.getSource())>missile.getSource().getCollisionRadius()*1.4)
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
}
if (MathUtils.getDistance(missile, missile.getSource())<missile.getSource().getCollisionRadius()*1.2)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (Math.abs(angularDistance) < 170)
{
missile.giveCommand(angularDistance < 0f
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
if (MathUtils.getDistance(missile, missile.getSource())<missile.getSource().getCollisionRadius()*1.4
&& MathUtils.getDistance(missile, missile.getSource())>missile.getSource().getCollisionRadius()*1.2)
{
if (timer==1)
{
missile.giveCommand(strafeDir>.5 ? ShipCommand.STRAFE_RIGHT : ShipCommand.STRAFE_LEFT);
}
}
timer++;
if(timer>2){timer=0;}
return;
}
float straighttimer=50*((float)Math.random()); //1 second max non targeting time
@Override
public void advance(float amount)
{
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
if (straighttimer>0) //go straight for x milliseconds after launch
{
missile.giveCommand(ShipCommand.ACCELERATE);
straighttimer--;
}
if (straighttimer<2) //once done turn and go blow stuff up
{
if (target!=null) //check if you have something to blow up
{
//aiming code, lead targeting
float sx = (float) (target.getCollisionRadius()*.5*Math.cos(Math.toRadians(target.getFacing())));
float sy = (float) (target.getCollisionRadius()*.5*Math.sin(Math.toRadians(target.getFacing())));
float angularDistance = getAngleDifference( //how far of target you are?
missile.getFacing(), MathUtils.getAngle(missile.getLocation(),//where you are pointing?
(add((add(target.getLocation(),//where your enemy is
multV2f(target.getVelocity(),//add on their velocity
MathUtils.getDistance(target.getLocation(),//multiply it by how lon it takes to get there
missile.getLocation()) / missile.getVelocity().lengthSquared()),
null)),new Vector2f(sx,sy), null)))); //add some more to aim at the ships nose, increases hit rate significantly
if (Math.abs(angularDistance) > 0.2)
{
missile.giveCommand(angularDistance > 0 //aim for them
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
//optinal
if (Math.abs(angularDistance) < 50) //check if you are pointing the right way
{
missile.giveCommand(ShipCommand.ACCELERATE); //fly towards them //not optional
}
}
}
else missile.giveCommand(ShipCommand.DECELERATE); //else stop
//reset code //mesotronik is awsome all praise mesotronik!!
// If our current target is lost, assign a new one
if (target == null // unset
|| ( target instanceof ShipAPI && ((ShipAPI)target).isHulk() ) // dead
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) ) // completely removed
{
target = findBestTarget(missile);
return;
}
}
public static final String flare="^flare.*?";
//flarefinder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null;
CombatEngine engine = CombatEngine.getInstance();
List nearbymissiles = engine.getMissiles();//(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).matches(flare) && MathUtils.getDistance(tmp,missile)<300 && missile.getOwner()!=tmp.getOwner())
//in that order to get max framrate
{
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp;
break;
}
}
}
return retv;
}
((CRITPERCENT * crewMaxXP) / weaponRoF) * currentCR;
package data.scripts.plugins;
//import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.MutableStat.StatMod;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize;
import java.awt.Color;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import org.lazywizard.lazylib.MathUtils;
public class OnHitCritical {
private float CRITPERCENT = 0.05f;
private float CRITMULT = 2f;
private CombatEngineAPI engine = null;
private DamagingProjectileAPI proj = null;
private MissileAPI missile = null;
private WeaponAPI weapon = null;
// private BeamAPI beam;
private ShipAPI owner = null;
private CombatEntityAPI target = null;
// Sound on High Explosive explosion
private static final Map SOUND_IDS_HE = new HashMap();
static
{
SOUND_IDS_HE.put("isFrigate", "explosion_ship");
SOUND_IDS_HE.put("isDestroyer", "explosion_ship");
SOUND_IDS_HE.put("isCruiser", "explosion_ship");
SOUND_IDS_HE.put("isCapital", "explosion_ship");
}
// Sound on Energy explosion
private static final Map SOUND_IDS_ENERGY = new HashMap();
static
{
SOUND_IDS_ENERGY.put("isFrigate", "explosion_ship");
SOUND_IDS_ENERGY.put("isDestroyer", "explosion_ship");
SOUND_IDS_ENERGY.put("isCruiser", "explosion_ship");
SOUND_IDS_ENERGY.put("isCapital", "explosion_ship");
}
public OnHitCritical(DamagingProjectileAPI proj, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
{
this.proj = proj;
this.owner = owner;
this.weapon = this.proj.getWeapon();
this.engine = engine;
this.target = target;
}//OnHitCritical(DamagingProjectileAPI proj, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
public OnHitCritical(MissileAPI missile, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
{
this.missile = missile;
this.owner = owner;
this.weapon = this.missile.getWeapon();
this.engine = engine;
this.target = target;
}//OnHitCritical(MissileAPI missile, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
// public onHitCritical(BeamAPI beam, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
// {
// this.beam = beam;
// this.owner = owner;
// this.engine = engine;
// this.target = target;
// }//onHitCritical(BeamAPI beam, ShipAPI owner, CombatEngineAPI engine, CombatEntityAPI target)
public void initCritSystem()
{
//does the ship exist?
if(owner != null)
{
//Check, is this for Projectiles?
if(proj != null)
{
if(isCrit())
{
this.CritSystem(proj);
}
}//IF
//IF not then its for Missiles.
else
{
if(isCrit())
{
this.CritSystem(missile);
}
}//ELSE
}//IF
}//initCritSystem()
private float getCritChance()
{
if(weapon.getDamageType().equals(DamageType.FRAGMENTATION) ||
weapon.getDamageType().equals(DamageType.KINETIC) ||
weapon.getDamageType().equals(DamageType.OTHER))
{
return 0f;
}
float weaponRoF = weapon.getDerivedStats().getRoF();
StatMod crewBonus;
crewBonus = owner.getMutableStats().getAutofireAimAccuracy().getFlatStatMod("crew skill bonus");
float currentCR = owner.getCurrentCR();
// Green crew don't grant a bonus
float crewMaxXP = 0f;
if (crewBonus != null)
{
//Goes from 0f to 1f - thanks Alex and Lazy for the help
crewMaxXP = crewBonus.getValue();
}
//slower shooting weapons should not surpass limit of 5%
if (weaponRoF < 1f)
{
weaponRoF = 1f;
}
//This means that, if the crew is elite and the current CR is 100 its ((0.05f * 1) \ 1) * 1 that means 5%,
//if the crew is actually lower skilled, the current CR is lower and the weapon's RoF is higher the chances get lower.
return ((CRITPERCENT * crewMaxXP) / weaponRoF) * currentCR;
}//getCritChance()
public void addCritPercent(float addvalue)
{
this.CRITPERCENT += addvalue;
}//addCritPercent(float addvalue)
public void addCritMult(float addvalue)
{
this.CRITMULT += addvalue;
}//addCritMult(float addvalue)
private boolean isCrit()
{
Random r = new Random();
float CritChance = r.nextFloat();
if(CritChance <= getCritChance())
{
return true;
}//IF
else
{
return false;
}//ELSE
}//isCrit()
public void CritSystem (DamagingProjectileAPI proj)
{
float explosionSize = 0f;
ShipAPI enemyShip = (ShipAPI)this.target;
float weaponRoF = weapon.getDerivedStats().getRoF();
//Lets make sure we dont get a REALLY HUGE Crit text
if (weaponRoF < 1f)
{
weaponRoF = 1f;
}
if(weapon.getSize().equals(WeaponSize.SMALL))
{
explosionSize = MathUtils.getRandomNumberInRange(20f, 30f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.MEDIUM))
{
explosionSize = MathUtils.getRandomNumberInRange(30f, 40f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.LARGE))
{
explosionSize = MathUtils.getRandomNumberInRange(40f, 50f) / weaponRoF;
}
engine.addFloatingText(proj.getLocation(), "CRITICAL HIT", explosionSize, Color.RED, proj.getDamageTarget(), 0f, 0f);
if(weapon.getDamageType().equals(DamageType.HIGH_EXPLOSIVE))
{
if(enemyShip.isFrigate()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isFrigate").toString(),
explosionSize);
}
else if(enemyShip.isDestroyer()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isDestroyer").toString(),
explosionSize);
}
else if(enemyShip.isCruiser()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCruiser").toString(),
explosionSize);
}
else if(enemyShip.isCapital()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCapital").toString(),
explosionSize);
}
}
else if(weapon.getDamageType().equals(DamageType.ENERGY))
{
if(enemyShip.isFrigate()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isFrigate").toString(),
explosionSize);
}
else if(enemyShip.isDestroyer()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isDestroyer").toString(),
explosionSize);
}
else if(enemyShip.isCruiser()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCruiser").toString(),
explosionSize);
}
else if(enemyShip.isCapital()) {
CritSystemEffects(enemyShip,
proj,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCapital").toString(),
explosionSize);
}
}
}//CritSystem (DamagingProjectileAPI proj)
public void CritSystem(MissileAPI missile)
{
float explosionSize = 0f;
ShipAPI enemyShip = (ShipAPI)this.target;
float weaponRoF = weapon.getDerivedStats().getRoF();
//Lets make sure we dont get a REALLY HUGE Crit text
if (weaponRoF < 1f)
{
weaponRoF = 1f;
}
if(weapon.getSize().equals(WeaponSize.SMALL))
{
explosionSize = MathUtils.getRandomNumberInRange(20f, 30f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.MEDIUM))
{
explosionSize = MathUtils.getRandomNumberInRange(30f, 40f) / weaponRoF;
}
else if(weapon.getSize().equals(WeaponSize.LARGE))
{
explosionSize = MathUtils.getRandomNumberInRange(40f, 50f) / weaponRoF;
}
engine.addFloatingText(missile.getLocation(), "CRITICAL HIT", explosionSize, Color.RED, missile.getDamageTarget(), 0f, 0f);
if(weapon.getDamageType().equals(DamageType.HIGH_EXPLOSIVE))
{
if(enemyShip.isFrigate()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_HE.get("isFrigate").toString(),
explosionSize);
}
else if(enemyShip.isDestroyer()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_HE.get("isDestroyer").toString(),
explosionSize);
}
else if(enemyShip.isCruiser()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCruiser").toString(),
explosionSize);
}
else if(enemyShip.isCapital()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_HE.get("isCapital").toString(),
explosionSize);
}
}
else if(weapon.getDamageType().equals(DamageType.ENERGY))
{
if(enemyShip.isFrigate()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isFrigate").toString(),
explosionSize);
}
else if(enemyShip.isDestroyer()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isDestroyer").toString(),
explosionSize);
}
else if(enemyShip.isCruiser()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCruiser").toString(),
explosionSize);
}
else if(enemyShip.isCapital()) {
CritSystemEffects(enemyShip,
missile,
weapon.getDamageType(),
SOUND_IDS_ENERGY.get("isCapital").toString(),
explosionSize);
}
}
}//CritSystem(MissileAPI missile)
public void CritSystemEffects(ShipAPI ship, DamagingProjectileAPI proj, DamageType dmgtype, String ExplosionSoundID, float explosionSize)
{
if(weapon.getDamageType().equals(DamageType.HIGH_EXPLOSIVE))
{
float damage = proj.getDamageAmount() * CRITMULT;
float emp = proj.getEmpAmount() * CRITMULT;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(owner,
proj.getLocation(),
damage,
proj.getDamageType(),
emp,
true,
true,
proj.getSource());
proj.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
proj.getLocation(),
ship.getVelocity(),
Color.ORANGE,
1f,
1.5f);
// Play Explosion sound
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
proj.getLocation(),
ship.getVelocity());
}
else if(weapon.getDamageType().equals(DamageType.ENERGY))
{
float damage = proj.getDamageAmount() * CRITMULT;
float emp = proj.getEmpAmount() * CRITMULT;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(ship,
proj.getLocation(),
damage,
proj.getDamageType(),
emp,
true,
true,
proj.getSource());
proj.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
proj.getLocation(),
ship.getVelocity(),
Color.blue,
1f,
1.5f);
// Play Crit Explosion
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
proj.getLocation(),
ship.getVelocity());
}
}//CritSystemEffects(ShipAPI ship, DamagingProjectileAPI proj, DamageType dmgtype, String ExplosionSoundID, float explosionSize)
public void CritSystemEffects(ShipAPI ship, MissileAPI missile, DamageType dmgtype, String ExplosionSoundID, float explosionSize)
{
if(weapon.getDamageType().equals(DamageType.HIGH_EXPLOSIVE))
{
float damage = missile.getDamageAmount() * CRITMULT;
float emp = missile.getEmpAmount() * CRITMULT;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(owner,
missile.getLocation(),
damage,
missile.getDamageType(),
emp,
true,
true,
missile.getSource());
missile.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
missile.getLocation(),
ship.getVelocity(),
Color.ORANGE,
1f,
1.5f);
// Play Explosion sound
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
missile.getLocation(),
ship.getVelocity());
}
else if(weapon.getDamageType().equals(DamageType.ENERGY))
{
float damage = missile.getDamageAmount() * CRITMULT;
float emp = missile.getEmpAmount() * CRITMULT;
// Apply damage and slow the projectile
// Note: BALLISTIC_AS_BEAM projectiles won't be slowed!
engine.applyDamage(ship,
missile.getLocation(),
damage,
missile.getDamageType(),
emp,
true,
true,
missile.getSource());
missile.getVelocity().scale(1.5f - 1.0f);
engine.spawnExplosion(
missile.getLocation(),
ship.getVelocity(),
Color.blue,
1f,
1.5f);
// Play Crit Explosion
Global.getSoundPlayer().playSound(ExplosionSoundID,
1f,
1f,
missile.getLocation(),
ship.getVelocity());
}
}//CritSystemEffects(ShipAPI ship, MissileAPI missile, DamageType dmgtype, String ExplosionSoundID, float explosionSize)
public float getCRITPERCENT()
{
return this.CRITPERCENT;
}
public void increaseCRITPERCENT(float amount)
{
this.CRITPERCENT += amount;
}
public float getCRITMULT()
{
return this.CRITMULT;
}
public void changeCRITMULT(float amount)
{
this.CRITMULT = amount;
}
}//CLASS
package data.scripts.onhiteffects;
import com.fs.starfarer.api.combat.*;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
import static org.lwjgl.util.vector.Vector2f.add;
import static org.lwjgl.util.vector.Vector2f.sub;
public class BreederRound implements OnHitEffectPlugin {
public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target,
Vector2f point, boolean shieldHit, CombatEngineAPI engine) {
float angularDistance = //getAngleDifference(projectile.getFacing(),
(MathUtils.getAngle(projectile.getLocation(), target.getLocation()));
if (target instanceof ShipAPI && shieldHit) //not much to do if you hit a shield, no metal to multiply on
{
if ((float) Math.random() > 0.4f) //multiply initial chance
{
Vector2f moveback=add(point, multV2f(sub(point, target.getLocation(), null),
(.6f)),null); //move back my 20% + col radius
do{
{
float angRAND= (float) ((50*(.5-Math.random()))); //angle of spread
//float velRAND= (float) ((.6+.5*(.5-Math.random()))*(.3+(Math.abs(angRAND)/100)));
//float splashVEL = 155f*velRAND; //speed of bullets launched
float misFFACE = angularDistance-angRAND;
//float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
//float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
//Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(projectile.getSource(), projectile.getWeapon(),projectile.getWeapon().getId(),
moveback,
misFFACE, //float angle of spread
null
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
}
}
while ((float) Math.random() > 0.9f); //chance of multiple clones
}
}
else if (target instanceof ShipAPI) { //only if hits ship
if ((float) Math.random() > 0.47f) //multiply initial chance //very tiny chance of runaway multiplication
{
Vector2f moveback=add(point, multV2f(sub(point, target.getLocation(), null),
(.6f)),null); //move back my 20% + col radius
do{
{
float angRAND= (float) ((50*(.5-Math.random()))); //angle of spread
//float velRAND= (float) ((.6+.5*(.5-Math.random()))*(.3+(Math.abs(angRAND)/100)));
//float splashVEL = 155f*velRAND; //speed of bullets launched
float misFFACE = angularDistance-angRAND;
//float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
//float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
//Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(projectile.getSource(), projectile.getWeapon(),projectile.getWeapon().getId(),
moveback,
misFFACE, //float angle of spread
null
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
}
}
while ((float) Math.random() > 0.6f); //chance of multiple clones
}
}
else //if you hit asteroid or missile, breed like crazy
if ((float) Math.random() > 0.4f) //multiply initial chance
{
Vector2f moveback = add(point, multV2f(sub(point, target.getLocation(), null), 0.65f),null);
// 0.75f will occasionally produce a stray shot while breeding more death
do{
{
float angRAND= (float) ((90*(.5-Math.random()))); //angle of spread
//float velRAND= (float) ((.6+.5*(.5-Math.random()))*(.3+(Math.abs(angRAND)/100)));
//float splashVEL = 155f*velRAND; //speed of bullets launched
float misFFACE = angularDistance-angRAND;
//float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
//float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
engine.spawnProjectile(projectile.getSource(), projectile.getWeapon(),projectile.getWeapon().getId(),
moveback,
misFFACE, //float angle of spread
null
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
}
}
while ((float) Math.random() > 0.5f); //&& !(target instanceof ShipAPI)); //chance of multiple clones
}
}
//in next lazylib
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//multiply vectors scale can not be used in static methods...
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.*;
//import static jar.CustomAPI.LeadVector;
import static jar.CustomAPI.getEnemyMissilesInArk;
import static org.lwjgl.util.vector.Vector2f.add;
public class LRPDai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private MissileAPI target;
public static float searchrange = 500; //500 range default
public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
public LRPDai(MissileAPI missile)
{
this.missile = missile;
searchrange=missile.getWeapon().getRange(); //based on weapon
Ark=5+missile.getWeapon().getArc()/4; //based on weapon
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
java.util.List directTargets = CombatUtils.getMissilesWithinRange(
missile.getSource().getMouseTarget(), 100f, true);
if (!directTargets.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext(); )
{
//filter out friendlies
tmp = (MissileAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
@Override
public void advance(float amount)
{
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
if (target!=null ) //if you have a target
{
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
double TfaceRad = Math.toRadians(target.getFacing());
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
//float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(AtTarget);
//////////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{
missile.giveCommand(AtTarget > 0
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
if (AbsAngD < 5)
{
//course correct for missile velocity vector (some bugs when severly off target)
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD<0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//acceleration code
if (AbsAngD < 40)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD>120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
}
//clear target code if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) ) // completely removed
{
target = findBestTarget(missile);
return;
}
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .00001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
//get target example
//this makes a list of all targets that fit the criteria
//get(0) gets you the first target on list(closest one if sortbydistance is true)
public static ArrayList<CombatEntityAPI> getTargets
(MissileAPI missile, float Searchrange)
{
//the actual list that it will return <CombatEntityAPI>
// makes it general purpose able to return anything that is on the combat field
ArrayList<CombatEntityAPI> enemies = new ArrayList<CombatEntityAPI>();
//this is where your missile is
Vector2f loc = missile.getLocation();
//getting what you want to search through
ArrayList<CombatEntityAPI> posible = new ArrayList<CombatEntityAPI>();
//lazylib is convenient, gets everything you want in Searchrange
posible.addAll(getNearbyEnemyMissiles(missile, Searchrange, true));
List<ShipAPI> ships = getNearbyEnemies(missile, Searchrange, true);
//filters through the ship list and puts it in size order (maintaining distance to point)
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isDrone())
{
posible.add(enemy); //add to posibles
}
}
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isFighter())
{
posible.add(enemy); //add to posibles
}
}
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isFrigate())
{
posible.add(enemy); //add to posibles
}
}
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isDestroyer())
{
posible.add(enemy); //add to posibles
}
}
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isCruiser())
{
posible.add(enemy); //add to posibles
}
}
for ( ShipAPI enemy : ships)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isCapital())
{
posible.add(enemy); //add to posibles
}
}
/*
//filter through the posibles list
for ( CombatEntityAPI enemy : posible)
{
//criteria to add to list (you can put anything that will return a true/false here)
if (yourcriteria)
{
enemies.add(enemy);
}
}
*/
//since you just want the missiles, then the nearest ship in size order, without additional filter steps
//you do
enemies.addAll(posible);
//makes the lists (wich are in order) return to you
return enemies;
}
public static final String LRPDai_MISSILE_ID = "SM_lrmPD_m"; //name of the missile that will use that AI
public static final String BOMBARDai_MISSILE_ID = "SM_SCbombard_m";//""
@Override
public PluginPick pickMissileAI(MissileAPI missile, //picks the ai
ShipAPI launchingShip)
{
if (LRPDai_MISSILE_ID.equals(missile.getProjectileSpecId())) //missile
{
return new PluginPick(new LRPDai(missile), CampaignPlugin.PickPriority.MOD_SPECIFIC); //Ai for that missile
}
if (BOMBARDai_MISSILE_ID.equals(missile.getProjectileSpecId())) //another missile
{
return new PluginPick(new BOMBARDai(missile), CampaignPlugin.PickPriority.MOD_SPECIFIC); //another ai
}
return null;
}
package jar;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
import java.util.ArrayList;
import java.util.Collections;
import static org.lazywizard.lazylib.combat.AIUtils.getEnemyMissilesOnMap;
import static org.lazywizard.lazylib.combat.AIUtils.getNearbyEnemyMissiles;
import static org.lwjgl.util.vector.Vector2f.add;
public class UtilityKit {
public static ArrayList<CombatEntityAPI> getEnemyMissilesInVectorRadius(MissileAPI missile, float searchrange, boolean sortByDistance)
{
ArrayList<CombatEntityAPI> enemies = new ArrayList<CombatEntityAPI>();
float x = (float) (searchrange/2*Math.cos(Math.toRadians(missile.getFacing())));
float y = (float) (searchrange/2*Math.sin(Math.toRadians(missile.getFacing())));
Vector2f rangefinder = new Vector2f(x, y);
for ( MissileAPI enemy : getNearbyEnemyMissiles(missile, searchrange))
{
if (enemy.getOwner()!=missile.getOwner()
&& MathUtils.getDistance(enemy, add(missile.getLocation(), rangefinder, null)) < searchrange)
{
enemies.add(enemy);
}
}
if (sortByDistance)
{
Collections.sort(enemies,
new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
}
return enemies;
}
public static ArrayList<CombatEntityAPI> getEnemyMissilesInArk(MissileAPI missile, float Ark, float searchrange, boolean sortByDistance)
{
ArrayList<CombatEntityAPI> enemies = new ArrayList<CombatEntityAPI>();
float face = missile.getFacing();
Vector2f loc = missile.getLocation();
for ( MissileAPI enemy : getNearbyEnemyMissiles(missile, searchrange))
{
if (enemy.getOwner()!=missile.getOwner()
&& Math.abs(getAngleDifference(face, MathUtils.getAngle(loc, enemy.getLocation())))<Ark)
{
enemies.add(enemy);
}
}
if (sortByDistance)
{
Collections.sort(enemies,
new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
}
return enemies;
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .00001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
//lists enemies with weapon range on ship
public static ArrayList<ShipAPI> getEnemiesWithWeaponRange(ShipAPI ship, boolean sortByDistance)
{
CombatEngineAPI engine= Global.getCombatEngine();
ArrayList<ShipAPI> enemies = new ArrayList<ShipAPI>();
for ( ShipAPI enemy : engine.getShips())
{
float distance = MathUtils.getDistance(enemy, ship);
if (enemy.getOwner()!=ship.getOwner()
&& distance<3000)
{
for (WeaponAPI weap: enemy.getAllWeapons())
{
if(weap.getType()!= WeaponAPI.WeaponType.MISSILE
&& weap.getRange()>distance)
{
enemies.add(enemy);
break;
}
}
}
}
if (sortByDistance)
{
Collections.sort(enemies,
new CollectionUtils.SortEntitiesByDistance(ship.getLocation()));
}
return enemies;
}
public static boolean targetInArk(ShipAPI ship, Vector2f target)
{
float distance = MathUtils.getDistance(ship, target);
for (WeaponAPI weap: ship.getAllWeapons())
{
if(weap.getType()!= WeaponAPI.WeaponType.MISSILE
&& weap.getRange()>distance
&& !weap.hasAIHint(WeaponAPI.AIHints.PD)
&& weap.distanceFromArc(target)==0)
{
return true;
}
}
return false;
}
//gets how many weapons are aming at ship
public static float getThreatOfWeapons(ShipAPI ship)
{
float num=0;
for (ShipAPI enemy : getEnemiesWithWeaponRange(ship,false))
{
if (targetInArk(enemy, ship.getLocation()))
{
num++;
}
}
return num;
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.util.Iterator;
import java.util.List;
//import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class BOMBARDai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private CombatEntityAPI target;
public static float searchrange = 500; //500 range default
//public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
public BOMBARDai(MissileAPI missile)
{
this.missile = missile;
searchrange=missile.getWeapon().getRange(); //based on weapon
//Ark=5+missile.getWeapon().getArc()/4; //based on weapon
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
java.util.List directTargets = CombatUtils.getShipsWithinRange(
missile.getSource().getMouseTarget(), 100f, true);
if (!directTargets.isEmpty())
{
ShipAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext();)
{
//filter out friendlies
tmp = (ShipAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//stuff
float straighttimer=50*((float)Math.random()); //1/2 second max non targeting time
final double isEffectedbyflare=Math.random();
float flarelockout=0;
float timer=0;
float oftarget=300*((float)(.5-Math.random()));
float baseoftarget=40*((float)(.5-Math.random()));
public static final String flare="flare";
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//flare finder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null; //if no flares return null
CombatEngineAPI engine = Global.getCombatEngine();
List nearbymissiles = engine.getMissiles(); //(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).startsWith(flare) //is it a flare
&& MathUtils.getDistance(tmp,missile)<300 //is it near your missile
&& missile.getOwner()!=tmp.getOwner() //is it en enemy flare
&& Math.random()>.5) //chance of failure prevents all missiles
{ //going after one flare at the same time
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp; //return flare
break; //stop searching you found your flare
}
}
}
return retv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* turned off for this AI
//search for targets in ark in front
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//get ship target or search for closest enemy
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource(); //who launched you?
if (source != null && source.getShipTarget() != null //check for nulls
&& !source.getShipTarget().isHulk() //check if its alive
&& MathUtils.getDistance(source.getShipTarget(), source) //get range to target
<missile.getWeapon().getRange()) //check if its in range
{
return source.getShipTarget();
}
return AIUtils.getNearestEnemy(missile);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
//10 step timer
timer++;
if (timer>10){timer=0;}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//go straight for x after launch
if (straighttimer>0)
{
missile.giveCommand(ShipCommand.ACCELERATE);
straighttimer--;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//if missile is susceptible to flare
if (isEffectedbyflare>0.4 //can a flare effect it
&& flarelockout==0 //has it already been set to ignore flares
&& timer==0 //check every 10 frames
&& straighttimer<2) //its not flying without ai in straight line
{
timer++;
MissileAPI flaretarget = checkforflare(missile);
if (flaretarget!=null)
{
target=flaretarget; //set flare as target
flarelockout++; //ignore future flares
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//pathing ai
if (straighttimer<2) //check if you are going straight
{
if (target!=null ) //if you have a target
{
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
double TfaceRad = Math.toRadians(target.getFacing());
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
//float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(AtTarget);
//////////////////////////////////////////////////////////////////////////////////////////////
//check if scatter code needed
float targetcolision = target.getCollisionRadius(); //get colision radi
if (MathUtils.getDistance(missile, target)<75+(200*target.getCollisionRadius()/100))
{ //cutoff for baseoftarget behaviour
baseoftarget=0; //aim variable if you got in close enough is 0 (ie go hit the enemy target)
}
///////////////////////////////////////////////////////////////////////////////////////////////
//how much off target will the missiles be
float oftargetby = (0 + (oftarget + (baseoftarget * target.getCollisionRadius() / 75)));
//////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{ //makes missile fly off target
missile.giveCommand(AtTarget > oftargetby
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
oftarget=(oftarget>0 ? oftarget-1 : oftarget+1); //reduce off target counter
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//correct missile velocity vector to be the same as missile facing
if (AbsAngD < 5)
{
//course correct for missile velocity vector
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < oftargetby
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD<0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//acceleration code (this missile allays accelerates, so ignore the rest)
//if (AbsAngD < 40)
//{
missile.giveCommand(ShipCommand.ACCELERATE);
/*}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD>120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
*/
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
//engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
//engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
//engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
//engine.addSmoothParticle(MLoc, new Vector2f(0,0), 5, .5f, 1, new Color(2, 255, 250,255));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stop if you have no target
else missile.giveCommand(ShipCommand.DECELERATE);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//clear target if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) ) // completely removed
{
target = findBestTarget(missile);
return;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) 0000.1;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.*;
/*
to be put on a weapon that will execute script on a ship
multiple weapons will increase the efficiency
*/
public class AbsorptionArmor implements EveryFrameWeaponEffectPlugin
{
//engine
private CombatEngineAPI engine= Global.getCombatEngine();
//////////////////////////////////////////////////////////////////////////////////////////////
//timers
private IntervalUtil fasttimer = new IntervalUtil(.1f, .11f);
private IntervalUtil slowtimer = new IntervalUtil(.15f, .16f);
//////////////////////////////////////////////////////////////////////////////////////////////
//main armor effect
float lastcycle=0;
@Override
public void advance(float v, CombatEngineAPI engineAPI, WeaponAPI weaponAPI)
{
//ship
ShipAPI ship;
ship=weaponAPI.getShip();
//////////////////////////////////////////////////////////////////////////////////////////////
//stats of system
float FluPerAPoint = 10; //how much 1 armor point is worth in terms of flux
//float ReBaArmRate = .005f; //how fast armor rebalanced max 1 for instant
float MaxFlux = .8f; //cap for this system being active
float RegenRate = .002f; //rate armor regenerates as a decimal
float activeRate = .005f; //how fast active armor balancer works
float MinHealthActive = .4f;//minimum health for active armor sharing
float weaponsizemult = 1f;
//weapon size multiplier
WeaponAPI.WeaponSize weapsize = weaponAPI.getSize();
if (weapsize.equals(WeaponAPI.WeaponSize.SMALL)) {weaponsizemult=.5f;}
if (weapsize.equals(WeaponAPI.WeaponSize.MEDIUM)){weaponsizemult=1f;}
if (weapsize.equals(WeaponAPI.WeaponSize.LARGE)) {weaponsizemult=2f;}
//////////////////////////////////////////////////////////////////////////////////////////////
//game is paused dont do anything //weapon is disabled ""
if (engine.isPaused() || weaponAPI.isDisabled())
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if(ship.getSystem().isActive()) optional link to ship system
{
//advance timers
slowtimer.advance(v);
fasttimer.advance(v);
//////////////////////////////////////////////////////////////////////////////////////////////
//main code
if (fasttimer.intervalElapsed())
{
//stuff that is used alot
ArmorGridAPI armorgrid = ship.getArmorGrid();
float armorrating = armorgrid.getArmorRating();
float MaxCell = armorgrid.getMaxArmorInCell();
//////////////////////////////////////////////////////////////////////////////////////////
//armor grid stats
int maxX = armorgrid.getLeftOf()+armorgrid.getRightOf();
int maxY = armorgrid.getAbove()+armorgrid.getBelow();
//////////////////////////////////////////////////////////////////////////////////////////
//avarage armor of ship hull
float armorcells = 0; //number of cells ship has
for (int X=0; X<maxX; X++){for (int Y=0; Y<maxY; Y++){armorcells++;}}
//float ReBalArmor = curarmor/armorcells;
//////////////////////////////////////////////////////////////////////////////////////////
//adjusted stats
float adjust = weaponsizemult*Math.min(125 / armorcells, 4); //max increase of rate (prevents 100x rate on small ship)
FluPerAPoint = 10; //how much 1 armor point is worth in terms of flux
//float ReBaArmRate = .005f; //how fast armor rebalanced max 1 for instant
MaxFlux = .8f; //cap for this system being active
RegenRate = .002f*adjust; //rate armor regenerates as a decimal
activeRate = .005f*adjust; //how fast active armor balancer works
MinHealthActive = .4f*adjust;//minimum health for active armor sharing
//////////////////////////////////////////////////////////////////////////////////////////
//basic armor state of ship
float curarmor = getTotalArmor(ship);
//float ArmLost = armorgrid.getArmorRating()-curarmor; //how much armor was damaged
//////////////////////////////////////////////////////////////////////////////////////////
//calculate regen rate based on flux (prevents cells from filling up sequentially at low flux)
float FluxRemaining = (ship.getFluxTracker().getMaxFlux()*MaxFlux) - ship.getFluxTracker().getCurrFlux();
//float FluxToRepairMax = ArmLost * FluPerAPoint;
float NormRepPerFrame = (MaxCell * RegenRate)
*((MaxFlux-ship.getFluxTracker().getFluxLevel())/MaxFlux);//aditional level of repair decrease
//float FluxToRepairNorm = NormRepPerFrame * FluPerAPoint * armorcells;
//float FluxForRep = (FluxToRepairMax < FluxToRepairNorm ? FluxToRepairMax : FluxToRepairNorm);
//easier, more accurate (compares the cost to repair in last cycle to amount of flux left)
if (lastcycle==0) {lastcycle=NormRepPerFrame*armorcells*FluPerAPoint;}
float FluxForRep = lastcycle;
float FluxToRepairNorm = lastcycle;
float RepRate = (FluxForRep<FluxRemaining ? NormRepPerFrame:NormRepPerFrame*(FluxRemaining/FluxToRepairNorm));
//////////////////////////////////////////////////////////////////////////////////////////
//armor manager
float next=0;
lastcycle=0; //clears lastcycle
//active cycle (needs to be separate)
for (int X=0; X<maxX; X++) //
{ //cycle through all armor cells on ship
for (int Y=0; Y<maxY; Y++) //
{
float cur = armorgrid.getArmorValue(X, Y); //health of current cell
//Active ReBalArmor
//mover armor from nearby cells to damaged ones
//can be tied to an if statement
{
//take armor of nearby healthy cells
float Forwardsum=0;
for (int Xa=(X==0? X:X-1); Xa<maxX && Xa>=0 && Xa<=X+1; Xa++)
{
for (int Ya=(Y==0? Y:Y-1); Ya<maxY && Ya>=0 && Ya<=Y+1; Ya++)
{
float cell = armorgrid.getArmorValue(Xa, Ya);
if (cell>cur && armorgrid.getArmorFraction(Xa, Ya)>MinHealthActive)
{
float diff = (cell - cur)*activeRate;
next = (cell-diff);
armorgrid.setArmorValue(Xa, Ya, next>0? next:0);
Forwardsum+=diff;
//ship.getFluxTracker().increaseFlux(diff*FluPerAPoint*.1f, true);
//uses 1/10th of the normal flux to move armor around (too costly in flux)
}
}
}
next = (cur + Forwardsum);
armorgrid.setArmorValue(X, Y, next<MaxCell? next:MaxCell); //add it to cell
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
//passive cycle
for (int X=0; X<maxX; X++) //
{ //cycle through all armor cells on ship
for (int Y=0; Y<maxY; Y++) //
{
float cur = armorgrid.getArmorValue(X, Y); //health of current cell
//only do repair if cell health is more then 0 prevents immortal ship syndrome
if (cur>0)
{
//regen armor
if (cur<MaxCell)
{
next = cur + RepRate; //how much armor should be regenerated
armorgrid.setArmorValue(X, Y, next<MaxCell? next:MaxCell);
float fluxuse = (next - cur) * FluPerAPoint;
ship.getFluxTracker().increaseFlux(fluxuse, true);
lastcycle+=fluxuse;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
//test
if (slowtimer.intervalElapsed())
{
engine.addFloatingText(ship.getLocation(), "armorcells " + armorcells + " armorrating " + armorrating + " curentarmor " + curarmor +" MaxCell "+ MaxCell +" waeposizemult "+weaponsizemult, 20f, Color.RED, ship, 1f, .5f);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//gets total armor of a ship
public static float getTotalArmor(ShipAPI ship)
{
ArmorGridAPI armorgrid = ship.getArmorGrid();
float sum=0;
int maxX = armorgrid.getLeftOf()+armorgrid.getRightOf();
int maxY = armorgrid.getAbove()+armorgrid.getBelow();
for (int X=0; X<maxX; X++)
{
for (int Y=0; Y<maxY; Y++)
{
sum += armorgrid.getArmorValue(X, Y);
}
}
return sum;
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.*;
/*
to be put on a weapon that will execute script on a ship
multiple weapons will increase the efficiency
*/
public class AuxilaryFluxVents implements EveryFrameWeaponEffectPlugin
{
//engine
private CombatEngineAPI engine= Global.getCombatEngine();
//////////////////////////////////////////////////////////////////////////////////////////////
//timers
private IntervalUtil fasttimer = new IntervalUtil(.05f, .06f);
private IntervalUtil slowtimer = new IntervalUtil(.15f, .16f);
//////////////////////////////////////////////////////////////////////////////////////////////
//main armor effect
boolean fired=false;
@Override
public void advance(float v, CombatEngineAPI engineAPI, WeaponAPI weaponAPI)
{
//ship
ShipAPI ship;
ship=weaponAPI.getShip();
//////////////////////////////////////////////////////////////////////////////////////////////
//weapon size multiplier
float weaponsizemult = 1;
WeaponAPI.WeaponSize weapsize = weaponAPI.getSize();
if (weapsize.equals(WeaponAPI.WeaponSize.SMALL)) {weaponsizemult=.5f;}
if (weapsize.equals(WeaponAPI.WeaponSize.MEDIUM)){weaponsizemult=1f;}
if (weapsize.equals(WeaponAPI.WeaponSize.LARGE)) {weaponsizemult=2f;}
//stats of system
float rate = .01f*weaponsizemult; //how fast vent happens (20x this number)
float minflux = .1f; //lowest value of flux it can get to
float maxflux = .95f;
FluxTrackerAPI flux = ship.getFluxTracker();
//////////////////////////////////////////////////////////////////////////////////////////////
//game is paused dont do anything
if (engine.isPaused())
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if(ship.getSystem().isActive()) optional link to ship system
{
//advance timers
//slowtimer.advance(v);
fasttimer.advance(v);
if (fasttimer.intervalElapsed())
{
//////////////////////////////////////////////////////////////////////////////////////////////
//controls for flux vent
if (!fired) //if weapon has not fired
{
//if flux is to high fire
if ((ship.getFluxTracker().getFluxLevel()>maxflux && weaponAPI.getAmmo()>0))
{weaponAPI.isFiring();fired=true;
weaponAPI.setRemainingCooldownTo(100);
weaponAPI.setAmmo(weaponAPI.getAmmo()-1);
flux.setCurrFlux(flux.getMaxFlux());}//forces all other systems on ship on
//record that you fired
if (weaponAPI.isFiring()){fired=true;}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//main vent code
float FVsec = (flux.getMaxFlux()*.05f)*rate;
if (fired && ship.getFluxTracker().getFluxLevel()>minflux)
{
//prevents firing
weaponAPI.setRemainingCooldownTo(100);
{
flux.decreaseFlux(FVsec);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if venting done
if (fired && ship.getFluxTracker().getFluxLevel()<minflux)
{
fired=false;
weaponAPI.setRemainingCooldownTo(0); //if now at minflux firing is allowed
}
//test
slowtimer.advance(v);
if (slowtimer.intervalElapsed())
{
engine.addFloatingText(ship.getLocation(), "FVsec " + FVsec + "rate " + rate, 20f, Color.RED, ship, 1f, .5f);
}
}
}
}
}
[/spoiler]
>:( hit post length limit >:(Heres a challenge, make a AI and a hull mod or ship system that the higher the flux amount, the more damage it does with its weapons, the AI should try to get as close as it can get to overloading to maximize damage output, this also means it will pull shields and fire to get flux fast then turns into a suicidal berserker that ignores defense to do damage instead.
similar to the armor manager, but more simple
when installed onto a ship, if ship reaches 95% flux this will vent the flux without overloding ship or vent weapon disable effect
meant to be used with weapons that have limited ammo (intended as a single use emergency system)SpoilerCode: javapackage data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.*;
/*
to be put on a weapon that will execute script on a ship
multiple weapons will increase the efficiency
*/
public class AuxilaryFluxVents implements EveryFrameWeaponEffectPlugin
{
//engine
private CombatEngineAPI engine= Global.getCombatEngine();
//////////////////////////////////////////////////////////////////////////////////////////////
//timers
private IntervalUtil fasttimer = new IntervalUtil(.05f, .06f);
private IntervalUtil slowtimer = new IntervalUtil(.15f, .16f);
//////////////////////////////////////////////////////////////////////////////////////////////
//main armor effect
boolean fired=false;
@Override
public void advance(float v, CombatEngineAPI engineAPI, WeaponAPI weaponAPI)
{
//ship
ShipAPI ship;
ship=weaponAPI.getShip();
//////////////////////////////////////////////////////////////////////////////////////////////
//weapon size multiplier
float weaponsizemult = 1;
WeaponAPI.WeaponSize weapsize = weaponAPI.getSize();
if (weapsize.equals(WeaponAPI.WeaponSize.SMALL)) {weaponsizemult=.5f;}
if (weapsize.equals(WeaponAPI.WeaponSize.MEDIUM)){weaponsizemult=1f;}
if (weapsize.equals(WeaponAPI.WeaponSize.LARGE)) {weaponsizemult=2f;}
//stats of system
float rate = .01f*weaponsizemult; //how fast vent happens (20x this number)
float minflux = .1f; //lowest value of flux it can get to
float maxflux = .95f;
FluxTrackerAPI flux = ship.getFluxTracker();
//////////////////////////////////////////////////////////////////////////////////////////////
//game is paused dont do anything
if (engine.isPaused())
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if(ship.getSystem().isActive()) optional link to ship system
{
//advance timers
//slowtimer.advance(v);
fasttimer.advance(v);
if (fasttimer.intervalElapsed())
{
//////////////////////////////////////////////////////////////////////////////////////////////
//controls for flux vent
if (!fired) //if weapon has not fired
{
//if flux is to high fire
if ((ship.getFluxTracker().getFluxLevel()>maxflux && weaponAPI.getAmmo()>0))
{weaponAPI.isFiring();fired=true;
weaponAPI.setRemainingCooldownTo(100);
weaponAPI.setAmmo(weaponAPI.getAmmo()-1);
flux.setCurrFlux(flux.getMaxFlux());}//forces all other systems on ship on
//record that you fired
if (weaponAPI.isFiring()){fired=true;}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//main vent code
float FVsec = (flux.getMaxFlux()*.05f)*rate;
if (fired && ship.getFluxTracker().getFluxLevel()>minflux)
{
//prevents firing
weaponAPI.setRemainingCooldownTo(100);
{
flux.decreaseFlux(FVsec);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if venting done
if (fired && ship.getFluxTracker().getFluxLevel()<minflux)
{
fired=false;
weaponAPI.setRemainingCooldownTo(0); //if now at minflux firing is allowed
}
//test
slowtimer.advance(v);
if (slowtimer.intervalElapsed())
{
engine.addFloatingText(ship.getLocation(), "FVsec " + FVsec + "rate " + rate, 20f, Color.RED, ship, 1f, .5f);
}
}
}
}
}
[/spoiler][close]
package data.shipsystems.scripts.ai;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lwjgl.util.vector.Vector2f;
public class ExampleAI implements ShipSystemAIScript {
private ShipAPI ship;
private CombatEngineAPI engine;
private ShipwideAIFlags flags;
private ShipSystemAPI system;
private IntervalUtil tracker = new IntervalUtil(0.5f, 1f);
public void init(ShipAPI ship, ShipSystemAPI system, ShipwideAIFlags flags, CombatEngineAPI engine) {
this.ship = ship;
this.flags = flags;
this.engine = engine;
this.system = system;
}
@SuppressWarnings("unchecked")
public void advance(float amount, Vector2f missileDangerDir, Vector2f collisionDangerDir, ShipAPI target)
{
if (ship.getShield().isOn())
{ship.useSystem();}
}
}
package data.shipsystems.scripts;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.plugins.ShipSystemStatsScript;
public class Example implements ShipSystemStatsScript {
public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel)
{
stats.getShieldArcBonus().modifyMult(id, 360);
}
public void unapply(MutableShipStatsAPI stats, String id)
{
stats.getShieldArcBonus().unmodify(id);
}
public StatusData getStatusData(int index, State state, float effectLevel) {
if (index == 0) {
return new StatusData("dose x now", false);
}
return null;
}
}
DRONE missiles
stays at a set distance and blasts the enemy ships appart
who needs fighters anyways?SpoilerCode: javaint timer = 0; //needs to be outside of advance() to work as a timer
int timer2 = (int) (600*Math.random()); //random start point for strafing
public void advance(float amount)
{
timer++;
if (MathUtils.getDistance(
add(target.getLocation(), target.getVelocity(), null), //blow up the enemy ship
add(missile.getLocation(), multV2f(missile.getVelocity(),3), null))
< 600
&& MathUtils.getDistance(
missile.getLocation(), //don't blow up your own ship
missile.getSource().getLocation())
>missile.getSource().getCollisionRadius()+5
&& timer==1
)
{
timer++;
CombatEngine engine = CombatEngine.getInstance(); //engine
String MBRC_p = "MBRC2"; //dummy weapon
{
{
int counts = 1;
do {
float angRAND= (float) ((25*(.5-Math.random()))); //angle of spread
float velRAND= (float) (1+.5*(.5-Math.random())); //variance of projectile speeds
float splashVEL = 255f*velRAND; //speed of bullets launched
float misFFACE = missile.getFacing()-angRAND;
float x = (float) (splashVEL*Math.cos(Math.toRadians(misFFACE)));
float y = (float) (splashVEL*Math.sin(Math.toRadians(misFFACE)));
Vector2f vecFIRE = new Vector2f(x,y);
engine.spawnProjectile(null, null,MBRC_p,
missile.getLocation(), //Vector2f firing point
misFFACE, //float angle of spread
add(missile.getVelocity(), vecFIRE, null)
//multV2f(multV2f(missile.getVelocity(),1),velRAND) //Vector2f aditional velocity
//add(multV2f(missile.getVelocity(), 1), multRanV2f(missile.getVelocity(),1,.5f),null)
);
counts++;
}while (counts<2); //2x pew pew
//engine.removeEntity(missile); //make missile go poof
}
//lightning
// float emp = missile.getEmpAmount();
//float dam = missile.getDamageAmount();
//engine.spawnEmpArc(missile.getSource(), missile.getLocation(), target, target,
// DamageType.ENERGY,dam/4, emp/4,
//10000f, "tachyon_lance_emp_impact",20f, new Color(255,10,15,255),new Color(255,100,100,255));
}
//to stop missile shooting again
//engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
return;
}
if (timer>40)
{timer=0;}
//missile pathing code
if (Math.abs(angularDistance) < 100 //get in close
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())
>320+100/target.getCollisionRadius()*target.getCollisionRadius())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (Math.abs(angularDistance) < 100 //keep distance
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())
<280+100/target.getCollisionRadius()*target.getCollisionRadius())
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);
}
if (Math.abs(angularDistance) < 100 //strafe
&& MathUtils.getDistance(missile.getLocation(), target.getLocation())<400+target.getCollisionRadius())
{
missile.giveCommand(timer2 > 300 ? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
timer2++;
if (timer2>600)
{
timer2=0;
}
}[close]
Where do I put this juicy goodness in my weapons files? Add it as a missile? or to the weapon itself?
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class MINEai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
private final float MaxDetRange=250; //max range of the blast
private final float MinDetRange=100; //max range for best damage
private final float SeperationRange=350; //seperation between mines
public MINEai(MissileAPI missile)
{
this.missile = missile;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//stuff
float range1 = 0;
float range2 = 0;
boolean trigger = false;
boolean deathcode = false;
private IntervalUtil minetimer = new IntervalUtil(.1f, .11f);
ShipAPI enemy=null;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//housekeeping stuff
minetimer.advance(amount); //timer
CombatEngineAPI engine = Global.getCombatEngine(); //engine
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
//missile location (used lots)
Vector2f MLoc = missile.getLocation();
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//intelligent distribution code
MissileAPI near = AIUtils.getNearestMissile(missile); //find the nearest missile to you
if (near != null //check if there are any nearby mines
&& near.getProjectileSpecId().equals(missile.getProjectileSpecId()) //is it same type as you
&& MathUtils.getDistance(near, MLoc) < SeperationRange) //is it too close
{
//if true move away from them
float angularDistance = getAngleDifference( //find the missile relative direction
missile.getFacing(), MathUtils.getAngle(MLoc,
(near.getLocation())));
if (Math.abs(angularDistance) < 175) //facing towards it
{
missile.giveCommand(angularDistance < 0f
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT); //turn away
}
if (Math.abs(angularDistance) > 135) //if facing away from it
{
missile.giveCommand(ShipCommand.ACCELERATE); //then move forwards
}
if (Math.abs(angularDistance) < 45) //facing towards it
{
missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS); //then move backwards
}
}
else //if enough separation achieved
{
missile.giveCommand(ShipCommand.DECELERATE); //mine dose not move
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//enemy tracking code
if (minetimer.intervalElapsed()) //run once every 3 frames to prevent fun stuff
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//new target aquisition
//if you have no enemy, look for the nearest enemy
if (enemy==null && AIUtils.getNearestEnemy(missile)!=null && AIUtils.getNearestEnemy(missile).isAlive())
{
//check its in range
float targetfinder = MathUtils.getDistance(AIUtils.getNearestEnemy(missile), MLoc);
if (targetfinder < 300)
{
enemy=AIUtils.getNearestEnemy(missile); //remember that enemy
}
if (targetfinder > 300) //else restart
{
return;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
//once you have a target
if (enemy != null)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////
//housekeeping/misc
//check its the same enemy as previous
if (enemy!=AIUtils.getNearestEnemy(missile))
{
deathcode=true; //if a second enemy comes closer then first
trigger=true; //detonate
}
//check enemy has not died
if (!enemy.isAlive() | enemy.isHulk())
{
enemy=null; //if tracking enemy is dead, reset
range1=0;
deathcode=false;
trigger=false;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//rangetracker (part1)
//tracks how close enemy is (predicts where the enemy will be next frame to get its motion)
if (!deathcode && range1==0 && enemy!=null
&& MathUtils.getDistance(enemy, MLoc)< MaxDetRange*.9f) //find out if there are enemy in range
{
range1 = MathUtils.getDistance(enemy, MLoc); //store range to target
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
if (!deathcode && range1!=0) //for all grouped in here
{
//when enemy is close enough go boom
if (range1<MinDetRange)
{
trigger=true; //make it go boom
deathcode=true; //stop tracking
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//rangetracker (cont)
//find get the range to target now
range2 = MathUtils.getDistance(enemy,MLoc);
if (range1>range2)
{
//predict its motion based on velocity
float range3 = MathUtils.getDistance(Vector2f.add(
enemy.getLocation(), multV2f(enemy.getVelocity(), .5f), null), // its velocity
MLoc)-enemy.getCollisionRadius(); //distance to missile
//compare new range to curent range
if (range1<range3) //if it mover away explode
{
trigger=true;
deathcode=true;
return;
}
else {range1=range2;} //store new target range, if it got closer
}
else//if its moving away
{
trigger=true; //make it go boom
deathcode=true; //stop tracking
}
//if damaged below 90%hp while in range, detonate
if (missile.getMaxHitpoints()*0.9>missile.getHitpoints())
{
trigger=true;
deathcode=true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//explosion code
if (trigger && MathUtils.getDistance(
missile,AIUtils.getNearestAlly(missile))>MaxDetRange*.8f)//don't blow up your own ships)
{
//for projectiles
{
//dummy weapon supports missiles and projectiles
//suggested projectile stats, range:1, speed:1
String Projectile = "DUMMY1";
{
{
//spawn projectiles
int counts=0;
do {
float angRAND= (float) ((360*(.5-Math.random()))); //angle of spread
float speedRAND= (float) ((MaxDetRange-MinDetRange)*Math.random());//variance of speed
float PSpeed = MinDetRange+speedRAND; //speed of bullets launched
float PAng = missile.getFacing()-angRAND; //direction of fire
float x = (float) (PSpeed*Math.cos(Math.toRadians(PAng)));
float y = (float) (PSpeed*Math.sin(Math.toRadians(PAng)));
Vector2f PVec = new Vector2f(x,y); //convert all that into a vector
Vector2f PMVec = add(missile.getVelocity(), PVec, null); //true Pvec
engine.spawnProjectile(
missile.getSource(),
missile.getWeapon(), //who made you
Projectile, //spawn the projectile
MLoc, //Vector2f firing origin point
PAng, //float angle of fire
PMVec); //Vector2f firing velocity
/////////////////////////////////////////////////////////////////////////////////////
counts++;
////////////////////////////////////////////////////////////////////////////////
//optional explosion/particle glows
engine.addHitParticle(MLoc, PMVec,
15f,1f,1f,new Color(112, 152, 137,255)); //size, alpha, duration, color
engine.spawnExplosion(MLoc, PMVec,
new Color(255, 86, 13,255), 15f, 1f); //color,size,duration(max)
//////////////////////////////////////////////////////////////////////////////////
}while (counts<100); //how many projectiles?
}
}
//////////////////////////////////////////////////////////////////////////////////
//main explosion flash
engine.addHitParticle(MLoc, missile.getVelocity(),MaxDetRange,1f,1f,new Color(255, 12, 25,255));
//////////////////////////////////////////////////////////////////////////////////
//to stop missile shooting again
engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
return;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.util.Iterator;
import java.util.List;
//import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class BOMBARDai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private CombatEntityAPI target;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//stuff that you can change
float straighttimer=(float)(10*Math.random()); //non targeting time
private IntervalUtil STimer = new IntervalUtil(.3f, .0f); //rate of countdown
private final double isEffectedbyflare=Math.random(); //flare stuff
private boolean flarelockout=false;
private float oftarget=300*((float)(.5-Math.random())); //max initial off target (scatters missiles at launch)
private float baseoftarget=40*((float)(.5-Math.random())); //min off target (makes missiles stay scattered)
public static final String flare="flare";
private IntervalUtil Timer = new IntervalUtil(.1f, .0f); //rate of countdown for off target
private final boolean StopNTurn=false; //when far off target missile will stop and turn to aim
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public static float searchrange = 500; //500 range default
//public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
static float MWRange = 9999f; //hack to make missile ignore null weapon
public BOMBARDai(MissileAPI missile)
{
this.missile = missile;
MWRange = missile.getWeapon() != null ? missile.getWeapon().getRange() : 9999f;
searchrange=MWRange;//based on weapon
//Ark=5+missile.getWeapon().getArc()/4; //based on weapon
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
Vector2f MouseT = missile.getSource().getMouseTarget();
if (MathUtils.getDistance(missile,MouseT)<searchrange)
{
List directTargets = CombatUtils.getShipsWithinRange(
MouseT, 100f, true);
if (!directTargets.isEmpty())
{
ShipAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext();)
{
//filter out friendlies
tmp = (ShipAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//flare finder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null; //if no flares return null
CombatEngineAPI engine = Global.getCombatEngine();
List nearbymissiles = engine.getMissiles(); //(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).startsWith(flare) //is it a flare
&& MathUtils.getDistance(tmp,missile)<300 //is it near your missile
&& missile.getOwner()!=tmp.getOwner() //is it en enemy flare
&& Math.random()>.5) //chance of failure prevents all missiles
{ //going after one flare at the same time
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp; //return flare
break; //stop searching you found your flare
}
}
}
return retv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* turned off for this AI
//search for targets in ark in front
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//get ship target or search for closest enemy
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource(); //who launched you?
if (source != null && source.getShipTarget() != null //check for nulls
&& !source.getShipTarget().isHulk() //check if its alive
&& MathUtils.getDistance(source.getShipTarget(), source) //get range to target
<MWRange) //check if its in range
{
return source.getShipTarget();
}
return AIUtils.getNearestEnemy(missile);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
//.1 second timer
Timer.advance(amount);
STimer.advance(amount);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//go straight for x after launch
if (straighttimer>0)
{
missile.giveCommand(ShipCommand.ACCELERATE);
if (STimer.intervalElapsed())
{straighttimer--;}
return;
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//if missile is susceptible to flare
if (isEffectedbyflare>0.4 //can a flare effect it
&& !flarelockout //has it already been set to ignore flares
&& Timer.intervalElapsed()) //check every .10 sec
{
MissileAPI flaretarget = checkforflare(missile);
if (flaretarget!=null)
{
target=flaretarget; //set flare as target
flarelockout=true; //ignore future flares
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//pathing ai
if (target!=null) //if you have a target
{
//reduction off target/gradual aiming
if (Timer.intervalElapsed())
{
oftarget=(oftarget>0 ? oftarget-1 : oftarget+1); //reduce off target counter;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//check if scatter code needed
float targetcolision = target.getCollisionRadius(); //get colision radi
if (MathUtils.getDistance(missile, target)<75+(targetcolision))
{ //cutoff for baseoftarget behaviour
baseoftarget=0; //aim variable if you got in close enough is 0 (ie go hit the enemy target)
}
///////////////////////////////////////////////////////////////////////////////////////////////
//how much off target will the missiles be
float oftargetby = (0 + (oftarget + (baseoftarget * targetcolision / 75)));
//////////////////////////////////////////////////////////////////////////////////////////
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
double TfaceRad = Math.toRadians(target.getFacing());
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
//float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(AtTarget-oftargetby);
//////////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{ //makes missile fly off target
missile.giveCommand(AtTarget > oftargetby
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//correct missile velocity vector to be the same as missile facing
if (AbsAngD < 5)
{
//course correct for missile velocity vector
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD < 0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//acceleration code (stopNturn compatible)
if (StopNTurn)
{
if (AbsAngD < 40)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD > 120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
}
else{missile.giveCommand(ShipCommand.ACCELERATE);}
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
//engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
//engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
//engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
//engine.addSmoothParticle(MLoc, new Vector2f(0,0), 5, .5f, 1, new Color(2, 255, 250,255));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stop if you have no target
if (target==null && straighttimer<1)
{missile.giveCommand(ShipCommand.DECELERATE);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//clear target if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) // completely removed
|| ( target instanceof ShipAPI && ((ShipAPI)target).isHulk() ))//dead
{
target = findBestTarget(missile);
return;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .00001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
//import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class MIRVai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private CombatEntityAPI target;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//stuff that you can change
float straighttimer=(float)(10*Math.random()); //non targeting time
private IntervalUtil STimer = new IntervalUtil(.3f, .0f); //rate of countdown
private final double isEffectedbyflare=Math.random(); //flare stuff
private boolean flarelockout=false;
private float oftarget=300f*((float)(.5-Math.random())); //max initial off target (scatters missiles at launch)
private float baseoftarget=40f*((float)(.5-Math.random())); //min off target (makes missiles stay scattered)
public static final String flare="flare";
private IntervalUtil Timer = new IntervalUtil(.1f, .0f); //rate of countdown for off target
private final boolean StopNTurn=false; //when far off target missile will stop and turn to aim
//MIRV/Flechet script
//dummy weapon supports missiles and projectiles
//suggested projectile stats, range:1, speed:1
//replace with projectile cannon like "chaingun" or "vulcan" from vanilla,
// or your own weapon(no beams)(Ballistic_as_beam are fine)
private final String Projectile = "harpoon_single";
private final float MaxRange=850f; //max range when it starts to direct aim target
private final float MinRange=800f; //min range when it detonates
private final float PArk=45; //ark projectiles fly in
private final int PNum=4; //shots per volley
private int PVolleys=4; //volleys
private final float Lifetime=10f; //how long the projectile being used can stay alive (IMPORTANT)
//this is a value you must enter it is equal to range/ProjSpeed and works as a multiplier to make
// firing velocity not overshoot the target (due to long life)
private final float Leadtime=1f; //fiddle with this if your projectiles are missing the target
//determines how far ahead of the target your weapon aims
private IntervalUtil VTimer = new IntervalUtil(.05f, .05f); //rate of fire
private final boolean IsFlechet=true;//are the shots fired by missile aimed or follow missile facing?
//if IsFlechet=false missile will fire shots at the target ignoring its direction of travel
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public static float searchrange = 500; //500 range default
//public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
static float MWRange = 9999f; //hack to make missile ignore null weapon
public MIRVai(MissileAPI missile)
{
this.missile = missile;
MWRange = missile.getWeapon() != null ? missile.getWeapon().getRange() : 9999f;
searchrange=MWRange;//based on weapon
//Ark=5+missile.getWeapon().getArc()/4; //based on weapon
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
Vector2f MouseT = missile.getSource().getMouseTarget();
if (MathUtils.getDistance(missile,MouseT)<searchrange)
{
List directTargets = CombatUtils.getShipsWithinRange(
MouseT, 100f, true);
if (!directTargets.isEmpty())
{
ShipAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext();)
{
//filter out friendlies
tmp = (ShipAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//flare finder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null; //if no flares return null
CombatEngineAPI engine = Global.getCombatEngine();
List nearbymissiles = engine.getMissiles(); //(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).startsWith(flare) //is it a flare
&& MathUtils.getDistance(tmp,missile)<300 //is it near your missile
&& missile.getOwner()!=tmp.getOwner() //is it en enemy flare
&& Math.random()>.5) //chance of failure prevents all missiles
{ //going after one flare at the same time
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp; //return flare
break; //stop searching you found your flare
}
}
}
return retv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* turned off for this AI
//search for targets in ark in front
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//get ship target or search for closest enemy
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource(); //who launched you?
if (source != null && source.getShipTarget() != null //check for nulls
&& !source.getShipTarget().isHulk() //check if its alive
&& MathUtils.getDistance(source.getShipTarget(), source) //get range to target
<MWRange) //check if its in range
{
return source.getShipTarget();
}
return AIUtils.getNearestEnemy(missile);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
//timer
Timer.advance(amount);
STimer.advance(amount);
VTimer.advance(amount);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//go straight for x after launch
if (straighttimer>0)
{
missile.giveCommand(ShipCommand.ACCELERATE);
if (STimer.intervalElapsed())
{straighttimer--;}
return;
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//if missile is susceptible to flare
if (isEffectedbyflare>0.4 //can a flare effect it
&& !flarelockout //has it already been set to ignore flares
&& Timer.intervalElapsed()) //check every .10 sec
{
MissileAPI flaretarget = checkforflare(missile);
if (flaretarget!=null)
{
target=flaretarget; //set flare as target
flarelockout=true; //ignore future flares
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//pathing ai
if (target!=null) //if you have a target
{
//reduction off target/gradual aiming
if (Timer.intervalElapsed())
{
oftarget=(oftarget>0 ? oftarget-1 : oftarget+1); //reduce off target counter;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//check if scatter code needed
float targetcolision = target.getCollisionRadius(); //get colision radi
if (MathUtils.getDistance(missile, target)<75+(targetcolision))
{ //cutoff for baseoftarget behaviour
baseoftarget=0; //aim variable if you got in close enough is 0 (ie go hit the enemy target)
}
///////////////////////////////////////////////////////////////////////////////////////////////
//how much off target will the missiles be
float oftargetby = (0 + (oftarget + (baseoftarget * targetcolision / 75)));
//////////////////////////////////////////////////////////////////////////////////////////
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
double TfaceRad = Math.toRadians(target.getFacing());
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//////////////////////////////////////////////////////////////////////////////////////////
//override when in range
if (RangeToTarget<MaxRange)
{
Lvec= multV2f(TVel, Leadtime);
baseoftarget=0;
oftarget=0;
oftargetby=0;
}
//////////////////////////////////////////////////////////////////////////////////////////
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(AtTarget-oftargetby);
//////////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{ //makes missile fly off target
missile.giveCommand(AtTarget > oftargetby
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//correct missile velocity vector to be the same as missile facing
if (AbsAngD < 5)
{
//course correct for missile velocity vector
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD < 0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//acceleration code (stopNturn compatible)
if (StopNTurn)
{
if (AbsAngD < 40)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD > 120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
}
else{missile.giveCommand(ShipCommand.ACCELERATE);}
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
//engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
//engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
//engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
//engine.addSmoothParticle(MLoc, new Vector2f(0,0), 5, .5f, 1, new Color(2, 255, 250,255));
/////////////////////////////////////////////////////////////////////////////////////////////////
if (VTimer.intervalElapsed() && RangeToTarget<MinRange && PVolleys>0)
{
PVolleys--;
{
{
//spawn projectiles
int counts=0; //how many projectiles?
do {
float angRAND= (float) ((PArk*(.5-Math.random()))); //angle of spread
float speedRAND= (float) (((MaxRange-MinRange)/Lifetime)*Math.random());//variance of speed
float PSpeed = (MinRange/Lifetime)+speedRAND; //speed of bullets launched
float PAng = (IsFlechet ? missile.getFacing() //direction of fire
:missile.getFacing()+AtTarget)-angRAND;
float x = (float) (PSpeed*Math.cos(Math.toRadians(PAng)));
float y = (float) (PSpeed*Math.sin(Math.toRadians(PAng)));
Vector2f PVec = new Vector2f(x,y); //convert all that into a vector
Vector2f PMVec = add(MVel, PVec, null); //true Pvec
engine.spawnProjectile(
missile.getSource(),
null, //who made you
Projectile, //spawn the projectile
MLoc, //Vector2f firing origin point
PAng, //float angle of fire
PMVec); //Vector2f firing velocity
/////////////////////////////////////////////////////////////////////////////////////
counts++;
////////////////////////////////////////////////////////////////////////////////
//optional explosion/particle glows
engine.addHitParticle(MLoc, PMVec,
5f,1f,1f,new Color(112, 152, 137,255)); //size, alpha, duration, color
engine.spawnExplosion(MLoc, PMVec,
new Color(255, 86, 13,255), 15f, .1f); //color,size,duration(max)
//////////////////////////////////////////////////////////////////////////////////
}while (counts<PNum);
}
}
//////////////////////////////////////////////////////////////////////////////////
//main explosion flash
//engine.addHitParticle(MLoc, missile.getVelocity(),10,1f,1f,new Color(255, 12, 25,255));
}
//////////////////////////////////////////////////////////////////////////////////
if (PVolleys<1)
{
//to stop missile shooting again
engine.removeEntity(missile); //make missile go poof
//missile.flameOut(); //make missile flame out
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stop if you have no target
if (target==null && straighttimer<1)
{missile.giveCommand(ShipCommand.DECELERATE);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//clear target if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) // completely removed
|| ( target instanceof ShipAPI && ((ShipAPI)target).isHulk() ))//dead
{
target = findBestTarget(missile);
return;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .00001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
//import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class KitchenSinkai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private CombatEntityAPI target;
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//nasty stuff drone uses as weapons
//replace with projectile cannon like "chaingun" or "vulcan" from vanilla,
// or your own weapon(no beams)(Ballistic_as_beam are fine)
private static String [] Guns = {
"DUMMY1",
"DUMMY2",
"chaingun",
"vulcan",
};
//and missiles
private static String [] Missiles = {
"Linghning", //special, shoots lightning bolts
"harpoon_single",
"heatseeker",
};
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stuff that you can change (MAIN missile stage AI components)
private float straighttimer=(float)(10*Math.random()); //non targeting time
private IntervalUtil STimer = new IntervalUtil(.3f, .0f); //rate of countdown
private final double isEffectedbyflare=Math.random(); //flare stuff
private boolean flarelockout=false;
private float oftarget=300f*((float)(.5-Math.random())); //max initial off target (scatters missiles at launch)
private float baseoftarget=40f*((float)(.5-Math.random())); //min off target (makes missiles stay scattered)
public static final String flare="flare";
private IntervalUtil Timer = new IntervalUtil(.1f, .0f); //rate of countdown for off target
private final boolean StopNTurn=false; //when far off target missile will stop and turn to aim
//weaponpicker
private float MislChance=.8f; //chance of missiles (1-MislChance)*100=%shots missile
private int MislVolley=1; //number of missiles per
private int ProjVolley=4; //number of Projectiles per
private boolean Missil=(Math.random() > MislChance); //is it firing a missile
private String Projectile = (Missil? Missiles:Guns) //picks the weapon
[(int) ((Missil? Missiles:Guns).length * Math.random())];
//MIRV/Flechet script (core firing script of a drone missile)
//dummy weapon supports missiles and projectiles
//suggested projectile stats, range:1, speed:1
private final float MaxRange=850f; //max range when it starts to direct aim target
private final float MinRange=600f; //min range when it detonates
private final float PArk=25; //ark projectiles fly in
private final int PNum=1; //shots per volley
private int PVolleys=Missil ? MislVolley:ProjVolley; //volleys/burst fire for drone
private int VCount=0; //counter
private final float Lifetime=1f; //how long the projectile being used can stay alive (IMPORTANT)
//this is a value you must enter it is equal to range/ProjSpeed and works as a multiplier to make
// firing velocity not overshoot the target (due to long life)
private final float Leadtime=1f; //fiddle with this if your projectiles are missing the target
//determines how far ahead of the target your weapon aims
private IntervalUtil VTimer = new IntervalUtil(.15f, .01f); //rate of fire
private final boolean IsFlechet=true;//are the shots fired by missile aimed or follow missile facing?
//if IsFlechet=false missile will fire shots at the target ignoring its direction of travel
//DRONE script (core behaviour script)
private final boolean Orbit=true; //will the drone strafe around target?
private double ODir=Math.random(); //direction of strafe
private IntervalUtil OTimer = new IntervalUtil(3f, .01f); //delay between strafe switch
private IntervalUtil BTimer = new IntervalUtil(1.8f, .01f); //delay between each burst
private final float MinOrbit=.9f; //how close will the drone move in
private final float MaxOrbit=1.2f; //how far will the drone move away
private boolean Close=true; //thruster bool
private boolean Loaded=true; //fireing bool
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public static float searchrange = 500; //500 range default
//public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
static float MWRange = 9999f; //hack to make missile ignore null weapon
public KitchenSinkai(MissileAPI missile)
{
this.missile = missile;
MWRange = missile.getWeapon() != null ? missile.getWeapon().getRange() : 9999f;
searchrange=MWRange;//based on weapon
//Ark=5+missile.getWeapon().getArc()/4; //based on weapon
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
Vector2f MouseT = missile.getSource().getMouseTarget();
if (MathUtils.getDistance(missile,MouseT)<searchrange)
{
List directTargets = CombatUtils.getShipsWithinRange(
MouseT, 100f, true);
if (!directTargets.isEmpty())
{
ShipAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext();)
{
//filter out friendlies
tmp = (ShipAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//flare finder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null; //if no flares return null
CombatEngineAPI engine = Global.getCombatEngine();
List nearbymissiles = engine.getMissiles(); //(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).startsWith(flare) //is it a flare
&& MathUtils.getDistance(tmp,missile)<300 //is it near your missile
&& missile.getOwner()!=tmp.getOwner() //is it en enemy flare
&& Math.random()>.5) //chance of failure prevents all missiles
{ //going after one flare at the same time
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp; //return flare
break; //stop searching you found your flare
}
}
}
return retv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* turned off for this AI
//search for targets in ark in front
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//get ship target or search for closest enemy
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource(); //who launched you?
if (source != null && source.getShipTarget() != null //check for nulls
&& !source.getShipTarget().isHulk() //check if its alive
&& MathUtils.getDistance(source.getShipTarget(), source) //get range to target
<MWRange) //check if its in range
{
return source.getShipTarget();
}
return AIUtils.getNearestEnemy(missile);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
//timer
Timer.advance(amount);
STimer.advance(amount);
VTimer.advance(amount);
OTimer.advance(amount);
BTimer.advance(amount);
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//go straight for x after launch
if (straighttimer>0)
{
missile.giveCommand(ShipCommand.ACCELERATE);
if (STimer.intervalElapsed())
{straighttimer--;}
return;
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//if missile is susceptible to flare
if (isEffectedbyflare>0.4 //can a flare effect it
&& !flarelockout //has it already been set to ignore flares
&& Timer.intervalElapsed()) //check every .10 sec
{
MissileAPI flaretarget = checkforflare(missile);
if (flaretarget!=null)
{
target=flaretarget; //set flare as target
flarelockout=true; //ignore future flares
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//pathing ai
if (target!=null) //if you have a target
{
//reduction off target/gradual aiming
if (Timer.intervalElapsed())
{
oftarget=(oftarget>0 ? oftarget-1 : oftarget+1); //reduce off target counter;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//check if scatter code needed
float targetcolision = target.getCollisionRadius(); //get colision radi
if (MathUtils.getDistance(missile, target)<75+(targetcolision))
{ //cutoff for baseoftarget behaviour
baseoftarget=0; //aim variable if you got in close enough is 0 (ie go hit the enemy target)
}
///////////////////////////////////////////////////////////////////////////////////////////////
//how much off target will the missiles be
float oftargetby = (0 + (oftarget + (baseoftarget * targetcolision / 75)));
//////////////////////////////////////////////////////////////////////////////////////////
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
double TfaceRad = Math.toRadians(target.getFacing());
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//////////////////////////////////////////////////////////////////////////////////////////
//override when in range
if (RangeToTarget<MaxRange)
{
Lvec= multV2f(TVel, Leadtime);
baseoftarget=0;
oftarget=0;
oftargetby=0;
}
//////////////////////////////////////////////////////////////////////////////////////////
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(AtTarget-oftargetby);
//////////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{ //makes missile fly off target
missile.giveCommand(AtTarget > oftargetby
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//correct missile velocity vector to be the same as missile facing
if (AbsAngD < 5)
{
//course correct for missile velocity vector
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD < 0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
if(RangeToTarget>MaxRange)
{
//acceleration code (stopNturn compatible)
if (StopNTurn)
{
if (AbsAngD < 40)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD > 120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
}
else{missile.giveCommand(ShipCommand.ACCELERATE);}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
else //move back and forth
{
if (RangeToTarget<MinRange*MinOrbit){Close=false;} //boolean to achive back and forth motion
if (RangeToTarget>MinRange*MaxOrbit){Close=true;}
if (Close) {missile.giveCommand(ShipCommand.ACCELERATE);}
else {missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);}
/////////////////////////////////////////////////////////////////////////////////////////////////
//orbit target
if (Orbit)
{
if (OTimer.intervalElapsed())
{
ODir=Math.random();
}
missile.giveCommand(ODir < .5
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
//engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
//engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
//engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
//engine.addSmoothParticle(MLoc, new Vector2f(0,0), 5, .5f, 1, new Color(2, 255, 250,255));
/////////////////////////////////////////////////////////////////////////////////////////////////
//reload
if (!Loaded && BTimer.intervalElapsed())
{
Loaded=true;
VCount=0;
{
//weaponpicker
Missil=(Math.random() > MislChance); //is it firing a missile
Projectile = (Missil? Missiles:Guns) //picks the weapon
[(int) ((Missil? Missiles:Guns).length * Math.random())];
PVolleys=Missil ? MislVolley:ProjVolley;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
if (Loaded && VTimer.intervalElapsed() && RangeToTarget<MinRange && VCount<PVolleys)
{
VCount++;
{
if (Projectile.equals("Linghning")) //cause lightningbolts!!
{
engine.spawnEmpArc(missile.getSource(), MLoc, target, target,
DamageType.ENERGY,
50f,
2000,
10000f, // max range
"tachyon_lance_emp_impact",
20f, // thickness
new Color(255,10,15,255),
new Color(255,255,255,255)
);
}
else
{
//spawn projectiles
int counts=0; //how many projectiles?
do {
float angRAND= (float) ((PArk*(.5-Math.random()))); //angle of spread
float speedRAND= (float) (((MaxRange-MinRange)/Lifetime)*Math.random());//variance of speed
float PSpeed = (MinRange/Lifetime)+speedRAND; //speed of bullets launched
float PAng = (IsFlechet ? missile.getFacing() //direction of fire
:missile.getFacing()+AtTarget)-angRAND;
float x = (float) (PSpeed*Math.cos(Math.toRadians(PAng)));
float y = (float) (PSpeed*Math.sin(Math.toRadians(PAng)));
Vector2f PVec = new Vector2f(x,y); //convert all that into a vector
Vector2f PMVec = add(MVel, PVec, null); //true Pvec
engine.spawnProjectile(
missile.getSource(),
null, //who made you
Projectile, //spawn the projectile
MLoc, //Vector2f firing origin point
PAng, //float angle of fire
PMVec); //Vector2f firing velocity
/////////////////////////////////////////////////////////////////////////////////////
counts++;
////////////////////////////////////////////////////////////////////////////////
//optional explosion/particle glows
engine.addHitParticle(MLoc, PMVec,
5f,1f,1f,new Color(112, 152, 137,255)); //size, alpha, duration, color
engine.spawnExplosion(MLoc, PMVec,
new Color(255, 86, 13,255), 15f, .1f); //color,size,duration(max)
//////////////////////////////////////////////////////////////////////////////////
}while (counts<PNum);
}
}
//////////////////////////////////////////////////////////////////////////////////
//main explosion flash
//engine.addHitParticle(MLoc, missile.getVelocity(),10,1f,1f,new Color(255, 12, 25,255));
}
//////////////////////////////////////////////////////////////////////////////////
if (VCount>=PVolleys)
{
Loaded=false;
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stop if you have no target
if (target==null && straighttimer<1)
{missile.giveCommand(ShipCommand.DECELERATE);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//clear target if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) // completely removed
|| ( target instanceof ShipAPI && ((ShipAPI)target).isHulk() ))//dead
{
target = findBestTarget(missile);
return;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= .0001f;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.Iterator;
import java.util.List;
//import static jar.UtilityKit.LeadVector;
import static org.lwjgl.util.vector.Vector2f.add;
public class kitchenSINKai implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
// Our current target (can be null)
private CombatEntityAPI target;
//stuff that you can change (MAIN missile stage AI components)
private float straighttimer=(float)(10*Math.random()); //non targeting time
private IntervalUtil STimer = new IntervalUtil(.3f, .0f); //rate of countdown
private final double isEffectedbyflare=Math.random(); //flare stuff
private boolean flarelockout=false;
private float oftarget=30f*((float)(.5-Math.random())); //max initial off target (scatters missiles at launch)
private float baseoftarget=4f*((float)(.5-Math.random())); //min off target (makes missiles stay scattered)
//flarechasing
public static final String flare="flare";
public final float BaseIgnoreChance=.3f; //chance that flares cannot effect it by default
public final float LockCHance=.3f; //chance that future flares it encounters wont effect it
private IntervalUtil Timer = new IntervalUtil(.1f, .0f); //rate of countdown for off target
private final boolean StopNTurn=false; //when far off target missile will stop and turn to aim
//weaving behaviour
private final boolean Weave=false; //go in a zigzag path (improved over extingency script)
private float Weavesize=40f; //how far of course it will go, in degrees
private float WeaveFreq=40f; //how ofter it cycles (arbitary)
private float weavecount=0; //misc dont change
//scripts for HardTargeting/orbitrange
private final float MaxRange=850f; //max range when it starts to direct aim target
private final float MinRange=500f; //min range when it detonates
private final boolean HardTargeting=true;//will it turn to target once in range
//Heatseeker like behaviour (uses Min/Max range)
private final boolean Backtarget=true; //dose it circle around back of ship
private final float BackAng=45f; //min angle from rear
private final boolean Straferound=false; //strafe there or turn there
private boolean BTcutout=false; //shuts down the circling, don't touch
//DRONE script (core behaviour script)
private final boolean isDrone=false; //makes missile bob in and out of MinRange and maintain that distance
private final boolean Orbit=false; //will the drone strafe around target?
private double ODir=Math.random(); //direction of strafe
private IntervalUtil OTimer = new IntervalUtil(3f, .01f); //delay between strafe switch
//used in Backtarget as well
private final float MinOrbit=.9f; //how close will the drone move in
private final float MaxOrbit=1.2f; //how far will the drone move away
private boolean Close=true; //thruster bool, don't touch
//weapons
//controls firing, everything below needs this on
private final boolean canFire=false; //is shooting enabled
//////////////////////////////////////////////////////////////////////////////////////////////////////////
//weaponlist
//nasty stuff drone uses as weapons
//replace with projectile cannon like "chaingun" or "vulcan" from vanilla,
// or your own weapon(no beams)(Ballistic_as_beam are fine)
private static String [] Guns = {
"DUMMY1",
"DUMMY2",
"chaingun",
"vulcan",
};
//and missiles
private static String [] Missiles = {
"Linghning", //special, shoots lightning bolts
"harpoon_single",
"heatseeker",
};
//////////////////////////////////////////////////////////////////////////////////////////////////////
//weaponpicker
private float MislChance=.8f; //chance of missiles (1-MislChance)*100=%shots missile
private int MislVolley=1; //number of missiles per
private int ProjVolley=4; //number of Projectiles per
private boolean Missil=(Math.random() > MislChance); //is it firing a missile
private String Projectile = (Missil? Missiles:Guns) //picks the weapon
[(int) ((Missil? Missiles:Guns).length * Math.random())];
//MIRV/Flechet script (core firing script of a drone missile)
//dummy weapon supports missiles and projectiles
//suggested projectile stats, range:1, speed:1
private final float PArk=25; //ark projectiles fly in
private final int PNum=1; //shots per volley
private int PVolleys=Missil ? MislVolley:ProjVolley; //volleys/burst fire for drone
private int VCount=0; //counter
private final float Lifetime=1f; //how long the projectile being used can stay alive (IMPORTANT)
//this is a value you must enter it is equal to range/ProjSpeed and works as a multiplier to make
// firing velocity not overshoot the target (due to long life)
private final float Leadtime=1f; //fiddle with this if your projectiles are missing the target
//determines how far ahead of the target your weapon aims
private IntervalUtil VTimer = new IntervalUtil(.15f, .01f); //rate of fire
private final boolean IsFlechet=true;//are the shots fired by missile aimed or follow missile facing?
//if IsFlechet=false missile will fire shots at the target ignoring its direction of travel
private boolean Loaded=true; //fireing bool
private boolean DespawnEmpty=false; //should it de-spawn the missile once it has run out of ammo
private boolean SpawnEmpty=false; //to spawn an empty type subprojectile(casing)
private boolean canReload=true; //can reload after a burst
private IntervalUtil BTimer = new IntervalUtil(1.8f, .01f); //delay between each burst
public static void emptyspawn(CombatEngineAPI engine, MissileAPI missile)
{
engine.spawnProjectile(missile.getSource(),null,//who made you
"DUMMY2", //empty missile,
missile.getLocation(), //Vector2f firing origin point
missile.getFacing(), //float angle of fire
missile.getVelocity()); //Vector2f firing velocity)
}
//end of stuff you can change
//////////////////////////////////////////////////////////////////////////////////////////////////////////
public static float searchrange = 500; //500 range default
//public static float Ark = 30; //60deg ark default
boolean newlaunch=true; //only do mouse target once on a newly launched missile
static float MWRange = 9999f; //hack to make missile ignore null weapon
public kitchenSINKai(MissileAPI missile)
{
this.missile = missile;
MWRange = missile.getWeapon() != null ? missile.getWeapon().getRange() : 9999f;
searchrange=MWRange;//based on weapon
//Ark=5+missile.getWeapon().getArc()/4; //based on weapon
//missile.setCollisionClass(CollisionClass.FIGHTER);//hack reclasify as fighter
// Support for 'fire at target by clicking on them' behavior
if (newlaunch=true)
{
newlaunch=false;
//get targets near mouse
Vector2f MouseT = missile.getSource().getMouseTarget();
if (MathUtils.getDistance(missile,MouseT)<searchrange)
{
List directTargets = CombatUtils.getShipsWithinRange(
MouseT, 100f, true);
if (!directTargets.isEmpty())
{
ShipAPI tmp;
for (Iterator iter = directTargets.iterator(); iter.hasNext();)
{
//filter out friendlies
tmp = (ShipAPI) iter.next();
if (tmp.getOwner() != missile.getSource().getOwner())
{
target = tmp;
break;
}
}
}
}
}
// Otherwise, use default targeting AI
if (target == null)
{
target = findBestTarget(missile);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
//flare finder
public static MissileAPI checkforflare(MissileAPI missile)
{
MissileAPI retv = null; //if no flares return null
CombatEngineAPI engine = Global.getCombatEngine();
List nearbymissiles = engine.getMissiles(); //(target.getCollisionRadius()*10)
if (!nearbymissiles.isEmpty())
{
MissileAPI tmp;
for (Iterator iter = nearbymissiles.iterator(); iter.hasNext();)
{
tmp = (MissileAPI) iter.next();
if ((tmp.getProjectileSpecId()).startsWith(flare) //is it a flare
&& MathUtils.getDistance(tmp,missile)<300 //is it near your missile
&& missile.getOwner()!=tmp.getOwner() //is it en enemy flare
&& Math.random()>.5) //chance of failure prevents all missiles
{ //going after one flare at the same time
//tmp= (MissileAPI) nearbymissiles.get(0); //testing code
retv = tmp; //return flare
break; //stop searching you found your flare
}
}
}
return retv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* turned off for this AI
//search for targets in ark in front
public static MissileAPI findBestTarget(MissileAPI missile)
{
//find targets in ark in front of missile
ArrayList targets = getEnemyMissilesInArk(missile, Ark, searchrange, true);
if (!targets.isEmpty())
{ //pick random missile in list //replace with .get(0) for nearest
return (MissileAPI) targets.get((int)(targets.size()*Math.random()));
}
else return null;
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//get ship target or search for closest enemy
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource(); //who launched you?
if (source != null && source.getShipTarget() != null //check for nulls
&& !source.getShipTarget().isHulk() //check if its alive
&& MathUtils.getDistance(source.getShipTarget(), source) //get range to target
<MWRange) //check if its in range
{
return source.getShipTarget();
}
return AIUtils.getNearestEnemy(missile);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//main missile AI
@Override
public void advance(float amount)
{
//timer
Timer.advance(amount);
STimer.advance(amount);
VTimer.advance(amount);
OTimer.advance(amount);
BTimer.advance(amount);
weavecount++;
if (weavecount>4000){weavecount=0;} //arbitary reset
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//go straight for x after launch
if (straighttimer>0)
{
missile.giveCommand(ShipCommand.ACCELERATE);
if (STimer.intervalElapsed())
{straighttimer--;}
return;
}
else
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apparently commands still work while fizzling
if (missile.isFading() || missile.isFizzling())
{
return;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find a target
if (target == null)
{
target = findBestTarget(missile);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
//if missile is susceptible to flare
if (isEffectedbyflare>BaseIgnoreChance //can a flare effect it
&& !flarelockout //has it already been set to ignore flares
&& Timer.intervalElapsed()) //check every .10 sec
{
MissileAPI flaretarget = checkforflare(missile);
if (flaretarget!=null)
{
target=flaretarget; //set flare as target
if (LockCHance<(float)Math.random())flarelockout=true; //ignore future flares
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//pathing ai
if (target!=null) //if you have a target
{
//reduction off target/gradual aiming
if (Timer.intervalElapsed())
{
oftarget=(oftarget>0 ? oftarget-1 : oftarget+1); //reduce off target counter;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//check if scatter code needed
float targetcolision = target.getCollisionRadius(); //get colision radi
if (MathUtils.getDistance(missile, target)<75+(targetcolision))
{ //cutoff for baseoftarget behaviour
baseoftarget=0; //aim variable if you got in close enough is 0 (ie go hit the enemy target)
}
///////////////////////////////////////////////////////////////////////////////////////////////
//how much off target will the missiles be
float oftargetby = (0 + (oftarget + (baseoftarget * targetcolision / 75)));
//////////////////////////////////////////////////////////////////////////////////////////
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
float TFace = target.getFacing();
double TfaceRad = Math.toRadians(TFace);
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//////////////////////////////////////////////////////////////////////////////////////////
//override when in range
if (HardTargeting && RangeToTarget<MaxRange)
{
Lvec= multV2f(TVel, Leadtime);
baseoftarget=0;
oftarget=0;
oftargetby=0;
}
//////////////////////////////////////////////////////////////////////////////////////////
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
//for backtargeting
if (Backtarget && RangeToTarget<MaxRange)
{
float AngFromTtoM = MathUtils.getAngle(TLoc, MLoc); //get angle from target
float ToMissile = getAngleDifference(TFace, AngFromTtoM); //to missile
if (Math.abs(ToMissile)>180-BackAng) //check if you are behind the target
{BTcutout=true;}
else if (isDrone) //override for drones, re enables the AI
{BTcutout=false;}
if (!Straferound && !BTcutout)
{
AtTarget=AtTarget-(ToMissile>0? 90:-90);
}
if (Straferound && !BTcutout)
{
missile.giveCommand(ToMissile < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
//makes the missile weave
if (Weave)
{AtTarget=AtTarget+(float)(Math.sin(weavecount/WeaveFreq)*Weavesize);}
float AbsAngD = Math.abs(AtTarget-oftargetby);
//////////////////////////////////////////////////////////////////////////////////////////////
//point towards target
if (AbsAngD > 0.5)
{ //makes missile fly off target
missile.giveCommand(AtTarget > oftargetby
? ShipCommand.TURN_LEFT : ShipCommand.TURN_RIGHT);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//correct missile velocity vector to be the same as missile facing
if (AbsAngD < 5)
{
//course correct for missile velocity vector
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
missile.giveCommand(MFlightCC < 0
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//stop turning once you are on target (way of the hack, ignores missile limitations)
if (AbsAngD < 0.4)
{
missile.setAngularVelocity(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////
if((!isDrone && !Straferound) //not a drone or strafingBT
|| (isDrone && RangeToTarget>MaxRange) //is out or orbital range
|| (Backtarget && RangeToTarget>MaxRange && !BTcutout)
|| BTcutout) //is BTcutout
{
//acceleration code (stopNturn compatible)
if (StopNTurn)
{
if (AbsAngD < 40)
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (MVel.lengthSquared()<TVel.lengthSquared())
{
missile.giveCommand(ShipCommand.ACCELERATE);
}
if (AbsAngD > 120) //if you missed stop and turn around
{
missile.giveCommand(ShipCommand.DECELERATE);
}
}
else{missile.giveCommand(ShipCommand.ACCELERATE);}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
else //move back and forth, if its a drone
{
if (RangeToTarget<MinRange*MinOrbit){Close=false;} //boolean to achive back and forth motion
if (RangeToTarget>MinRange*MaxOrbit){Close=true;}
if (Close) {missile.giveCommand(ShipCommand.ACCELERATE);}
else {missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS);}
/////////////////////////////////////////////////////////////////////////////////////////////////
//orbit target
if (Orbit)
{
if (OTimer.intervalElapsed())
{
ODir=Math.random();
}
missile.giveCommand(ODir < .5
? ShipCommand.STRAFE_LEFT : ShipCommand.STRAFE_RIGHT);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
//testcode spawns particle at aim points
CombatEngineAPI engine = Global.getCombatEngine(); //engine
//engine.addSmoothParticle(TLead, new Vector2f(0,0), 5, 1, 1, new Color(255,10,15,255));
//engine.addSmoothParticle(TNoseLead, new Vector2f(0,0), 5, 1, 1, new Color(93, 255, 40,255));
//engine.addSmoothParticle(MLoc, multV2f(MVel, 4), 5, .5f, 1, new Color(248, 244, 255,255));
//engine.addSmoothParticle(MLoc, new Vector2f(0,0), 5, .5f, 1, new Color(2, 255, 250,255));
/////////////////////////////////////////////////////////////////////////////////////////////////
if (canFire) //overriding controll for firing
{
//reload
if (canReload && !Loaded && BTimer.intervalElapsed())
{
Loaded=true;
VCount=0;
{
//weaponpicker
Missil=(Math.random() > MislChance); //is it firing a missile
Projectile = (Missil? Missiles:Guns) //picks the weapon
[(int) ((Missil? Missiles:Guns).length * Math.random())];
PVolleys=Missil ? MislVolley:ProjVolley;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////
if (Loaded && VTimer.intervalElapsed() && RangeToTarget<MinRange && VCount<PVolleys)
{
VCount++;
{
if (Projectile.equals("Linghning")) //cause lightningbolts!!
{
engine.spawnEmpArc(missile.getSource(), MLoc, target, target,
DamageType.ENERGY,
50f,
2000,
10000f, // max range
"tachyon_lance_emp_impact",
20f, // thickness
new Color(255,10,15,255),
new Color(255,255,255,255)
);
}
else
{
//spawn projectiles
int counts=0; //how many projectiles?
do {
float angRAND= (float) ((PArk*(.5-Math.random()))); //angle of spread
float speedRAND= (float) (((MaxRange-MinRange)/Lifetime)*Math.random());//variance of speed
float PSpeed = (MinRange/Lifetime)+speedRAND; //speed of bullets launched
float PAng = (IsFlechet ? missile.getFacing() //direction of fire
:missile.getFacing()+AtTarget)-angRAND;
float x = (float) (PSpeed*Math.cos(Math.toRadians(PAng)));
float y = (float) (PSpeed*Math.sin(Math.toRadians(PAng)));
Vector2f PVec = new Vector2f(x,y); //convert all that into a vector
Vector2f PMVec = add(MVel, PVec, null); //true Pvec
//for missile weapon offset
float xa = (float) (11*Math.cos(Math.toRadians(missile.getFacing()-95)));
float ya = (float) (11*Math.sin(Math.toRadians(missile.getFacing()-95)));
Vector2f MoffVec = new Vector2f(xa,ya);
//for projectile weapon ofset
float xb = (float) (25*Math.cos(Math.toRadians(missile.getFacing()+45)));
float yb = (float) (25*Math.sin(Math.toRadians(missile.getFacing()+45)));
Vector2f PoffVec = new Vector2f(xb,yb);
Vector2f Lpoint = add(MLoc, Missil ? MoffVec : PoffVec, null);
engine.spawnProjectile(
missile.getSource(),
null, //who made you
Projectile, //spawn the projectile
Lpoint, //Vector2f firing origin point
PAng, //float angle of fire
PMVec); //Vector2f firing velocity
/////////////////////////////////////////////////////////////////////////////////////
counts++;
////////////////////////////////////////////////////////////////////////////////
//optional explosion/particle glows
//engine.addHitParticle(Lpoint, PMVec,
// 5f,1f,1f,new Color(112, 152, 137,255)); //size, alpha, duration, color
engine.spawnExplosion(Lpoint, PMVec,
new Color(255, 86, 13,255), 15f, .1f); //color,size,duration(max)
//////////////////////////////////////////////////////////////////////////////////
}while (counts<PNum);
}
}
//////////////////////////////////////////////////////////////////////////////////
//main explosion flash
//engine.addHitParticle(MLoc, missile.getVelocity(),10,1f,1f,new Color(255, 12, 25,255));
}
//////////////////////////////////////////////////////////////////////////////////
if (VCount>=PVolleys)
{
Loaded=false;
if(DespawnEmpty)
{
if (SpawnEmpty) {emptyspawn(engine,missile);}
engine.removeEntity(missile);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
//stop if you have no target
if (target==null && straighttimer<1)
{missile.giveCommand(ShipCommand.DECELERATE);}
///////////////////////////////////////////////////////////////////////////////////////////////////////////
//clear target if target is gone
if (target == null // unset
|| ( missile.getOwner() == target.getOwner() ) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) // completely removed
|| ( target instanceof ShipAPI && ((ShipAPI)target).isHulk() ))//dead
{
target = findBestTarget(missile);
return;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
//multiply vectors //own code
public static Vector2f multV2f(Vector2f Vector1, float Multiplier)
{
float v1x = Vector1.getX()*Multiplier;
float v1y = Vector1.getY()*Multiplier;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .0001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
}
package data.scripts.weapons;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import com.fs.starfarer.api.util.IntervalUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
//GuidedMissileAI_NoDrones script
//A low-latency guided missile AI with an example of sorting out non-drone targets
//Based on Dmaiski's work on LRPD AI
public class GuidedMissileAI_NoDrones implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
private float waitTime = 0f;
// Our current target (can be null)
private ShipAPI target = null;
//Booleans set current control state, updated every tick
private boolean isAccelerating = false;
private boolean turnRight = false;
private boolean turnLeft = false;
private boolean strafeRight = false;
private boolean strafeLeft = false;
private final IntervalUtil checkInterval = new IntervalUtil(0.05f, 0.1f);
//Initial setup. If waitTime = 0f, the guidance begins one frame after launch.
//Otherwise, drifts until waitTime = 0f (useful for anime-style launch systems)
public GuidedMissileAI_NoDrones (MissileAPI missile, float waitTime)
{
this.missile = missile;
this.waitTime = waitTime;
//This line can be enabled to give missile targetting data even while drifting.
//target = findBestTarget(missile);
}
//This is our sorter, that finds the closest ShipAPI that is not a Drone.
public static ShipAPI findBestTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource();
if(source != null){
ShipAPI manTarget = source.getShipTarget();
if(manTarget != null && !manTarget.isHulk() && !(missile.getOwner() == manTarget.getOwner()) // friendly
&& Global.getCombatEngine().isEntityInPlay(manTarget))
{
return manTarget;
}
}
//this is where your missile is
Vector2f loc = missile.getLocation();
//getting what you want to search through
ArrayList<ShipAPI> possibleTarget = new ArrayList<ShipAPI>();
//lazylib is convenient, gets everything you want in Searchrange
List nearbyEnemies = AIUtils.getNearbyEnemies(missile, 5000f, true);
//filters through the ship list and puts it in size order (maintaining distance to point)
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext(); )
{
ShipAPI enemy = (ShipAPI) iterMe.next();
//criteria to add to list (you can put anything that will return a true/false here)
if (!enemy.isDrone())
{
possibleTarget.add(enemy); //add to posibles
}
}
//If we don't have a manual target, then the missile looks for the closest one in the forward arc.
float searchArc = 45f;
for(Iterator iterMe = possibleTarget.iterator(); iterMe.hasNext(); ){
ShipAPI target = (ShipAPI) iterMe.next();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
float MFace = missile.getFacing();
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TLoc);
//Gets the difference in the angle to the enemy vs. the missile's current angle
float angleToTarget = getAngleDifference(MFace, AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
if(AbsAngD < searchArc){
return target;
}
}
//If we haven't found a target within the search arc, this eturns the closest enemy to this missile, or null
if(!possibleTarget.isEmpty()){
return possibleTarget.get(0);
}else{
return null;
}
}
//Main logic
@Override
public void advance(float amount)
{
//LOGIC CHECKS
// If fading or fizzling, don't bother with updates.
if (missile.isFading() || missile.isFizzling())
{
return;
}
//Do nothing while waitTime > 0
if(waitTime > 0f){
waitTime --;
return;
}
//If neither of the above are true, go ahead and increment our checkInterval
checkInterval.advance(amount);
//UPDATE TARGETS
//If we don't have a target, find one.
//If our target is dead, removed from the sim or has switched sides, find a new target.
if (target == null // unset
|| target.isHulk()
|| (missile.getOwner() == target.getOwner()) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) ) // completely removed
{
target = null;
target = findBestTarget(missile);
return;
}
//CONTINUAL CONTROL STATES
//Checks booleans and gives continual commands here.
//Done here so that we can use IntervalUtil to save CPU later
if(isAccelerating){
missile.giveCommand(ShipCommand.ACCELERATE);
}else{
missile.giveCommand(ShipCommand.DECELERATE);
}
if(turnRight){
missile.giveCommand(ShipCommand.TURN_RIGHT);
}
if (turnLeft){
missile.giveCommand(ShipCommand.TURN_LEFT);
}
if(strafeRight){
missile.giveCommand(ShipCommand.STRAFE_RIGHT);
}
if(strafeLeft){
missile.giveCommand(ShipCommand.STRAFE_LEFT);
}
//MAIN AI LOGIC
//This code governs the missile's behavior and outputs boolean states that...
//...are used in the continual control state code.
//By using an IntervalUtil here, we can do practical load-balancing at the cost of accuracy.
if (target!=null && checkInterval.intervalElapsed()) //if you have a target
{
//Reset all of the continuous control states here.
isAccelerating = false;
turnRight = false;
turnLeft = false;
strafeRight = false;
strafeLeft = false;
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TVel = target.getVelocity();
Vector2f TLoc = target.getLocation();
float MFace = missile.getFacing();
//Testing InterceptPoint
float TCol = target.getCollisionRadius();
Vector2f TVelShort = VectorDirection(TVel, TCol);
Vector2f TLead = new Vector2f(TLoc.getX() + TVelShort.getX(),TLoc.getY() + TVelShort.getY());
//Main aiming logic
float AngleToEnemy = MathUtils.getAngle(MLoc, TLead);
float angleToTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(angleToTarget);
//////////////////////////////////////////////////////////////////////////////////////////////
if (AbsAngD < 5)
{
//course correct for missile velocity vector (some bugs when severly off target)
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
if(MFlightCC < 0){
strafeLeft = true;
} else {
strafeRight = true;
}
}
}
//point towards target
if (AbsAngD >= 0.5)
{
if(angleToTarget > 0){
turnLeft = true;
}else{
turnRight = true;
}
}
//Damp angular velocity if we're getting close to the target angle
if(AbsAngD < Math.abs(missile.getAngularVelocity())){
if(angleToTarget > 0){
missile.setAngularVelocity(AbsAngD);
}else if (angleToTarget < 0){
missile.setAngularVelocity(-AbsAngD);
}
}
//acceleration code
if (AbsAngD < 20)
{
isAccelerating = true;
}
}
}
//Returns a new vector with original direction but new length
public static Vector2f VectorDirection(Vector2f vec, float size)
{
float x = vec.getX();
float y = vec.getY();
float lenght =(float) Math.hypot(x, y);
return new Vector2f(x*size/lenght, y*size/lenght);
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
}
Ever wanted a simple, no-frills missile AI that:
Well, this might just work for you :) Based on Dmaiski's LRPD source, with a bunch of changes by me :)
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
Vector2f TVel = target.getVelocity();
//float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//float TSpeed = (float)Math.sqrt(TVel.lengthSquared());
float MFace = missile.getFacing();
float TFace = target.getFacing();
double TfaceRad = Math.toRadians(TFace);
float TCol = target.getCollisionRadius();
float sx = (float) (TCol*.5*Math.cos(TfaceRad));
float sy = (float) (TCol*.5*Math.sin(TfaceRad));
Vector2f TNose = new Vector2f(sx, sy);
float RangeToTarget = MathUtils.getDistance(TLoc, MLoc);
//float TimeToTarget = RangeToTarget/MSpeed; //how long till you hit
//testing InterceptPoint
Vector2f Lvec = LeadVector(TLoc, TVel, MLoc, MVel);
//////////////////////////////////////////////////////////////////////////////////////////
/*
//override when in range
if (HardTargeting && RangeToTarget<MaxRange)
{
Lvec= multV2f(TVel, Leadtime);
baseoftarget=0;
oftarget=0;
oftargetby=0;
}
*/
//////////////////////////////////////////////////////////////////////////////////////////
//lead target
Vector2f TLead = add(TLoc, //target location+
Lvec, //target speed*time to get to target+
null); // if its too long just assume 3 seconds is long enough to course correct
//aim at nose (can be changed to part targeting with a few tweaks)
Vector2f TNoseLead = add(TLead, TNose, null);//aim at the nose of the target(optional)
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TNoseLead);
float AtTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy);
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
//find intercept between a missile and a target
//adapted from code by Jens Seiler
//http://jaran.de/goodbits/2011/07/17/calculating-an-intercept-course-to-a-target-with-constant-direction-and-velocity-in-a-2-dimensional-plane/
//have fun if you want to actually understand what going on
//basicaly gets where T will be by the time M gets to it
public static Vector2f LeadVector(Vector2f TLoc, Vector2f TVel, Vector2f MLoc, Vector2f MVel)
{
//get missiles speed
float MSpeed = (float)Math.sqrt(MVel.lengthSquared());
//separate out the vectors
float Tx = TLoc.getX();
float Ty = TLoc.getY();
float Mx = MLoc.getX();
float My = MLoc.getY();
float TVx = TVel.getX();
float TVy = TVel.getY();
float MVx = MVel.getX();
float MVy = MVel.getY();
//subtract position vectors
float x1 = Tx - Mx;
float y1 = Ty - My;
//quadratic fun
float h1 = TVx*TVx + TVy*TVy - MSpeed*MSpeed;
if (h1==0)
{
h1= (float) .0001;
}
float minusMHalf = -(x1*TVx + y1*TVy)/h1; // h2/h1
float discriminant = minusMHalf * minusMHalf - (x1*x1 + y1*y1)/h1; // (h2/h1)^2-h3/h1 (ie D)
//can they intersect?
if (discriminant < 0)
{
return TLoc;
}
double root = Math.sqrt(discriminant);
double t1 = minusMHalf + root;
double t2 = minusMHalf - root;
double tMin = Math.min(t1, t2);
double tMax = Math.max(t1, t2);
//which of the 2 is smaller
double time = tMin > 0 ? tMin : tMax;
//can return -ve time (this is less then useful)
if (time < 0)
{
return TLoc;
}
//calculate vector
return new Vector2f((float)(time * TVx), (float)(time * TVy));
}
package data.scripts.weapons;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import com.fs.starfarer.api.util.IntervalUtil;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
//GuidedMissileAI_NoDrones script
//A low-latency guided missile AI with an example of a "smart" PD system.
//Targets missiles, fighters, frigates and then other ships, in that order
//Based on Dmaiski's work on LRPD AI
public class GuidedMissileAI_SmartPD implements MissileAIPlugin
{
// Our missile object
private final MissileAPI missile;
private float waitTime = 0f;
// Our current target (can be null)
private CombatEntityAPI target;
//Booleans set current control state, updated every tick
private boolean isAccelerating = false;
private boolean turnRight = false;
private boolean turnLeft = false;
private boolean strafeRight = false;
private boolean strafeLeft = false;
private final IntervalUtil checkInterval = new IntervalUtil(0.05f, 0.1f);
//Initial setup. If waitTime = 0f, the guidance begins one frame after launch.
//Otherwise, drifts until waitTime = 0f (useful for anime-style launch systems)
public GuidedMissileAI_SmartPD (MissileAPI missile, float waitTime)
{
this.missile = missile;
this.waitTime = waitTime;
//This line can be enabled to give missile targetting data even while drifting.
//target = findBestTarget(missile);
}
//This is our sorter, that finds the closest ShipAPI that is not a Drone.
public static CombatEntityAPI findBestTarget(MissileAPI missile)
{
//Allows manual targets to get top priority. Disabled here, since it's meant to be PD
/*
ShipAPI source = missile.getSource();
if(source != null){
CombatEntityAPI manTarget = (CombatEntityAPI) source.getShipTarget();
if(manTarget != null){
return manTarget;
}
}
*/
//this is where your missile is
Vector2f loc = missile.getLocation();
//This is the search arc
float searchArc = 45f;
List nearbyEnemyMissiles = AIUtils.getNearbyEnemyMissiles(missile, 2000f);
//Looks for any nearby missiles that are within the missile's arc.
for(Iterator iterMe = nearbyEnemyMissiles.iterator(); iterMe.hasNext(); ){
MissileAPI target = (MissileAPI) iterMe.next();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
float MFace = missile.getFacing();
//Finds angle to enemy missile
float AngleToEnemy = MathUtils.getAngle(MLoc, TLoc);
//Gets the difference in the angle to the enemy vs. the missile's current angle
float angleToTarget = getAngleDifference(MFace, AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
if(AbsAngD < searchArc){
return (CombatEntityAPI) target; //return target, after casting to CombatEntityAPI
}
}
//No missiles were found in the arc. Any nearby missiles at all?
if(!nearbyEnemyMissiles.isEmpty()){
return (CombatEntityAPI) nearbyEnemyMissiles.get(0);
}
//We didn't find any missiles. So how's about ships?
//An array that we can prune of members we don't want included later
ArrayList<CombatEntityAPI> possibleTarget = new ArrayList<CombatEntityAPI>();
//Let's get all of the nearby enemy ships
List nearbyEnemies = AIUtils.getNearbyEnemies(missile, 2000f, true);
//filters through the ship list and eliminates anything that isn't a Fighter or a Frigate.
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext(); )
{
ShipAPI enemy = (ShipAPI) iterMe.next();
//criteria to add to list (you can put anything that will return a true/false here)
if (enemy.isFighter() || enemy.isFrigate() && !enemy.isDrone())
{
possibleTarget.add((CombatEntityAPI) enemy); //add to possible targets, after casting to CombatEntityAPI
}
}
//The missile looks for the closest target in the forward arc.
for(Iterator iterMe = possibleTarget.iterator(); iterMe.hasNext(); ){
ShipAPI target = (ShipAPI) iterMe.next();
Vector2f MLoc = missile.getLocation();
Vector2f TLoc = target.getLocation();
float MFace = missile.getFacing();
//Finds angle to the enemy ship
float AngleToEnemy = MathUtils.getAngle(MLoc, TLoc);
//Gets the difference in the angle to the enemy vs. the missile's current angle
float angleToTarget = getAngleDifference(MFace, AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
if(AbsAngD < searchArc){
return (CombatEntityAPI) target; //return target, after casting to CombatEntityAPI
}
}
//If we haven't found a target within the search arc, this returns the closest enemy to this missile, or null
if(!possibleTarget.isEmpty()){
//Can't find anything in the arc? Get the nearest Fighter / Frigate.
return (CombatEntityAPI) possibleTarget.get(0);
}else if(!nearbyEnemies.isEmpty()){
//No nearby Fighters or Frigates? Get nearest ship
CombatEntityAPI theEnemy = (CombatEntityAPI) nearbyEnemies.get(0);
return theEnemy;
}else{
//We've utterly failed to find any target at all
return null;
}
}
//Main logic
@Override
public void advance(float amount)
{
//LOGIC CHECKS
// If fading or fizzling, don't bother with updates.
if (missile.isFading() || missile.isFizzling())
{
return;
}
//Do nothing while waitTime > 0
if(waitTime > 0f){
waitTime --;
return;
}
//If neither of the above are true, go ahead and increment our checkInterval
checkInterval.advance(amount);
//UPDATE TARGETS
//If we don't have a target, find one.
//If our target is dead, removed from the sim or has switched sides, find a new target.
if (target == null // unset
|| ( missile.getOwner() == target.getOwner()) // friendly
|| !Global.getCombatEngine().isEntityInPlay(target) //Not in play
)
{
target = findBestTarget(missile);
return;
}
//Dead ShipAPIs
if(target instanceof ShipAPI){
ShipAPI thisTarget = (ShipAPI) target;
if(thisTarget.isHulk()){
target = findBestTarget(missile);
return;
}
}
//CONTINUAL CONTROL STATES
//Checks booleans and gives continual commands here.
//Done here so that we can use IntervalUtil to save CPU later
if(isAccelerating){
missile.giveCommand(ShipCommand.ACCELERATE);
}else{
missile.giveCommand(ShipCommand.DECELERATE);
}
if(turnRight){
missile.giveCommand(ShipCommand.TURN_RIGHT);
}
if (turnLeft){
missile.giveCommand(ShipCommand.TURN_LEFT);
}
if(strafeRight){
missile.giveCommand(ShipCommand.STRAFE_RIGHT);
}
if(strafeLeft){
missile.giveCommand(ShipCommand.STRAFE_LEFT);
}
//MAIN AI LOGIC
//This code governs the missile's behavior and outputs boolean states that...
//...are used in the continual control state code.
//By using an IntervalUtil here, we can do practical load-balancing at the cost of accuracy.
if (target!=null && checkInterval.intervalElapsed()) //if you have a target
{
//Reset all of the continuous control states here.
isAccelerating = false;
turnRight = false;
turnLeft = false;
strafeRight = false;
strafeLeft = false;
//aming
//1step calculations of various stats
Vector2f MVel = missile.getVelocity();
Vector2f MLoc = missile.getLocation();
Vector2f TVel = target.getVelocity();
Vector2f TLoc = target.getLocation();
float MFace = missile.getFacing();
//testing InterceptPoint
Vector2f TVelShort = new Vector2f(TVel.getX() * 0.5f, TVel.getY() * 0.5f);
Vector2f TLead = new Vector2f(TLoc.getX() + TVelShort.getX(),TLoc.getY() + TVelShort.getY());
//main aiming (to find angle you are off target by)
float AngleToEnemy = MathUtils.getAngle(MLoc, TLead);
float angleToTarget = getAngleDifference( //how far off target you are
MFace, AngleToEnemy); //where missile pointing, where target is in relation
float AbsAngD = Math.abs(angleToTarget);
//////////////////////////////////////////////////////////////////////////////////////////////
if (AbsAngD < 5)
{
//course correct for missile velocity vector (some bugs when severly off target)
float MFlightAng = MathUtils.getAngle(new Vector2f(0, 0), MVel);
float MFlightCC = getAngleDifference(MFace, MFlightAng);
if (Math.abs(MFlightCC)>20)
{
if(MFlightCC < 0){
strafeLeft = true;
} else {
strafeRight = true;
}
}
}
//point towards target
if (AbsAngD >= 0.5)
{
if(angleToTarget > 0){
turnLeft = true;
}else{
turnRight = true;
}
}
//Damp angular velocity if we're getting close to the target angle
if(AbsAngD < Math.abs(missile.getAngularVelocity())){
if(angleToTarget > 0){
missile.setAngularVelocity(AbsAngD);
}else if (angleToTarget < 0){
missile.setAngularVelocity(-AbsAngD);
}
}
//acceleration code
if (AbsAngD < 40 || MVel.lengthSquared()<TVel.lengthSquared())
{
isAccelerating = true;
}
}
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
}
float TCol =target.getCollisionRadius();
//float VariableLeadDistance = //some math to produce how far ahead the missile should extrapolate the targets location
Vector2f TVelShort = VectorDirection(TVel, TCol)
//TCol+VariableLeadDistance) //optional if you want to use VariableLeadDistance
//...yor pathing code
//returns a new vector with originals direction but new length
public static Vector2f VectorDirection(Vector2f vec, float size)
{
float x = vec.getX();
float y = vec.getY();
float lenght =(float) Math.hypot(x, y);
return new Vector2f(x*size/lenght, y*size/lenght);
}
/**
*
* @param vec original vector
* @param size size of new vector in the direction of motion
* @return new vector with all variables applied to it
*/
public static Vector2f V2fReSize(Vector2f vec, float size)
{
float x = vec.getX();
float y = vec.getY();
float lenght =(float) Math.hypot(x, y);
return new Vector2f(x*size/lenght, y*size/lenght);
}
/**
*
* @param vec original vector
* @param sizeY size of new vector in perpendicular plane of motion
* @param RiLeRVar determines direction of Y 0 left, 1 right, .5 balanced
* @return vector with all variables applied to it
*/
public static Vector2f V2fPerpendicularDisc(Vector2f vec, float sizeY, float RiLeRVar)
{
float randomdir = Math.random()>RiLeRVar ? 1:-1;
float x = vec.getX();
float y = vec.getY();
float lenght =(float) Math.hypot(x, y);
float xa = x*sizeY*randomdir/lenght;
float ya = y*sizeY*randomdir/lenght;
return new Vector2f(ya, -xa);
}
/**
*
* @param vec original vector
* @param sizeX size of new vector in the direction of motion
* @param sizeY size of new vector in perpendicular plane of motion
* @param RiLeRVar determines direction of Y 0 left, 1 right, .5 balanced
* @return new vector with all variables applied to it
*/
public static Vector2f V2fAdjust(Vector2f vec, float sizeX, float sizeY, float RiLeRVar)
{
float randomdir = Math.random()>RiLeRVar ? 1:-1;
float x = vec.getX();
float y = vec.getY();
float lenght =(float) Math.hypot(x, y);
float xx = x * sizeX / lenght;
float yx = y * sizeX / lenght;
float xy = x*sizeY*randomdir/lenght;
float yy = y*sizeY*randomdir/lenght;
return new Vector2f(xx+yy, yx-xy);
}
/**
*
* @param vec original vector
* @param mult multiplier to apply
* @return new vector with all variables applied to it
*/
public static Vector2f V2fmult(Vector2f vec, float mult)
{
float v1x = vec.getX()*mult;
float v1y = vec.getY()*mult;
Vector2f v1end = new Vector2f(v1x, v1y);
return v1end;
}
package data.scripts.weapons;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.AutofireAIPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.util.IntervalUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.WeaponUtils;
import org.lwjgl.util.vector.Vector2f;
public class StandardAutofireAI implements AutofireAIPlugin {
private final CombatEngineAPI engine = Global.getCombatEngine();
private final WeaponAPI theWeapon;
private Vector2f theTargetLoc;
private ShipAPI manTarget;
private float energyCost = 0.0f;
private boolean isPD = false;
private boolean isBeam = false;
private boolean shouldFire = false;
private boolean holdFire = true;
private CombatEntityAPI theTarget = null;
private final IntervalUtil checkInterval = new IntervalUtil(0.20f, 0.30f);
//Initial setup. We just send over the WeaponAPI in this case; we could send over a lot of other parameters here...
//...if we wanted to build a more general-purpose weapon AI. This is just a simple example of how to build the state machines and aiming logic.
public StandardAutofireAI (WeaponAPI weapon)
{
this.theWeapon = weapon;
}
public static List<ShipAPI> getEnemiesInArcLong(WeaponAPI weapon)
{
List<ShipAPI> enemies = new ArrayList<ShipAPI>();
float range = 10000f;
for (ShipAPI ship : AIUtils.getEnemiesOnMap(weapon.getShip()))
{
if (MathUtils.getDistance(ship, weapon.getLocation()) <= range
&& weapon.distanceFromArc(ship.getLocation()) == 0f)
{
enemies.add(ship);
}
}
return enemies;
}
//This is our sorter, that finds the closest ShipAPI that is in the arc that is not a Drone.
public ShipAPI findBestTarget(WeaponAPI weapon)
{
holdFire = false;
ShipAPI source = weapon.getShip();
if(source != null){
manTarget = source.getShipTarget();
if( manTarget != null
&& !manTarget.isHulk()
&& !(source.getOwner() == manTarget.getOwner()) // not friendly
&& Global.getCombatEngine().isEntityInPlay(manTarget) //Still exists
&& MathUtils.getDistance(manTarget,theWeapon.getLocation()) < theWeapon.getRange()//in range
&& WeaponUtils.isWithinArc(manTarget,theWeapon) // can be engaged
)
{
holdFire = false;
return manTarget;
}
}
//Let's get all of the ShipAPIs in the weapon's arc, sorted by distance
ArrayList<ShipAPI> possibleTarget = new ArrayList<ShipAPI>();
//lazylib is convenient, gets everything you want in Searchrange
List nearbyEnemies = WeaponUtils.getEnemiesInArc(weapon);
Collections.sort(nearbyEnemies,new CollectionUtils.SortEntitiesByDistance(weapon.getLocation()));
//filters through the ship list and removes Drones
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext(); )
{
ShipAPI enemy = (ShipAPI) iterMe.next();
//criteria to add to list (you can put anything that will return a true/false here)
if (!enemy.isDrone())
{
possibleTarget.add(enemy); //add to posibles
holdFire = false;
}
}
if(possibleTarget.isEmpty())//No valid target found, so hunt for nearest enemy in arc and aim at it
{
nearbyEnemies = getEnemiesInArcLong(weapon);
Collections.sort(nearbyEnemies,new CollectionUtils.SortEntitiesByDistance(weapon.getLocation()));
possibleTarget.addAll(nearbyEnemies);
holdFire = true;
}
//Returns the closest enemy ship.
if(!possibleTarget.isEmpty()){
return possibleTarget.get(0);
}else{
return null;
}
}
private Vector2f getFiringSolution(Vector2f source, Vector2f destLocation, Vector2f destVelocity, float projVel)
{
float tx = destLocation.getX() - source.getX();
float ty = destLocation.getY() - source.getY();
float tvx = destVelocity.getX();
float tvy = destVelocity.getY();
// Get quadratic equation components
float a = tvx*tvx + tvy*tvy - projVel*projVel;
float b = 2 * (tvx * tx + tvy * ty);
float c = tx*tx + ty*ty;
// Solve quadratic
Vector2f ts = quad(a, b, c); // See quad(), below
// Find smallest positive solution
Vector2f finalSolution = new Vector2f();
if(ts != null)
{
float t0 = ts.getX();
float t1 = ts.getY();
float t = Math.min(t0, t1);
if (t < 0) t = Math.max(t0, t1);
if (t > 0)
{
finalSolution.setX(destLocation.getX() + destVelocity.getX()*t);
finalSolution.setY(destLocation.getY() + destVelocity.getY()*t);
}
}
return finalSolution;
}
private Vector2f quad(float a, float b,float c) {
Vector2f sol = new Vector2f();
if (Math.abs(a) < 1e-6) {
if (Math.abs(b) < 1e-6) {
if(Math.abs(c) < 1e-6){
sol.set(0f,0f);
} else {
sol = null;
}
} else {
sol.setX(-c/b);
sol.setY(-c/b);
}
} else {
float disc = b*b - 4*a*c;
if (disc >= 0f) {
disc = (float) Math.sqrt(disc);
a = 2*a;
sol.set((-b-disc)/a, (-b+disc)/a);
}
}
return sol;
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
@Override
public void advance(float amount) {
if (engine.isPaused()) return;
//If neither of the above are true, go ahead and increment our checkInterval
checkInterval.advance(amount);
if(checkInterval.intervalElapsed()){
findBestTarget(theWeapon);//Every once in awhile, refresh the target (gets manual targets)
}
isPD = theWeapon.hasAIHint(WeaponAPI.AIHints.PD);
isBeam = theWeapon.isBeam() || theWeapon.isBurstBeam();
energyCost = theWeapon.getFluxCostToFire();
if(energyCost < 1.0f){
energyCost = 0.0f;
}
if((theWeapon.getShip().getFluxTracker().getCurrFlux() + energyCost) > theWeapon.getShip().getFluxTracker().getMaxFlux()){
theTarget = null;
return;
}
//UPDATE TARGETS
//If we don't have a target, find one.
//If our target is dead, out of range or removed from the sim or has switched sides, find a new target.
ShipAPI targShip = null;
if(theTarget instanceof ShipAPI) targShip = (ShipAPI) theTarget;
if ( theTarget == null // unset
|| (targShip != null && targShip.isHulk())
|| (theWeapon.getShip().getOwner() == theTarget.getOwner()) //Friendly!
|| !Global.getCombatEngine().isEntityInPlay(theTarget) //Completely removed from play
|| MathUtils.getDistance(theTarget,theWeapon.getLocation()) > theWeapon.getRange() // Out of range
|| !WeaponUtils.isWithinArc(theTarget,theWeapon) // Has left the arc
|| holdFire //We have a distant target, but we need to update and not fire
) //Is no longer in range
{
theTarget = null;
shouldFire = false;
theTarget = findBestTarget(theWeapon);
if(theTarget != null) theTargetLoc = theTarget.getLocation();//Pre-aim at distant targets
return;
}
if(theTarget != null && !holdFire){
if(isBeam){
theTargetLoc = theTarget.getLocation();
//Are we aimed at the target?
float AngleToEnemy = VectorUtils.getAngle(theWeapon.getLocation(), theTargetLoc);
float angleToTarget = getAngleDifference(theWeapon.getCurrAngle(), AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
shouldFire = AbsAngD < 1.5f;//a little loose for beams, to give them pre-fire
} else{
theTargetLoc = getFiringSolution(theWeapon.getLocation(),theTarget.getLocation(),theTarget.getVelocity(),theWeapon.getProjectileSpeed());
//Are we aimed at the target?
float AngleToEnemy = VectorUtils.getAngle(theWeapon.getLocation(), theTargetLoc);
float angleToTarget = getAngleDifference(theWeapon.getCurrAngle(), AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
shouldFire = AbsAngD < 1f;//tighter for non-beams, since they need to lead well
}
} else {
shouldFire = false;
}
}
@Override
public boolean shouldFire() {
return shouldFire;
}
@Override
public void forceOff() {
//nothing
}
@Override
public Vector2f getTarget() {
if(theTarget != null){
return theTargetLoc;
}
return null;
}
@Override
public ShipAPI getTargetShip() {
if(theTarget instanceof ShipAPI) return (ShipAPI) theTarget;
return null;
}
@Override
public WeaponAPI getWeapon() {
return theWeapon;
}
}
package data.scripts.weapons;
import java.awt.geom.Line2D;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.AutofireAIPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.geom.AffineTransform;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.WeaponUtils;
import org.lwjgl.util.vector.Vector2f;
public class StandardAutofireAI implements AutofireAIPlugin {
private final CombatEngineAPI engine = Global.getCombatEngine();
private final WeaponAPI theWeapon;
private final float barrelLength;
private Vector2f theTargetLoc;
private ShipAPI manTarget;
private float energyCost = 0.0f;
private boolean isPD = false;
private boolean isBeam = false;
private boolean shouldFire = false;
private boolean holdFire = true;
private CombatEntityAPI theTarget = null;
private final IntervalUtil checkInterval = new IntervalUtil(0.20f, 0.30f);
//Initial setup. If waitTime = 0f, the guidance begins one frame after launch.
//Otherwise, drifts until waitTime = 0f (useful for anime-style launch systems)
public StandardAutofireAI (WeaponAPI weapon, float theBarrelLength)
{
this.theWeapon = weapon;
this.barrelLength = theBarrelLength;
}
public static List<ShipAPI> getEnemiesInArcLong(WeaponAPI weapon)
{
List<ShipAPI> enemies = new ArrayList<ShipAPI>();
float range = 10000f;
for (ShipAPI ship : AIUtils.getEnemiesOnMap(weapon.getShip()))
{
if (MathUtils.getDistance(ship, weapon.getLocation()) <= range
&& weapon.distanceFromArc(ship.getLocation()) == 0f)
{
enemies.add(ship);
}
}
return enemies;
}
//This is our sorter, that finds the closest ShipAPI that is in the arc that is not a Drone.
public ShipAPI findBestTarget(WeaponAPI weapon)
{
holdFire = false;
ShipAPI source = weapon.getShip();
if(source != null){
manTarget = source.getShipTarget();
if( manTarget != null
&& !manTarget.isHulk()
&& !(source.getOwner() == manTarget.getOwner()) // not friendly
&& Global.getCombatEngine().isEntityInPlay(manTarget) //Still exists
&& MathUtils.getDistance(manTarget,theWeapon.getLocation()) < theWeapon.getRange()//in range
&& WeaponUtils.isWithinArc(manTarget,theWeapon) // can be engaged
)
{
holdFire = false;
return manTarget;
}
}
//Let's get all of the ShipAPIs in the weapon's arc, sorted by distance
ArrayList<ShipAPI> possibleTarget = new ArrayList<ShipAPI>();
//lazylib is convenient, gets everything you want in Searchrange
List nearbyEnemies = WeaponUtils.getEnemiesInArc(weapon);
Collections.sort(nearbyEnemies,new CollectionUtils.SortEntitiesByDistance(weapon.getLocation()));
//filters through the ship list and removes Drones
for(Iterator iterMe = nearbyEnemies.iterator(); iterMe.hasNext(); )
{
ShipAPI enemy = (ShipAPI) iterMe.next();
//criteria to add to list (you can put anything that will return a true/false here)
if (!enemy.isDrone())
{
possibleTarget.add(enemy); //add to posibles
holdFire = false;
}
}
if(possibleTarget.isEmpty())//No valid target found, so hunt for nearest enemy in arc and aim at it
{
nearbyEnemies = getEnemiesInArcLong(weapon);
Collections.sort(nearbyEnemies,new CollectionUtils.SortEntitiesByDistance(weapon.getLocation()));
possibleTarget.addAll(nearbyEnemies);
holdFire = true;
}
//Returns the closest enemy ship.
if(!possibleTarget.isEmpty()){
return possibleTarget.get(0);
}else{
return null;
}
}
private Vector2f getFiringSolution(Vector2f source, Vector2f destLocation, Vector2f destVelocity, float projVel)
{
float tx = destLocation.getX() - source.getX();
float ty = destLocation.getY() - source.getY();
float tvx = destVelocity.getX();
float tvy = destVelocity.getY();
// Get quadratic equation components
float a = tvx*tvx + tvy*tvy - projVel*projVel;
float b = 2 * (tvx * tx + tvy * ty);
float c = tx*tx + ty*ty;
// Solve quadratic
Vector2f ts = quad(a, b, c); // See quad(), below
// Find smallest positive solution
Vector2f finalSolution = new Vector2f();
if(ts != null)
{
float t0 = ts.getX();
float t1 = ts.getY();
float t = Math.min(t0, t1);
if (t < 0) t = Math.max(t0, t1);
if (t > 0)
{
finalSolution.setX(destLocation.getX() + destVelocity.getX()*t);
finalSolution.setY(destLocation.getY() + destVelocity.getY()*t);
}
}
return finalSolution;
}
private Vector2f quad(float a, float b,float c) {
Vector2f sol = new Vector2f();
if (Math.abs(a) < 1e-6) {
if (Math.abs(b) < 1e-6) {
if(Math.abs(c) < 1e-6){
sol.set(0f,0f);
} else {
sol = null;
}
} else {
sol.setX(-c/b);
sol.setY(-c/b);
}
} else {
float disc = b*b - 4*a*c;
if (disc >= 0f) {
disc = (float) Math.sqrt(disc);
a = 2f*a;
sol.set((-b-disc)/a, (-b+disc)/a);
}
}
return sol;
}
// Will be in next LazyLib version
public static float getAngleDifference(float angle1, float angle2)
{
float distance = (angle2 - angle1) + 180f;
distance = (distance / 360.0f);
distance = ((distance - (float) Math.floor(distance)) * 360f) - 180f;
return distance;
}
@Override
public void advance(float amount) {
if (engine.isPaused()) return;
//If neither of the above are true, go ahead and increment our checkInterval
checkInterval.advance(amount);
if(checkInterval.intervalElapsed()){
findBestTarget(theWeapon);//Every once in awhile, refresh the target (gets manual targets)
}
isPD = theWeapon.hasAIHint(WeaponAPI.AIHints.PD);
isBeam = theWeapon.isBeam() || theWeapon.isBurstBeam();
energyCost = theWeapon.getFluxCostToFire();
if(energyCost < 1.0f){
energyCost = 0.0f;
}
if((theWeapon.getShip().getFluxTracker().getCurrFlux() + energyCost) > theWeapon.getShip().getFluxTracker().getMaxFlux()){
theTarget = null;
return;
}
//UPDATE TARGETS
//If we don't have a target, find one.
//If our target is dead, out of range or removed from the sim or has switched sides, find a new target.
ShipAPI targShip = null;
if(theTarget instanceof ShipAPI) targShip = (ShipAPI) theTarget;
if ( theTarget == null // unset
|| (theWeapon.getShip().getOwner() == theTarget.getOwner()) //Friendly!
|| !Global.getCombatEngine().isEntityInPlay(theTarget) //Completely removed from play
|| MathUtils.getDistance(theTarget,theWeapon.getLocation()) > theWeapon.getRange() // Out of range
|| !WeaponUtils.isWithinArc(theTarget,theWeapon) // Has left the arc
|| barrelLength == -500f //catches screwed-up barrelLength
|| holdFire //We have a distant target, but we need to update and not fire
) //Is no longer in range
{
theTarget = null;
shouldFire = false;
theTarget = findBestTarget(theWeapon);
if(theTarget != null) theTargetLoc = theTarget.getLocation();//Pre-aim at distant targets
return;
}
if(targShip != null){
if(targShip.getPhaseCloak() != null){
if(targShip.getPhaseCloak().isOn() || targShip.getPhaseCloak().isActive()){
if(targShip.getPhaseCloak().getId().contains("phasecloak")){
theTarget = null;
shouldFire = false;
theTarget = findBestTarget(theWeapon);
if(theTarget != null) theTargetLoc = theTarget.getLocation();//Pre-aim at distant targets
return;
}
}
}
if(targShip.isHulk()){
theTarget = null;
shouldFire = false;
theTarget = findBestTarget(theWeapon);
if(theTarget != null) theTargetLoc = theTarget.getLocation();//Pre-aim at distant targets
return;
}
}
if(theTarget != null && !holdFire){
if(isBeam){
theTargetLoc = theTarget.getLocation();
//Are we aimed at the target?
float AngleToEnemy = VectorUtils.getAngle(theWeapon.getLocation(), theTargetLoc);
float angleToTarget = getAngleDifference(theWeapon.getCurrAngle(), AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
shouldFire = AbsAngD < 1.5f;//a little loose for beams, to give them pre-fire
} else{
//For projectile guns, getting the correct point to shoot at is considerably more complicated than for laser beams (duh!)
//To do this, we need to take the barrel's length into account (at least right now) and we also need to factor the relative velocity of
//the shooter and target. The first part of this code rotates a line segment so that we have the right offset from the barrel
//once we have that and combVelocity (our relative velocity) we can then do the quadratic equation needed to get a valid aiming point.
//Many thanks to Louis Wasserman for this example of simple line segment rotation on Stack Overflow.
double[] pt = {(double) barrelLength, (double) 0f};
AffineTransform.getRotateInstance(Math.toRadians(theWeapon.getCurrAngle()), 0, 0).transform(pt, 0, pt, 0, 1); // specifying to use this double[] to hold coords
float newX = (float) pt[0];
float newY = (float) pt[1];
Vector2f offset = new Vector2f(theWeapon.getLocation().getX() + newX, theWeapon.getLocation().getY() + newY);
Vector2f combVelocity = new Vector2f(theTarget.getVelocity().getX() - theWeapon.getShip().getVelocity().getX(),theTarget.getVelocity().getY() -theWeapon.getShip().getVelocity().getY());
theTargetLoc = getFiringSolution(offset,theTarget.getLocation(),combVelocity,theWeapon.getProjectileSpeed());
//Are we aimed at the target?
float AngleToEnemy = VectorUtils.getAngle(theWeapon.getLocation(), theTargetLoc);
float angleToTarget = getAngleDifference(theWeapon.getCurrAngle(), AngleToEnemy);
float AbsAngD = Math.abs(angleToTarget);
shouldFire = AbsAngD < 1f;//tighter for non-beams, since they need to lead well
}
} else {
shouldFire = false;
}
}
@Override
public boolean shouldFire() {
return shouldFire;
}
@Override
public void forceOff() {
//nothing
}
@Override
public Vector2f getTarget() {
if(theTarget != null){
return theTargetLoc;
}
return null;
}
@Override
public ShipAPI getTargetShip() {
if(theTarget instanceof ShipAPI) return (ShipAPI) theTarget;
return null;
}
@Override
public WeaponAPI getWeapon() {
return theWeapon;
}
}
package data.missions.randombattle1;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.fleet.FleetGoal;
import com.fs.starfarer.api.fleet.FleetMemberType;
import com.fs.starfarer.api.mission.FleetSide;
import com.fs.starfarer.api.mission.MissionDefinitionAPI;
import com.fs.starfarer.api.mission.MissionDefinitionPlugin;
import java.io.IOException;
import java.util.Iterator;
import org.json.JSONException;
import org.json.JSONObject;
import org.lazywizard.lazylib.MathUtils;
public class MissionDefinition implements MissionDefinitionPlugin {
//This string is used to follow the steps as we attempt to get data out of JSON
private String nameString = "blah";
private void generateFleet(FleetSide side, MissionDefinitionAPI api) {
//If we only get this far, show this upon crashing
nameString = "foobar";
String[] possibleFactionNames = new String[] {"glaug", "hegemony", "tritachyon", "independent", "punkjunkers", "pirates", "exigency"};
String factionName = possibleFactionNames[(int) MathUtils.getRandomNumberInRange(0, possibleFactionNames.length - 1)];
if(factionName.contains("glaug")){
api.addToFleet(side, "glaug_battleship_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("hegemony")){
api.addToFleet(side, "onslaught_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("tritachyon")){
api.addToFleet(side, "odyssey_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("pirates")){
api.addToFleet(side, "acanthus_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("independent")){
api.addToFleet(side, "conquest_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("exigency")){
api.addToFleet(side, "exigency_carrier_Standard", FleetMemberType.SHIP, true);
}else if(factionName.contains("punkjunkers")){
api.addToFleet(side, "pj_queen_Standard", FleetMemberType.SHIP, true);
}else{
api.addToFleet(side, "conquest_Standard", FleetMemberType.SHIP, true);
}
try {
JSONObject json = Global.getSettings().loadJSON("data/world/factions/" + factionName + ".faction");
//Success, thus far
nameString = "Parsed JSON";
String test = json.getString("shipNamePrefix");
//We've gotten this far...
nameString = test;
JSONObject jsonTwo = json.getJSONObject("fleetCompositions").getJSONObject("StationAssaultFleet").getJSONObject("ships");
Iterator<String> keysIterator = jsonTwo.keys();
int min = 0;
int max = 0;
while (keysIterator.hasNext())
{
//Are we there yet?
nameString = "" + min + max;
test = (String)keysIterator.next();
min = jsonTwo.getJSONArray(test).getInt(0);
max = jsonTwo.getJSONArray(test).getInt(1);
//We're there now :-)
nameString = "" + min + max;
if(test.contains("wing")){
for(int i = 0; i < MathUtils.getRandomNumberInRange(min,max)+1; i++){
api.addToFleet(side, test, FleetMemberType.FIGHTER_WING, false);
}
}else {
for(int i = 0; i < MathUtils.getRandomNumberInRange(min,max)+1; i++){
api.addToFleet(side, test, FleetMemberType.SHIP, false);
}
}
}
//We've succeeded; remove the nameString
nameString = "";
} catch (IOException ex) {
//Error; couldn't find the file
nameString = "no IO";
} catch (JSONException ex) {
//Error; bad JSON command
nameString = "bad JSON";
}
}
@Override
public void defineMission(MissionDefinitionAPI api) {
// Set up the fleets so we can add ships and fighter wings to them.
// In this scenario, the fleets are attacking each other, but
// in other scenarios, a fleet may be defending or trying to escape
api.initFleet(FleetSide.PLAYER, "ISS", FleetGoal.ATTACK, false, 5);
api.initFleet(FleetSide.ENEMY, "ISS", FleetGoal.ATTACK, true, 5);
// Set a small blurb for each fleet that shows up on the mission detail and
// mission results screens to identify each side.
api.setFleetTagline(FleetSide.PLAYER, "Your forces");
api.setFleetTagline(FleetSide.ENEMY, "Enemy forces");
// Set up the fleets
generateFleet(FleetSide.PLAYER, api);
generateFleet(FleetSide.ENEMY, api);
// These show up as items in the bulleted list under
// "Tactical Objectives" on the mission detail screen
api.addBriefingItem("Defeat all enemy forces " + nameString);
// Set up the map.
float width = 18000f;
float height = 14000f;
api.initMap((float)-width/2f, (float)width/2f, (float)-height/2f, (float)height/2f);
float minX = -width/2;
float minY = -height/2;
for (int i = 0; i < 50; i++) {
float x = (float) Math.random() * width - width/2;
float y = (float) Math.random() * height - height/2;
float radius = 100f + (float) Math.random() * 400f;
api.addNebula(x, y, radius);
}
}
}
Thread-5] ERROR com.fs.starfarer.combat.O0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO - java.lang.RuntimeException: Error compiling [data.scripts.plugins.ExplosiveOnHitEffect]
java.lang.RuntimeException: Error compiling [data.scripts.plugins.ExplosiveOnHitEffect]
at com.fs.starfarer.loading.scripts.ScriptStore$3.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: Parsing compilation unit "com.fs.starfarer.loading.superThread-5] ERROR com.fs.starfarer.combat.O0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO - java.lang.RuntimeException: Error compiling [data.scripts.plugins.ExplosiveOnHitEffect]
java.lang.RuntimeException: Error compiling [data.scripts.plugins.ExplosiveOnHitEffect]
at com.fs.starfarer.loading.scripts.ScriptStore$3.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: Parsing compilation unit "com.fs.starfarer.loading.super$1@112bba0"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:180)
at org.codehaus.janino.IClassLoader.loadIClass(IClassLoader.java:158)
at org.codehaus.janino.JavaSourceClassLoader.generateBytecodes(JavaSourceClassLoader.java:199)
at org.codehaus.janino.JavaSourceClassLoader.findClass(JavaSourceClassLoader.java:164)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 2 more
Caused by: org.codehaus.commons.compiler.CompileException: Source file "data/scripts/plugins/ExplosiveOnHitEffect.java" does not declare class "data.scripts.plugins.ExplosiveOnHitEffect"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:165)
... 7 more
@112bba0"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:180)
at org.codehaus.janino.IClassLoader.loadIClass(IClassLoader.java:158)
at org.codehaus.janino.JavaSourceClassLoader.generateBytecodes(JavaSourceClassLoader.java:199)
at org.codehaus.janino.JavaSourceClassLoader.findClass(JavaSourceClassLoader.java:164)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 2 more
Caused by: org.codehaus.commons.compiler.CompileException: Source file "data/scripts/plugins/ExplosiveOnHitEffect.java" does not declare class "data.scripts.plugins.ExplosiveOnHitEffect"
at org.codehaus.janino.JavaSourceIClassLoader.findIClass(JavaSourceIClassLoader.java:165)
... 7 more
Source file "data/scripts/plugins/ExplosiveOnHitEffect.java" does not declare class "data.scripts.plugins.ExplosiveOnHitEffect"
That pretty much is the start of your problem
If you note the class itself is in fact called.
public class ExplosiveOnHitEffectWithAOE implements OnHitEffectPlugin {
So, either rename the java file, or refactor the code with the changed class name.
// Scan all shots on the map for armor piercing projectiles
for (Iterator itproj = engine.getProjectiles().iterator(); itproj.hasNext()<img src="http://fractalsoftworks.com/forum/Smileys/default/wink.gif" alt="Wink" border="0">
{
// Scan all shots on the map for armor piercing projectiles
for (Iterator itproj = engine.getProjectiles().iterator(); itproj.hasNext(); ) //without the space
// <img src="http://fractalsoftworks.com/forum/Smileys/default/wink.gif" alt="Wink" border="0">
{
for (Iterator iter2 = toCheck.iterator(); iter2.hasNext()<img src="http://fractalsoftworks.com/forum/Smileys/default/wink.gif" alt="Wink" border="0">
106266 [Thread-5] ERROR com.fs.starfarer.combat.O0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO - java.lang.NullPointerException
java.lang.NullPointerException
at data.scripts.plugins.CriticalHit.critSucessfull(CriticalHit.java:198)
at data.scripts.plugins.CriticalHit.advance(CriticalHit.java:178)
at com.fs.starfarer.title.ooOO.K$Oo.o00000(Unknown Source)
at com.fs.starfarer.combat.oOOO.new.super(Unknown Source)
at com.fs.starfarer.combat.CombatEngine.advance(Unknown Source)
at com.fs.starfarer.combat.G.??00(Unknown Source)
at com.fs.oOOO.A.?0000(Unknown Source)
at com.fs.starfarer.combat.O0OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.o00000(Unknown Source)
at com.fs.starfarer.StarfarerLauncherjava.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Dumb question: how do I make a mod use these scripts again?
Edit: This happens when I try to implement the AOE explosion code:
Same thing also happens with the smart PD AI and Lightning Storm scripts.
Dumb question: how do I make a mod use these scripts again?
Edit: This happens when I try to implement the AOE explosion code:
Same thing also happens with the smart PD AI and Lightning Storm scripts.
If you're going for AOE weapons, just use the PROXIMITY_CHARGE weapon behaviour (you can have a MIRV missile fire proximity charges for AOE missiles).
public static final String LRPDai_MISSILE_ID = "SM_lrmPD_m"; //name of the missile that will use that AI
public static final String BOMBARDai_MISSILE_ID = "SM_SCbombard_m";//""
@Override
public PluginPick pickMissileAI(MissileAPI missile, //picks the ai
ShipAPI launchingShip)
{
if (LRPDai_MISSILE_ID.equals(missile.getProjectileSpecId())) //missile
{
return new PluginPick(new LRPDai(missile), CampaignPlugin.PickPriority.MOD_SPECIFIC); //Ai for that missile
}
if (BOMBARDai_MISSILE_ID.equals(missile.getProjectileSpecId())) //another missile
{
return new PluginPick(new BOMBARDai(missile), CampaignPlugin.PickPriority.MOD_SPECIFIC); //another ai
}
return null;
}
package data.scripts.sfx;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
public class weaponSide implements EveryFrameWeaponEffectPlugin {
private boolean runOnce = false;
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
if (engine.isPaused()) return;
if(runOnce == false){
if (weapon.getShip().getOwner() == 0 && weapon.getLocation().getX() < weapon.getShip().getLocation().getX()
|| weapon.getShip().getOwner() == 1 && weapon.getLocation().getX() > weapon.getShip().getLocation().getX()){
SpriteAPI theSprite = weapon.getSprite();
theSprite.setWidth(-theSprite.getWidth());
theSprite.setCenter(-theSprite.getCenterX(),theSprite.getCenterY());
}
runOnce = true;
}
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.*;
/*
to be put on a weapon that will execute script on a ship
multiple weapons will increase the efficiency
*/
public class AbsorptionArmor implements EveryFrameWeaponEffectPlugin
{
//engine
private CombatEngineAPI engine= Global.getCombatEngine();
//////////////////////////////////////////////////////////////////////////////////////////////
//timers
private IntervalUtil fasttimer = new IntervalUtil(.1f, .11f);
private IntervalUtil slowtimer = new IntervalUtil(.15f, .16f);
//////////////////////////////////////////////////////////////////////////////////////////////
//main armor effect
float lastcycle=0;
@Override
public void advance(float v, CombatEngineAPI engineAPI, WeaponAPI weaponAPI)
{
//ship
ShipAPI ship;
ship=weaponAPI.getShip();
//////////////////////////////////////////////////////////////////////////////////////////////
//stats of system
float FluPerAPoint = 10; //how much 1 armor point is worth in terms of flux
//float ReBaArmRate = .005f; //how fast armor rebalanced max 1 for instant
float MaxFlux = .8f; //cap for this system being active
float RegenRate = .002f; //rate armor regenerates as a decimal
float activeRate = .005f; //how fast active armor balancer works
float MinHealthActive = .4f;//minimum health for active armor sharing
float weaponsizemult = 1f;
//weapon size multiplier
WeaponAPI.WeaponSize weapsize = weaponAPI.getSize();
if (weapsize.equals(WeaponAPI.WeaponSize.SMALL)) {weaponsizemult=.5f;}
if (weapsize.equals(WeaponAPI.WeaponSize.MEDIUM)){weaponsizemult=1f;}
if (weapsize.equals(WeaponAPI.WeaponSize.LARGE)) {weaponsizemult=2f;}
//////////////////////////////////////////////////////////////////////////////////////////////
//game is paused dont do anything //weapon is disabled ""
if (engine.isPaused() || weaponAPI.isDisabled())
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if(ship.getSystem().isActive()) optional link to ship system
{
//advance timers
slowtimer.advance(v);
fasttimer.advance(v);
//////////////////////////////////////////////////////////////////////////////////////////////
//main code
if (fasttimer.intervalElapsed())
{
//stuff that is used alot
ArmorGridAPI armorgrid = ship.getArmorGrid();
float armorrating = armorgrid.getArmorRating();
float MaxCell = armorgrid.getMaxArmorInCell();
//////////////////////////////////////////////////////////////////////////////////////////
//armor grid stats
int maxX = armorgrid.getLeftOf()+armorgrid.getRightOf();
int maxY = armorgrid.getAbove()+armorgrid.getBelow();
//////////////////////////////////////////////////////////////////////////////////////////
//avarage armor of ship hull
float armorcells = 0; //number of cells ship has
for (int X=0; X<maxX; X++){for (int Y=0; Y<maxY; Y++){armorcells++;}}
//float ReBalArmor = curarmor/armorcells;
//////////////////////////////////////////////////////////////////////////////////////////
//adjusted stats
float adjust = weaponsizemult*Math.min(125 / armorcells, 4); //max increase of rate (prevents 100x rate on small ship)
FluPerAPoint = 10; //how much 1 armor point is worth in terms of flux
//float ReBaArmRate = .005f; //how fast armor rebalanced max 1 for instant
MaxFlux = .8f; //cap for this system being active
RegenRate = .002f*adjust; //rate armor regenerates as a decimal
activeRate = .005f*adjust; //how fast active armor balancer works
MinHealthActive = .4f*adjust;//minimum health for active armor sharing
//////////////////////////////////////////////////////////////////////////////////////////
//basic armor state of ship
float curarmor = getTotalArmor(ship);
//float ArmLost = armorgrid.getArmorRating()-curarmor; //how much armor was damaged
//////////////////////////////////////////////////////////////////////////////////////////
//calculate regen rate based on flux (prevents cells from filling up sequentially at low flux)
float FluxRemaining = (ship.getFluxTracker().getMaxFlux()*MaxFlux) - ship.getFluxTracker().getCurrFlux();
//float FluxToRepairMax = ArmLost * FluPerAPoint;
float NormRepPerFrame = (MaxCell * RegenRate)
*((MaxFlux-ship.getFluxTracker().getFluxLevel())/MaxFlux);//aditional level of repair decrease
//float FluxToRepairNorm = NormRepPerFrame * FluPerAPoint * armorcells;
//float FluxForRep = (FluxToRepairMax < FluxToRepairNorm ? FluxToRepairMax : FluxToRepairNorm);
//easier, more accurate (compares the cost to repair in last cycle to amount of flux left)
if (lastcycle==0) {lastcycle=NormRepPerFrame*armorcells*FluPerAPoint;}
float FluxForRep = lastcycle;
float FluxToRepairNorm = lastcycle;
float RepRate = (FluxForRep<FluxRemaining ? NormRepPerFrame:NormRepPerFrame*(FluxRemaining/FluxToRepairNorm));
//////////////////////////////////////////////////////////////////////////////////////////
//armor manager
float next=0;
lastcycle=0; //clears lastcycle
//active cycle (needs to be separate)
//lets damaged cells take armor from nearby healthy cells
for (int X=0; X<maxX; X++) //
{ //cycle through all armor cells on ship
for (int Y=0; Y<maxY; Y++) //
{
float cur = armorgrid.getArmorValue(X, Y); //health of current cell
//Active ReBalArmor
//mover armor from nearby cells to damaged ones
//can be tied to an if statement
{
//take armor from nearby healthy cells
float Forwardsum=0;
for (int Xa=(X==0? X:X-1); Xa<maxX && Xa>=0 && Xa<=X+1; Xa++)
{
for (int Ya=(Y==0? Y:Y-1); Ya<maxY && Ya>=0 && Ya<=Y+1; Ya++)
{
float cell = armorgrid.getArmorValue(Xa, Ya);
if (cell>cur && armorgrid.getArmorFraction(Xa, Ya)>MinHealthActive)
{
float diff = (cell - cur)*activeRate;
next = (cell-diff);
armorgrid.setArmorValue(Xa, Ya, next>0? next:0);
cur+=diff;
//ship.getFluxTracker().increaseFlux(diff*FluPerAPoint*.1f, true);
//uses 1/10th of the normal flux to move armor around (too costly in flux)
}
}
}
next = cur;
armorgrid.setArmorValue(X, Y, next<MaxCell? next:MaxCell); //add it to cell
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
//passive cycle
for (int X=0; X<maxX; X++) //
{ //cycle through all armor cells on ship
for (int Y=0; Y<maxY; Y++) //
{
float cur = armorgrid.getArmorValue(X, Y); //health of current cell
//only do repair if cell health is more then 0 prevents immortal ship syndrome
if (cur>0)
{
//regen armor
if (cur<MaxCell)
{
next = cur + (cur/MaxCell)*RepRate; //how much armor should be regenerated
armorgrid.setArmorValue(X, Y, next<MaxCell? next:MaxCell);
float fluxuse = (next - cur) * FluPerAPoint;
ship.getFluxTracker().increaseFlux(fluxuse, true);
lastcycle+=fluxuse;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
//test
if (slowtimer.intervalElapsed())
{
engine.addFloatingText(ship.getLocation(), "armorcells " + armorcells + " armorrating " + armorrating + " curentarmor " + curarmor +" MaxCell "+ MaxCell +" waeposizemult "+weaponsizemult, 20f, Color.RED, ship, 1f, .5f);
}
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
//gets total armor of a ship
public static float getTotalArmor(ShipAPI ship)
{
ArmorGridAPI armorgrid = ship.getArmorGrid();
float sum=0;
int maxX = armorgrid.getLeftOf()+armorgrid.getRightOf();
int maxY = armorgrid.getAbove()+armorgrid.getBelow();
for (int X=0; X<maxX; X++)
{
for (int Y=0; Y<maxY; Y++)
{
sum += armorgrid.getArmorValue(X, Y);
}
}
return sum;
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import java.awt.*;
/*
to be put on a weapon that will execute script on a ship
multiple weapons will increase the efficiency
*/
public class AuxilaryFluxVents implements EveryFrameWeaponEffectPlugin
{
//engine
private CombatEngineAPI engine= Global.getCombatEngine();
//////////////////////////////////////////////////////////////////////////////////////////////
//timers
private IntervalUtil fasttimer = new IntervalUtil(.05f, .06f);
private IntervalUtil slowtimer = new IntervalUtil(.15f, .16f);
//////////////////////////////////////////////////////////////////////////////////////////////
//main armor effect
boolean fired=false;
@Override
public void advance(float v, CombatEngineAPI engineAPI, WeaponAPI weaponAPI)
{
//ship
ShipAPI ship;
ship=weaponAPI.getShip();
//////////////////////////////////////////////////////////////////////////////////////////////
//weapon size multiplier
float weaponsizemult = 1;
WeaponAPI.WeaponSize weapsize = weaponAPI.getSize();
if (weapsize.equals(WeaponAPI.WeaponSize.SMALL)) {weaponsizemult=.5f;}
if (weapsize.equals(WeaponAPI.WeaponSize.MEDIUM)){weaponsizemult=1f;}
if (weapsize.equals(WeaponAPI.WeaponSize.LARGE)) {weaponsizemult=2f;}
//stats of system
float rate = .01f*weaponsizemult; //how fast vent happens (20x this number)
float minflux = .1f; //lowest value of flux it can get to
float maxflux = .95f;
FluxTrackerAPI flux = ship.getFluxTracker();
//////////////////////////////////////////////////////////////////////////////////////////////
//game is paused dont do anything
if (engine.isPaused())
{
return;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if(ship.getSystem().isActive()) optional link to ship system
{
//advance timers
//slowtimer.advance(v);
fasttimer.advance(v);
if (fasttimer.intervalElapsed())
{
//////////////////////////////////////////////////////////////////////////////////////////////
//controls for flux vent
if (!fired) //if weapon has not fired
{
//if flux is to high fire
if ((ship.getFluxTracker().getFluxLevel()>maxflux && weaponAPI.getAmmo()>0))
{weaponAPI.isFiring();fired=true;
weaponAPI.setRemainingCooldownTo(100);
weaponAPI.setAmmo(weaponAPI.getAmmo()-1);
flux.setCurrFlux(flux.getMaxFlux());}//forces all other systems on ship on
//record that you fired
if (weaponAPI.isFiring()){fired=true;}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//main vent code
float FVsec = (flux.getMaxFlux()*.05f)*rate;
if (fired && ship.getFluxTracker().getFluxLevel()>minflux)
{
//prevents firing
weaponAPI.setRemainingCooldownTo(100);
{
flux.decreaseFlux(FVsec);
}
}
//////////////////////////////////////////////////////////////////////////////////////////////
//if venting done
if (fired && ship.getFluxTracker().getFluxLevel()<minflux)
{
fired=false;
weaponAPI.setRemainingCooldownTo(0); //if now at minflux firing is allowed
}
//test
slowtimer.advance(v);
if (slowtimer.intervalElapsed())
{
engine.addFloatingText(ship.getLocation(), "FVsec " + FVsec + "rate " + rate, 20f, Color.RED, ship, 1f, .5f);
}
}
}
}
}
package data.scripts.plugins;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.ArrayList;
import static jar.UtilityKit.V2fPerpendicularDisc;
import static jar.UtilityKit.V2fReSize;
import static org.lwjgl.util.vector.Vector2f.add;
import static org.lwjgl.util.vector.Vector2f.sub;
public class Gravotron implements BeamEffectPlugin {
private IntervalUtil Interval = new IntervalUtil(0.02f, 0.01f);
float MaxPoint=400;
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam)
{
if (beam.getBrightness() >= .5f) {
Interval.advance(amount);
if (Interval.intervalElapsed()) {
Vector2f from = beam.getFrom();
Vector2f to = beam.getTo();
Vector2f dir = sub(to, from, null);
float Bang = MathUtils.getFacing(dir);
ArrayList<CombatEntityAPI> PQueries = new ArrayList<CombatEntityAPI>();
PQueries.addAll(engine.getAsteroids());
PQueries.addAll(engine.getProjectiles());
for (CombatEntityAPI query : PQueries)
{
Vector2f QLoc = query.getLocation();
float DtoL = V2fDistanceToLine(from, to, QLoc, true);
float AbDtoL = Math.abs(DtoL);
if (AbDtoL<MaxPoint
&& (!(query instanceof DamagingProjectileAPI) //catch for just launched weapons
|| (((DamagingProjectileAPI) query).getWeapon()!=null
&& MathUtils.getDistance(((DamagingProjectileAPI) query).getWeapon().getLocation(), QLoc)>40)))
{
float PSadj = (MaxPoint-AbDtoL)/40;
float Sadj = PSadj*PSadj;
Vector2f QVel = query.getVelocity();
engine.addFloatingText(QLoc, "Av-> "+QVel, 20f, Color.RED, beam.getSource(), 1f, .5f);
//towards line
Vector2f ToL = V2fPerpendicularDisc(dir, (Sadj < 100 ? Sadj : 100), (DtoL<0? 1:0));
//towards end
Vector2f ToE = V2fReSize(dir, (Sadj < 100 ? Sadj : 100));
//adjust the velocity
Vector2f Qre = new Vector2f(QVel.getX()+ToE.getX()/*+ToL.getX()*/,
QVel.getY()+ToE.getY()/*+ToL.getY()*/);
QVel.set(Qre);
query.setFacing(query.getFacing()+((MathUtils.getFacing(Qre)-query.getFacing())/2));
/*
//collide with everything
if (query instanceof DamagingProjectileAPI) {
ShipAPI Ne = ((DamagingProjectileAPI) query).getSource();
if (Ne!=null && MathUtils.getDistance(Ne,query.getLocation())>10) {
query.setCollisionClass(CollisionClass.HITS_SHIPS_AND_ASTEROIDS);}}
*/
//engine.addHitParticle(QLoc, ToE, 30f, .5f, 1, new Color(235, 157, 43,120));
}
}
//sparkly beam
while (Math.random()>.3)
{
Vector2f AdjDir = V2fReSize(dir, (float) (Math.hypot(dir.getX(), dir.getY())*Math.random()));
Vector2f Adj2Dir = V2fPerpendicularDisc(dir, (float)(30*Math.random()), .5f);
Vector2f RX = add(add(from,Adj2Dir,null), AdjDir, null);
engine.addHitParticle(RX, V2fReSize(dir,50), 30f, .5f, 1, new Color(40, 136, 235,120));
}
}
}
}
private static double[] V2ftoArray(Vector2f point)
{
double[] array = new double[2];
array[0] = point.getX();
array[1] = point.getY();
return array;
}
private static Vector2f ArraytoV2f(double[] point)
{
return new Vector2f((float)point[0],(float)point[1]);
}
private static double DotProduct(double[] pointA, double[] pointB, double[] pointC)
{
double[] AB = new double[2];
double[] BC = new double[2];
AB[0] = pointB[0] - pointA[0];
AB[1] = pointB[1] - pointA[1];
BC[0] = pointC[0] - pointB[0];
BC[1] = pointC[1] - pointB[1];
double dot = AB[0] * BC[0] + AB[1] * BC[1];
return dot;
}
private static double CrossProduct(double[] pointA, double[] pointB, double[] pointC)
{
double[] AB = new double[2];
double[] AC = new double[2];
AB[0] = pointB[0] - pointA[0];
AB[1] = pointB[1] - pointA[1];
AC[0] = pointC[0] - pointA[0];
AC[1] = pointC[1] - pointA[1];
double cross = AB[0] * AC[1] - AB[1] * AC[0];
return cross;
}
//Compute the distance from A to B
private static double ToLineDistance(double[] pointA, double[] pointB)
{
double d1 = pointA[0] - pointB[0];
double d2 = pointA[1] - pointB[1];
return Math.sqrt(d1 * d1 + d2 * d2);
}
private static double[] ToLineVector(double[] pointA, double[] pointB)
{
double[] ABC = new double[2];
ABC[0] = pointA[0] - pointB[0];
ABC[1] = pointA[1] - pointB[1];
return ABC;
}
//Compute the distance from AB to C
//if isSegment is true, AB is a segment, not a line.
private static double LineToPointDistance2D(double[] pointA, double[] pointB, double[] pointC, boolean isSegment)
{
double dist = CrossProduct(pointA, pointB, pointC) / ToLineDistance(pointA, pointB);
if (isSegment)
{
double dot1 = DotProduct(pointA, pointB, pointC);
if (dot1 > 0)
return ToLineDistance(pointB, pointC);
double dot2 = DotProduct(pointB, pointA, pointC);
if (dot2 > 0)
return ToLineDistance(pointA, pointC);
}
return dist;
}
public static float V2fDistanceToLine(Vector2f from, Vector2f to, Vector2f point, boolean segment)
{
return (float)LineToPointDistance2D(V2ftoArray(from),V2ftoArray(to),V2ftoArray(point), segment);
}
// use reverse perpendicular calculation to get vector to line!!!
}
package data.scripts.MissileAI;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import static jar.UtilityKit.V2fAdjust;
import static jar.UtilityKit.getNearestMissileToPoint;
public class BalundirLightningAI implements MissileAIPlugin{
private final MissileAPI missile;
private ShipAPI launchingShip;
private IntervalUtil MainTimer = new IntervalUtil(2f, 0.5f);
private IntervalUtil ShortTimer = new IntervalUtil(.5f, 0.4f);
private IntervalUtil LongTimer = new IntervalUtil(5f, 0.5f);
private boolean Fire=false;
private boolean Fire2=false;
private boolean FireStop=false;
public BalundirLightningAI(MissileAPI missile, ShipAPI launchingShip)
{
this.missile = missile;
this.launchingShip = launchingShip;
missile.setCollisionClass(CollisionClass.FIGHTER);
}
//main missile AI
private Vector2f MouseT=new Vector2f(0,0);
@Override
public void advance(float amount)
{
CombatEngineAPI engine = Global.getCombatEngine();
/*
// pause when you are not needed
if (missile.isFading() || missile.isFizzling() || engine.isPaused())
{
return;
}
*/
if (MouseT.lengthSquared()==0)
{
MouseT = new Vector2f(launchingShip.getMouseTarget());
if (MouseT.lengthSquared()==0)
{
missile.setSource(AIUtils.getNearestAlly(missile));
}
}
LongTimer.advance(amount);
Vector2f MLoc = missile.getLocation();
if (!FireStop)
{
ShortTimer.advance(amount);
MainTimer.advance(amount);
if (MainTimer.intervalElapsed())
{
Fire = true;
}
if (ShortTimer.intervalElapsed()){
Fire2 = true;
}
if (Fire && Fire2) {
Fire2=false;
engine.spawnProjectile(
launchingShip,
null,
"targetdot",
MouseT,
0,
new Vector2f(0,0));
MissileAPI target = getNearestMissileToPoint(MouseT);
engine.spawnEmpArc(
launchingShip,
missile.getLocation(),
target, target,
DamageType.ENERGY,
0f, //real
0, //emp
10000f, // max range
"tachyon_lance_emp_impact",
5f, // thickness
new Color(255,10,15,255),
new Color(31, 214,255,255)
);
}
}
else
{
Vector2f WLoc = missile.getWeapon().getLocation();
Vector2f DVec = MathUtils.getDirectionalVector(MLoc, WLoc);
//engine.addHitParticle(WLoc, new Vector2f(0,0), 70f, .5f, 1, new Color(235, 157, 43,120));
missile.getVelocity().set(V2fAdjust(DVec, 200, 0, 0));
if (MathUtils.getDistance(MLoc,WLoc)<5)
{
missile.getWeapon().setAmmo(missile.getWeapon().getAmmo()+1);
engine.removeEntity(missile);
}
}
if (LongTimer.intervalElapsed()) {
FireStop=true;
}
}
}
When he lifts his face to the people, I will ban them orderly from the ship, and we aren't even talking about radios the last time I checked, anyway what are the only sprites?
public enum WeaponGroupConfig {
STANDARD, MISSILE, BROADSIDE, MISSILE_BROADSIDE, ALPHA_STRIKE
}
public static final Map weaponTypeOverride;
static {
Map weaponTypeOverrideTemp = new HashMap();
weaponTypeOverrideTemp.put("Missile",
createWeaponList(new String[]{
"annihilator", "annihilatorpod", "swarmer", "pilum",
"exigency_mmm", "exigency_mmm_f"
}));
weaponTypeOverrideTemp.put("No Aim",
createWeaponList(new String[]{
"swarmer", "pilum", "exigency_mmm", "exigency_mmm_f",
"exigency_drumlauncher"
}));
weaponTypeOverrideTemp.put("Anti-Fighter",
createWeaponList(new String[]{
"swarmer", "phasecl"
}));
weaponTypeOverrideTemp.put("Point Defense",
createWeaponList(new String[]{
"phasecl"
}));
weaponTypeOverrideTemp.put("Strike",
createWeaponList(new String[]{
"x"
}));
weaponTypeOverrideTemp.put("Assault",
createWeaponList(new String[]{
"annihilator", "annihilatorpod", "phasecl", "exigency_mmm",
"exigency_mmm_f"
}));
weaponTypeOverrideTemp.put("Close Support",
createWeaponList(new String[]{
"exigency_drumlauncher"
}));
weaponTypeOverrideTemp.put("Fire Support",
createWeaponList(new String[]{
"ssp_redeemer", "pilum"
}));
weaponTypeOverrideTemp.put("Special",
createWeaponList(new String[]{
"x"
}));
weaponTypeOverrideTemp.put("Low Flux", // Cannot be autodetected
createWeaponList(new String[]{
"lightmg", "lightdualmg", "lightmortar", "vulcan",
"fragbomb", "clusterbomb", "bomb", "flak",
"heavymg", "dualflak", "mininglaser", "pdlaser",
"taclaser", "lrpdlaser", "pdburst", "gravitonbeam",
"heavyburst", "guardian", "ssp_tpc", "annihilator",
"annihilatorpod", "swarmer", "phasecl", "pilum",
"exigency_mmm", "exigency_mmm_f"
}));
weaponTypeOverrideTemp.put("High Flux", // Cannot be autodetected
createWeaponList(new String[]{
"mjolnir", "miningblaster", "heavyblaster", "plasma",
"exigency_rr", "exigency_cigenrepeater", "exipirated_rr_p"
}));
weaponTypeOverrideTemp.put("Sustained Beam", // Cannot be autodetected
createWeaponList(new String[]{
"hil", "gravitonbeam", "lrpdlaser", "pdlaser",
"mininglaser", "phasebeam", "taclaser", "exigency_repulsor_beam",
"exigency_lightning_gun"
}));
weaponTypeOverrideTemp.put("Limited Ammo", // Cannot be autodetected
createWeaponList(new String[]{
"amblaster"
}));
weaponTypeOverrideTemp.put("Override", // Disables autodetection
createWeaponList(new String[]{
"ssp_redeemer", "annihilator", "annihilatorpod", "swarmer",
"phasecl", "pilum", "exigency_mmm", "exigency_mmm_f",
"exigency_drumlauncher"
}));
weaponTypeOverride = Collections.unmodifiableMap(weaponTypeOverrideTemp);
}
private static List<String> createWeaponList(String[] variants) {
return Collections.unmodifiableList(Arrays.asList(variants));
}
private static List<String> getWeaponList(String key) {
return (List<String>) weaponTypeOverride.get(key);
}
private static void integrateGroup(WeaponGroupSpec source, WeaponGroupSpec destination) {
for (String slot : source.getSlots()) {
destination.addSlot(slot);
}
destination.setType(source.getType());
destination.setAutofireOnByDefault(source.isAutofireOnByDefault());
}
public static void generateWeaponGroups(ShipVariantAPI variant, WeaponGroupConfig config) {
// Clean up the existing groups
// We go through this whole song and dance rather than just flushing and making new groups for reasons of maximum compatability
for (WeaponGroupSpec group : variant.getWeaponGroups()) {
WeaponGroupSpec clone = group.clone();
for (String slot : clone.getSlots()) {
group.removeSlot(slot);
}
}
// These are programmable variables passed from the WeaponGroupConfig
// This will change how the algorithm will behave
boolean splitMissiles = false;
boolean enforceSides = false;
boolean linkedStrike = false;
if (config == WeaponGroupConfig.MISSILE) {
splitMissiles = true;
} else if (config == WeaponGroupConfig.BROADSIDE) {
enforceSides = true;
} else if (config == WeaponGroupConfig.MISSILE_BROADSIDE) {
splitMissiles = true;
enforceSides = true;
} else if (config == WeaponGroupConfig.ALPHA_STRIKE) {
linkedStrike = true;
}
// Now we define all of the possible weapon groups that the variant can use
List<WeaponGroupSpec> groupList = new ArrayList();
WeaponGroupSpec PDGroup = new WeaponGroupSpec();
PDGroup.setType(WeaponGroupType.LINKED);
PDGroup.setAutofireOnByDefault(true);
groupList.add(PDGroup);
WeaponGroupSpec AFGroup = new WeaponGroupSpec();
AFGroup.setType(WeaponGroupType.LINKED);
AFGroup.setAutofireOnByDefault(true);
groupList.add(AFGroup);
WeaponGroupSpec AssGroup = new WeaponGroupSpec();
AssGroup.setType(WeaponGroupType.LINKED);
AssGroup.setAutofireOnByDefault(false);
groupList.add(AssGroup);
WeaponGroupSpec AssTGroup = new WeaponGroupSpec();
AssTGroup.setType(WeaponGroupType.LINKED);
AssTGroup.setAutofireOnByDefault(true);
groupList.add(AssTGroup);
WeaponGroupSpec CSGroup = new WeaponGroupSpec();
CSGroup.setType(WeaponGroupType.LINKED);
CSGroup.setAutofireOnByDefault(false);
groupList.add(CSGroup);
WeaponGroupSpec CSTGroup = new WeaponGroupSpec();
CSTGroup.setType(WeaponGroupType.LINKED);
CSTGroup.setAutofireOnByDefault(true);
groupList.add(CSTGroup);
WeaponGroupSpec SupGroup = new WeaponGroupSpec();
SupGroup.setType(WeaponGroupType.LINKED);
SupGroup.setAutofireOnByDefault(true);
groupList.add(SupGroup);
WeaponGroupSpec HvyGroup = new WeaponGroupSpec();
if (linkedStrike) {
HvyGroup.setType(WeaponGroupType.LINKED);
} else {
HvyGroup.setType(WeaponGroupType.ALTERNATING);
}
HvyGroup.setAutofireOnByDefault(false);
groupList.add(HvyGroup);
WeaponGroupSpec HvyTGroup = new WeaponGroupSpec();
if (linkedStrike) {
HvyTGroup.setType(WeaponGroupType.LINKED);
} else {
HvyTGroup.setType(WeaponGroupType.ALTERNATING);
}
HvyTGroup.setAutofireOnByDefault(false);
groupList.add(HvyTGroup);
WeaponGroupSpec BeaGroup = new WeaponGroupSpec();
BeaGroup.setType(WeaponGroupType.LINKED);
BeaGroup.setAutofireOnByDefault(false);
groupList.add(BeaGroup);
WeaponGroupSpec BeaTGroup = new WeaponGroupSpec();
BeaTGroup.setType(WeaponGroupType.LINKED);
BeaTGroup.setAutofireOnByDefault(false);
groupList.add(BeaTGroup);
WeaponGroupSpec StrGroup = new WeaponGroupSpec();
if (linkedStrike) {
StrGroup.setType(WeaponGroupType.LINKED);
} else {
StrGroup.setType(WeaponGroupType.ALTERNATING);
}
StrGroup.setAutofireOnByDefault(false);
groupList.add(StrGroup);
WeaponGroupSpec MisGroup = new WeaponGroupSpec();
if (linkedStrike) {
MisGroup.setType(WeaponGroupType.LINKED);
} else {
MisGroup.setType(WeaponGroupType.ALTERNATING);
}
MisGroup.setAutofireOnByDefault(false);
groupList.add(MisGroup);
WeaponGroupSpec SMisGroup = new WeaponGroupSpec();
SMisGroup.setType(WeaponGroupType.LINKED);
SMisGroup.setAutofireOnByDefault(true);
groupList.add(SMisGroup);
WeaponGroupSpec AMisGroup = new WeaponGroupSpec();
if (linkedStrike) {
AMisGroup.setType(WeaponGroupType.LINKED);
} else {
AMisGroup.setType(WeaponGroupType.ALTERNATING);
}
AMisGroup.setAutofireOnByDefault(false);
groupList.add(AMisGroup);
WeaponGroupSpec LGroup = new WeaponGroupSpec();
LGroup.setType(WeaponGroupType.LINKED);
LGroup.setAutofireOnByDefault(false);
groupList.add(LGroup);
WeaponGroupSpec RGroup = new WeaponGroupSpec();
RGroup.setType(WeaponGroupType.LINKED);
RGroup.setAutofireOnByDefault(false);
groupList.add(RGroup);
// This loops through all of the weapons and individually assigns them to initial groups
// Most weapon groupings are auto-detected based on AI hints, weapon data, and description strings
// The remaning groupings are enforced via overrides and configuration parameters
List<WeaponSlotAPI> slots = variant.getHullSpec().getAllWeaponSlotsCopy();
Collection<String> fittedSlots = variant.getFittedWeaponSlots();
for (String fittedSlot : fittedSlots) {
String weapon = variant.getWeaponId(fittedSlot);
String type = Global.getSettings().getDescription(weapon, Description.Type.WEAPON).getText2();
WeaponSlotAPI slot = null;
for (WeaponSlotAPI slott : slots) {
if (slott.getId().equals(fittedSlot)) {
slot = slott;
}
}
// These are the various weapon properties that are checked when making the groupings
boolean antiFighter = false;
boolean pointDefense = false;
boolean strike = false;
boolean noAim = false;
boolean assault = false;
boolean closeSupport = false;
boolean fireSupport = false;
boolean missile = false;
boolean lowFlux = false;
boolean highFlux = false;
boolean hardpoint = false;
boolean limitedAmmo = false;
boolean beam = false;
boolean special = false;
boolean front = false;
boolean back = false;
boolean left = false;
boolean right = false;
WeaponSize size = WeaponSize.SMALL;
if (slot != null) {
if (slot.isHardpoint() || slot.getArc() <= 5f) {
hardpoint = true;
}
if (slot.getAngle() <= 45f && slot.getAngle() >= -45f) {
front = true;
} else if (slot.getAngle() >= 45f && slot.getAngle() <= 135f) {
left = true;
} else if (slot.getAngle() > 135f || slot.getAngle() < -135f) {
back = true;
} else {
right = true;
}
size = slot.getSlotSize();
}
if (getWeaponList("Missile").contains(weapon)) {
missile = true;
}
if (getWeaponList("No Aim").contains(weapon)) {
noAim = true;
}
if (getWeaponList("Anti-Fighter").contains(weapon)) {
antiFighter = true;
}
if (getWeaponList("Point Defense").contains(weapon)) {
pointDefense = true;
}
if (getWeaponList("Strike").contains(weapon)) {
strike = true;
}
if (getWeaponList("Assault").contains(weapon)) {
assault = true;
}
if (getWeaponList("Close Support").contains(weapon)) {
closeSupport = true;
}
if (getWeaponList("Fire Support").contains(weapon)) {
fireSupport = true;
}
if (getWeaponList("Special").contains(weapon)) {
special = true;
}
if (getWeaponList("Low Flux").contains(weapon)) {
lowFlux = true;
}
if (getWeaponList("High Flux").contains(weapon)) {
highFlux = true;
}
if (getWeaponList("Sustained Beam").contains(weapon)) {
beam = true;
}
if (getWeaponList("Limited Ammo").contains(weapon)) {
limitedAmmo = true;
}
// If "Override" is set for the weapon, skip this auto-detection stuff and only use the overrides
// Weapons without "Override" set will use both auto-detected stuff and overrides
if (!getWeaponList("Override").contains(weapon)) {
EnumSet<AIHints> hints = variant.getWeaponSpec(fittedSlot).getAIHints();
for (AIHints hint : hints) {
if (hint == AIHints.ANTI_FTR) {
antiFighter = true;
}
if (hint == AIHints.PD || hint == AIHints.PD_ONLY) {
pointDefense = true;
}
if (hint == AIHints.STRIKE) {
strike = true;
}
if (hint == AIHints.DO_NOT_AIM || hint == AIHints.HEATSEEKER) {
noAim = true;
}
}
if (variant.getWeaponSpec(fittedSlot).getType() == WeaponType.MISSILE) {
missile = true;
lowFlux = true;
limitedAmmo = true;
}
if (type.equals("Anti-Fighter")) {
antiFighter = true;
}
if (type.equals("Point Defense")) {
pointDefense = true;
}
if (type.equals("Strike")) {
strike = true;
}
if (type.equals("Assault")) {
assault = true;
}
if (type.equals("Close Support")) {
closeSupport = true;
}
if (type.equals("Fire Support")) {
fireSupport = true;
}
if (type.equals("Special")) {
special = true;
}
}
// This is the logic for assigning weapons to general groups
// Make sure you know what you are doing before changing this stuff around
// The order of operations is important!
if (hardpoint && (highFlux || (size == WeaponSize.LARGE && (variant.getHullSize() == HullSize.FRIGATE || variant.getHullSize() == HullSize.DESTROYER || variant.getHullSize() == HullSize.DEFAULT)))) {
if (!beam) {
HvyGroup.addSlot(fittedSlot);
} else {
BeaGroup.addSlot(fittedSlot);
}
} else if (!hardpoint && (highFlux || (size == WeaponSize.LARGE && (variant.getHullSize() == HullSize.FRIGATE || variant.getHullSize() == HullSize.DESTROYER || variant.getHullSize() == HullSize.DEFAULT)))) {
if (!beam) {
HvyTGroup.addSlot(fittedSlot);
} else {
BeaTGroup.addSlot(fittedSlot);
}
} else if (missile && left && enforceSides && !noAim && !limitedAmmo) {
LGroup.addSlot(fittedSlot);
} else if (missile && right && enforceSides && !noAim && !limitedAmmo) {
RGroup.addSlot(fittedSlot);
} else if (missile && limitedAmmo && (noAim || !hardpoint) && !strike) {
MisGroup.addSlot(fittedSlot);
} else if (missile && !limitedAmmo && !strike) {
SMisGroup.addSlot(fittedSlot);
} else if (missile && limitedAmmo && !noAim && !strike) {
AMisGroup.addSlot(fittedSlot);
} else if (pointDefense && !hardpoint && !limitedAmmo) {
PDGroup.addSlot(fittedSlot);
} else if (antiFighter) {
AFGroup.addSlot(fittedSlot);
} else if (left && (hardpoint || enforceSides) && !lowFlux) {
LGroup.addSlot(fittedSlot);
} else if (right && (hardpoint || enforceSides) && !lowFlux) {
RGroup.addSlot(fittedSlot);
} else if (assault && hardpoint && !lowFlux) {
AssGroup.addSlot(fittedSlot);
} else if (assault && !hardpoint && !lowFlux) {
AssTGroup.addSlot(fittedSlot);
} else if (closeSupport && hardpoint && !lowFlux) {
CSGroup.addSlot(fittedSlot);
} else if (closeSupport && !hardpoint && !lowFlux) {
CSTGroup.addSlot(fittedSlot);
} else if (strike || limitedAmmo) {
if (!beam) {
StrGroup.addSlot(fittedSlot);
} else {
if (hardpoint) {
BeaGroup.addSlot(fittedSlot);
} else {
BeaTGroup.addSlot(fittedSlot);
}
}
} else if (fireSupport || lowFlux || special) {
SupGroup.addSlot(fittedSlot);
} else {
SupGroup.addSlot(fittedSlot); // This should always be at the end
}
}
// Now we consolidate the general weapon groups into five final weapon groups
// This is only done if the number of general weapon groups is greater than 5
// If Alex ever increases the maximum number of weapon groups, this number can be changed
int groupCount = 0;
for (WeaponGroupSpec group : groupList) {
if (group.getSlots().isEmpty()) {
groupCount++;
}
}
// This section is the most difficult part of the algorithm and can make or break large ships like the Onslaught
// Make sure you know what you are doing when adding logic here
// Do not pass weapons into a group that may have already become null; NetBeans should warn you about this
// Order of operations is extremely important
if (!AFGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!PDGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : AFGroup.getSlots()) {
PDGroup.addSlot(toAdd);
}
AFGroup = null;
}
} else {
AFGroup = null;
}
if (!AssGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!CSGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : AssGroup.getSlots()) {
CSGroup.addSlot(toAdd);
}
AssGroup = null;
}
} else {
AssGroup = null;
}
if (!splitMissiles) {
if (!AMisGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!MisGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : AMisGroup.getSlots()) {
MisGroup.addSlot(toAdd);
}
AMisGroup = null;
}
} else {
AMisGroup = null;
}
}
if (!PDGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!SupGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : PDGroup.getSlots()) {
SupGroup.addSlot(toAdd);
}
PDGroup = null;
}
} else {
PDGroup = null;
}
if (!splitMissiles) {
if (!SMisGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!AssTGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : SMisGroup.getSlots()) {
AssTGroup.addSlot(toAdd);
}
SMisGroup = null;
}
} else {
SMisGroup = null;
}
}
if (!AssTGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!CSTGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : AssTGroup.getSlots()) {
CSTGroup.addSlot(toAdd);
}
AssTGroup = null;
}
} else {
AssTGroup = null;
}
if (!BeaTGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!CSTGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : BeaTGroup.getSlots()) {
CSTGroup.addSlot(toAdd);
}
BeaTGroup = null;
}
} else {
BeaTGroup = null;
}
if (!BeaGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!CSGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : BeaGroup.getSlots()) {
CSGroup.addSlot(toAdd);
}
BeaGroup = null;
}
} else {
BeaGroup = null;
}
if (!enforceSides) {
if (!LGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!RGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : LGroup.getSlots()) {
RGroup.addSlot(toAdd);
}
LGroup = null;
}
} else {
LGroup = null;
}
}
if (!CSTGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!SupGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : CSTGroup.getSlots()) {
SupGroup.addSlot(toAdd);
}
CSTGroup = null;
}
} else {
CSTGroup = null;
}
if (!HvyTGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!HvyGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : HvyTGroup.getSlots()) {
HvyGroup.addSlot(toAdd);
}
HvyTGroup = null;
}
} else {
HvyTGroup = null;
}
if (!HvyGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!StrGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : HvyGroup.getSlots()) {
StrGroup.addSlot(toAdd);
}
HvyGroup = null;
}
} else {
HvyGroup = null;
}
if (splitMissiles && AMisGroup != null) {
if (!AMisGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!MisGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : AMisGroup.getSlots()) {
MisGroup.addSlot(toAdd);
}
AMisGroup = null;
}
} else {
AMisGroup = null;
}
}
if (splitMissiles && SMisGroup != null) {
if (!SMisGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!SupGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : SMisGroup.getSlots()) {
SupGroup.addSlot(toAdd);
}
SMisGroup = null;
}
} else {
SMisGroup = null;
}
}
if (enforceSides) {
if (!CSGroup.getSlots().isEmpty()) {
if (groupCount > 5) {
if (!SupGroup.getSlots().isEmpty()) {
groupCount--;
}
for (String toAdd : CSGroup.getSlots()) {
SupGroup.addSlot(toAdd);
}
CSGroup = null;
}
} else {
CSGroup = null;
}
} else {
if (CSGroup.getSlots().isEmpty()) {
CSGroup = null;
}
}
if (enforceSides && LGroup != null) {
if (LGroup.getSlots().isEmpty()) {
LGroup = null;
}
}
if (RGroup.getSlots().isEmpty()) {
RGroup = null;
}
if (StrGroup.getSlots().isEmpty()) {
StrGroup = null;
}
if (SupGroup.getSlots().isEmpty()) {
SupGroup = null;
}
if (MisGroup.getSlots().isEmpty()) {
MisGroup = null;
}
if (variant.getWeaponGroups().size() < 5) {
for (int i = variant.getWeaponGroups().size(); i < 5; i++) {
variant.addWeaponGroup(new WeaponGroupSpec());
}
}
// I didn't make a loop for this because the order in which you put these functions determines their order on the ship
int currentGroup = 0;
if (HvyGroup != null) {
integrateGroup(HvyGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (HvyTGroup != null) {
integrateGroup(HvyTGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (BeaGroup != null) {
integrateGroup(BeaGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (BeaTGroup != null) {
integrateGroup(BeaTGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (LGroup != null) {
integrateGroup(LGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (RGroup != null) {
integrateGroup(RGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (AssGroup != null) {
integrateGroup(AssGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (CSGroup != null) {
integrateGroup(CSGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (AssTGroup != null) {
integrateGroup(AssTGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (CSTGroup != null) {
integrateGroup(CSTGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (StrGroup != null) {
integrateGroup(StrGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (AMisGroup != null) {
integrateGroup(AMisGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (MisGroup != null) {
integrateGroup(MisGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (SMisGroup != null) {
integrateGroup(SMisGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (SupGroup != null) {
integrateGroup(SupGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (AFGroup != null) {
integrateGroup(AFGroup, variant.getGroup(currentGroup));
currentGroup++;
}
if (PDGroup != null) {
integrateGroup(PDGroup, variant.getGroup(currentGroup));
currentGroup++;
}
}
package data.scripts.weapons;
import com.fs.starfarer.api.AnimationAPI;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.util.*;
public class BaseAnimateOnFireEffect2 implements EveryFrameWeaponEffectPlugin
{
// Default to 15 frames per second
private float timeSinceLastFrame, timeBetweenFrames = 1.0f / 140f;
private Map pauseFrames = new HashMap();
private int curFrame = 0, pausedFor = 0;
private boolean isFiring = false;
private boolean runOnce2 = false;
private boolean runOnce3 = false;
protected void setFramesPerSecond(float fps)
{
timeBetweenFrames = 1.0f / fps;
}
protected void pauseOnFrame(int frame, int pauseFor)
{
pauseFrames.put(frame, pauseFor);
}
private void incFrame(AnimationAPI anim)
{
if (pauseFrames.containsKey(curFrame))
{
if (pausedFor < (Integer) pauseFrames.get(curFrame))
{
pausedFor++;
return;
}
else
{
pausedFor = 0;
}
}
curFrame = Math.min(curFrame + 1, anim.getNumFrames() - 1);
}
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon)
{
if (engine.isPaused())
{
return;
}
AnimationAPI anim = weapon.getAnimation();
anim.setFrame(curFrame);
if (isFiring)
{
timeSinceLastFrame += amount;
if (timeSinceLastFrame >= timeBetweenFrames)
{
timeSinceLastFrame = 0f;
anim.setFrame(curFrame);
if(runOnce2 == false){
if (weapon.getShip().getOwner() == 0 && weapon.getLocation().getX() < weapon.getShip().getLocation().getX()
|| weapon.getShip().getOwner() == 1 && weapon.getLocation().getX() > weapon.getShip().getLocation().getX()){
SpriteAPI theSprite = weapon.getSprite();
theSprite.setWidth(-theSprite.getWidth());
theSprite.setCenter(-theSprite.getCenterX(),theSprite.getCenterY());
}
}
incFrame(anim);
if (curFrame == anim.getNumFrames() - 1)
{
isFiring = false;
runOnce2 = true;
}
}
}
else
{
if (weapon.isFiring() && weapon.getChargeLevel() == 1.0f)
{
isFiring = true;
incFrame(anim);
anim.setFrame(curFrame);
}
else
{
curFrame = 0;
anim.setFrame(curFrame);
if(runOnce3 == false){
if (weapon.getShip().getOwner() == 0 && weapon.getLocation().getX() < weapon.getShip().getLocation().getX()
|| weapon.getShip().getOwner() == 1 && weapon.getLocation().getX() > weapon.getShip().getLocation().getX()){
SpriteAPI theSprite = weapon.getSprite();
theSprite.setWidth(-theSprite.getWidth());
theSprite.setCenter(-theSprite.getCenterX(),theSprite.getCenterY());
}
runOnce3 = true;
}
}
}
}
}
package data.scripts.hullmods;
import com.fs.starfarer.api.Global;
import java.util.*;
import com.fs.starfarer.api.campaign.BuffManagerAPI.Buff;
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.combat.HullModEffect;
import com.fs.starfarer.api.combat.HullModFleetEffect;
import com.fs.starfarer.api.combat.MutableShipStatsAPI;
import com.fs.starfarer.api.combat.MutableStat;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.MutableStat.StatMod;
import com.fs.starfarer.api.combat.ShipAPI.HullSize;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
public class TowCable implements HullModEffect, HullModFleetEffect {
public static final String HULLMOD_ID = "tow_cable";
public static class TowCableBuff implements Buff {
public int buffAmount = 1;
private final String buffId;
public TowCableBuff(String buffId) {
this.buffId = buffId;
}
@Override
public boolean isExpired() {
return false;
}
@Override
public String getId() {
return buffId;
}
@Override
public void apply(FleetMemberAPI member) {
member.getStats().getMaxBurnLevel().modifyFlat(buffId, buffAmount);
}
@Override
public void advance(float days) {
}
};
private final static float INTERVAL = 0.2f; // in days
private long timestamp = 0;
private long lastTimestamp = 0;
// We only run this once in a while, since exact timing is not an issue in the campaign map
@Override
public void advanceInCampaign(CampaignFleetAPI fleet) {
if (fleet == null) {
return;
}
if (timestamp == 0) {
timestamp = Global.getSector().getClock().getTimestamp();
lastTimestamp = timestamp;
}
if (Global.getSector().getClock().getElapsedDaysSince(timestamp) < INTERVAL && Global.getSector().getClock().getTimestamp() != lastTimestamp) {
return;
}
timestamp = Global.getSector().getClock().getTimestamp();
lastTimestamp = timestamp;
List<FleetMemberAPI> all = fleet.getFleetData().getMembersListCopy();
int numCables = 0;
float towSpeed = Float.MAX_VALUE; // We make an assumption that all Oxen have the same burn speed
for (FleetMemberAPI curr : all) {
if (!curr.canBeDeployedForCombat()) {
continue;
}
if (curr.getVariant().getHullMods().contains(HULLMOD_ID)) {
numCables++;
towSpeed = Math.min(towSpeed, getMaxBurnWithoutCables(curr));
}
}
if (numCables <= 0) {
cleanUpTowCableBuffs(fleet);
return;
}
Map<FleetMemberAPI, Integer> cables = new HashMap();
for (int cableIndex = 0; cableIndex < numCables; cableIndex++) {
FleetMemberAPI slowest = getSlowest(all, towSpeed, cables);
if (slowest == null) {
break;
}
Integer bonus = cables.get(slowest);
if (bonus == null) {
bonus = Integer.valueOf(0);
}
bonus = bonus + 1;
cables.put(slowest, bonus);
}
for (FleetMemberAPI curr : all) {
if (!cables.containsKey(curr)) {
curr.getBuffManager().removeBuff(TOW_CABLE_KEY);
continue;
}
if (cables.get(curr) <= 0) {
curr.getBuffManager().removeBuff(TOW_CABLE_KEY);
continue;
}
boolean renew = true;
for (StatMod mod : curr.getStats().getMaxBurnLevel().getFlatMods().values()) {
if (mod.getSource().equals(TOW_CABLE_KEY)) {
if (mod.value == cables.get(curr)) {
renew = false;
}
}
}
if (renew) {
TowCableBuff buff = new TowCableBuff(TOW_CABLE_KEY);
buff.buffAmount = cables.get(curr);
curr.getBuffManager().addBuff(buff);
}
}
}
@Override
public void onFleetSync(CampaignFleetAPI fleet) {
}
public TowCable() {
}
@Override
public void advanceInCampaign(FleetMemberAPI member, float amount) {
}
private FleetMemberAPI getSlowest(List<FleetMemberAPI> all, float speedCutoff, Map<FleetMemberAPI, Integer> cables) {
FleetMemberAPI slowest = null;
float minLevel = Float.MAX_VALUE;
for (FleetMemberAPI curr : all) {
if (!isSuitable(curr)) {
continue;
}
float baseBurn = getMaxBurnWithoutCables(curr);
Integer bonus = cables.get(curr);
if (bonus == null) {
bonus = Integer.valueOf(0);
}
if (bonus >= getMaxCablesFor(curr)) {
continue;
}
float burnLevel = baseBurn + bonus;
if (burnLevel >= speedCutoff) {
continue;
}
if (burnLevel < minLevel) {
minLevel = burnLevel;
slowest = curr;
}
}
return slowest;
}
private int getMaxCablesFor(FleetMemberAPI member) {
switch (member.getHullSpec().getHullSize()) {
case CAPITAL_SHIP:
return 4;
case CRUISER:
return 3;
case DESTROYER:
return 2;
case FRIGATE:
return 1;
}
return 1;
}
private static float getMaxBurnWithoutCables(FleetMemberAPI member) {
MutableStat burn = member.getStats().getMaxBurnLevel();
float val = burn.getModifiedValue();
float sub = 0;
for (StatMod mod : burn.getFlatMods().values()) {
if (mod.getSource().equals(TOW_CABLE_KEY)) {
sub = mod.getValue();
break;
}
}
return Math.max(0, val - sub);
}
private boolean isSuitable(FleetMemberAPI member) {
return !member.isFighterWing();
}
private void cleanUpTowCableBuffs(CampaignFleetAPI fleet) {
if (fleet == null) {
return;
}
for (FleetMemberAPI curr : fleet.getFleetData().getMembersListCopy()) {
curr.getBuffManager().removeBuff(TOW_CABLE_KEY);
}
}
/**
* One instance of the buff object per ship with a Tow Cable.
*/
public static final String TOW_CABLE_KEY = "TowCable_PersistentBuffs";
@Override
public void advanceInCombat(ShipAPI ship, float amount) {
}
@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, String id) {
}
@Override
public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
}
@Override
public boolean isApplicableToShip(ShipAPI ship) {
return true;
}
@Override
public String getDescriptionParam(int index, HullSize hullSize) {
return null;
}
}
package data.hullmods;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import java.util.WeakHashMap;
import org.lwjgl.util.vector.Vector2f;
public class GravityAnchor extends BaseHullMod {
static WeakHashMap locations = new WeakHashMap();
static WeakHashMap facings = new WeakHashMap();
public static void anchorShip(FleetMemberAPI ship, Vector2f location, float angle) {
locations.put(ship.getId(), location);
facings.put(ship.getId(), angle);
}
public static void anchorShip(FleetMemberAPI ship, Vector2f location) {
locations.put(ship.getId(), location);
}
public static void anchorShip(ShipAPI ship, Vector2f location, float angle) {
locations.put(ship.getFleetMemberId(), location);
facings.put(ship.getFleetMemberId(), angle);
}
public static void anchorShip(ShipAPI ship, Vector2f location) {
locations.put(ship.getFleetMemberId(), location);
}
public void setLocation(CombatEntityAPI entity, Vector2f location) {
Vector2f dif = new Vector2f(location);
Vector2f.sub(location, entity.getLocation(), dif);
Vector2f.add(entity.getLocation(), dif, entity.getLocation());
}
@Override
public void advanceInCombat(ShipAPI ship, float amount) {
super.advanceInCombat(ship, amount);
String id = ship.getFleetMemberId();
if(!locations.containsKey(id)) return;
else setLocation(ship, (Vector2f)locations.get(id));
if(!facings.containsKey(id)) return;
else ship.setFacing((Float)facings.get(id));
}
@Override
public boolean isApplicableToShip(ShipAPI ship){
return false;
}
}
FleetMemberAPI member = api.addToFleet(side, variantID, FleetMemberType.SHIP, false);
GravityAnchor.anchorShip(member, new Vector2f(100, -100), 360);
package data.scripts.weapons;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.util.IntervalUtil;
/**
*
* @author Debido
* fragments derived from Xenoargh's MegaBeamDamageEffect plugin
*
* This script is intended to allow the given Beam weapon using this script to apply hard flux damage to the enemy ship if the beam hits their shield
* This is probably OP, so be careful with game balancing!
*/
public class BeamFullFlux implements BeamEffectPlugin {
private final IntervalUtil fireInterval = new IntervalUtil(0.1f, 0.1f); //interval between applying flux
private final float fluxMultiplier = 10.0f; //determines how much hard flux is generated in the enemy ship. 1.0f would be 100% of weapon DPS, 0.1f would be 10%, and 10f would be 1000%
@Override
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
if (engine.isPaused()) {
return;
}
fireInterval.advance(amount);
CombatEntityAPI target = beam.getDamageTarget();
//If we have a target, target is a Ship, and shields are being hit.
if (target != null && target instanceof ShipAPI && target.getShield() != null && target.getShield().isWithinArc(beam.getTo())) {
if (fireInterval.intervalElapsed()) {
if (beam.getBrightness() >= 1f) {
ShipAPI ship = (ShipAPI) target; //cast as ship
ship.getFluxTracker().increaseFlux(fluxMultiplier * beam.getWeapon().getDerivedStats().getDps() / 10f, true); //apply flux
}
}
}
}
}
package data.scripts.weapons;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.WeaponAPI.DerivedWeaponStatsAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.ShipAPI;
public class BeamDamageEffect implements BeamEffectPlugin {
@Override
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam) {
if (engine.isPaused()) return;
if(beam.getBrightness() < 0.99f) return;
float frameTime = engine.getElapsedInLastFrame();
//Get the beam's target
CombatEntityAPI target = beam.getDamageTarget();
//If we have a target, target is a Ship, and shields are being hit.
if (target != null && target instanceof ShipAPI && target.getShield() != null && target.getShield().isWithinArc(beam.getTo())) {
//Now that we have the target, get the weapon ID and get the DPS
WeaponAPI weapon = beam.getWeapon();
DamageType damType = weapon.getDamageType();
DerivedWeaponStatsAPI stats = weapon.getDerivedStats();
engine.applyDamage(
target, //enemy Ship
beam.getTo(), //Our 2D vector to the exact world-position
dps * frameTime, //We're dividing the DPS by the time that's passed here.
damType, //Using the damage type here.
0f, //No EMP, as EMP already has specific rules. However EMP could go through shields this way if we wanted it to.
false, //Does not bypass shields.
false, //Does not do Soft Flux damage (would kind've defeat the whole point, eh?
beam.getSource() //Who owns this beam?
);
}
}
}
if(runOnce){
String weaponSlot = weapon.getSlot().getId();
List<WeaponGroupSpec> weaponGroups = weapon.getShip().getVariant().getWeaponGroups();
for(WeaponGroupSpec group : weaponGroups){
List<String> slots = group.getSlots();
String slotID = new String();
for(String slot : slots){
if(slot.equalsIgnoreCase(weaponSlot)){
slotID = slot.toString();
}
}
if(slotID.equalsIgnoreCase(weaponSlot)) group.removeSlot(slotID);
}
runOnce = false;
}
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package uaf.data.scripts.weapons;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import java.awt.Color;
import java.util.List;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
/**
*
* @author Debido
*/
public class SPSBomber implements EveryFrameWeaponEffectPlugin {
private static final Color BOOM_COLOR = new Color(255, 165, 50, 245);
private static final Vector2f ZERO_VECTOR = new Vector2f(0f,0f);
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
ShipAPI ship = weapon.getShip();
if (ship == null || !engine.isEntityInPlay(ship) || weapon.getAmmo() == 0) {
return;
}
List<ShipAPI> ships = CombatUtils.getShipsWithinRange(ship.getLocation(), 100f);
for (ShipAPI s : ships){
if (s.isAlive() && s.getOwner() != ship.getOwner()){
if (CollisionUtils.isPointWithinBounds(ship.getLocation(), s)){
weapon.setAmmo(0);
engine.applyDamage(s, ship.getLocation(), weapon.getDerivedStats().getDamagePerShot(), DamageType.HIGH_EXPLOSIVE, 0f, false, false, ship);
engine.spawnExplosion(ship.getLocation(), ZERO_VECTOR, BOOM_COLOR, 500f, 1f);
engine.addSmokeParticle(ship.getLocation(), ZERO_VECTOR, 600f, 0.9f, 5f, Color.DARK_GRAY);
for (int i = 0; i < 10; i++){
Vector2f radialVector = MathUtils.getRandomPointOnCircumference(ship.getLocation(), 500f);
engine.addHitParticle(ship.getLocation(), radialVector , 10f, 255, 2f, BOOM_COLOR);
}
}
}
}
}
}
//by Tartiflette, this script allow for a beam weapon to slash during the main firing sequence
//feel free to use it, credit is appreciated but not mandatory
package data.scripts.weapons;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
public class SCY_slasherBeamEffect implements EveryFrameWeaponEffectPlugin {
private boolean isFiring = false;
private float aim = 0.0f;
private float count = 0.0f;
private final float offset = 0.5f;
private boolean hasFired = false;
private boolean RUN_ONCE = false;
private float multiplier = 1;
private boolean turretMode = true;
private float arc = 0.0f;
private float arcFacing = 0.0f;
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) {
// Don't bother with any checks if the game is paused
if (engine.isPaused()) {
return;
}
//run only once to check the available arc, double the slashing width if arc > 90 degrees, hardpoint mode if arc < 20 degrees
if (!RUN_ONCE) {
if (weapon.getArc() >= 90f) {
multiplier = 2;
}
if (weapon.getArc() <= 10f) {
multiplier = 0.5f;
}
if (weapon.getArc() <= 20f) {
turretMode = false;
arc = weapon.getArc();
arcFacing = weapon.getArcFacing();
aim = 0.5f;
}
RUN_ONCE = true;
}
if (!turretMode) {
//HARPOINT MODE: goes back and forth in the arc for the duration
if (weapon.isFiring()) {
//initialize the first offset to start on the same direction the weapon is facing
if (!hasFired) {
count = weapon.getCurrAngle() - arcFacing - weapon.getShip().getFacing();
hasFired = true;
}
//if the weapon hit an arc limit, change the rotation direction
if (count >= ( arc * 0.5f )) {
aim = -0.5f*multiplier;
}
if (count <= -( arc * 0.5f )) {
aim = 0.5f*multiplier;
}
//slashing in the arc
weapon.setCurrAngle( weapon.getShip().getFacing() + arcFacing + count );
count = count + aim;
}
//reset the variables when the weapon is cooling down
if (!weapon.isFiring() && hasFired) {
hasFired = false;
aim = 0.5f;
count = 0f;
}
} else {
//TURRET MODE: one large slash
//charging: the gun turn left in preparation of the slashing
if (weapon.isFiring() && weapon.getChargeLevel() < 1.0f && !hasFired) {
//the gun orientation is stored before being altered
if (weapon.isFiring() && isFiring == false) {
aim = weapon.getCurrAngle();
}
isFiring = true;
count++;
weapon.setCurrAngle(aim + (count * offset * multiplier));
}
//firing: the gun slash to the right
if (weapon.isFiring() && weapon.getChargeLevel() == 1.0f) {
hasFired = true;
count = count - 0.5f;
weapon.setCurrAngle(aim + (count * offset * multiplier));
}
//cooldown: the weapon is released and the variables reseted
if (!weapon.isFiring() && hasFired) {
isFiring = false;
hasFired = false;
aim = 0.0f;
count = 0f;
}
}
}
}
//by Tartiflette, this script allow for a beam weapon to vibrate at a predetermined frequency during the main firing sequence
//feel free to use it, credit is appreciated but not mandatory
package data.scripts.weapons;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
public class SCY_vibratingBeamEffect implements EveryFrameWeaponEffectPlugin
{
private boolean isFiring = false;
private float aim = 0.0f;
private float offset = 0.0f;
private final IntervalUtil timer = new IntervalUtil(0.05f, 0.05f);
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon)
{
// Don't bother with any checks if the game is paused
if (engine.isPaused()) {
return;
}
if (weapon.isFiring())
{
//don't vibrate during chargeup
if (weapon.getChargeLevel() == 1.0f)
{
//store the weapon orientation once it reach full power
if (weapon.isFiring() && !isFiring) {
aim = weapon.getCurrAngle();
isFiring = true;
}
//change the orientation every few moments
if(timer.intervalElapsed())
{
offset = (float)MathUtils.getRandomNumberInRange(-2f, 2f);
}
timer.advance(amount);
//lock the weapon orientation every frame to avoid trying to aim back to the curser
weapon.setCurrAngle(aim + offset);
}
else
{
isFiring = false;
offset = 0.0f;
aim = 0.0f;
}
}
}
}
//by Tartiflette, this script allow for a weapon to reload it's ammo after some time since it's last shot
//feel free to use it, credit is appreciated but not mandatory
package data.scripts.weapons;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
public class SCY_clipEffect implements EveryFrameWeaponEffectPlugin
{
private boolean runOnce = false;
private float timer = 0f;
private int clipSize;
private int magazine;
private int clipTimer;
@Override
public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon)
{
// Don't bother with any checks if the game is paused
if (engine.isPaused()) {
return;
}
// store the max reloading ammo amount, the timer between reloads and initialise the current magazine size
//WARNING to properly function in autoresolve, the weapon need the equivalent of the clip reloading in ammo regen and a cooldown
if (!runOnce){
if (weapon.getId().equals("SCY_laserRepeater_mkiii")){
clipSize=5;
clipTimer=5;
} else {
//default values
clipSize=weapon.getMaxAmmo();
clipTimer=5;
}
magazine = weapon.getMaxAmmo();
runOnce=true;
}
//prevent normal ammo regeneration
if (weapon.getAmmo()>magazine){
weapon.setAmmo(magazine);
}
//only run if the ammo isn't full
if (weapon.getAmmo()<weapon.getMaxAmmo()){
//lock the weapon if the ammo reach 0
//necessary because the weapons can fire upon the ammo regeneration before the script kicks in and remove it
if(weapon.getAmmo()==0){
weapon.setRemainingCooldownTo(1);
}
//interrupt the reloading if the weapon fire
if(weapon.isFiring()){
timer = 0;
magazine = weapon.getAmmo();
} else {
//timer
timer+=amount;
//timer elapsed: reload and reset the timer
if(timer>=clipTimer){
//add a clip to the current ammo
weapon.setAmmo(Math.min(weapon.getMaxAmmo(),magazine+clipSize));
timer = 0;
magazine = weapon.getAmmo();
//reset the cooldown
weapon.setRemainingCooldownTo(0);
}
}
}
}
}
//By Tartiflette, allows for custom muzzle flashes and other weapons animations without damage decals troubles
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.MathUtils;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.glEnable;
import org.lwjgl.util.vector.Vector2f;
public class SCY_muzzleFlashesPlugin extends BaseEveryFrameCombatPlugin {
//MEMBERS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used
public static Map< WeaponAPI , Float > MEMBERS = new HashMap<>();
//set the function to access MEMBERS from the weapons scripts
public static void addMember(WeaponAPI weapon, Float index) {
MEMBERS.put(weapon, index);
}
public static void removeMember(WeaponAPI weapon) {
MEMBERS.remove(weapon);
}
private List<WeaponAPI> toRemove = new ArrayList<>();
//All the info needed, per muzzle type.
//minigun MKI
public final String MINIGUN_I_ID = "SCY_minigun_mki";
//load the muzzle sprites map. Since it's a random muzzle I keep the index 0 free to skip some frame from time to time.
private final Map<Integer, SpriteAPI> MINIGUN_I = new HashMap<>();
{
MINIGUN_I.put(1, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle01"));
MINIGUN_I.put(2, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle02"));
MINIGUN_I.put(3, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle03"));
MINIGUN_I.put(4, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle04"));
MINIGUN_I.put(5, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle05"));
MINIGUN_I.put(6, Global.getSettings().getSprite("muzzle", "SCY_minigunMki_muzzle06"));
}
//set the muzzle sprite size
private final float minigun1Width = 28;
private final float minigun1Height = 53;
//set the offset from the weapon's center. T for turret, H for Hardpoint. I always keep my muzzle sprites on the weapon axis to only have to worry about the length offset.
private final float minigun1TOffset=25;
private final float minigun1HOffset=25;
//KAcc MKII
public final String KACC2_ID = "SCY_kacc_mkii";
private final Map<Integer, SpriteAPI> KACC2 = new HashMap<>();
{
KACC2.put(0, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle00"));
KACC2.put(1, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle01"));
KACC2.put(2, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle02"));
KACC2.put(3, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle03"));
KACC2.put(4, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle04"));
KACC2.put(5, Global.getSettings().getSprite("muzzle", "SCY_kaccMkii_muzzle05"));
}
private final float kacc2Width = 34;
private final float kacc2Height = 48;
private final float kacc2TOffset = 11;
private final float kacc2HOffset = 15;
private final float kacc2Delay = 0.05f;
@Override
public void init(CombatEngineAPI engine) {
//initialise all the data
MEMBERS.clear();
}
@Override
public void renderInWorldCoords(ViewportAPI view)
{
CombatEngineAPI engine = Global.getCombatEngine();
if (engine == null){return;}
if (!MEMBERS.isEmpty())
{
float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());
//dig through the MEMBERS
for (Iterator<Map.Entry< WeaponAPI , Float >> iter = MEMBERS.entrySet().iterator(); iter.hasNext(); ) {
Map.Entry< WeaponAPI , Float > entry = iter.next();
//Apply the right effect for the right type of weapon
switch (entry.getKey().getId()) {
case MINIGUN_I_ID:
//Check if the ship is in play
if (!engine.isEntityInPlay(entry.getKey().getShip())){
toRemove.add(entry.getKey());
break;
}
//random muzzle flash
//turret or hardpoint offset
float minigun1Offset;
if(entry.getKey().getSlot().isHardpoint()){
minigun1Offset=minigun1HOffset;
} else {
minigun1Offset=minigun1TOffset;
}
//change the muzzle sprite 8 out of 10 frames if the engine isn't paused
if (!engine.isPaused() && Math.random()>0.2f){
//place the new muzzle sprite index in the MEMBERS map
MEMBERS.put(entry.getKey(), (float)MathUtils.getRandomNumberInRange(0, 6));
}
//skip if the index is 0, to give accidents in the animation like the real thing
if (entry.getValue()!=0){
//call the render
render(entry.getKey(), MINIGUN_I.get((int)Math.round(entry.getValue())), minigun1Width, minigun1Height, minigun1Offset, true);
}
break;
case KACC2_ID:
//animated muzzle flash
//turret or hardpoint offset
float kacc2Offset;
if(entry.getKey().getSlot().isHardpoint()){
kacc2Offset=kacc2HOffset;
} else {
kacc2Offset=kacc2TOffset;
}
int kacc2Frame;
//put the animation timer as the MEMBERS' value
//randomly keep the muzzle frame a bit longer
if (!engine.isPaused() && Math.random()>0.25){
MEMBERS.put(entry.getKey(), amount+entry.getValue());
}
//calculate the current frame from the animation's time
kacc2Frame = (int)Math.abs(entry.getValue()/kacc2Delay);
//add to the remove list and break if the animation is finished
if (kacc2Frame>KACC2.size()-1){
toRemove.add(entry.getKey());
break;
}
//randomly flip the muzzle flash
float flip2 = 1;
if (Math.sin(100*entry.getValue())>0 ){flip2=-1;}
//call the renderer
render(entry.getKey(), KACC2.get(kacc2Frame), flip2*kacc2Width, kacc2Height, kacc2Offset, true);
break;
}
}
//remove the weapons that needs to
//can't be done from within the iterator or it will fail when members will be missing
if (!toRemove.isEmpty()){
for(WeaponAPI w : toRemove ){
MEMBERS.remove(w);
}
toRemove.clear();
}
}
}
private void render (WeaponAPI weapon, SpriteAPI sprite, float width, float height, float offset, boolean additive){
//where the magic happen
sprite.setAlphaMult(1);
sprite.setSize(width, height);
if (additive){
sprite.setAdditiveBlend();
}
float aim = weapon.getCurrAngle();
Vector2f loc = MathUtils.getPointOnCircumference(weapon.getLocation(),offset,aim); //apply muzzle the offset
sprite.setAngle(aim-90);
sprite.renderAtCenter(loc.x, loc.y);
}
}
//By Tartiflette
package data.scripts.weapons;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin;
import com.fs.starfarer.api.combat.WeaponAPI;
import data.scripts.plugins.SCY_muzzleFlashesPlugin;
public class SCY_addToMuzzlePlugin implements EveryFrameWeaponEffectPlugin{
private boolean put=false;
private boolean runOnce=false;
private boolean hidden=false;
@Override
public void advance (float amount, CombatEngineAPI engine, WeaponAPI weapon) {
if(!hidden && engine.isPaused()){return;}
if(!runOnce){
runOnce=true;
//check if the mount is hidden
if (weapon.getSlot().isHidden()){
hidden=true;
return;
}
}
//assign the weapon for the muzzle flash plugin
if (weapon.getChargeLevel()==1){
//add the weapon to the MEMBERS map when it fires
//"put" is to make sure it's added only once
if (!put){
put=true;
SCY_muzzleFlashesPlugin.addMember(weapon,0f);
}
} else if (weapon.getChargeLevel()== 0){
//reinitialise "put"
if (put){
put=false;
}
}
}
}
if (!hidden){
//assign the weapon for the muzzle flash plugin
if (weapon.getChargeLevel()==1){
//add the weapon to the MEMBERS map
if (!put){
put=true;
SCY_muzzleFlashesPlugin.addMember(weapon,0f);
}
} else if (weapon.getChargeLevel()== 0){
//remove the weapon from the MEMBERS map
if (put){
put=false;
SCY_muzzleFlashesPlugin.removeMember(weapon);
}
}
}
}
}
"muzzle":{
"SCY_minigunMki_muzzle01":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_01.png",
"SCY_minigunMki_muzzle02":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_02.png",
"SCY_minigunMki_muzzle03":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_03.png",
"SCY_minigunMki_muzzle04":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_04.png",
"SCY_minigunMki_muzzle05":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_05.png",
"SCY_minigunMki_muzzle06":"graphics/SCY/weapons/SCY_minigun_mki/SCY_minigunMki_muzzle_06.png",
"SCY_orionMkiii_muzzle01":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_01.png",
"SCY_orionMkiii_muzzle02":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_02.png",
"SCY_orionMkiii_muzzle03":"graphics/SCY/weapons/SCY_orionMkiii/SCY_orionMkiiiMuzzle_03.png",
},
//By Tartiflette, simple and fast Missile AI that will lead it's target and avoid endless circling. Also Engage a new target if the current one get destroyed
package data.scripts.ai;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
public class SCY_simpleMissileAI implements MissileAIPlugin, GuidedMissileAI {
private CombatEngineAPI engine;
private final MissileAPI missile;
private CombatEntityAPI target;
private Vector2f lead = new Vector2f(0f,0f);
// Distance under witch the missile cease to lead the target aim directly for it
private final float closeRange = 750;
// Max area the missile seek a target in if the launching ship don't have one
private final float maxSearchRange = 1000;
// Angle with the target beyond witch the missile will first turn around before accelerating again
private final float overshoot = 45;
//data
private final float flightSpeed;
private final static float damping = 0.1f;
//delay between target actualisation
private final IntervalUtil actualisation = new IntervalUtil(0.2f, 1f);
private boolean launch=true;
private boolean runOnce=false;
private float eccm=3;
//////////////////////
// DATA COLLECTING //
//////////////////////
public SCY_simpleMissileAI(MissileAPI missile, ShipAPI launchingShip) {
this.missile = missile;
flightSpeed = missile.getMaxSpeed();
}
//////////////////////
// MAIN AI LOOP //
//////////////////////
@Override
public void advance(float amount) {
if (engine != Global.getCombatEngine()) {
this.engine = Global.getCombatEngine();
}
if(!runOnce){
runOnce=true;
for (String s : missile.getSource().getVariant().getHullMods()) {
if (s.equals("ECCMPackage")){
eccm=1;
}
}
}
//skip the AI if the game is paused, the missile is engineless or fading
if (Global.getCombatEngine().isPaused() || missile.isFading() || missile.isFizzling()) {return;}
//assigning a target if there is none or it got destroyed
if (target == null
|| target.getOwner()==missile.getOwner()
|| (target instanceof ShipAPI && ((ShipAPI) target).isHulk()) //comment out this line to remove target reengagement
){
setTarget(assignTarget(missile));
//forced acceleration by default
missile.giveCommand(ShipCommand.ACCELERATE);
return;
}
actualisation.advance(amount);
//fiding lead point to aim to
if(launch || actualisation.intervalElapsed()){
launch=false;
if (MathUtils.getDistance(missile, target) <= closeRange) {
lead = target.getLocation();
} else {
lead = AIUtils.getBestInterceptPoint(missile.getLocation(), flightSpeed*eccm, target.getLocation(), target.getVelocity());
lead = MathUtils.getRandomPointInCircle(lead,Math.max(0,MathUtils.getDistance(missile, target)-closeRange)); //comment out this line to remove the waving during the flight
}
}
if(lead == null) {
return; //just in case to make sure a correct lead has been calculated
}
//aimAngle = angle between the missile facing and the lead direction
float aimAngle = MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), lead));
//if the missile overshoot the target, turn around
if (Math.abs(aimAngle) > overshoot) {
if (aimAngle < 0) {
missile.giveCommand(ShipCommand.TURN_RIGHT);
} else {
missile.giveCommand(ShipCommand.TURN_LEFT);
}
} else {
//if the lead is forward, turn the missile toward the lead accelerating
missile.giveCommand(ShipCommand.ACCELERATE);
if (aimAngle < 0) {
missile.giveCommand(ShipCommand.TURN_RIGHT);
} else {
missile.giveCommand(ShipCommand.TURN_LEFT);
}
}
// Damp angular velocity if the missile aim is getting close to the targeted angle
if (Math.abs(aimAngle) < Math.abs(missile.getAngularVelocity()) * damping)
{
missile.setAngularVelocity(aimAngle / damping);
}
}
//////////////////////
// TARGETTING //
//////////////////////
public CombatEntityAPI assignTarget(MissileAPI missile)
{
ShipAPI currentTarget = missile.getSource().getShipTarget();
if (currentTarget != null &&
!currentTarget.isFighter() &&
!currentTarget.isDrone() &&
currentTarget.isAlive() &&
currentTarget.getOwner()!=missile.getOwner()){
//return the ship's target if it's valid
return (CombatEntityAPI)currentTarget;
} else {
//else return the closest enemy if in range
ShipAPI theEnemy = AIUtils.getNearestEnemy(missile);
if (theEnemy!=null && MathUtils.getDistance(missile,theEnemy)<=maxSearchRange){
return AIUtils.getNearestEnemy(missile);
} else {return null;}
}
}
@Override
public CombatEntityAPI getTarget() {
return target;
}
@Override
public void setTarget(CombatEntityAPI target) {
this.target = target;
}
public void init(CombatEngineAPI engine) {}
}
//By Tartiflette, simple and fast rocket AI that will try to attack a target in a frontal cone, and not reengage any if it misses.
package data.scripts.ai;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lwjgl.util.vector.Vector2f;
public class SCY_simpleRocketAI implements MissileAIPlugin, GuidedMissileAI {
private CombatEngineAPI engine;
private final MissileAPI missile;
private CombatEntityAPI target;
private Vector2f lead = new Vector2f(0f,0f);
private boolean overshoot = false;
private boolean runOnce = false;
//data
private final float flightSpeed;
private final float maxSearchRange = 2000;
//if the ship has no target, search one in this forward cone
private final float searchCone = 35;
//if the ship's target is out of this attack cone, it can't be reached
private final float cancellingCone = 45;
private final float damping = 0.1f;
private float offsetX=0, offsetY=0;
private boolean eccm=false;
//////////////////////
// DATA COLLECTING //
//////////////////////
public SCY_simpleRocketAI(MissileAPI missile, ShipAPI launchingShip) {
this.missile = missile;
flightSpeed = missile.getMaxSpeed();
for (String s : missile.getSource().getVariant().getHullMods()) {
if (s.equals("ECCMPackage")){
eccm=true;
break;
}
}
}
//////////////////////
// MAIN AI LOOP //
//////////////////////
@Override
public void advance(float amount) {
if (engine != Global.getCombatEngine()) {
this.engine = Global.getCombatEngine();
}
// assign a target only once
if (!runOnce){
setTarget(assignTarget(missile));
if (target!=null && !eccm){
offsetX=MathUtils.getRandomNumberInRange(0, target.getCollisionRadius()/2);
offsetY=MathUtils.getRandomNumberInRange(0, target.getCollisionRadius()/2);
}
runOnce=true;
}
//always accelerate
missile.giveCommand(ShipCommand.ACCELERATE);
//skip the AI if the game is paused, the missile is way off course, engineless or without a target
if (Global.getCombatEngine().isPaused()
|| overshoot
|| missile.isFading()
|| missile.isFizzling()
|| target == null) {
return;
}
//fiding lead point to aim to
lead = AIUtils.getBestInterceptPoint(missile.getLocation(), flightSpeed, target.getLocation(), target.getVelocity());
lead = new Vector2f(lead.x+offsetX,lead.y+offsetY);
if(lead == null) {
return; //just in case to makes sure a correct lead has been calculated
}
//aimAngle = angle between the missile facing and the lead direction
float aimAngle = MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), lead));
//if the missile overshoot the target, just shut the AI
if (Math.abs(aimAngle) > 90) {
overshoot = true;
return;
} else {
//if the lead is forward, turn the missile toward the lead accelerating
if (aimAngle < 0) {
missile.giveCommand(ShipCommand.TURN_RIGHT);
} else {
missile.giveCommand(ShipCommand.TURN_LEFT);
}
}
// Damp angular velocity if the missile aim is getting close to the targeted angle
if (Math.abs(aimAngle) < Math.abs(missile.getAngularVelocity()) * damping)
{
missile.setAngularVelocity(aimAngle / damping);
}
}
//////////////////////
// TARGETTING //
//////////////////////
public CombatEntityAPI assignTarget(MissileAPI missile)
{
ShipAPI source = missile.getSource();
ShipAPI currentTarget = source.getShipTarget();
if (currentTarget != null &&
!currentTarget.isFighter() &&
!currentTarget.isDrone() &&
currentTarget.isAlive() &&
currentTarget.getOwner()!=missile.getOwner() &&
//current target is in the attack cone
Math.abs(MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), currentTarget.getLocation()))) < cancellingCone){
//return the ship's target if it's valid
return (CombatEntityAPI)currentTarget;
} else {
//search for the closest enemy in the cone of attack
ShipAPI closest = null;
float distance, closestDistance = Float.MAX_VALUE;
//grab all nearby enemies
for (ShipAPI tmp : AIUtils.getNearbyEnemies(missile, maxSearchRange))
{
//rule out ships out of the missile attack cone
if (Math.abs(MathUtils.getShortestRotation(missile.getFacing(), VectorUtils.getAngle(missile.getLocation(), tmp.getLocation()))) > searchCone)
{
continue;
}
//sort closest enemy
distance = MathUtils.getDistance(tmp, missile.getLocation());
if (distance < closestDistance)
{
closest = tmp;
closestDistance = distance;
}
}
//return the closest enemy
return closest;
}
}
@Override
public CombatEntityAPI getTarget() {
return target;
}
@Override
public void setTarget(CombatEntityAPI target) {
this.target = target;
}
public void init(CombatEngineAPI engine) {}
}
//Fake beam AI script: stretches a "beam" shaped missile to the nearest intersection with any ship or asteroid and apply damage on impact.
//By Tartiflette and Deathfly
package data.scripts.ai;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import static com.fs.starfarer.api.combat.WeaponAPI.WeaponSize.LARGE;
import static com.fs.starfarer.api.combat.WeaponAPI.WeaponSize.MEDIUM;
import java.awt.Color;
import java.awt.geom.Line2D;
import java.util.List;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
public class SCY_fakeBeamAI implements MissileAIPlugin, GuidedMissileAI {
private CombatEngineAPI engine;
private final MissileAPI missile;
private CombatEntityAPI target=null;
private Vector2f start = new Vector2f();
private boolean runOnce = false;
private float aim=0, timer=0;
//DATA
//beam MAX range
private float range = 500;
//beam DURATION in seconds
private final float duration = 0.5f;
//beam WIDTH in su
private final float width = 15;
//beam burst DAMAGE
private final float defaultDamage = 200;
//beam damage TYPE
private final DamageType type = DamageType.HIGH_EXPLOSIVE;
//beam EMP
private final float emp = 50;
//impact SIZE
private final float size = 50;
//impact COLOR
private final Color color = new Color (150,200,255,255);
//////////////////////
// DATA COLLECTING //
//////////////////////
public SCY_fakeBeamAI(MissileAPI missile, ShipAPI launchingShip) {
this.missile = missile;
//adjust the range depending on the source weapon size
WeaponAPI.WeaponSize wSize = missile.getWeapon().getSize();
if (wSize.equals((WeaponAPI.WeaponSize)LARGE)){
range = 700f;
} else {
if (wSize.equals((WeaponAPI.WeaponSize)MEDIUM)){
range = 600f;
} else {
range = 500f;
}
}
}
//////////////////////
// MAIN AI LOOP //
//////////////////////
@Override
public void advance(float amount) {
if (engine != Global.getCombatEngine()) {
this.engine = Global.getCombatEngine();
}
//cancelling IF
if (Global.getCombatEngine().isPaused()) {return;}
timer+=amount;
//random delay
if (Math.random()+timer*5 <= 0.85f){return;} //comment out this line to remove the random delay
//only run once, with a random delay first
if (!runOnce){
start = missile.getLocation();
aim = missile.getFacing();
CombatEntityAPI theTarget= null;
float damage = defaultDamage;
//default end point
Vector2f end = MathUtils.getPointOnCircumference(start,range,aim);
//list all nearby entities that could be hit
List <CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range+250);
if (!entity.isEmpty()){
for (CombatEntityAPI e : entity){
//ignore phased ships
if (e.getCollisionClass() == CollisionClass.NONE){continue;}
//damage can be reduced against some modded ships
float newDamage = defaultDamage;
Vector2f col = new Vector2f();
//ignore everything but ships...
if (
e instanceof ShipAPI
&&
CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
){
//check for a shield impact, then hull and take the closest one
ShipAPI s = (ShipAPI) e;
//can the beam intersect a shield
Vector2f cShield = getShieldCollisionPoint(start, end, s);
if ( cShield != null ){
col = cShield;
}
//now check for a direct hit
Vector2f cHull = CollisionUtils.getCollisionPoint(start, end, s);
if (
cHull != null
&&
MathUtils.getDistance(start, col) > MathUtils.getDistance(start, cHull)
){
col = cHull;
//check for modded ships with damage reduction
if (s.getHullSpec().getBaseHullId().startsWith("exigency_")){
newDamage = defaultDamage/2;
}
}
} else
//...and asteroids!
if (
e instanceof CombatAsteroidAPI
&&
CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
){
Vector2f cAst = getCollisionPointOnCircumference(start,end,e.getLocation(),e.getCollisionRadius());
if ( cAst != null){
col = cAst;
}
}
//if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
if (col != new Vector2f() && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
end = col;
theTarget = e;
damage = newDamage;
}
}
}
//if the beam impacted something, apply the damage
if (theTarget!=null){
//damage
engine.applyDamage(
theTarget,
end,
damage,
type,
emp,
false,
true,
missile.getSource()
);
//impact flash
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float)Math.random()*size/2+size,
1,
(float)Math.random()*duration/2+duration,
color
);
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float)Math.random()*size/4+size/2,
1,
0.1f,
Color.WHITE
);
}
//stretch the "missile" sprite to the impact point, plus a little margin
float length = MathUtils.getDistance(start, end);
missile.getSpriteAPI().setHeight(length+10);
missile.getSpriteAPI().setAdditiveBlend();
//never run this again
runOnce = true;
}
//check for removal
if (timer>=duration){
engine.removeEntity(missile);
return;
}
//check if the position and orientation has been changed by other scripts and weapons
if (missile.getLocation() != start){
setLocation(missile, start);
}
if (missile.getFacing() != aim){
missile.setFacing(aim);
}
//fading
float fadeColor = Math.max(0,(Math.min(1,(float) Math.cos(timer * Math.PI/(2*duration)))));
//shrinking
float fadeWidth = width*fadeColor;
missile.getSpriteAPI().setWidth(fadeWidth);
missile.getSpriteAPI().setCenterY(fadeWidth/2);
missile.getSpriteAPI().setColor(new Color(fadeColor,fadeColor,fadeColor,fadeColor));
}
@Override
public CombatEntityAPI getTarget() {
return target;
}
@Override
public void setTarget(CombatEntityAPI target) {
this.target = target;
}
/////////////////////////////////////////
// //
// SHIELD HIT //
// //
/////////////////////////////////////////
public static Vector2f getShieldCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship){
// if target not shielded, return null
ShieldAPI shield = ship.getShield();
if (shield == null || shield.isOff()) return null;
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the collision point
Vector2f tmp = new Vector2f(lineEnd);
boolean shieldHit = false;
Vector2f tmp1 = getCollisionPointOnCircumference(lineStart,lineEnd, circleCenter, circleRadius);
if (tmp1 != null){
if(shield.isWithinArc(tmp1)) {
tmp = tmp1;
shieldHit = true;
}
// just in case...
// if the hit come outside the shield's arc but did not hit the hull and hit the shield's "edge".
if (!shield.isWithinArc(tmp1)){
// find the hit point on shield's "edge"
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
tmp = tmp2 == null ? tmp: tmp2;
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge2);
tmp = tmp3 == null ? tmp : tmp3;
// make sure the line did not hit hull.
if (CollisionUtils.getCollisionPoint(lineStart, tmp, ship) == null){
shieldHit = true;
}
}
}
return shieldHit? tmp : null;
}
/////////////////////////////////////////
// //
// CIRCLE COLLISION POINT //
// //
/////////////////////////////////////////
// return the first intersection point of segment lineStart to lineEnd and circumference.
// if lineStart is outside the circle and segment can not intersection with the circumference, will return null
// if lineStart is inside the circle, will return lineStart.
public static Vector2f getCollisionPointOnCircumference(Vector2f lineStart, Vector2f lineEnd, Vector2f circleCenter, float circleRadius){
Vector2f startToEnd = Vector2f.sub(lineEnd, lineStart, null);
Vector2f startToCenter = Vector2f.sub(circleCenter, lineStart, null);
double ptSegDistSq = (float) Line2D.ptSegDistSq(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, circleCenter.x, circleCenter.y);
float circleRadiusSq = circleRadius * circleRadius;
// if lineStart is outside the circle and segment can not reach the circumference, return null
if (ptSegDistSq > circleRadiusSq || (startToCenter.lengthSquared() >= circleRadiusSq && startToCenter.lengthSquared()>startToEnd.lengthSquared())) return null;
// if lineStart is within the circle, return it directly
if (startToCenter.lengthSquared() < circleRadiusSq) return lineStart;
// calculate the intersection point.
startToEnd.normalise(startToEnd);
double dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptSegDistSq);
startToEnd.scale((float) dist);
return Vector2f.add(lineStart, startToEnd, null);
}
/////////////////////////////////////////
// //
// SET TELEPORT LOCATION //
// //
/////////////////////////////////////////
public void setLocation(CombatEntityAPI entity, Vector2f location) {
Vector2f dif = new Vector2f(location);
Vector2f.sub(location, entity.getLocation(), dif);
Vector2f.add(entity.getLocation(), dif, entity.getLocation());
}
}
// By Tartiflette and DeathFly
package data.scripts.util;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import data.scripts.plugins.FakeBeamPlugin;
import java.awt.geom.Line2D;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
public class FakeBeam {
//
//Fake beam generator.
//
//Create a visually convincing beam from arbitrary coordinates.
//It however has several limitation:
// - It deal damage instantly and is therefore only meant to be used for burst beams.
// - It cannot be "cut" by another object passing between the two ends, a very short duration is thus preferable.
// - Unlike vanilla, it deals full damage to armor, be carefull when using HIGH_EXPLOSIVE damage type.
//
// Most of the parameters are self explanatory but just in case:
//
//engine : Combat Engine
//start : source point of the beam
//range : maximum effective range (the beam will visually fade a few pixels farther)
//aim : direction of the beam
//width : width of the beam
//fading : duration of the beam
//normalDamage : nominal burst damage of the beam (don't forget to calculate the skill modifiers before that)
// will potentially be modified when fighting some modded factions like Exigency.
//type : damage type of the beam
//emp : nominal emp damage if any
//source : ship dealing the damage
//size : glow size on the impact point
//duration : duration of the impact glow (should be at least as long as the beam fading)
//color : color of the impact glow
//
//Note that there is no control over the beam's color, you'll have to directly modify the fakeBeamFX.png for that
//
/////////////////////////////////////////
// //
// FAKE BEAM //
// //
/////////////////////////////////////////
public static void applyFakeBeamEffect (CombatEngineAPI engine, Vector2f start, float range, float aim, float width, float fading, float normalDamage, DamageType type, float emp, ShipAPI source, float size, float duration, Color color)
{
CombatEntityAPI theTarget= null;
float damage = normalDamage;
//default end point
Vector2f end = MathUtils.getPointOnCircumference(start,range,aim);
//list all nearby entities that could be hit
List <CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range+500);
if (!entity.isEmpty()){
for (CombatEntityAPI e : entity){
//ignore un-hittable stuff like phased ships
if (e.getCollisionClass() == CollisionClass.NONE){continue;}
//damage can be reduced against some modded ships
float newDamage = normalDamage;
Vector2f col = new Vector2f(1000000,1000000);
//ignore everything but ships...
if (
e instanceof ShipAPI
&&
CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
){
//check for a shield impact, then hull and take the closest one
ShipAPI s = (ShipAPI) e;
//find the collision point with shields/hull
Vector2f hitPoint = getShipCollisionPoint(start, end, s);
if ( hitPoint != null ){
col = hitPoint;
}
//check for modded ships with damage reduction
if (s.getHullSpec().getBaseHullId().startsWith("exigency_")){
newDamage = normalDamage/2;
}
} else
//...and asteroids!
if (
e instanceof CombatAsteroidAPI
&&
CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())
){
Vector2f cAst = getCollisionPointOnCircumference(start,end,e.getLocation(),e.getCollisionRadius());
if ( cAst != null){
col = cAst;
}
}
//if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
if (col != new Vector2f(1000000,1000000) && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
end = col;
theTarget = e;
damage = newDamage;
}
}
//if the beam impacted something, apply the damage
if (theTarget!=null){
//damage
engine.applyDamage(
theTarget,
end,
damage,
type,
emp,
false,
true,
source
);
//impact flash
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float)Math.random()*size/2+size,
1,
(float)Math.random()*duration/2+duration,
color
);
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float)Math.random()*size/4+size/2,
1,
0.1f,
Color.WHITE
);
}
//create the visual effect
Map <String,Float> VALUES = new HashMap<>();
VALUES.put("t", fading); //duration
VALUES.put("w", width); //width
VALUES.put("h", MathUtils.getDistance(start, end)+10); //length
VALUES.put("x", start.x); //origin X
VALUES.put("y", start.y); //origin Y
VALUES.put("a", aim); //angle
//Add the beam to the plugin
FakeBeamPlugin.addMember(VALUES);
}
}
/////////////////////////////////////////
// //
// SHIP HIT //
// //
/////////////////////////////////////////
// return the collision point of segment lineStart to lineEnd and a ship (will consider shield).
// if line can not hit the ship, will return null.
// if lineStart hit the ship, will return lineStart.
// if lineStart hit the shield, will return lineStart.
public static Vector2f getShipCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship){
// if target can not be hit, return null
if (ship.getCollisionClass() == CollisionClass.NONE) return null;
ShieldAPI shield = ship.getShield();
// Check hit point when shield is off.
if(shield == null || shield.isOff()){
return CollisionUtils.getCollisionPoint(lineStart, lineEnd, ship);
}
// If ship's shield is on, thing goes complicated...
else{
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircumference(lineStart, lineEnd, circleCenter, circleRadius);
if (tmp1 != null){
// OK! hit the shield in face
if(shield.isWithinArc(tmp1)){
return tmp1;
} else {
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
boolean hit = false;
Vector2f tmp = new Vector2f(lineEnd);
//the beam cannot go farther than it's max range or the hull
Vector2f hullHit = CollisionUtils.getCollisionPoint(lineStart, lineEnd, ship);
if (hullHit != null){
tmp = hullHit;
hit = true;
}
//find if the shield is hit from the left or right side
if (MathUtils.getShortestRotation(
VectorUtils.getAngle(lineStart, lineEnd),
VectorUtils.getAngle(lineStart, circleCenter)
)
<=0){
//left side hit
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
if(tmp2 != null){
tmp = tmp2;
hit = true;
}
} else {
//right side hit
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge2);
if(tmp3 != null){
tmp = tmp3;
hit = true;
}
}
// return null if do not hit anything.
return hit ? tmp : null;
}
}
}
return null;
}
/////////////////////////////////////////
// //
// CIRCLE COLLISION POINT //
// //
/////////////////////////////////////////
// return the first intersection point of segment lineStart to lineEnd and circumference.
// if lineStart is outside the circle and segment can not intersection with the circumference, will return null.
// if lineStart is inside the circle, will return lineStart.
public static Vector2f getCollisionPointOnCircumference(Vector2f lineStart, Vector2f lineEnd, Vector2f circleCenter, float circleRadius){
Vector2f startToEnd = Vector2f.sub(lineEnd, lineStart, null);
Vector2f startToCenter = Vector2f.sub(circleCenter, lineStart, null);
double ptSegDistSq = (float) Line2D.ptSegDistSq(lineStart.x, lineStart.y, lineEnd.x, lineEnd.y, circleCenter.x, circleCenter.y);
float circleRadiusSq = circleRadius * circleRadius;
// if lineStart is outside the circle and segment can not reach the circumference, return null
if (ptSegDistSq > circleRadiusSq || (startToCenter.lengthSquared() >= circleRadiusSq && startToCenter.lengthSquared()>startToEnd.lengthSquared())) return null;
// if lineStart is within the circle, return it directly
if (startToCenter.lengthSquared() < circleRadiusSq) return lineStart;
// calculate the intersection point.
startToEnd.normalise(startToEnd);
double dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptSegDistSq);
startToEnd.scale((float) dist);
return Vector2f.add(lineStart, startToEnd, null);
}
/////////////////////////////////////////
// //
// SHIELD HIT //
// //
/////////////////////////////////////////
// SHOULD ONLY BE USED WHEN YOU ONLY NEED SHIELD COLLISION POINT!
// if you need the check for a ship hit (considering it's shield), use getShipCollisionPoint instead.
// return the collision point of segment lineStart to lineEnd and ship's shield.
// if the line can not hit the shield or if the ship has no shield, return null.
// if ignoreHull = flase and the line hit the ship's hull first, return null.
// if lineStart is inside the shield, will return lineStart.
public static Vector2f getShieldCollisionPoint(Vector2f lineStart, Vector2f lineEnd, ShipAPI ship, boolean ignoreHull){
// if target not shielded, return null
ShieldAPI shield = ship.getShield();
if (ship.getCollisionClass()==CollisionClass.NONE || shield == null || shield.isOff()) return null;
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircumference(lineStart,lineEnd, circleCenter, circleRadius);
if (tmp1 != null){
// OK! hit the shield in face
if(shield.isWithinArc(tmp1)) return tmp1;
else {
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
Vector2f tmp = new Vector2f(lineEnd);
boolean hit = false;
//find if the shield is hit from the left or right side
if (MathUtils.getShortestRotation(
VectorUtils.getAngle(lineStart, lineEnd),
VectorUtils.getAngle(lineStart, circleCenter)
)
>=0){
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc()/2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge1);
if(tmp2 != null){
tmp = tmp2;
hit = true;
}
} else {
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc()/2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(lineStart, tmp, circleCenter, shieldEdge2);
if(tmp3 != null){
tmp = tmp3;
hit = true;
}
}
// If we don't ignore hull hit, check if there is one...
if (!ignoreHull || CollisionUtils.getCollisionPoint(lineStart, tmp, ship) != null) return null;
// return null if do not hit shield.
return hit ? tmp : null;
}
}
return null;
}
}
//By Tartiflette with DeathFly's help
//draw arbitrary beam sprites wherever you need them and fade them out
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FakeBeamPlugin extends BaseEveryFrameCombatPlugin {
//FAKEBEAMS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used
public static List<Map<String,Float>> FAKEBEAMS = new ArrayList();
//set the function to access FAKEBEAMS from the weapons scripts
public static void addMember(Map<String,Float> data) {
FAKEBEAMS.add(data);
}
private List<Map<String,Float>> toRemove = new ArrayList<>();
private SpriteAPI beam = Global.getSettings().getSprite("beams", "SCY_fakeBeamFX");
@Override
public void init(CombatEngineAPI engine) {
//reinitialize the map
FAKEBEAMS.clear();
}
@Override
public void renderInWorldCoords(ViewportAPI view)
{
CombatEngineAPI engine = Global.getCombatEngine();
if (engine == null){return;}
if (!FAKEBEAMS.isEmpty()){
float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());
//dig through the FAKEBEAMS
for (Map< String,Float > entry : FAKEBEAMS) {
//Time calculation
float time = entry.get("t");
time -= amount;
if (time <= 0){
//faded out, remove the beam and skip
toRemove.add(entry);
} else {
//draw the beam otherwise
float opacity = Math.max(0,(Math.min(1,(float) Math.sin(time * Math.PI))));
render(
beam, //Sprite to draw
entry.get("w") * opacity, //Width entry srinking with the opacity
2*entry.get("h"), //Height entry, multiplied by two because centered
entry.get("a"), //Angle entry
opacity, //opacity duh!
entry.get("x"), //X position entry
entry.get("y") //Y position entry
);
//and store the new time value
entry.put("t", time);
}
}
//remove the beams that faded out
//can't be done from within the iterator or it will fail when members will be missing
if (!toRemove.isEmpty()){
for(Map< String,Float > w : toRemove ){
FAKEBEAMS.remove(w);
}
toRemove.clear();
}
}
}
private void render ( SpriteAPI sprite, float width, float height, float angle, float opacity, float posX, float posY){
//where the magic happen
sprite.setAlphaMult(opacity);
sprite.setSize(width, height);
sprite.setAdditiveBlend();
sprite.setAngle(angle-90);
sprite.renderAtCenter(posX, posY);
}
}
{
"plugins":{
"FakeBeamPlugin":"data.scripts.plugins.FakeBeamPlugin",
},
"graphics":{
"beams":{
"SCY_fakeBeamFX":"graphics/FAKEBEAM/fakeBeamFX.png",
},
}
}
for (int x=0; x<nbRays; x++) {
FakeBeam.applyFakeBeamEffect(
engine,
weapon.getLocation(), //start point
beamRange, //beam range
weapon.getFacing()+MathUtils.getRandomNumberInRange(-10, 10), //spreading
width, //beam width
duration, //beam duration
defaultDamage, //effective damage (will be modified against some specific mods)
dmgType, //damage type. Remember that beams are supposed to deal half damage against armor
defaultEmp, //effective EMP
missile.getSource(), //damage source
size, //impact size
duration, //impact duration
color //impact color
);
}
*Awesome quote regarding laser shotguns*
//By Tartiflette and Deathfly
//draw arbitrary beam sprites wherever you need them and fade them out
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.awt.Color;
import java.util.ArrayList;
import java.util.List;
import org.lazywizard.lazylib.FastTrig;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lwjgl.util.vector.Vector2f;
public class FakeBeamPlugin extends BaseEveryFrameCombatPlugin {
//FAKEBEAMS is the core of the script, storing both the weapons that have a flash rendered and the index of the current sprite used
private static final List<FakeBeam> FAKEBEAMS = new ArrayList<>();
private final List<FakeBeam> toRemove = new ArrayList<>();
@Override
public void init(CombatEngineAPI engine) {
//reinitialize the map
FAKEBEAMS.clear();
}
@Override
public void renderInWorldCoords(ViewportAPI view) {
CombatEngineAPI engine = Global.getCombatEngine();
if (engine == null) {
return;
}
if (!FAKEBEAMS.isEmpty()) {
float amount = (engine.isPaused() ? 0f : engine.getElapsedInLastFrame());
//dig through the FAKEBEAMS
for (FakeBeam fakeBeam : FAKEBEAMS) {
//Time calculation
float fadeIn = fakeBeam.getFadeInTimer();
float liveTime = fakeBeam.getDurationTimer();
float fadeOut = fakeBeam.getFadeOutTimer();
float opacity = 0;
// check if the beam is fading in
if (fadeIn > 0) {
fadeIn -= amount;
fakeBeam.setFadeInTimer(fadeIn);
opacity = (float) FastTrig.cos((fadeIn / fakeBeam.getFadeInDuration()) * (Math.PI / 2));
// check if the beam is on full power
} else if (liveTime > 0) {
liveTime -= amount;
fakeBeam.setDurationTimer(liveTime);
opacity = 1;
// check if the beam is fading out
} else if (fadeOut > 0) {
fadeOut -= amount;
fakeBeam.setFadeOutTimer(fadeOut);
opacity = (float) FastTrig.sin((fadeOut / fakeBeam.getFadeOutDuration()) * (Math.PI / 2));
} else {
//completely faded out, remove the beam and skip
toRemove.add(fakeBeam);
continue;
}
//draw the beam
render(
fakeBeam.getSprite(), //Sprite to draw
fakeBeam.getColor(), //Sprite color
fakeBeam.getWidth() * opacity, //Width entry srinking with the opacity
fakeBeam.getLength(), //Height entry, multiplied by two because centered
fakeBeam.getAngleToRender(), //Angle entry
opacity, //opacity duh!
fakeBeam.getCenter()//Center
);
}
//remove the beams that faded out
//can't be done from within the iterator or it will fail when members will be missing
if (!toRemove.isEmpty()) {
for (FakeBeam w : toRemove) {
FAKEBEAMS.remove(w);
}
toRemove.clear();
}
}
}
private void render(SpriteAPI sprite, Color color, float width, float height, float angle, float opacity, Vector2f center) {
//where the magic happen
if (color != null) {
sprite.setColor(color);
}
sprite.setAlphaMult(opacity);
sprite.setSize(height, width);
sprite.setAdditiveBlend();
sprite.setAngle(angle);
sprite.renderAtCenter(center.x, center.y);
}
// a class to record all we need to render a fake beam.
public static class FakeBeam {
protected SpriteAPI beamSprite = Global.getSettings().getSprite("graphics/fx/beamcore.png");
protected Color beamColor = Color.white;
protected float width = 0f;
protected float length = 0f;
protected float angleForRender = 0f;
protected float duration = 0f;
protected float durationTimer = 0f;
protected float fadeInDuration = 0f;
protected float fadeInTimer = 0f;
protected float fadeOutDuration = 0f;
protected float fadeOutTimer = 0f;
protected Vector2f centerLoc = null;
protected Vector2f form = null;
protected Vector2f to = null;
public void setSprite(SpriteAPI beamSprite) {
this.beamSprite = beamSprite;
}
public SpriteAPI getSprite() {
return beamSprite;
}
public void setColor(Color beamColor) {
this.beamColor = beamColor;
}
public Color getColor() {
return beamColor;
}
public void setWidth(float width) {
this.width = width;
}
public float getWidth() {
return width;
}
public void setLength(float length) {
this.length = length;
if (form != null) {
setTo(MathUtils.getPointOnCircumference(form, length, angleForRender));
}
}
public float getLength() {
return length;
}
public void setCurrAngle(float currAngle) {
if (form != null) {
setTo(MathUtils.getPointOnCircumference(form, length, currAngle));
}
}
public float getCurrAngle() {
return angleForRender;
}
public float getAngleToRender() {
return angleForRender;
}
public void setDuration(float duration) {
this.duration = duration;
this.durationTimer = duration;
}
public float getDuration() {
return duration;
}
public void setDurationTimer(float durationTimer) {
this.durationTimer = durationTimer;
}
public float getDurationTimer() {
return durationTimer;
}
public void setFadeInDuration(float fadeInDuration) {
this.fadeInDuration = fadeInDuration;
this.fadeInTimer = fadeInDuration;
}
public float getFadeInDuration() {
return fadeInDuration;
}
public void setFadeInTimer(float fadeInTimer) {
this.fadeInTimer = fadeInTimer;
}
public float getFadeInTimer() {
return fadeInTimer;
}
public void setFadeOutDuration(float fadeOutDuration) {
this.fadeOutDuration = fadeOutDuration;
this.fadeOutTimer = fadeOutDuration;
}
public float getFadeOutDuration() {
return fadeOutDuration;
}
public void setFadeOutTimer(float fadeOutTimer) {
this.fadeOutTimer = fadeOutTimer;
}
public float getFadeOutTimer() {
return fadeOutTimer;
}
public void setForm(Vector2f form) {
this.form = form;
if (this.form != null && this.to != null) {
this.centerLoc = MathUtils.getMidpoint(this.form, this.to);
this.length = MathUtils.getDistance(form, to);
this.angleForRender = VectorUtils.getAngle(form, to);
}
}
public Vector2f getForm() {
return form;
}
public void setTo(Vector2f to) {
this.to = to;
if (this.to != null && this.form != null) {
this.centerLoc = MathUtils.getMidpoint(this.form, this.to);
this.length = MathUtils.getDistance(form, to);
this.angleForRender = VectorUtils.getAngle(form, to);
}
}
public Vector2f getTo() {
return to;
}
public void setCenter(Vector2f center) {
this.centerLoc = center;
}
public Vector2f getCenter() {
return centerLoc;
}
}
// 4 methods for render fake beam, will return FakeBeam for some after rendered modify.
public static FakeBeam renderFakeBeam(Vector2f form, Vector2f to, float width, float duration, float fadeInDuration, float fadeOutDuration, SpriteAPI beamSprite, Color beamColor) {
FakeBeam fakeBeam = new FakeBeam();
if (beamSprite != null) {
fakeBeam.setSprite(beamSprite);
}
fakeBeam.setColor(beamColor);
fakeBeam.setForm(form);
fakeBeam.setTo(to);
fakeBeam.setWidth(width);
fakeBeam.setDuration(duration);
fakeBeam.setFadeInDuration(fadeInDuration);
fakeBeam.setFadeOutDuration(fadeOutDuration);
FAKEBEAMS.add(fakeBeam);
return fakeBeam;
}
public static FakeBeam renderFakeBeam(Vector2f form, Vector2f to, float width, float duration, SpriteAPI beamSprite, Color beamColor) {
return renderFakeBeam(form, to, width, duration - 0.5f, 0.25f, 0.25f, beamSprite, beamColor);
}
public static FakeBeam renderFakeBeam(Vector2f form, float length, float aim, float width, float duration, float fadeInDuration, float fadeOutDuration, SpriteAPI beamSprite, Color beamColor) {
Vector2f to = MathUtils.getPointOnCircumference(form, length, aim);
return renderFakeBeam(form, to, width, duration, fadeInDuration, fadeOutDuration, beamSprite, beamColor);
}
public static FakeBeam renderFakeBeam(Vector2f form, float length, float aim, float width, float duration, SpriteAPI beamSprite, Color beamColor) {
Vector2f to = MathUtils.getPointOnCircumference(form, length, aim);
return renderFakeBeam(form, to, width, duration, beamSprite, beamColor);
}
}
// by Deathfly and Tartiflette
package data.scripts.util;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import java.awt.geom.Line2D;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
public class CollisionUtilsEX {
/////////////////////////////////////////
// //
// SHIP HIT //
// //
/////////////////////////////////////////
// return the collision point of segment segStart to segEnd and a ship (will consider shield).
// if segment can not hit the ship, will return null.
// if segStart hit the ship, will return segStart.
// if segStart hit the shield, will return segStart.
public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship) {
// if target can not be hit, return null
if (ship.getCollisionClass() == CollisionClass.NONE) {
return null;
}
ShieldAPI shield = ship.getShield();
// Check hit point when shield is off.
if (shield == null || shield.isOff()) {
return CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
} // If ship's shield is on, thing goes complicated...
else {
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircumference(segStart, segEnd, circleCenter, circleRadius);
if (tmp1 != null) {
// OK! hit the shield in face
if (shield.isWithinArc(tmp1)) {
return tmp1;
} else {
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
boolean hit = false;
Vector2f tmp = new Vector2f(segEnd);
//the beam cannot go farther than it's max range or the hull
Vector2f hullHit = CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
if (hullHit != null) {
tmp = hullHit;
hit = true;
}
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
if (tmp2 != null) {
tmp = tmp2;
hit = true;
}
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
if (tmp3 != null) {
tmp = tmp3;
hit = true;
}
// return null if do not hit anything.
return hit ? tmp : null;
}
}
}
return null;
}
/////////////////////////////////////////
// //
// CIRCLE COLLISION POINT //
// //
/////////////////////////////////////////
// return the first intersection point of segment segStart to segEnd and circumference.
// if segStart is outside the circle and segment can not intersection with the circumference, will return null.
// if segStart is inside the circle, will return segStart.
public static Vector2f getCollisionPointOnCircumference(Vector2f segStart, Vector2f segEnd, Vector2f circleCenter, float circleRadius) {
Vector2f startToEnd = Vector2f.sub(segEnd, segStart, null);
Vector2f startToCenter = Vector2f.sub(circleCenter, segStart, null);
double ptLineDistSq = (float) Line2D.ptLineDistSq(segStart.x, segStart.y, segEnd.x, segEnd.y, circleCenter.x, circleCenter.y);
float circleRadiusSq = circleRadius * circleRadius;
// if lineStart is within the circle, return it directly
if (startToCenter.lengthSquared() < circleRadiusSq) {
return segStart;
}
// if lineStart is outside the circle and segment can not reach the circumference, return null
if (ptLineDistSq > circleRadiusSq || startToCenter.length() - circleRadius > startToEnd.length()) {
return null;
}
// calculate the intersection point.
startToEnd.normalise(startToEnd);
double dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptLineDistSq);
startToEnd.scale((float) dist);
return Vector2f.add(segStart, startToEnd, null);
}
/////////////////////////////////////////
// //
// SHIELD HIT //
// //
/////////////////////////////////////////
// SHOULD ONLY BE USED WHEN YOU ONLY NEED SHIELD COLLISION POINT!
// if you need the check for a ship hit (considering it's shield), use getShipCollisionPoint instead.
// return the collision point of segment segStart to segEnd and ship's shield.
// if the segment can not hit the shield or if the ship has no shield, return null.
// if ignoreHull = flase and the segment hit the ship's hull first, return null.
// if segStart is inside the shield, will return segStart.
public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull) {
// if target not shielded, return null
ShieldAPI shield = ship.getShield();
if (ship.getCollisionClass() == CollisionClass.NONE || shield == null || shield.isOff()) {
return null;
}
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircumference(segStart, segEnd, circleCenter, circleRadius);
if (tmp1 != null) {
// OK! hit the shield in face
if (shield.isWithinArc(tmp1)) {
return tmp1;
} else {
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
Vector2f tmp = new Vector2f(segEnd);
boolean hit = false;
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
if (tmp2 != null) {
tmp = tmp2;
hit = true;
}
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
if (tmp3 != null) {
tmp = tmp3;
hit = true;
}
// If we don't ignore hull hit, check if there is one...
if (!ignoreHull && CollisionUtils.getCollisionPoint(segStart, tmp, ship) != null) {
return null;
}
// return null if do not hit shield.
return hit ? tmp : null;
}
}
return null;
}
}
// By Tartiflette and Deathfly
package data.scripts.util;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.CombatAsteroidAPI;
import java.awt.Color;
import org.lwjgl.util.vector.Vector2f;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import data.scripts.plugins.FakeBeamPlugin;
import java.util.List;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
public class FakeBeam {
//
//Fake beam generator.
//
//Create a visually convincing beam from arbitrary coordinates.
//It however has several limitation:
// - It deal damage instantly and is therefore only meant to be used for burst beams.
// - It cannot be "cut" by another object passing between the two ends, a very short duration is thus preferable.
// - Unlike vanilla, it deals full damage to armor, be carefull when using HIGH_EXPLOSIVE damage type.
//
// Most of the parameters are self explanatory but just in case:
//
//engine : Combat Engine
//start : source point of the beam
//range : maximum effective range (the beam will visually fade a few pixels farther)
//aim : direction of the beam
//width : width of the beam
//fading : duration of the beam
//normalDamage : nominal burst damage of the beam (don't forget to calculate the skill modifiers before that)
// will potentially be modified when fighting some modded factions like Exigency.
//type : damage type of the beam
//emp : nominal emp damage if any
//source : ship dealing the damage
//size : glow size on the impact point
//duration : duration of the impact glow (should be at least as long as the beam fading)
//color : color of the impact glow
//
//Note that there is no control over the beam's color, you'll have to directly modify the fakeBeamFX.png for that
//
/////////////////////////////////////////
// //
// FAKE BEAM //
// //
/////////////////////////////////////////
public static void applyFakeBeamEffect(CombatEngineAPI engine, Vector2f start, float range, float aim, float width, float fading, float normalDamage, DamageType type, float emp, ShipAPI source, float size, float duration, Color color) {
CombatEntityAPI theTarget = null;
float damage = normalDamage;
// beam sprite to darw.
SpriteAPI beamSprite = Global.getSettings().getSprite("beams", "SCY_fakeBeamFX");
//default end point
Vector2f end = MathUtils.getPointOnCircumference(start, range, aim);
//list all nearby entities that could be hit
List<CombatEntityAPI> entity = CombatUtils.getEntitiesWithinRange(start, range + 500);
if (!entity.isEmpty()) {
for (CombatEntityAPI e : entity) {
//ignore un-hittable stuff like phased ships
if (e.getCollisionClass() == CollisionClass.NONE) {
continue;
}
//damage can be reduced against some modded ships
float newDamage = normalDamage;
Vector2f col = new Vector2f(1000000, 1000000);
//ignore everything but ships...
if (e instanceof ShipAPI
&& CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())) {
//check for a shield impact, then hull and take the closest one
ShipAPI s = (ShipAPI) e;
//find the collision point with shields/hull
Vector2f hitPoint = CollisionUtilsEX.getShipCollisionPoint(start, end, s);
if (hitPoint != null) {
col = hitPoint;
}
//check for modded ships with damage reduction (not in use)
// if (s.getHullSpec().getBaseHullId().startsWith("exigency_")) {
// newDamage = normalDamage / 2;
// }
//check for beam damage reduction
newDamage = normalDamage * s.getMutableStats().getBeamDamageTakenMult().getModifiedValue();
} else //...and asteroids!
if (e instanceof CombatAsteroidAPI
&& CollisionUtils.getCollides(start, end, e.getLocation(), e.getCollisionRadius())) {
Vector2f cAst = CollisionUtilsEX.getCollisionPointOnCircumference(start, end, e.getLocation(), e.getCollisionRadius());
if (cAst != null) {
col = cAst;
}
}
//if there was an impact and it is closer than the curent beam end point, set it as the new end point and store the target to apply damage later damage
if (col != new Vector2f(1000000, 1000000) && MathUtils.getDistance(start, col) < MathUtils.getDistance(start, end)) {
end = col;
theTarget = e;
damage = newDamage;
}
}
//if the beam impacted something, apply the damage
if (theTarget != null) {
//damage
engine.applyDamage(
theTarget,
end,
damage,
type,
emp,
false,
true,
source
);
//impact flash
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float) Math.random() * size / 2 + size,
1,
(float) Math.random() * duration / 2 + duration,
color
);
engine.addHitParticle(
end,
theTarget.getVelocity(),
(float) Math.random() * size / 4 + size / 2,
1,
0.1f,
Color.WHITE
);
}
//Add the beam to the plugin
FakeBeamPlugin.renderFakeBeam(start, MathUtils.getDistance(start, end) + 10, aim, width, duration, beamSprite, null);
}
}
}
{
"plugins":{
"FakeBeamPlugin":"data.scripts.plugins.FakeBeamPlugin",
},
"graphics":{
"beams":{
"SCY_fakeBeamFX":"graphics/FAKEBEAM/fakeBeamFX.png",
},
}
}
If( col != new Vector2f(1000000, 1000000)........It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.
That expression is redundant, it will always be true.
If( col != new Vector2f(1000000, 1000000)........It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.
That expression is redundant, it will always be true.
If( col != new Vector2f(1000000, 1000000)........It's there to check if the temporary "col" point has been moved to a collision point since it's creation, witch is the purpose of the few lines above. If it's still at (1000000,1000000) that means the beam didn't collide with anything, but it's not always the case.
That expression is redundant, it will always be true.
You're creating a new object, then checking its reference for inequality against something else.
A newly created reference will never be equal to something (anything!) else.
Thus "col != new Vector2f(1000000, 1000000)" is a tautology, it will always evaluate to "true". (RuntimeExceptions/Errors/concurrency not withstanding.)
package data.scripts.plugins.beamRenderer;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import com.fs.starfarer.api.input.InputEventAPI;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
/** Draw arbitrary beam sprites wherever you need them and fade them out. Has none of the drawbacks of the old code (static beams, one-time damage).
* @author Tartiflette and Deathfly (complete and TOTAL rewrite by kazi)
*/
public class BeamRendererPlugin extends BaseEveryFrameCombatPlugin {
private CombatEngineAPI engine;
private static ArrayList<BeamSpec> beamsToRender = new ArrayList<>();
private ArrayList<BeamSpec> toRemove = new ArrayList<>();
// add beams to the rendering/damage thread this way (by creating a NEW beamSpec object using the constructor)
public static void addBeam(BeamSpec newBeam) {
beamsToRender.add(newBeam);
newBeam.calcImpactPoint();
// only draw flashes once
newBeam.engine.addHitParticle(newBeam.startLoc, new Vector2f(),
(float) Math.random() * newBeam.size / 2 + newBeam.size,
1,
(float) Math.random() * newBeam.duration / 2 + newBeam.duration,
newBeam.beamColor);
if (newBeam.target != null) {
newBeam.engine.addHitParticle(newBeam.hitLoc, newBeam.target.getVelocity(),
(float) Math.random() * newBeam.size / 4 + newBeam.size / 2,
1, 0.1f, Color.WHITE);
newBeam.engine.addHitParticle(newBeam.hitLoc, newBeam.target.getVelocity(),
(float) Math.random() * newBeam.size / 2 + newBeam.size,
1,
(float) Math.random() * newBeam.duration / 2 + newBeam.duration,
newBeam.beamColor);
}
}
@Override
public void init(CombatEngineAPI combatEngineAPI) {
engine = Global.getCombatEngine();
//reinitialize the map
beamsToRender.clear();
}
@Override
public void advance(float amount, List<InputEventAPI> events) {
if (engine == null || engine.isPaused()) {
return;
}
// recalculate render coords and apply damage
for (BeamSpec beam : beamsToRender) {
beam.update(amount);
}
}
@Override
public void renderInWorldCoords(ViewportAPI view) {
if (engine == null) {
return;
}
if (!beamsToRender.isEmpty()) {
// iterate through the beams, rendering each in turn
for (BeamSpec beam : beamsToRender) {
if (beam.isDone) {
toRemove.add(beam);
continue;
}
//draw the beam
SpriteAPI sprite = beam.sprite;
sprite.setAlphaMult(beam.opacity);
sprite.setSize(MathUtils.getDistance(beam.startLoc, beam.hitLoc) + 25f, beam.width);
sprite.setAngle(beam.aim);
Vector2f center = MathUtils.getMidpoint(beam.startLoc, beam.hitLoc);
sprite.renderAtCenter(center.x, center.y);
}
//remove the beams that are done
if (!toRemove.isEmpty()) {
beamsToRender.removeAll(toRemove);
toRemove.clear();
}
}
}
}
package data.scripts.plugins.beamRenderer;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.graphics.SpriteAPI;
import data.scripts.util.ilk_CollisionUtilsEX;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.FastTrig;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
import java.awt.*;
import java.util.List;
/**
* Created by Jeff on 2015-08-09.
*/
public class BeamSpec {
// initialization variables
CombatEngineAPI engine;
ShipAPI source;
float damage;
DamageType type;
float empDamage;
float duration;
float fadeOut;
float fadeIn;
float aim;
float range;
float width;
SpriteAPI sprite;
float size;
Color beamColor;
// dynamic variables calculated by update method
private float interval;
private float delta;
private static final float RECALC_INTERVAL = 0.125f;
//renderer variables
Vector2f startLoc;
Vector2f hitLoc;
boolean isDone;
float intensity;
float opacity;
CombatEntityAPI target;
public BeamSpec(CombatEngineAPI combatEngineAPI, ShipAPI setSource, Vector2f startLocSet, float rangeSet, float aimSet, float damageAmt,
DamageType damageType, float empDamageAmt, float time, float fadeInSet, float fadeOutSet,
String spriteKey, String spriteName, float wide, Color colorSet) {
engine = combatEngineAPI;
source = setSource;
startLoc = startLocSet;
damage = damageAmt;
type = damageType;
empDamage = empDamageAmt;
duration = time;
fadeOut = fadeOutSet;
fadeIn = fadeInSet;
range = rangeSet;
aim = aimSet;
width = wide;
size = 2 * width;
sprite = Global.getSettings().getSprite(spriteKey, spriteName);
sprite.setAdditiveBlend();
beamColor = colorSet;
interval = 0f;
intensity = 0f;
opacity = 0f;
hitLoc = new Vector2f();
isDone = false;
}
/** Recalculate damage and hit location based on updated time since last frame
*
* @param amount delta time
*/
public void update(float amount) {
delta += amount;
// where are we at in the current beam firing cycle
if (delta <= fadeIn) {
intensity = delta / fadeIn;
opacity = (float) (FastTrig.sin(intensity * Math.PI / 2));
//second condition for elseif not necessary for next as values lower than fadeIn have already been caught
} else if (delta <= duration + fadeIn) {
intensity = 1f;
opacity = 1f;
} else if (delta <= fadeIn + duration + fadeOut) {
intensity = 1f - ((delta - fadeIn - duration) / fadeOut);
opacity = (float) (FastTrig.sin(intensity * Math.PI / 2));
} else {
intensity = 0f;
opacity = 0f;
isDone = true;
}
// only recalc hitpoint after a certain update interval to avoid wasting cpu for no reason
interval += amount;
if (interval > RECALC_INTERVAL) {
interval = 0f;
calcImpactPoint();
}
// recalc damage if we've hit something
if (target != null) {
float currDamage = damage * amount * intensity;
float currEmp = empDamage * amount * intensity;
if (target instanceof ShipAPI) {
ShipAPI ship = (ShipAPI) target;
//check for modded ships with damage reduction
//if (ship.getHullSpec().getBaseHullId().startsWith("exigency_") ) currDamage /= 2;
//check for beam damage reduction
currDamage *= ship.getMutableStats().getBeamDamageTakenMult().getModifiedValue();
//check for emp damage reduction
currEmp *= ship.getMutableStats().getEmpDamageTakenMult().getModifiedValue();
}
// DEAL DE DAMAGE!
engine.applyDamage(target, hitLoc, currDamage, type, currEmp, false, true, source);
}
}
// did we hit something?
void calcImpactPoint() {
//default end point
Vector2f end = MathUtils.getPointOnCircumference(startLoc, range, aim);
CombatEntityAPI theTarget = null;
//list all nearby entities that could be hit
for (CombatEntityAPI entity : CombatUtils.getEntitiesWithinRange(startLoc, range + 500f)) {
// ignore un-hittable stuff like phased ships
if (entity.getCollisionClass() == CollisionClass.NONE) {
continue;
}
// ignore friendlies
if (entity.getOwner() == source.getOwner()) continue;
// check for collision
if (CollisionUtils.getCollides(startLoc, end, entity.getLocation(), entity.getCollisionRadius())) {
Vector2f collide = null;
// ship collision?
if (entity instanceof ShipAPI) {
//find the collision point with shields/hull
Vector2f hitPoint = ilk_CollisionUtilsEX.getShipCollisionPoint(startLoc, end, (ShipAPI) entity);
if (hitPoint != null) collide = hitPoint;
// asteroid collision?
} else if (entity instanceof CombatAsteroidAPI) {
Vector2f hitPoint = ilk_CollisionUtilsEX.getCollisionPointOnCircumference(startLoc, end, entity.getLocation(), entity.getCollisionRadius());
if (hitPoint != null) collide = hitPoint;
}
//if impact is closer than the curent beam end point, set it as the new end point and save target
if ((collide != null) && (MathUtils.getDistance(startLoc, collide) < MathUtils.getDistance(startLoc, end))) {
end = collide;
theTarget = entity;
}
}
}
//okay update variables
target = theTarget;
hitLoc = end;
}
public void setColor(Color color) {
sprite.setColor(color);
beamColor = color;
}
public void setWidth(float wide) {
width = wide;
}
public void setStartLoc(Vector2f startLoc) {
this.startLoc = startLoc;
}
}
BeamRendererPlugin.addBeam(new BeamSpec(engine, proj.getSource(), fireLoc, 700f, proj.getFacing(),
1000f, DamageType.ENERGY, 0f,
0.7f, 0.1f, 0.2f, //duration, in, out
"beams", "ilk_fakeBeamFX", 27, new Color(224,184,225,175)));
//By Tartiflette, highly customizable Missile AI.
package data.scripts.ai;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
public class CustomMissileAI implements MissileAIPlugin, GuidedMissileAI {
//////////////////////
// SETTINGS //
//////////////////////
//Angle with the target beyond which the missile turn around without accelerating. Avoid endless circling.
// Set to a negative value to disable
private final float OVERSHOT_ANGLE=60;
//Time to complete a wave in seconds.
private final float WAVE_TIME=2;
//Angle of the waving in degree (divided by 3 with ECCM). Set to a negative value to avoid all waving.
private final float WAVE_AMPLITUDE=15;
//Damping of the turn speed when closing on the desired aim. The smaller the snappier.
private final float DAMPING=0.1f;
//Does the missile try to correct it's velocity vector as fast as possible or just point to the desired direction and drift a bit?
// Can create strange results with large waving
// Require a projectile with a decent turn rate and around twice that in turn acceleration compared to their top speed
// Useful for slow torpedoes with low forward acceleration, or ultra precise anti-fighter missiles.
private final boolean OVERSTEER=false; //REQUIRE NO OVERSHOOT ANGLE!
//Does the missile switch its target if it has been destroyed?
private final boolean TARGET_SWITCH=true;
//Does the missile find a random target or aways tries to hit the ship's one?
// 0: No random target seeking,
// If the launching ship has a valid target, the missile will pursue that one.
// If there is no target selected, it will check for an unselected cursor target.
// If there is none, it will pursue its closest valid threat.
// 1: Local random target,
// If the ship has a target selected, the missile will pick a random valid threat around that one.
// If the ship has none, the missile will pursue a random valid threat around the cursor, or itself in AI control.
// 2: Full random,
// The missile will always seek a random valid threat around itself.
private final Integer RANDOM_TARGET=0;
//Both targeting behavior can be false for a missile that always get the ship's target or the closest one
//Prioritize hitting fighters and drones (if false, the missile will still be able to target fighters but not drones)
private final boolean ANTI_FIGHTER=false; //INCOMPATIBLE WITH ASSAULT
//Target the biggest threats first
private final boolean ASSAULT=true; //INCOMPATIBLE WITH ANTI-FIGHTER
//range in which the missile seek a target in game units.
private final float MAX_SEARCH_RANGE = 1500;
//range under which the missile start to get progressively more precise in game units.
private float PRECISION_RANGE=500;
//Is the missile lead the target or tailchase it?
private final boolean LEADING=true;
//Leading loss without ECCM hullmod. The higher, the less accurate the leading calculation will be.
// 1: perfect leading with and without ECCM
// 2: half precision without ECCM
// 3: a third as precise without ECCM. Default
// 4, 5, 6 etc : 1/4th, 1/5th, 1/6th etc precision.
private float ECCM=3; //A VALUE BELOW 1 WILL PREVENT THE MISSILE FROM EVER HITTING ITS TARGET!
//////////////////////
// VARIABLES //
//////////////////////
//max speed of the missile after modifiers.
private final float MAX_SPEED;
//Max range of the missile after modifiers.
private final float MAX_RANGE;
//Random starting offset for the waving.
private final float OFFSET;
private CombatEngineAPI engine;
private final MissileAPI MISSILE;
private CombatEntityAPI target;
private Vector2f lead = new Vector2f();
private boolean launch=true;
private float timer=0, check=0f;
//////////////////////
// DATA COLLECTING //
//////////////////////
public CustomMissileAI(MissileAPI missile, ShipAPI launchingShip) {
this.MISSILE = missile;
MAX_SPEED = missile.getMaxSpeed();
MAX_RANGE = missile.getWeapon().getRange();
if (missile.getSource().getVariant().getHullMods().contains("eccm")){
ECCM=1;
}
//calculate the precision range factor
PRECISION_RANGE=(float)Math.pow((2*PRECISION_RANGE),2);
OFFSET=(float)(Math.random()*Math.PI*2);
}
//////////////////////
// MAIN AI LOOP //
//////////////////////
@Override
public void advance(float amount) {
if (engine != Global.getCombatEngine()) {
this.engine = Global.getCombatEngine();
}
//skip the AI if the game is paused, the missile is engineless or fading
if (Global.getCombatEngine().isPaused() || MISSILE.isFading() || MISSILE.isFizzling()) {return;}
//assigning a target if there is none or it got destroyed
if (target == null
|| target.getOwner()==MISSILE.getOwner()
|| (TARGET_SWITCH && (target instanceof ShipAPI && ((ShipAPI) target).isHulk())
|| !engine.isEntityInPlay(target)
)
){
setTarget(assignTarget(MISSILE));
//forced acceleration by default
MISSILE.giveCommand(ShipCommand.ACCELERATE);
return;
}
timer+=amount;
//finding lead point to aim to
if(launch || timer>=check){
launch=false;
timer -=check;
//set the next check time
check = Math.min(
0.25f,
Math.max(
0.03f,
MathUtils.getDistanceSquared(MISSILE, target)/PRECISION_RANGE)
);
if(LEADING){
//best intercepting point
lead = AIUtils.getBestInterceptPoint(
MISSILE.getLocation(),
MAX_SPEED*ECCM, //if eccm is intalled the point is accurate, otherwise it's placed closer to the target (almost tailchasing)
target.getLocation(),
target.getVelocity()
);
//null pointer protection
if (lead == null) {
lead = target.getLocation();
}
} else {
lead = target.getLocation();
}
}
//best velocity vector angle for interception
float correctAngle = VectorUtils.getAngle(
MISSILE.getLocation(),
lead
);
if (OVERSTEER){
//velocity angle correction
float offCourseAngle = MathUtils.getShortestRotation(
VectorUtils.getFacing(MISSILE.getVelocity()),
correctAngle
);
float correction = MathUtils.getShortestRotation(
correctAngle,
VectorUtils.getFacing(MISSILE.getVelocity())+180
)
* 0.5f * //oversteer
(float)((Math.sin(Math.PI/90*(Math.min(Math.abs(offCourseAngle),45))))); //damping when the correction isn't important
//modified optimal facing to correct the velocity vector angle as soon as possible
correctAngle = correctAngle+correction;
}
if(WAVE_AMPLITUDE>0){
//waving
float multiplier=1;
if(ECCM<=1){
multiplier=0.3f;
}
correctAngle+=multiplier*WAVE_AMPLITUDE*check*Math.cos(OFFSET+MISSILE.getElapsed()*(2*Math.PI/WAVE_TIME));
}
//target angle for interception
float aimAngle = MathUtils.getShortestRotation( MISSILE.getFacing(), correctAngle);
if(OVERSHOT_ANGLE<=0 || Math.abs(aimAngle)<OVERSHOT_ANGLE){
MISSILE.giveCommand(ShipCommand.ACCELERATE);
}
if (aimAngle < 0) {
MISSILE.giveCommand(ShipCommand.TURN_RIGHT);
} else {
MISSILE.giveCommand(ShipCommand.TURN_LEFT);
}
// Damp angular velocity if the missile aim is getting close to the targeted angle
if (Math.abs(aimAngle) < Math.abs(MISSILE.getAngularVelocity()) * DAMPING) {
MISSILE.setAngularVelocity(aimAngle / DAMPING);
}
}
//////////////////////
// TARGETING //
//////////////////////
public CombatEntityAPI assignTarget(MissileAPI missile){
ShipAPI theTarget=null;
ShipAPI source = missile.getSource();
ShipAPI currentTarget;
//check for a target from its source
if(source != null
&& source.getShipTarget() != null
&& source.getShipTarget() instanceof ShipAPI
&& source.getShipTarget().getOwner() != missile.getOwner()
){
currentTarget=source.getShipTarget();
} else {
currentTarget=null;
}
//random target selection
if (RANDOM_TARGET>0){
//random mode 1: the missile will look for a target around itsef
Vector2f location = missile.getLocation();
//random mode 2: if its source has a target selected, it will look for random one around that point
if( RANDOM_TARGET<2){
if(currentTarget != null
&& currentTarget.isAlive()
&& MathUtils.isWithinRange(missile, currentTarget, MAX_RANGE)
){
location = currentTarget.getLocation();
} else if (source != null
&& source.getMouseTarget()!=null){
location=source.getMouseTarget();
}
}
//fetch the right kind of target
if(ANTI_FIGHTER){
theTarget = getRandomFighterTarget(location);
} else if(ASSAULT){
theTarget = getRandomLargeTarget(location);
} else {
theTarget = getAnyTarget(location);
}
//non random targeting
} else {
if(source!=null){
//ship target first
if(currentTarget!=null
&& currentTarget.isAlive()
&& currentTarget.getOwner()!=missile.getOwner()
&& !(ANTI_FIGHTER && !(currentTarget.isDrone() && currentTarget.isFighter()))
&& !(ASSAULT && (currentTarget.isDrone() || currentTarget.isFighter()))
){
theTarget=currentTarget;
} else {
//or cursor target if there isn't one
List<ShipAPI> mouseTargets = CombatUtils.getShipsWithinRange(source.getMouseTarget(), 100f);
if (!mouseTargets.isEmpty()) {
Collections.sort(mouseTargets, new CollectionUtils.SortEntitiesByDistance(source.getMouseTarget()));
for (ShipAPI tmp : mouseTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& !(ANTI_FIGHTER && !(tmp.isDrone() && tmp.isFighter()))
&& !(ASSAULT && (tmp.isDrone() || tmp.isFighter() || tmp.isFrigate()))
) {
theTarget=tmp;
break;
}
}
}
}
}
//still no valid target? lets try the closest one
//most of the time a ship will have a target so that doesn't need to be perfect.
if(theTarget==null){
List<ShipAPI> closeTargets = AIUtils.getNearbyEnemies(missile, MAX_SEARCH_RANGE);
if (!closeTargets.isEmpty()) {
Collections.sort(closeTargets, new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
if (ASSAULT){ //assault missiles will somewhat prioritize toward bigger threats even if there is a closer small one, and avoid fighters and drones.
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
) {
if (tmp.isCapital() || tmp.isCruiser()){
theTarget=tmp;
break;
} else if (tmp.isDestroyer() && Math.random()>0.5){
theTarget=tmp;
break;
} else if (tmp.isDestroyer() && Math.random()>0.75){
theTarget=tmp;
break;
} else if (!tmp.isDrone() && !tmp.isFighter() && Math.random()>0.95){
theTarget=tmp;
break;
}
}
}
}else if(ANTI_FIGHTER){ //anti-fighter missile will target the closest drone or fighter
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& (tmp.isDrone() || tmp.isFighter())
) {
theTarget=tmp;
break;
}
}
}else{ //non assault, non anti-fighter missile target the closest non-drone ship
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& !tmp.isDrone()
) {
theTarget=tmp;
break;
}
}
}
}
}
}
return theTarget;
}
//Random picker for fighters and drones
public ShipAPI getRandomFighterTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> PRIORITYLIST = new HashMap<>();
Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
int i=1, u=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
) {
if (tmp.isFighter() || tmp.isDrone()){
PRIORITYLIST.put(i, tmp);
i++;
} else {
OTHERSLIST.put(u, tmp);
u++;
}
}
}
if (!PRIORITYLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=PRIORITYLIST.get(chooser);
} else if (!OTHERSLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
select=OTHERSLIST.get(chooser);
}
}
return select;
}
//Random target selection strongly weighted toward bigger threats in range
public ShipAPI getRandomLargeTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> PRIORITY1 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY2 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY3 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY4 = new HashMap<>();
Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
int i=1, u=1, v=1, x=1, y=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
&& !tmp.isDrone()
) {
if (tmp.isCapital()){
PRIORITY1.put(i, tmp);
i++;
PRIORITY2.put(u, tmp);
u++;
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isCruiser()){
PRIORITY2.put(u, tmp);
u++;
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isDestroyer()){
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isFrigate()){
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else {
OTHERSLIST.put(y, tmp);
y++;
}
}
}
if (!PRIORITY1.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=PRIORITY1.get(chooser);
} else if (!PRIORITY2.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
select=PRIORITY2.get(chooser);
} else if (!PRIORITY3.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(x-1)+0.5f);
select=PRIORITY3.get(chooser);
} else if (!PRIORITY4.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(v-1)+0.5f);
select=PRIORITY4.get(chooser);
} else if (!OTHERSLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(y-1)+0.5f);
select=OTHERSLIST.get(chooser);
}
}
return select;
}
//Pure random target picker
public ShipAPI getAnyTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> TARGETLIST = new HashMap<>();
int i=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
&& !tmp.isDrone()
){
TARGETLIST.put(i, tmp);
i++;
}
}
if (!TARGETLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=TARGETLIST.get(chooser);
}
}
return select;
}
@Override
public CombatEntityAPI getTarget() {
return target;
}
@Override
public void setTarget(CombatEntityAPI target) {
this.target = target;
}
public void init(CombatEngineAPI engine) {}
}
HULLMOD CONFLICT,IncompatibleHullmodWarning,,TRUE,0,0,0,0,data.hullmods.IncompatibleHullmodWarning,"%s, the hullmod you tried to install conflicts with this hull or another previously installed hullmod.",graphics/icons/intel/investigation.png
package data.hullmods;
import com.fs.starfarer.api.combat.ShipAPI;
public class IncompatibleHullmodWarning extends BaseHullMod {
@Override
public String getDescriptionParam(int index, ShipAPI.HullSize hullSize) {
if (index == 0) return "WARNING";
return null;
}
}
package data.hullmods;
import com.fs.starfarer.api.combat.ShipAPI;
import java.util.HashSet;
import java.util.Set;
public class YourIncompatibleHullMod extends BaseHullMod {
private static final Set<String> BLOCKED_HULLMODS = new HashSet<>();
static
{
// These hullmods will automatically be removed
BLOCKED_HULLMODS.add("augmentedengines");
BLOCKED_HULLMODS.add("auxiliarythrusters");
BLOCKED_HULLMODS.add("unstable_injector");
BLOCKED_HULLMODS.add("drive_shunt");
}
private float check=0;
private String ERROR="IncompatibleHullmodWarning";
@Override
public void applyEffectsAfterShipCreation(ShipAPI ship, String id){
if (check>0) {
check-=1;
if (check<1){
ship.getVariant().removeMod(ERROR);
}
}
for (String tmp : BLOCKED_HULLMODS) {
if (ship.getVariant().getHullMods().contains(tmp)) {
ship.getVariant().removeMod(tmp);
ship.getVariant().addMod(ERROR);
check=3;
}
}
}
}
// by Deathfly and Tartiflette
package data.scripts.util;
import com.fs.starfarer.api.combat.CollisionClass;
import com.fs.starfarer.api.combat.ShieldAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import java.awt.geom.Line2D;
import org.lazywizard.lazylib.CollisionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lwjgl.util.vector.Vector2f;
public class CollisionUtilsEX {
/**
* return the collision point of segment segStart to segEnd and a ship (will
* consider shield).
*
* @return the collision point of segment segStart to segEnd and a ship
* (will consider shield). if segment can not hit the ship, will return
* null.
* @param segStart if segStart hit the ship hull or shield, will return
* segStart.
* @param accurateShieldEdgeTest use an additional test to check if the
* segment hit the shield on edge. Set to false if you want a vanilla like
* behaviour.
*/
public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean accurateShieldEdgeTest) {
// if target can not be hit, return null
if (ship.getCollisionClass() == CollisionClass.NONE) {
return null;
}
ShieldAPI shield = ship.getShield();
// Check hit point when shield is off.
if (shield == null || shield.isOff()) {
return CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
} // If ship's shield is on, thing goes complicated...
else {
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircle(segStart, segEnd, circleCenter, circleRadius);
if (tmp1 != null) {
// OK! hit the shield in face
if (shield.isWithinArc(tmp1)) {
return tmp1;
} else {
boolean hit = false;
Vector2f tmp = new Vector2f(segEnd);
//the beam cannot go farther than it's max range or the hull
Vector2f hullHit = CollisionUtils.getCollisionPoint(segStart, segEnd, ship);
if (hullHit != null) {
tmp = hullHit;
hit = true;
}
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
if (accurateShieldEdgeTest) {
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
if (tmp2 != null) {
tmp = tmp2;
hit = true;
}
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
if (tmp3 != null) {
tmp = tmp3;
hit = true;
}
}
// return null if segment not hit anything.
return hit ? tmp : null;
}
}
}
return null;
}
// Just a compatible method.
/**
* return the collision point of segment segStart to segEnd and a ship (will
* consider shield).
*
* @return the collision point of segment segStart to segEnd and a ship
* (will consider shield). if segment can not hit the ship, will return
* null.
* @param segStart if segStart hit the ship hull or shield, will return
* segStart.
*/
public static Vector2f getShipCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship) {
return getShipCollisionPoint(segStart, segEnd, ship, false);
}
/**
* return the first intersection point of the segment segStart to segEnd and
* the circle.
*
* @return the first intersection point of segment segStart to segEnd and
* circumference. if segStart is outside the circle and segment can not
* intersection with the circumference, will return null.
* @param segStart if segStart is inside the circle, will return segStart.
*/
public static Vector2f getCollisionPointOnCircle(Vector2f segStart, Vector2f segEnd, Vector2f circleCenter, float circleRadius) {
Vector2f startToEnd = Vector2f.sub(segEnd, segStart, null);
Vector2f startToCenter = Vector2f.sub(circleCenter, segStart, null);
double ptLineDistSq = (float) Line2D.ptLineDistSq(segStart.x, segStart.y, segEnd.x, segEnd.y, circleCenter.x, circleCenter.y);
float circleRadiusSq = circleRadius * circleRadius;
// if lineStart is within the circle, return it directly
if (startToCenter.lengthSquared() < circleRadiusSq) {
return segStart;
}
// if lineStart is outside the circle and segment can not reach the circumference, return null
if (ptLineDistSq > circleRadiusSq || startToCenter.length() - circleRadius > startToEnd.length()) {
return null;
}
// calculate the intersection point.
startToEnd.normalise(startToEnd);
double dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptLineDistSq);
startToEnd.scale((float) dist);
return Vector2f.add(segStart, startToEnd, null);
}
/**
* return the first intersection point of the segment segStart to segEnd and
* the circumference.
*
* @return the first intersection point of segment segStart to segEnd and
* circumference. if segment can not intersection with the circumference,
* will return null.
*/
public static Vector2f getCollisionPointOnCircumference(Vector2f segStart, Vector2f segEnd, Vector2f circleCenter, float circleRadius) {
Vector2f startToEnd = Vector2f.sub(segEnd, segStart, null);
Vector2f startToCenter = Vector2f.sub(circleCenter, segStart, null);
double ptLineDistSq = (float) Line2D.ptLineDistSq(segStart.x, segStart.y, segEnd.x, segEnd.y, circleCenter.x, circleCenter.y);
float circleRadiusSq = circleRadius * circleRadius;
boolean CoS = false;
if (startToCenter.lengthSquared() < circleRadiusSq) {
CoS = true;
}
// if lineStart is outside the circle and segment can not reach the circumference, return null
if (ptLineDistSq > circleRadiusSq || startToCenter.length() - circleRadius > startToEnd.length()) {
return null;
}
// calculate the intersection point.
startToEnd.normalise(startToEnd);
double dist;
if (CoS) {
dist = Vector2f.dot(startToCenter, startToEnd) + Math.sqrt(circleRadiusSq - ptLineDistSq);
if (dist < startToEnd.length()) {
return null;
}
} else {
dist = Vector2f.dot(startToCenter, startToEnd) - Math.sqrt(circleRadiusSq - ptLineDistSq);
}
startToEnd.scale((float) dist);
return Vector2f.add(segStart, startToEnd, null);
}
/**
* SHOULD ONLY BE USED WHEN YOU ONLY NEED CHECK FOR SHIELD COLLISION POINT!
* if you need the check for a ship hit (considering it's shield), use
* getShipCollisionPoint instead.
*
* @return the collision point of segment segStart to segEnd and ship's
* shield. if the segment can not hit the shield or the ship has no shield,
* return null.
* @param ignoreHull if ignoreHull = flase and the segment hit the ship's
* hull first, return null.
* @param segStart if segStart hit the shield, will return segStart.
* @param accurateShieldEdgeTest use an additional test to check if the
* segment hit the shield on edge. Set to false if you want a vanilla like
* behaviour.
*/
public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull, boolean accurateShieldEdgeTest) {
// if target not shielded, return null
ShieldAPI shield = ship.getShield();
if (ship.getCollisionClass() == CollisionClass.NONE || shield == null || shield.isOff()) {
return null;
}
Vector2f circleCenter = shield.getLocation();
float circleRadius = shield.getRadius();
// calculate the shield collision point
Vector2f tmp1 = getCollisionPointOnCircle(segStart, segEnd, circleCenter, circleRadius);
if (tmp1 != null) {
// OK! hit the shield in face
if (shield.isWithinArc(tmp1)) {
return tmp1;
} else {
// if the hit come outside the shield's arc but it hit the shield's "edge", find that point.
Vector2f tmp = new Vector2f(segEnd);
boolean hit = false;
if (accurateShieldEdgeTest) {
Vector2f shieldEdge1 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() + shield.getActiveArc() / 2));
Vector2f tmp2 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge1);
if (tmp2 != null) {
tmp = tmp2;
hit = true;
}
Vector2f shieldEdge2 = MathUtils.getPointOnCircumference(circleCenter, circleRadius, MathUtils.clampAngle(shield.getFacing() - shield.getActiveArc() / 2));
Vector2f tmp3 = CollisionUtils.getCollisionPoint(segStart, tmp, circleCenter, shieldEdge2);
if (tmp3 != null) {
tmp = tmp3;
hit = true;
}
}
// If we don't ignore hull hit, check if there is one...
if (!ignoreHull && CollisionUtils.getCollisionPoint(segStart, tmp, ship) != null) {
return null;
}
// return null if do not hit shield.
return hit ? tmp : null;
}
}
return null;
}
// Just a compatible method.
/**
* SHOULD ONLY BE USED WHEN YOU ONLY NEED CHECK FOR SHIELD COLLISION POINT!
* if you need the check for a ship hit (considering it's shield), use
* getShipCollisionPoint instead.
*
* @return the collision point of segment segStart to segEnd and ship's
* shield. if the segment can not hit the shield or the ship has no shield,
* return null.
* @param ignoreHull if ignoreHull = flase and the segment hit the ship's
* hull first, return null.
* @param segStart if segStart hit the shield, will return segStart.
*/
public static Vector2f getShieldCollisionPoint(Vector2f segStart, Vector2f segEnd, ShipAPI ship, boolean ignoreHull) {
return getShieldCollisionPoint(segStart, segEnd, ship, ignoreHull, false);
}
}
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
Thanks, I knew there had to be a simpler way to do it but never bothered checking, will change the many instances where I have that piece of code. ::) Btw since you are here, I was about to ask in the modding questions if there is a way to get a script to run last within a frame? That would solve the one frame delay from the sprite render manager. I'm pretty sure there isn't but never hurt to ask.
package data.scripts.weapons;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.BeamEffectPlugin;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
public class TSC_HardFluxBeamEffect implements BeamEffectPlugin
{
public void advance(float amount, CombatEngineAPI engine, BeamAPI beam)
{
//Time per Frame
float frameTime = engine.getElapsedInLastFrame();
//Hard Flux to total Flux generation
float hFluxRatio = 0.5f;
//Gets the beam's target
CombatEntityAPI target = beam.getDamageTarget();
//Checks if a target exist, if it is a Ship, and if shields are being hit.
if (target != null && target instanceof ShipAPI && target.getShield() != null && target.getShield().isWithinArc(beam.getTo()))
{
//Gets the beam's DPS
WeaponAPI weapon = beam.getWeapon();
float dps = weapon.getDerivedStats().getDps();
//Gets the target ship's shield efficiency
ShipAPI ship = (ShipAPI)target;
float absorption = ship.getShield().getFluxPerPointOfDamage();
//Modifies the target's flux stats removing part of the beam's soft flux replacing it with hard flux
//(damage/second)*(flux/damage)*(second/frame) = (flux/frame)
ship.getFluxTracker().decreaseFlux((dps*absorption*frameTime*(hFluxRatio)));
ship.getFluxTracker().increaseFlux((dps*absorption*frameTime*(hFluxRatio)),true);
}
}
}
//What this does: scales Beam and Energy damage w/ range, so that closer ranges mean more damage for these weapon types.
package data.scripts.plugins;
import com.fs.starfarer.api.GameState;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BeamAPI;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.DamageAPI;
import com.fs.starfarer.api.combat.DamageType;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.combat.listeners.DamageDealtModifier;
import com.fs.starfarer.api.input.InputEventAPI;
import org.lazywizard.lazylib.MathUtils;
import java.util.List;
import org.lwjgl.util.vector.Vector2f;
public class BeamDamageScalar implements EveryFrameCombatPlugin {
CombatEngineAPI engine;
public static float CurrentAmount;
@Override
public void init(CombatEngineAPI cEngine) {
engine = cEngine;
}
public static float beamDamageAmount(BeamAPI beam, WeaponAPI weapon, float dps){
if(weapon == null) return dps;
if(weapon.getShip() == null) return dps;
//Fixes Beams that have multiple barrels doing absurd damage here (looking at you, Guardian).
float damMul = Math.max((float) weapon.getSpec().getTurretFireOffsets().size(),1f);
damMul *= weapon.getShip().getMutableStats().getBeamWeaponDamageMult().getMult();
//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 3X damage (but easily adjusted).
float theRangeMult = beamDistMult(beam, weapon);
return (dps * theRangeMult) / damMul;
}
public static float beamDistMult(BeamAPI beam, WeaponAPI weapon){
if(weapon == null) return 1f;
float maxRange = weapon.getRange();
maxRange *= maxRange;
//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 4X damage (but easily adjusted).
return Math.min(Math.max((maxRange / MathUtils.getDistanceSquared(beam.getRayEndPrevFrame(),beam.getFrom())),1f),3f);
}
//Gets energy-weapon damage bonus.
public static float energyDamDistMult(WeaponAPI weapon, Vector2f point){
if(weapon == null) return 1f;
float maxRange = weapon.getRange();
maxRange *= maxRange;
//The range is now used to give additional damage output, using how close we are to the target.
//Anything less than 1/3 range is at maximum damage, here at 4X damage (but easily adjusted).
return Math.min(Math.max((maxRange / MathUtils.getDistanceSquared(point,weapon.getLocation())),1f),1.5f);
}
public static float damageAfterFormShield(ShipAPI ship, DamageAPI damage, float finalDamage){
float mulDamage = finalDamage;
switch(damage.getType()){
case KINETIC:
mulDamage *= 2f;
break;
case HIGH_EXPLOSIVE:
mulDamage *= 0.5f;
break;
case FRAGMENTATION:
//Note: this is actually a BUFF, since normal Frag damage vs. shields is 0.25!
//0.5 (50% damage) got everything murdered by Mining Lasers, lol!
mulDamage *= 0.3f;
break;
default:
break;
}
mulDamage *= 0.5f * captainSkill(ship);
return mulDamage;
}
public static float captainSkill(ShipAPI ship){
return ship.getMutableStats().getShieldDamageTakenMult().getMult();
}
@Override
public void advance(float amount, List events){
if(engine == null || engine.isPaused() || Global.getCurrentState() != GameState.COMBAT) return;
CurrentAmount = amount;
for(ShipAPI ship : engine.getShips()){
if(ship.hasListenerOfClass(HardFluxBeamMod.class) == false){
ship.addListener(new HardFluxBeamMod(ship));
}
}
for(BeamAPI beam : engine.getBeams()){
if(beam.didDamageThisFrame()){
if(beam.getDamageTarget() instanceof ShipAPI){
ShipAPI target = (ShipAPI) beam.getDamageTarget();
if(target.getShield() == null) continue;
if(target.getShield().isOn()){
if(target.getVariant().hasHullMod("shields_formshield")){
float baseDamage = beam.getDamage().computeDamageDealt(amount);
baseDamage = beamDamageAmount(beam,beam.getWeapon(),baseDamage);
baseDamage = damageAfterFormShield(target,beam.getDamage(),baseDamage);
target.getFluxTracker().increaseFlux(baseDamage, true);
FormShieldPlugin.damagedMap.add(target);
}
}
}
}
}
}
@Override
public void renderInWorldCoords(ViewportAPI vapi) {
}
@Override
public void renderInUICoords(ViewportAPI vapi) {
}
@Override
public void processInputPreCoreControls(float f, List<InputEventAPI> list) {
}
public static class HardFluxBeamMod implements DamageDealtModifier {
protected ShipAPI ship;
public HardFluxBeamMod(ShipAPI ship) {
this.ship = ship;
}
public String modifyDamageDealt(Object param,
CombatEntityAPI target, DamageAPI damage,
Vector2f point, boolean shieldHit) {
if(param == null || target == null || damage == null) return null;
if(target instanceof ShipAPI == false) return null;
ShipAPI sTarg = (ShipAPI) target;
WeaponAPI weapon = null;
boolean hasFormShield = false;
if(sTarg.getShield() != null){
if(sTarg.getShield().isOn()){
if(sTarg.getVariant().hasHullMod("shields_formshield")){
hasFormShield = true;
}
}
}
if(param instanceof BeamAPI){
BeamAPI beam = (BeamAPI) param;
weapon = beam.getWeapon();
if(hasFormShield){
damage.getModifier().modifyPercent("hardfluxbeam", 0f);
return null;
}
if(beam == null || weapon == null) return null;
float damageMul = beamDistMult(beam,weapon);
damage.getModifier().modifyPercent("hardfluxbeam", (damageMul) * 100f);
damage.setForceHardFlux(true);
} else if(param instanceof DamagingProjectileAPI){
DamagingProjectileAPI proj = (DamagingProjectileAPI) param;
weapon = proj.getWeapon();
boolean handleNullDamage = proj == null || weapon == null;
float finalDamage = proj.getDamageAmount();
float energyDamageBuff = 1f;
//Ignores EZ-Damage weapons, because you get into recursive loops, lol.
boolean ez_exception = handleNullDamage || proj.getProjectileSpecId().equals("ez_damage_shot");
if(proj.getDamageType().equals(DamageType.ENERGY) && !ez_exception && !handleNullDamage){
energyDamageBuff = energyDamDistMult(weapon,point);
damage.getModifier().modifyPercent("hardfluxbeam", (energyDamageBuff) * 100f);
}
if(hasFormShield){
finalDamage = damageAfterFormShield(sTarg, damage, finalDamage * energyDamageBuff);
sTarg.getFluxTracker().increaseFlux(finalDamage, true);
FormShieldPlugin.damagedMap.add(sTarg);
damage.setDamage(0f);
}
//Projectile explodes and is removed.
AAA_Weapon_FX_Plugin.explodingProjectiles.add(proj);
} else {
if(hasFormShield){
sTarg.getFluxTracker().increaseFlux(damage.getDamage()*0.5f*captainSkill(sTarg), true);
damage.getModifier().modifyPercent("hardfluxbeam", 0f);
return null;
}
}
return null;
}
}
}
}
Yet another missile AI, this time with all the knobs and switches you need to customize it anyway you want. No Java skill needed, but it require to be compiled.
Customizable features:Tested on a hundred missiles at once with no noticeable performance impact.
- ECCM dependent target leading.
- Different target selection priorities.
- Option to reengage a new target when the current one got killed.
- Controllable vanilla-like waving.
- Oversteering behavior that tries to orient the velocity toward the leading point instead of just pointing the missile at it.
SpoilerCode: Java//By Tartiflette, highly customizable Missile AI.
package data.scripts.ai;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.CombatEngineAPI;
import com.fs.starfarer.api.combat.CombatEntityAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.combat.MissileAIPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ShipCommand;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.CollectionUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.combat.AIUtils;
import org.lazywizard.lazylib.combat.CombatUtils;
import org.lwjgl.util.vector.Vector2f;
public class CustomMissileAI implements MissileAIPlugin, GuidedMissileAI {
//////////////////////
// SETTINGS //
//////////////////////
//Angle with the target beyond which the missile turn around without accelerating. Avoid endless circling.
// Set to a negative value to disable
private final float OVERSHOT_ANGLE=60;
//Time to complete a wave in seconds.
private final float WAVE_TIME=2;
//Angle of the waving in degree (divided by 3 with ECCM). Set to a negative value to avoid all waving.
private final float WAVE_AMPLITUDE=15;
//Damping of the turn speed when closing on the desired aim. The smaller the snappier.
private final float DAMPING=0.1f;
//Does the missile try to correct it's velocity vector as fast as possible or just point to the desired direction and drift a bit?
// Can create strange results with large waving
// Require a projectile with a decent turn rate and around twice that in turn acceleration compared to their top speed
// Useful for slow torpedoes with low forward acceleration, or ultra precise anti-fighter missiles.
private final boolean OVERSTEER=false; //REQUIRE NO OVERSHOOT ANGLE!
//Does the missile switch its target if it has been destroyed?
private final boolean TARGET_SWITCH=true;
//Does the missile find a random target or aways tries to hit the ship's one?
// 0: No random target seeking,
// If the launching ship has a valid target, the missile will pursue that one.
// If there is no target selected, it will check for an unselected cursor target.
// If there is none, it will pursue its closest valid threat.
// 1: Local random target,
// If the ship has a target selected, the missile will pick a random valid threat around that one.
// If the ship has none, the missile will pursue a random valid threat around the cursor, or itself in AI control.
// 2: Full random,
// The missile will always seek a random valid threat around itself.
private final Integer RANDOM_TARGET=0;
//Both targeting behavior can be false for a missile that always get the ship's target or the closest one
//Prioritize hitting fighters and drones (if false, the missile will still be able to target fighters but not drones)
private final boolean ANTI_FIGHTER=false; //INCOMPATIBLE WITH ASSAULT
//Target the biggest threats first
private final boolean ASSAULT=true; //INCOMPATIBLE WITH ANTI-FIGHTER
//range in which the missile seek a target in game units.
private final float MAX_SEARCH_RANGE = 1500;
//range under which the missile start to get progressively more precise in game units.
private float PRECISION_RANGE=500;
//Is the missile lead the target or tailchase it?
private final boolean LEADING=true;
//Leading loss without ECCM hullmod. The higher, the less accurate the leading calculation will be.
// 1: perfect leading with and without ECCM
// 2: half precision without ECCM
// 3: a third as precise without ECCM. Default
// 4, 5, 6 etc : 1/4th, 1/5th, 1/6th etc precision.
private float ECCM=3; //A VALUE BELOW 1 WILL PREVENT THE MISSILE FROM EVER HITTING ITS TARGET!
//////////////////////
// VARIABLES //
//////////////////////
//max speed of the missile after modifiers.
private final float MAX_SPEED;
//Max range of the missile after modifiers.
private final float MAX_RANGE;
//Random starting offset for the waving.
private final float OFFSET;
private CombatEngineAPI engine;
private final MissileAPI MISSILE;
private CombatEntityAPI target;
private Vector2f lead = new Vector2f();
private boolean launch=true;
private float timer=0, check=0f;
//////////////////////
// DATA COLLECTING //
//////////////////////
public CustomMissileAI(MissileAPI missile, ShipAPI launchingShip) {
this.MISSILE = missile;
MAX_SPEED = missile.getMaxSpeed();
MAX_RANGE = missile.getWeapon().getRange();
if (missile.getSource().getVariant().getHullMods().contains("eccm")){
ECCM=1;
}
//calculate the precision range factor
PRECISION_RANGE=(float)Math.pow((2*PRECISION_RANGE),2);
OFFSET=(float)(Math.random()*Math.PI*2);
}
//////////////////////
// MAIN AI LOOP //
//////////////////////
@Override
public void advance(float amount) {
if (engine != Global.getCombatEngine()) {
this.engine = Global.getCombatEngine();
}
//skip the AI if the game is paused, the missile is engineless or fading
if (Global.getCombatEngine().isPaused() || MISSILE.isFading() || MISSILE.isFizzling()) {return;}
//assigning a target if there is none or it got destroyed
if (target == null
|| target.getOwner()==MISSILE.getOwner()
|| (TARGET_SWITCH && (target instanceof ShipAPI && ((ShipAPI) target).isHulk())
|| !engine.isEntityInPlay(target)
)
){
setTarget(assignTarget(MISSILE));
//forced acceleration by default
MISSILE.giveCommand(ShipCommand.ACCELERATE);
return;
}
timer+=amount;
//finding lead point to aim to
if(launch || timer>=check){
launch=false;
timer -=check;
//set the next check time
check = Math.min(
0.25f,
Math.max(
0.03f,
MathUtils.getDistanceSquared(MISSILE, target)/PRECISION_RANGE)
);
if(LEADING){
//best intercepting point
lead = AIUtils.getBestInterceptPoint(
MISSILE.getLocation(),
MAX_SPEED*ECCM, //if eccm is intalled the point is accurate, otherwise it's placed closer to the target (almost tailchasing)
target.getLocation(),
target.getVelocity()
);
//null pointer protection
if (lead == null) {
lead = target.getLocation();
}
} else {
lead = target.getLocation();
}
}
//best velocity vector angle for interception
float correctAngle = VectorUtils.getAngle(
MISSILE.getLocation(),
lead
);
if (OVERSTEER){
//velocity angle correction
float offCourseAngle = MathUtils.getShortestRotation(
VectorUtils.getFacing(MISSILE.getVelocity()),
correctAngle
);
float correction = MathUtils.getShortestRotation(
correctAngle,
VectorUtils.getFacing(MISSILE.getVelocity())+180
)
* 0.5f * //oversteer
(float)((Math.sin(Math.PI/90*(Math.min(Math.abs(offCourseAngle),45))))); //damping when the correction isn't important
//modified optimal facing to correct the velocity vector angle as soon as possible
correctAngle = correctAngle+correction;
}
if(WAVE_AMPLITUDE>0){
//waving
float multiplier=1;
if(ECCM<=1){
multiplier=0.3f;
}
correctAngle+=multiplier*WAVE_AMPLITUDE*check*Math.cos(OFFSET+MISSILE.getElapsed()*(2*Math.PI/WAVE_TIME));
}
//target angle for interception
float aimAngle = MathUtils.getShortestRotation( MISSILE.getFacing(), correctAngle);
if(OVERSHOT_ANGLE<=0 || Math.abs(aimAngle)<OVERSHOT_ANGLE){
MISSILE.giveCommand(ShipCommand.ACCELERATE);
}
if (aimAngle < 0) {
MISSILE.giveCommand(ShipCommand.TURN_RIGHT);
} else {
MISSILE.giveCommand(ShipCommand.TURN_LEFT);
}
// Damp angular velocity if the missile aim is getting close to the targeted angle
if (Math.abs(aimAngle) < Math.abs(MISSILE.getAngularVelocity()) * DAMPING) {
MISSILE.setAngularVelocity(aimAngle / DAMPING);
}
}
//////////////////////
// TARGETING //
//////////////////////
public CombatEntityAPI assignTarget(MissileAPI missile){
ShipAPI theTarget=null;
ShipAPI source = missile.getSource();
ShipAPI currentTarget;
//check for a target from its source
if(source != null
&& source.getShipTarget() != null
&& source.getShipTarget() instanceof ShipAPI
&& source.getShipTarget().getOwner() != missile.getOwner()
){
currentTarget=source.getShipTarget();
} else {
currentTarget=null;
}
//random target selection
if (RANDOM_TARGET>0){
//random mode 1: the missile will look for a target around itsef
Vector2f location = missile.getLocation();
//random mode 2: if its source has a target selected, it will look for random one around that point
if( RANDOM_TARGET<2){
if(currentTarget != null
&& currentTarget.isAlive()
&& MathUtils.isWithinRange(missile, currentTarget, MAX_RANGE)
){
location = currentTarget.getLocation();
} else if (source != null
&& source.getMouseTarget()!=null){
location=source.getMouseTarget();
}
}
//fetch the right kind of target
if(ANTI_FIGHTER){
theTarget = getRandomFighterTarget(location);
} else if(ASSAULT){
theTarget = getRandomLargeTarget(location);
} else {
theTarget = getAnyTarget(location);
}
//non random targeting
} else {
if(source!=null){
//ship target first
if(currentTarget!=null
&& currentTarget.isAlive()
&& currentTarget.getOwner()!=missile.getOwner()
&& !(ANTI_FIGHTER && !(currentTarget.isDrone() && currentTarget.isFighter()))
&& !(ASSAULT && (currentTarget.isDrone() || currentTarget.isFighter()))
){
theTarget=currentTarget;
} else {
//or cursor target if there isn't one
List<ShipAPI> mouseTargets = CombatUtils.getShipsWithinRange(source.getMouseTarget(), 100f);
if (!mouseTargets.isEmpty()) {
Collections.sort(mouseTargets, new CollectionUtils.SortEntitiesByDistance(source.getMouseTarget()));
for (ShipAPI tmp : mouseTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& !(ANTI_FIGHTER && !(tmp.isDrone() && tmp.isFighter()))
&& !(ASSAULT && (tmp.isDrone() || tmp.isFighter() || tmp.isFrigate()))
) {
theTarget=tmp;
break;
}
}
}
}
}
//still no valid target? lets try the closest one
//most of the time a ship will have a target so that doesn't need to be perfect.
if(theTarget==null){
List<ShipAPI> closeTargets = AIUtils.getNearbyEnemies(missile, MAX_SEARCH_RANGE);
if (!closeTargets.isEmpty()) {
Collections.sort(closeTargets, new CollectionUtils.SortEntitiesByDistance(missile.getLocation()));
if (ASSAULT){ //assault missiles will somewhat prioritize toward bigger threats even if there is a closer small one, and avoid fighters and drones.
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
) {
if (tmp.isCapital() || tmp.isCruiser()){
theTarget=tmp;
break;
} else if (tmp.isDestroyer() && Math.random()>0.5){
theTarget=tmp;
break;
} else if (tmp.isDestroyer() && Math.random()>0.75){
theTarget=tmp;
break;
} else if (!tmp.isDrone() && !tmp.isFighter() && Math.random()>0.95){
theTarget=tmp;
break;
}
}
}
}else if(ANTI_FIGHTER){ //anti-fighter missile will target the closest drone or fighter
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& (tmp.isDrone() || tmp.isFighter())
) {
theTarget=tmp;
break;
}
}
}else{ //non assault, non anti-fighter missile target the closest non-drone ship
for (ShipAPI tmp : closeTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != missile.getOwner()
&& !tmp.isDrone()
) {
theTarget=tmp;
break;
}
}
}
}
}
}
return theTarget;
}
//Random picker for fighters and drones
public ShipAPI getRandomFighterTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> PRIORITYLIST = new HashMap<>();
Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
int i=1, u=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
) {
if (tmp.isFighter() || tmp.isDrone()){
PRIORITYLIST.put(i, tmp);
i++;
} else {
OTHERSLIST.put(u, tmp);
u++;
}
}
}
if (!PRIORITYLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=PRIORITYLIST.get(chooser);
} else if (!OTHERSLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
select=OTHERSLIST.get(chooser);
}
}
return select;
}
//Random target selection strongly weighted toward bigger threats in range
public ShipAPI getRandomLargeTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> PRIORITY1 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY2 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY3 = new HashMap<>();
Map<Integer, ShipAPI> PRIORITY4 = new HashMap<>();
Map<Integer, ShipAPI> OTHERSLIST = new HashMap<>();
int i=1, u=1, v=1, x=1, y=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
&& !tmp.isDrone()
) {
if (tmp.isCapital()){
PRIORITY1.put(i, tmp);
i++;
PRIORITY2.put(u, tmp);
u++;
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isCruiser()){
PRIORITY2.put(u, tmp);
u++;
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isDestroyer()){
PRIORITY3.put(x, tmp);
x++;
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else if (tmp.isFrigate()){
PRIORITY4.put(v, tmp);
v++;
OTHERSLIST.put(y, tmp);
y++;
} else {
OTHERSLIST.put(y, tmp);
y++;
}
}
}
if (!PRIORITY1.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=PRIORITY1.get(chooser);
} else if (!PRIORITY2.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(u-1)+0.5f);
select=PRIORITY2.get(chooser);
} else if (!PRIORITY3.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(x-1)+0.5f);
select=PRIORITY3.get(chooser);
} else if (!PRIORITY4.isEmpty() && Math.random()>0.8f){
int chooser=Math.round((float)Math.random()*(v-1)+0.5f);
select=PRIORITY4.get(chooser);
} else if (!OTHERSLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(y-1)+0.5f);
select=OTHERSLIST.get(chooser);
}
}
return select;
}
//Pure random target picker
public ShipAPI getAnyTarget(Vector2f location){
ShipAPI select=null;
Map<Integer, ShipAPI> TARGETLIST = new HashMap<>();
int i=1;
List<ShipAPI> potentialTargets = CombatUtils.getShipsWithinRange(location, MAX_RANGE);
if (!potentialTargets.isEmpty()) {
for (ShipAPI tmp : potentialTargets) {
if (tmp.isAlive()
&& tmp.getOwner() != MISSILE.getOwner()
&& !tmp.isDrone()
){
TARGETLIST.put(i, tmp);
i++;
}
}
if (!TARGETLIST.isEmpty()){
int chooser=Math.round((float)Math.random()*(i-1)+0.5f);
select=TARGETLIST.get(chooser);
}
}
return select;
}
@Override
public CombatEntityAPI getTarget() {
return target;
}
@Override
public void setTarget(CombatEntityAPI target) {
this.target = target;
}
public void init(CombatEngineAPI engine) {}
}[close]