How do I add a listener to a projectile?
I want to create a projectile that, if shot down by pd, will spawn an emp arc to the nearest target within range. I've added listeners to ships but I don't see a similar method for projectiles.
you can't add a listener directly to a projectile, but I've got a similar thing that I do this (see below) for.
this method technically has a few pitfalls (particularly if the projectile is spawned using spawnProjectile() without manually calling the onFire for it), but it works well enoughtm for me to not be bothered by it
Thank you. With your help I was able to make what I wanted. Code below.
package data.scripts.weapons;
import com.fs.starfarer.api.combat.*;
import com.fs.starfarer.api.combat.WeaponAPI;
import com.fs.starfarer.api.combat.DamagingProjectileAPI;
import com.fs.starfarer.api.combat.EveryFrameCombatPlugin;
import com.fs.starfarer.api.combat.OnFireEffectPlugin;
import java.util.Random;
import java.lang.Math;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.combat.*;
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.loading.WeaponSlotAPI;
import com.fs.starfarer.api.combat.GuidedMissileAI;
import com.fs.starfarer.api.util.IntervalUtil;
import org.lazywizard.lazylib.combat.CombatUtils;
import com.fs.starfarer.api.combat.listeners.AdvanceableListener;
import org.lazywizard.lazylib.VectorUtils;
import org.lazywizard.lazylib.MathUtils;
import org.lazywizard.console.Console;
import org.lazywizard.lazylib.CollisionUtils;
import com.fs.starfarer.api.combat.FluxTrackerAPI;
import com.fs.starfarer.api.loading.DamagingExplosionSpec;
import com.fs.starfarer.api.combat.listeners.AdvanceableListener;
// import com.fs.starfarer.api.combat.listeners.DamageDealtModifier;
import java.awt.Color;
import java.util.*;
import org.lwjgl.util.vector.Vector2f;
import static com.fs.starfarer.api.util.Misc.ZERO;
public class SFB_Nuclear_Torpedo_OnFireEffect implements OnFireEffectPlugin {
public void onFire(final DamagingProjectileAPI projectile, final WeaponAPI weapon, final CombatEngineAPI engine) {
if (weapon.getShip() != null) {
if (!weapon.getShip().hasListenerOfClass(nuclearListener.class)) {
weapon.getShip().addListener(new nuclearListener(weapon.getShip()));
}
SFB_Nuclear_Torpedo_OnFireEffect.nuclearListener listener = getFirstListenerOfClass(weapon.getShip(), SFB_Nuclear_Torpedo_OnFireEffect.nuclearListener.class);
listener.addProj(projectile);
}
}
public static nuclearListener getFirstListenerOfClass(ShipAPI ship, Class listenerClass) {
if (!ship.hasListenerOfClass(listenerClass)) {
return null;
}
nuclearListener listener = (nuclearListener)ship.getListeners(listenerClass).get(0);
return listener;
}
public static class nuclearListener implements AdvanceableListener /*, DamageDealtModifier */ {
protected final ShipAPI ship;
private final ArrayList<DamagingProjectileAPI> projs = new ArrayList<DamagingProjectileAPI>();
private final ArrayList<DamagingProjectileAPI> deadProjs = new ArrayList<DamagingProjectileAPI>();
private static final Color ARC_FRINGE_COLOR = new Color(85, 60, 205, 225);
private static final Color ARC_CORE_COLOR = new Color(235, 175, 235, 255);
private static final int NUM_ARCS = 3f;
public nuclearListener(ShipAPI ship) {
this.ship = ship;
}
public void addProj(DamagingProjectileAPI proj) {
projs.add(proj);
}
public boolean isHarmless(MissileAPI m) {
return m.isFizzling() || m.isExpired() || m.isFading();
}
@Override
public void advance(float amount) {
Iterator<DamagingProjectileAPI> iter = projs.iterator();
while (iter.hasNext()) {
CombatEngineAPI engine = Global.getCombatEngine();
if (engine.isPaused() ) {
return;
}
DamagingProjectileAPI proj = (DamagingProjectileAPI)iter.next();
if ( isHarmless((MissileAPI)proj) || proj.didDamage() ) {
//projs.remove(proj);
continue;
}
if (proj.getHitpoints() > 300 ) {
continue;
}
java.lang.Object missileAI = proj.getAI();
if (missileAI == null ) {
continue;
}
CombatEntityAPI target = ((GuidedMissileAI)missileAI).getTarget();
//CombatEntityAPI target = proj.getDamageTarget(); // wrong. This is what it damaged
if (target == null) {
continue;
}
Vector2f projPos = proj.getLocation();
Vector2f targetPos = target.getLocation();
float distance = MathUtils.getDistanceSquared(projPos, targetPos) ;
if (distance > 250000f) { // range 500 = 250000
continue;
}
// At this point the projectile should have zero hp, not have done damage, and be within range.
SpawnEMP(target, proj);
Console.showMessage("proj in arraylist " + projs.size());
// clear out dead missles. Need a better way.
if (!deadProjs.contains(proj)){
deadProjs.add(proj);
}
}
for (DamagingProjectileAPI deadProjectile : deadProjs) {
if (projs.contains(deadProjectile)) {
projs.remove(deadProjectile);
}
}
deadProjs.clear();
}
public void SpawnEMP(CombatEntityAPI target, DamagingProjectileAPI proj) {
//Console.showMessage( proj.getHitpoints() + " hp" + " Boom!");
CombatEngineAPI engine = Global.getCombatEngine();
for (int x = 0 ; x < NUM_ARCS; x++) {
engine.spawnEmpArcPierceShields(proj.getSource(),
proj.getLocation(),
proj,
target,
DamageType.ENERGY,
0f, // damage
750f, // emp damage
500f, // range
"shock_repeater_emp_impact", // sound
8f, // thickness
ARC_FRINGE_COLOR, // fringe
ARC_CORE_COLOR // core color
);
}
proj.setHitpoints(0f);
}
}
}
One difference from yours is that I iterate through the original arraylist instead of creating a copy inside the advance() method. Isn't creating a copy of the arraylist expensive? Sinec advance runs every frame I'm concerned about performance.
I want to remove the dead projectiles but, like you said, doing so causes an error within the loop. The sources say it should work this way but it gave an error. Anyway, since I don't expect to spam thousands of missiles in a given battle I can live with this. Thanks again.