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