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