001package com.fs.starfarer.api.impl.campaign.missions;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.List;
006import java.util.Map;
007import java.util.Random;
008
009import org.lwjgl.util.vector.Vector2f;
010
011import com.fs.starfarer.api.Global;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.FactionAPI;
014import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
015import com.fs.starfarer.api.campaign.InteractionDialogAPI;
016import com.fs.starfarer.api.campaign.JumpPointAPI;
017import com.fs.starfarer.api.campaign.JumpPointAPI.JumpDestination;
018import com.fs.starfarer.api.campaign.LocationAPI;
019import com.fs.starfarer.api.campaign.PlanetAPI;
020import com.fs.starfarer.api.campaign.SectorEntityToken;
021import com.fs.starfarer.api.campaign.StarSystemAPI;
022import com.fs.starfarer.api.campaign.ai.FleetAIFlags;
023import com.fs.starfarer.api.campaign.econ.MarketAPI;
024import com.fs.starfarer.api.campaign.listeners.GateTransitListener;
025import com.fs.starfarer.api.campaign.rules.MemoryAPI;
026import com.fs.starfarer.api.characters.PersonAPI;
027import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.CustomRepImpact;
028import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
029import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
030import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepRewards;
031import com.fs.starfarer.api.impl.campaign.ids.Factions;
032import com.fs.starfarer.api.impl.campaign.ids.Tags;
033import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch;
034import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch.RequiredSystemTags;
035import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithTriggers;
036import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerAction;
037import com.fs.starfarer.api.impl.campaign.missions.hub.MissionTrigger.TriggerActionContext;
038import com.fs.starfarer.api.impl.campaign.missions.hub.ReqMode;
039import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
040import com.fs.starfarer.api.util.Misc;
041import com.fs.starfarer.api.util.Misc.Token;
042import com.fs.starfarer.api.util.WeightedRandomPicker;
043
044public class DelayedFleetEncounter extends HubMissionWithTriggers implements GateTransitListener {
045        
046        public static String TRIGGER_REP_LOSS_MINOR = "DFEFWTRepLossMinor";
047        public static String TRIGGER_REP_LOSS_MEDIUM = "DFEFWTRepLossMedium";
048        public static String TRIGGER_REP_LOSS_HIGH = "DFEFWTRepLossHigh";
049        
050        public static enum EncounterType {
051                OUTSIDE_SYSTEM,
052                IN_HYPER_EN_ROUTE,
053                JUMP_IN_NEAR_PLAYER,
054                FROM_SOMEWHERE_IN_SYSTEM,
055        }
056        
057        public static enum EncounterLocation {
058                ANYWHERE,
059                POPULATED_SYSTEM,
060                NEAR_CORE,
061                MIDRANGE,
062                FRINGE,
063        }
064        
065        public static float RADIUS_FROM_CORE = 30000f; // may send fleet when within this radius from core
066        public static float BASE_DAYS_IN_SYSTEM_BEFORE_AMBUSH_IN_HYPER = 5f;
067        public static float BASE_DAYS_IN_SYSTEM_BEFORE_IN_SYSTEM_ATTACK = 10f;
068        public static float BASE_TIMEOUT = 10f;
069        
070        public static float BASE_DELAY_VERY_SHORT = 365f * 0.25f; 
071        public static float BASE_DELAY_SHORT = 365f * 0.67f; 
072        public static float BASE_DELAY_MEDIUM = 365f * 2f; 
073        public static float BASE_DELAY_LONG = 365f * 5f;
074        
075        public static float BASE_ONLY_CHECK_IN_SYSTEM_DAYS = 15f; 
076        
077        public enum Stage {
078                WAITING,
079                SPAWN_FLEET,
080                ENDED,
081        }
082        
083        public static float getRandomValue(float base) {
084                return StarSystemGenerator.getNormalRandom(Misc.random, base * 0.75f, base * 1.25f);
085        }
086        
087        public static String TIMEOUT_KEY = "$core_dfe_timeout";
088        public static boolean isInTimeout() {
089                return Global.getSector().getMemoryWithoutUpdate().getBoolean(TIMEOUT_KEY);
090        }
091        public static void setTimeout() {
092                Global.getSector().getMemoryWithoutUpdate().set(TIMEOUT_KEY, true, getRandomValue(BASE_TIMEOUT));
093        }
094        
095        public class CanSpawnFleetConditionChecker implements ConditionChecker {
096                protected StarSystemAPI lastSystemPlayerWasIn = null;
097                protected float daysInSystem = 0f;
098                protected boolean conditionsMet = false;
099                protected EncounterType typePicked = null;
100                protected Vector2f location;
101                protected SectorEntityToken foundEntity;
102                
103                protected float daysBeforeInHyper = getRandomValue(BASE_DAYS_IN_SYSTEM_BEFORE_AMBUSH_IN_HYPER);
104                protected float daysBeforeInSystem = getRandomValue(BASE_DAYS_IN_SYSTEM_BEFORE_IN_SYSTEM_ATTACK);
105                
106                
107                public boolean conditionsMet() {
108                        doCheck();
109                        return conditionsMet;
110                }
111                
112                public void advance(float amount) {
113                        if (conditionsMet) return;
114                        
115                        float days = Global.getSector().getClock().convertToDays(amount);
116                        CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
117                        
118                        
119                        StarSystemAPI curr = playerFleet.getStarSystem();
120                        if (curr != null) {
121                                if (curr != lastSystemPlayerWasIn) {
122                                        daysInSystem = 0f;
123                                }
124                                lastSystemPlayerWasIn = curr;
125                                daysInSystem += days;
126                        }
127                }
128                
129                public void doCheck() {
130                        if (isInTimeout()) return;
131                        if (conditionsMet) return;
132                        
133                        CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
134                        if (playerFleet.getFleetPoints() > estimatedFleetPoints * playerFleetSizeAbortMult) {
135                                return;
136                        }
137                        
138                        if (madeGateTransit && initialTransitFrom != null && 
139                                        Misc.getDistanceLY(initialTransitFrom.getLocation(), 
140                                                                           playerFleet.getLocationInHyperspace()) > 2f) {
141                                return;
142                        }
143                        
144                        boolean onlyCheckInSystem = true;
145                        StageData stage = getData(currentStage);
146                        if (stage != null && stage.elapsed > onlyCheckForSpawnInSystemDays) {
147                                onlyCheckInSystem = false;
148                        }
149                        
150                        
151                        if (isPlayerInRightRangeBand(lastSystemPlayerWasIn)) {
152                                if (allowedTypes.contains(EncounterType.IN_HYPER_EN_ROUTE) && !onlyCheckInSystem) {
153                                        if (playerFleet.isInHyperspace()) {
154                                                float maxSpeed = Misc.getSpeedForBurnLevel(playerFleet.getFleetData().getBurnLevel());
155                                                //float threshold = Misc.getSpeedForBurnLevel(15);
156                                                float currSpeed = playerFleet.getVelocity().length();
157                                                if (currSpeed >= maxSpeed * 0.9f) {
158                                                        //Misc.findNearestJumpPointThatCouldBeExitedFrom(playerFleet);
159                                                        float dist = getRandomValue(2500f);
160                                                        float dir = Misc.getAngleInDegrees(playerFleet.getVelocity());
161                                                        dir += 75f - 150f * genRandom.nextFloat();
162                                                        location = Misc.getUnitVectorAtDegreeAngle(dir);
163                                                        location.scale(dist);
164                                                        Vector2f.add(location, playerFleet.getLocation(), location);
165                                                        conditionsMet = true;
166                                                        typePicked = EncounterType.IN_HYPER_EN_ROUTE;
167                                                        //getPreviousCreateFleetAction().params.locInHyper = playerFleet.getLocationInHyperspace();
168                                                        return;
169                                                }
170                                        }
171                                }
172                                if (allowedTypes.contains(EncounterType.OUTSIDE_SYSTEM)) {
173                                        if (playerFleet.isInHyperspace() && daysInSystem > daysBeforeInHyper && lastSystemPlayerWasIn != null) {
174                                                float dist = Misc.getDistance(lastSystemPlayerWasIn.getLocation(), playerFleet.getLocationInHyperspace());
175                                                if (dist < 3000f) {
176                                                        conditionsMet = true;
177                                                        typePicked = EncounterType.OUTSIDE_SYSTEM;
178                                                        return;
179                                                }
180                                        }
181                                }
182                                if (allowedTypes.contains(EncounterType.FROM_SOMEWHERE_IN_SYSTEM)) {
183                                        if (playerFleet.getStarSystem() == lastSystemPlayerWasIn && daysInSystem > daysBeforeInSystem && 
184                                                        lastSystemPlayerWasIn != null) {
185                                                conditionsMet = true;
186                                                typePicked = EncounterType.FROM_SOMEWHERE_IN_SYSTEM;
187                                                return;
188                                        }
189                                }
190                                if (allowedTypes.contains(EncounterType.JUMP_IN_NEAR_PLAYER)) {
191                                        if (playerFleet.getStarSystem() == lastSystemPlayerWasIn && daysInSystem > daysBeforeInSystem && 
192                                                                        lastSystemPlayerWasIn != null) {
193                                                // spawn from:
194                                                // a nearby jump-point
195                                                // a gas giant gravity well
196                                                // a planet gravity well (transverse jump)
197                                                // a star gravity well
198                                                SectorEntityToken entity = Misc.findNearestJumpPointTo(playerFleet);
199                                                if (entity != null) {
200                                                        float dist = Misc.getDistance(playerFleet, entity);
201                                                        if (dist < 3000f) {
202                                                                conditionsMet = true;
203                                                        }
204                                                }
205                                                if (!conditionsMet) {
206                                                        entity = Misc.findNearestPlanetTo(playerFleet, true, false);
207                                                        if (entity != null) {
208                                                                float dist = Misc.getDistance(playerFleet, entity);
209                                                                if (dist < 3000f) {
210                                                                        conditionsMet = true;
211                                                                }
212                                                        }
213                                                }
214                                                // only jump in near gas giants; don't fall back to other planets/stars
215                                                // it feels too weird/forced if the fleet can just jump in anywhere
216//                                              if (!conditionsMet) {
217//                                                      entity = Misc.findNearestPlanetTo(playerFleet, false, false);
218//                                                      if (entity != null) {
219//                                                              float dist = Misc.getDistance(playerFleet, entity);
220//                                                              if (dist < 3000f) {
221//                                                                      conditionsMet = true;
222//                                                              }
223//                                                      }
224//                                              }
225//                                              if (!conditionsMet) {
226//                                                      entity = Misc.findNearestPlanetTo(playerFleet, false, true);
227//                                                      if (entity != null) {
228//                                                              float dist = Misc.getDistance(playerFleet, entity);
229//                                                              if (dist < 3000f) {
230//                                                                      conditionsMet = true;
231//                                                              }
232//                                                      }
233//                                              }
234                                                
235                                                if (conditionsMet) {
236                                                        foundEntity = entity;
237                                                        typePicked = EncounterType.JUMP_IN_NEAR_PLAYER;
238                                                        return;
239                                                }
240                                        }
241                                }
242                        }
243                }
244                
245                protected boolean isPlayerInRightRangeBand(LocationAPI system) {
246                        //if (allowedLocations.contains(EncounterLocation.ANYWHERE)) return true;
247                        CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
248                        
249                        if (system instanceof StarSystemAPI && system == playerFleet.getContainingLocation()) {
250                                if (requiredTags != null) {
251                                        for (RequiredSystemTags req : requiredTags) {
252                                                if (!req.systemMatchesRequirement((StarSystemAPI) system)) {
253                                                        return false;
254                                                }
255                                        }
256                                }
257                                
258                                List<MarketAPI> markets = Misc.getMarketsInLocation(system);
259                                if (requireLargestMarketNotHostileToFaction != null) {
260                                        MarketAPI largest = null;
261                                        MarketAPI largestHostile = null;
262                                        int maxSize = 0;
263                                        int maxHostileSize = 0;
264                                        for (MarketAPI market : markets) {
265                                                if (market.getSize() > maxSize) {
266                                                        largest = market;
267                                                        maxSize = market.getSize();
268                                                }
269                                                if (market.getFaction().isHostileTo(requireLargestMarketNotHostileToFaction)) {
270                                                        if (market.getSize() > maxHostileSize) {
271                                                                largestHostile = market;
272                                                                maxHostileSize = market.getSize();
273                                                        }
274                                                }
275                                        }
276                                        if (largestHostile != null && maxHostileSize > maxSize) {
277                                                return false;
278                                        }
279                                }
280                                if (requiredFactionPresence != null) {
281                                        boolean found = false;
282                                        for (MarketAPI market : markets) {
283                                                if (requiredFactionPresence.contains(market.getFactionId())) {
284                                                        found = true;
285                                                        break;
286                                                }
287                                        }
288                                        if (!found) {
289                                                return false;
290                                        }
291                                }
292                        }
293                        
294                        Vector2f coreCenter = new Vector2f();
295                        
296                        float fringeRange = 46000;
297                        
298                        float nearMarketRange = 5000f;
299                        boolean nearCoreMarket = false;
300                        boolean nearAnyMarket = false;
301                        MarketAPI nearest = null;
302                        float minDist = Float.MAX_VALUE;
303                        
304                        float count = 0f;
305                        for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
306                                if (market.isHidden()) continue;
307                                if (market.getContainingLocation().hasTag(Tags.THEME_CORE_POPULATED)) {
308                                        Vector2f.add(coreCenter, market.getLocationInHyperspace(), coreCenter);
309                                        count++;
310                                        
311                                        if (!nearCoreMarket) {
312                                                float dist = Misc.getDistance(market.getLocation(), playerFleet.getLocationInHyperspace());
313                                                nearCoreMarket = dist < nearMarketRange;
314                                                if (dist < minDist) {
315                                                        nearest = market;
316                                                        minDist = dist;
317                                                }
318                                        }
319                                } else if (!nearAnyMarket) {
320                                        float dist = Misc.getDistance(market.getLocation(), playerFleet.getLocationInHyperspace());
321                                        nearAnyMarket = dist < nearMarketRange;
322                                        if (dist < minDist) {
323                                                nearest = market;
324                                                minDist = dist;
325                                        }
326                                }
327                        }
328                        
329                        if ((nearCoreMarket || nearAnyMarket) && !allowInsidePopulatedSystems && nearest != null) {
330                                if (!Global.getSector().getPlayerFleet().isInHyperspace() && 
331                                                system == nearest.getStarSystem()) {
332                                        return false;
333                                }
334                        }
335                                
336                        
337                        if (count > 0) {
338                                coreCenter.scale(1f / count);
339                        }
340                        
341                        if (nearCoreMarket && allowedLocations.contains(EncounterLocation.NEAR_CORE)) {
342                                return true;
343                        }
344                        if (nearAnyMarket && allowedLocations.contains(EncounterLocation.POPULATED_SYSTEM)) {
345                                return true;
346                        }
347                        
348                        for (EncounterLocation location : allowedLocations) {
349                                if (location == EncounterLocation.NEAR_CORE) continue;
350                                if (location == EncounterLocation.POPULATED_SYSTEM) continue;
351                                
352                                //location = EncounterLocation.FRINGE;
353                                
354                                float distFromCore = Misc.getDistance(coreCenter, playerFleet.getLocationInHyperspace());
355                                
356                                if (location == EncounterLocation.MIDRANGE) {
357                                        if (distFromCore > fringeRange || nearCoreMarket) {
358                                                continue;
359                                        }
360                                }
361                                
362                                if (location == EncounterLocation.FRINGE) {
363                                        if (distFromCore < fringeRange || nearCoreMarket) {
364                                                continue;
365                                        }
366                                }
367                                
368                                return true;
369                        }
370                        return false;
371                }
372        }
373        
374        public class DFEPlaceFleetAction implements TriggerAction {
375                public DFEPlaceFleetAction() {
376                }
377                
378                public void doAction(TriggerActionContext context) {
379                        setTimeout();
380                        
381//                      protected EncounterType typePicked = null;
382//                      protected Vector2f location;
383//                      protected SectorEntityToken foundEntity;
384                        CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
385                        playerFleet.getContainingLocation().addEntity(context.fleet);
386                        
387                        if (checker.typePicked == EncounterType.OUTSIDE_SYSTEM) {
388                                Vector2f loc = Misc.getPointAtRadius(playerFleet.getLocationInHyperspace(), 1000f);
389                                context.fleet.setLocation(loc.x, loc.y);
390                        } else if (checker.typePicked == EncounterType.FROM_SOMEWHERE_IN_SYSTEM) {
391                                WeightedRandomPicker<MarketAPI> from = new WeightedRandomPicker<MarketAPI>(genRandom);
392                                for (MarketAPI curr : Global.getSector().getEconomy().getMarkets(playerFleet.getContainingLocation())) {
393                                        float w = 0f;
394                                        if (curr.getFaction() == context.fleet.getFaction()) {
395                                                w = curr.getSize() * 10000f;
396                                        } else if (!curr.getFaction().isHostileTo(context.fleet.getFaction())) {
397                                                w = curr.getSize();
398                                        }
399                                        if (w > 0f) {
400                                                from.add(curr, w);
401                                        }
402                                }
403                                MarketAPI market = from.pick();
404                                if (market != null) {
405                                        float dir = Misc.getAngleInDegrees(playerFleet.getLocation(), market.getPrimaryEntity().getLocation());
406                                        Vector2f loc = HubMissionWithTriggers.pickLocationWithinArc(genRandom, playerFleet,
407                                                                dir, 30f, 3000f, 3000f, 3000f);
408                                        context.fleet.setLocation(loc.x, loc.y);
409                                } else {
410                                        Vector2f loc = Misc.getPointAtRadius(playerFleet.getLocation(), 3000f);
411                                        context.fleet.setLocation(loc.x, loc.y);
412                                }
413                        } else if (checker.typePicked == EncounterType.JUMP_IN_NEAR_PLAYER) {
414                                JumpDestination dest = new JumpDestination(checker.foundEntity, null);
415                                if (checker.foundEntity instanceof JumpPointAPI) {
416                                        JumpPointAPI jp = (JumpPointAPI) checker.foundEntity;
417                                        jp.open();
418                                        context.fleet.setLocation(jp.getLocation().x, jp.getLocation().y);
419                                } else if (checker.foundEntity instanceof PlanetAPI) {
420                                        dest.setMinDistFromToken(checker.foundEntity.getRadius() + 50f);
421                                        dest.setMaxDistFromToken(checker.foundEntity.getRadius() + 400f);
422                                }
423                                context.fleet.updateFleetView(); // so that ship views exist and can do the jump-in warping animation
424                                context.fleet.getContainingLocation().removeEntity(context.fleet);
425                                Global.getSector().getHyperspace().addEntity(context.fleet);
426                                context.fleet.setLocation(1000000000, 0);
427                                Global.getSector().doHyperspaceTransition(context.fleet, null, dest);
428                        } else if (checker.typePicked == EncounterType.IN_HYPER_EN_ROUTE) {
429                                context.fleet.setLocation(checker.location.x, checker.location.y);
430                        }
431                        
432                        
433                        // this is only relevant if an intercept isn't ordered; the triggerIntercept method sets this
434                        // as well.
435                        float radius = 2000f * (0.5f + 0.5f * genRandom.nextFloat());
436                        Vector2f approximatePlayerLoc = Misc.getPointAtRadius(playerFleet.getLocation(), radius);
437                        context.fleet.getMemoryWithoutUpdate().set(FleetAIFlags.PLACE_TO_LOOK_FOR_TARGET, approximatePlayerLoc, 2f);
438                }
439        }
440
441        
442        protected float minDelay;
443        protected float maxDelay;
444        protected float onlyCheckForSpawnInSystemDays;
445        protected String globalEndFlag;
446        
447        protected List<EncounterType> allowedTypes = new ArrayList<EncounterType>();
448        protected List<EncounterLocation> allowedLocations = new ArrayList<EncounterLocation>();
449        protected boolean allowInsidePopulatedSystems = true;
450        protected String requireLargestMarketNotHostileToFaction = null;
451        protected List<String> requiredFactionPresence = null;
452        protected List<RequiredSystemTags> requiredTags = null;
453        
454        protected boolean canBeAvoidedByGateTransit = true;
455        protected boolean madeGateTransit = false;
456        protected LocationAPI initialTransitFrom = null;
457        
458        protected CanSpawnFleetConditionChecker checker;
459        
460        public DelayedFleetEncounter(Random random, String missionId) {
461                if (random == null) random = new Random(Misc.genRandomSeed());
462                setGenRandom(random);
463                setNoRepChanges();
464                globalEndFlag = "$" + "dfe"+ "_" + missionId + "_" + Misc.genUID();
465                setMissionId(missionId);
466                
467                setTypes(EncounterType.OUTSIDE_SYSTEM, EncounterType.JUMP_IN_NEAR_PLAYER, EncounterType.IN_HYPER_EN_ROUTE);
468                // don't, since in-system attacks are a jump-in near the player, so it's probably fine anyway
469                // and possibly interesting if not fine
470                //requireDFESystemTags(ReqMode.NOT_ANY, Tags.THEME_UNSAFE);
471                
472                onlyCheckForSpawnInSystemDays = BASE_ONLY_CHECK_IN_SYSTEM_DAYS * (0.5f + genRandom.nextFloat());
473                Global.getSector().getListenerManager().addListener(this);
474        }
475        
476        public void setCanNotBeAvoidedByGateTransit() {
477                canBeAvoidedByGateTransit = false;
478        }
479        
480        public void reportFleetTransitingGate(CampaignFleetAPI fleet, SectorEntityToken gateFrom, SectorEntityToken gateTo) {
481                if (!fleet.isPlayerFleet() || gateFrom == null) return;
482                
483                if (getCurrentStage() == Stage.WAITING && 
484                                getElapsedInCurrentStage() < waitDays * 0.9f) {
485                        return;
486                }
487                
488                float dist = Misc.getDistanceLY(gateFrom, gateTo);
489                if (dist > 2f) {
490                        madeGateTransit = true;
491                }
492                if (initialTransitFrom == null) {
493                        initialTransitFrom = gateFrom.getContainingLocation();
494                }
495        }
496        
497        @Override
498        protected void notifyEnding() {
499                super.notifyEnding();
500                Global.getSector().getListenerManager().removeListener(this);
501        }
502        
503        public void setAllowInsidePopulatedSystems(boolean allowInsidePopulatedSystems) {
504                this.allowInsidePopulatedSystems = allowInsidePopulatedSystems;
505        }
506        public void setRequireLargestMarketNotHostileToFaction(String requireLargestMarketNotHostileToFaction) {
507                this.requireLargestMarketNotHostileToFaction = requireLargestMarketNotHostileToFaction;
508        }
509        public void setRequireFactionPresence(String ... factions) {
510                if (factions == null || factions.length <= 0) {
511                        requiredFactionPresence = null;
512                } else {
513                        requiredFactionPresence = new ArrayList<String>();
514                        requiredFactionPresence.addAll(Arrays.asList(factions));
515                }
516        }
517        
518        public void clearDFESystemTagRequirements() {
519                requiredTags = null;
520        }
521        public void requireDFESystemTags(ReqMode mode, String ... tags) {
522                if (requiredTags == null) {
523                        requiredTags = new ArrayList<HubMissionWithSearch.RequiredSystemTags>();
524                }
525                RequiredSystemTags req = new RequiredSystemTags(mode, tags);
526                requiredTags.add(req);
527        }
528        
529        public void setEncounterInHyper() {
530                setTypes(EncounterType.OUTSIDE_SYSTEM, EncounterType.IN_HYPER_EN_ROUTE);
531        }
532        public void setEncounterOutsideSystem() {
533                setTypes(EncounterType.OUTSIDE_SYSTEM);
534        }
535        public void setEncounterInSystemFromJumpPoint() {
536                setTypes(EncounterType.JUMP_IN_NEAR_PLAYER);
537        }
538        public void setEncounterFromSomewhereInSystem() {
539                setTypes(EncounterType.FROM_SOMEWHERE_IN_SYSTEM);
540        }
541        public void setEncounterInHyperEnRoute() {
542                setTypes(EncounterType.IN_HYPER_EN_ROUTE);
543        }
544        
545        public void setTypes(EncounterType ... types) {
546                allowedTypes.clear();
547                allowedTypes.addAll(Arrays.asList(types));
548        }
549        public void setLocations(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction,
550                                                                EncounterLocation ... locations) {
551                allowedLocations.clear();
552                allowedLocations.addAll(Arrays.asList(locations));
553                setAllowInsidePopulatedSystems(allowInsidePopulatedSystems);
554                setRequireLargestMarketNotHostileToFaction(requireLargestMarketNotHostileToFaction);
555        }
556
557        public void setDelay(float minDays, float maxDays) {
558                this.minDelay = minDays;
559                this.maxDelay = maxDays;
560        }
561        
562        public void setDelay(float base) {
563                this.minDelay = base * 0.5f;
564                this.maxDelay = base * 1.5f;
565        }
566        
567        public void setLocationAnyPopulated(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
568                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
569                                EncounterLocation.POPULATED_SYSTEM);
570        }
571        
572        public void setLocationCoreOnly(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
573                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
574                                EncounterLocation.NEAR_CORE);
575        }
576        
577        public void setLocationOuterSector(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
578                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
579                                EncounterLocation.FRINGE, EncounterLocation.MIDRANGE);
580        }
581        
582        public void setLocationAnywhere(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
583                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
584                                EncounterLocation.ANYWHERE);
585        }
586        
587        public void setLocationFringeOnly(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
588                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
589                                EncounterLocation.FRINGE);
590        }
591        
592        public void setLocationInnerSector(boolean allowInsidePopulatedSystems, String requireLargestMarketNotHostileToFaction) {
593                setLocations(allowInsidePopulatedSystems, requireLargestMarketNotHostileToFaction,
594                                EncounterLocation.NEAR_CORE, EncounterLocation.MIDRANGE);
595        }
596        
597        
598        public void setDelayNone() {
599                setDelay(0f);
600        }
601        
602        public void setDelayVeryShort() {
603                setDelay(BASE_DELAY_VERY_SHORT);
604        }
605        
606        public void setDelayShort() {
607                setDelay(BASE_DELAY_SHORT);
608        }
609        
610        public void setDelayMedium() {
611                setDelay(BASE_DELAY_MEDIUM);
612        }
613        
614        public void setDelayLong() {
615                setDelay(BASE_DELAY_LONG);
616        }
617        
618        public void triggerSetStandardAggroInterceptFlags() {
619                triggerMakeHostileAndAggressive();
620                triggerFleetAllowLongPursuit();
621                triggerSetFleetAlwaysPursue();
622                triggerOrderFleetInterceptPlayer();
623                triggerOrderFleetMaybeEBurn();
624        }
625        
626        
627        @Override
628        protected void advanceImpl(float amount) {
629                super.advanceImpl(amount);
630                
631                //System.out.println("Wait: " + waitDays);
632                //System.out.println("Elapsed: " + getElapsedInCurrentStage());
633
634                if (getCurrentStage() == Stage.SPAWN_FLEET && checker != null) {
635                        checker.advance(amount);
636                }
637                
638        }
639
640        protected float waitDays;
641        public void beginCreate() {
642                checker = new CanSpawnFleetConditionChecker();
643                
644                if (minDelay <= 0 && maxDelay <= 0) {
645                        // this check does NOT apply to in hyper in route
646                        // so, if "immediate" spawn, assuming all encounter types are allowed:
647                        // - wait a bit before spawning when player exits system
648                        // - wait a bit longer before it jumps into the system
649                        // - and wait a little bit (i.e. only check in-system spawn) before spawning in-hyper-en-route 
650                        checker.daysBeforeInHyper = (0.5f + (0.5f + genRandom.nextFloat())) * 2f;
651                        checker.daysBeforeInSystem = (0.5f + (0.5f + genRandom.nextFloat())) * 3f;
652                        onlyCheckForSpawnInSystemDays = 0.5f + (0.5f + genRandom.nextFloat());
653                }
654                
655                waitDays = minDelay + (maxDelay - minDelay) * genRandom.nextFloat();
656                connectWithDaysElapsed(Stage.WAITING, Stage.SPAWN_FLEET, waitDays);
657                setStartingStage(Stage.WAITING);
658                setSuccessStage(Stage.ENDED);
659                setStageOnGlobalFlag(Stage.ENDED, globalEndFlag);
660                
661                setStageOnCustomCondition(Stage.ENDED, new ConditionChecker() {
662                        public boolean conditionsMet() {
663                                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
664                                // abort if player is too strong
665                                if (playerFleet.getFleetPoints() > estimatedFleetPoints * playerFleetSizeAbortMult) {
666                                        if ((getCurrentStage() == Stage.WAITING && 
667                                                        getElapsedInCurrentStage() > waitDays * 0.9f) || 
668                                                        getCurrentStage() == Stage.SPAWN_FLEET) {
669                                                return true;
670                                        }
671                                        return false;
672                                }
673                                
674                                // abort if player made a gate transit shortly before the encounter would trigger
675                                // the "shortly before" part is handled in reportFleetTransitingGate()
676                                // and also check that they didn't transit back to their original location
677                                if (madeGateTransit && initialTransitFrom != null && 
678                                                Misc.getDistanceLY(initialTransitFrom.getLocation(), 
679                                                                                   playerFleet.getLocationInHyperspace()) > 2f) {
680                                        return true;
681                                }
682                                return false;
683                        }
684                });
685                
686                beginCustomTrigger(checker, Stage.SPAWN_FLEET);
687                triggerMakeAllFleetFlagsPermanent();
688        }
689        
690        public void endCreate() {
691                triggerSetGlobalMemoryValue(globalEndFlag, true);
692                triggerCustomAction(new DFEPlaceFleetAction());
693                endTrigger();
694                accept(null, null);
695        }
696
697        
698        public void triggerSetAdjustStrengthBasedOnQuality(boolean randomize, float quality) {
699                if (randomize) {
700                        triggerRandomizeFleetProperties();
701                }
702                setQuality(quality);
703                setUseQualityInsteadOfQualityFraction(true);
704                triggerAutoAdjustFleetStrengthMajor();
705                setUseQualityInsteadOfQualityFraction(false);
706        }
707        
708        protected PersonAPI personForRepLoss = null;
709        public void setFleetWantsThing(String originalFactionId,
710                                                                   String thing,
711                                                                   String thingItOrThey,
712                                                                   String thingDesc,
713                                                                   int paymentOffered,
714                                                                   boolean aggressiveIfDeclined,
715                                                                   ComplicationRepImpact repImpact,
716                                                                   String failTrigger,
717                                                                   PersonAPI personForRepLoss) {
718                
719                this.personForRepLoss = personForRepLoss;
720                
721                triggerSetFleetMissionRef("$" + getMissionId() + "_ref");
722                triggerSetFleetMissionRef("$fwt_ref");
723                
724                if (aggressiveIfDeclined) {
725                        triggerSetPirateFleet();
726                        triggerMakeHostileAndAggressive();
727                }
728                
729                if (repImpact == ComplicationRepImpact.LOW) {
730                        triggerMakeLowRepImpact();
731                } else if (repImpact == ComplicationRepImpact.NONE) {
732                        triggerMakeNoRepImpact();
733                }
734                
735                triggerFleetAllowLongPursuit();
736                triggerSetFleetAlwaysPursue();
737                
738                FactionAPI faction = Global.getSector().getFaction(originalFactionId);
739                if (faction.getCustomBoolean(Factions.CUSTOM_SPAWNS_AS_INDEPENDENT)) {
740                        triggerSetFleetFaction(Factions.INDEPENDENT);
741                        triggerSetFleetMemoryValue("$fwt_originalFaction", originalFactionId);
742                }
743                
744                triggerSetFleetMemoryValue("$fwt_wantsThing", true);
745                triggerSetFleetMemoryValue("$fwt_aggressive", aggressiveIfDeclined);
746                triggerSetFleetMemoryValue("$fwt_thing", getWithoutArticle(thing));
747                triggerSetFleetMemoryValue("$fwt_Thing", Misc.ucFirst(getWithoutArticle(thing)));
748                triggerSetFleetMemoryValue("$fwt_theThing", thing);
749                triggerSetFleetMemoryValue("$fwt_TheThing", Misc.ucFirst(thing));
750                triggerSetFleetMemoryValue("$fwt_payment", Misc.getWithDGS(paymentOffered));
751                triggerSetFleetMemoryValue("$fwt_itOrThey", thingItOrThey);
752                triggerSetFleetMemoryValue("$fwt_ItOrThey", Misc.ucFirst(thingItOrThey));
753                
754                String thingItOrThem = "them";
755                if ("it".equals(thingItOrThey)) thingItOrThem = "it";
756                triggerSetFleetMemoryValue("$fwt_itOrThem", thingItOrThem);
757                triggerSetFleetMemoryValue("$fwt_ItOrThem", Misc.ucFirst(thingItOrThem));
758                
759                triggerSetFleetMemoryValue("$fwt_thingDesc", thingDesc);
760                triggerSetFleetMemoryValue("$fwt_ThingDesc", Misc.ucFirst(thingDesc));
761                
762                if (failTrigger == null) {
763                        failTrigger = "FWTDefaultFailTrigger";
764                }
765                triggerSetFleetMemoryValue("$fwt_missionFailTrigger", failTrigger);
766        }
767        
768        
769//      public void setAbortWhenPlayerFleetTooStrong() {
770//              playerFleetSizeAbortMult = 2f;
771//      }
772        
773        public void setAlwaysAbort() {
774                playerFleetSizeAbortMult = 0f;
775        }
776        public void setDoNotAbortWhenPlayerFleetTooStrong() {
777                playerFleetSizeAbortMult = 100000000f;
778        }
779        
780        public void setPlayerFleetSizeAbortMult(float playerFleetSizeAbortMult) {
781                this.playerFleetSizeAbortMult = playerFleetSizeAbortMult;
782        }
783
784        protected FleetSize fleetSize = FleetSize.MEDIUM;
785        protected float estimatedFleetPoints = 0f;
786        protected float playerFleetSizeAbortMult = 2f;
787        protected void computeThresholdPoints(String factionId) {
788                FactionAPI faction = Global.getSector().getFaction(factionId);
789                float maxPoints = faction.getApproximateMaxFPPerFleet(ShipPickMode.PRIORITY_THEN_ALL);
790                estimatedFleetPoints = fleetSize.maxFPFraction * maxPoints;
791        }
792        
793        public void triggerFleetSetFaction(String factionId) {
794                computeThresholdPoints(factionId);
795                super.triggerSetFleetFaction(factionId);
796        }
797        
798        @Override
799        public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, SectorEntityToken roughlyWhere) {
800                fleetSize = size;
801                computeThresholdPoints(factionId);
802                super.triggerCreateFleet(size, quality, factionId, type, roughlyWhere);
803        }
804        @Override
805        public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, StarSystemAPI roughlyWhere) {
806                fleetSize = size;
807                computeThresholdPoints(factionId);
808                super.triggerCreateFleet(size, quality, factionId, type, roughlyWhere);
809        }
810        @Override
811        public void triggerCreateFleet(FleetSize size, FleetQuality quality, String factionId, String type, Vector2f locInHyper) {
812                fleetSize = size;
813                computeThresholdPoints(factionId);
814                super.triggerCreateFleet(size, quality, factionId, type, locInHyper);
815        }
816        
817        @Override
818        protected boolean create(MarketAPI createdAt, boolean barEvent) {
819                return false;
820        }
821        @Override
822        public String getBaseName() {
823                return null;
824        }
825
826        @Override
827        public boolean isHidden() {
828                return true;
829        }
830        
831        
832        @Override
833        protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
834                float repLossPerson = 0f;
835                float repLossFaction = 0f;
836                if ("repLossMinor".equals(action)) {
837                        repLossPerson = -RepRewards.SMALL;
838                        repLossFaction = -RepRewards.TINY;
839                } else if ("repLossMedium".equals(action)) {
840                        repLossPerson = -RepRewards.HIGH;
841                        repLossFaction = -RepRewards.SMALL;
842                } else if ("repLossHigh".equals(action)) {
843                        repLossPerson = -RepRewards.EXTREME;
844                        repLossFaction = -RepRewards.HIGH;
845                }
846                
847                if (repLossPerson != 0 && personForRepLoss != null) {
848                        CustomRepImpact impact = new CustomRepImpact();
849                        impact.delta = repLossPerson;
850                        Global.getSector().adjustPlayerReputation(
851                                        new RepActionEnvelope(RepActions.CUSTOM, impact,
852                                                        null, dialog.getTextPanel(), true), personForRepLoss);
853                        
854                        impact.delta = repLossFaction;
855                        Global.getSector().adjustPlayerReputation(
856                                        new RepActionEnvelope(RepActions.CUSTOM, impact,
857                                                        null, dialog.getTextPanel(), true), personForRepLoss.getFaction().getId());
858                        
859                        return true;
860                }
861                return super.callAction(action, ruleId, dialog, params, memoryMap);
862        }
863        
864        
865}
866
867
868
869