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