Here's the code from the current release of FX mod, for comparison's sake. Note all the ways I'm trying to
during Advance() here.
package data.scripts.plugins;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.BeamAPI;
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.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.MissileAPI;
import com.fs.starfarer.api.combat.ShipAPI;
import com.fs.starfarer.api.combat.ViewportAPI;
import com.fs.starfarer.api.graphics.SpriteAPI;
import data.scripts.fx_Particle;
import static data.scripts.fx_SharedLib.circularVelocity;
import static data.scripts.fx_SharedLib.getBeamColors;
import static data.scripts.fx_SharedLib.getProjectileColors;
import static data.scripts.fx_SharedLib.isWithinShieldArc;
import java.awt.Color;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.lazylib.VectorUtils;
import org.lwjgl.util.vector.Vector2f;
import org.lwjgl.opengl.GL11;
import static org.lazywizard.lazylib.MathUtils.getRandomNumberInRange;
public class FX_Plugin implements EveryFrameCombatPlugin
{
CombatEngineAPI engine;
private Vector2f centerPoint;
private float frameTime;
private final float longDist = 3000f * 3000f;
//Default Sprite
private final SpriteAPI eSprite = Global.getSettings().getSprite("fx", "fx_muzzleflash_001");
//FX Sprites
private final SpriteAPI fx_muzzleflash_001 = Global.getSettings().getSprite("fx", "fx_muzzleflash_001");
private final SpriteAPI fx_muzzleflash_002 = Global.getSettings().getSprite("fx", "fx_muzzleflash_002");
private final SpriteAPI fx_muzzleflash_003 = Global.getSettings().getSprite("fx", "fx_muzzleflash_003");
private final SpriteAPI fx_shieldflash_001 = Global.getSettings().getSprite("fx", "fx_shieldflash_001");
private final SpriteAPI fx_shieldflash_002 = Global.getSettings().getSprite("fx", "fx_shieldflash_002");
private final SpriteAPI fx_shieldflash_003 = Global.getSettings().getSprite("fx", "fx_shieldflash_003");
private final SpriteAPI fx_laserflash_001 = Global.getSettings().getSprite("fx", "fx_laserflash_001");
private final SpriteAPI fx_laserflash_002 = Global.getSettings().getSprite("fx", "fx_laserflash_002");
private final SpriteAPI fx_laserflash_003 = Global.getSettings().getSprite("fx", "fx_laserflash_003");
private final SpriteAPI fx_smoke_tendril_001 = Global.getSettings().getSprite("fx", "fx_smoke_tendril_001");
private final SpriteAPI fx_smoke_tendril_002 = Global.getSettings().getSprite("fx", "fx_smoke_tendril_002");
private final SpriteAPI fx_smoke_tendril_003 = Global.getSettings().getSprite("fx", "fx_smoke_tendril_003");
private final SpriteAPI fx_generic_pulse = Global.getSettings().getSprite("fx", "fx_generic_pulse");
public static List<fx_Particle> particleList = new ArrayList<>();
public static Map projCoreColorMap = new HashMap();
public static Map projFringeColorMap = new HashMap();
//public static List<ShipAPI> engineList = new ArrayList<>();
public static HashSet<DamagingProjectileAPI> deadShotList = new HashSet<>(100);
//public static Map engineMap = new HashMap();
public static HashSet<MissileAPI> seenMissiles = new HashSet<>(100);
@Override
public void init(CombatEngineAPI engine) {
this.engine = engine;
clearMe();
}
public void clearMe(){
particleList.clear();
deadShotList.clear();
seenMissiles.clear();
}
@Override
public void advance(float amount, List events)
{
// Obvious exploits are obvious
if (engine == null){ clearMe(); return;}
if(engine.isPaused()){ frameTime = 0f; return;}
if(engine.isInFastTimeAdvance() || engine.isUIShowingDialog()){ clearMe(); return;}
frameTime = amount;
if(engine.getTimeMult().getModifiedValue() != 1f){
frameTime /= engine.getTimeMult().getModifiedValue();
}
// get the missiles in this frame
HashSet<MissileAPI> activeMissiles = new HashSet<>();
activeMissiles.addAll(engine.getMissiles());
// the difference between the missiles in the last frame to this frame are those that *could* have done damage
//seenMissiles.removeAll(activeMissiles);
for(MissileAPI missile : seenMissiles){
if(deadShotList.contains(missile)) continue;
if(missile.didDamage()){
int damageType;
switch (missile.getDamageType()) {
case ENERGY:
damageType = 0;
break;
case HIGH_EXPLOSIVE:
damageType = 1;
break;
case KINETIC:
damageType = 2;
break;
case FRAGMENTATION:
damageType = 3;
break;
default:
damageType = 0;
break;
}
int power = Math.min(200,Math.max(1,Math.round(missile.getWeapon().getDerivedStats().getDamagePerShot() / 5)));
doProjectileExplosion(missile,null,power,power > 3,damageType);
deadShotList.add(missile);
}
}
// set our missiles for the next frame
seenMissiles = activeMissiles;
for(BeamAPI beam : engine.getBeams()){
//Generates a bit of "spark" when the Beam strikes something.
if(beam.didDamageThisFrame() && beam.getBrightness() > 0.99f){
float angle = VectorUtils.getAngle(beam.getFrom(), beam.getTo());
int power = Math.min(5,Math.max(1,Math.round(beam.getWeapon().getDerivedStats().getDps() / 75)));
boolean didEffect = false;
CombatEntityAPI target = beam.getDamageTarget();
if(target instanceof ShipAPI){
ShipAPI tShip = (ShipAPI) target;
if(tShip.getShield() != null){
if(tShip.getShield().isOn() && isWithinShieldArc(tShip,beam.getTo())){
didEffect = true;
if(beam.getWeapon() == null) continue;
if(beam.getWeapon().getShip() == null) continue;
if(beam.getWeapon().isBurstBeam() || getRandomNumberInRange(0f,10f) > 9f){
power = Math.min(400,Math.max(1,Math.round(beam.getWeapon().getDerivedStats().getDamagePerShot())));
float newAngle = VectorUtils.getAngle(tShip.getShield().getLocation(),beam.getTo()) + getRandomNumberInRange(-45f, 45f);
float vel = getRandomNumberInRange(0, 3f);
float size = getRandomNumberInRange(50f + (float) power,75f + (float) power);
float growth = getRandomNumberInRange(0.995f,1.0f);
float ratio = 1.0f;
float lifeTime = getRandomNumberInRange(0.1f,0.15f);
float fadeTime = getRandomNumberInRange(0.1f,0.15f);
Color projColor;
Color projColorFade;
if(!projCoreColorMap.containsKey(beam.getWeapon().getId())){
Color[] bColors = getBeamColors(beam);
projColor = bColors[0];
projColorFade = bColors[1];
} else {
projColor = (Color) projCoreColorMap.get(beam.getWeapon().getId());
projColorFade = (Color) projFringeColorMap.get(beam.getWeapon().getId());
}
int muzzleflash = getRandomNumberInRange(1,3);
Vector2f velocity = circularVelocity(newAngle,vel);
Vector2f targetVel = beam.getWeapon().getShip().getVelocity();
velocity = velocity.translate(targetVel.getX(), targetVel.getY());
newParticle(beam.getTo(),velocity, size, ratio, newAngle, true, true, "fx_shieldflash_00"+muzzleflash,projColor,projColorFade,lifeTime,fadeTime,growth);
}
}
}
}
if(target instanceof MissileAPI || target instanceof CombatAsteroidAPI || !didEffect){
for(int i = 0; i < power; i++){
if(getRandomNumberInRange(0f,10f) > 5f){
float newAngle = MathUtils.clampAngle(angle -180f + MathUtils.getRandomNumberInRange(-20f, 20f));
float vel = getRandomNumberInRange(200f + (power * 100f), 400f + (power * 100f));
float size = getRandomNumberInRange(10f + (float) power * 3f,20f + (float) power * 3f);
float growth = getRandomNumberInRange(0.995f,1.0f);
float ratio = 1.0f;
float lifeTime = getRandomNumberInRange(0.1f,0.3f);
float fadeTime = getRandomNumberInRange(0.1f,0.3f);
Vector2f velocity = circularVelocity(newAngle,vel);
Vector2f targetVel = beam.getDamageTarget().getVelocity();
velocity = velocity.translate(targetVel.getX(), targetVel.getY());
newParticle(beam.getTo(),velocity, size, ratio, newAngle, true, true, "fx_generic_pulse",new Color(255,255,0,255),new Color(128,32,0,255),lifeTime,fadeTime,growth);
}
}
}
}
//Generates a "muzzle flash" for a Beam that's active
if(beam.getBrightness() > 0.5f && getRandomNumberInRange(0f,10f) > 5f){
if(beam.getWeapon() == null) continue;
if(beam.getWeapon().getShip() == null) continue;
int power = Math.min(200,Math.max(1,Math.round(beam.getWeapon().getDerivedStats().getDamagePerShot() / 15)));
float newAngle = VectorUtils.getAngle(beam.getFrom(), beam.getTo());
float vel = getRandomNumberInRange(10, 15f);
float size = getRandomNumberInRange(100f + (float) power,125f + (float) power);
float growth = getRandomNumberInRange(0.8f,0.9f);
float ratio = 1.0f;
float lifeTime = getRandomNumberInRange(0.1f,0.15f);
float fadeTime = getRandomNumberInRange(0.1f,0.15f);
Color projColor;
Color projColorFade;
if(!projCoreColorMap.containsKey(beam.getWeapon().getId())){
Color[] bColors = getBeamColors(beam);
projColor = bColors[0];
projColorFade = bColors[1];
} else {
projColor = (Color) projCoreColorMap.get(beam.getWeapon().getId());
projColorFade = (Color) projFringeColorMap.get(beam.getWeapon().getId());
}
int muzzleflash = getRandomNumberInRange(1,3);
Vector2f velocity = circularVelocity(newAngle,vel);
Vector2f targetVel = beam.getWeapon().getShip().getVelocity();
velocity = velocity.translate(targetVel.getX(), targetVel.getY());
newParticle(beam.getFrom(),velocity, size, ratio, newAngle, true, true, "fx_laserflash_00"+muzzleflash,projColor,projColorFade,lifeTime,fadeTime,growth);
}
}
//Kills dead particles; fast boolean checker
for(int i = 0; i < particleList.size(); i++){
if(particleList.get(i).isDead() == true){
particleList.remove(i);
i--;
}
}
//Handles explosions and muzzle-flashes for projectiles.
for(DamagingProjectileAPI projectile : engine.getProjectiles()){
if(deadShotList.contains(projectile)) continue;
if(projectile instanceof MissileAPI){
if(projectile.getElapsed() < 0.01f){
projectileHandler(projectile);
}
} else {
projectileHandler(projectile);
}
}
}
public void projectileHandler(DamagingProjectileAPI projectile){
if(projectile.getWeapon() == null) return;
if(projectile.getWeapon().getShip() == null) return;
CombatEntityAPI dTarg = projectile.getDamageTarget();
//PROJECTILE EXPLOSION HANDLER
if(dTarg != null){
int damageType;
switch (projectile.getDamageType()) {
case ENERGY:
damageType = 0;
break;
case HIGH_EXPLOSIVE:
damageType = 1;
break;
case KINETIC:
damageType = 2;
break;
case FRAGMENTATION:
damageType = 3;
break;
default:
damageType = 0;
break;
}
int power = Math.min(200,Math.max(1,Math.round(projectile.getWeapon().getDerivedStats().getDamagePerShot() / 15)));
doProjectileExplosion(projectile,dTarg,power,power > 3,damageType);
deadShotList.add(projectile);
}
//MUZZLE-FLASH HANDLER
if(projectile.getElapsed() < 0.01f){
int power = Math.min(200,Math.max(1,Math.round(projectile.getWeapon().getDerivedStats().getDamagePerShot() / 15)));
float newAngle = projectile.getFacing();
float vel = getRandomNumberInRange(5, 10f);
float size = getRandomNumberInRange(50f + (float) power,100f + (float) power);
float growth = getRandomNumberInRange(0.8f,0.9f);
float ratio = 1.0f;
float lifeTime = getRandomNumberInRange(0.1f,0.3f);
float fadeTime = getRandomNumberInRange(0.1f,0.15f);
Color projColor;
Color projColorFade;
if(!projCoreColorMap.containsKey(projectile.getProjectileSpecId())){
Color[] bColors = getProjectileColors(projectile);
projColor = bColors[0];
projColorFade = bColors[1];
} else {
projColor = (Color) projCoreColorMap.get(projectile.getProjectileSpecId());
projColorFade = (Color) projFringeColorMap.get(projectile.getProjectileSpecId());
}
int muzzleflash = getRandomNumberInRange(1,3);
Vector2f velocity = circularVelocity(newAngle,vel);
Vector2f targetVel = projectile.getWeapon().getShip().getVelocity();
velocity = velocity.translate(targetVel.getX(), targetVel.getY());
newParticle(projectile.getLocation(),velocity, size, ratio, newAngle, true, true, "fx_muzzleflash_00"+muzzleflash,projColor,projColorFade,lifeTime,fadeTime,growth);
}
}
public void doProjectileExplosion(DamagingProjectileAPI proj, CombatEntityAPI targ, int power, boolean doSmoke, int damageType){
int repeats = Math.min(7,Math.max(1, power / 5));
float facing = proj.getFacing();
Vector2f targetVel;
if(targ == null){
targetVel = new Vector2f(0.0f,0.0f);
} else {
targetVel = targ.getVelocity();
}
for(int i = 0; i < repeats; i++){
float angle = facing + 180 + getRandomNumberInRange(-35f,35f);
float vel = getRandomNumberInRange(5, 10f);
float size = getRandomNumberInRange(10f + (float) power,20f + (float) power);
float growth = getRandomNumberInRange(1.02f,1.05f);
float ratio = 1.0f;
float lifeTime = getRandomNumberInRange(0.1f,0.3f);
float fadeTime = getRandomNumberInRange(0.1f,0.15f);
Color projColor;
Color projColorFade;
if(!projCoreColorMap.containsKey(proj.getProjectileSpecId())){
Color[] bColors = getProjectileColors(proj);
projColor = bColors[0];
projColorFade = bColors[1];
} else {
projColor = (Color) projCoreColorMap.get(proj.getProjectileSpecId());
projColorFade = (Color) projFringeColorMap.get(proj.getProjectileSpecId());
}
int muzzleflash = getRandomNumberInRange(1,3);
Vector2f velocity = circularVelocity(angle,vel);
velocity = new Vector2f(velocity.getX() + targetVel.getX(), velocity.getY() + targetVel.getY());
//Smoke particles. Limits on how powerful a shot needs to be to generate, to prevent certain borks and optimize
if(doSmoke && getRandomNumberInRange(0,10) > 7){
switch (damageType) {
case 0:
//Does a glow effect
newParticle(proj.getLocation(),new Vector2f(0f,0f), size * getRandomNumberInRange(0.75f,2.0f), ratio, getRandomNumberInRange(0f,359f), false, false, "fx_generic_pulse",projColor,projColorFade,lifeTime * 0.5f,fadeTime * getRandomNumberInRange(0.5f,0.85f),getRandomNumberInRange(1.005f,1.01f));
break;
case 1:
//Does a smoke-tendril effect
newParticle(proj.getLocation(),new Vector2f(0f,0f), size * getRandomNumberInRange(0.75f,2.0f), ratio, getRandomNumberInRange(0f,359f), false, false, "fx_smoke_tendril_00"+muzzleflash,new Color (255,255,255,255),new Color (255,255,255,255),lifeTime * getRandomNumberInRange(2f,5f),fadeTime * getRandomNumberInRange(5f,10f),getRandomNumberInRange(1.001f,1.005f));
break;
case 2:
//Does a spike effect
newParticle(proj.getLocation(),new Vector2f(0f,0f), size * getRandomNumberInRange(0.75f,2.0f), ratio, getRandomNumberInRange(0f,359f), false, false, "fx_smoke_tendril_00"+muzzleflash,new Color (255,255,255,255),new Color (255,255,255,255),lifeTime * getRandomNumberInRange(2f,5f),fadeTime * getRandomNumberInRange(5f,10f),getRandomNumberInRange(1.001f,1.005f));
break;
case 3:
//Does a burst effect
newParticle(proj.getLocation(),new Vector2f(0f,0f), size * getRandomNumberInRange(0.75f,2.0f), ratio, getRandomNumberInRange(0f,359f), false, false, "fx_smoke_tendril_00"+muzzleflash,new Color (255,255,255,255),new Color (255,255,255,255),lifeTime * getRandomNumberInRange(2f,5f),fadeTime * getRandomNumberInRange(5f,10f),getRandomNumberInRange(1.001f,1.005f));
break;
default:
//Does a burst effect
newParticle(proj.getLocation(),new Vector2f(0f,0f), size * getRandomNumberInRange(0.75f,2.0f), ratio, getRandomNumberInRange(0f,359f), false, false, "fx_smoke_tendril_00"+muzzleflash,new Color (255,255,255,255),new Color (255,255,255,255),lifeTime * getRandomNumberInRange(2f,5f),fadeTime * getRandomNumberInRange(5f,10f),getRandomNumberInRange(1.001f,1.005f));
break;
}
}
newParticle(proj.getLocation(),velocity, size, ratio, angle, true, true, "fx_muzzleflash_00"+muzzleflash,projColor,projColorFade,lifeTime,fadeTime,growth);
}
}
public SpriteAPI pickSprite(String spriteName){
if (spriteName.equalsIgnoreCase("fx_muzzleflash_001")){
return fx_muzzleflash_001;
} else if (spriteName.equalsIgnoreCase("fx_muzzleflash_002")) {
return fx_muzzleflash_002;
} else if (spriteName.equalsIgnoreCase("fx_muzzleflash_003")) {
return fx_muzzleflash_003;
} else if (spriteName.equalsIgnoreCase("fx_laserflash_001")) {
return fx_laserflash_001;
} else if (spriteName.equalsIgnoreCase("fx_laserflash_002")) {
return fx_laserflash_002;
} else if (spriteName.equalsIgnoreCase("fx_laserflash_003")) {
return fx_laserflash_003;
} else if (spriteName.equalsIgnoreCase("fx_shieldflash_001")) {
return fx_shieldflash_001;
} else if (spriteName.equalsIgnoreCase("fx_shieldflash_002")) {
return fx_shieldflash_002;
} else if (spriteName.equalsIgnoreCase("fx_shieldflash_003")) {
return fx_shieldflash_003;
} else if (spriteName.equalsIgnoreCase("fx_smoke_tendril_001")) {
return fx_smoke_tendril_001;
} else if (spriteName.equalsIgnoreCase("fx_smoke_tendril_002")) {
return fx_smoke_tendril_002;
} else if (spriteName.equalsIgnoreCase("fx_smoke_tendril_003")) {
return fx_smoke_tendril_003;
} else if (spriteName.equalsIgnoreCase("fx_generic_pulse")) {
return fx_generic_pulse;
} else {
return eSprite;
}
}
public void newParticle(Vector2f location, Vector2f velocity, float size, float ratio, float angle, boolean isAdditive, boolean isBlended, String spriteName, Color startColor, Color endColor, float lifeTime, float fadeTime, float growth){
//Don't build new particles if there are already too many.
if(particleList.size() > 1000) return;
//Don't build new particles outside the view-distance.
if(location == null || centerPoint == null) return;
if(MathUtils.getDistanceSquared(location, centerPoint) > longDist) return;
//Weird fix for angle weirdness
angle = angle - 90f;
Vector2f initialSize = new Vector2f(size / ratio, size * ratio);
fx_Particle data = new fx_Particle(pickSprite(spriteName), lifeTime, fadeTime, initialSize, location, velocity, angle, isAdditive, isBlended, startColor, endColor, growth);
particleList.add(data);
}
public Color blend(Color c1, Color c2, float ratio) {
if ( ratio > 1f ) ratio = 1f;
else if ( ratio < 0f ) ratio = 0f;
float iRatio = 1.0f - ratio;
int redOne = Math.min(255,Math.max(0,Math.round((c1.getRed() * iRatio) + (c2.getRed() * ratio))));
int greenOne = Math.min(255,Math.max(0,Math.round((c1.getGreen() * iRatio) + (c2.getGreen() * ratio))));
int blueOne = Math.min(255,Math.max(0,Math.round((c1.getBlue() * iRatio) + (c2.getBlue() * ratio))));
int alphaOne = Math.min(255,Math.max(0,Math.round((c1.getAlpha() * iRatio) + (c2.getAlpha() * ratio))));
return new Color(redOne,greenOne,blueOne,alphaOne);
}
@Override
public void renderInWorldCoords(ViewportAPI vapi) {
if(engine == null) return;
if(engine.isInFastTimeAdvance() || engine.isUIShowingDialog() || engine.isPaused()){ frameTime = 0f; return;}
centerPoint = vapi.getCenter();
//if(centerPoint == null) return;
/*
for(ShipAPI ship : engine.getShips()){
if(!engineList.contains(ship)){
for(ShipEngineAPI sEngine : ship.getEngineController().getShipEngines()){
EngineSlotAPI slot = sEngine.getEngineSlot();
ship.getEngineController().setFlameLevel(slot, 0f);
}
engineList.add(ship);
} else {
ShipEngineControllerAPI sEngineControl = ship.getEngineController();
if(!sEngineControl.isDisabled()
&& !sEngineControl.isFlamedOut()
&& !sEngineControl.isFlamingOut()
){
float angle = ship.getFacing() - 270f;
for(ShipEngineAPI sEngine : sEngineControl.getShipEngines()){
if(!sEngine.isDisabled()){
EngineSlotAPI slot = sEngine.getEngineSlot();
ship.getEngineController().setFlameLevel(slot, 0f);
Vector2f eLoc = sEngine.getLocation();
eSprite.setAngle(angle);
eSprite.setSize(20f, 20f);
eSprite.setColor(Color.yellow);
eSprite.setAdditiveBlend();
eSprite.renderAtCenter(eLoc.getX(), eLoc.getY());
}
}
}
}
}*/
//Runs the projectiles that are generated here, based on their parameters
for (fx_Particle thisParticle : particleList) {
//Don't bother moving or doing any math on dead particles
if(thisParticle.isDead()) continue;
//Age the living particles
thisParticle.timerChange(thisParticle.timeLeft() - frameTime);
float timeLeft = thisParticle.timeLeft();
boolean isFading = thisParticle.isFading();
//Sets this particle to fade out, if we're not already there
if(timeLeft <= 0f && !isFading){
thisParticle.fade();
thisParticle.timerChange(thisParticle.fadeTime);
thisParticle.totalTime = thisParticle.fadeTime;
}
//Kill particles and exit the loop, or move them around and Do Stuff.
if(timeLeft <= 0f && isFading){
thisParticle.kill();
} else {
Vector2f movement = thisParticle.position;
if(frameTime > 0f){
if(thisParticle.isFading){
thisParticle.curColor = blend(new Color(0,0,0,0),thisParticle.endRGB,timeLeft / thisParticle.totalTime);
} else {
if(thisParticle.isBlended){
thisParticle.curColor = blend(thisParticle.endRGB,thisParticle.startRGB,timeLeft / thisParticle.totalTime);
}
}
if (thisParticle.growth != 1.0f) thisParticle.size = new Vector2f(thisParticle.size.getX() * thisParticle.growth, thisParticle.size.getY() * thisParticle.growth);
movement = new Vector2f(movement.getX() + (thisParticle.velocity.getX() * frameTime),
movement.getY() + (thisParticle.velocity.getY() * frameTime));
}
thisParticle.position = movement;
SpriteAPI thisSprite = thisParticle.sprite;
thisSprite.setColor(thisParticle.curColor);
thisSprite.setAngle(thisParticle.angle);
thisSprite.setSize(thisParticle.size.getX(), thisParticle.size.getY());
if(thisParticle.isAdditive){
thisSprite.setAdditiveBlend();
} else {
thisSprite.setBlendFunc(GL11.GL_SRC_ALPHA,GL11.GL_ONE_MINUS_SRC_ALPHA);
}
thisSprite.renderAtCenter(movement.getX(), movement.getY());
}
}
}
@Override
public void renderInUICoords(ViewportAPI vapi) {
}
}