001package com.fs.starfarer.api.impl.campaign.fleets; 002 003import java.util.LinkedHashMap; 004 005import com.fs.starfarer.api.Global; 006import com.fs.starfarer.api.campaign.CampaignFleetAPI; 007import com.fs.starfarer.api.campaign.StarSystemAPI; 008import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI; 009import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.EncounterOption; 010import com.fs.starfarer.api.campaign.econ.MarketAPI; 011import com.fs.starfarer.api.impl.campaign.ids.MemFlags; 012import com.fs.starfarer.api.impl.campaign.ids.Tags; 013import com.fs.starfarer.api.impl.campaign.tutorial.TutorialMissionIntel; 014import com.fs.starfarer.api.util.IntervalUtil; 015import com.fs.starfarer.api.util.Misc; 016import com.fs.starfarer.api.util.TimeoutTracker; 017 018/** 019 * Picks a star system near the player meeting certain criteria and spawns certain types of fleets there, 020 * but outside the player's vision. 021 * 022 * Despawns them as soon as possible when a different star system is picked. 023 * 024 * Copyright 2018 Fractal Softworks, LLC 025 */ 026public abstract class DisposableFleetManager extends PlayerVisibleFleetManager { 027 028 public static boolean DEBUG = false; 029 030 public static final String KEY_SYSTEM = "$core_disposableFleetSpawnSystem"; 031 public static final String KEY_SPAWN_FP = "$core_disposableFleetSpawnFP"; 032 //public static final float MAX_RANGE_FROM_PLAYER_LY = 3f; 033 public static final float MAX_RANGE_FROM_PLAYER_LY = RouteManager.SPAWN_DIST_LY; 034 public static final float DESPAWN_RANGE_LY = MAX_RANGE_FROM_PLAYER_LY + 1.4f; 035 036 protected IntervalUtil tracker2 = new IntervalUtil(0.75f, 1.25f);; 037 protected LinkedHashMap<String, TimeoutTracker<Boolean>> recentSpawns = new LinkedHashMap<String, TimeoutTracker<Boolean>>(); 038 039 protected Object readResolve() { 040 super.readResolve(); 041 return this; 042 } 043 044 protected float getExpireDaysPerFleet() { 045 return 30f; 046 } 047 048 protected String getSpawnKey(StarSystemAPI system) { 049 String sysId = system.getOptionalUniqueId(); 050 if (sysId == null) sysId = system.getName(); 051 return "$core_recentSpawn_" + getSpawnId() + "_" + sysId; 052 } 053 054 protected void addRecentSpawn(StarSystemAPI system) { 055 String key = getSpawnKey(system); 056 float e = Global.getSector().getMemoryWithoutUpdate().getExpire(key); 057 if (e < 0) e = 0; 058 e += getExpireDaysPerFleet(); 059 Global.getSector().getMemoryWithoutUpdate().set(key, true); 060 Global.getSector().getMemoryWithoutUpdate().expire(key, e); 061 } 062 063 protected float getRecentSpawnsForSystem(StarSystemAPI system) { 064 if (system == null) return 0f; 065 String key = getSpawnKey(system); 066 float e = Global.getSector().getMemoryWithoutUpdate().getExpire(key); 067 if (e < 0) e = 0; 068 return e / getExpireDaysPerFleet(); 069 } 070 071 @Override 072 protected int getMaxFleets() { 073 return 100; // limiting is based on spawnRateMult instead 074 } 075 076 @Override 077 protected boolean isOkToDespawnAssumingNotPlayerVisible(CampaignFleetAPI fleet) { 078 if (currSpawnLoc == null) return true; 079 String system = fleet.getMemoryWithoutUpdate().getString(KEY_SYSTEM); 080 float spawnFP = fleet.getMemoryWithoutUpdate().getFloat(KEY_SPAWN_FP); 081 CampaignFleetAPI player = Global.getSector().getPlayerFleet(); 082 float playerFP = player.getFleetPoints(); 083 084 if (system == null || !system.equals(currSpawnLoc.getName())) return true; 085 086 if (spawnFP >= fleet.getFleetPoints() * 2f) { 087 if (fleet.getAI() instanceof CampaignFleetAIAPI) { 088 CampaignFleetAIAPI ai = (CampaignFleetAIAPI) fleet.getAI(); 089 EncounterOption option = ai.pickEncounterOption(null, player, true); 090 if (option == EncounterOption.DISENGAGE) return true; 091 } else { 092 return fleet.getFleetPoints() <= playerFP * 0.5f; 093 } 094 } 095 096 return false; 097 } 098 099 @Override 100 public float getSpawnRateMult() { 101 return spawnRateMult; 102 } 103 104 protected float spawnRateMult = 1f; 105 protected StarSystemAPI currSpawnLoc = null; 106 107 protected void currSpawnLocChanged() { 108 109 } 110 111 @Override 112 public void advance(float amount) { 113 if (TutorialMissionIntel.isTutorialInProgress()) { 114 return; 115 } 116 117 super.advance(amount); 118 119 120 CampaignFleetAPI player = Global.getSector().getPlayerFleet(); 121 if (player == null) return; 122 123 float days = Global.getSector().getClock().convertToDays(amount); 124 if (DEBUG) { 125 days *= 100f; 126 } 127 128 tracker2.advance(days); 129 if (tracker2.intervalElapsed()) { 130 StarSystemAPI closest = pickCurrentSpawnLocation(); 131 if (closest != currSpawnLoc) { 132 currSpawnLoc = closest; 133 currSpawnLocChanged(); 134 } 135 136 if (withReturnToSourceAssignments()) { 137 //List<ManagedFleetData> remove = new ArrayList<ManagedFleetData>(); 138 for (ManagedFleetData data : active) { 139 if (Misc.isFleetReturningToDespawn(data.fleet)) continue; 140 // if it's player-visible/in the currently active location, 141 // make it return to source when it's been beat up enough 142 // to be worth despawning 143 //if (isOkToDespawnAssumingNotPlayerVisible(data.fleet)) { 144 145 float fp = data.fleet.getFleetPoints(); 146 float spawnFP = data.fleet.getMemoryWithoutUpdate().getFloat(KEY_SPAWN_FP); 147 if (fp < spawnFP * 0.33f) { 148 Misc.giveStandardReturnToSourceAssignments(data.fleet); 149 //remove.add(data); 150 } 151 } 152 } 153 154 //active.removeAll(remove); 155 156 updateSpawnRateMult(); 157 } 158 } 159 160 protected boolean withReturnToSourceAssignments() { 161 return true; 162 } 163 164 public StarSystemAPI getCurrSpawnLoc() { 165 return currSpawnLoc; 166 } 167 168 protected void updateSpawnRateMult() { 169 if (currSpawnLoc == null) { 170 if (DEBUG) { 171 System.out.println("No target system, spawnRateMult is 1"); 172 } 173 spawnRateMult = 1f; 174 return; 175 } 176 177 float desiredNumFleets = getDesiredNumFleetsForSpawnLocation(); 178 float recentSpawns = getRecentSpawnsForSystem(currSpawnLoc); 179 if (active != null) { 180 float activeInSystem = 0f; 181 for (ManagedFleetData data : active) { 182 if (data.spawnedFor == currSpawnLoc || data.fleet.getContainingLocation() == currSpawnLoc) { 183 activeInSystem++; 184 } 185 } 186 recentSpawns = Math.max(recentSpawns, activeInSystem); 187 } 188 189 spawnRateMult = (float) Math.pow(Math.max(0, (desiredNumFleets - recentSpawns) * 1f), 4f); 190 if (spawnRateMult < 0) spawnRateMult = 0; 191 192 //if (DEBUG || this instanceof DisposableHostileActivityFleetManager) { 193 if (DEBUG) { 194 System.out.println(String.format("ID: %s, system: %s, recent: %s, desired: %s, spawnRateMult: %s", 195 getSpawnId(), 196 currSpawnLoc.getName(), 197 "" + recentSpawns, 198 "" + desiredNumFleets, 199 "" + spawnRateMult)); 200 } 201 } 202 203 protected abstract int getDesiredNumFleetsForSpawnLocation(); 204 205 protected abstract CampaignFleetAPI spawnFleetImpl(); 206 protected abstract String getSpawnId(); 207 208 protected StarSystemAPI pickCurrentSpawnLocation() { 209 return pickNearestPopulatedSystem(); 210 } 211 protected StarSystemAPI pickNearestPopulatedSystem() { 212 if (Global.getSector().isInNewGameAdvance()) return null; 213 CampaignFleetAPI player = Global.getSector().getPlayerFleet(); 214 if (player == null) return null; 215 StarSystemAPI nearest = null; 216 float minDist = Float.MAX_VALUE; 217 for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 218 if (market.isHidden()) continue; 219 if (market.getStarSystem() != null && market.getStarSystem().hasTag(Tags.SYSTEM_ABYSSAL)) continue; 220 221 if (market.isPlayerOwned() && market.getSize() <= 3) continue; 222 if (!market.hasSpaceport()) continue; 223 224 float distToPlayerLY = Misc.getDistanceLY(player.getLocationInHyperspace(), market.getLocationInHyperspace()); 225 226 if (distToPlayerLY > MAX_RANGE_FROM_PLAYER_LY) continue; 227 228 if (distToPlayerLY < minDist && market.getStarSystem() != null) { 229 if (market.getStarSystem().getStar() != null) { 230 if (market.getStarSystem().getStar().getSpec().isPulsar()) continue; 231 } 232 233 nearest = market.getStarSystem(); 234 minDist = distToPlayerLY; 235 } 236 } 237 238 239 // stick with current system longer unless something else is closer 240 if (nearest == null && currSpawnLoc != null) { 241 float distToPlayerLY = Misc.getDistanceLY(player.getLocationInHyperspace(), currSpawnLoc.getLocation()); 242 if (distToPlayerLY <= DESPAWN_RANGE_LY) { 243 nearest = currSpawnLoc; 244 } 245 } 246 247 return nearest; 248 } 249 250 public CampaignFleetAPI spawnFleet() { 251 if (currSpawnLoc == null) return null; 252 253 // otherwise, possible for jump-point dialog to say there's nothing on other side 254 // but there will be by the time the player comes out 255 if (Global.getSector().getPlayerFleet() != null && Global.getSector().getPlayerFleet().isInHyperspaceTransition()) { 256 return null; 257 } 258 259 CampaignFleetAPI fleet = spawnFleetImpl(); 260 if (fleet != null) { 261 fleet.getMemoryWithoutUpdate().set(KEY_SYSTEM, currSpawnLoc.getName()); 262 fleet.getMemoryWithoutUpdate().set(KEY_SPAWN_FP, fleet.getFleetPoints()); 263 } 264 265 // do this even if fleet is null, to avoid non-stop fail-spawning of fleets 266 // if spawnFleetImpl() can't spawn one, for whatever reason 267 addRecentSpawn(currSpawnLoc); 268 updateSpawnRateMult(); 269 270 return fleet; 271 } 272 273 protected String getTravelText(StarSystemAPI system, CampaignFleetAPI fleet) { 274 return "traveling to the " + system.getBaseName() + " star system"; 275 } 276 277 protected String getActionInsideText(StarSystemAPI system, CampaignFleetAPI fleet) { 278 boolean patrol = fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET); 279 String verb = "raiding"; 280 if (patrol) verb = "patrolling"; 281 return verb + " the " + system.getBaseName() + " star system"; 282 } 283 284 protected String getActionOutsideText(StarSystemAPI system, CampaignFleetAPI fleet) { 285 boolean patrol = fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.MEMORY_KEY_PATROL_FLEET); 286 String verb = "raiding"; 287 if (patrol) verb = "patrolling"; 288 return verb + " around the " + system.getBaseName() + " star system"; 289 } 290 291 protected void setLocationAndOrders(CampaignFleetAPI fleet, float probStartInHyper, float probStayInHyper) { 292 StarSystemAPI system = getCurrSpawnLoc(); 293 294 boolean forceStartInHyper = false; 295 if (currSpawnLoc != null) { 296 float recentSpawns = getRecentSpawnsForSystem(currSpawnLoc); 297 float max = getDesiredNumFleetsForSpawnLocation(); 298 if (recentSpawns > max * 0.75f || currSpawnLoc.getDaysSinceLastPlayerVisit() < 30f) { 299 forceStartInHyper = Global.getSector().getPlayerFleet() != null && Global.getSector().getPlayerFleet().isInHyperspace(); 300 } 301 } 302 303 if ((float) Math.random() < probStartInHyper || forceStartInHyper) { 304 Global.getSector().getHyperspace().addEntity(fleet); 305 } else { 306 system.addEntity(fleet); 307 } 308 fleet.addScript(new DisposableAggroAssignmentAI(fleet, system, this, probStayInHyper)); 309 } 310} 311 312 313 314 315 316 317 318