001package com.fs.starfarer.api.impl.campaign.abilities;
002
003import java.util.LinkedHashMap;
004import java.util.Map;
005import java.util.Random;
006
007import java.awt.Color;
008
009import org.lwjgl.util.vector.Vector2f;
010
011import com.fs.starfarer.api.EveryFrameScript;
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.CampaignFleetAPI;
014import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
015import com.fs.starfarer.api.campaign.FactionAPI;
016import com.fs.starfarer.api.campaign.JumpPointAPI;
017import com.fs.starfarer.api.campaign.PlanetAPI;
018import com.fs.starfarer.api.campaign.PlanetSpecAPI;
019import com.fs.starfarer.api.campaign.SectorEntityToken;
020import com.fs.starfarer.api.characters.AbilityPlugin;
021import com.fs.starfarer.api.fleet.FleetMemberViewAPI;
022import com.fs.starfarer.api.impl.campaign.ids.Abilities;
023import com.fs.starfarer.api.impl.campaign.ids.Factions;
024import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
025import com.fs.starfarer.api.impl.campaign.ids.Pings;
026import com.fs.starfarer.api.impl.campaign.ids.StarTypes;
027import com.fs.starfarer.api.impl.campaign.ids.Tags;
028import com.fs.starfarer.api.impl.campaign.ids.Terrain;
029import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
030import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2;
031import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamParams2;
032import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamSegment;
033import com.fs.starfarer.api.ui.Alignment;
034import com.fs.starfarer.api.ui.TooltipMakerAPI;
035import com.fs.starfarer.api.util.Misc;
036import com.fs.starfarer.api.util.Misc.FleetMemberDamageLevel;
037
038public class GenerateSlipsurgeAbility extends DurationAbilityWithCost2 {
039
040        public static boolean REQUIRE_GIANT_STARS_OR_STRONGER = true;
041        
042        public static Map<String, Float> SLIPSURGE_STRENGTH = new LinkedHashMap<String, Float>();
043        static {
044                // I'm aware that these mappings do not reflect actual star mass, which varies
045                // wildly in any case please don't @ me thank you -am
046                
047                SLIPSURGE_STRENGTH.put(StarTypes.BLACK_HOLE, 1f);
048                SLIPSURGE_STRENGTH.put(StarTypes.NEUTRON_STAR, 0.9f);
049                
050                SLIPSURGE_STRENGTH.put(StarTypes.BLUE_SUPERGIANT, 0.8f);
051                SLIPSURGE_STRENGTH.put(StarTypes.RED_SUPERGIANT, 0.8f);
052                
053                SLIPSURGE_STRENGTH.put(StarTypes.RED_GIANT, 0.6f);
054                SLIPSURGE_STRENGTH.put(StarTypes.BLUE_GIANT, 0.6f);
055                SLIPSURGE_STRENGTH.put(StarTypes.ORANGE_GIANT, 0.6f);
056                
057                SLIPSURGE_STRENGTH.put(StarTypes.ORANGE, 0.4f);
058                SLIPSURGE_STRENGTH.put(StarTypes.YELLOW, 0.4f);
059                
060                SLIPSURGE_STRENGTH.put(StarTypes.WHITE_DWARF, 0.25f);
061                SLIPSURGE_STRENGTH.put(StarTypes.RED_DWARF, 0.2f);
062                
063                SLIPSURGE_STRENGTH.put(StarTypes.BROWN_DWARF, 0.1f);
064                
065                SLIPSURGE_STRENGTH.put(StarTypes.GAS_GIANT, 0f);
066                SLIPSURGE_STRENGTH.put(StarTypes.ICE_GIANT, 0f);
067        }
068        
069        public static float SLIPSURGE_STRENGTH_MULT = 1.3f;
070        
071        public static float TRANSIT_MUSIC_SUPPRESSION = 1f;
072        public static String TRANSIT_SOUND_LOOP = "ui_slipsurge_travel_loop";
073        
074        public static float FUEL_COST_MULT = 5f;
075        public static float CR_COST_MULT = 0.25f;
076        public static float SENSOR_RANGE_MULT = 0.1f;
077        
078        public static String SENSOR_MOD_ID = "slipsurge_sensor_penalty";
079        
080        public static class SlipsurgeFadeInScript implements EveryFrameScript {
081                protected SlipstreamTerrainPlugin2 plugin;
082                protected boolean done = false;
083                protected int index = 0;
084                protected float elapsed = 0f;
085                protected float maxElapsed = 0f;
086                public SlipsurgeFadeInScript(SlipstreamTerrainPlugin2 plugin) {
087                        this.plugin = plugin;
088
089                        float fadeInLength = 300f;
090                        float lengthSoFar = 0f;
091                        float durIn = 0.2f;
092                        maxElapsed = durIn * plugin.getSegments().size() * 0.34f + 3f;
093                        for (SlipstreamSegment seg : plugin.getSegments()) {
094                                seg.fader.setDurationIn(durIn);
095                                seg.fader.forceOut();
096                                
097                                seg.bMult = Math.min(1f, lengthSoFar / fadeInLength);
098                                lengthSoFar += seg.lengthToNext;
099                        }
100                }
101                public void advance(float amount) {
102                        if (done) return;
103                        
104                        // amount == 1 when not current location
105                        // which isn't fine-grained enough to fade the segments in
106                        // one at a time since each takes <1s
107                        if (!plugin.getEntity().isInCurrentLocation()) {
108                                for (SlipstreamSegment curr : plugin.getSegments()) {
109                                        curr.fader.fadeIn();
110                                }
111                                done = true;
112                                return;
113                        }
114                        
115                        
116                        elapsed += amount;
117                        if (index >= plugin.getSegments().size() || elapsed > maxElapsed) {
118                                done = true;
119                                return;
120                        }
121                        SlipstreamSegment curr = plugin.getSegments().get(index);
122                        if (curr.fader.isFadedIn()) {
123                                index++;
124                                return;
125                        }
126                        curr.fader.fadeIn();
127                        if (curr.fader.getBrightness() > 0.33f && index < plugin.getSegments().size() - 1) {
128                                plugin.getSegments().get(index + 1).fader.fadeIn();
129                        }
130                        if (curr.fader.getBrightness() > 0.67f && index < plugin.getSegments().size() - 2) {
131                                plugin.getSegments().get(index + 2).fader.fadeIn();
132                        }
133                }
134
135                public boolean isDone() {
136                        return done;
137                }
138                public boolean runWhilePaused() {
139                        return false;
140                }
141        }
142        
143        public static class ExpandStormRadiusScript implements EveryFrameScript {
144                protected float elapsed = 0f;
145                protected float extraRadius;
146                public ExpandStormRadiusScript(float extraRadius) {
147                        this.extraRadius = extraRadius;
148                }
149                public void advance(float amount) {
150                        elapsed += amount;
151                        
152                        CampaignTerrainAPI terrain = Misc.getHyperspaceTerrain();
153                        if (terrain != null) {
154                                HyperspaceTerrainPlugin htp = (HyperspaceTerrainPlugin) terrain.getPlugin();
155                                htp.setExtraDistanceAroundPlayerToAdvanceStormCells(extraRadius);
156                                htp.setStormCellTimeMultOutsideBaseArea(5f);
157                        }
158                }
159
160                public boolean isDone() {
161                        return elapsed >= 10f;
162                }
163                public boolean runWhilePaused() {
164                        return false;
165                }
166        }
167        
168        public static class SlipsurgeEffectScript implements EveryFrameScript {
169                protected CampaignFleetAPI fleet;
170                protected boolean triggered = false;
171                protected boolean done = false;
172                protected boolean didTempCleanup = false;
173                protected float elapsed = 0f;
174                protected SlipstreamTerrainPlugin2 plugin;
175                public SlipsurgeEffectScript(CampaignFleetAPI fleet, SlipstreamTerrainPlugin2 plugin) {
176                        this.fleet = fleet;
177                        this.plugin = plugin;
178                }
179                
180                public void abort() {
181                        done = true;
182                        didTempCleanup = true;
183                        fleet.fadeInIndicator();
184                        fleet.setNoEngaging(0f);
185                        fleet.getStats().getSensorRangeMod().unmodifyMult(SENSOR_MOD_ID);
186                        for (FleetMemberViewAPI view : fleet.getViews()) {
187                                view.endJitter();
188                        }
189                        if (fleet.isPlayerFleet()) {
190                                Global.getSector().getCampaignUI().setFollowingDirectCommand(false);
191                        }
192                        //fleet.setMoveDestination(dest.x, dest.y)
193                }
194                
195                public void advance(float amount) {
196                        if (done) return;
197                        
198                        if (fleet.isInHyperspaceTransition() ||
199                                                plugin.getEntity().getContainingLocation() != fleet.getContainingLocation()) {
200                                if (!didTempCleanup) {
201                                        abort();
202                                }
203                                // fleet could conceivably come back, set done back to false so the script keeps running
204                                done = false;
205                                return;
206                        }
207                        
208                        elapsed += amount;
209                        if (!triggered) {
210                                if (elapsed >= 30f || 
211                                                !plugin.getEntity().isAlive()) {
212                                        done = true;
213                                        return;
214                                }
215                        }
216                        
217                        float burn = Misc.getBurnLevelForSpeed(fleet.getVelocity().length());
218                        if (burn > 50 && plugin.containsEntity(fleet)) {
219                                triggered = true;
220                        }
221                        
222                        if (triggered) {
223                                if (burn < 50) {
224                                        plugin.despawn(0, 0.2f, new Random());
225                                        abort();
226                                        return;
227                                }
228                                
229                                AbilityPlugin gs = fleet.getAbility(Abilities.GENERATE_SLIPSURGE);
230                                if (gs instanceof BaseAbilityPlugin) {
231                                        BaseAbilityPlugin base = (BaseAbilityPlugin) gs;
232                                        for (AbilityPlugin curr : fleet.getAbilities().values()) {
233                                                if (curr == this) continue;
234                                                if (!base.isCompatible(curr)) {
235                                                        if (curr.isActiveOrInProgress()) {
236                                                                curr.deactivate();
237                                                        }
238                                                        curr.forceDisable();
239                                                }
240                                        }
241                                }
242                                
243                                float decelMult = Misc.getAbyssalDepth(fleet) * 1f;
244                                if (fleet.getGoSlowOneFrame()) decelMult = 1f;
245                                //if (fleet.getGoSlowOneFrame() && !plugin.containsEntity(fleet)) {
246                                if (decelMult > 0f && !plugin.containsEntity(fleet)) {
247                                        float angle = Misc.getAngleInDegrees(fleet.getVelocity());
248                                        Vector2f decelDir = Misc.getUnitVectorAtDegreeAngle(angle + 180f);
249                                        
250                                        float targetDecelSeconds = 0.5f;
251                                        float speed = fleet.getVelocity().length();
252                                        float decelAmount = amount * speed / targetDecelSeconds;
253                                        decelDir.scale(decelAmount * decelMult);
254                                        
255                                        Vector2f vel = fleet.getVelocity();
256                                        fleet.setVelocity(vel.x + decelDir.x, vel.y + decelDir.y);
257                                }
258                                
259                                
260                                fleet.fadeOutIndicator();
261                                fleet.setNoEngaging(0.1f);
262                                fleet.setInteractionTarget(null);
263                                if (fleet.isPlayerFleet()) {
264                                        Global.getSector().getCampaignUI().setFollowingDirectCommand(true);
265                                }
266                                fleet.getMemoryWithoutUpdate().set(MemFlags.NO_HIGH_BURN_TOPOGRAPHY_READINGS, true, 0.1f);
267                                fleet.getStats().getSensorRangeMod().modifyMult(SENSOR_MOD_ID, SENSOR_RANGE_MULT, "Extreme burn level");
268                                didTempCleanup = false;
269                                
270                                float angle = Misc.getAngleInDegrees(fleet.getVelocity());
271                                Vector2f jitterDir = Misc.getUnitVectorAtDegreeAngle(angle + 180f);
272                                Vector2f windDir = Misc.getUnitVectorAtDegreeAngle(angle);
273                                float windIntensity = (burn - 50f) / 250f;
274                                if (windIntensity < 0) windIntensity = 0;
275                                //if (windIntensity > 1f) windIntensity = 1f;
276                                
277                                float b = (burn - 50f) / 450f;
278                                if (b < 0) b = 0;
279                                if (b > 1f) b = 1f;
280                                if (b > 0) {
281                                        
282                                        if (fleet.isPlayerFleet()) {
283                                                float volume = b;
284                                                Global.getSector().getCampaignUI().suppressMusic(TRANSIT_MUSIC_SUPPRESSION * volume);
285                                                Global.getSoundPlayer().setNextLoopFadeInAndOut(0.05f, 0.5f);
286                                                Global.getSoundPlayer().playLoop(TRANSIT_SOUND_LOOP, fleet, 
287                                                                        1f, volume,
288                                                                        fleet.getLocation(), fleet.getVelocity());                                              
289                                        }
290                                        
291                                        
292                                        //String modId = "SlipsurgeEffectScript_" + fleet.getId();
293                                        for (FleetMemberViewAPI view : fleet.getViews()) {
294                                                Color c = view.getMember().getHullSpec().getHyperspaceJitterColor();
295//                                              view.setJitter(1f, 1f, c, 7, 10f);
296//                                              view.setJitterBrightness(b);
297//                                              //view.setJitter(0.25f, 1f, c, 10 + Math.round(40f * b), 20f);
298//                                              view.setUseCircularJitter(true);
299                                                
300                                                c = Misc.setAlpha(c, 60);
301                                                view.setJitter(0.1f, 1f, c, 10 + Math.round(40f * b), 20f);
302                                                view.setUseCircularJitter(true);
303                                                view.setJitterDirection(jitterDir);
304                                                view.setJitterLength(30f * b);
305                                                view.setJitterBrightness(b);
306                                                
307                                        }
308                                } else {
309                                        for (FleetMemberViewAPI view : fleet.getViews()) {
310                                                view.endJitter();
311                                        }
312                                }
313                        }
314                        
315                }
316
317                public boolean isDone() {
318                        return done;
319                }
320                public boolean runWhilePaused() {
321                        return false;
322                }
323        }
324        
325        protected Boolean primed = null;
326        protected Vector2f startLoc = null;
327        protected JumpPointAPI well = null;
328        
329        
330        public float getFuelCostMult() {
331                return FUEL_COST_MULT;
332        }
333        public float getCRCostMult() {
334                return CR_COST_MULT;
335        }
336        public FleetMemberDamageLevel getActivationDamageLevel() {
337                return FleetMemberDamageLevel.MEDIUM;
338        }
339        public boolean canRecoverCRWhileActive() {
340                return true;
341        }
342        
343        
344        @Override
345        protected void activateImpl() {
346                CampaignFleetAPI fleet = getFleet();
347                if (fleet == null) return;
348                
349                primed = true;
350                well = findGravityWell();
351                if (well == null) return;
352                
353                //startLoc = new Vector2f(fleet.getLocation());
354                float angle = Misc.getAngleInDegrees(well.getLocation(), fleet.getLocation());
355                float offset = 100f;
356                Vector2f from = Misc.getUnitVectorAtDegreeAngle(angle);
357                from.scale(offset + fleet.getRadius());
358                Vector2f.add(from, fleet.getLocation(), from);
359                startLoc = from;
360                
361                
362                SectorEntityToken token = fleet.getContainingLocation().createToken(startLoc);
363                //Global.getSector().addPing(fleet, Pings.SLIPSURGE);
364                Global.getSector().addPing(token, Pings.SLIPSURGE);
365                
366                well.getContainingLocation().addScript(new ExpandStormRadiusScript(16000f));
367                
368                deductCost();
369        }
370        
371        @Override
372        protected void applyStatsEffect(float amount, float level) {
373                CampaignFleetAPI fleet = getFleet();
374                if (fleet == null) return;
375                
376                if (level > 0 && level < 1 && amount > 0) {
377                        fleet.goSlowOneFrame();
378                        return;
379                }
380                
381                if (level == 1 && primed != null) {
382                        generateSlipstream();
383                        primed = null;
384                        startLoc = null;
385                        well = null;
386                }
387                
388        }
389        
390        
391        protected void generateSlipstream() {
392                CampaignFleetAPI fleet = getFleet();
393                if (fleet == null) return;
394                
395                //JumpPointAPI jp = findGravityWell();
396                JumpPointAPI jp = well;
397                if (jp ==  null) return;
398                
399                float strength = getStrengthForGravityWell(jp);
400                strength *= SLIPSURGE_STRENGTH_MULT;
401                
402                float angle = Misc.getAngleInDegrees(jp.getLocation(), startLoc);
403                float offset = 100f;
404//              Vector2f from = Misc.getUnitVectorAtDegreeAngle(angle);
405//              from.scale(offset + fleet.getRadius());
406//              Vector2f.add(from, startLoc, from);
407                Vector2f from = startLoc;
408                
409                SlipstreamParams2 params = new SlipstreamParams2();
410                
411                params.enteringSlipstreamTextOverride = "Entering slipsurge";
412                params.enteringSlipstreamTextDurationOverride = 0.1f;
413                params.forceNoWindVisualEffectOnFleets = true;
414                
415                float width = 600f;
416                float length = 1000f;
417
418                length += strength * 500f;
419                params.burnLevel = Math.round(400f + strength * strength * 500f);
420                params.accelerationMult = 20f + strength * strength * 280f; 
421                
422                //params.accelerationMult = 500f;
423                
424                params.baseWidth = width;
425                params.widthForMaxSpeed = 400f;
426                params.widthForMaxSpeedMinMult = 0.34f;
427                //params.widthForMaxSpeed = 300f;
428                params.slowDownInWiderSections = true;
429                //params.edgeWidth = 100f;
430                //params.accelerationMult = 100f;
431                
432                
433                params.minSpeed = Misc.getSpeedForBurnLevel(params.burnLevel - params.burnLevel/8);
434                params.maxSpeed = Misc.getSpeedForBurnLevel(params.burnLevel + params.burnLevel/8);
435                //params.lineLengthFractionOfSpeed = 0.25f * Math.max(0.25f, Math.min(1f, 30f / (float) params.burnLevel));
436                params.lineLengthFractionOfSpeed = 2000f / ((params.maxSpeed + params.minSpeed) * 0.5f);
437                
438                float lineFactor = 0.1f;
439                params.minSpeed *= lineFactor;
440                params.maxSpeed *= lineFactor;
441                //params.lineLengthFractionOfSpeed *= 0.25f;
442                //params.lineLengthFractionOfSpeed *= 1f;
443                params.maxBurnLevelForTextureScroll = (int) (params.burnLevel * 0.1f);
444                
445                params.particleFadeInTime = 0.01f;
446                params.areaPerParticle = 1000f; 
447                
448                Vector2f to = Misc.getUnitVectorAtDegreeAngle(angle);
449                to.scale(offset + fleet.getRadius() + length);
450                Vector2f.add(to, startLoc, to);
451                
452                CampaignTerrainAPI slipstream = (CampaignTerrainAPI) well.getContainingLocation().addTerrain(Terrain.SLIPSTREAM, params);
453                slipstream.addTag(Tags.SLIPSTREAM_VISIBLE_IN_ABYSS);
454                slipstream.setLocation(from.x, from.y);
455                
456                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) slipstream.getPlugin();
457                
458                float spacing = 100f;
459                float incr = spacing / length;
460
461                Vector2f diff = Vector2f.sub(to, from, new Vector2f());
462                for (float f = 0; f <= 1f; f += incr) {
463                        Vector2f curr = new Vector2f(diff);
464                        curr.scale(f);
465                        Vector2f.add(curr, from, curr);
466                        plugin.addSegment(curr, width - Math.min(300f, 300f * (float)Math.sqrt(f)));
467                        //plugin.addSegment(curr, width);
468                }
469                
470                plugin.recomputeIfNeeded();
471                
472                plugin.despawn(1.5f, 0.2f, new Random());
473                
474                slipstream.addScript(new SlipsurgeFadeInScript(plugin));
475                fleet.addScript(new SlipsurgeEffectScript(fleet, plugin));
476                
477        }
478        
479        @Override
480        protected void unapplyStatsEffect() {
481                CampaignFleetAPI fleet = getFleet();
482                if (fleet == null) return;
483                
484                primed = null;
485                startLoc = null;
486                well = null;
487        }
488        
489
490        @Override
491        public boolean isUsable() {
492                if (!super.isUsable()) return false;
493                return super.isUsable() && 
494                                        getFleet() != null &&
495                                        getFleet().isInHyperspace() &&
496                                        findGravityWell() != null;
497                                        //findGravityWell() != null || Misc.isInsideSlipstream(getFleet()));
498                                        //(getFleet().isAIMode() || computeFuelCost(Misc.isInsideSlipstream(getFleet())) <= getFleet().getCargo().getFuel());
499        }
500
501        
502        @Override
503        public void addInitialDescription(TooltipMakerAPI tooltip, boolean expanded) {
504                CampaignFleetAPI fleet = getFleet();
505                if (fleet == null) return;
506                
507                Color text = Misc.getTextColor();
508                Color gray = Misc.getGrayColor();
509                Color highlight = Misc.getHighlightColor();
510                Color bad = Misc.getNegativeHighlightColor();
511                
512                float pad = 10f;
513                
514                
515                String extra = "";
516                if (REQUIRE_GIANT_STARS_OR_STRONGER) {
517                        extra = "and availability ";
518                }
519                        
520                        
521                // some of the other factors: it has "dwarf" or "giant" in the name
522                tooltip.addPara("Overload and modulate the fleet's drive field "
523                                + "to induce an extremely powerful, "
524                                + "short-duration slipstream flowing away from the nearest gravity well, its strength " + extra
525                                + "based on the stellar object's mass, density, and some other poorly-understood factors. "
526                                + "Largely ineffective inside abyssal hyperspace.", pad);
527                
528//              tooltip.addPara("A stronger surge can allow the fleet to rapidly travel up to %s light-years. "
529//                              + "Attempting to move slowly "
530//                              + "during the transit will decelerate the fleet quickly.", pad, highlight, 
531//                              "10", "move slowly"); 
532
533                FactionAPI player = Global.getSector().getFaction(Factions.PLAYER);
534                Color starColor = Misc.getBasePlayerColor();
535                float tw = getTooltipWidth();
536                float strW = 150f;
537                if (Global.CODEX_TOOLTIP_MODE) {
538                        tw = 500f;
539                        strW = 220f;
540                }
541                tooltip.beginTable(player, 20f, "Stellar object type", tw - strW, "Surge strength", strW);
542                if (REQUIRE_GIANT_STARS_OR_STRONGER) {
543                        tooltip.addRow(Alignment.LMID, starColor, "Black holes, neutron stars",
544                                                   Alignment.MID, highlight, "Extreme");
545                        tooltip.addRow(Alignment.LMID, starColor, "Supergiant stars",
546                                        Alignment.MID, highlight, "High");
547                        tooltip.addRow(Alignment.LMID, starColor, "Giant stars",
548                                        Alignment.MID, highlight, "Average");
549                        tooltip.addRow(Alignment.LMID, starColor, "Smaller stars / stellar objects",
550                                        Alignment.MID, highlight, "---");
551                } else {
552                        tooltip.addRow(Alignment.LMID, starColor, "Black holes, neutron stars",
553                                           Alignment.MID, highlight, "Extreme");
554                        tooltip.addRow(Alignment.LMID, starColor, "Supergiant stars",
555                                        Alignment.MID, highlight, "Very high");
556                        tooltip.addRow(Alignment.LMID, starColor, "Giant stars",
557                                        Alignment.MID, highlight, "High");
558                        tooltip.addRow(Alignment.LMID, starColor, "Sol-like stars",
559                                        Alignment.MID, highlight, "Average");
560                        tooltip.addRow(Alignment.LMID, starColor, "Dwarf stars",
561                                        Alignment.MID, highlight, "Low");
562                        tooltip.addRow(Alignment.LMID, starColor, "Gas giants",
563                                        Alignment.MID, highlight, "Very low");
564                }
565                
566                tooltip.addTable("", 0, pad);
567                
568                if (Global.CODEX_TOOLTIP_MODE) {
569                        float tw2 = tooltip.getWidthSoFar();
570                        float xOff = (int)((tw2 - tw) / 2f);
571                        tooltip.getPrev().getPosition().setXAlignOffset(xOff);
572                        tooltip.addSpacer(0f).getPosition().setXAlignOffset(-xOff);
573                }
574                
575                tooltip.addSpacer(5f);
576                
577                tooltip.addPara("A stronger surge can allow the fleet to rapidly travel up to %s light-years. "
578                                + "Attempting to %s "
579                                + "during the transit will decelerate the fleet quickly. Fleet sensor range is "
580                                + "reduced by %s during the transit.", pad, highlight, 
581                                "15", "move slowly",
582                                "" + (int)Math.round((1f - SENSOR_RANGE_MULT) * 100f) + "%"); 
583                
584        }
585        
586        @Override
587        public boolean addNotUsableReasonBeforeFuelCost(TooltipMakerAPI tooltip, boolean expanded) {
588                CampaignFleetAPI fleet = getFleet();
589                if (fleet == null) return false;
590                
591                Color bad = Misc.getNegativeHighlightColor();
592                
593                float pad = 10f;
594
595                if (!fleet.isInHyperspace()) {
596                        tooltip.addPara("Can only be used in hyperspace.", bad, pad);
597                        return true;
598                //} else if (findGravityWell() == null && !Misc.isInsideSlipstream(getFleet())) {
599                } else if (findGravityWell() == null) {
600                        //tooltip.addPara("Must be near a gravity well or inside a slipstream.", bad, pad);
601                        if (REQUIRE_GIANT_STARS_OR_STRONGER) {
602                                tooltip.addPara("Must be near a powerful gravity well.", bad, pad);
603                        } else {
604                                tooltip.addPara("Must be near a gravity well.", bad, pad);
605                        }
606                        return true;
607                }
608                
609                return false;
610        }
611
612        
613        public JumpPointAPI findGravityWell() {
614                CampaignFleetAPI fleet = getFleet();
615                if (fleet == null) return null;
616
617                JumpPointAPI closest = null;
618                float minDist = Float.MAX_VALUE;
619                for (SectorEntityToken curr : fleet.getContainingLocation().getJumpPoints()) {
620                        JumpPointAPI jp = (JumpPointAPI) curr;
621                        if (!jp.isStarAnchor() && !jp.isGasGiantAnchor()) continue;
622                        if (jp.getDestinationVisualEntity() == null) continue;
623                        
624                        if (REQUIRE_GIANT_STARS_OR_STRONGER) {
625                                float str = getStrengthForGravityWell(jp);
626                                float min = SLIPSURGE_STRENGTH.get(StarTypes.YELLOW);
627                                if (str <= min) continue;
628                        }
629                        
630                        float dist = Misc.getDistance(fleet, jp) - jp.getRadius();
631                        if (dist > jp.getRadius() + 150f) continue;
632                        if (dist < minDist) {
633                                closest = jp;
634                                minDist = dist;
635                        }
636                }
637                return closest;
638        }
639        
640        public float getStrengthForGravityWell(JumpPointAPI jp) {
641                return getStrengthForStellarObject(jp.getDestinationVisualEntity());
642        }
643        
644        public float getStrengthForStellarObject(SectorEntityToken object) {
645                if (!(object instanceof PlanetAPI)) return 0f;
646                PlanetAPI star = (PlanetAPI) object;
647                PlanetSpecAPI spec = star.getSpec();
648                Float val = SLIPSURGE_STRENGTH.get(spec.getPlanetType());
649                if (val != null) return val;
650                
651                if (spec.isGasGiant()) return 0f;
652                
653                // probably a mod-added star of some sort
654                // time to make some guesses based on the "poorly understood factors"
655                
656                String key = StarTypes.YELLOW;
657                
658                String name = spec.getName().toLowerCase();
659                if (name.contains("neutron") || spec.isPulsar()) {
660                        key = StarTypes.NEUTRON_STAR;
661                } else if (name.contains("dwarf")) {
662                        key = StarTypes.WHITE_DWARF;
663                } else if (name.contains("supergiant")) {
664                        key = StarTypes.BLUE_SUPERGIANT;
665                } else if (name.contains("giant")) {
666                        key = StarTypes.BLUE_GIANT;
667                } else  if (name.contains(" hole")) {
668                        key = StarTypes.BLACK_HOLE;
669                } else if (name.contains("brown")) {
670                        key = StarTypes.BROWN_DWARF;
671                }
672                
673                val = SLIPSURGE_STRENGTH.get(key);
674                if (val != null) return val;
675                return 0.4f;
676        }
677        
678        
679}
680
681
682
683
684