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.campaign.FactionAPI; 012import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 013import com.fs.starfarer.api.combat.CollisionClass; 014import com.fs.starfarer.api.combat.CombatEngineAPI; 015import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 016import com.fs.starfarer.api.combat.DamageType; 017import com.fs.starfarer.api.combat.ShipAPI; 018import com.fs.starfarer.api.combat.ShipCommand; 019import com.fs.starfarer.api.combat.WeaponGroupAPI; 020import com.fs.starfarer.api.impl.campaign.ids.Factions; 021import com.fs.starfarer.api.impl.combat.RiftLanceEffect; 022import com.fs.starfarer.api.impl.combat.threat.RoilingSwarmEffect.SwarmMember; 023import com.fs.starfarer.api.input.InputEventAPI; 024import com.fs.starfarer.api.util.IntervalUtil; 025import com.fs.starfarer.api.util.Misc; 026 027public class ThreatShipConstructionScript extends BaseEveryFrameCombatPlugin { 028 029 public static String SWARM_CONSTRUCTING_SHIP = "swarm_constructing_ship"; 030 public static String SHIP_UNDER_CONSTRUCTION = "ship_under_construction"; 031 032 public static float FADE_IN_RATE_MULT_WHEN_DESTROYED = 10f; 033 034 protected float elapsed = 0f; 035 protected ShipAPI ship = null; 036 protected CollisionClass collisionClass; 037 038 protected String variantId; 039 protected ShipAPI source; 040 protected float delay; 041 protected float fadeInTime; 042 protected float origMaxSpeed = 500f; 043 protected List<ShipAPI> explodedPieces = new ArrayList<>(); 044 045 protected IntervalUtil interval = new IntervalUtil(0.075f, 0.125f); 046 047 public ThreatShipConstructionScript(String variantId, ShipAPI source, float delay, float fadeInTime) { 048 this.variantId = variantId; 049 this.source = source; 050 this.delay = delay; 051 this.fadeInTime = fadeInTime; 052 053 interval.forceIntervalElapsed(); 054 055 spawnShip(); 056 } 057 058 public ShipAPI getShip() { 059 return ship; 060 } 061 062 protected void spawnShip() { 063 float facing = source.getFacing() + 15f * ((float) Math.random() - 0.5f); 064 065 Vector2f loc = new Vector2f(source.getLocation()); 066 067 CombatEngineAPI engine = Global.getCombatEngine(); 068 CombatFleetManagerAPI fleetManager = engine.getFleetManager(source.getOriginalOwner()); 069 boolean wasSuppressed = fleetManager.isSuppressDeploymentMessages(); 070 fleetManager.setSuppressDeploymentMessages(true); 071 072 ship = engine.getFleetManager(source.getOriginalOwner()).spawnShipOrWing(variantId, loc, facing, 0f, null); 073 if (Global.getCombatEngine().isInCampaign() || Global.getCombatEngine().isInCampaignSim()) { 074 FactionAPI faction = Global.getSector().getFaction(Factions.THREAT); 075 if (faction != null) { 076 String name = faction.pickRandomShipName(); 077 ship.setName(name); 078 } 079 } 080 fleetManager.setSuppressDeploymentMessages(wasSuppressed); 081 collisionClass = ship.getCollisionClass(); 082 083 084 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 085 RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(source); 086 if (swarm != null) { 087 swarm.params.withInitialMembers = false; 088 swarm.params.withRespawn = false; 089 } 090 if (sourceSwarm != null) { 091 origMaxSpeed = sourceSwarm.params.maxSpeed; 092// sourceSwarm.params.maxSpeed *= 0.25f; 093 sourceSwarm.params.outspeedAttachedEntityBy = 0f; 094 if (swarm != null) { 095 swarm.params.withInitialMembers = false; 096 swarm.params.flashFringeColor = sourceSwarm.params.flashFringeColor; 097 } 098 } 099 100 ship.addTag(SHIP_UNDER_CONSTRUCTION); 101 source.addTag(SWARM_CONSTRUCTING_SHIP); 102 source.setCollisionClass(CollisionClass.NONE); 103 source.getMutableStats().getHullDamageTakenMult().modifyMult("ThreatShipConstructionScript", 0f); 104 105 ship.setShipAI(null); 106 for (WeaponGroupAPI g : ship.getWeaponGroupsCopy()) { 107 g.toggleOff(); 108 } 109 } 110 111 protected float hulkFor = 0f; 112 113 @Override 114 public void advance(float amount, List<InputEventAPI> events) { 115 if (Global.getCombatEngine().isPaused()) return; 116 117 if (ship.isHulk()) { 118 hulkFor += amount; 119 amount *= FADE_IN_RATE_MULT_WHEN_DESTROYED; 120 // ship splitting into pieces doesn't happen immediately 121 if (explodedPieces.isEmpty() || hulkFor < 0.25f) { 122 explodedPieces.clear(); 123 for (ShipAPI curr : Global.getCombatEngine().getShips()) { 124 if (curr.getFleetMember() == ship.getFleetMember() || 125 (curr.getParentPieceId() != null && curr.getParentPieceId().equals(ship.getId()))) { 126 explodedPieces.add(curr); 127 } 128 } 129 } 130 //elapsed += delay + fadeInTime; // instant fade in while hidden by the explosion 131 } 132 elapsed += amount; 133 if (elapsed < delay) return; 134 135 CombatEngineAPI engine = Global.getCombatEngine(); 136 137 float progress = (elapsed - delay) / fadeInTime; 138 if (progress > 1f) progress = 1f; 139 140 float remaining = fadeInTime - (elapsed - delay); 141 142 ship.setAlphaMult(progress); 143// if (!explodedPieces.isEmpty()) { 144// System.out.println("Pieces: " + explodedPieces.size()); 145// } 146 for (ShipAPI curr : explodedPieces) { 147 curr.setAlphaMult(progress); 148 } 149 ship.getMutableStats().getEffectiveArmorBonus().modifyMult("ThreatShipConstructionScript", progress * progress); 150 151 Global.getSoundPlayer().playLoop("construction_swarm_loop", ship, 1f, 1f, ship.getLocation(), ship.getVelocity()); 152 153 154 155 if (remaining > 1f) { 156 Vector2f deltaLoc = Vector2f.sub(ship.getLocation(), source.getLocation(), new Vector2f()); 157 source.getLocation().set(ship.getLocation()); 158 RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(source); 159 if (sourceSwarm != null) { 160 for (SwarmMember p : sourceSwarm.members) { 161 Vector2f.add(p.loc, deltaLoc, p.loc); 162 } 163 } 164 ship.giveCommand(ShipCommand.DECELERATE, null, 0); 165 } 166 167 float jitterLevel = progress; 168 if (fadeInTime <= 4f) { 169 if (jitterLevel < 0.5f) { 170 jitterLevel *= 2f; 171 } else { 172 jitterLevel = (1f - jitterLevel) * 2f; 173 } 174 } else { 175 if (jitterLevel < 0.5f) { 176 jitterLevel *= 2f; 177 } else if (remaining <= 2f) { 178 jitterLevel = remaining / 2f; 179 } else { 180 jitterLevel = 1f; 181 } 182 } 183 jitterLevel = (float) Math.sqrt(jitterLevel); 184 185 //float jitterRange = 1f - progress; 186 float jitterRange = 1f; 187 if (remaining < 2f) { 188 jitterRange = remaining / 2f; 189 } else { 190 jitterRange = (elapsed - delay) / Math.max(1f, fadeInTime - 2f); 191 } 192 float maxRangeBonus = 25f; 193 float jitterRangeBonus = jitterRange * maxRangeBonus; 194 195 Color c = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 196 c = Misc.setAlpha(c, 127); 197 198 ship.setJitter(this, c, jitterLevel, 3, 0f, jitterRangeBonus); 199 ship.getEngineController().fadeToOtherColor(this, Misc.zeroColor, Misc.zeroColor, 1f, 1f); 200 201 202 RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(source); 203 if (sourceSwarm != null) { 204 float speedMult = 0.25f + 0.75f * Math.max(0f, 1f - (elapsed - delay) / 2f); 205 sourceSwarm.params.maxSpeed = origMaxSpeed * speedMult; 206 207 if (remaining > 3f) { 208 float numFragMult = sourceSwarm.params.initialMembers / 150f; 209 if (numFragMult < 0.25f) numFragMult = 0.25f; 210 if (numFragMult > 1f) numFragMult = 1f; 211 sourceSwarm.params.flashFrequency = 5f * progress * 2f * numFragMult; 212 sourceSwarm.params.flashProbability = 1f; 213 sourceSwarm.params.flashFringeColor = VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR; 214 sourceSwarm.params.flashFringeColor = Misc.setAlpha(sourceSwarm.params.flashFringeColor, 200); 215 //sourceSwarm.params.flashCoreRadiusMult = 0f; 216 sourceSwarm.params.flashCoreRadiusMult = 1.5f; 217 sourceSwarm.params.flashRadius = 50f; 218 sourceSwarm.params.renderFlashOnSameLayer = true; 219 } else { 220 sourceSwarm.params.flashFrequency = 1f; 221 sourceSwarm.params.flashProbability = 0f; 222 } 223 } 224 225 226 spawnParticles(amount); 227 228 229 if (elapsed > fadeInTime + delay) { 230 ship.setDefaultAI(null); 231 ship.removeTag(SHIP_UNDER_CONSTRUCTION); 232 ship.setAlphaMult(1f); 233 ship.setHoldFire(false); 234 ship.setCollisionClass(collisionClass); 235 ship.getMutableStats().getEffectiveArmorBonus().unmodifyMult("ThreatShipConstructionScript"); 236 engine.removePlugin(this); 237 238 if (sourceSwarm != null) { 239 sourceSwarm.getParams().despawnSound = null; 240 } 241 242 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(ship); 243 //RoilingSwarmEffect sourceSwarm = RoilingSwarmEffect.getSwarmFor(source); 244 if (swarm != null && sourceSwarm != null) { 245 int transfer = Math.min(swarm.params.baseMembersToMaintain, sourceSwarm.getNumActiveMembers()); 246 sourceSwarm.transferMembersTo(swarm, transfer); 247 } 248 if (swarm != null) { 249 swarm.params.withRespawn = true; 250 swarm.params.withInitialMembers = true; 251 } 252 253 source.setHitpoints(0f); 254 source.setSpawnDebris(false); 255 engine.applyDamage(source, source.getLocation(), 100f, DamageType.ENERGY, 0f, true, false, source, false); 256 } 257 } 258 259 protected void spawnParticles(float amount) { 260 if (ship == null) return; 261 262 float remaining = fadeInTime - (elapsed - delay); 263 264 interval.advance(amount); 265 if (interval.intervalElapsed() && remaining > 1f) { 266 267 RoilingSwarmEffect swarm = RoilingSwarmEffect.getSwarmFor(source); 268 if (swarm != null) { 269 for (SwarmMember p : swarm.members) { 270 if ((float) Math.random() > 0.9f) { 271 p.rollOffset(swarm.params, ship); 272 } 273 } 274 } 275 276 CombatEngineAPI engine = Global.getCombatEngine(); 277 278 Color c = RiftLanceEffect.getColorForDarkening(VoltaicDischargeOnFireEffect.EMP_FRINGE_COLOR); 279 c = Misc.setAlpha(c, 50); 280 float baseDuration = 2f; 281 Vector2f vel = new Vector2f(ship.getVelocity()); 282 //float size = ship.getCollisionRadius() * 0.35f; 283 float size = ship.getCollisionRadius() * 0.33f; 284 285 float extraDur = 0f; 286 if (remaining < 1f) extraDur = 1f; 287 288 //for (int i = 0; i < 3; i++) { 289 for (int i = 0; i < 11; i++) { 290 Vector2f point = new Vector2f(ship.getLocation()); 291 point = Misc.getPointWithinRadiusUniform(point, ship.getCollisionRadius() * 0.75f, Misc.random); 292 float dur = baseDuration + baseDuration * (float) Math.random(); 293 dur += extraDur; 294 float nSize = size; 295 Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f); 296 Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 297 v.scale(nSize + nSize * (float) Math.random() * 0.5f); 298 v.scale(0.2f); 299 Vector2f.add(vel, v, v); 300 301 float maxSpeed = nSize * 1.5f * 0.2f; 302 float minSpeed = nSize * 1f * 0.2f; 303 float overMin = v.length() - minSpeed; 304 if (overMin > 0) { 305 float durMult = 1f - overMin / (maxSpeed - minSpeed); 306 if (durMult < 0.1f) durMult = 0.1f; 307 dur *= 0.5f + 0.5f * durMult; 308 } 309 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f, 310 0.5f / dur, 0f, dur, c); 311 } 312 } 313 } 314 315 316}