001package com.fs.starfarer.api.impl.campaign.intel.deciv; 002 003import java.util.ArrayList; 004import java.util.LinkedHashMap; 005import java.util.List; 006import java.util.Random; 007 008import com.fs.starfarer.api.EveryFrameScript; 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.campaign.PlanetAPI; 011import com.fs.starfarer.api.campaign.SectorEntityToken; 012import com.fs.starfarer.api.campaign.econ.Industry; 013import com.fs.starfarer.api.campaign.econ.MarketAPI; 014import com.fs.starfarer.api.campaign.econ.MarketConditionAPI; 015import com.fs.starfarer.api.campaign.econ.SubmarketAPI; 016import com.fs.starfarer.api.campaign.listeners.ListenerUtil; 017import com.fs.starfarer.api.characters.PersonAPI; 018import com.fs.starfarer.api.impl.campaign.DebugFlags; 019import com.fs.starfarer.api.impl.campaign.ids.Conditions; 020import com.fs.starfarer.api.impl.campaign.ids.Factions; 021import com.fs.starfarer.api.impl.campaign.population.CoreImmigrationPluginImpl; 022import com.fs.starfarer.api.util.IntervalUtil; 023import com.fs.starfarer.api.util.Misc; 024import com.fs.starfarer.api.util.TimeoutTracker; 025 026public class DecivTracker implements EveryFrameScript { 027 028 public static final String KEY = "$core_decivTracker"; 029 030 public static final String NO_DECIV_KEY = "$core_noDeciv"; 031 032 public static class MarketDecivData { 033 MarketAPI market; 034 List<Float> stabilityHistory = new ArrayList<Float>(); 035 } 036 037 038 public static DecivTracker getInstance() { 039 Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY); 040 return (DecivTracker) test; 041 } 042 043 public DecivTracker() { 044 super(); 045 Global.getSector().getMemoryWithoutUpdate().set(KEY, this); 046 } 047 048 protected LinkedHashMap<MarketAPI, MarketDecivData> decivData = new LinkedHashMap<MarketAPI, MarketDecivData>(); 049 protected IntervalUtil sampler = new IntervalUtil(20f, 40f); 050 protected IntervalUtil checker = new IntervalUtil(5f, 15f); 051 protected TimeoutTracker<String> sentWarning = new TimeoutTracker<String>(); 052 protected Random random = new Random(); 053 054 055 protected Object readResolve() { 056 if (sentWarning == null) { 057 sentWarning = new TimeoutTracker<String>(); 058 } 059 return this; 060 } 061 062 public void advance(float amount) { 063 064 float days = Misc.getDays(amount); 065 if (DebugFlags.DECIV_DEBUG) { 066 days *= 1000f; 067 } 068 069 sentWarning.advance(days); 070 071 sampler.advance(days); 072 if (sampler.intervalElapsed()) { 073 updateSamples(); 074 } 075 checker.advance(days); 076 if (checker.intervalElapsed()) { 077 checkDeciv(); 078 } 079 } 080 081 public MarketDecivData getDataFor(MarketAPI market) { 082 MarketDecivData data = decivData.get(market); 083 if (data == null) { 084 data = new MarketDecivData(); 085 data.market = market; 086 decivData.put(market, data); 087 } 088 return data; 089 } 090 091 public static int getMaxMonths() { 092 return Global.getSettings().getInt("decivSamplingMonths"); 093 } 094 public static int getMinStreak() { 095 return Global.getSettings().getInt("decivMinStreak"); 096 } 097 public static float getProbPerMonth() { 098 return Global.getSettings().getFloat("decivProbPerMonthOverStreak"); 099 } 100 public static float getMinFraction() { 101 return Global.getSettings().getFloat("decivZeroStabilityMinFraction"); 102 } 103 104 105 protected void updateSamples() { 106 107 for (MarketAPI market : new ArrayList<MarketAPI>(decivData.keySet())) { 108 if (!market.isInEconomy()) { 109 decivData.remove(market); 110 } 111 } 112 113 int maxSamples = getMaxMonths(); 114 for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 115 MarketDecivData data = getDataFor(market); 116 117 data.stabilityHistory.add(market.getStabilityValue()); 118 while (data.stabilityHistory.size() > maxSamples && !data.stabilityHistory.isEmpty()) { 119 data.stabilityHistory.remove(0); 120 } 121 } 122 } 123 124 protected void checkDeciv() { 125 for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 126 if (checkDeciv(market)) break; 127 } 128 } 129 130 131 protected boolean checkDeciv(MarketAPI market) { 132 MarketDecivData data = getDataFor(market); 133 134// if (market.getId().contains("chicomoztoc")) { 135// decivilize(market, false); 136// return true; 137// } 138 139 int max = getMaxMonths(); 140 int min = getMinStreak(); 141 float per = getProbPerMonth(); 142 float fraction = getMinFraction(); 143 144 if (data.stabilityHistory.size() < max) return false; 145 if (data.stabilityHistory.get(0) > 0 || market.getStabilityValue() > 0) return false; 146 147 float streak = 0; 148 float zeroCount = 0; 149 boolean streakEnded = false; 150 for (int i = data.stabilityHistory.size() - 1; i >= 0; i--) { 151 Float curr = data.stabilityHistory.get(i); 152 if (curr <= 0) { 153 zeroCount++; 154 if (!streakEnded) streak++; 155 } else { 156 streakEnded = true; 157 } 158 } 159 160 if (streak < min) return false; 161 if (zeroCount / max < fraction) return false; 162 163 if (Misc.isStoryCritical(market)) return false; 164 165 float prob = (streak - min) * per; 166 167 168 String id = market.getId(); 169 if (!sentWarning.contains(id)) { 170 sendWarning(market); 171 sentWarning.add(id, 180f); 172 return false; 173 } 174// if (prob == 0f) { 175// sendWarning(market); 176// return false; 177// } 178 179 if (random.nextFloat() >= prob) return false; 180 181 decivilize(market, false); 182 return true; 183 } 184 185 public static void decivilize(MarketAPI market, boolean fullDestroy) { 186 decivilize(market, fullDestroy, true); 187 } 188 189 public static void decivilize(MarketAPI market, boolean fullDestroy, boolean withIntel) { 190 if (market.getMemoryWithoutUpdate().getBoolean(NO_DECIV_KEY) && !fullDestroy) return; 191// System.out.println("Location: " + market.getLocationInHyperspace()); 192// if (true) return; 193 194 // issues with decivilizing stand-alone stations at the moment since they become treated as planets 195 //if (!(market.getPrimaryEntity() instanceof PlanetAPI)) return; 196 197 if (market.getPrimaryEntity().isDiscoverable()) return; 198 199 ListenerUtil.reportColonyAboutToBeDecivilized(market, fullDestroy); 200 201 if (withIntel) { 202 DecivIntel intel = new DecivIntel(market, market.getPrimaryEntity(), fullDestroy, false); 203 Global.getSector().getIntelManager().addIntel(intel); 204 } 205 206 market.setAdmin(null); 207 208 for (SectorEntityToken entity : market.getConnectedEntities()) { 209 entity.setFaction(Factions.NEUTRAL); 210 } 211 212 market.setPlanetConditionMarketOnly(true); 213 market.setFactionId(Factions.NEUTRAL); 214 215 market.getCommDirectory().clear(); 216 for (PersonAPI person : market.getPeopleCopy()) { 217 market.removePerson(person); 218 } 219 market.clearCommodities(); 220 221 for (MarketConditionAPI mc : new ArrayList<MarketConditionAPI>(market.getConditions())) { 222 if (mc.getSpec().isDecivRemove()) { 223 market.removeSpecificCondition(mc.getIdForPluginModifications()); 224 } 225 } 226 227 for (Industry ind : new ArrayList<Industry>(market.getIndustries())) { 228 market.removeIndustry(ind.getId(), null, false); 229 } 230 231 if (!fullDestroy && !market.hasCondition(Conditions.DECIVILIZED)) { 232 market.addCondition(Conditions.DECIVILIZED); 233 } 234 235 int size = market.getSize(); 236 market.removeCondition(Conditions.RUINS_SCATTERED); 237 market.removeCondition(Conditions.RUINS_WIDESPREAD); 238 market.removeCondition(Conditions.RUINS_EXTENSIVE); 239 market.removeCondition(Conditions.RUINS_VAST); 240 String id = null; 241 if (size <= 3) { 242 id = market.addCondition(Conditions.RUINS_SCATTERED); 243 } else if (size <= 4) { 244 id = market.addCondition(Conditions.RUINS_WIDESPREAD); 245 } else if (size <= 6) { 246 id = market.addCondition(Conditions.RUINS_EXTENSIVE); 247 } else { 248 id = market.addCondition(Conditions.RUINS_VAST); 249 } 250 if (id != null) { 251 MarketConditionAPI ruins = market.getSpecificCondition(id); 252 if (ruins != null) { 253 ruins.setSurveyed(true); 254 } 255 } 256 257 market.getMemoryWithoutUpdate().set("$wasCivilized", true); 258 259 market.setSize(1); 260 market.getPopulation().setWeight(CoreImmigrationPluginImpl.getWeightForMarketSizeStatic(market.getSize())); 261 market.getPopulation().normalize(); 262 263 for (SubmarketAPI sub : market.getSubmarketsCopy()) { 264 market.removeSubmarket(sub.getSpecId()); 265 } 266 267 for (SectorEntityToken entity : market.getConnectedEntities()) { 268 if (!(entity instanceof PlanetAPI)) { 269 Misc.setAbandonedStationMarket(market.getId() + "_deciv", entity); 270 } 271 } 272 273 SectorEntityToken primary = market.getPrimaryEntity(); 274 market.getConnectedEntities().clear(); 275 market.setPrimaryEntity(primary); 276 market.setPlayerOwned(false); 277 278 Global.getSector().getEconomy().removeMarket(market); 279 Misc.removeRadioChatter(market); 280 market.advance(0f); 281 282 ListenerUtil.reportColonyDecivilized(market, fullDestroy); 283 284// if (!(market.getPrimaryEntity() instanceof PlanetAPI)) { 285// Misc.setAbandonedStationMarket(market.getId() + "_deciv", primary); 286// } 287 288 } 289 290 291 public static void removeColony(MarketAPI market, boolean withRuins) { 292 market.setAdmin(null); 293 294 for (SectorEntityToken entity : market.getConnectedEntities()) { 295 entity.setFaction(Factions.NEUTRAL); 296 } 297 298 market.setPlanetConditionMarketOnly(true); 299 market.setFactionId(Factions.NEUTRAL); 300 301 market.getCommDirectory().clear(); 302 for (PersonAPI person : market.getPeopleCopy()) { 303 market.removePerson(person); 304 } 305 market.clearCommodities(); 306 307 for (MarketConditionAPI mc : new ArrayList<MarketConditionAPI>(market.getConditions())) { 308 if (mc.getSpec().isDecivRemove()) { 309 market.removeSpecificCondition(mc.getIdForPluginModifications()); 310 } 311 } 312 313 for (Industry ind : new ArrayList<Industry>(market.getIndustries())) { 314 market.removeIndustry(ind.getId(), null, false); 315 } 316 317 if (withRuins) { 318 int size = market.getSize(); 319 market.removeCondition(Conditions.RUINS_SCATTERED); 320 market.removeCondition(Conditions.RUINS_WIDESPREAD); 321 market.removeCondition(Conditions.RUINS_EXTENSIVE); 322 market.removeCondition(Conditions.RUINS_VAST); 323 if (size <= 3) { 324 market.addCondition(Conditions.RUINS_SCATTERED); 325 } else if (size <= 4) { 326 market.addCondition(Conditions.RUINS_WIDESPREAD); 327 } else if (size <= 6) { 328 market.addCondition(Conditions.RUINS_EXTENSIVE); 329 } else { 330 market.addCondition(Conditions.RUINS_VAST); 331 } 332 } 333 334 market.getMemoryWithoutUpdate().set("$wasCivilized", true); 335 336 market.setSize(1); 337 market.getPopulation().setWeight(CoreImmigrationPluginImpl.getWeightForMarketSizeStatic(market.getSize())); 338 market.getPopulation().normalize(); 339 340 for (SubmarketAPI sub : market.getSubmarketsCopy()) { 341 market.removeSubmarket(sub.getSpecId()); 342 } 343 344 for (SectorEntityToken entity : market.getConnectedEntities()) { 345 if (!(entity instanceof PlanetAPI)) { 346 Misc.setAbandonedStationMarket(market.getId() + "_deciv", entity); 347 } 348 } 349 350 market.setIncentiveCredits(0); 351 352 SectorEntityToken primary = market.getPrimaryEntity(); 353 market.getConnectedEntities().clear(); 354 market.setPrimaryEntity(primary); 355 market.setPlayerOwned(false); 356 357 Global.getSector().getEconomy().removeMarket(market); 358 Misc.removeRadioChatter(market); 359 market.advance(0f); 360 } 361 362 public static void sendWarning(MarketAPI market) { 363 if (market.getMemoryWithoutUpdate().getBoolean(DecivTracker.NO_DECIV_KEY)) return; 364 365 DecivIntel intel = new DecivIntel(market, market.getPrimaryEntity(), false, true); 366 Global.getSector().getIntelManager().addIntel(intel); 367 } 368 369 public boolean isDone() { 370 return false; 371 } 372 373 public boolean runWhilePaused() { 374 return false; 375 } 376 377} 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392