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