43 public static String
DATA_KEY =
"core_shard_spawner_data_key";
47 public static enum ShardType {
55 public static class ShardTypeVariants {
56 public Map<ShardType, WeightedRandomPicker<String>> variants =
new HashMap<ShardType, WeightedRandomPicker<String>>();
57 public ShardTypeVariants() {
59 public WeightedRandomPicker<String>
get(ShardType type) {
60 WeightedRandomPicker<String> result = variants.get(type);
62 result =
new WeightedRandomPicker<String>();
63 variants.put(type, result);
69 public static Map<HullSize, ShardTypeVariants>
variantData =
new HashMap<HullSize, ShardTypeVariants>();
71 ShardTypeVariants fighters =
new ShardTypeVariants();
73 fighters.get(ShardType.GENERAL).add(
"aspect_attack_wing", 10f);
74 fighters.get(ShardType.GENERAL).add(
"aspect_missile_wing", 1f);
76 fighters.get(ShardType.MISSILE).add(
"aspect_missile_wing", 10f);
78 fighters.get(ShardType.ANTI_ARMOR).add(
"aspect_attack_wing", 10f);
80 fighters.get(ShardType.ANTI_SHIELD).add(
"aspect_shieldbreaker_wing", 10f);
82 fighters.get(ShardType.POINT_DEFENSE).add(
"aspect_shock_wing", 10f);
85 ShardTypeVariants small =
new ShardTypeVariants();
88 small.get(ShardType.GENERAL).add(
"shard_left_Attack", 10f);
89 small.get(ShardType.GENERAL).add(
"shard_left_Attack2", 10f);
90 small.get(ShardType.GENERAL).add(
"shard_right_Attack", 10f);
91 small.get(ShardType.GENERAL).add(
"aspect_attack_wing", 10f);
92 small.get(ShardType.GENERAL).add(
"aspect_missile_wing", 1f);
94 small.get(ShardType.ANTI_ARMOR).add(
"shard_left_Armorbreaker", 10f);
96 small.get(ShardType.ANTI_SHIELD).add(
"shard_left_Shieldbreaker", 10f);
97 small.get(ShardType.ANTI_SHIELD).add(
"shard_right_Shieldbreaker", 10f);
100 small.get(ShardType.POINT_DEFENSE).add(
"shard_left_Defense", 10f);
101 small.get(ShardType.POINT_DEFENSE).add(
"shard_right_Shock", 10f);
104 small.get(ShardType.MISSILE).add(
"shard_left_Missile", 10f);
105 small.get(ShardType.MISSILE).add(
"shard_right_Missile", 10f);
109 ShardTypeVariants medium =
new ShardTypeVariants();
112 medium.get(ShardType.GENERAL).add(
"facet_Attack");
113 medium.get(ShardType.GENERAL).add(
"facet_Attack2");
115 medium.get(ShardType.ANTI_ARMOR).add(
"facet_Armorbreaker");
117 medium.get(ShardType.ANTI_SHIELD).add(
"facet_Shieldbreaker");
119 medium.get(ShardType.POINT_DEFENSE).add(
"facet_Defense");
121 medium.get(ShardType.MISSILE).add(
"facet_Missile");
123 ShardTypeVariants large =
new ShardTypeVariants();
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");
131 large.get(ShardType.ANTI_ARMOR).add(
"tesseract_Disruptor");
132 large.get(ShardType.ANTI_ARMOR).add(
"tesseract_Strike");
134 large.get(ShardType.ANTI_SHIELD).add(
"tesseract_Shieldbreaker");
136 large.get(ShardType.POINT_DEFENSE).add(
"tesseract_Defense");
138 large.get(ShardType.MISSILE).add(
"tesseract_Strike");
141 public static class ShardSpawnerData {
142 boolean done =
false;
143 float delay = 2f + (float) Math.random() * 1f;
147 stats.getBreakProb().modifyMult(
id, 0f);
153 if (ship.getOriginalOwner() != 0) {
154 engine.setCombatNotOverForAtLeast(
SPAWN_TIME + 1f);
157 if (!ship.isHulk() || !engine.isEntityInPlay(ship))
return;
159 String key =
DATA_KEY +
"_" + ship.getId();
160 ShardSpawnerData data = (ShardSpawnerData) engine.getCustomData().get(key);
162 data =
new ShardSpawnerData();
163 engine.getCustomData().put(key, data);
166 if (data.done)
return;
169 ship.setHitpoints(ship.getMaxHitpoints());
170 ship.getMutableStats().getHullDamageTakenMult().modifyMult(
"ShardSpawnerInvuln", 0f);
171 data.delay -= amount;
172 if (data.delay > 0)
return;
179 float splitWeight = 0f;
181 float probNothingAtAll = 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;
193 if (ship.isCapital()) {
197 cruiserProbMult = 0.5f;
200 destroyerProbMult = 1f;
203 frigateProbMult = 1f;
205 }
else if (ship.isCruiser()) {
218 destroyerProbMult = 0.5f;
221 frigateProbMult = 1f;
223 }
else if (ship.isDestroyer()) {
233 frigateProbMult = 1f;
235 }
else if (ship.isFrigate()) {
247 if ((
float) Math.random() < probNothingAtAll) {
251 WeightedRandomPicker<Float> spawnAngles =
new WeightedRandomPicker<Float>();
252 int spawnAnglesIter = 0;
253 float angleOffset = (float) Math.random() * 360f;
255 float addedWeight = 0f;
258 float destroyers = 0f;
260 List<ShardFadeInPlugin> shards =
new ArrayList<ShardFadeInPlugin>();
261 while (addedWeight < splitWeight) {
262 ShardType type = typePicker.pick();
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;
269 String variant =
null;
273 ShardTypeVariants variants =
variantData.get(HullSize.CRUISER);
274 WeightedRandomPicker<String> variantPicker = variants.get(type);
275 variant = variantPicker.pick();
276 if (variant !=
null) {
279 cruiserProb *= cruiserProbMult;
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) {
290 destroyerProb *= destroyerProbMult;
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) {
301 frigateProb *= frigateProbMult;
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) {
317 if (variant !=
null) {
318 if (spawnAngles ==
null || spawnAngles.isEmpty()) {
321 float angle = spawnAngles.pickAndRemove() + angleOffset;
325 addedWeight += weight;
342 WeightedRandomPicker<Float> picker =
new WeightedRandomPicker<Float>();
347 }
else if (iter == 2) {
353 for (
float i = start; i < 360f + start; i += incr) {
363 float checkRadius = 5000;
364 Iterator<Object> iter = engine.getAiGridShips().getCheckIterator(ship.getLocation(), checkRadius * 2f, checkRadius * 2f);
366 float weightFighters = 0f;
367 float weightGoodShields = 0f;
368 float weightGoodArmor = 0f;
369 float weightVulnerable = 0f;
370 float weightCarriers = 0f;
372 float weightEnemies = 0f;
373 float weightFriends = 0f;
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;
381 boolean enemy = ship.getOwner() != other.getOwner();
383 if (other.isFighter() || other.isDrone()) {
384 weightFighters += 0.25f;
385 weightEnemies += 0.25f;
387 float w = Misc.getShipWeight(other);
391 weightGoodShields += w;
394 weightGoodArmor += w;
397 weightVulnerable += w;
399 if (other.getVariant().isCarrier()) {
404 if (other.isFighter() || other.isDrone()) {
405 weightFriends += 0.25f;
407 float w = Misc.getShipWeight(other);
414 WeightedRandomPicker<ShardType> picker =
new WeightedRandomPicker<ShardType>();
416 float total = weightFighters + weightGoodShields + weightGoodArmor + weightVulnerable + weightCarriers;
417 if (total <= 1f) total = 1f;
419 float antiFighter = (weightFighters + weightCarriers) / total;
420 float antiShield = weightGoodShields / total;
421 float antiArmor = weightGoodArmor / total;
422 float missile = weightVulnerable / total;
424 float friends = weightFriends / Math.max(1f, weightEnemies + weightFriends);
426 picker.add(ShardType.GENERAL, 0.0f + (1f - friends) * 0.4f);
432 float unlikelyWeight = 0f;
433 float unlikelyThreshold = 0.2f;
435 if (antiFighter < unlikelyThreshold) antiFighter = unlikelyWeight;
436 picker.add(ShardType.POINT_DEFENSE, antiFighter);
438 if (antiShield < unlikelyThreshold) antiShield = unlikelyWeight;
439 picker.add(ShardType.ANTI_SHIELD, antiShield);
441 if (antiArmor < unlikelyThreshold) antiArmor = unlikelyWeight;
442 picker.add(ShardType.ANTI_ARMOR, antiArmor);
444 if (missile < unlikelyThreshold) missile = unlikelyWeight;
445 picker.add(ShardType.MISSILE, missile);
451 float incap = Misc.getIncapacitatedTime(other);
453 float dist = Misc.getDistance(from.getLocation(), other.getLocation());
454 if (dist > 2000)
return false;
456 float assumedMissileSpeed = 500;
457 float eta = dist / assumedMissileSpeed;
461 return incap >= eta || (other.getFluxLevel() >= 0.95f && other.getFluxTracker().getTimeToVent() >= eta);
465 float requiredArmor = 1240;
467 if (other.getArmorGrid().getArmorRating() < requiredArmor)
return false;
469 float armor = other.getAverageArmorInSlice(other.getFacing(), 120f);
470 return armor >= requiredArmor * 0.8f;
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;
480 float requiredCapacity = 10000000f;
481 switch (other.getHullSize()) {
483 requiredCapacity = 25000;
484 if (shield.getType() == ShieldType.FRONT && shield.getArc() < 250) {
485 requiredCapacity = 1000000;
489 requiredCapacity = 12500;
490 if (shield.getType() == ShieldType.FRONT && shield.getArc() < 250) {
491 requiredCapacity = 1000000;
495 requiredCapacity = 8000;
498 requiredCapacity = 4000;
502 float e = other.getShield().getFluxPerPointOfDamage() *
503 other.getMutableStats().getShieldDamageTakenMult().getModifiedValue();
504 float capacity = other.getMaxFlux();
505 capacity /= Math.max(0.1f, e);
507 return capacity >= requiredCapacity && e <= 1f;
513 final List<ShardFadeInPlugin> shards) {
514 return new BaseEveryFrameCombatPlugin() {
516 IntervalUtil interval =
new IntervalUtil(0.075f, 0.125f);
518 protected void pushShipsAway(
float amount) {
519 Vector2f com =
new Vector2f();
521 for (ShardFadeInPlugin shard : shards) {
522 ShipAPI ship = shard.ships[0];
523 if (ship.isFighter())
continue;
524 Vector2f.add(com, ship.getLocation(), com);
527 com.scale(1f / Math.max(1f, count));
529 Vector2f comForFighters =
new Vector2f();
531 for (ShardFadeInPlugin shard : shards) {
532 ShipAPI ship = shard.ships[0];
533 if (!ship.isFighter())
continue;
534 Vector2f.add(comForFighters, ship.getLocation(), comForFighters);
537 comForFighters.scale(1f / Math.max(1f, count));
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;
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());
554 public void advance(
float amount, List<InputEventAPI> events) {
560 float progress = elapsed / fadeOutTime;
561 if (progress > 1f) progress = 1f;
562 ship.setAlphaMult(1f - progress);
565 pushShipsAway(amount);
568 if (progress > 0.5f) {
569 ship.setCollisionClass(CollisionClass.NONE);
572 float jitterLevel = progress;
573 if (jitterLevel < 0.5f) {
576 jitterLevel = (1f - jitterLevel) * 2f;
579 float jitterRange = progress;
581 float maxRangeBonus = 100f;
582 float jitterRangeBonus = jitterRange * maxRangeBonus;
584 int alpha = c.getAlpha();
585 alpha += 100f * progress;
586 if (alpha > 255) alpha = 255;
587 c = Misc.setAlpha(c, alpha);
589 ship.setJitter(
this, c, jitterLevel, 35, 0f, jitterRangeBonus);
591 interval.advance(amount);
592 if (interval.intervalElapsed() && elapsed < fadeOutTime * 0.75f) {
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();
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);
607 Vector2f.add(vel, v, v);
609 float maxSpeed = nSize * 1.5f * 0.2f;
610 float minSpeed = nSize * 1f * 0.2f;
611 float overMin = v.length() - minSpeed;
613 float durMult = 1f - overMin / (maxSpeed - minSpeed);
614 if (durMult < 0.1f) durMult = 0.1f;
615 dur *= 0.5f + 0.5f * durMult;
617 engine.addNegativeNebulaParticle(pt, v, nSize * 1f, 2f,
618 0.5f / dur, 0f, dur, c);
622 if (elapsed > fadeOutTime) {
623 ship.setHitpoints(0f);
625 ship.setAlphaMult(0f);
634 final float delay,
final float fadeInTime,
final float angle) {
636 return new ShardFadeInPlugin(variantId, source, delay, fadeInTime, angle);
639 public static class ShardFadeInPlugin
extends BaseEveryFrameCombatPlugin {
641 ShipAPI [] ships =
null;
642 CollisionClass collisionClass;
650 public ShardFadeInPlugin(String variantId, ShipAPI source,
float delay,
float fadeInTime,
float angle) {
651 this.variantId = variantId;
652 this.source = source;
654 this.fadeInTime = fadeInTime;
661 public void advance(
float amount, List<InputEventAPI> events) {
665 if (elapsed < delay)
return;
670 float facing = source.getFacing() + 15f * ((float) Math.random() - 0.5f);
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")) {
681 ships =
new ShipAPI[spec.getNumFighters()];
683 captain.setPersonality(Personalities.RECKLESS);
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);
692 collisionClass = ships[0].getCollisionClass();
694 ships =
new ShipAPI[1];
695 ships[0] = engine.getFleetManager(source.getOriginalOwner()).spawnShipOrWing(variantId, loc, facing, 0f, source.getOriginalCaptain());
697 for (
int i = 0; i < ships.length; i++) {
698 ships[i].cloneVariant();
699 ships[i].getVariant().addTag(Tags.SHIP_LIMITED_TOOLTIP);
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);
709 fleetManager.setSuppressDeploymentMessages(wasSuppressed);
710 collisionClass = ships[0].getCollisionClass();
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);
719 if (sourceMember !=
null) {
720 map.put(deployed, sourceMember);
727 float progress = (elapsed - delay) / fadeInTime;
728 if (progress > 1f) progress = 1f;
730 for (
int i = 0; i < ships.length; i++) {
731 ShipAPI ship = ships[i];
732 ship.setAlphaMult(progress);
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);
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);
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");
765 float jitterLevel = progress;
766 if (jitterLevel < 0.5f) {
769 jitterLevel = (1f - jitterLevel) * 2f;
772 float jitterRange = 1f - progress;
773 float maxRangeBonus = 50f;
774 float jitterRangeBonus = jitterRange * maxRangeBonus;
777 ship.setJitter(
this, c, jitterLevel, 25, 0f, jitterRangeBonus);
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");
788 engine.removePlugin(
this);