001package com.fs.starfarer.api.impl.campaign.intel.bases; 002 003import java.util.ArrayList; 004import java.util.Iterator; 005import java.util.LinkedHashSet; 006import java.util.List; 007import java.util.Random; 008import java.util.Set; 009 010import com.fs.starfarer.api.EveryFrameScript; 011import com.fs.starfarer.api.Global; 012import com.fs.starfarer.api.campaign.FactionAPI; 013import com.fs.starfarer.api.campaign.StarSystemAPI; 014import com.fs.starfarer.api.campaign.econ.MarketAPI; 015import com.fs.starfarer.api.impl.campaign.DebugFlags; 016import com.fs.starfarer.api.impl.campaign.ids.Conditions; 017import com.fs.starfarer.api.impl.campaign.ids.Factions; 018import com.fs.starfarer.api.impl.campaign.ids.Tags; 019import com.fs.starfarer.api.impl.campaign.intel.bases.PirateBaseIntel.PirateBaseTier; 020import com.fs.starfarer.api.impl.campaign.intel.events.PiracyRespiteScript; 021import com.fs.starfarer.api.util.IntervalUtil; 022import com.fs.starfarer.api.util.Misc; 023import com.fs.starfarer.api.util.WeightedRandomPicker; 024 025public class PlayerRelatedPirateBaseManager implements EveryFrameScript { 026 027 public static final String KEY = "$core_PR_pirateBaseManager"; 028 029 030 //public static int MIN_MONTHS_BEFORE_RAID = Global.getSettings().getInt("minMonthsBeforeFirstPirateRaidOnPlayerColony"); 031 032 public static int MIN_TIMEOUT = Global.getSettings().getIntFromArray("playerRelatedPirateBaseCreationTimeoutMonths", 0); 033 public static int MAX_TIMEOUT = Global.getSettings().getIntFromArray("playerRelatedPirateBaseCreationTimeoutMonths", 1); 034 035 public static int MIN_TIMEOUT_DESTROYED = Global.getSettings().getIntFromArray("playerRelatedPirateBaseCreationTimeoutExtraAfterBaseDestroyed", 0); 036 public static int MAX_TIMEOUT_DESTROYED = Global.getSettings().getIntFromArray("playerRelatedPirateBaseCreationTimeoutExtraAfterBaseDestroyed", 1); 037 038 039 public static PlayerRelatedPirateBaseManager getInstance() { 040 Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY); 041 return (PlayerRelatedPirateBaseManager) test; 042 } 043 044 045 protected long start = 0; 046 //protected boolean sentFirstRaid = false; 047 protected IntervalUtil monthlyInterval = new IntervalUtil(20f, 40f); 048 protected int monthsPlayerColoniesExist = 0; 049 protected int baseCreationTimeout = 0; 050 protected Random random = new Random(); 051 052 protected List<PirateBaseIntel> bases = new ArrayList<PirateBaseIntel>(); 053 054 public PlayerRelatedPirateBaseManager() { 055 super(); 056 Global.getSector().getMemoryWithoutUpdate().set(KEY, this); 057 start = Global.getSector().getClock().getTimestamp(); 058 } 059 060 061 public void advance(float amount) { 062 063 for (PirateBaseIntel intel : bases) { 064 intel.advance(amount); 065 } 066 067 float days = Misc.getDays(amount); 068 069 if (DebugFlags.RAID_DEBUG) { 070 days *= 100f; 071 } 072 073 monthlyInterval.advance(days); 074 075 if (monthlyInterval.intervalElapsed()) { 076 removeDestroyedBases(); 077 078 FactionAPI player = Global.getSector().getPlayerFaction(); 079 List<MarketAPI> markets = Misc.getFactionMarkets(player); 080 081 Iterator<MarketAPI> iter = markets.iterator(); 082 while (iter.hasNext()) { 083 if (iter.next().isHidden()) iter.remove(); 084 } 085 086 if (markets.isEmpty()) { 087 return; 088 } 089 090 monthsPlayerColoniesExist++; 091 092// if (!sentFirstRaid) { 093// if (monthsPlayerColoniesExist >= MIN_MONTHS_BEFORE_RAID && !markets.isEmpty()) { 094// sendFirstRaid(markets); 095// baseCreationTimeout = MIN_TIMEOUT + random.nextInt(MAX_TIMEOUT - MIN_TIMEOUT + 1); 096// } 097// return; 098// } 099 100 if (baseCreationTimeout > 0) { 101 baseCreationTimeout--; 102 } else { 103 if (random.nextFloat() > 0.5f && PiracyRespiteScript.get() == null) { 104 addBasesAsNeeded(); 105 } 106 } 107 } 108 } 109 110 protected void removeDestroyedBases() { 111 Iterator<PirateBaseIntel> iter = bases.iterator(); 112 while (iter.hasNext()) { 113 PirateBaseIntel intel = iter.next(); 114 if (intel.isEnded() && !intel.getMarket().isInEconomy()) { 115 iter.remove(); 116 117// int baseTimeout = 3; 118// switch (intel.getTier()) { 119// case TIER_1_1MODULE: baseTimeout = 3; break; 120// case TIER_2_1MODULE: baseTimeout = 3; break; 121// case TIER_3_2MODULE: baseTimeout = 4; break; 122// case TIER_4_3MODULE: baseTimeout = 5; break; 123// case TIER_5_3MODULE: baseTimeout = 6; break; 124// } 125// baseCreationTimeout += baseTimeout + random.nextInt(baseTimeout + 1); 126 baseCreationTimeout += MIN_TIMEOUT_DESTROYED + random.nextInt(MAX_TIMEOUT_DESTROYED - MIN_TIMEOUT_DESTROYED + 1); 127 } 128 } 129 } 130 131 protected void addBasesAsNeeded() { 132 FactionAPI player = Global.getSector().getPlayerFaction(); 133 List<MarketAPI> markets = Misc.getFactionMarkets(player); 134 135 Set<StarSystemAPI> systems = new LinkedHashSet<StarSystemAPI>(); 136 for (MarketAPI curr : markets) { 137 StarSystemAPI system = curr.getStarSystem(); 138 if (system != null) { 139 systems.add(system); 140 } 141 } 142 if (systems.isEmpty()) return; 143 144 float marketTotal = markets.size(); 145 int numBases = (int) (marketTotal / 2); 146 if (numBases < 1) numBases = 1; 147 if (numBases > 2) numBases = 2; 148 149 150 if (bases.size() >= numBases) { 151 return; 152 } 153 154 155 StarSystemAPI initialTarget = null; 156 float bestWeight = 0f; 157 OUTER: for (StarSystemAPI curr : systems) { 158 float w = 0f; 159 for (MarketAPI m : Global.getSector().getEconomy().getMarkets(curr)) { 160 if (m.hasCondition(Conditions.PIRATE_ACTIVITY)) continue OUTER; 161 if (m.getFaction().isPlayerFaction()) { 162 w += m.getSize() * m.getSize(); 163 } 164 } 165 if (w > bestWeight) { 166 bestWeight = w; 167 initialTarget = curr; 168 } 169 } 170 171 if (initialTarget == null) return; 172 173 StarSystemAPI target = pickSystemForPirateBase(initialTarget); 174 if (target == null) return; 175 176 PirateBaseTier tier = pickTier(target); 177 178 String factionId = pickPirateFaction(); 179 if (factionId == null) return; 180 181 //factionId = Factions.HEGEMONY; 182 183 PirateBaseIntel intel = new PirateBaseIntel(target, factionId, tier); 184 if (intel.isDone()) { 185 intel = null; 186 return; 187 } 188 189 //intel.setTargetPlayerColoniesOnly(true); 190 // this is for raids: don't do it since raids are handled by HostileActivityEventIntel now 191 //intel.setForceTarget(initialTarget); 192 intel.updateTarget(); 193 bases.add(intel); 194 195 baseCreationTimeout = MIN_TIMEOUT + random.nextInt(MAX_TIMEOUT - MIN_TIMEOUT + 1); 196 } 197 198 public String pickPirateFaction() { 199 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random); 200 for (FactionAPI faction : Global.getSector().getAllFactions()) { 201 if (!faction.isHostileTo(Factions.PLAYER)) continue; 202 203 if (faction.getCustomBoolean(Factions.CUSTOM_MAKES_PIRATE_BASES)) { 204 picker.add(faction.getId(), 1f); 205 } 206 } 207 return picker.pick(); 208 } 209 210// protected void sendFirstRaid(List<MarketAPI> markets) { 211// if (markets.isEmpty()) return; 212// 213// 214// WeightedRandomPicker<MarketAPI> picker = new WeightedRandomPicker<MarketAPI>(random); 215// picker.addAll(markets); 216// MarketAPI target = picker.pick(); 217// 218// PirateBaseIntel closest = null; 219// float minDist = Float.MAX_VALUE; 220// for (IntelInfoPlugin p : Global.getSector().getIntelManager().getIntel(PirateBaseIntel.class)) { 221// PirateBaseIntel intel = (PirateBaseIntel) p; 222// if (intel.isEnding()) continue; 223// 224// float dist = Misc.getDistanceLY(intel.getMarket().getPrimaryEntity(), target.getPrimaryEntity()); 225// if (dist < minDist && dist <= 15) { 226// minDist = dist; 227// closest = intel; 228// } 229// } 230// 231// if (closest != null && target != null) { 232// float raidFP = 120 + 30f * random.nextFloat(); 233//// raidFP = 1000; 234//// raidFP = 500; 235// closest.startRaid(target.getStarSystem(), raidFP); 236// sentFirstRaid = true; 237// } 238// } 239 240 241 242 protected PirateBaseTier pickTier(StarSystemAPI system) { 243 float max = 0f; 244 for (MarketAPI m : Global.getSector().getEconomy().getMarkets(system)) { 245 if (m.getFaction().isPlayerFaction()) { 246 max = Math.max(m.getSize(), max); 247 } 248 } 249 if (max >= 7) { 250 return PirateBaseTier.TIER_5_3MODULE; 251 } else if (max >= 6) { 252 return PirateBaseTier.TIER_4_3MODULE; 253 } else if (max >= 5) { 254 return PirateBaseTier.TIER_3_2MODULE; 255 } else if (max >= 4) { 256 return PirateBaseTier.TIER_2_1MODULE; 257 } else { 258 return PirateBaseTier.TIER_1_1MODULE; 259 } 260 261 } 262 263 protected StarSystemAPI pickSystemForPirateBase(StarSystemAPI initialTarget) { 264 WeightedRandomPicker<StarSystemAPI> veryFar = new WeightedRandomPicker<StarSystemAPI>(random); 265 WeightedRandomPicker<StarSystemAPI> far = new WeightedRandomPicker<StarSystemAPI>(random); 266 WeightedRandomPicker<StarSystemAPI> picker = new WeightedRandomPicker<StarSystemAPI>(random); 267 268 for (StarSystemAPI system : Global.getSector().getStarSystems()) { 269 if (system.hasPulsar()) continue; 270 271 float days = Global.getSector().getClock().getElapsedDaysSince(system.getLastPlayerVisitTimestamp()); 272 if (days < 180f) continue; 273 274 if (system.getCenter().getMemoryWithoutUpdate().contains(PirateBaseManager.RECENTLY_USED_FOR_BASE)) continue; 275 276 float weight = 0f; 277 if (system.hasTag(Tags.THEME_MISC_SKIP)) { 278 weight = 1f; 279 } else if (system.hasTag(Tags.THEME_MISC)) { 280 weight = 3f; 281 } else if (system.hasTag(Tags.THEME_REMNANT_NO_FLEETS)) { 282 weight = 3f; 283 } else if (system.hasTag(Tags.THEME_REMNANT_DESTROYED)) { 284 weight = 3f; 285 } else if (system.hasTag(Tags.THEME_RUINS)) { 286 weight = 5f; 287 } else if (system.hasTag(Tags.THEME_CORE_UNPOPULATED)) { 288 //weight = 1f; 289 weight = 0f; 290 } 291 if (weight <= 0f) continue; 292 293 float usefulStuff = system.getCustomEntitiesWithTag(Tags.OBJECTIVE).size() + 294 system.getCustomEntitiesWithTag(Tags.STABLE_LOCATION).size(); 295 if (usefulStuff <= 0) continue; 296 297 if (Misc.getMarketsInLocation(system).size() > 0) continue; 298 299 float dist = Misc.getDistance(initialTarget.getLocation(), system.getLocation()); 300 301 float distMult = 100000f / dist; 302 distMult *= distMult; 303 304 if (dist > 30000f) { 305 veryFar.add(system, weight * usefulStuff * distMult); 306 } else if (dist > 10000f) { 307 far.add(system, weight * usefulStuff * distMult); 308 } else { 309 picker.add(system, weight * usefulStuff * distMult); 310 } 311 } 312 313 if (picker.isEmpty()) { 314 picker.addAll(far); 315 } 316 if (picker.isEmpty()) { 317 picker.addAll(veryFar); 318 } 319 320 return picker.pick(); 321 } 322 323 324 public boolean isDone() { 325 return false; 326 } 327 328 329 public boolean runWhilePaused() { 330 return false; 331 } 332 333} 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348