001package com.fs.starfarer.api.impl.combat.threat; 002 003import java.util.HashMap; 004import java.util.Map; 005 006import org.lwjgl.util.vector.Vector2f; 007 008import com.fs.starfarer.api.GameState; 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.combat.CombatEngineAPI; 011import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 012import com.fs.starfarer.api.combat.DamagingProjectileAPI; 013import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin; 014import com.fs.starfarer.api.combat.FighterWingAPI; 015import com.fs.starfarer.api.combat.OnFireEffectPlugin; 016import com.fs.starfarer.api.combat.ShipAPI; 017import com.fs.starfarer.api.combat.WeaponAPI; 018import com.fs.starfarer.api.impl.campaign.ids.HullMods; 019import com.fs.starfarer.api.impl.campaign.ids.Stats; 020import com.fs.starfarer.api.util.Misc; 021 022public class SwarmLauncherEffect implements OnFireEffectPlugin, EveryFrameWeaponEffectPlugin, SwarmLaunchingWeapon { 023 024 public static String CONSTRUCTION_SWARM_WING = "construction_swarm_wing"; 025 public static String CONSTRUCTION_SWARM_VARIANT = "attack_swarm_Construction"; 026 public static String RECLAMATION_SWARM_WING = "reclamation_swarm_wing"; 027 public static String RECLAMATION_SWARM_VARIANT = "attack_swarm_Reclamation"; 028 029 public static String ATTACK_SWARM_HULL = "attack_swarm"; 030 public static String ATTACK_SWARM_WING = "attack_swarm_wing"; 031 public static String ATTACK_SWARM_VARIANT = "attack_swarm_Attack"; 032 public static String SWARM_LAUNCHER = "swarm_launcher"; 033 public static float IMPACT_VOLUME_MULT = 0.33f; 034 035 public static float INITIAL_SPAWN_DELAY = 1f; 036 037 public static Map<String, Integer> FRAGMENT_NUM = new HashMap<>(); 038 public static Map<String, Integer> SWARM_RADIUS = new HashMap<>(); 039 public static Map<String, Integer> WING_SIZE = new HashMap<>(); 040 public static Map<String, String> WING_IDS = new HashMap<>(); 041 042 static { 043 FRAGMENT_NUM.put(ATTACK_SWARM_WING, 50); 044 SWARM_RADIUS.put(ATTACK_SWARM_WING, 20); 045 WING_SIZE.put(ATTACK_SWARM_WING, 4); 046 WING_IDS.put(SWARM_LAUNCHER, ATTACK_SWARM_WING); 047 048 FRAGMENT_NUM.put(CONSTRUCTION_SWARM_WING, 50); 049 SWARM_RADIUS.put(CONSTRUCTION_SWARM_WING, 50); 050 } 051 052 protected FighterWingAPI currWing = null; 053 protected boolean waitUntilOneLeft = false; 054 protected float elapsed = 0f; 055 056 public SwarmLauncherEffect() { 057 058 } 059 060 @Override 061 public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) { 062 ShipAPI ship = weapon.getShip(); 063 if (ship == null) return; 064 065 elapsed += amount; 066 if (elapsed < INITIAL_SPAWN_DELAY || ship.hasTag(ThreatShipConstructionScript.SHIP_UNDER_CONSTRUCTION)) { 067 weapon.setForceDisabled(true); 068 return; 069 } 070 071 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 072 int active = swarm == null ? 0 : swarm.getNumActiveMembers(); 073 int required = FRAGMENT_NUM.get(getWingId(weapon)); 074 boolean disable = active < required; 075 //disable = false; 076 int max = getWingSize(weapon); 077 int swarmsActive = 0; 078 if (currWing != null) swarmsActive = currWing.getWingMembers().size(); 079 waitUntilOneLeft |= currWing != null && swarmsActive >= max; 080 if (currWing != null && currWing.getWingMembers().size() == 1 && currWing.getLeader() != null) { 081 float dist = Misc.getDistance(currWing.getLeader().getLocation(), weapon.getLocation()); 082 if (dist > 1000f) { 083 waitUntilOneLeft = true; 084 } 085 } 086 if (waitUntilOneLeft) { 087 if (currWing != null && ((swarmsActive <= 1 && max > 1) || swarmsActive <= 0 && max <= 1)) { 088 waitUntilOneLeft = false; 089 currWing = null; 090 } else { 091 disable = true; 092 } 093 } 094 095 if (currWing == null && swarm != null) { 096 int preferred = getPreferredNumFragmentsToFireConsideringAllWeapons(ship); 097 preferred = (int) Math.min(preferred, swarm.params.baseMembersToMaintain * 0.9f); 098 if (active < preferred ) { 099 disable = true; 100 } 101 } 102 103 weapon.setForceDisabled(disable); 104 105 boolean playerShip = Global.getCurrentState() == GameState.COMBAT && 106 Global.getCombatEngine() != null && Global.getCombatEngine().getPlayerShip() == ship; 107 if (playerShip) { 108 Global.getCombatEngine().maintainStatusForPlayerShip(this, 109 Global.getSettings().getSpriteName("ui", "icon_tactical_swarm_launcher"), 110 weapon.getDisplayName(), 111 "SWARMS ACTIVE: " + swarmsActive, // + " / " + max, 112 swarmsActive <= 0); 113 } 114 115 weapon.setCustom(currWing); 116 117 if (ship.getVariant().hasHullMod(HullMods.THREAT_HULLMOD)) { 118 weapon.setAmmo(weapon.getMaxAmmo()); 119 } 120 121 if (disable) { 122 return; 123 } 124 125 weapon.setForceFireOneFrame(true); 126 } 127 128 public String getWingId(WeaponAPI weapon) { 129 //String wingId = WING_IDS.get(SWARM_LAUNCHER); 130 String wingId = WING_IDS.get(weapon.getId()); 131 if (wingId != null) return wingId; 132 return ATTACK_SWARM_WING; 133 } 134 135 public int getWingSize(WeaponAPI weapon) { 136 float wingSize = WING_SIZE.get(getWingId(weapon)); 137 if (weapon.getShip() != null) { 138 wingSize = weapon.getShip().getMutableStats().getDynamic().getValue( 139 Stats.SWARM_LAUNCHER_WING_SIZE_MOD, wingSize); 140 } 141 //wingSize = 20; 142 //wingSize = 1; 143 return (int) Math.round(wingSize); 144 } 145 146 public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 147 148 //FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(ATTACK_SWARM_WING); 149 150 CombatFleetManagerAPI manager = engine.getFleetManager(projectile.getOwner()); 151 manager.setSuppressDeploymentMessages(true); 152 ShipAPI leader = manager.spawnShipOrWing(getWingId(weapon), 153 projectile.getLocation(), projectile.getFacing(), 0f, null); 154 leader.getWing().setSourceShip(projectile.getSource()); 155 manager.setSuppressDeploymentMessages(false); 156 157 Vector2f takeoffVel = Misc.getUnitVectorAtDegreeAngle(projectile.getFacing()); 158 takeoffVel.scale(leader.getMaxSpeed() * 1f); 159 160 //Global.getSoundPlayer().playSound("threat_swarm_launched", 1f, 1f, projectile.getLocation(), takeoffVel); 161 162 for (ShipAPI curr : leader.getWing().getWingMembers()) { 163 curr.setDoNotRender(true); 164 curr.setExplosionScale(0f); 165 curr.setHulkChanceOverride(0f); 166 curr.setImpactVolumeMult(IMPACT_VOLUME_MULT); 167 curr.getArmorGrid().clearComponentMap(); // no damage to weapons/engines 168 169 if (currWing != null) { 170 if (curr.getWing() != null) { 171 curr.getWing().removeMember(curr); 172 // really important, otherwise this doesn't get cleaned up 173 manager.removeDeployed(curr.getWing(), false); 174 } 175 curr.setWing(currWing); 176 currWing.addMember(curr); 177 } 178 179 Vector2f.add(curr.getVelocity(), takeoffVel, curr.getVelocity()); 180 } 181 182 currWing = leader.getWing(); 183 184 RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(weapon.getShip()); 185 if (sourceSwarm != null) { 186 RoilingSwarmEffect swarm = FragmentSwarmHullmod.createSwarmFor(leader); 187 188 int required = FRAGMENT_NUM.get(getWingId(weapon)); 189 int transfer = required; 190 191 if (transfer > 0) { 192 Vector2f loc = new Vector2f(takeoffVel); 193 loc.scale(0.5f); 194 Vector2f.add(loc, leader.getLocation(), loc); 195 sourceSwarm.transferMembersTo(swarm, transfer, loc, 100f); 196 } 197 198 } 199 200// if (currWing.getWingMembers().size() >= getWingSize(weapon)) { 201// currWing = null; 202// } 203 204 engine.removeEntity(projectile); 205 } 206 207 public int getPreferredNumFragmentsToFireConsideringAllWeapons(ShipAPI ship) { 208 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 209 if (swarm == null) return 0; 210 211 int req = 0; 212 for (WeaponAPI w : ship.getAllWeapons()) { 213 if (w.getEffectPlugin() instanceof SwarmLaunchingWeapon) { 214 if (w.isFiring()) continue; 215 SwarmLaunchingWeapon effect = (SwarmLaunchingWeapon) w.getEffectPlugin(); 216 req += effect.getPreferredNumFragmentsToFire(w); 217 } 218 } 219 return req; 220 } 221 222 @Override 223 public int getPreferredNumFragmentsToFire(WeaponAPI weapon) { 224 Integer required = FRAGMENT_NUM.get(getWingId(weapon)); 225 if (required == null) return 0; 226 int wingSize = getWingSize(weapon); 227 int num = Math.min(wingSize, 2); 228 return required * num; 229 } 230 231 232// 233// public static RoilingSwarmEffect createTestDwellerSwarmFor(ShipAPI ship) { 234// RoilingSwarmEffect existing = RoilingSwarmEffect.getSwarmFor(ship); 235// if (existing != null) { 236// if (!"dweller_pieces".equals(existing.params.spriteKey)) { 237// existing.setForceDespawn(true); 238// } 239// //return existing; 240// } 241// 242// RoilingSwarmParams params = new RoilingSwarmParams(); 243// float radius = 20f; 244// int numMembers = 50; 245// 246// 247//// "fx_particles1":"graphics/fx/fx_clouds00.png", 248//// "fx_particles2":"graphics/fx/fx_clouds01.png", 249//// "nebula_particles":"graphics/fx/nebula_colorless.png", 250//// "nebula_particles2":"graphics/fx/cleaner_clouds00.png", 251//// "dust_particles":"graphics/fx/dust_clouds_colorless.png", 252// 253//// params.spriteKey = "dust_particles"; 254// params.spriteKey = "fx_particles1"; 255// params.spriteKey = "nebula_particles"; 256// params.spriteKey = "dweller_pieces"; 257// 258// params.baseDur = 1f; 259// params.durRange = 2f; 260// params.memberRespawnRate = 100f; 261// 262// params.memberExchangeClass = null; 263// params.flockingClass = null; 264// params.maxSpeed = ship.getMaxSpeedWithoutBoost() + 265// Math.max(ship.getMaxSpeedWithoutBoost() * 0.25f + 50f, 100f); 266// 267// params.baseSpriteSize = 256f; 268// params.baseSpriteSize = 128f * 1.5f * 0.67f; 269// params.maxTurnRate = 120f; 270// 271// radius = 100f; 272// numMembers = 100; 273// 274// params.flashCoreRadiusMult = 0f; 275// //params.flashRadius = 0f; 276// params.flashRadius = 200f; 277// params.flashRateMult = 0.1f; 278// params.flashFrequency = 10f; 279// params.flashProbability = 1f; 280// 281// 282// params.alphaMult = 1f; 283// params.alphaMultBase = 0.1f; 284// params.alphaMultFlash = 1f; 285// params.color = RiftCascadeEffect.STANDARD_RIFT_COLOR; 286// params.color = Misc.setAlpha(RiftCascadeEffect.EXPLOSION_UNDERCOLOR, 255); 287// params.color = Misc.setBrightness(params.color, 155); 288// params.flashFringeColor = params.color; 289// params.flashCoreColor = params.color; 290// 291// params.color = new Color(100, 50, 255, 255); 292// params.flashFringeColor = new Color(100, 100, 255, 255); 293// params.flashCoreColor = new Color(100, 100, 255, 255); 294// 295// //params.renderFlashOnSameLayer = true; 296// 297// params.maxOffset = radius; 298// 299// //params.despawnDist = params.maxOffset + 300f; 300// 301// params.initialMembers = numMembers; 302// params.baseMembersToMaintain = params.initialMembers; 303// 304// //params.springStretchMult = 1f; 305// 306// 307// return new RoilingSwarmEffect(ship, params) { 308// protected IntervalUtil interval = new IntervalUtil(0.075f, 0.125f); 309// 310// @Override 311// public int getNumMembersToMaintain() { 312// return super.getNumMembersToMaintain(); 313// } 314// 315// @Override 316// public void advance(float amount) { 317// super.advance(amount); 318// 319// interval.advance(amount); 320// if (interval.intervalElapsed() && false) { 321// 322// CombatEngineAPI engine = Global.getCombatEngine(); 323// 324// //Color c = RiftLanceEffect.getColorForDarkening(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR); 325// Color c = RiftLanceEffect.getColorForDarkening(params.color); 326// c = Misc.setAlpha(c, 50); 327// float baseDuration = 2f; 328// Vector2f vel = new Vector2f(ship.getVelocity()); 329// 330// float baseSize = params.maxOffset * 2f; 331// 332// //float size = ship.getCollisionRadius() * 0.35f; 333// float size = baseSize * 0.33f; 334// 335// float extraDur = 0f; 336// 337// //for (int i = 0; i < 3; i++) { 338// for (int i = 0; i < 11; i++) { 339// Vector2f point = new Vector2f(ship.getLocation()); 340// point = Misc.getPointWithinRadiusUniform(point, baseSize * 0.75f, Misc.random); 341// float dur = baseDuration + baseDuration * (float) Math.random(); 342// dur += extraDur; 343// float nSize = size; 344// Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f); 345// Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 346// v.scale(nSize + nSize * (float) Math.random() * 0.5f); 347// v.scale(0.2f); 348// Vector2f.add(vel, v, v); 349// 350// float maxSpeed = nSize * 1.5f * 0.2f; 351// float minSpeed = nSize * 1f * 0.2f; 352// float overMin = v.length() - minSpeed; 353// if (overMin > 0) { 354// float durMult = 1f - overMin / (maxSpeed - minSpeed); 355// if (durMult < 0.1f) durMult = 0.1f; 356// dur *= 0.5f + 0.5f * durMult; 357// } 358// engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f, 359// 0.5f / dur, 0f, dur, c); 360// } 361// } 362// 363// } 364// 365// }; 366// } 367} 368 369 370 371 372 373 374 375