001package com.fs.starfarer.api.impl.campaign.terrain;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.EnumSet;
006import java.util.List;
007
008import org.lwjgl.opengl.GL11;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignEngineLayers;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.CampaignTerrainAPI;
014import com.fs.starfarer.api.campaign.SectorEntityToken;
015import com.fs.starfarer.api.campaign.TerrainAIFlags;
016import com.fs.starfarer.api.combat.ViewportAPI;
017import com.fs.starfarer.api.impl.campaign.ids.Tags;
018import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
019import com.fs.starfarer.api.loading.Description.Type;
020import com.fs.starfarer.api.ui.Alignment;
021import com.fs.starfarer.api.ui.TooltipMakerAPI;
022import com.fs.starfarer.api.util.FaderUtil;
023import com.fs.starfarer.api.util.Misc;
024import com.fs.starfarer.api.util.WeightedRandomPicker;
025
026public class DebrisFieldTerrainPlugin extends BaseRingTerrain {
027        
028        // when this many days are left, density will gradually go to 0
029        public static final float DISSIPATE_DAYS = 3f;
030        //public static final float VISIBLITY_MULT = 0.25f;
031        
032        public static float computeDetectionRange(float radius) {
033                float range = 100f + radius * 5f;
034                if (range > 2000) range = 2000;
035                return range;
036        }
037        
038        public static enum DebrisFieldSource {
039                GEN,
040                PLAYER_SALVAGE,
041                SALVAGE,
042                BATTLE,
043                MIXED,
044        }
045        
046        public static class DebrisFieldParams extends RingParams {
047                public float density;
048                public float baseDensity;
049                public float glowsDays;
050                public float lastsDays;
051                
052                public float minSize = 4;
053                public float maxSize = 16;
054                public Color glowColor = new Color(255,165,100,255);
055                
056                
057                public String defFaction = null;
058                public float defenderProb = 0;
059                public int minStr = 0;
060                public int maxStr = 0;
061                public int maxDefenderSize = 4;
062                public long baseSalvageXP = 0;
063                public DebrisFieldSource source = DebrisFieldSource.MIXED;
064                
065                public DebrisFieldParams(float bandWidthInEngine, float density,
066                                                                 float lastsDays, float glowsDays) {
067                        super(bandWidthInEngine, bandWidthInEngine / 2f, null);
068//                      this.density = density;
069//                      this.baseDensity = density;
070                        if (density < 0) {
071                                this.density = 0.1f + StarSystemGenerator.random.nextFloat() * 0.9f;
072                        } else {
073                                this.density = density;
074                        }
075                        this.baseDensity = 1f;
076                        this.glowsDays = glowsDays;
077                        this.lastsDays = lastsDays;
078                }
079        }
080        
081        
082        protected transient List<DebrisPiece> pieces;
083        protected transient boolean initedDebris = false;
084
085        public DebrisFieldParams params;
086        protected boolean fadingOut = false;
087        protected FaderUtil expander; // days;
088        //protected float glowDaysLeft, daysLeft;
089        protected float elapsed;
090        
091        protected Boolean scavenged = null; 
092        
093        public void init(String terrainId, SectorEntityToken entity, Object param) {
094                super.init(terrainId, entity, param);
095                params = (DebrisFieldParams) param;
096                name = params.name;
097                if (name == null) {
098                        name = "Debris Field";
099                }
100                
101//              glowDaysLeft = params.glowsDays;
102//              daysLeft = params.lastsDays;
103                
104                float dur = params.bandWidthInEngine / 500f;
105                expander = new FaderUtil(0, dur, dur);
106                expander.fadeIn();
107                
108                ((CampaignTerrainAPI)entity).setRadius(params.bandWidthInEngine);
109                
110                entity.setDetectionRangeDetailsOverrideMult(0.4f);
111                entity.addTag(Tags.DEBRIS_FIELD);
112        }
113        
114        public boolean isScavenged() {
115                return scavenged != null && scavenged;
116        }
117
118        public void setScavenged(Boolean scavenged) {
119                this.scavenged = scavenged;
120        }
121
122
123        public DebrisFieldParams getParams() {
124                return params;
125        }
126
127        @Override
128        protected Object readResolve() {
129                super.readResolve();
130                layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7A);
131                //initDebrisIfNeeded();
132                return this;
133        }
134        
135        protected transient boolean wasInNonCurrentLocation = false;
136        public void advance(float amount) {
137                super.advance(amount);
138                
139                if (amount <= 0) {
140                        //((CampaignTerrainAPI)entity).setRadius(params.bandWidthInEngine);
141                        return; // happens during game load
142                }
143                
144                float days = Global.getSector().getClock().convertToDays(amount);
145                elapsed += days;
146                
147                if (!entity.isInCurrentLocation()) {
148                        pieces = null;
149                        initedDebris = false;
150                        wasInNonCurrentLocation = true;
151                        
152                        if (params.lastsDays - elapsed <= 0) {
153                                getEntity().setExpired(true);
154                        }
155                        return;
156                }
157                
158                // not necessary?
159                // necessary because it affects the number of sensor contact indicators
160                ((CampaignTerrainAPI)entity).setRadius(params.bandWidthInEngine);
161                
162                
163//              daysLeft -= days;
164//              glowDaysLeft -= days;
165                
166                float left = params.lastsDays - elapsed;
167                if (left < DISSIPATE_DAYS) {
168                        float decr = days / DISSIPATE_DAYS * params.baseDensity;
169                        params.density -= decr;
170                        if (params.density < 0) params.density = 0;
171                }
172                
173                if (wasInNonCurrentLocation) {
174                        expander.forceIn();
175                        wasInNonCurrentLocation = false;
176                }
177                expander.advance(days);
178                
179                initDebrisIfNeeded();
180                
181                List<DebrisPiece> remove = new ArrayList<DebrisPiece>();
182                int withIndicator = 0;
183                for (DebrisPiece piece : pieces) {
184                        piece.advance(days);
185                        if (piece.isDone()) {
186                                remove.add(piece);
187                        } else {
188                                if (piece.hasIndicator()) {
189                                        withIndicator++;
190                                }
191//                              if (glowDaysLeft > 0) {
192//                                      piece.getGlowFader().fadeIn();
193//                              } else {
194//                                      piece.getGlowFader().fadeOut();
195//                              }
196                        }
197                }
198                pieces.removeAll(remove);
199
200                int withIndicatorGoal = (int) (pieces.size() * 0.1f);
201                if (withIndicatorGoal < 3) withIndicatorGoal = 3;
202                
203                int addIndicators = withIndicatorGoal - withIndicator;
204                if (addIndicators > 0) {
205                        WeightedRandomPicker<DebrisPiece> picker = new WeightedRandomPicker<DebrisPiece>();
206                        for (DebrisPiece piece : pieces) {
207                                if (!piece.hasIndicator()) {
208                                        picker.add(piece);
209                                }
210                        }
211                        for (int i = 0; i < addIndicators && !picker.isEmpty(); i++) {
212                                DebrisPiece piece = picker.pickAndRemove();
213                                if (piece != null) {
214                                        piece.showIndicator();
215                                }
216                        }
217                }
218                
219                
220                if (left > 0) {
221                        addPiecesToMax();
222                } else {
223                        if (pieces.isEmpty()) {
224                                getEntity().setExpired(true);
225                        }
226                }
227
228        }
229                
230        @Override
231        protected float getMaxRadiusForContains() {
232                return super.getMaxRadiusForContains() * expander.getBrightness();
233        }
234
235        @Override
236        protected float getMinRadiusForContains() {
237                return super.getMinRadiusForContains() * expander.getBrightness();
238        }
239
240        public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
241                //System.out.println("RENDER");
242                super.render(layer, viewport);
243                
244                float alphaMult = viewport.getAlphaMult();
245                alphaMult *= entity.getSensorFaderBrightness();
246                alphaMult *= entity.getSensorContactFaderBrightness();
247                if (alphaMult <= 0) return;
248
249                
250                GL11.glPushMatrix();
251                GL11.glTranslatef(entity.getLocation().x, entity.getLocation().y, 0);
252                
253                initDebrisIfNeeded();
254                for (DebrisPiece piece : pieces) {
255                        piece.render(alphaMult);
256                }
257                
258                //if (scavenged == null || !scavenged) {
259                        for (DebrisPiece piece : pieces) {
260                                piece.renderIndicator(alphaMult);
261                        }
262                //}
263                
264                GL11.glPopMatrix();
265                
266        }
267        
268        protected void addPiecesToMax() {
269                float mult = params.bandWidthInEngine / 500f;
270                mult *= mult;
271                int baseMax = (int) (mult * 100f * (0.5f + 0.5f * params.density));
272                
273                if (baseMax < 7) baseMax = 7;
274                //baseMax = 100;
275                //System.out.println(baseMax);
276                //System.out.println(baseMax);
277                int max = (int) (baseMax * expander.getBrightness() * expander.getBrightness());
278                
279                max *= 2;
280                
281                while (pieces.size() < max) {
282                        DebrisPiece piece = new DebrisPiece(this);
283                        pieces.add(piece);
284                }
285        }
286        
287        protected void initDebrisIfNeeded() {
288                if (initedDebris) return;
289                initedDebris = true;
290                
291                pieces = new ArrayList<DebrisPiece>();
292                
293                if (params.lastsDays - elapsed > 0) {
294                        addPiecesToMax();
295                }
296                
297                for (DebrisPiece piece : pieces) {
298                        piece.advance(1f * expander.getBrightness());
299                }
300        }
301        
302        
303        
304        
305
306        @Override
307        public void applyEffect(SectorEntityToken entity, float days) {
308                if (entity instanceof CampaignFleetAPI) {
309                        CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
310                        //if (fleet.getCurrBurnLevel() <= RingSystemTerrainPlugin.MAX_SNEAK_BURN_LEVEL) {
311                        if (Misc.isSlowMoving(fleet)) {
312                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_1",
313                                                                        "Hiding inside debris field", RingSystemTerrainPlugin.getVisibilityMult(fleet), 
314                                                                        fleet.getStats().getDetectedRangeMod());
315                        }
316                }
317        }
318
319        public boolean hasTooltip() {
320                return true;
321        }
322
323        private String nameForTooltip = null;
324        public String getNameForTooltip() {
325                if (nameForTooltip == null) return "Debris Field";
326                return nameForTooltip;
327        }
328
329        public void setNameForTooltip(String nameForTooltip) {
330                this.nameForTooltip = nameForTooltip;
331        }
332
333        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
334                float pad = 10f;
335                float small = 5f;
336                Color gray = Misc.getGrayColor();
337                Color highlight = Misc.getHighlightColor();
338                Color fuel = Global.getSettings().getColor("progressBarFuelColor");
339                Color bad = Misc.getNegativeHighlightColor();
340                
341                tooltip.addTitle(getNameForTooltip());
342                tooltip.addPara(Global.getSettings().getDescription(getTerrainId(), Type.TERRAIN).getText1(), pad);
343                
344                float left = params.lastsDays - elapsed;
345                if (left >= 1000) {
346                        tooltip.addPara("This particular field appears stable and is unlikely to drift apart any time soon.", pad);
347                } else {
348                        String atLeastTime = Misc.getAtLeastStringForDays((int) left);
349                        tooltip.addPara("This particular field is unstable, but should not drift apart for " + atLeastTime + ".", pad);
350                }
351                
352                
353                float nextPad = pad;
354                if (expanded) {
355                        tooltip.addSectionHeading("Travel", Alignment.MID, pad);
356                        nextPad = small;
357                }
358//              tooltip.addPara("Reduces the range at which stationary fleets inside it can be detected by %s.", nextPad,
359//                              highlight, 
360//                              "" + (int) ((1f - VISIBLITY_MULT) * 100) + "%"
361//              );
362                
363                String stop = Global.getSettings().getControlStringForEnumName("GO_SLOW");
364                tooltip.addPara("Reduces the range at which stationary or slow-moving* fleets inside it can be detected by %s.", nextPad,
365                                highlight, 
366                                "" + (int) ((1f - RingSystemTerrainPlugin.getVisibilityMult(Global.getSector().getPlayerFleet())) * 100) + "%"
367                );
368                tooltip.addPara("*Press and hold %s to stop; combine with holding the left mouse button down to move slowly.", nextPad,
369                                Misc.getGrayColor(), highlight, 
370                                stop
371                );
372                
373                tooltip.addPara("Scavenging through the debris for anything useful is possible, but can be dangerous for the crew and equipment involved.", pad);
374                
375//              if (expanded) {
376//                      tooltip.addSectionHeading("Combat", Alignment.MID, pad);
377//                      tooltip.addPara("Numerous small bodies that make up the ring system present on the battlefield. Not large enough to be an in-combat navigational hazard.", small);
378//              }
379        }
380        
381        public boolean isTooltipExpandable() {
382                return true;
383        }
384        
385        public float getTooltipWidth() {
386                return 350f;
387        }
388        
389        public String getEffectCategory() {
390                return "ringsystem-like";
391        }
392        
393        public boolean hasAIFlag(Object flag) {
394                return flag == TerrainAIFlags.HIDING_STATIONARY;
395        }
396        
397        @Override
398        public String getIconSpriteName() {
399                return Global.getSettings().getSpriteName("terrain", "debrisFieldMapIcon");
400        }
401
402        public boolean isFadingOut() {
403                return fadingOut;
404        }
405
406        public FaderUtil getExpander() {
407                return expander;
408        }
409
410        public float getGlowDaysLeft() {
411                return params.glowsDays - elapsed;
412        }
413        
414        public float getPieceGlowProbability() {
415                float glowLeft = getGlowDaysLeft();
416                if (glowLeft <= 0) return 0;
417                if (params.glowsDays <= 0) return 0;
418                return glowLeft / params.glowsDays;
419        }
420
421        public float getDaysLeft() {
422                return params.lastsDays - elapsed;
423        }
424        
425        
426}