001package com.fs.starfarer.api.impl.campaign.terrain;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.EnumSet;
006import java.util.HashMap;
007import java.util.Iterator;
008import java.util.LinkedHashMap;
009import java.util.List;
010import java.util.Map;
011
012import org.lwjgl.util.vector.Vector2f;
013
014import com.fs.starfarer.api.Global;
015import com.fs.starfarer.api.campaign.CampaignEngineLayers;
016import com.fs.starfarer.api.campaign.CampaignFleetAPI;
017import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
018import com.fs.starfarer.api.campaign.LocationAPI;
019import com.fs.starfarer.api.campaign.SectorEntityToken;
020import com.fs.starfarer.api.combat.ViewportAPI;
021import com.fs.starfarer.api.ui.TooltipMakerAPI;
022import com.fs.starfarer.api.util.Misc;
023import com.fs.starfarer.api.util.TimeoutTracker;
024
025public class SlipstreamTerrainPlugin extends BaseTerrain {
026        
027        public static final String LOCATION_SLIPSTREAM_KEY = "local_slipstream_terrain";
028        
029        public static final float MIN_POINT_DIST = 20f;
030        public static final float MAX_POINT_DIST = 250f;
031        
032        public static final float WIDTH_GROWTH_PER_DAY = 200f;
033        
034        
035        public static CampaignTerrainAPI getSlipstream(LocationAPI location) {
036                if (location == null) return null;
037                return (CampaignTerrainAPI) location.getPersistentData().get(LOCATION_SLIPSTREAM_KEY);
038        }
039        
040        public static SlipstreamTerrainPlugin getSlipstreamPlugin(LocationAPI location) {
041                CampaignTerrainAPI slipstream = getSlipstream(location);
042                if (slipstream != null) {
043                        return (SlipstreamTerrainPlugin)slipstream.getPlugin();
044                }
045                return null;
046        }
047        
048        public static class StreamPoint {
049                public float x, y;
050                public float daysLeft;
051                public float burn;
052                public float width;
053                public StreamPoint(float x, float y, float daysLeft, float burn, float width) {
054                        this.x = x;
055                        this.y = y;
056                        this.daysLeft = daysLeft;
057                        this.burn = burn;
058                        this.width = width;
059                }
060        }
061        
062        public static class Stream {
063                transient public float minX, minY, maxX, maxY;
064                public List<StreamPoint> points = new ArrayList<StreamPoint>();
065                //public List<StreamParticle> particles = new ArrayList<StreamParticle>();
066                public SectorEntityToken key;
067                public int particlesPerPoint;
068
069                public Stream(SectorEntityToken key, int particlesPerPoint) {
070                        this.key = key;
071                        this.particlesPerPoint = particlesPerPoint;
072                        readResolve();
073                }
074
075                Object readResolve() {
076                        minX = Float.MAX_VALUE;
077                        minY = Float.MAX_VALUE;
078                        maxX = -Float.MAX_VALUE;
079                        maxY = -Float.MAX_VALUE;
080                        for (StreamPoint p : points) {
081                                updateMinMax(p);
082                        }
083                        return this;
084                }
085                
086                public boolean isEmpty() {
087                        return points.isEmpty();
088                }
089                
090                public StreamPoint getLastPoint() {
091                        if (isEmpty()) return null;
092                        return points.get(points.size() - 1);
093                }
094                
095                public StreamPoint getFirstPoint() {
096                        if (isEmpty()) return null;
097                        return points.get(0);
098                }
099                
100                private void updateMinMax(StreamPoint p) {
101                        if (p.x < minX) minX = p.x;
102                        if (p.y < minY) minY = p.y;
103                        if (p.x > maxX) maxX = p.x;
104                        if (p.y > maxY) maxY = p.y;
105                }
106                
107                public void addPoint(StreamPoint p) {
108                        updateMinMax(p);
109                        points.add(p);
110                }
111                
112                public boolean isNearViewport(ViewportAPI v, float pad) {
113                        float x = v.getLLX();
114                        float y = v.getLLY();
115                        float w = v.getVisibleWidth();
116                        float h = v.getVisibleHeight();
117                        
118                        if (minX > x + w + pad) return false;
119                        if (minY > y + h + pad) return false;
120                        if (maxX < x - pad) return false;
121                        if (maxY < y - pad) return false;
122                        
123                        return true;
124                }
125                
126                public boolean couldContainLocation(Vector2f loc, float radius) {
127                        if (minX > loc.x + radius) return false;
128                        if (minY > loc.y + radius) return false;
129                        if (maxX < loc.x - radius) return false;
130                        if (maxY < loc.y - radius) return false;
131                        return true;
132                }
133        }
134        
135        
136        protected Map<SectorEntityToken, Stream> streams = new LinkedHashMap<SectorEntityToken, Stream>();
137        transient protected Map<SectorEntityToken, StreamPoint> containsCache = new HashMap<SectorEntityToken, StreamPoint>();
138        //transient protected SpriteAPI pointTexture;
139        
140        protected TimeoutTracker<SectorEntityToken> disrupted = new TimeoutTracker<SectorEntityToken>();
141        
142        public void init(String terrainId, SectorEntityToken entity, Object param) {
143                super.init(terrainId, entity, param);
144                readResolve();
145        }
146        
147        public void advance(float amount) {
148                super.advance(amount);
149                if (true) return;
150                
151                containsCache.clear();
152                
153                float days = Global.getSector().getClock().convertToDays(amount);
154                
155                disrupted.advance(days);
156                
157                for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) {
158                        //if (!fleet.isPlayerFleet()) continue;
159                        if (disrupted.contains(fleet)) continue;
160                        
161                        float burnLevel = fleet.getCurrBurnLevel();
162                        if (burnLevel >= 1) {
163                                Stream s = getStream(fleet);
164                                Vector2f loc = fleet.getLocation();
165                                boolean addPoint = false;
166                                if (s.isEmpty()) {
167                                        addPoint = true;
168                                } else {
169                                        StreamPoint last = s.getLastPoint();
170                                        float dist = Misc.getDistance(loc.x, loc.y, last.x, last.y);
171                                        if (dist > MIN_POINT_DIST) {
172                                                addPoint = true;
173                                        }
174                                }
175                                if (addPoint) {
176                                        StreamPoint p = new StreamPoint(loc.x, loc.y, 0.25f,
177                                                                                                    burnLevel, 
178                                                                                                    Math.max(MIN_POINT_DIST * 2f, fleet.getRadius()));
179                                        s.addPoint(p);
180                                }
181                        }
182                }
183                
184                Iterator<Stream> iter1 = streams.values().iterator();
185                while (iter1.hasNext()) {
186                        Stream s = iter1.next();
187
188                        advancePoints(s, days);
189                        //advanceParticles(s, days);
190                        //addNewParticles(s, days);
191                        
192                        if (s.isEmpty()) {// && s.particles.isEmpty()) {
193                                iter1.remove();
194                        } 
195                }
196        }
197        
198        public TimeoutTracker<SectorEntityToken> getDisrupted() {
199                return disrupted;
200        }
201        
202        public void disrupt(CampaignFleetAPI fleet, float dur) {
203                disrupted.set(fleet, dur);
204        }
205
206        private void advancePoints(Stream s, float days) {
207                Iterator<StreamPoint> iter2 = s.points.iterator();
208                while (iter2.hasNext()) {
209                        StreamPoint p = iter2.next();
210                        p.daysLeft -= days;
211                        p.width += WIDTH_GROWTH_PER_DAY * days;
212                        if (p.daysLeft <= 0) {
213                                iter2.remove();
214                        }
215                }
216        }
217        
218        protected Stream getStream(SectorEntityToken key) {
219                Stream s = streams.get(key);
220                if (s == null) {
221                        s = new Stream(key, 50);
222                        streams.put(key, s);
223                }
224                return s;
225        }
226                
227        @Override
228        public void applyEffect(SectorEntityToken entity, float days) {
229                if (entity instanceof CampaignFleetAPI) {
230                        CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
231                        containsPointCaching(fleet, fleet.getLocation(), fleet.getRadius());
232                        if (containsCache.containsKey(fleet)) {
233                                StreamPoint point = containsCache.get(fleet);
234                                float fleetBurn = fleet.getFleetData().getBurnLevel();
235                                if (point.burn > fleetBurn || true) {
236                                        float diff = point.burn - fleetBurn;
237                                        if (diff > 2) diff = 2;
238                                        diff = (int) diff;
239                                        diff = 2;
240                                        fleet.getStats().addTemporaryModFlat(0.1f, getModId(), "In slipstream", diff, fleet.getStats().getFleetwideMaxBurnMod());
241                                }
242                        }
243                }
244        }
245
246        @Override
247        public boolean containsEntity(SectorEntityToken other) {
248                return containsPointCaching(other, other.getLocation(), other.getRadius()) && !isPreventedFromAffecting(other);
249        }
250
251        @Override
252        public boolean containsPoint(Vector2f point, float radius) {
253                return containsPointCaching(null, point, radius);
254        }
255        
256        private boolean containsPointCaching(SectorEntityToken key, Vector2f point, float radius) {
257                if (key != null && containsCache.containsKey(key)) {
258                        return true;
259                }
260                if (true) return false;
261                
262                float maxBurn = 0f;
263                StreamPoint result = null;
264                for (Stream s : streams.values()) {
265                        if (s.key == key) continue;
266                        
267                        if (!s.couldContainLocation(point, radius)) continue;
268                        for (StreamPoint p : s.points) {
269//                              if (key instanceof CampaignFleetAPI) {
270//                                      float fleetBurn = ((CampaignFleetAPI)key).getFleetData().getBurnLevel();
271//                                      if (p.burn <= fleetBurn) {
272//                                              continue;
273//                                      }
274//                              }
275                                
276                                float dist = Misc.getDistance(p.x, p.y, point.x, point.y);
277                                if (dist < p.width && maxBurn < p.burn) {
278                                        maxBurn = p.burn;
279                                        result = p;
280                                }
281                        }
282                }
283                
284                if (result != null && key != null) {
285                        containsCache.put(key, result);
286                }
287                
288                return result != null;
289        }
290
291        
292        
293        Object readResolve() {
294                layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
295                //pointTexture = Global.getSettings().getSprite("terrain", "slipstream_point");
296                if (containsCache == null) {
297                        containsCache = new HashMap<SectorEntityToken, StreamPoint>();
298                }
299                return this;
300        }
301        
302        Object writeReplace() {
303                return this;
304        }
305        
306        transient private EnumSet<CampaignEngineLayers> layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
307        public EnumSet<CampaignEngineLayers> getActiveLayers() {
308                return layers;
309        }
310        
311        @Override
312        public boolean stacksWithSelf() {
313                return super.stacksWithSelf();
314        }
315
316        @Override
317        public float getRenderRange() {
318                return Float.MAX_VALUE;
319        }
320
321        public boolean hasTooltip() {
322                return true;
323        }
324        
325        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
326                float pad = 10f;
327                Color gray = Misc.getGrayColor();
328                Color highlight = Misc.getHighlightColor();
329                Color fuel = Global.getSettings().getColor("progressBarFuelColor");
330                Color bad = Misc.getNegativeHighlightColor();
331                
332                tooltip.addTitle(getTerrainName());
333                tooltip.addPara("Reduces the range at which fleets inside it can be detected by %s.", pad,
334                                highlight, 
335                                "50%"
336                );
337                tooltip.addPara("Does not stack with other similar terrain effects.", pad);
338        }
339        
340        public boolean isTooltipExpandable() {
341                return false;
342        }
343        
344        public float getTooltipWidth() {
345                return 350f;
346        }
347        
348        public String getTerrainName() {
349                return "Slipstream";
350        }
351        
352        public String getNameForTooltip() {
353                return "Slipstream";
354        }
355        
356        public String getEffectCategory() {
357                return "slipstream";
358        }
359}
360
361
362
363//public static class StreamParticle {
364//      public float x, y;
365//      public float age, maxAge;
366//      public StreamPoint point;
367//      //public Vector2f lastVel = null;
368//      //public LinkedList<Vector2f> prevLocs = new LinkedList<Vector2f>();
369//      public Color color;
370//}
371
372//private void advanceParticles(Stream s, float days) {
373//      Iterator<StreamParticle> iter3 = s.particles.iterator();
374//      while (iter3.hasNext()) {
375//              StreamParticle p = iter3.next();
376//              p.age += days;
377//              if (p.age >= p.maxAge) {
378//                      iter3.remove();
379//              } else if (p.point != null) {
380//                      StreamPoint curr = p.point;
381//                      StreamPoint prev = null, next = null;
382//                      int index = s.points.indexOf(curr);
383//                      if (index != -1) {
384//                              if (index > 0) {
385//                                      prev = s.points.get(index - 1);
386//                              }
387//                              if (index < s.points.size() - 1) {
388//                                      next = s.points.get(index + 1);
389//                              }
390//                      }
391//                      Vector2f vel = new Vector2f();
392//                      Vector2f cv = new Vector2f(curr.x, curr.y);
393//                      Vector2f pLoc = new Vector2f(p.x, p.y);
394//                      float maxSpeed = 1000f;
395//                      float minSpeed = 1000f;
396//                      float maxDist = 200f;
397//                      Vector2f unitDirPrev = null;
398//                      if (prev != null) {
399//                              Vector2f pv = new Vector2f(prev.x, prev.y);
400//                              Vector2f dir = Vector2f.sub(cv, pv, new Vector2f());
401//                              unitDirPrev = new Vector2f(dir);
402//                              Misc.normalise(dir);
403//                              float dist = Misc.getDistance(pLoc, pv);
404//                              float speed = minSpeed;
405//                              if (dist < maxDist) {
406//                                      speed = minSpeed + (maxSpeed - minSpeed) * (1f - dist / maxDist); 
407//                              }
408//                              dir.scale(speed);
409//                              Vector2f.add(vel, dir, vel);
410//                      }
411//                      if (next != null) {
412//                              Vector2f nv = new Vector2f(next.x, next.y);
413//                              Vector2f dir = Vector2f.sub(nv, cv, new Vector2f());
414//                              Misc.normalise(dir);
415//                              float dist = Misc.getDistance(cv, pLoc);
416//                              boolean angleOk = unitDirPrev == null || Vector2f.dot(unitDirPrev, dir) > 0;
417//                              if (dist < MAX_POINT_DIST && angleOk) {
418//                                      float speed = minSpeed;
419//                                      if (dist < maxDist) {
420//                                              speed = minSpeed + (maxSpeed - minSpeed) * (1f - dist / maxDist); 
421//                                      }
422//                                      dir.scale(speed);
423//                                      Vector2f.add(vel, dir, vel);
424//                                      
425//                                      float distNext = Misc.getDistance(nv, pLoc);
426//                                      if (distNext < dist) {
427//                                              p.point = next;
428//                                      }
429//                              }
430//                      }
431//                      
432//                      p.x += vel.x * days;
433//                      p.y += vel.y * days;
434//                      
435//                      pLoc.set(p.x, p.y);
436//                      if (p.prevLocs.size() < 2) {
437//                              p.prevLocs.add(pLoc);
438//                      } else {
439//                              float dist = Misc.getDistance(p.prevLocs.get(p.prevLocs.size() - 2), p.prevLocs.getLast());
440//                              if (dist >= 2) {
441//                                      p.prevLocs.add(pLoc);
442//                              } else {
443//                                      p.prevLocs.getLast().set(pLoc);
444//                              }
445//                      }
446//                      
447//                      
448//                      while (p.prevLocs.size() > 5) {
449//                              p.prevLocs.removeFirst();
450//                      }
451//              }
452//      }
453//}
454//
455//
456//private void addNewParticles(Stream s, float days) {
457//      if (s.isEmpty()) return;
458//      
459//      if (!s.key.isInCurrentLocation() || !s.isNearViewport(Global.getSector().getViewport(), 500f)) {
460//              return;
461//      }
462//      
463//      int points = s.points.size();
464//      int maxParticles = (points - 0) * s.particlesPerPoint;
465//
466//      WeightedRandomPicker<Color> colorPicker = new WeightedRandomPicker<Color>();
467//      if (s.key instanceof CampaignFleetAPI) {
468//              CampaignFleetAPI fleet = (CampaignFleetAPI) s.key;
469//              for (FleetMemberViewAPI view : fleet.getViews()) {
470//                      colorPicker.add(view.getContrailColor().getCurr(), view.getMember().getHullSpec().getHullSize().ordinal());
471//              }
472//      }
473//      if (colorPicker.isEmpty()) {
474//              colorPicker.add(new Color(100, 150, 255, 255), 1f);
475//      }
476//      
477//      Random r = new Random();
478//      for (int i = 0; i < s.particlesPerPoint * 0.1f; i++) {
479//              if (s.particles.size() >= maxParticles || s.isEmpty() || s.points.size() <= 1) {
480//                      break;
481//              }
482//              
483//              int index = r.nextInt(s.points.size() - 1);
484//              StreamPoint p = s.points.get(index);
485//              if (index < s.points.size() - 1) {
486//                      StreamPoint next = s.points.get(index + 1);
487//                      float dist = Misc.getDistance(p.x, p.y, next.x, next.y);
488//                      if (dist > MAX_POINT_DIST) continue;
489//              }
490//              
491//              StreamParticle particle = new StreamParticle();
492//              float spread = 10f + (s.points.size() - index) * 10f;
493//              particle.x = p.x + (float) Math.random() * spread - spread/2f;
494//              particle.y = p.y + (float) Math.random() * spread - spread/2f;
495////            particle.x = p.x;
496////            particle.y = p.y;
497//              particle.age = 0;
498//              particle.maxAge = 0.1f + 0.1f * (float) Math.random();
499//              particle.point = p;
500//              
501//              particle.color = colorPicker.pick();
502//              
503//              s.particles.add(particle);
504//      }
505//}
506//public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
507//      super.render(layer, viewport);
508//      
509//      float alphaMult = viewport.getAlphaMult();
510//      
511//      for (Stream s : streams.values()) {
512//              if (!s.isNearViewport(viewport, 300f)) continue;
513//              if (s.particles.isEmpty()) continue;
514//              GL11.glEnable(GL11.GL_BLEND);
515//              GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
516//              //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
517//              //Color color = new Color(100, 150, 255, 255);
518////            GL11.glColor4ub((byte)color.getRed(),
519////                            (byte)color.getGreen(),
520////                            (byte)color.getBlue(),
521////                            (byte)((float)color.getAlpha() * alphaMult));
522//              
523//              pointTexture.bindTexture();
524//              GL11.glBegin(GL11.GL_QUADS);
525//              StreamPoint last = null;
526//              if (!s.points.isEmpty()) {
527//                      last = s.points.get(s.points.size() - 1);
528//              }
529//              for (StreamParticle p : s.particles) {
530////                    if (p.prevLocs == null || p.prevLocs.size() < 5) continue;
531////                    float index = 0f;
532////                    float max = p.prevLocs.size();
533////                    for (Vector2f loc : p.prevLocs) {
534////                            float b;
535////                            float t = Math.min(0.05f, p.maxAge / 2f);
536////                            if (p.age < t) {
537////                                    b = p.age / t;
538////                            } else {
539////                                    b = 1f - (p.age - t) / (p.maxAge - t);
540////                            }
541////                            b *= 1f - ((index + 1) / max);
542////                            if (index == 0) b = 0;
543////                            if (index == max - 1) b = 0;
544////                            
545////                            GL11.glColor4ub((byte)color.getRed(),
546////                                            (byte)color.getGreen(),
547////                                            (byte)color.getBlue(),
548////                                            (byte)((float)color.getAlpha() * alphaMult * b));
549////                            
550////                            float size = 7f;
551////                            GL11.glTexCoord2f(0, 0);
552////                            GL11.glVertex2f(loc.x - size/2f, loc.y - size/2f);
553////            
554////                            GL11.glTexCoord2f(0.01f, 0.99f);
555////                            GL11.glVertex2f(loc.x - size/2f, loc.y + size/2f);
556////            
557////                            GL11.glTexCoord2f(0.99f, 0.99f);
558////                            GL11.glVertex2f(loc.x + size/2f, loc.y + size/2f);
559////            
560////                            GL11.glTexCoord2f(0.99f, 0.01f);
561////                            GL11.glVertex2f(loc.x + size/2f, loc.y - size/2f);
562////                            
563////                            index++;
564////                    }
565//                      
566//                      float b;
567//                      float t = Math.min(0.05f, p.maxAge / 2f);
568//                      if (p.age < t) {
569//                              b = p.age / t;
570//                      } else {
571//                              b = 1f - (p.age - t) / (p.maxAge - t);
572//                      }
573//                      b *= 4f;
574//                      
575//                      if (last != null) {
576////                            if (s.key.isPlayerFleet()) {
577////                                    System.out.println("23rasdf");
578////                            }
579//                              float dist = Misc.getDistance(last.x, last.y, p.x, p.y);
580//                              float max = s.key.getRadius() + 50f;
581//                              if (dist < max) {
582//                                      b *= Math.max(0, (dist - s.key.getRadius())/ (max - s.key.getRadius()));
583//                                      if (b <= 0) {
584//                                              p.age = p.maxAge + 1f;
585//                                      }
586//                              }
587//                      }
588//                      if (p.age >= p.maxAge) {
589//                              b = 0f;
590//                      }
591//                      
592//                      Color color = p.color;
593//                      float alpha = ((float)color.getAlpha() * alphaMult * b);
594//                      if (alpha > 255f) alpha = 255f;
595//                      GL11.glColor4ub((byte)color.getRed(),
596//                                      (byte)color.getGreen(),
597//                                      (byte)color.getBlue(),
598//                                      (byte)(alpha));
599//                      
600//                      float size = 7f;
601//                      GL11.glTexCoord2f(0, 0);
602//                      GL11.glVertex2f(p.x - size/2f, p.y - size/2f);
603//      
604//                      GL11.glTexCoord2f(0.01f, 0.99f);
605//                      GL11.glVertex2f(p.x - size/2f, p.y + size/2f);
606//      
607//                      GL11.glTexCoord2f(0.99f, 0.99f);
608//                      GL11.glVertex2f(p.x + size/2f, p.y + size/2f);
609//      
610//                      GL11.glTexCoord2f(0.99f, 0.01f);
611//                      GL11.glVertex2f(p.x + size/2f, p.y - size/2f);
612//              }
613//              
614//              GL11.glEnd();
615//              
616//      }
617//}