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}