001package com.fs.starfarer.api.impl.combat.threat; 002 003import java.util.List; 004import java.util.Random; 005 006import org.lwjgl.util.vector.Vector2f; 007 008import com.fs.starfarer.api.Global; 009import com.fs.starfarer.api.campaign.CampaignFleetAPI; 010import com.fs.starfarer.api.campaign.FactionAPI; 011import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode; 012import com.fs.starfarer.api.campaign.FactionAPI.ShipPickParams; 013import com.fs.starfarer.api.campaign.LocationAPI; 014import com.fs.starfarer.api.campaign.StarSystemAPI; 015import com.fs.starfarer.api.campaign.listeners.CurrentLocationChangedListener; 016import com.fs.starfarer.api.fleet.FleetMemberAPI; 017import com.fs.starfarer.api.fleet.ShipRolePick; 018import com.fs.starfarer.api.impl.campaign.enc.AbyssalRogueStellarObjectEPEC; 019import com.fs.starfarer.api.impl.campaign.fleets.DisposableFleetManager; 020import com.fs.starfarer.api.impl.campaign.ids.Factions; 021import com.fs.starfarer.api.impl.campaign.ids.FleetTypes; 022import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 023import com.fs.starfarer.api.impl.campaign.ids.ShipRoles; 024import com.fs.starfarer.api.impl.campaign.ids.Tags; 025import com.fs.starfarer.api.util.Misc; 026import com.fs.starfarer.api.util.WeightedRandomPicker; 027 028public class DisposableThreatFleetManager extends DisposableFleetManager implements CurrentLocationChangedListener { 029 030 public static float DEPTH_0 = 2f; 031 public static float DEPTH_1 = 3f; 032 public static float DEPTH_2 = 4f; 033 034 public static enum FabricatorEscortStrength { 035 NONE, 036 LOW, 037 MEDIUM, 038 HIGH, 039 MAXIMUM, 040 } 041 042 public static class ThreatFleetCreationParams { 043 public int numFabricators = 0; 044 public int numHives = 0; 045 public int numOverseers = 0; 046 public int numCapitals = 0; 047 public int numCruisers = 0; 048 public int numDestroyers = 0; 049 public int numFrigates = 0; 050 051 public String fleetType = FleetTypes.PATROL_SMALL; 052 } 053 054 055 /** 056 * In $player memory. 057 */ 058 public static String SENSOR_MODS_KEY = "$hasThreatDetectionSensorMods"; 059 060 public static String THREAT_DETECTED_RANGE_MULT_ID = "threat_fleet_stealth"; 061 062 public static float THREAT_DETECTED_RANGE_MULT = 0.1f; 063 public static float ONSLAUGHT_MKI_SENSOR_MODIFICATIONS_RANGE_MULT = 10f; 064 065 public static int MIN_FLEETS = 1; 066 public static int MAX_FLEETS = 6; 067 068 069 public DisposableThreatFleetManager() { 070 Global.getSector().getListenerManager().addListener(this); 071 } 072 073 protected Object readResolve() { 074 super.readResolve(); 075 return this; 076 } 077 078 @Override 079 protected String getSpawnId() { 080 return "threat"; 081 } 082 083 084 @Override 085 public void advance(float amount) { 086 super.advance(amount); 087 088 // want Threat fleets to basically "be there" not gradually spawn in 089 if (spawnRateMult > 0) { 090 spawnRateMult = 1000f; 091 } 092 } 093 094 095 @Override 096 public void reportCurrentLocationChanged(LocationAPI prev, LocationAPI curr) { 097 if (tracker2 != null) { 098 tracker2.forceIntervalElapsed(); 099 } 100 } 101 102 @Override 103 protected float getExpireDaysPerFleet() { 104 return 365f; // don't spawn again for a long time after reaching max 105 } 106 107 @Override 108 protected int getDesiredNumFleetsForSpawnLocation() { 109 String id = currSpawnLoc.getOptionalUniqueId(); 110 if (id == null) id = currSpawnLoc.getId(); 111 112 Random random = new Random(id.hashCode() * 1343890534L); 113 114 float depth = Misc.getAbyssalDepth(currSpawnLoc.getLocation(), true); 115 if (depth <= 1f) return 0; 116 117 float maxDepth = AbyssalRogueStellarObjectEPEC.MAX_THREAT_PROB_DEPTH; 118 119 float f = (depth - 1f) / (maxDepth - 1f); 120 if (f > 1f) f = 1f; 121 122 int minFleets = 1; 123 int maxFleets = MIN_FLEETS + 124 Math.round((MAX_FLEETS - MIN_FLEETS) * f); 125 126 return minFleets + random.nextInt(maxFleets - minFleets + 1); 127 } 128 129 @Override 130 protected boolean withReturnToSourceAssignments() { 131 return false; 132 } 133 134 protected StarSystemAPI pickCurrentSpawnLocation() { 135 if (Global.getSector().isInNewGameAdvance()) return null; 136 CampaignFleetAPI player = Global.getSector().getPlayerFleet(); 137 if (player == null) return null; 138 StarSystemAPI nearest = null; 139 float minDist = Float.MAX_VALUE; 140 141 for (StarSystemAPI system : Global.getSector().getStarSystems()) { 142 if (!system.hasTag(Tags.SYSTEM_CAN_SPAWN_THREAT)) continue; 143 144 float distToPlayerLY = Misc.getDistanceLY(player.getLocationInHyperspace(), system.getLocation()); 145 if (distToPlayerLY > MAX_RANGE_FROM_PLAYER_LY) continue; 146 147 if (distToPlayerLY < minDist) { 148 nearest = system; 149 minDist = distToPlayerLY; 150 } 151 } 152 153 // stick with current system longer unless something else is closer 154 if (nearest == null && currSpawnLoc != null) { 155 float distToPlayerLY = Misc.getDistanceLY(player.getLocationInHyperspace(), currSpawnLoc.getLocation()); 156 if (distToPlayerLY <= DESPAWN_RANGE_LY) { 157 nearest = currSpawnLoc; 158 } 159 } 160 161 return nearest; 162 } 163 164 165 protected CampaignFleetAPI spawnFleetImpl() { 166 StarSystemAPI system = currSpawnLoc; 167 if (system == null) return null; 168 169 CampaignFleetAPI player = Global.getSector().getPlayerFleet(); 170 if (player == null) return null; 171 172 float depth = Misc.getAbyssalDepth(system.getLocation(), true); 173 174 int numFirst = 0; 175 int numSecond = 0; 176 int numThird = 0; 177 for (CampaignFleetAPI fleet : currSpawnLoc.getFleets()) { 178 if (fleet.getFaction().getId().equals(Factions.THREAT)) { 179 String type = Misc.getFleetType(fleet); 180 if (type == null) continue; 181 182 if (type.equals(FleetTypes.PATROL_SMALL)) { 183 numFirst++; 184 } else if (type.equals(FleetTypes.PATROL_MEDIUM)) { 185 numSecond++; 186 } else if (type.equals(FleetTypes.PATROL_LARGE)) { 187 numThird++; 188 } 189 } 190 } 191 192 // this is not entriely accruate because depths don't correspond 100% with first/second/third strike 193 // that's fine, though 194 int maxSecond = 1; 195 if (depth >= DEPTH_2 && (float) Math.random() < 0.5f) maxSecond = 2; 196 197 if (numThird > 0) { 198 depth = Math.min(depth, DEPTH_2 - 0.1f); 199 } 200 if (numSecond > maxSecond) { 201 if ((float) Math.random() < 0.5f) { 202 depth = Math.min(depth, DEPTH_0 - 0.1f); 203 } else { 204 depth = Math.min(depth, DEPTH_1 - 0.1f); 205 } 206 } 207 208 209 WeightedRandomPicker<FabricatorEscortStrength> picker = new WeightedRandomPicker<>(); 210 FabricatorEscortStrength strength = FabricatorEscortStrength.NONE; 211 int fabricators = 0; 212 if (depth < DEPTH_0) { 213 fabricators = 0; 214 picker.add(FabricatorEscortStrength.LOW, 3f); 215 picker.add(FabricatorEscortStrength.MEDIUM, 10f); 216 picker.add(FabricatorEscortStrength.HIGH, 1f); 217 strength = picker.pick(); 218 } else if (depth < DEPTH_1) { 219 fabricators = 1; 220 picker.add(FabricatorEscortStrength.NONE, 1f); 221 picker.add(FabricatorEscortStrength.LOW, 10f); 222 picker.add(FabricatorEscortStrength.MEDIUM, 5f); 223 strength = picker.pick(); 224 if (strength == FabricatorEscortStrength.NONE) { 225 fabricators = 1; 226 } 227 } else if (depth < DEPTH_2) { 228 fabricators = 2; 229 picker.add(FabricatorEscortStrength.LOW, 10f); 230 picker.add(FabricatorEscortStrength.MEDIUM, 5f); 231 picker.add(FabricatorEscortStrength.HIGH, 5f); 232 picker.add(FabricatorEscortStrength.MAXIMUM, 5f); 233 strength = picker.pick(); 234 if (strength == FabricatorEscortStrength.MAXIMUM) { 235 fabricators = 1; 236 } 237 } else { 238 fabricators = 2; 239 picker.add(FabricatorEscortStrength.LOW, 10f); 240 picker.add(FabricatorEscortStrength.MEDIUM, 5f); 241 picker.add(FabricatorEscortStrength.HIGH, 5f); 242 picker.add(FabricatorEscortStrength.MAXIMUM, 5f); 243 strength = picker.pick(); 244 if (strength == FabricatorEscortStrength.LOW || strength == FabricatorEscortStrength.MEDIUM) { 245 fabricators = 3; 246 } 247 } 248 249 CampaignFleetAPI f = createThreatFleet(fabricators, 0, 0, strength, null); 250 251 system.addEntity(f); 252 253 float radius = 4000f + 2000f * (float) Math.random(); 254 Vector2f loc = Misc.getPointAtRadius(new Vector2f(), radius); 255 f.setLocation(loc.x, loc.y); 256 257 f.addScript(new ThreatFleetBehaviorScript(f, system)); 258 259 return f; 260 } 261 262 public static CampaignFleetAPI createThreatFleet(int numFabricators, 263 int minOtherCapitals, int maxOtherCapitals, FabricatorEscortStrength escorts, Random random) { 264 if (random == null) random = Misc.random; 265 266 int minHives = 0; 267 int maxHives = 0; 268 int minOverseers = 0; 269 int maxOverseers = 0; 270 int minCruisers = 0; 271 int maxCruisers = 0; 272 int minDestroyers = 0; 273 int maxDestroyers = 0; 274 int minFrigates = 0; 275 int maxFrigates = 0; 276 277 switch (escorts) { 278 case NONE: 279 break; 280 case LOW: 281 minOverseers = 0; 282 maxOverseers = 1; 283 minDestroyers = 0; 284 maxDestroyers = 1; 285 minFrigates = 2; 286 maxFrigates = 4; 287 if (numFabricators <= 0) { 288 minOverseers = 1; 289 } 290 break; 291 case MEDIUM: 292 minHives = 0; 293 maxHives = 1; 294 minOverseers = 1; 295 maxOverseers = 1; 296 minCruisers = 0; 297 maxCruisers = 1; 298 minDestroyers = 1; 299 maxDestroyers = 2; 300 minFrigates = 2; 301 maxFrigates = 4; 302 if (numFabricators <= 0) { 303 minHives = 1; 304 } 305 break; 306 case HIGH: 307 minHives = 2; 308 maxHives = 3; 309 minOverseers = 2; 310 maxOverseers = 2; 311 minCruisers = 2; 312 maxCruisers = 3; 313 minDestroyers = 3; 314 maxDestroyers = 6; 315 minFrigates = 7; 316 maxFrigates = 11; 317 318 break; 319 case MAXIMUM: 320 minHives = 3; 321 maxHives = 4; 322 minOverseers = 3; 323 maxOverseers = 3; 324 minCruisers = 4; 325 maxCruisers = 5; 326 minDestroyers = 5; 327 maxDestroyers = 6; 328 minFrigates = 5; 329 maxFrigates = 6; 330 break; 331 } 332 333 ThreatFleetCreationParams params = new ThreatFleetCreationParams(); 334 params.numFabricators = numFabricators; 335 params.numHives = minHives + random.nextInt(maxHives - minHives + 1); 336 params.numOverseers = minOverseers + random.nextInt(maxOverseers - minOverseers + 1); 337 params.numCapitals = minOtherCapitals + random.nextInt(maxOtherCapitals - minOtherCapitals + 1); 338 params.numCruisers = minCruisers + random.nextInt(maxCruisers - minCruisers + 1); 339 params.numDestroyers = minDestroyers + random.nextInt(maxDestroyers - minDestroyers + 1); 340 params.numFrigates = minFrigates + random.nextInt(maxFrigates - minFrigates + 1); 341 342 params.fleetType = FleetTypes.PATROL_SMALL; 343 if (numFabricators >= 3 || 344 (numFabricators >= 2 && escorts.ordinal() >= FabricatorEscortStrength.HIGH.ordinal())) { 345 params.fleetType = FleetTypes.PATROL_LARGE; 346 } else if (numFabricators >= 2 || 347 (numFabricators >= 1 && escorts.ordinal() >= FabricatorEscortStrength.HIGH.ordinal())) { 348 params.fleetType = FleetTypes.PATROL_MEDIUM; 349 } 350 351 return createThreatFleet(params, random); 352 } 353 354 public static CampaignFleetAPI createThreatFleet(ThreatFleetCreationParams params, Random random) { 355 CampaignFleetAPI f = Global.getFactory().createEmptyFleet(Factions.THREAT, "Host", true); 356 f.setInflater(null); 357 f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_TYPE, params.fleetType); 358 359 addShips(f, params.numFabricators, ShipRoles.THREAT_FABRICATOR, random); 360 addShips(f, params.numHives, ShipRoles.THREAT_HIVE, random); 361 addShips(f, params.numOverseers, ShipRoles.THREAT_OVERSEER, random); 362 addShips(f, params.numCapitals, ShipRoles.COMBAT_CAPITAL, random); 363 addShips(f, params.numCruisers, ShipRoles.COMBAT_LARGE, random); 364 addShips(f, params.numDestroyers, ShipRoles.COMBAT_MEDIUM, random); 365 addShips(f, params.numFrigates, ShipRoles.COMBAT_SMALL, random); 366 367 f.getFleetData().setSyncNeeded(); 368 f.getFleetData().syncIfNeeded(); 369 f.getFleetData().sort(); 370 371 for (FleetMemberAPI curr : f.getFleetData().getMembersListCopy()) { 372 curr.getRepairTracker().setCR(curr.getRepairTracker().getMaxCR()); 373 } 374 375 FactionAPI faction = Global.getSector().getFaction(Factions.THREAT); 376 f.setName(faction.getFleetTypeName(params.fleetType)); 377 378 379 f.getMemoryWithoutUpdate().set(MemFlags.FLEET_INTERACTION_DIALOG_CONFIG_OVERRIDE_GEN, 380 new ThreatFIDConfig()); 381 f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_AGGRESSIVE, true); 382 f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_HOSTILE, true); 383 f.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_ALLOW_LONG_PURSUIT, true); 384 f.getMemoryWithoutUpdate().set(MemFlags.MAY_GO_INTO_ABYSS, true); 385 f.getDetectedRangeMod().modifyMult(THREAT_DETECTED_RANGE_MULT_ID, THREAT_DETECTED_RANGE_MULT, "Low emission drives"); 386 387 388 return f; 389 } 390 391 public static void addShips(CampaignFleetAPI fleet, int num, String role, Random random) { 392 FactionAPI faction = Global.getSector().getFaction(Factions.THREAT); 393 394 ShipPickParams p = new ShipPickParams(ShipPickMode.ALL); 395 p.blockFallback = true; 396 p.maxFP = 1000000; 397 398 for (int i = 0; i < num; i++) { 399 List<ShipRolePick> picks = faction.pickShip(role, p, null, random); 400 for (ShipRolePick pick : picks) { 401 fleet.getFleetData().addFleetMember(pick.variantId); 402 } 403 } 404 } 405 406} 407 408 409 410 411 412 413 414