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}