001package com.fs.starfarer.api.impl.campaign.velfield;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.Iterator;
006import java.util.LinkedHashMap;
007import java.util.LinkedHashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Random;
011import java.util.Set;
012
013import org.lwjgl.util.vector.Vector2f;
014
015import com.fs.starfarer.api.EveryFrameScript;
016import com.fs.starfarer.api.Global;
017import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
018import com.fs.starfarer.api.campaign.JumpPointAPI;
019import com.fs.starfarer.api.campaign.LocationAPI;
020import com.fs.starfarer.api.campaign.NascentGravityWellAPI;
021import com.fs.starfarer.api.campaign.SectorEntityToken;
022import com.fs.starfarer.api.campaign.StarSystemAPI;
023import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
024import com.fs.starfarer.api.impl.campaign.DebugFlags;
025import com.fs.starfarer.api.impl.campaign.ids.Tags;
026import com.fs.starfarer.api.impl.campaign.ids.Terrain;
027import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceAbyssPlugin;
028import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
029import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamBuilder.StreamType;
030import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamParams2;
031import com.fs.starfarer.api.impl.campaign.velfield.SlipstreamTerrainPlugin2.SlipstreamSegment;
032import com.fs.starfarer.api.impl.campaign.world.TTBlackSite;
033import com.fs.starfarer.api.util.CollisionGridUtil;
034import com.fs.starfarer.api.util.IntervalUtil;
035import com.fs.starfarer.api.util.Misc;
036import com.fs.starfarer.api.util.WeightedRandomPicker;
037
038public class SlipstreamManager implements EveryFrameScript {
039        
040        public static int WIDTH = 21;
041        public static int HEIGHT = 11;
042        public static float MAP_WIDTH_PADDING = 8000;
043        public static float MAP_HEIGHT_PADDING = 5000;
044        /**
045         * 21x11 characters
046         * Capital letter: endpoints
047         * Lowercase letter, optional: control point for 3-point stream (if 2: cubic bezier curve instead of quadratic)
048         * Direction of flow is determined by time of cycle and relative location of endpoints
049         * X is ignored; just marks the center to make it easier to edit
050         * After a capital letter:
051         * < makes the stream go in the opposite-to-standard direction
052         * +-<single digit number> adjusts the stream's burn level; 0 means +10
053         * ~ makes the stream narrower, on average
054         * = makes it wider
055         * | makes it much straighter than usual
056         * ! priority, spawned before other streams (and thus other streams will fade when intersecting it, not this one)
057         * ^ only keep longest segment - but, from intersections with streams ONLY, not with stars/other blockers
058         * * randomize: up to +10 burn, reverse, straightness, wide/narrow 
059         * ? randomize: same as above, but no reverse and only up to +5 burn
060         */
061        public static Map<String, Float> STREAM_CONFIGS = new LinkedHashMap<String, Float>();
062        
063        static {
064                loadConfigs();
065        }
066        
067        public static void loadConfigs() {
068                // begin "finished"
069                STREAM_CONFIGS.put(
070                                "aD?                 B" +
071                                "C?                 EA" +
072                                "                  c  " +
073                                "                     " +
074                                "                     " +
075                                "          X          " +
076                                "                     " +
077                                "                     " +
078                                "  d                  " +
079                                "A!E?                C" +
080                                "B!                 Db"
081                                , 10f);
082                STREAM_CONFIGS.put(
083                                "Ba   C         D   aB" +
084                                "                     " +
085                                "                     " +
086                                "                     " +
087                                "                     " +
088                                "         CXD         " +
089                                "          e          " +
090                                "                     " +
091                                "                     " +
092                                "                     " +
093                                "A!bE              EbA"
094                , 10f);         
095                STREAM_CONFIGS.put(
096                                "A! D?            F? A" +
097                                "                     " +
098                                "                     " +
099                                "a                   a" +
100                                "                     " +
101                                "C!   DE   X   FG    C" +
102                                "                     " +
103                                "b                   b" +
104                                "                     " +
105                                "                     " +
106                                "B! E?            G? B"
107                                , 10f);         
108                STREAM_CONFIGS.put(
109                                "C!   D!            cA" +
110                                "                     " +
111                                "                     " +
112                                "                     " +
113                                "A?                 aa" +
114                                "          X         c" +
115                                "B?                 bb" +
116                                "                     " +
117                                "                     " +
118                                "d             C      " +
119                                "d                  DB"
120                                , 10f);         
121                STREAM_CONFIGS.put(
122                                "B^!     I^?   H^? a G" +
123                                "                     " +
124                                "   i         h       " +
125                                "                    g" +
126                                "    A!|  H^G^?       " +
127                                "   C^<I   X          " +
128                                "    D^    E^?F^?    F" +
129                                "c  AB                " +
130                                "                     " +
131                                "b    d               " +
132                                "C?          D?e   a E"
133                                , 10f);
134                STREAM_CONFIGS.put(
135                                "       b  a          " +
136                                "A|~+0               A" +
137                                "                     " +
138                                "B|~+0               B" +
139                                "      c          a   " +
140                                "          X          " +
141                                "                     " +
142                                "C|!+0 d             C" +
143                                "               b     " +
144                                "D|~+0               D" +
145                                "          d   c      "
146                                , 10f);
147                
148                // highly randomized and mirrored
149                STREAM_CONFIGS.put(
150                                "A*       B*         G" +
151                                "    H*         g     " +
152                                "           h      H  " +
153                                "    b                " +
154                                " C*     A        G*  " +
155                                "          X         F" +
156                                "     c       D       " +
157                                "  B     E*      e    " +
158                                "          d       f  " +
159                                "                     " +
160                                "D*     C    F*      E"
161                                , 5f);
162                String prev = (String) STREAM_CONFIGS.keySet().toArray()[STREAM_CONFIGS.size() - 1];
163                STREAM_CONFIGS.put(mirrorHorz(prev), 5f);
164                STREAM_CONFIGS.put(mirrorVert(prev), 5f);
165                STREAM_CONFIGS.put(mirrorHorz(mirrorVert(prev)), 5f);
166                
167                // end "finished"
168                
169                
170                // maybe?
171//              STREAM_CONFIGS.put(
172//                              "A|!~+0C   D    FE   A" +
173//                              "c    d              B" +
174//                              "                     " +
175//                              "                     " +
176//                              "                     " +
177//                              "        f X      e   " +
178//                              "                     " +
179//                              "                     " +
180//                              "                     " +
181//                              "B|!<-5D^             " +
182//                              "  C^       F^E<^   bb"
183//                              , 10f);         
184//              STREAM_CONFIGS.put( // also maybe, very similar to another one though
185//                              "A        C D        A" +
186//                              "  c               d  " +
187//                              "                     " +
188//                              "                     " +
189//                              "          e          " +
190//                              "          X          " +
191//                              "          b          " +
192//                              "E         a         E" +
193//                              "                     " +
194//                              "                     " +
195//                              "B  C            D   B"
196//                              , 10f);
197
198                // blank
199//              STREAM_CONFIGS.put(
200//                              "                     " +
201//                              "                     " +
202//                              "                     " +
203//                              "                     " +
204//                              "                     " +
205//                              "          X          " +
206//                              "                     " +
207//                              "                     " +
208//                              "                     " +
209//                              "                     " +
210//                              "                     "
211//                              , 10f);
212                
213        }
214        
215        public static void mirrorPrevVert(float weight) {
216                String prev = (String) STREAM_CONFIGS.keySet().toArray()[STREAM_CONFIGS.size() - 1];
217                STREAM_CONFIGS.put(mirrorVert(prev), weight);
218        }
219        public static String mirrorVert(String in) {
220                String out = "";
221                for (int j = 0; j < HEIGHT; j++) {
222                        out = in.substring(j * WIDTH, (j + 1) * WIDTH) + out;
223                }
224                return out;
225        }
226        public static void mirrorPrevHorz(float weight) {
227                String prev = (String) STREAM_CONFIGS.keySet().toArray()[STREAM_CONFIGS.size() - 1];
228                STREAM_CONFIGS.put(mirrorHorz(prev), weight);
229        }
230        public static String mirrorHorz(String in) {
231                String out = "";
232                for (int j = 0; j < HEIGHT; j++) {
233                        out = out + new StringBuilder(in.substring(j * WIDTH, (j + 1) * WIDTH)).reverse();
234                }
235                return out;
236        }
237        
238        public static void validateConfigs() {
239                // make sure they all parse
240                for (String key : STREAM_CONFIGS.keySet()) {
241                        if (key.length() != WIDTH * HEIGHT) {
242                                throw new RuntimeException("Length of slipstream config not WIDTHxHEIGHT [" + key + "]");
243                        }
244                        try {
245                                new StreamConfig(key, null);
246                        } catch (Throwable t) {
247                                throw new RuntimeException("Error parsing slipstream config [" + key + "]", t);
248                        }
249                }
250                
251        }
252        
253        public static class StreamData {
254                public String id;
255                public int p0x = -1;
256                public int p0y = -1;
257                public int p1x = -1;
258                public int p1y = -1;
259                public int controlX = -1;
260                public int controlY = -1;
261                public int control2X = -1;
262                public int control2Y = -1;
263                public int burnMod = 0;
264                public boolean reverse = false;
265                public StreamType type = StreamType.NORMAL;
266                public boolean wasUsed = false;
267                public boolean straight = false;
268                public boolean priority = false;
269                public boolean onlyKeepLongestSegment = false;
270                public boolean randomize = false;
271                public boolean minorRandomize = false;
272                
273                public StreamData(String id) {
274                        this.id = id;
275                }
276                public Vector2f generateP0(Random random) {
277                        if (p0x < 0) return null;
278                        return generate(p0x, p0y, random);
279                }
280                public Vector2f generateP1(Random random) {
281                        if (p1x < 0) return null;
282                        return generate(p1x, p1y, random);
283                }
284                public Vector2f generateControl(Random random) {
285                        if (controlX < 0) return null;
286                        return generate(controlX, controlY, random);
287                }
288                public Vector2f generateControl2(Random random) {
289                        if (control2X < 0) return null;
290                        return generate(control2X, control2Y, random);
291                }
292                public Vector2f generate(int cellX, int cellY, Random random) {
293                        float sw = Global.getSettings().getFloat("sectorWidth") - MAP_WIDTH_PADDING;
294                        float sh = Global.getSettings().getFloat("sectorHeight") - MAP_HEIGHT_PADDING;
295                        float cellWidth = sw / WIDTH;
296                        float cellHeight = sh / HEIGHT;
297                        Vector2f p = new Vector2f();
298//                      if (cellX == 20) {
299//                              System.out.println("efwefwef");
300//                      }
301                        float minX = -sw/2f + cellWidth * cellX;
302                        float maxX = -sw/2f + cellWidth * (cellX + 1);
303                        float minY = -sh/2f + cellHeight * cellY;
304                        float maxY = -sh/2f + cellHeight * (cellY + 1);
305                        
306                        p.x = minX + (maxX - minX) * random.nextFloat();
307                        p.y = minY + (maxY - minY) * random.nextFloat();
308//                      p.x = minX + (maxX - minX) * 0f;
309//                      p.y = minY + (maxY - minY) * 0f;
310//                      p.x = minX + (maxX - minX) * 1f;
311//                      p.y = minY + (maxY - minY) * 1f;
312                        
313                        return p;
314                }
315        }
316        
317        public static class StreamConfig {
318                
319                public List<StreamData> streams = new ArrayList<StreamData>();
320                public String data;
321                public StreamConfig(String data, Random random) {
322                        this.data = data;
323                        Set<String> ids = new LinkedHashSet<String>();
324                        for (int i = 0; i < data.length(); i++) {
325                                char c = data.charAt(i);
326                                if (c == 'X') continue;
327                                if (Character.isUpperCase(c)) {
328                                        ids.add("" + c);
329                                }
330                        }
331                        
332                        for (String id : ids) {
333                                StreamData curr = new StreamData(id);
334                                for (int i = 0; i < data.length(); i++) {
335                                        char c = data.charAt(i);
336                                        if (c == 'X') continue;
337                                        
338                                        int cellX = i % WIDTH;
339                                        int cellY = HEIGHT - i / WIDTH - 1;
340                                        if (id.equals("" + c)) {
341                                                if (curr.p0x < 0) {
342                                                        curr.p0x = cellX;
343                                                        curr.p0y = cellY;
344                                                } else if (curr.p1x < 0) {
345                                                        curr.p1x = cellX;
346                                                        curr.p1y = cellY;
347                                                }
348                                        } else if (id.toLowerCase().equals("" + c)) {
349                                                if (curr.controlX < 0) {
350                                                        curr.controlX = cellX;
351                                                        curr.controlY = cellY;
352                                                } else {
353                                                        curr.control2X = cellX;
354                                                        curr.control2Y = cellY;
355                                                }
356                                        }
357                                        
358                                        if (id.equals("" + c)) {
359                                                for (int j = i + 1; j < data.length() && j < i + 10; j++) {
360                                                        char c2 = data.charAt(j);
361                                                        if (c2 == ' ' || Character.isAlphabetic(c2)) break;
362                                                        if (c2 == '<') {
363                                                                curr.reverse = true;
364                                                        } else if (c2 == '~') {
365                                                                curr.type = StreamType.NARROW;
366                                                        } else if (c2 == '=') {
367                                                                curr.type = StreamType.WIDE;
368                                                        } else if (c2 == '-') {
369                                                                int burnMod = data.charAt(j + 1) - '0';
370                                                                if (burnMod == 0) burnMod = 10;
371                                                                curr.burnMod = -1 * burnMod;
372                                                        } else if (c2 == '+') {
373                                                                int burnMod = data.charAt(j + 1) - '0';
374                                                                if (burnMod == 0) burnMod = 10;
375                                                                curr.burnMod = burnMod;
376                                                        } else if (c2 == '|') {
377                                                                curr.straight = true;
378                                                        } else if (c2 == '!') {
379                                                                curr.priority = true;
380                                                        } else if (c2 == '^') {
381                                                                curr.onlyKeepLongestSegment = true;
382                                                        } else if (c2 == '*') {
383                                                                curr.randomize = true;
384                                                        } else if (c2 == '?') {
385                                                                curr.minorRandomize = true;
386                                                        }
387                                                }
388                                        }
389                                }
390                                if (curr.p0x >= 0 || curr.p1x >= 0) {
391                                        if ((curr.randomize || curr.minorRandomize) && random != null) {
392                                                if (!curr.reverse && !curr.minorRandomize) {
393                                                        curr.reverse = random.nextFloat() < 0.5f;
394                                                }
395                                                if (!curr.straight) {
396                                                        curr.straight = random.nextFloat() < 0.25f;
397                                                }
398                                                if (curr.burnMod == 0) {
399                                                        //curr.burnMod = (random.nextBoolean() ? 1 : -1) * random.nextInt(6);
400                                                        if (curr.minorRandomize) {
401                                                                curr.burnMod = random.nextInt(6);
402                                                        } else {
403                                                                curr.burnMod = random.nextInt(11);
404                                                        }
405                                                }
406                                                if (curr.type == StreamType.NORMAL) {
407                                                        float r = random.nextFloat();
408                                                        if (r < 0.2f) {
409                                                                curr.type = StreamType.NARROW;
410                                                        } else if (r < 0.4f) {
411                                                                curr.type = StreamType.WIDE;
412                                                        }
413                                                }
414                                        }
415                                        streams.add(curr);
416                                }
417                        }
418                }
419        }
420        
421
422        public static class AddedStream {
423                public CampaignTerrainAPI terrain;
424                public SlipstreamTerrainPlugin2 plugin;
425                public Vector2f from;
426                public Vector2f to;
427                public Vector2f control;
428                public long timestamp;
429                public AddedStream(SlipstreamTerrainPlugin2 plugin) {
430                        this.plugin = plugin;
431                        terrain = (CampaignTerrainAPI) plugin.getEntity();
432                        from = new Vector2f(plugin.getSegments().get(0).loc);
433                        to = new Vector2f(plugin.getSegments().get(plugin.getSegments().size() - 1).loc);
434                        timestamp = Global.getSector().getClock().getTimestamp();
435                }
436        }
437        
438        
439        protected transient CollisionGridUtil grid;
440        
441        protected IntervalUtil interval = new IntervalUtil(1f, 2f);
442        protected Random random = new Random();
443        protected int prevMonth = -1;
444        protected int desiredNumStreams = 0;
445        protected List<AddedStream> active = new ArrayList<SlipstreamManager.AddedStream>();
446        protected StreamConfig config;
447        protected String prevConfig;
448        
449        protected Object readResolve() {
450                if (active == null) {
451                        active = new ArrayList<SlipstreamManager.AddedStream>();
452                }
453                return this;
454        }
455        
456        
457        public void advance(float amount) {
458                //if (true) return;
459                if (DebugFlags.SLIPSTREAM_DEBUG) {
460                        random = Misc.random;
461                }
462                
463//              int total = 0;
464//              for (AddedStream curr : active) {
465//                      total += curr.plugin.getSegments().size();
466//              }
467//              System.out.println("TOTAL SEGMENTS: " + total + ", streams: " + active.size());
468                
469                float days = Global.getSector().getClock().convertToDays(amount);
470                if (DebugFlags.SLIPSTREAM_DEBUG) {
471                        days *= 100f;
472                }
473                interval.advance(days);
474                //DebugFlags.SLIPSTREAM_DEBUG = true;
475                if (interval.intervalElapsed()) {
476                        Iterator<AddedStream> iter = active.iterator();
477                        while (iter.hasNext()) {
478                                AddedStream curr = iter.next();
479                                if (!curr.terrain.isAlive()) {
480                                        iter.remove();
481                                }
482                        }
483                        
484                        int month = Global.getSector().getClock().getMonth();
485                        //month = 6;
486                        if (month == 6 || month == 12) {
487                                for (AddedStream curr : active) {
488                                        if (curr.plugin.isDespawning()) continue;
489                                        float despawnDelay = 0f + random.nextFloat() * 20f;
490                                        float timeMinusDelay = 27f - despawnDelay;
491                                        float despawnDays = timeMinusDelay * 0.5f + random.nextFloat() * timeMinusDelay * 0.5f;
492                                        curr.plugin.despawn(despawnDelay, despawnDays, random);
493                                }
494                                if (config != null) {
495                                        prevConfig = config.data;
496                                }
497                                config = null;
498                        } else if (month != 6 && month != 12) {
499                                if (config == null) {
500                                        if (DebugFlags.SLIPSTREAM_DEBUG) {
501                                                STREAM_CONFIGS.clear();
502                                                loadConfigs();
503                                        }
504                                        
505                                        WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random);
506                                        for (String key : STREAM_CONFIGS.keySet()) {
507                                                if (prevConfig != null && prevConfig.equals(key) && STREAM_CONFIGS.size() > 1) {
508                                                        continue;
509                                                }
510                                                picker.add(key, STREAM_CONFIGS.get(key));
511                                        }
512                                        if (picker.isEmpty() && prevConfig != null) {
513                                                picker.add(prevConfig, 1f);
514                                        }
515                                        ListenerUtil.updateSlipstreamConfig(prevConfig, picker, this);
516                                        String data = picker.pick();
517                                        if (data != null) {
518                                                config = new StreamConfig(data, random);
519                                        }
520                                }
521                                addStream(month);
522                        }
523                        
524                        
525                        prevMonth = month;
526                }
527                grid = null;
528        }
529        
530        
531        public void addStream(int month) {
532                if (config == null) return;
533                
534                //random = new Random();
535//              long seed = 23895464576452L + 4384357483229348234L + 4343253L;
536//              seed = 1181783497276652981L ^ seed;
537//              Random random = new Random(seed);
538                
539                WeightedRandomPicker<StreamData> picker = new WeightedRandomPicker<StreamData>(random);
540                for (StreamData data : config.streams) {
541                        if (data.wasUsed) continue;
542                        if (!data.priority) continue;
543                        picker.add(data);
544                }
545                if (picker.isEmpty()) {
546                        // add non-priority if all priority ones are already used
547                        for (StreamData data : config.streams) {
548                                if (data.wasUsed) continue;
549                                picker.add(data);
550                        }
551                }
552                
553                StreamData data = picker.pick();
554                if (data == null) return;
555                
556                SlipstreamParams2 params = new SlipstreamParams2();
557                params.burnLevel = 30 + data.burnMod;
558                params.minSpeed = Misc.getSpeedForBurnLevel(params.burnLevel - 5);
559                params.maxSpeed = Misc.getSpeedForBurnLevel(params.burnLevel + 5);
560                params.lineLengthFractionOfSpeed = 0.25f * Math.max(0.25f, Math.min(1f, 30f / (float) params.burnLevel));
561                
562                
563                Vector2f from = data.generateP0(random);
564                Vector2f to = data.generateP1(random);
565                Vector2f control = data.generateControl(random);
566                Vector2f control2 = data.generateControl2(random);
567                if (from == null || to == null) return;
568
569                // default direction is east in first half of the cycle, west in the second half
570                // months 6 and 12 don't really matter since the slipstreams despawn during those
571                if (month == 12 || month < 6) {
572                //if (!(month == 12 || month < 6)) {
573                        if ((!data.reverse && from.x > to.x) || (data.reverse && from.x < to.x)) {
574                                Vector2f temp = to;
575                                to = from;
576                                from = temp;
577                        }
578                } else {
579                        if ((!data.reverse && from.x < to.x) || (data.reverse && from.x > to.x)) {
580                                Vector2f temp = to;
581                                to = from;
582                                from = temp;
583                        }
584                }
585                
586                
587                LocationAPI hyperspace = Global.getSector().getHyperspace();
588                CampaignTerrainAPI slipstream = (CampaignTerrainAPI) hyperspace.addTerrain(Terrain.SLIPSTREAM, params);
589                
590                slipstream.setLocation(from.x, from.y);
591                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) slipstream.getPlugin();
592                SlipstreamBuilder builder = new SlipstreamBuilder(slipstream.getLocation(), plugin, data.type, random);
593                
594                if (data.straight) {
595                        float mult = 0.25f;
596                        builder.setMaxAngleVariance(builder.getMaxAngleVariance() * mult);
597                        builder.setMaxAngleVarianceForCurve(builder.getMaxAngleVarianceForCurve() * mult);
598                }
599                
600                if (control2 != null) {
601                        float dist1 = Misc.getDistance(from, control);
602                        float dist2 = Misc.getDistance(from, control2);
603                        if (dist2 < dist1) {
604                                Vector2f temp = control2;
605                                control2 = control;
606                                control = temp;
607                        }
608                        builder.buildToDestination(control, control2, to);
609                } else if (control != null) {
610                        builder.buildToDestination(control, to);
611                } else {
612                        builder.buildToDestination(to);
613                }
614                
615                checkIntersectionsAndFadeSections(plugin, data.onlyKeepLongestSegment);
616                
617                if (plugin.getSegments().size() < 3) {
618                        hyperspace.removeEntity(slipstream);
619                        return;
620                }
621                
622                float spawnDays = 1f + 2f * random.nextFloat();
623                if (DebugFlags.SLIPSTREAM_DEBUG) {
624                        spawnDays = 0f;
625                }
626                plugin.spawn(spawnDays, random);
627                
628                plugin.recomputeEncounterPoints();
629                
630                AddedStream added = new AddedStream(plugin);
631                active.add(added);
632                data.wasUsed = true;
633        }
634        
635        public void checkIntersectionsAndFadeSections(SlipstreamTerrainPlugin2 plugin, boolean onlyKeepLongestBetweenStreams) {
636                updateGrid();
637                
638                plugin.recomputeIfNeeded();
639                
640                List<SlipstreamSegment> segments = plugin.getSegments();
641                
642                Set<SlipstreamSegment> otherStreamCuts = new HashSet<SlipstreamSegment>();
643                
644                for (SlipstreamSegment curr : segments) {
645                        
646                        Iterator<Object> iter = grid.getCheckIterator(curr.loc, curr.width / 2f, curr.width / 2f);
647                        while (iter.hasNext()) {
648                                Object obj = iter.next();
649                                if (obj instanceof JumpPointAPI) {
650                                        JumpPointAPI jp = (JumpPointAPI) obj;
651                                        Vector2f loc = jp.getLocation();
652                                        float radius = jp.getRadius();
653                                        if (jp.getOrbitFocus() != null) {
654                                                loc = jp.getOrbitFocus().getLocation();
655                                                radius = Misc.getDistance(jp.getOrbitFocus(), jp) + jp.getRadius();
656                                        }
657                                        
658                                        Vector2f diff = Vector2f.sub(loc, curr.loc, new Vector2f());
659                                        
660                                        float distPerp = Math.abs(Vector2f.dot(curr.normal, diff));
661                                        float distAlong = Math.abs(Vector2f.dot(curr.dir, diff));
662                                        
663                                        distPerp -= radius;
664                                        distAlong -= radius;
665                                        
666                                        float minDistAlong = Math.max(curr.lengthToNext, curr.lengthToPrev);
667                                        float fadeDistAlong = 500f + minDistAlong;
668                                        if (distPerp < curr.width / 2f && 
669                                                        distAlong < fadeDistAlong) {
670                                                if (distAlong < minDistAlong) {
671                                                        curr.fader.forceOut();
672                                                        curr.bMult = 0f;
673                                                } else {
674                                                        curr.bMult = Math.min(curr.bMult, 
675                                                                                                 (distAlong - minDistAlong) / (fadeDistAlong - minDistAlong));
676                                                }
677                                        }
678                                } else if (obj instanceof CampaignTerrainAPI) {
679                                        CampaignTerrainAPI terrain = (CampaignTerrainAPI) obj;
680                                        SlipstreamTerrainPlugin2 otherPlugin = (SlipstreamTerrainPlugin2) terrain.getPlugin();
681                                        if (otherPlugin == plugin) continue;
682                                        
683                                        for (SlipstreamSegment other : otherPlugin.getSegmentsNear(curr.loc, curr.width / 2f)) {
684                                                //if (other.fader.getBrightness() == 0 || other.bMult <= 0) continue;
685                                                if (other.bMult <= 0) continue;
686                                                
687                                                float dist = Misc.getDistance(curr.loc, other.loc);
688                                                float minDist = curr.width / 2f + other.width / 2f;
689                                                float fadeDist = minDist + 500f;
690                                                if (dist < fadeDist) {
691                                                        if (dist < minDist) {
692                                                                curr.fader.forceOut();
693                                                                curr.bMult = 0f;
694                                                                otherStreamCuts.add(curr);
695                                                        } else {
696                                                                curr.bMult = Math.min(curr.bMult, 
697                                                                                                         (dist - minDist) / (fadeDist - minDist));
698                                                        }
699                                                }
700                                        }
701                                } else if (obj instanceof CustomStreamBlocker) {
702                                        CustomStreamBlocker blocker = (CustomStreamBlocker) obj;
703                                        Vector2f loc = blocker.loc;
704                                        float radius = blocker.radius;
705                                        
706                                        Vector2f diff = Vector2f.sub(loc, curr.loc, new Vector2f());
707                                        float distPerp = Math.abs(Vector2f.dot(curr.normal, diff));
708                                        float distAlong = Math.abs(Vector2f.dot(curr.dir, diff));
709                                        
710                                        distPerp -= radius;
711                                        distAlong -= radius;
712                                        
713                                        float minDistAlong = Math.max(curr.lengthToNext, curr.lengthToPrev);
714                                        float fadeDistAlong = 500f + minDistAlong;
715                                        if (distPerp < curr.width / 2f && 
716                                                        distAlong < fadeDistAlong) {
717                                                if (distAlong < minDistAlong) {
718                                                        curr.fader.forceOut();
719                                                        curr.bMult = 0f;
720                                                } else {
721                                                        curr.bMult = Math.min(curr.bMult, 
722                                                                                                 (distAlong - minDistAlong) / (fadeDistAlong - minDistAlong));
723                                                }
724                                        }
725                                } else if (obj instanceof AbyssStreamBlocker) {
726                                        AbyssStreamBlocker abyss = (AbyssStreamBlocker) obj;
727                                        if (abyss.containsPoint(curr.loc)) {
728                                                curr.fader.forceOut();
729                                                curr.bMult = 0f;
730                                        }
731                                }
732                        }
733                }
734
735                fadeOutSectionsShorterThan(segments, 5000f);
736                
737                // unrelated to the above - for proximity-to-something fades that
738                // were unnecessary because there wasn't an actual intersection with that something
739                removedFadesThatDoNotReachZero(segments);
740                
741                
742                
743                if (onlyKeepLongestBetweenStreams) {
744                        List<SlipstreamSegment> longest = new ArrayList<SlipstreamTerrainPlugin2.SlipstreamSegment>();
745                        
746                        List<SlipstreamSegment> currList = new ArrayList<SlipstreamTerrainPlugin2.SlipstreamSegment>();
747                        
748                        for (SlipstreamSegment curr : segments) {
749                                if (otherStreamCuts.contains(curr)) {
750                                        if (currList.size() > longest.size()) {
751                                                longest = currList;
752                                        }
753                                        currList = new ArrayList<SlipstreamSegment>();
754                                }
755                                if (curr.bMult > 0f) {
756                                        currList.add(curr);
757                                }
758                        }
759                        if (currList.size() > longest.size()) {
760                                longest = currList;
761                        }
762                        for (SlipstreamSegment curr : segments) {
763                                if (!longest.contains(curr)) {
764                                        curr.bMult = 0f;
765                                        curr.fader.forceOut();
766                                }
767                        }
768                        
769                }
770        }
771        
772        
773        public static void fadeOutSectionsShorterThan(List<SlipstreamSegment> segments, float minLength) {
774                float minRunLength = minLength;
775                List<SlipstreamSegment> currRun = new ArrayList<SlipstreamSegment>();
776                for (SlipstreamSegment curr : segments) {
777                        if (curr.bMult <= 0f) {
778                                float runLength = 0f;
779                                for (SlipstreamSegment inRun : currRun) {
780                                        runLength += inRun.lengthToNext; // counts one more than it should; meh 
781                                }
782                                if (runLength < minRunLength) {
783                                        for (SlipstreamSegment inRun : currRun) {
784                                                inRun.fader.forceOut();
785                                                inRun.bMult = 0f;
786                                        }
787                                }
788                                currRun.clear();
789                        } else {
790                                currRun.add(curr);
791                        }
792                }
793        }
794        
795        public static void removedFadesThatDoNotReachZero(List<SlipstreamSegment> segments) {
796                List<SlipstreamSegment> currRun = new ArrayList<SlipstreamSegment>();
797                boolean currRunReachedZero = false;
798                for (SlipstreamSegment curr : segments) {
799                        if (curr.bMult < 1f) {
800                                currRun.add(curr);
801                                if (curr.bMult <= 0f || (curr.fader.getBrightness() == 0 && !curr.fader.isFadingIn())) {
802                                        currRunReachedZero = true;
803                                }
804                        } else {
805                                if (!currRunReachedZero) {
806                                        for (SlipstreamSegment inRun : currRun) {
807                                                inRun.fader.fadeIn();
808                                                inRun.bMult = 1f;
809                                        }
810                                }
811                                currRun.clear();
812                                currRunReachedZero = false;
813                        } 
814                }
815        }
816        
817        
818        
819        public static class CustomStreamRevealer extends CustomStreamBlocker {
820                public CustomStreamRevealer(Vector2f loc, float radius) {
821                        super(loc, radius);
822                }
823        }
824        
825        public static class CustomStreamBlocker {
826                public Vector2f loc;
827                public float radius;
828                public CustomStreamBlocker(Vector2f loc, float radius) {
829                        this.loc = new Vector2f(loc);
830                        this.radius = radius;
831                }
832        }
833        public static class AbyssStreamBlocker {
834                public HyperspaceAbyssPlugin plugin;
835                public AbyssStreamBlocker() {
836                        CampaignTerrainAPI terrain = Misc.getHyperspaceTerrain();
837                        if (terrain != null) {
838                                this.plugin = ((HyperspaceTerrainPlugin) terrain.getPlugin()).getAbyssPlugin();
839                        }
840                }
841                public boolean containsPoint(Vector2f loc) {
842                        if (plugin == null) return false;
843                        return plugin.getAbyssalDepth(loc) > 0;
844                }
845        }
846        
847        public CollisionGridUtil getGrid() {
848                return grid;
849        }
850
851        public void updateGrid() {
852                if (grid != null) return;
853                
854                float sw = Global.getSettings().getFloat("sectorWidth");
855                float sh = Global.getSettings().getFloat("sectorHeight");
856                float minCellSize = 12000f;
857                float cellSize = Math.max(minCellSize, sw * 0.05f);
858                
859                grid = new CollisionGridUtil(-sw/2f, sw/2f, -sh/2f, sh/2f, cellSize);
860                
861                LocationAPI hyperspace = Global.getSector().getHyperspace();
862                for (SectorEntityToken jp : hyperspace.getJumpPoints()) {
863                        float size = jp.getRadius() * 2f + 100f;
864                        grid.addObject(jp, jp.getLocation(), size * 2f, size * 2f);
865                }
866                
867//              for (NascentGravityWellAPI well : hyperspace.getGravityWells()) {
868//                      float size = 1000f + well.getRadius();
869//                      CustomStreamBlocker blocker = new CustomStreamBlocker(well.getLocation(), size);
870//                      grid.addObject(blocker, well.getLocation(), size * 2f, size * 2f);
871//              }
872                Object alphaSiteWell = Global.getSector().getMemoryWithoutUpdate().get(TTBlackSite.NASCENT_WELL_KEY);
873                if (alphaSiteWell instanceof NascentGravityWellAPI) {
874                        NascentGravityWellAPI well = (NascentGravityWellAPI) alphaSiteWell;
875                        float size = 1000f + well.getRadius();
876                        CustomStreamBlocker blocker = new CustomStreamBlocker(well.getLocation(), size);
877                        grid.addObject(blocker, well.getLocation(), size * 2f, size * 2f);
878                }
879                
880                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
881                        if (system.hasTag(Tags.THEME_CORE)) {
882                                Vector2f loc = system.getLocation();
883                                float size = 4000f;
884                                CustomStreamBlocker blocker = new CustomStreamBlocker(loc, size);
885                                grid.addObject(blocker, loc, size * 2f, size * 2f);
886                        }
887                }
888                
889                {
890                        float w = Global.getSettings().getFloat("sectorWidth");
891                        float h = Global.getSettings().getFloat("sectorHeight");
892//                      Vector2f loc = new Vector2f(-w/2f, -h/2f);
893//                      float size = 26000;
894//                      CustomStreamBlocker orionPersusAbyssBlocker = new CustomStreamBlocker(loc, size);
895//                      grid.addObject(orionPersusAbyssBlocker, loc, size * 2f, size * 2f);
896                        AbyssStreamBlocker orionPersusAbyssBlocker = new AbyssStreamBlocker();
897                        grid.addObject(orionPersusAbyssBlocker, new Vector2f(), w, h);
898                }
899                
900//              for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
901//                      if (market.isHidden()) continue;
902//                      //if (market.hasIndustry(Industries.SPACEPORT)) continue;
903//                      Industry spaceport = market.getIndustry(Industries.SPACEPORT);
904//                      if (spaceport == null || !spaceport.isFunctional()) continue;
905//                      
906//                      Vector2f loc = market.getLocationInHyperspace();
907//                      float size = 5000f;
908//                      CustomStreamRevealer revealer = new CustomStreamRevealer(loc, size);
909//                      grid.addObject(revealer, loc, size * 2f, size * 2f);
910//              }
911                
912                
913                int segmentsToSkip = (int) ((cellSize - 2000) / 400f);
914                float checkSize = minCellSize - 2000f;
915                
916                for (CampaignTerrainAPI curr : hyperspace.getTerrainCopy()) {
917                        if (curr.getPlugin() instanceof SlipstreamTerrainPlugin2) {
918                                SlipstreamTerrainPlugin2 plugin = (SlipstreamTerrainPlugin2) curr.getPlugin();
919                                List<SlipstreamSegment> segments = plugin.getSegments();
920                                List<SlipstreamSegment> check = new ArrayList<SlipstreamTerrainPlugin2.SlipstreamSegment>();
921                                for (int i = 0; i < segments.size(); i += segmentsToSkip) {
922                                        check.add(segments.get(i));
923                                }
924                                if (!check.contains(segments.get(segments.size() - 1))) {
925                                        check.add(segments.get(segments.size() - 1));
926                                }
927                                
928                                for (SlipstreamSegment seg : check) {
929                                        grid.addObject(curr, seg.loc, checkSize, checkSize);
930                                }
931                        }
932                }
933                
934                ListenerUtil.updateSlipstreamBlockers(grid, this);
935        }
936        
937        
938        public boolean isDone() {
939                return false;
940        }
941
942        public boolean runWhilePaused() {
943                return false;
944        }
945        
946        
947        
948}