001package com.fs.starfarer.api.impl.hullmods; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.HashMap; 006import java.util.Iterator; 007import java.util.List; 008import java.util.Map; 009 010import org.lwjgl.util.vector.Vector2f; 011 012import com.fs.starfarer.api.Global; 013import com.fs.starfarer.api.campaign.FactionAPI; 014import com.fs.starfarer.api.characters.PersonAPI; 015import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 016import com.fs.starfarer.api.combat.BaseHullMod; 017import com.fs.starfarer.api.combat.CollisionClass; 018import com.fs.starfarer.api.combat.CombatEngineAPI; 019import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 020import com.fs.starfarer.api.combat.DeployedFleetMemberAPI; 021import com.fs.starfarer.api.combat.EveryFrameCombatPlugin; 022import com.fs.starfarer.api.combat.MutableShipStatsAPI; 023import com.fs.starfarer.api.combat.ShieldAPI; 024import com.fs.starfarer.api.combat.ShieldAPI.ShieldType; 025import com.fs.starfarer.api.combat.ShipAPI; 026import com.fs.starfarer.api.combat.ShipAPI.HullSize; 027import com.fs.starfarer.api.combat.ShipCommand; 028import com.fs.starfarer.api.impl.campaign.ids.Factions; 029import com.fs.starfarer.api.impl.campaign.ids.Personalities; 030import com.fs.starfarer.api.impl.campaign.ids.Skills; 031import com.fs.starfarer.api.impl.campaign.ids.Tags; 032import com.fs.starfarer.api.impl.combat.RiftCascadeEffect; 033import com.fs.starfarer.api.impl.combat.RiftLanceEffect; 034import com.fs.starfarer.api.input.InputEventAPI; 035import com.fs.starfarer.api.loading.FighterWingSpecAPI; 036import com.fs.starfarer.api.util.IntervalUtil; 037import com.fs.starfarer.api.util.Misc; 038import com.fs.starfarer.api.util.WeightedRandomPicker; 039 040public class ShardSpawner extends BaseHullMod { 041 042 public static Color JITTER_COLOR = new Color(100,100,255,50); 043 public static String DATA_KEY = "core_shard_spawner_data_key"; 044 045 public static float SPAWN_TIME = 4f; 046 047 public static enum ShardType { 048 GENERAL, 049 ANTI_ARMOR, 050 ANTI_SHIELD, 051 POINT_DEFENSE, 052 MISSILE, 053 } 054 055 public static class ShardTypeVariants { 056 public Map<ShardType, WeightedRandomPicker<String>> variants = new HashMap<ShardType, WeightedRandomPicker<String>>(); 057 public ShardTypeVariants() { 058 } 059 public WeightedRandomPicker<String> get(ShardType type) { 060 WeightedRandomPicker<String> result = variants.get(type); 061 if (result == null) { 062 result = new WeightedRandomPicker<String>(); 063 variants.put(type, result); 064 } 065 return result; 066 } 067 } 068 069 public static Map<HullSize, ShardTypeVariants> variantData = new HashMap<HullSize, ShardTypeVariants>(); 070 static { 071 ShardTypeVariants fighters = new ShardTypeVariants(); 072 variantData.put(HullSize.FIGHTER, fighters); 073 fighters.get(ShardType.GENERAL).add("aspect_attack_wing", 10f); 074 fighters.get(ShardType.GENERAL).add("aspect_missile_wing", 1f); 075 076 fighters.get(ShardType.MISSILE).add("aspect_missile_wing", 10f); 077 078 fighters.get(ShardType.ANTI_ARMOR).add("aspect_attack_wing", 10f); 079 080 fighters.get(ShardType.ANTI_SHIELD).add("aspect_shieldbreaker_wing", 10f); 081 082 fighters.get(ShardType.POINT_DEFENSE).add("aspect_shock_wing", 10f); 083 084 085 ShardTypeVariants small = new ShardTypeVariants(); 086 variantData.put(HullSize.FRIGATE, small); 087 088 small.get(ShardType.GENERAL).add("shard_left_Attack", 10f); 089 small.get(ShardType.GENERAL).add("shard_left_Attack2", 10f); 090 small.get(ShardType.GENERAL).add("shard_right_Attack", 10f); 091 small.get(ShardType.GENERAL).add("aspect_attack_wing", 10f); 092 small.get(ShardType.GENERAL).add("aspect_missile_wing", 1f); 093 094 small.get(ShardType.ANTI_ARMOR).add("shard_left_Armorbreaker", 10f); 095 096 small.get(ShardType.ANTI_SHIELD).add("shard_left_Shieldbreaker", 10f); 097 small.get(ShardType.ANTI_SHIELD).add("shard_right_Shieldbreaker", 10f); 098 //small.get(ShardType.ANTI_SHIELD).add("aspect_shieldbreaker_wing", 10f); 099 100 small.get(ShardType.POINT_DEFENSE).add("shard_left_Defense", 10f); 101 small.get(ShardType.POINT_DEFENSE).add("shard_right_Shock", 10f); 102 //small.get(ShardType.POINT_DEFENSE).add("aspect_shock_wing", 10f); 103 104 small.get(ShardType.MISSILE).add("shard_left_Missile", 10f); 105 small.get(ShardType.MISSILE).add("shard_right_Missile", 10f); 106 //small.get(ShardType.MISSILE).add("aspect_missile_wing", 10f); 107 108 109 ShardTypeVariants medium = new ShardTypeVariants(); 110 variantData.put(HullSize.DESTROYER, medium); 111 112 medium.get(ShardType.GENERAL).add("facet_Attack"); 113 medium.get(ShardType.GENERAL).add("facet_Attack2"); 114 115 medium.get(ShardType.ANTI_ARMOR).add("facet_Armorbreaker"); 116 117 medium.get(ShardType.ANTI_SHIELD).add("facet_Shieldbreaker"); 118 119 medium.get(ShardType.POINT_DEFENSE).add("facet_Defense"); 120 121 medium.get(ShardType.MISSILE).add("facet_Missile"); 122 123 ShardTypeVariants large = new ShardTypeVariants(); 124 variantData.put(HullSize.CRUISER, large); 125 126 large.get(ShardType.GENERAL).add("tesseract_Attack"); 127 large.get(ShardType.GENERAL).add("tesseract_Attack2"); 128 large.get(ShardType.GENERAL).add("tesseract_Strike"); 129 large.get(ShardType.GENERAL).add("tesseract_Disruptor"); 130 131 large.get(ShardType.ANTI_ARMOR).add("tesseract_Disruptor"); 132 large.get(ShardType.ANTI_ARMOR).add("tesseract_Strike"); 133 134 large.get(ShardType.ANTI_SHIELD).add("tesseract_Shieldbreaker"); 135 136 large.get(ShardType.POINT_DEFENSE).add("tesseract_Defense"); 137 138 large.get(ShardType.MISSILE).add("tesseract_Strike"); 139 } 140 141 public static class ShardSpawnerData { 142 boolean done = false; 143 float delay = 2f + (float) Math.random() * 1f; 144 } 145 146 public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) { 147 stats.getBreakProb().modifyMult(id, 0f); 148 } 149 150 @Override 151 public void advanceInCombat(ShipAPI ship, float amount) { 152 CombatEngineAPI engine = Global.getCombatEngine(); 153 if (ship.getOriginalOwner() != 0) { 154 engine.setCombatNotOverForAtLeast(SPAWN_TIME + 1f); 155 } 156 157 if (!ship.isHulk() || !engine.isEntityInPlay(ship)) return; 158 159 String key = DATA_KEY + "_" + ship.getId(); 160 ShardSpawnerData data = (ShardSpawnerData) engine.getCustomData().get(key); 161 if (data == null) { 162 data = new ShardSpawnerData(); 163 engine.getCustomData().put(key, data); 164 } 165 166 if (data.done) return; 167 168 169 ship.setHitpoints(ship.getMaxHitpoints()); 170 ship.getMutableStats().getHullDamageTakenMult().modifyMult("ShardSpawnerInvuln", 0f); 171 data.delay -= amount; 172 if (data.delay > 0) return; 173 174 //ship.setCollisionClass(CollisionClass.NONE); 175 float dur = SPAWN_TIME; 176 float extraDur = 0f; 177 178 179 float splitWeight = 0f; 180 181 float probNothingAtAll = 0f; 182 //float forceFighterProb = 0f; 183 float cruiserProb = 0f; 184 float cruiserProbMult = 0f; 185 float maxCruisers = 0f; 186 float destroyerProb = 0f; 187 float destroyerProbMult = 0f; 188 float maxDestroyers = 0f; 189 float frigateProb = 0f; 190 float frigateProbMult = 0f; 191 float maxFrigates = 0f; 192 193 if (ship.isCapital()) { 194 splitWeight = 12f; 195 196 cruiserProb = 1f; 197 cruiserProbMult = 0.5f; 198 maxCruisers = 1f; 199 destroyerProb = 1f; 200 destroyerProbMult = 1f; 201 maxDestroyers = 2f; 202 frigateProb = 1f; 203 frigateProbMult = 1f; 204 maxFrigates = 3f; 205 } else if (ship.isCruiser()) { 206// splitWeight = 7f; 207// 208// destroyerProb = 1f; 209// destroyerProbMult = 0.5f; 210// maxDestroyers = 1f; 211// frigateProb = 1f; 212// frigateProbMult = 0.67f; 213// maxFrigates = 3f; 214 215 splitWeight = 7f; 216 217 destroyerProb = 1f; 218 destroyerProbMult = 0.5f; 219 maxDestroyers = 1f; 220 frigateProb = 1f; 221 frigateProbMult = 1f; 222 maxFrigates = 2f; 223 } else if (ship.isDestroyer()) { 224// splitWeight = 4f; 225// 226// frigateProb = 1f; 227// frigateProbMult = 0.75f; 228// maxFrigates = 3f; 229 230 splitWeight = 4f; 231 232 frigateProb = 1f; 233 frigateProbMult = 1f; 234 maxFrigates = 2f; 235 } else if (ship.isFrigate()) { 236 splitWeight = 2f; 237 } 238 WeightedRandomPicker<ShardType> typePicker = getTypePickerBasedOnLocalConditions(ship); 239 240// splitWeight = 8f; 241// destroyerProb = 0.8f; 242// maxDestroyers = 2f; 243// //maxDestroyers = 0f; 244// 245// splitWeight = 1f; 246 247 if ((float) Math.random() < probNothingAtAll) { 248 splitWeight = 0f; 249 } 250 251 WeightedRandomPicker<Float> spawnAngles = new WeightedRandomPicker<Float>(); 252 int spawnAnglesIter = 0; 253 float angleOffset = (float) Math.random() * 360f; 254 255 float addedWeight = 0f; 256 float fighters = 0f; 257 float frigates = 0f; 258 float destroyers = 0f; 259 float cruisers = 0f; 260 List<ShardFadeInPlugin> shards = new ArrayList<ShardFadeInPlugin>(); 261 while (addedWeight < splitWeight) { 262 ShardType type = typePicker.pick(); 263 264 float rem = splitWeight - addedWeight; 265 boolean cruiser = (float) Math.random() < cruiserProb && cruisers < maxCruisers && rem >= 3.5f; 266 boolean destroyer = (float) Math.random() < destroyerProb && destroyers < maxDestroyers && rem >= 1.5f; 267 boolean frigate = (float) Math.random() < frigateProb && frigates < maxFrigates; 268 //boolean fighter = (float) Math.random() < forceFighterProb && fighters < maxFromFightersOnlyCategory; 269 String variant = null; 270 float weight = 1f; 271 272 if (cruiser) { 273 ShardTypeVariants variants = variantData.get(HullSize.CRUISER); 274 WeightedRandomPicker<String> variantPicker = variants.get(type); 275 variant = variantPicker.pick(); 276 if (variant != null) { 277 weight = 4f; 278 cruisers++; 279 cruiserProb *= cruiserProbMult; 280 } 281 } 282 283 if (destroyer && variant == null) { 284 ShardTypeVariants variants = variantData.get(HullSize.DESTROYER); 285 WeightedRandomPicker<String> variantPicker = variants.get(type); 286 variant = variantPicker.pick(); 287 if (variant != null) { 288 weight = 2f; 289 destroyers++; 290 destroyerProb *= destroyerProbMult; 291 } 292 } 293 294 if (frigate && variant == null) { 295 ShardTypeVariants variants = variantData.get(HullSize.FRIGATE); 296 WeightedRandomPicker<String> variantPicker = variants.get(type); 297 variant = variantPicker.pick(); 298 if (variant != null) { 299 weight = 1f; 300 frigates++; 301 frigateProb *= frigateProbMult; 302 } 303 } 304 305 if (variant == null) { 306 ShardTypeVariants variants = variantData.get(HullSize.FIGHTER); 307 WeightedRandomPicker<String> variantPicker = variants.get(type); 308 variant = variantPicker.pick(); 309 if (variant != null) { 310 fighters++; 311 } 312 } 313 314 //variant = "aspect_shock_wing"; 315 //variant = "aspect_shieldbreaker_wing"; 316 //variant = "aspect_missile_wing"; 317 if (variant != null) { 318 if (spawnAngles == null || spawnAngles.isEmpty()) { 319 spawnAngles = getSpawnAngles(spawnAnglesIter++); 320 } 321 float angle = spawnAngles.pickAndRemove() + angleOffset; 322 ShardFadeInPlugin shard = createShipFadeInPlugin(variant, ship, extraDur, dur, angle); 323 shards.add(shard); 324 Global.getCombatEngine().addPlugin(shard); 325 addedWeight += weight; 326 327// float delay = 0.5f + (float) Math.random() * 0.5f; 328// extraDur += delay; 329 } else { 330 addedWeight += 0.1f; // if we didn't manage to add anything, eventually break out of the loop 331 } 332 } 333 334 Global.getCombatEngine().addPlugin(createShipFadeOutPlugin(ship, dur + extraDur * 0.5f, shards)); 335 336// Global.getCombatEngine().addPlugin(createShipFadeInPlugin("shard_left_Attack", ship, 0f, dur)); 337// Global.getCombatEngine().addPlugin(createShipFadeInPlugin("shard_right_Attack", ship, 0f, dur)); 338 data.done = true; 339 } 340 341 public WeightedRandomPicker<Float> getSpawnAngles(int iter) { 342 WeightedRandomPicker<Float> picker = new WeightedRandomPicker<Float>(); 343 float start = 0f; 344 float incr = 60f; 345 if (iter == 1) { 346 start = 30f; 347 } else if (iter == 2) { 348 start = 15f; 349 incr = 30f; 350 } else { 351 incr = 10f; 352 } 353 for (float i = start; i < 360f + start; i += incr) { 354 picker.add(i); 355 } 356 return picker; 357 } 358 359 360 361 public WeightedRandomPicker<ShardType> getTypePickerBasedOnLocalConditions(ShipAPI ship) { 362 CombatEngineAPI engine = Global.getCombatEngine(); 363 float checkRadius = 5000; 364 Iterator<Object> iter = engine.getAiGridShips().getCheckIterator(ship.getLocation(), checkRadius * 2f, checkRadius * 2f); 365 366 float weightFighters = 0f; 367 float weightGoodShields = 0f; 368 float weightGoodArmor = 0f; 369 float weightVulnerable = 0f; 370 float weightCarriers = 0f; 371 372 float weightEnemies = 0f; 373 float weightFriends = 0f; 374 375 while (iter.hasNext()) { 376 Object o = iter.next(); 377 if (o instanceof ShipAPI) { 378 ShipAPI other = (ShipAPI) o; 379 if (other.getOwner() == Misc.OWNER_NEUTRAL) continue; 380 381 boolean enemy = ship.getOwner() != other.getOwner(); 382 if (enemy) { 383 if (other.isFighter() || other.isDrone()) { 384 weightFighters += 0.25f; 385 weightEnemies += 0.25f; 386 } else { 387 float w = Misc.getShipWeight(other); 388 weightEnemies += w; 389 390 if (hasGoodShields(other)) { 391 weightGoodShields += w; 392 } 393 if (hasGoodArmor(other)) { 394 weightGoodArmor += w; 395 } 396 if (isVulnerableToMissileBarrage(ship, other)) { 397 weightVulnerable += w; 398 } 399 if (other.getVariant().isCarrier()) { 400 weightCarriers += w; 401 } 402 } 403 } else { 404 if (other.isFighter() || other.isDrone()) { 405 weightFriends += 0.25f; 406 } else { 407 float w = Misc.getShipWeight(other); 408 weightFriends += w; 409 } 410 } 411 } 412 } 413 414 WeightedRandomPicker<ShardType> picker = new WeightedRandomPicker<ShardType>(); 415 416 float total = weightFighters + weightGoodShields + weightGoodArmor + weightVulnerable + weightCarriers; 417 if (total <= 1f) total = 1f; 418 419 float antiFighter = (weightFighters + weightCarriers) / total; 420 float antiShield = weightGoodShields / total; 421 float antiArmor = weightGoodArmor / total; 422 float missile = weightVulnerable / total; 423 424 float friends = weightFriends / Math.max(1f, weightEnemies + weightFriends); 425 426 picker.add(ShardType.GENERAL, 0.0f + (1f - friends) * 0.4f); 427// picker.add(ShardType.GENERAL, 0.2f); 428// if (friends < 0.3f) { 429// picker.add(ShardType.GENERAL, Math.min(0.25f, (1f - friends) * 0.25f)); 430// } 431 432 float unlikelyWeight = 0f; 433 float unlikelyThreshold = 0.2f; 434 435 if (antiFighter < unlikelyThreshold) antiFighter = unlikelyWeight; 436 picker.add(ShardType.POINT_DEFENSE, antiFighter); 437 438 if (antiShield < unlikelyThreshold) antiShield = unlikelyWeight; 439 picker.add(ShardType.ANTI_SHIELD, antiShield); 440 441 if (antiArmor < unlikelyThreshold) antiArmor = unlikelyWeight; 442 picker.add(ShardType.ANTI_ARMOR, antiArmor); 443 444 if (missile < unlikelyThreshold) missile = unlikelyWeight; 445 picker.add(ShardType.MISSILE, missile); 446 447 return picker; 448 } 449 450 public boolean isVulnerableToMissileBarrage(ShipAPI from, ShipAPI other) { 451 float incap = Misc.getIncapacitatedTime(other); 452 453 float dist = Misc.getDistance(from.getLocation(), other.getLocation()); 454 if (dist > 2000) return false; 455 456 float assumedMissileSpeed = 500; 457 float eta = dist / assumedMissileSpeed; 458 eta += SPAWN_TIME; 459 eta += 2f; 460 461 return incap >= eta || (other.getFluxLevel() >= 0.95f && other.getFluxTracker().getTimeToVent() >= eta); 462 } 463 464 public boolean hasGoodArmor(ShipAPI other) { 465 float requiredArmor = 1240; 466 467 if (other.getArmorGrid().getArmorRating() < requiredArmor) return false; 468 469 float armor = other.getAverageArmorInSlice(other.getFacing(), 120f); 470 return armor >= requiredArmor * 0.8f; 471 472 } 473 474 public boolean hasGoodShields(ShipAPI other) { 475 ShieldAPI shield = other.getShield(); 476 if (shield == null) return false; 477 if (shield.getType() == ShieldType.NONE) return false; 478 if (shield.getType() == ShieldType.PHASE) return false; 479 480 float requiredCapacity = 10000000f; 481 switch (other.getHullSize()) { 482 case CAPITAL_SHIP: 483 requiredCapacity = 25000; 484 if (shield.getType() == ShieldType.FRONT && shield.getArc() < 250) { 485 requiredCapacity = 1000000; 486 } 487 break; 488 case CRUISER: 489 requiredCapacity = 12500; 490 if (shield.getType() == ShieldType.FRONT && shield.getArc() < 250) { 491 requiredCapacity = 1000000; 492 } 493 break; 494 case DESTROYER: 495 requiredCapacity = 8000; 496 break; 497 case FRIGATE: 498 requiredCapacity = 4000; 499 break; 500 } 501 502 float e = other.getShield().getFluxPerPointOfDamage() * 503 other.getMutableStats().getShieldDamageTakenMult().getModifiedValue(); 504 float capacity = other.getMaxFlux(); 505 capacity /= Math.max(0.1f, e); 506 507 return capacity >= requiredCapacity && e <= 1f; 508 } 509 510 511 512 protected EveryFrameCombatPlugin createShipFadeOutPlugin(final ShipAPI ship, final float fadeOutTime, 513 final List<ShardFadeInPlugin> shards) { 514 return new BaseEveryFrameCombatPlugin() { 515 float elapsed = 0f; 516 IntervalUtil interval = new IntervalUtil(0.075f, 0.125f); 517 518 protected void pushShipsAway(float amount) { 519 Vector2f com = new Vector2f(); 520 float count = 0f; 521 for (ShardFadeInPlugin shard : shards) { 522 ShipAPI ship = shard.ships[0]; 523 if (ship.isFighter()) continue; 524 Vector2f.add(com, ship.getLocation(), com); 525 count++; 526 } 527 com.scale(1f / Math.max(1f, count)); 528 529 Vector2f comForFighters = new Vector2f(); 530 count = 0f; 531 for (ShardFadeInPlugin shard : shards) { 532 ShipAPI ship = shard.ships[0]; 533 if (!ship.isFighter()) continue; 534 Vector2f.add(comForFighters, ship.getLocation(), comForFighters); 535 count++; 536 } 537 comForFighters.scale(1f / Math.max(1f, count)); 538 539 float progress = elapsed / fadeOutTime; 540 if (progress > 1f) progress = 1f; 541 for (ShardFadeInPlugin shard : shards) { 542 ShipAPI ship = shard.ships[0]; 543 Vector2f currCom = com; 544 if (ship.isFighter()) currCom = comForFighters; 545 546 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(currCom, ship.getLocation())); 547 float speed = ship.getCollisionRadius() * 0.5f; 548 dir.scale(amount * speed * progress); 549 Vector2f.add(ship.getLocation(), dir, ship.getLocation()); 550 } 551 } 552 553 @Override 554 public void advance(float amount, List<InputEventAPI> events) { 555 if (Global.getCombatEngine().isPaused()) return; 556 557 elapsed += amount; 558 559 560 float progress = elapsed / fadeOutTime; 561 if (progress > 1f) progress = 1f; 562 ship.setAlphaMult(1f - progress); 563 564 //if (progress < 0.5f) { 565 pushShipsAway(amount); 566 //} 567 568 if (progress > 0.5f) { 569 ship.setCollisionClass(CollisionClass.NONE); 570 } 571 572 float jitterLevel = progress; 573 if (jitterLevel < 0.5f) { 574 jitterLevel *= 2f; 575 } else { 576 jitterLevel = (1f - jitterLevel) * 2f; 577 } 578 579 float jitterRange = progress; 580 //jitterRange = (float) Math.sqrt(jitterRange); 581 float maxRangeBonus = 100f; 582 float jitterRangeBonus = jitterRange * maxRangeBonus; 583 Color c = JITTER_COLOR; 584 int alpha = c.getAlpha(); 585 alpha += 100f * progress; 586 if (alpha > 255) alpha = 255; 587 c = Misc.setAlpha(c, alpha); 588 589 ship.setJitter(this, c, jitterLevel, 35, 0f, jitterRangeBonus); 590 591 interval.advance(amount); 592 if (interval.intervalElapsed() && elapsed < fadeOutTime * 0.75f) { 593 CombatEngineAPI engine = Global.getCombatEngine(); 594 c = RiftLanceEffect.getColorForDarkening(RiftCascadeEffect.STANDARD_RIFT_COLOR); 595 float baseDuration = 2f; 596 Vector2f vel = new Vector2f(ship.getVelocity()); 597 float size = ship.getCollisionRadius() * 0.35f; 598 for (int i = 0; i < 3; i++) { 599 Vector2f point = new Vector2f(ship.getLocation()); 600 point = Misc.getPointWithinRadiusUniform(point, ship.getCollisionRadius() * 0.5f, Misc.random); 601 float dur = baseDuration + baseDuration * (float) Math.random(); 602 float nSize = size; 603 Vector2f pt = Misc.getPointWithinRadius(point, nSize * 0.5f); 604 Vector2f v = Misc.getUnitVectorAtDegreeAngle((float) Math.random() * 360f); 605 v.scale(nSize + nSize * (float) Math.random() * 0.5f); 606 v.scale(0.2f); 607 Vector2f.add(vel, v, v); 608 609 float maxSpeed = nSize * 1.5f * 0.2f; 610 float minSpeed = nSize * 1f * 0.2f; 611 float overMin = v.length() - minSpeed; 612 if (overMin > 0) { 613 float durMult = 1f - overMin / (maxSpeed - minSpeed); 614 if (durMult < 0.1f) durMult = 0.1f; 615 dur *= 0.5f + 0.5f * durMult; 616 } 617 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f, 618 0.5f / dur, 0f, dur, c); 619 } 620 } 621 622 if (elapsed > fadeOutTime) { 623 ship.setHitpoints(0f); 624 Global.getCombatEngine().removeEntity(ship); 625 ship.setAlphaMult(0f); 626 Global.getCombatEngine().removePlugin(this); 627 } 628 } 629 }; 630 } 631 632 633 protected ShardFadeInPlugin createShipFadeInPlugin(final String variantId, final ShipAPI source, 634 final float delay, final float fadeInTime, final float angle) { 635 636 return new ShardFadeInPlugin(variantId, source, delay, fadeInTime, angle); 637 } 638 639 public static class ShardFadeInPlugin extends BaseEveryFrameCombatPlugin { 640 float elapsed = 0f; 641 ShipAPI [] ships = null; 642 CollisionClass collisionClass; 643 644 String variantId; 645 ShipAPI source; 646 float delay; 647 float fadeInTime; 648 float angle; 649 650 public ShardFadeInPlugin(String variantId, ShipAPI source, float delay, float fadeInTime, float angle) { 651 this.variantId = variantId; 652 this.source = source; 653 this.delay = delay; 654 this.fadeInTime = fadeInTime; 655 this.angle = angle; 656 657 } 658 659 660 @Override 661 public void advance(float amount, List<InputEventAPI> events) { 662 if (Global.getCombatEngine().isPaused()) return; 663 664 elapsed += amount; 665 if (elapsed < delay) return; 666 667 CombatEngineAPI engine = Global.getCombatEngine(); 668 669 if (ships == null) { 670 float facing = source.getFacing() + 15f * ((float) Math.random() - 0.5f); 671// Vector2f loc = new Vector2f(); 672// loc = Misc.getPointWithinRadius(loc, source.getCollisionRadius() * 0.25f); 673 Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle); 674 loc.scale(source.getCollisionRadius() * 0.1f); 675 Vector2f.add(loc, source.getLocation(), loc); 676 CombatFleetManagerAPI fleetManager = engine.getFleetManager(source.getOriginalOwner()); 677 boolean wasSuppressed = fleetManager.isSuppressDeploymentMessages(); 678 fleetManager.setSuppressDeploymentMessages(true); 679 if (variantId.endsWith("_wing")) { 680 FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(variantId); 681 ships = new ShipAPI[spec.getNumFighters()]; 682 PersonAPI captain = Global.getSettings().createPerson(); 683 captain.setPersonality(Personalities.RECKLESS); // doesn't matter for fighters 684 captain.getStats().setSkillLevel(Skills.POINT_DEFENSE, 2); 685 captain.getStats().setSkillLevel(Skills.GUNNERY_IMPLANTS, 2); 686 captain.getStats().setSkillLevel(Skills.IMPACT_MITIGATION, 2); 687 ShipAPI leader = engine.getFleetManager(source.getOriginalOwner()).spawnShipOrWing(variantId, loc, facing, 0f, captain); 688 for (int i = 0; i < ships.length; i++) { 689 ships[i] = leader.getWing().getWingMembers().get(i); 690 ships[i].getLocation().set(loc); 691 } 692 collisionClass = ships[0].getCollisionClass(); 693 } else { 694 ships = new ShipAPI[1]; 695 ships[0] = engine.getFleetManager(source.getOriginalOwner()).spawnShipOrWing(variantId, loc, facing, 0f, source.getOriginalCaptain()); 696 } 697 for (int i = 0; i < ships.length; i++) { 698 ships[i].cloneVariant(); 699 ships[i].getVariant().addTag(Tags.SHIP_LIMITED_TOOLTIP); 700 701 if (Global.getCombatEngine().isInCampaign() || Global.getCombatEngine().isInCampaignSim()) { 702 FactionAPI faction = Global.getSector().getFaction(Factions.OMEGA); 703 if (faction != null) { 704 String name = faction.pickRandomShipName(); 705 ships[i].setName(name); 706 } 707 } 708 } 709 fleetManager.setSuppressDeploymentMessages(wasSuppressed); 710 collisionClass = ships[0].getCollisionClass(); 711 712 DeployedFleetMemberAPI sourceMember = fleetManager.getDeployedFleetMemberFromAllEverDeployed(source); 713 DeployedFleetMemberAPI deployed = fleetManager.getDeployedFleetMemberFromAllEverDeployed(ships[0]); 714 if (sourceMember != null && deployed != null) { 715 Map<DeployedFleetMemberAPI, DeployedFleetMemberAPI> map = fleetManager.getShardToOriginalShipMap(); 716 while (map.containsKey(sourceMember)) { 717 sourceMember = map.get(sourceMember); 718 } 719 if (sourceMember != null) { 720 map.put(deployed, sourceMember); 721 } 722 } 723 } 724 725 726 727 float progress = (elapsed - delay) / fadeInTime; 728 if (progress > 1f) progress = 1f; 729 730 for (int i = 0; i < ships.length; i++) { 731 ShipAPI ship = ships[i]; 732 ship.setAlphaMult(progress); 733 734 if (progress < 0.5f) { 735 ship.blockCommandForOneFrame(ShipCommand.ACCELERATE); 736 ship.blockCommandForOneFrame(ShipCommand.TURN_LEFT); 737 ship.blockCommandForOneFrame(ShipCommand.TURN_RIGHT); 738 ship.blockCommandForOneFrame(ShipCommand.STRAFE_LEFT); 739 ship.blockCommandForOneFrame(ShipCommand.STRAFE_RIGHT); 740 } 741 742 ship.blockCommandForOneFrame(ShipCommand.USE_SYSTEM); 743 ship.blockCommandForOneFrame(ShipCommand.TOGGLE_SHIELD_OR_PHASE_CLOAK); 744 ship.blockCommandForOneFrame(ShipCommand.FIRE); 745 ship.blockCommandForOneFrame(ShipCommand.PULL_BACK_FIGHTERS); 746 ship.blockCommandForOneFrame(ShipCommand.VENT_FLUX); 747 ship.setHoldFireOneFrame(true); 748 ship.setHoldFire(true); 749 750 751 ship.setCollisionClass(CollisionClass.NONE); 752 ship.getMutableStats().getHullDamageTakenMult().modifyMult("ShardSpawnerInvuln", 0f); 753 if (progress < 0.5f) { 754 ship.getVelocity().set(source.getVelocity()); 755 } else if (progress > 0.75f){ 756 ship.setCollisionClass(collisionClass); 757 ship.getMutableStats().getHullDamageTakenMult().unmodifyMult("ShardSpawnerInvuln"); 758 } 759 760// Vector2f dir = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(source.getLocation(), ship.getLocation())); 761// dir.scale(amount * 50f * progress); 762// Vector2f.add(ship.getLocation(), dir, ship.getLocation()); 763 764 765 float jitterLevel = progress; 766 if (jitterLevel < 0.5f) { 767 jitterLevel *= 2f; 768 } else { 769 jitterLevel = (1f - jitterLevel) * 2f; 770 } 771 772 float jitterRange = 1f - progress; 773 float maxRangeBonus = 50f; 774 float jitterRangeBonus = jitterRange * maxRangeBonus; 775 Color c = JITTER_COLOR; 776 777 ship.setJitter(this, c, jitterLevel, 25, 0f, jitterRangeBonus); 778 } 779 780 if (elapsed > fadeInTime) { 781 for (int i = 0; i < ships.length; i++) { 782 ShipAPI ship = ships[i]; 783 ship.setAlphaMult(1f); 784 ship.setHoldFire(false); 785 ship.setCollisionClass(collisionClass); 786 ship.getMutableStats().getHullDamageTakenMult().unmodifyMult("ShardSpawnerInvuln"); 787 } 788 engine.removePlugin(this); 789 } 790 } 791 } 792 793} 794 795 796 797 798 799 800 801 802