001package com.fs.starfarer.api.impl.campaign.terrain;
002
003import java.awt.Color;
004import java.util.Random;
005
006import com.fs.starfarer.api.Global;
007import com.fs.starfarer.api.campaign.AsteroidAPI;
008import com.fs.starfarer.api.campaign.CampaignEngineLayers;
009import com.fs.starfarer.api.campaign.CampaignFleetAPI;
010import com.fs.starfarer.api.campaign.LocationAPI;
011import com.fs.starfarer.api.campaign.SectorEntityToken;
012import com.fs.starfarer.api.campaign.TerrainAIFlags;
013import com.fs.starfarer.api.campaign.rules.MemoryAPI;
014import com.fs.starfarer.api.combat.ViewportAPI;
015import com.fs.starfarer.api.loading.Description.Type;
016import com.fs.starfarer.api.ui.Alignment;
017import com.fs.starfarer.api.ui.TooltipMakerAPI;
018import com.fs.starfarer.api.util.Misc;
019
020public class AsteroidBeltTerrainPlugin extends BaseRingTerrain implements AsteroidSource {
021        
022//      public static float MIN_BURN_PENALTY = 0.1f;
023//      public static float BURN_PENALTY_RANGE = 0.4f;
024        
025        public static class AsteroidBeltParams extends RingParams {
026                public int numAsteroids;
027                //public float orbitRadius;
028                //public float width;
029                public float minOrbitDays;
030                public float maxOrbitDays;
031                public float minSize;
032                public float maxSize;
033                public AsteroidBeltParams(int numAsteroids, float orbitRadius,
034                                float width, float minOrbitDays, float maxOrbitDays,
035                                float minSize, float maxSize, String name) {
036                        super(width, orbitRadius, null, name);
037                        this.numAsteroids = numAsteroids;
038                        //this.orbitRadius = orbitRadius;
039                        //this.width = width;
040                        this.minOrbitDays = minOrbitDays;
041                        this.maxOrbitDays = maxOrbitDays;
042                        this.minSize = minSize;
043                        this.maxSize = maxSize;
044                }
045        }
046
047        
048        @Override
049        protected Object readResolve() {
050                super.readResolve();
051                return this;
052        }
053        
054        private transient RingRenderer rr;
055        public void renderOnMap(float factor, float alphaMult) {
056                if (params == null) return;
057                if (rr == null) {
058                        rr = new RingRenderer("systemMap", "map_asteroid_belt");
059                }
060                Color color = Global.getSettings().getColor("asteroidBeltMapColor");
061                float bandWidth = params.bandWidthInEngine;
062                bandWidth = 300f;
063                rr.render(entity.getLocation(),
064                                  params.middleRadius - bandWidth * 0.5f,
065                                  params.middleRadius + bandWidth * 0.5f,
066                                  color,
067                                  false, factor, alphaMult);
068        }
069
070        public void regenerateAsteroids() {
071                createAsteroids();
072        }
073
074        protected boolean needToCreateAsteroids = true;
075        protected void createAsteroids() {
076                if (!(params instanceof AsteroidBeltParams)) return;
077                
078                Random rand = new Random(Global.getSector().getClock().getTimestamp() + entity.getId().hashCode());
079                
080                LocationAPI location = entity.getContainingLocation();
081                for (int i = 0; i < params.numAsteroids; i++) {
082                        //float size = 8f + (float) Math.random() * 25f;
083                        float size = params.minSize + rand.nextFloat() * (params.maxSize - params.minSize);
084                        AsteroidAPI asteroid = location.addAsteroid(size);
085
086                        asteroid.setFacing(rand.nextFloat() * 360f);
087                        float currRadius = params.middleRadius - params.bandWidthInEngine/2f + rand.nextFloat() * params.bandWidthInEngine;
088                        float angle = rand.nextFloat() * 360f;
089                        float orbitDays = params.minOrbitDays + rand.nextFloat() * (params.maxOrbitDays - params.minOrbitDays);
090                        asteroid.setCircularOrbit(this.entity, angle, currRadius, orbitDays);
091                        Misc.setAsteroidSource(asteroid, this);
092                }
093                needToCreateAsteroids = false;
094        }
095        
096        public void advance(float amount) {
097                if (needToCreateAsteroids) {
098                        createAsteroids();
099                }
100                super.advance(amount);
101                
102//              if (entity.isInCurrentLocation()) {
103//                      System.out.println("Params: " + params + ", name: " + getNameForTooltip());
104//                      if (params == null) {
105//                              System.out.println("efwefwe");
106//                      }
107//              }
108        }
109        
110        
111        
112//      public static Map<HullSize, Float> burnPenalty = new HashMap<HullSize, Float>();
113//      static {
114//              burnPenalty.put(HullSize.FIGHTER, 0f);
115//              burnPenalty.put(HullSize.FRIGATE, 0f);
116//              burnPenalty.put(HullSize.DESTROYER, 1f);
117//              burnPenalty.put(HullSize.CRUISER, 2f);
118//              burnPenalty.put(HullSize.CAPITAL_SHIP, 3f);
119//      }
120        
121        
122//      public void init(String terrainId, SectorEntityToken entity, Object param) {
123//              super.init(terrainId, entity, param);
124//              if (params.name == null) {
125//                      params.name = "Asteroid Belt";
126//              }
127//      }
128        
129        public AsteroidBeltParams params;
130        public void init(String terrainId, SectorEntityToken entity, Object param) {
131                super.init(terrainId, entity, param);
132                if (param instanceof AsteroidBeltParams) {
133                        params = (AsteroidBeltParams) param;
134                        name = params.name;
135                        if (name == null) {
136                                name = "Asteroid Belt";
137                        }
138                }
139        }
140        
141        
142        public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
143                super.render(layer, viewport);
144        }
145
146        @Override
147        public void applyEffect(SectorEntityToken entity, float days) {
148                if (entity instanceof CampaignFleetAPI) {
149                        CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
150                        
151//                      float penalty = getBurnPenalty(fleet);
152//                      fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_1",
153//                                                              "Inside " + getNameForTooltip().toLowerCase(), 1f - penalty, 
154//                                                              fleet.getStats().getFleetwideMaxBurnMod());
155                        
156                        if (Misc.isSlowMoving(fleet)) {
157                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_2",
158                                                                        "Hiding inside " + getNameForTooltip().toLowerCase(), RingSystemTerrainPlugin.getVisibilityMult(fleet), 
159                                                                        fleet.getStats().getDetectedRangeMod());
160                        }
161//                      if (fleet.isPlayerFleet()) {
162//                              System.out.println("efwefwe");
163//                      }
164                        if (!fleet.isInHyperspaceTransition()) {
165                                String key = "$asteroidImpactTimeout";
166                                String sKey = "$skippedImpacts";
167                                String recentKey = "$recentImpact";
168                                float probPerSkip = 0.15f;
169                                float maxProb = 1f;
170                                float maxSkipsToTrack = 7;
171                                float durPerSkip = 0.2f;
172                                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
173                                if (!mem.contains(key)) {
174                                        float expire = mem.getExpire(sKey);
175                                        if (expire < 0) expire = 0;
176                                        
177                                        float hitProb = Misc.getFleetRadiusTerrainEffectMult(fleet) * 0.5f;
178                                        //hitProb = 0.33f;
179                                        hitProb = 0.5f;
180                                        //hitProb = 1f;
181                                        hitProb = expire / durPerSkip * probPerSkip;
182                                        if (hitProb > maxProb) hitProb = maxProb;
183                                        if ((float) Math.random() < hitProb) {
184                                                boolean hadRecent = mem.is(recentKey, true);
185                                                hadRecent &= (float) Math.random() > 0.5f;
186                                                fleet.addScript(new AsteroidImpact(fleet, hadRecent));
187                                                mem.set(sKey, true, 0);
188                                                mem.set(recentKey, true, 0.5f + 1f * (float) Math.random());
189                                        } else {
190                                                mem.set(sKey, true, Math.min(expire + durPerSkip, maxSkipsToTrack * durPerSkip));
191                                        }
192                                        mem.set(key, true, (float) (0.05f + 0.1f * Math.random()));
193                                        //mem.set(key, true, (float) (0.01f + 0.02f * Math.random()));
194                                }
195                        }
196                }
197        }
198        
199//      public static float getFleetRadiusTerrainEffectMult(CampaignFleetAPI fleet) {
200//              float min = Global.getSettings().getBaseFleetSelectionRadius() + Global.getSettings().getFleetSelectionRadiusPerUnitSize();
201//              float max = Global.getSettings().getMaxFleetSelectionRadius();
202//              float radius = fleet.getRadius();
203//              
204//              //radius = 1000;
205//
206//              float mult = (radius - min) / (max - min);
207//              if (mult > 1) mult = 1;
208//              //if (mult < 0) mult = 0;
209//              if (mult < 0.1f) mult = 0.1f;
210//              //mult = MIN_BURN_PENALTY + mult * BURN_PENALTY_RANGE;
211//
212//              float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
213//              mult *= skillMod;
214//              
215//              return mult;
216//      }
217        
218//      protected float getBurnPenalty(CampaignFleetAPI fleet) {
219//              float min = Global.getSettings().getBaseFleetSelectionRadius() + Global.getSettings().getFleetSelectionRadiusPerUnitSize();
220//              float max = Global.getSettings().getMaxFleetSelectionRadius();
221//              float radius = fleet.getRadius();
222//
223//              float penalty = (radius - min) / (max - min);
224//              if (penalty > 1) penalty = 1;
225//              if (penalty < 0) penalty = 0;
226//              penalty = MIN_BURN_PENALTY + penalty * BURN_PENALTY_RANGE;
227//
228//              float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
229//              penalty *= skillMod;
230//              
231//              return penalty;
232//      }
233
234        public boolean hasTooltip() {
235                return true;
236        }
237        
238        public String getNameForTooltip() {
239                return "Asteroid Belt";
240        }
241        
242        public String getNameAOrAn() {
243                return "an";
244        }
245        
246        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
247                float pad = 10f;
248                float small = 5f;
249                Color gray = Misc.getGrayColor();
250                Color highlight = Misc.getHighlightColor();
251                Color fuel = Global.getSettings().getColor("progressBarFuelColor");
252                Color bad = Misc.getNegativeHighlightColor();
253                
254                //tooltip.addTitle(params.name);
255                tooltip.addTitle(getNameForTooltip());
256                tooltip.addPara(Global.getSettings().getDescription(getTerrainId(), Type.TERRAIN).getText1(), pad);
257                
258                float nextPad = pad;
259                if (expanded) {
260                        tooltip.addSectionHeading("Travel", Alignment.MID, pad);
261                        nextPad = small;
262                }
263                
264//              tooltip.addPara("Reduces the travel speed of fleets inside by up to %s. Smaller fleets are more easily able to maneuver the enclosing drive bubble to avoid collisions and suffer a lower penalty.",
265//                              nextPad,
266//                              highlight,
267//                              "" + (int) ((MIN_BURN_PENALTY + BURN_PENALTY_RANGE) * 100f) + "%"                               
268//              );
269//              
270//              float penalty = getBurnPenalty(Global.getSector().getPlayerFleet());
271//              String penaltyStr = Misc.getRoundedValue(1f - penalty);
272//              tooltip.addPara("Your fleet's speed is reduced by %s.", pad,
273//                              highlight,
274//                              "" + (int) Math.round((penalty) * 100) + "%"
275//                              //Strings.X + penaltyStr
276//              );
277                
278//              tooltip.addPara("Chance of asteroid impacts on the drive field bubble. The impacts do not present a " +
279//                              "direct danger to ships but may briefly knock the fleet off course.", nextPad);
280                tooltip.addPara("Chance of asteroid impacts that briefly knock the fleet off course and " +
281                                "may occasionally impact ships directly, dealing moderate damage.", nextPad);
282                
283//              tooltip.addPara("Smaller fleets are usually able to avoid the heavier impacts, and fleets traveling at burn %s or below do not risk impacts at all.", pad,
284//                              highlight,
285//                              "" + (int)Math.round(AsteroidImpact.SAFE_BURN_LEVEL)
286//              );
287                tooltip.addPara("Smaller fleets are usually able to avoid the heavier impacts, " +
288                                "and slow-moving fleets do not risk impacts at all.", pad,
289                                highlight,
290                                "slow-moving"
291                );
292                
293//              tooltip.addPara("Reduces the range at which stationary fleets inside it can be detected by %s.", pad,
294//                              highlight, 
295//                              "" + (int) ((1f - RingSystemTerrainPlugin.VISIBLITY_MULT) * 100) + "%"
296//              );
297                
298                String stop = Global.getSettings().getControlStringForEnumName("GO_SLOW");
299                tooltip.addPara("Reduces the range at which stationary or slow-moving* fleets inside it can be detected by %s.", nextPad,
300                                highlight, 
301                                "" + (int) ((1f - RingSystemTerrainPlugin.getVisibilityMult(Global.getSector().getPlayerFleet())) * 100) + "%"
302                );
303                tooltip.addPara("*Press and hold %s to stop; combine with holding the left mouse button down to move slowly. " +
304                                "A slow-moving fleet moves at a burn level of half that of its slowest ship.", nextPad,
305                                Misc.getGrayColor(), highlight, 
306                                stop
307                );
308                
309//              tooltip.addPara("Reduces the maximum burn level of ships depending on size. Smaller ships are more easily able to manuver to avoid impacts and suffer a smaller penalty.", nextPad);
310//              tooltip.beginGrid(150, 1);
311//              tooltip.addToGrid(0, 0, "  Frigates", "" + -burnPenalty.get(HullSize.FRIGATE).intValue());
312//              tooltip.addToGrid(0, 1, "  Destroyers", "" + -burnPenalty.get(HullSize.DESTROYER).intValue());
313//              tooltip.addToGrid(0, 2, "  Cruisers", "" + -burnPenalty.get(HullSize.CRUISER).intValue());
314//              tooltip.addToGrid(0, 3, "  Capital ships", "" + -burnPenalty.get(HullSize.CAPITAL_SHIP).intValue());
315//              tooltip.addGrid(3f);
316                
317                if (expanded) {
318                        tooltip.addSectionHeading("Combat", Alignment.MID, pad);
319                        tooltip.addPara("Numerous asteroids present on the battlefield. Large enough to be an in-combat navigational hazard.", small);
320                }
321                
322                //tooltip.addPara("Does not stack with other similar terrain effects.", pad);
323        }
324        
325        public boolean isTooltipExpandable() {
326                return true;
327        }
328        
329        public float getTooltipWidth() {
330                return 350f;
331        }
332        
333        public String getEffectCategory() {
334                return "asteroid_belt";
335        }
336        
337        public boolean hasAIFlag(Object flag) {
338                return flag == TerrainAIFlags.REDUCES_SPEED_LARGE || flag == TerrainAIFlags.DANGEROUS_UNLESS_GO_SLOW ||
339                                flag == TerrainAIFlags.NOT_SUPER_DANGEROUS_UNLESS_GO_SLOW;
340        }
341
342        
343        public void reportAsteroidPersisted(SectorEntityToken asteroid) {
344                if (Misc.getAsteroidSource(asteroid) == this) {
345                        params.numAsteroids--;
346                }
347        }
348}