001package com.fs.starfarer.api.impl.combat.threat; 002 003import java.util.Iterator; 004 005import java.awt.Color; 006 007import org.lwjgl.util.vector.Vector2f; 008 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.combat.CollisionClass; 011import com.fs.starfarer.api.combat.CombatEngineAPI; 012import com.fs.starfarer.api.combat.CombatEntityAPI; 013import com.fs.starfarer.api.combat.DamageType; 014import com.fs.starfarer.api.combat.DamagingProjectileAPI; 015import com.fs.starfarer.api.combat.EmpArcEntityAPI; 016import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams; 017import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin; 018import com.fs.starfarer.api.combat.MissileAPI; 019import com.fs.starfarer.api.combat.OnFireEffectPlugin; 020import com.fs.starfarer.api.combat.ShipAPI; 021import com.fs.starfarer.api.combat.WeaponAPI; 022import com.fs.starfarer.api.combat.WeaponAPI.AIHints; 023import com.fs.starfarer.api.impl.campaign.ids.Stats; 024import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.SwarmMember; 025import com.fs.starfarer.api.util.Misc; 026import com.fs.starfarer.api.util.WeightedRandomPicker; 027 028/** 029 */ 030public class VoltaicDischargeOnFireEffect implements OnFireEffectPlugin, EveryFrameWeaponEffectPlugin, 031 FragmentWeapon { 032 033 public static String SWARM_TAG_PHASE_MODE = "swarm_tag_phase_mode"; 034 035 public static Color EMP_FRINGE_COLOR_BRIGHT = new Color(213,255,237,255); 036 037 public static Color EMP_FRINGE_COLOR = new Color(130,155,145,255); 038 public static Color PHASE_FRINGE_COLOR = new Color(120,110,185,255); 039 public static Color PHASE_CORE_COLOR = new Color(255,255,255,127); 040 041 public static float EXTRA_ARC = 360f; 042 public static int FRAGMENTS_TO_FIRE = 10; 043 044 045 public static boolean isSwarmPhaseMode(ShipAPI ship) { 046 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 047 return swarm != null && swarm.params.tags.contains(SWARM_TAG_PHASE_MODE); 048 } 049 public static void setSwarmPhaseMode(ShipAPI ship) { 050 new AttackSwarmPhaseModeScript(ship); 051 052// RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 053// if (swarm != null) { 054// Color color = Misc.setAlpha(VoltaicDischargeOnFireEffect.PHASE_FRINGE_COLOR, 60); 055// swarm.params.flashFringeColor = color; 056// swarm.params.flashRadius = 180f; 057// swarm.params.tags.add(VoltaicDischargeOnFireEffect.SWARM_TAG_PHASE_MODE); 058// 059// for (WeaponAPI w : ship.getAllWeapons()) { 060// if (w.usesAmmo() && w.getSpec().hasTag(Tags.FRAGMENT_GLOW)) { 061// w.setAmmo(Integer.MAX_VALUE); 062// w.setMaxAmmo(Integer.MAX_VALUE); 063// } 064// if (w.getSpec().hasTag(Tags.OVERSEER_CHARGE) || 065// (ship.isFighter() && w.getSpec().hasTag(Tags.OVERSEER_CHARGE_FIGHTER))) { 066// w.setAmmo(w.getMaxAmmo()); 067// } 068// } 069// } 070 } 071 072 @Override 073 public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) { 074 ShipAPI ship = weapon.getShip(); 075 if (ship == null) return; 076 077 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 078 int active = swarm == null ? 0 : swarm.getNumActiveMembers(); 079 int required = getNumFragmentsToFire(); 080 boolean disable = active < required; 081 weapon.setForceDisabled(disable); 082 083 showNoFragmentSwarmWarning(weapon, ship); 084 } 085 086 @Override 087 public int getNumFragmentsToFire() { 088 return FRAGMENTS_TO_FIRE; 089 } 090 091 public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 092 //ARC = 30f; 093 float emp = projectile.getEmpAmount(); 094 float dam = projectile.getDamageAmount(); 095 096 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(projectile.getSource()); 097 if (swarm == null || swarm.getAttachedTo() == null) return; 098 099 CombatEntityAPI target = findTarget(projectile, weapon, engine); 100 101 Vector2f noTargetDest = null; 102 if (target == null) noTargetDest = pickNoTargetDest(projectile, weapon, engine); 103 104 Vector2f towards = noTargetDest; 105 if (target != null) towards = target.getLocation(); 106 107 SwarmMember pick = pickFragmentTowardsPointWithinRange(swarm, towards, 150f); 108 if (pick == null) return; 109 110 pick.setRecentlyPicked(1f); 111 112 float thickness = 30f; 113 //Color color = weapon.getSpec().getGlowColor(); 114 //Color color = new Color(255,0,0,255); 115 Color color = EMP_FRINGE_COLOR; 116 Color coreColor = Color.white; 117 118 boolean phaseMode = isSwarmPhaseMode(projectile.getSource()); 119 if (phaseMode) { 120 color = PHASE_FRINGE_COLOR; 121 if (target instanceof ShipAPI && ((ShipAPI)target).isPhased()) { 122 coreColor = PHASE_CORE_COLOR; 123 } 124 } 125 126 float coreWidthMult = 0.75f; 127 128 EmpArcParams params = new EmpArcParams(); 129 params.segmentLengthMult = 8f; 130 //params.zigZagReductionFactor = 0.25f; 131 params.zigZagReductionFactor = 0.5f; 132 //params.maxZigZagMult = 0f; 133 //params.flickerRateMult = 0.75f; 134 params.flickerRateMult = 1f; 135 params.fadeOutDist = 1000f; 136 params.minFadeOutMult = 1f; 137// params.fadeOutDist = 200f; 138// params.minFadeOutMult = 2f; 139 params.glowSizeMult = 0.5f; 140 params.glowAlphaMult = 0.75f; 141 142 // actually, probably fine given how long it takes to chew through the missile health with low damage per hit 143 //params.flamesOutMissiles = false; // a bit much given the RoF and general prevalence 144 145 pick.flash(); 146 pick.flash.forceIn(); 147 pick.flash.setDurationOut(0.25f); 148 149 //weapon.setAmmo(20); 150 151 if (target != null) { 152 EmpArcEntityAPI arc = engine.spawnEmpArc(projectile.getSource(), pick.loc, weapon.getShip(), 153 target, 154 DamageType.ENERGY, 155 dam, 156 emp, // emp 157 100000f, // max range 158 "voltaic_discharge_emp_impact", 159 thickness, // thickness 160 color, 161 coreColor, 162 params 163 ); 164 arc.setCoreWidthOverride(thickness * coreWidthMult); 165 arc.setSingleFlickerMode(); 166 arc.setUpdateFromOffsetEveryFrame(true); 167 arc.setRenderGlowAtStart(false); 168 arc.setFadedOutAtStart(true); 169 } else { 170 params.flickerRateMult = 1f; 171 172 Vector2f to = noTargetDest; 173 //Vector2f to = targetLoc; 174 EmpArcEntityAPI arc = engine.spawnEmpArcVisual(pick.loc, weapon.getShip(), to, weapon.getShip(), thickness, color, coreColor, params); 175 arc.setCoreWidthOverride(thickness * coreWidthMult); 176 arc.setSingleFlickerMode(); 177 arc.setUpdateFromOffsetEveryFrame(true); 178 arc.setRenderGlowAtStart(false); 179 arc.setFadedOutAtStart(true); 180 //Global.getSoundPlayer().playSound("shock_repeater_emp_impact", 1f, 1f, to, new Vector2f()); 181 } 182 } 183 184 public Vector2f pickNoTargetDest(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 185 float spread = 50f; 186 float range = Math.min(weapon.getRange() - spread, 150f); 187 Vector2f from = projectile.getLocation(); 188 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(weapon.getCurrAngle() + (EXTRA_ARC/2f - EXTRA_ARC * (float) Math.random())); 189 dir.scale(range); 190 Vector2f.add(from, dir, dir); 191 dir = Misc.getPointWithinRadius(dir, spread); 192 return dir; 193 } 194 195 public CombatEntityAPI findTarget(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 196 float range = weapon.getRange() + 50f; 197 Vector2f from = projectile.getLocation(); 198 199 Iterator<Object> iter = Global.getCombatEngine().getAllObjectGrid().getCheckIterator(from, 200 range * 2f, range * 2f); 201 int owner = weapon.getShip().getOwner(); 202 CombatEntityAPI best = null; 203 float minScore = Float.MAX_VALUE; 204 205 ShipAPI ship = weapon.getShip(); 206 boolean ignoreFlares = ship != null && ship.getMutableStats().getDynamic().getValue(Stats.PD_IGNORES_FLARES, 0) >= 1; 207 ignoreFlares |= weapon.hasAIHint(AIHints.IGNORES_FLARES); 208 209 boolean phaseMode = isSwarmPhaseMode(ship); 210 211 while (iter.hasNext()) { 212 Object o = iter.next(); 213 if (!(o instanceof MissileAPI) && 214 //!(o instanceof CombatAsteroidAPI) && 215 !(o instanceof ShipAPI)) continue; 216 CombatEntityAPI other = (CombatEntityAPI) o; 217 if (other.getOwner() == owner) continue; 218 219 boolean phaseHit = false; 220 if (other instanceof ShipAPI) { 221 ShipAPI otherShip = (ShipAPI) other; 222 if (otherShip.isHulk()) continue; 223 //if (!otherShip.isAlive()) continue; 224 if (otherShip.isPhased()) { 225 if (phaseMode) { 226 phaseHit = true; 227 } else { 228 continue; 229 } 230 } 231 if (!otherShip.isTargetable()) continue; 232 } 233 234 if (!phaseHit && other.getCollisionClass() == CollisionClass.NONE) continue; 235 236 if (ignoreFlares && other instanceof MissileAPI) { 237 MissileAPI missile = (MissileAPI) other; 238 if (missile.isFlare()) continue; 239 } 240 241 float radius = Misc.getTargetingRadius(from, other, false); 242 float dist = Misc.getDistance(from, other.getLocation()) - radius; 243 if (dist > range) continue; 244 245 if (!Misc.isInArc(weapon.getCurrAngle(), EXTRA_ARC, from, other.getLocation())) continue; 246 247 float score = dist; 248 249 if (score < minScore) { 250 minScore = score; 251 best = other; 252 } 253 } 254 return best; 255 } 256 257 public static SwarmMember pickFragmentTowardsPointWithinRange(RoilingSwarmEffect swarm, Vector2f towards, float maxRange) { 258 WeightedRandomPicker<SwarmMember> picker = swarm.getPicker(true, true, towards); 259 while (!picker.isEmpty()) { 260 SwarmMember p = picker.pickAndRemove(); 261 float dist = Misc.getDistance(p.loc, swarm.getAttachedTo().getLocation()); 262 if (dist > maxRange) continue; 263 return p; 264 } 265 return null; 266 } 267 268 269}