001package com.fs.starfarer.api.impl.combat.threat; 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.BaseEveryFrameCombatPlugin; 012import com.fs.starfarer.api.combat.BoundsAPI.SegmentAPI; 013import com.fs.starfarer.api.combat.CombatEngineAPI; 014import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 015import com.fs.starfarer.api.combat.ShipAPI; 016import com.fs.starfarer.api.impl.combat.RiftLanceEffect; 017import com.fs.starfarer.api.impl.combat.threat.ConstructionSwarmSystemScript.SwarmConstructableVariant; 018import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.SwarmMember; 019import com.fs.starfarer.api.input.InputEventAPI; 020import com.fs.starfarer.api.util.IntervalUtil; 021import com.fs.starfarer.api.util.Misc; 022import com.fs.starfarer.api.util.WeightedRandomPicker; 023 024public class ThreatShipReclamationScript extends BaseEveryFrameCombatPlugin { 025 026 public static float CR_PER_RECLAMATION_SWARM = 0.02f; 027 028 public static float RECLAMATION_SWARM_SPEED_MULT = 0.67f; 029 public static float RECLAMATION_SWARM_COLLISION_MULT = 1.5f; 030 public static float RECLAMATION_SWARM_RADIUS_MULT = 2f; 031 public static float RECLAMATION_SWARM_HP_MULT = 2f; 032 public static float RECLAMATION_SWARM_FRAGMENT_SIZE_MULT = 0.67f; 033 034 035 036 protected float elapsed = 0f; 037 protected ShipAPI primary = null; 038 protected List<ShipAPI> pieces = new ArrayList<>(); 039 protected List<ShipAPI> swarms = new ArrayList<>(); 040 protected float delay; 041 protected float fadeOutTime; 042 protected float origMaxSpeed = 500f; 043 044 protected IntervalUtil interval = new IntervalUtil(0.075f, 0.125f); 045 protected IntervalUtil interval2 = new IntervalUtil(0.075f, 0.125f); 046 protected boolean spawnedSwarms = false; 047 048 public ThreatShipReclamationScript(ShipAPI ship, float delay) { 049 this.delay = delay; 050 051 this.primary = ship; 052 for (ShipAPI curr : Global.getCombatEngine().getShips()) { 053 if (curr.getFleetMember() == ship.getFleetMember()) { 054 pieces.add(curr); 055 } 056 } 057 058 switch (ship.getHullSize()) { 059 case CAPITAL_SHIP: 060 this.fadeOutTime = 15f; 061 break; 062 case CRUISER: 063 this.fadeOutTime = 12f; 064 break; 065 case DESTROYER: 066 this.fadeOutTime = 9f; 067 break; 068 case FRIGATE: 069 this.fadeOutTime = 7f; 070 break; 071 default: 072 this.fadeOutTime = 7f; 073 break; 074 } 075 076 this.fadeOutTime += 3f; 077 078 interval.forceIntervalElapsed(); 079 080 for (ShipAPI curr : pieces) { 081 curr.addTag(ThreatHullmod.SHIP_BEING_RECLAIMED); 082 } 083 } 084 085 public List<ShipAPI> getPieces() { 086 return pieces; 087 } 088 089 @Override 090 public void advance(float amount, List<InputEventAPI> events) { 091 if (Global.getCombatEngine().isPaused()) return; 092 093 elapsed += amount; 094 if (elapsed < delay) return; 095 096 097 CombatEngineAPI engine = Global.getCombatEngine(); 098 099 float progress = (elapsed - delay) / fadeOutTime; 100 if (progress < 0f) progress = 0f; 101 if (progress > 1f) progress = 1f; 102 103 104 float remaining = fadeOutTime - (elapsed - delay); 105 106 107 if (elapsed > delay + 2f && remaining > 4f) { 108 spawnSwarms(amount); 109 } 110 111 boolean first = true; 112 boolean anyInEngine = false; 113 for (ShipAPI ship : pieces) { 114 if (!engine.isInEngine(ship)) continue; 115 anyInEngine = true; 116 117 Vector2f vel = ship.getVelocity(); 118 Vector2f acc = new Vector2f(vel); 119 if (acc.length() != 0) { 120 acc.normalise(); 121 acc.scale(-1f); 122 acc.scale(amount * ship.getDeceleration()); 123 Vector2f.add(vel, acc, vel); 124 float speed = vel.length(); 125 if (speed <= 1 || speed < acc.length()) { 126 vel.set(0, 0); 127 } 128 } 129 130 float alpha = 1f; 131 if (progress > 0.5f) { 132 alpha = (1f - progress) * 2f; 133 } 134 135 ship.setAlphaMult(alpha); 136 137 if (first) { 138 Global.getSoundPlayer().playLoop("reclamation_loop", ship, 1f, 1f, ship.getLocation(), ship.getVelocity()); 139 first = false; 140 } 141 142 if (false) { 143 float jitterLevel = 1f - progress; 144 if (fadeOutTime <= 4f) { 145 if (jitterLevel < 0.5f) { 146 jitterLevel *= 2f; 147 } else { 148 jitterLevel = (1f - jitterLevel) * 2f; 149 } 150 } else { 151 if (jitterLevel < 0.5f) { 152 jitterLevel *= 2f; 153 } else if (remaining <= 2f) { 154 jitterLevel = remaining / 2f; 155 } else { 156 jitterLevel = 1f; 157 } 158 } 159 jitterLevel = (float) Math.sqrt(jitterLevel); 160 161 //float jitterRange = 1f - progress; 162 float jitterRange = 1f; 163 if (remaining < 2f) { 164 jitterRange = remaining / 2f; 165 } else { 166 jitterRange = (elapsed - delay) / Math.max(1f, fadeOutTime - 2f); 167 } 168 float maxRangeBonus = 25f; 169 float jitterRangeBonus = jitterRange * maxRangeBonus; 170 171 Color c = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 172 c = Misc.setAlpha(c, 127); 173 174 ship.setJitter(this, c, jitterLevel, 3, 0f, jitterRangeBonus); 175 } 176 177 spawnParticles(ship, amount, progress); 178 } 179 180 if (elapsed > fadeOutTime + delay || !anyInEngine) { 181 for (ShipAPI ship : pieces) { 182 engine.removeEntity(ship); 183 ship.setAlphaMult(0f); 184 } 185 engine.removePlugin(this); 186 } 187 } 188 189 protected void spawnParticles(ShipAPI ship, float amount, float progress) { 190 if (ship == null) return; 191 192 float remaining = fadeOutTime - (elapsed - delay); 193 194 interval.advance(amount); 195 if (interval.intervalElapsed()) { 196 CombatEngineAPI engine = Global.getCombatEngine(); 197 198 Color c = RiftLanceEffect.getColorForDarkening(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR); 199 c = Misc.setAlpha(c, 50); 200 float baseDuration = 2f; 201 Vector2f vel = new Vector2f(ship.getVelocity()); 202 //float size = ship.getCollisionRadius() * 0.35f; 203 float size = ship.getCollisionRadius() * 0.33f; 204 205 float extraDur = 0f; 206 if (remaining < 1f) extraDur = 1f; 207 208 //for (int i = 0; i < 3; i++) { 209 for (int i = 0; i < 11; i++) { 210 Vector2f point = new Vector2f(ship.getLocation()); 211 point = Misc.getPointWithinRadiusUniform(point, ship.getCollisionRadius() * 0.75f, Misc.random); 212 float dur = baseDuration + baseDuration * (float) Math.random(); 213 dur += extraDur; 214 float nSize = size; 215 Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f); 216 Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 217 v.scale(nSize + nSize * (float) Math.random() * 0.5f); 218 v.scale(0.2f); 219 Vector2f.add(vel, v, v); 220 221 float maxSpeed = nSize * 1.5f * 0.2f; 222 float minSpeed = nSize * 1f * 0.2f; 223 float overMin = v.length() - minSpeed; 224 if (overMin > 0) { 225 float durMult = 1f - overMin / (maxSpeed - minSpeed); 226 if (durMult < 0.1f) durMult = 0.1f; 227 dur *= 0.5f + 0.5f * durMult; 228 } 229 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f, 230 0.5f / dur, 0f, dur, c); 231 } 232 } 233 } 234 235 protected void spawnSwarms(float amount) { 236 if (!spawnedSwarms) { 237 int numSwarms = 3; 238 239 for (SwarmConstructableVariant curr : ConstructionSwarmSystemScript.CONSTRUCTABLE) { 240 if (curr.variantId.equals(primary.getVariant().getHullVariantId())) { 241 numSwarms = (int) Math.round(curr.cr * 100f); 242 break; 243 } 244 } 245 246 for (int i = 0; i < numSwarms; i++) { 247 ShipAPI curr = launchSwarm(); 248 swarms.add(curr); 249 } 250 spawnedSwarms = true; 251 } 252 253 interval2.advance(amount * 2f); 254 if (interval2.intervalElapsed()) { 255 WeightedRandomPicker<ShipAPI> picker = new WeightedRandomPicker<>(); 256 picker.addAll(pieces); 257 258 for (ShipAPI curr : swarms) { 259 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(curr); 260 if (swarm == null) continue; 261 if (swarm.getNumActiveMembers() > swarm.params.baseMembersToMaintain) continue; 262 263 SwarmMember p = swarm.addMember(); 264 265 ShipAPI piece = picker.pick(); 266 if (piece == null) continue; 267 268 Vector2f loc = Misc.getPointWithinRadius(piece.getLocation(), piece.getCollisionRadius() * 0.5f); 269 p.loc.set(loc); 270 p.fader.setDurationIn(0.3f); 271 } 272 273 } 274 } 275 276 protected ShipAPI launchSwarm() { 277 String wingId = SwarmLauncherEffect.RECLAMATION_SWARM_WING; 278 279 CombatEngineAPI engine = Global.getCombatEngine(); 280 CombatFleetManagerAPI manager = engine.getFleetManager(primary.getOriginalOwner()); 281 manager.setSuppressDeploymentMessages(true); 282 283 Vector2f loc = primary.getLocation(); 284 float facing = (float) Math.random() * 360f; 285 286 ShipAPI fighter = manager.spawnShipOrWing(wingId, loc, facing, 0f, null); 287 fighter.getWing().setSourceShip(primary); 288 289 manager.setSuppressDeploymentMessages(false); 290 291 fighter.getMutableStats().getMaxSpeed().modifyMult("construction_swarm", RECLAMATION_SWARM_SPEED_MULT); 292 293 Vector2f takeoffVel = Misc.getUnitVectorAtDegreeAngle(facing); 294 takeoffVel.scale(fighter.getMaxSpeed() * 1f); 295 296 fighter.setDoNotRender(true); 297 fighter.setExplosionScale(0f); 298 fighter.setHulkChanceOverride(0f); 299 fighter.setImpactVolumeMult(SwarmLauncherEffect.IMPACT_VOLUME_MULT); 300 fighter.getArmorGrid().clearComponentMap(); // no damage to weapons/engines 301 Vector2f.add(fighter.getVelocity(), takeoffVel, fighter.getVelocity()); 302 303 RoilingSwarmEffect swarm = FragmentSwarmHullmod.createSwarmFor(fighter); 304 RoilingSwarmEffect.getFlockingMap().remove(swarm.params.flockingClass, swarm); 305 swarm.params.flockingClass = FragmentSwarmHullmod.RECLAMATION_SWARM_FLOCKING_CLASS; 306 RoilingSwarmEffect.getFlockingMap().add(swarm.params.flockingClass, swarm); 307 swarm.params.memberExchangeClass = FragmentSwarmHullmod.RECLAMATION_SWARM_EXCHANGE_CLASS; 308 309 310 //swarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 311 swarm.params.flashFrequency = 5f; 312 swarm.params.flashProbability = 1f; 313 314 // brownish/rusty 315 //swarm.params.flashFringeColor = new Color(255,95,50,50); 316 317 swarm.params.flashFringeColor = new Color(255,70,30,50); 318 swarm.params.flashCoreRadiusMult = 0f; 319 320 swarm.params.springStretchMult = 1f; 321 322 //swarm.params.baseSpriteSize *= RECLAMATION_SWARM_FRAGMENT_SIZE_MULT; 323 //swarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 324 325 float collisionMult = RECLAMATION_SWARM_COLLISION_MULT; 326 float hpMult = RECLAMATION_SWARM_HP_MULT; 327 328 for (SegmentAPI s : fighter.getExactBounds().getOrigSegments()) { 329 s.getP1().scale(collisionMult); 330 s.getP2().scale(collisionMult); 331 s.set(s.getP1().x, s.getP1().y, s.getP2().x, s.getP2().y); 332 } 333 fighter.setCollisionRadius(fighter.getCollisionRadius() * collisionMult); 334 335 fighter.setMaxHitpoints(fighter.getMaxHitpoints() * hpMult); 336 fighter.setHitpoints(fighter.getHitpoints() * hpMult); 337 338 swarm.params.maxOffset *= RECLAMATION_SWARM_RADIUS_MULT; 339 340 swarm.params.initialMembers = 0; 341 swarm.params.baseMembersToMaintain = 50; 342 343// int transfer = Math.min(numFragments, sourceSwarm.getNumActiveMembers()); 344// if (transfer > 0) { 345// loc = new Vector2f(takeoffVel); 346// loc.scale(0.5f); 347// Vector2f.add(loc, fighter.getLocation(), loc); 348// sourceSwarm.transferMembersTo(swarm, transfer, loc, 100f); 349// } 350// 351// int add = numFragments - transfer; 352// if (add > 0) { 353// swarm.addMembers(add); 354// } 355 356 return fighter; 357 358 } 359} 360 361 362 363 364 365 366 367