001package com.fs.starfarer.api.impl.combat.dweller; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import java.awt.Color; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.combat.CombatEngineAPI; 012import com.fs.starfarer.api.combat.CombatEntityAPI; 013import com.fs.starfarer.api.combat.DamagingProjectileAPI; 014import com.fs.starfarer.api.combat.EmpArcEntityAPI; 015import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams; 016import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin; 017import com.fs.starfarer.api.combat.MissileAPI; 018import com.fs.starfarer.api.combat.OnFireEffectPlugin; 019import com.fs.starfarer.api.combat.OnHitEffectPlugin; 020import com.fs.starfarer.api.combat.ShipAPI; 021import com.fs.starfarer.api.combat.WeaponAPI; 022import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 023import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI; 024import com.fs.starfarer.api.impl.combat.dweller.DwellerShroud.DwellerShroudParams; 025import com.fs.starfarer.api.util.Misc; 026 027/** 028 * Multiple instances of this plugin - one for every projectile (on hit), and one for each weapon. 029 * 030 * The goal is for the on-hit effect to fire off a lightning arc in case of a hit, and for the onfire/every frame copy 031 * of the plugin to fire off a lightning arc in case there is a miss. 032 * 033 * @author Alex 034 * 035 */ 036public class RiftLightningEffect implements OnHitEffectPlugin, OnFireEffectPlugin, EveryFrameWeaponEffectPlugin { 037 038 039 040 041 public static Color RIFT_LIGHTNING_COLOR = new Color(255,50,50,255); 042 public static float RIFT_LIGHTNING_SPEED = 10000f; 043 044// public static String RIFT_LIGHTNING_PROJ_TAG = "rift_lightning_proj_tag"; 045 public static String RIFT_LIGHTNING_DAMAGE_REMOVER = "rift_lightning_damage_remover"; 046 public static String RIFT_LIGHTNING_FIRED_TAG = "rift_lightning_fired_tag"; 047 public static String RIFT_LIGHTNING_SOURCE_WEAPON = "rift_lightning_source_weapon"; 048 049 public static class FiredLightningProjectile { 050 public DamagingProjectileAPI projectile; 051 } 052 053 054// /** 055// * The actual damage is dealt by the rift explosion. 056// * (Removing this: setting multiplier to 0 on projectile instead) 057// * @author Alex 058// * 059// */ 060// public static class RiftLightningBaseDamageNegator implements DamageDealtModifier { 061// @Override 062// public String modifyDamageDealt(Object param, CombatEntityAPI target, DamageAPI damage, Vector2f point, boolean shieldHit) { 063// if (param instanceof DamagingProjectileAPI) { 064// DamagingProjectileAPI proj = (DamagingProjectileAPI) param; 065// if (proj.getCustomData().containsKey(RIFT_LIGHTNING_PROJ_TAG)) { 066// damage.getModifier().modifyMult(RIFT_LIGHTNING_PROJ_TAG, 0f); 067// return RIFT_LIGHTNING_PROJ_TAG; 068// } 069// } 070// return null; 071// } 072// } 073 074 protected List<FiredLightningProjectile> fired = new ArrayList<>(); 075 076 @Override 077 public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) { 078// if (!fired.isEmpty()) { 079// System.out.println("FIRED"); 080// } 081 List<FiredLightningProjectile> remove = new ArrayList<>(); 082 083 float maxRange = weapon.getRange(); 084 for (FiredLightningProjectile data : fired) { 085 float dist = Misc.getDistance(data.projectile.getSpawnLocation(), data.projectile.getLocation()); 086 boolean firedAlready = data.projectile.getCustomData().containsKey(RIFT_LIGHTNING_FIRED_TAG); 087 if (dist > maxRange || firedAlready) { 088 remove.add(data); 089 if (!firedAlready) { 090 fireArc(data.projectile, weapon, null, null); 091 } 092 } 093 } 094 fired.removeAll(remove); 095 } 096 097 public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 098// if (weapon.getShip() != null && 099// !weapon.getShip().hasListenerOfClass(RiftLightningBaseDamageNegator.class)) { 100// weapon.getShip().addListener(new RiftLightningBaseDamageNegator()); 101// } 102 //projectile.setCustomData(RIFT_LIGHTNING_PROJ_TAG, true); 103 104 projectile.getDamage().getModifier().modifyMult(RIFT_LIGHTNING_DAMAGE_REMOVER, 0f); 105 projectile.setCustomData(RIFT_LIGHTNING_SOURCE_WEAPON, weapon); 106 107 FiredLightningProjectile data = new FiredLightningProjectile(); 108 data.projectile = projectile; 109 fired.add(data); 110 } 111 112 113 public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, 114 Vector2f point, boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) { 115 116 WeaponAPI weapon = (WeaponAPI) projectile.getCustomData().get(RIFT_LIGHTNING_SOURCE_WEAPON); 117 if (weapon == null) return; 118 119 fireArc(projectile, weapon, point, target); 120 } 121 122 public static void fireArc(DamagingProjectileAPI projectile, WeaponAPI weapon, Vector2f point, CombatEntityAPI target) { 123 boolean firedAlready = projectile.getCustomData().containsKey(RIFT_LIGHTNING_FIRED_TAG); 124 if (firedAlready) return; 125 126 projectile.setCustomData(RIFT_LIGHTNING_FIRED_TAG, true); 127 128 CombatEngineAPI engine = Global.getCombatEngine(); 129 130 ShipAPI ship = weapon.getShip(); 131 if (ship == null) return; 132 133 //Vector2f from = weapon.getFirePoint(0); 134 Vector2f from = projectile.getSpawnLocation(); 135 136 137 float dist = Float.MAX_VALUE; 138 if (point != null) dist = Misc.getDistance(from, point); 139 140 float maxRange = weapon.getRange(); 141 if (dist > maxRange || point == null) { 142 dist = maxRange * (0.5f + 0.5f * (float) Math.random()); 143 if (projectile.didDamage()) { 144 dist = maxRange; 145 } 146 point = Misc.getUnitVectorAtDegreeAngle(projectile.getFacing()); 147 point.scale(dist); 148 Vector2f.add(point, from, point); 149 } 150 151 float arcSpeed = RIFT_LIGHTNING_SPEED; 152 153 DwellerShroud shroud = DwellerShroud.getShroudFor(ship); 154 if (shroud != null) { 155 float angle = Misc.getAngleInDegrees(ship.getLocation(), point); 156 from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random()); 157 from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset); 158 Vector2f.add(ship.getLocation(), from, from); 159 } 160 161 162 EmpArcParams params = new EmpArcParams(); 163 params.segmentLengthMult = 8f; 164 params.zigZagReductionFactor = 0.15f; 165 params.fadeOutDist = 50f; 166 params.minFadeOutMult = 10f; 167// params.flickerRateMult = 0.7f; 168 params.flickerRateMult = 0.3f; 169// params.flickerRateMult = 0.05f; 170// params.glowSizeMult = 3f; 171// params.brightSpotFullFraction = 0.5f; 172 173 params.movementDurOverride = Math.max(0.05f, dist / arcSpeed); 174 175 //Color color = weapon.getSpec().getGlowColor(); 176 Color color = RIFT_LIGHTNING_COLOR; 177 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual(from, ship, point, null, 178 80f, // thickness 179 color, 180 new Color(255,255,255,255), 181 params 182 ); 183 arc.setCoreWidthOverride(40f); 184 185 arc.setRenderGlowAtStart(false); 186 arc.setFadedOutAtStart(true); 187 arc.setSingleFlickerMode(true); 188 189 spawnMine(ship, point, params.movementDurOverride * 0.8f); // - 0.05f); 190 191 192 if (shroud != null) { 193 DwellerShroudParams shroudParams = shroud.getShroudParams(); 194 params = new EmpArcParams(); 195 params.segmentLengthMult = 4f; 196 params.glowSizeMult = 4f; 197 params.flickerRateMult = 0.5f + (float) Math.random() * 0.5f; 198 params.flickerRateMult *= 1.5f; 199 200 //Color fringe = shroudParams.overloadArcFringeColor; 201 Color fringe = color; 202 Color core = Color.white; 203 204 float thickness = shroudParams.overloadArcThickness; 205 206 //Vector2f to = Misc.getPointAtRadius(from, 1f); 207 208 float angle = Misc.getAngleInDegrees(from, ship.getLocation()); 209 angle = angle + 90f * ((float) Math.random() - 0.5f); 210 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle); 211 dist = shroudParams.maxOffset; 212 dist = dist * 0.5f + dist * 0.5f * (float) Math.random(); 213 //dist *= 1.5f; 214 dist *= 0.5f; 215 dir.scale(dist); 216 Vector2f to = Vector2f.add(from, dir, new Vector2f()); 217 218 arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual( 219 from, ship, to, ship, thickness, fringe, core, params); 220 221 arc.setCoreWidthOverride(shroudParams.overloadArcCoreThickness); 222 arc.setSingleFlickerMode(false); 223 //arc.setRenderGlowAtStart(false); 224 } 225 226 } 227 228 public static void spawnMine(ShipAPI source, Vector2f mineLoc, float delay) { 229 CombatEngineAPI engine = Global.getCombatEngine(); 230 231 232 //Vector2f currLoc = mineLoc; 233 MissileAPI mine = (MissileAPI) engine.spawnProjectile(source, null, 234 "rift_lightning_minelayer", 235 mineLoc, 236 (float) Math.random() * 360f, null); 237 if (source != null) { 238 Global.getCombatEngine().applyDamageModifiersToSpawnedProjectileWithNullWeapon( 239 source, WeaponType.ENERGY, false, mine.getDamage()); 240 } 241 242 243 float fadeInTime = 0.05f; 244 mine.getVelocity().scale(0); 245 mine.fadeOutThenIn(fadeInTime); 246 247 float liveTime = Math.max(delay, 0f); 248 mine.setFlightTime(mine.getMaxFlightTime() - liveTime); 249 mine.addDamagedAlready(source); 250 mine.setNoMineFFConcerns(true); 251 if (liveTime <= 0.016f) { 252 mine.explode(); 253 } 254 } 255 256}