001package com.fs.starfarer.api.impl.campaign.intel.group;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.Collections;
006import java.util.Comparator;
007import java.util.List;
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.SectorEntityToken;
015import com.fs.starfarer.api.campaign.StarSystemAPI;
016import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI;
017import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType;
018import com.fs.starfarer.api.campaign.econ.Industry;
019import com.fs.starfarer.api.campaign.econ.MarketAPI;
020import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript;
021import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams;
022import com.fs.starfarer.api.impl.campaign.command.WarSimScript;
023import com.fs.starfarer.api.impl.campaign.econ.impl.OrbitalStation;
024import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3;
025import com.fs.starfarer.api.impl.campaign.fleets.RouteManager;
026import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.OptionalFleetData;
027import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteData;
028import com.fs.starfarer.api.impl.campaign.fleets.RouteManager.RouteSegment;
029import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
030import com.fs.starfarer.api.impl.campaign.ids.Tags;
031import com.fs.starfarer.api.impl.campaign.intel.group.GenericRaidFGI.GenericPayloadAction;
032import com.fs.starfarer.api.impl.campaign.procgen.themes.BaseAssignmentAI.FleetActionDelegate;
033import com.fs.starfarer.api.impl.campaign.procgen.themes.WarfleetAssignmentAI;
034import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD;
035import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.BombardType;
036import com.fs.starfarer.api.util.CountingMap;
037import com.fs.starfarer.api.util.IntervalUtil;
038import com.fs.starfarer.api.util.Misc;
039
040public class FGRaidAction extends FGDurationAction implements FleetActionDelegate, GenericPayloadAction {
041
042        public static enum FGRaidType {
043                CONCURRENT,
044                SEQUENTIAL,
045        }
046        
047        
048        public static class FGRaidParams {
049                public StarSystemAPI where;
050                public FGRaidType type = FGRaidType.CONCURRENT;
051                public boolean doNotGetSidetracked = true;
052                public boolean tryToCaptureObjectives = true;
053                public boolean allowAnyHostileMarket = false;
054                public float maxDurationIfSpawnedFleetsConcurrent = 90f;
055                public float maxDurationIfSpawnedFleetsPerSequentialStage = 45f;
056                public int maxStabilityLostPerRaid = 3;
057                public int raidsPerColony = 2;
058                
059                public String raidApproachText = null;
060                public String raidActionText = null;
061                public String targetTravelText = null;
062                public boolean appendTargetNameToTravelText = true;
063                public String inSystemActionText = null;
064                public BombardType bombardment = null;
065                public List<String> disrupt = new ArrayList<String>();
066                
067                public boolean allowNonHostileTargets = false;
068                public List<MarketAPI> allowedTargets = new ArrayList<MarketAPI>();
069                
070                public void setBombardment(BombardType type) {
071                        this.bombardment = type;
072                        raidApproachText = "moving to bombard";
073                        raidActionText = "bombarding";
074                        raidsPerColony = 1;
075                }
076                
077                public void setDisrupt(String ... industries) {
078                        for (String id : industries) {
079                                disrupt.add(id);
080                        }
081                        raidsPerColony = Math.min(disrupt.size(), 3);
082                        if (raidsPerColony < 1) raidsPerColony = 1;
083                }
084        }
085        
086        public static class RaidSubstage {
087                protected boolean started = false;
088                public float maxDuration;
089                //public int raidsPerformed = 0;
090                public List<SectorEntityToken> objectives = new ArrayList<SectorEntityToken>();
091                public List<SectorEntityToken> markets = new ArrayList<SectorEntityToken>();
092                public List<SectorEntityToken> finishedRaiding = new ArrayList<SectorEntityToken>();
093                
094                protected Object readResolve() {
095                        if (objectives == null) {
096                                objectives = new ArrayList<SectorEntityToken>();
097                        }
098                        if (markets == null) {
099                                markets = new ArrayList<SectorEntityToken>();
100                        }
101                        if (finishedRaiding == null) {
102                                finishedRaiding = new ArrayList<SectorEntityToken>();
103                        }
104                        return this;
105                }
106                
107                public boolean allGoalsAchieved(FGRaidAction action) {
108                        if (markets.isEmpty()) {
109                                for (SectorEntityToken curr : objectives) {
110                                        if (curr.getFaction() != action.intel.getFaction()) {
111                                                return false;
112                                        }
113                                }
114                        }
115                        for (SectorEntityToken curr : markets) {
116                                if (action.raidCount.getCount(curr.getMarket()) < action.params.raidsPerColony) {
117                                        return false;
118                                }
119                                                
120                        }
121                        return true;
122                }
123        }
124        
125        protected IntervalUtil interval = new IntervalUtil(0.1f, 0.3f);
126        
127        
128        protected boolean computedSubstages = false;
129        
130        protected FGRaidParams params;
131        protected CountingMap<MarketAPI> raidCount = new CountingMap<MarketAPI>();
132        protected int bombardCount = 0;
133        protected List<RaidSubstage> stages = new ArrayList<FGRaidAction.RaidSubstage>();
134        protected List<MilitaryResponseScript> scripts = new ArrayList<MilitaryResponseScript>();
135        protected float originalDuration = 0f;
136
137        public FGRaidAction(FGRaidParams params, float raidDays) {
138                super(raidDays);
139                originalDuration = raidDays;
140                this.params = params;
141                
142                interval.forceIntervalElapsed();
143        }
144        
145        public Object readResolve() {
146                if (raidCount == null) {
147                        raidCount = new CountingMap<MarketAPI>();
148                }
149                return this;
150        }
151
152        @Override
153        public void addRouteSegment(RouteData route) {
154                RouteSegment segment = new RouteSegment(getDurDays(), params.where.getCenter());
155                route.addSegment(segment);
156        }
157
158        
159        @Override
160        public void notifyFleetsSpawnedMidSegment(RouteSegment segment) {
161                super.notifyFleetsSpawnedMidSegment(segment);
162        }
163
164        @Override
165        public void notifySegmentFinished(RouteSegment segment) {
166                super.notifySegmentFinished(segment);
167                
168                autoresolve();
169        }
170        
171        protected void computeSubstages() {
172                List<CampaignFleetAPI> fleets = intel.getFleets();
173                if (fleets.isEmpty()) return;
174                
175//              params.maxDurationIfSpawnedFleetsConcurrent = 90f;
176//              params.maxDurationIfSpawnedFleetsPerSequentialStage = 45f;
177//              params.maxStabilityLostPerRaid = 3;
178//              params.raidsPerColony = 2;
179//              params.doNotGetSidetracked = true;
180//              params.appendTargetNameToTravelText = true;
181//              params.type = FGRaidType.SEQUENTIAL;
182//              intel.setApproximateNumberOfFleets(5);
183                
184                
185                for (CampaignFleetAPI fleet : fleets) {
186                        if (!fleet.hasScriptOfClass(WarfleetAssignmentAI.class)) {
187                                WarfleetAssignmentAI script = new WarfleetAssignmentAI(fleet, true, true);
188                                script.setDelegate(this);
189                                fleet.addScript(script);
190                        }
191                        fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_WAR_FLEET, true);
192                        fleet.getMemoryWithoutUpdate().unset(MemFlags.FLEET_BUSY);
193                        //fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_PIRATE, true);
194                }
195                
196                List<MarketAPI> sortedTargetMarkets = new ArrayList<MarketAPI>();
197                for (MarketAPI market : Misc.getMarketsInLocation(params.where)) {
198                        if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(market)) continue;
199                        if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(market.getFaction())) continue;
200                
201                        sortedTargetMarkets.add(market);
202                }
203                
204                final Vector2f sortLoc = new Vector2f(fleets.get(0).getLocation());
205                Collections.sort(sortedTargetMarkets, new Comparator<MarketAPI>() {
206                        public int compare(MarketAPI o1, MarketAPI o2) {
207                                float d1 = Misc.getDistance(sortLoc, o1.getPrimaryEntity().getLocation());
208                                float d2 = Misc.getDistance(sortLoc, o2.getPrimaryEntity().getLocation());
209                                return (int) Math.signum(d1 - d2);
210                        }
211                });
212                
213                // otherwise, WasSimScript adds extra MilitaryResponseScripts for objectives and
214                // attacking fleets go there almost to the exclusion of other targets
215                for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.OBJECTIVE)) {
216                        WarSimScript.setNoFightingForObjective(objective, intel.getFaction(), 1000f);
217                }
218                
219                List<SectorEntityToken> objectives = new ArrayList<SectorEntityToken>();
220                float minDist = Float.MAX_VALUE;
221                SectorEntityToken closest = null;
222                for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.NAV_BUOY)) {
223                        float dist = Misc.getDistance(sortLoc, objective.getLocation());
224                        if (dist < minDist) {
225                                closest = objective;
226                                minDist = dist;
227                        }
228                }
229                if (closest != null) {
230                        objectives.add(closest);
231                }
232                
233                
234                minDist = Float.MAX_VALUE;
235                closest = null;
236                for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.SENSOR_ARRAY)) {
237                        float dist = Misc.getDistance(sortLoc, objective.getLocation());
238                        if (dist < minDist) {
239                                closest = objective;
240                                minDist = dist;
241                        }
242                }
243                if (closest != null) {
244                        objectives.add(closest);
245                }
246                
247                if (!params.tryToCaptureObjectives) {
248                        objectives.clear();
249                }
250                
251                if (params.type == FGRaidType.CONCURRENT) {
252                        RaidSubstage stage = new RaidSubstage();
253                        stage.maxDuration = params.maxDurationIfSpawnedFleetsConcurrent;
254                        stage.objectives.addAll(objectives);
255                        for (MarketAPI market : sortedTargetMarkets) {
256                                stage.markets.add(market.getPrimaryEntity());
257                        }
258                        stages.add(stage);
259                } else {
260                        if (!objectives.isEmpty()) {
261                                RaidSubstage stage = new RaidSubstage();
262                                stage.maxDuration = params.maxDurationIfSpawnedFleetsPerSequentialStage;
263                                stage.objectives.addAll(objectives);
264                                stages.add(stage);
265                        }
266                        
267                        for (MarketAPI market : sortedTargetMarkets) {
268                                RaidSubstage stage = new RaidSubstage();
269                                stage.maxDuration = params.maxDurationIfSpawnedFleetsConcurrent;
270                                stage.markets.add(market.getPrimaryEntity());
271                                stages.add(stage);
272                        }
273                }
274                
275                float totalDur = 0f;
276                for (RaidSubstage stage : stages) {
277                        totalDur += stage.maxDuration;
278                }
279                setDurDays(totalDur + 10f);
280                
281
282                // system defenders protect targeted markets
283                float responseFraction = 1f / Math.max(1f, sortedTargetMarkets.size());
284                for (MarketAPI market : sortedTargetMarkets) {
285                        MilitaryResponseParams defParams = new MilitaryResponseParams(ActionType.HOSTILE, 
286                                        "defRaid_" + market.getId(), 
287                                        market.getFaction(),
288                                        market.getPrimaryEntity(),
289                                        responseFraction,
290                                        getDurDays());
291                        MilitaryResponseScript defScript = new MilitaryResponseScript(defParams);
292                        params.where.addScript(defScript);
293                        scripts.add(defScript);
294                }
295                
296                computedSubstages = true;
297        }
298        
299        public void removeAggroMilitaryScripts(boolean clearAssignments) {
300                if (clearAssignments) {
301                        for (CampaignFleetAPI fleet : intel.getFleets()) {
302                                fleet.clearAssignments();
303                        }
304                }
305                if (scripts != null) {
306                        List<MilitaryResponseScript> remove = new ArrayList<MilitaryResponseScript>();
307                        for (MilitaryResponseScript s : scripts) {
308                                if (s.getParams() != null && 
309                                                s.getParams().responseReason != null && s.getParams().responseReason.startsWith("raid_")) {
310                                        s.forceDone();
311                                        remove.add(s);
312                                }
313                        }
314                        scripts.removeAll(remove);
315                }
316        }
317        
318        @Override
319        public void setActionFinished(boolean finished) {
320                if (finished && !this.finished) {
321                        List<CampaignFleetAPI> fleets = intel.getFleets();
322                        for (CampaignFleetAPI fleet : fleets) {
323                                fleet.removeScriptsOfClass(WarfleetAssignmentAI.class);
324                                Misc.setFlagWithReason(fleet.getMemoryWithoutUpdate(), MemFlags.FLEET_BUSY, fleet.getId(), true, -1f);
325                        }
326                        
327                        if (scripts != null) {
328                                for (MilitaryResponseScript s : scripts) {
329                                        s.forceDone();
330                                }
331                        }
332                        
333                        for (SectorEntityToken objective : params.where.getEntitiesWithTag(Tags.OBJECTIVE)) {
334                                WarSimScript.removeNoFightingTimeoutForObjective(objective, intel.getFaction());
335                        }
336                }
337                super.setActionFinished(finished);
338        }
339        
340
341        @Override
342        public void directFleets(float amount) {
343                super.directFleets(amount);
344                if (isActionFinished()) return;
345                
346                if (intel.isSpawning()) return; // could happen if source is in same system as target
347                
348                List<CampaignFleetAPI> fleets = intel.getFleets();
349                if (fleets.isEmpty()) {
350                        setActionFinished(true);
351                        return;
352                }
353                
354                if (!computedSubstages) {
355                        computeSubstages();
356                }
357                
358                if (stages.isEmpty()) {
359                        setActionFinished(true);
360                        return;
361                }
362                
363                float days = Global.getSector().getClock().convertToDays(amount);
364                
365                RaidSubstage stage = stages.get(0);
366                if (!stage.started) {
367                        stage.started = true;
368                        
369                        removeAggroMilitaryScripts(true);
370
371                        List<SectorEntityToken> targets = new ArrayList<SectorEntityToken>(stage.objectives);
372                        targets.addAll(stage.markets);
373                        
374                        orderFleetMovements(targets);
375                }
376                
377                
378                stage.maxDuration -= days;
379                if (stage.maxDuration <= 0 || stage.allGoalsAchieved(this)) {
380                        stages.remove(stage);
381                }
382                
383                
384                interval.advance(days);
385                if (!interval.intervalElapsed()) return;
386
387                boolean inSpawnRange = RouteManager.isPlayerInSpawnRange(params.where.getCenter());
388                if (!inSpawnRange && elapsed > originalDuration) {
389                        autoresolve();
390                        return;
391                }
392                
393                
394                for (SectorEntityToken obj : stage.objectives) {
395                        if (obj.getFaction() == intel.getFaction()) {
396                                WarSimScript.removeFightOrdersFor(obj, intel.getFaction());
397                        }
398                }
399                
400                boolean someRaidsFinished = false;
401                for (SectorEntityToken e : stage.markets) {
402                        if (stage.finishedRaiding.contains(e)) continue;
403                        if (!canRaid(null, e.getMarket())) {
404                                someRaidsFinished = true;
405                                stage.finishedRaiding.add(e);
406                        }
407                }
408                
409                if (someRaidsFinished) {
410                        for (SectorEntityToken e : stage.markets) {
411                                WarSimScript.removeFightOrdersFor(e, intel.getFaction());
412                        }
413                        List<SectorEntityToken> remaining = new ArrayList<SectorEntityToken>();
414                        remaining.addAll(stage.markets);
415                        remaining.removeAll(stage.finishedRaiding);
416                        
417                        // if wanted to re-fight for objectives that were lost, would add those here, but don't
418                        
419                        if (!remaining.isEmpty()) {
420                                orderFleetMovements(remaining);
421                        }
422                }
423                
424                
425                
426                for (CampaignFleetAPI fleet : fleets) {         
427                        if (params.doNotGetSidetracked) {
428                                boolean battleNear = false;
429                                for (CampaignFleetAPI other : fleets) {
430                                        if (other == fleet || other.getBattle() == null) continue;
431                                        if (other.getContainingLocation() != fleet.getContainingLocation());
432                                        float dist = Misc.getDistance(fleet, other);
433                                        if (dist < 1000) {
434                                                CampaignFleetAIAPI ai = fleet.getAI();
435                                                if (ai != null && ai.wantsToJoin(other.getBattle(), other.getBattle().isPlayerInvolved())) {
436                                                        battleNear = true;
437                                                        break;
438                                                }
439                                        }
440                                }
441                                if (!battleNear) {
442                                        fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_DO_NOT_GET_SIDETRACKED, true, 0.4f);
443                                }
444                        }
445                }
446        }
447
448        
449        protected void orderFleetMovements(List<SectorEntityToken> targets) {
450                float responseFraction = 1f / Math.max(1f, targets.size());
451                
452                for (SectorEntityToken target : targets) {
453                        if (!target.hasTag(Tags.OBJECTIVE) &&!canRaid(null, target.getMarket())) {
454                                continue;
455                        }
456                        
457                        float rf = responseFraction;
458                        MilitaryResponseParams aggroParams = new MilitaryResponseParams(ActionType.HOSTILE, 
459                                        "raid_" + target.getId(), 
460                                        intel.getFaction(),
461                                        target,
462                                        rf,
463                                        getDurDays());
464                        if (params.appendTargetNameToTravelText) {
465                                aggroParams.travelText = 
466                                                (params.targetTravelText != null ? params.targetTravelText + " " : "traveling to ") + target.getName();
467                        } else if (params.targetTravelText != null) {
468                                aggroParams.travelText = params.targetTravelText;
469                        }
470
471                        if (params.inSystemActionText != null) {
472                                aggroParams.actionText = params.inSystemActionText;
473                        } else {
474                                aggroParams.actionText = "raiding system";
475                        }
476                        
477                        MilitaryResponseScript script = new MilitaryResponseScript(aggroParams);
478                        params.where.addScript(script);
479                        scripts.add(script);
480                }
481        }
482        
483        public FGRaidParams getParams() {
484                return params;
485        }
486        
487        
488        public boolean canRaid(CampaignFleetAPI fleet, MarketAPI market) {
489                if (market == null) return false;
490                if (!market.isInEconomy()) {
491                        return false;
492                }
493                if (!params.allowedTargets.contains(market) && !params.allowedTargets.isEmpty() && !params.allowAnyHostileMarket) {
494                        return false;
495                }
496                if (raidCount.getCount(market) >= params.raidsPerColony) {
497                        return false;
498                }
499                
500                if (fleet != null && !intel.getFleets().contains(fleet)) {
501                        return false;
502                }
503                
504                if (params.bombardment != null && fleet != null) {
505                        float fp = fleet.getFleetPoints();
506                        for (CampaignFleetAPI other : intel.getFleets()) {
507                                if (other == fleet) continue;
508                                if (other.getContainingLocation() != fleet.getContainingLocation()) continue;
509                                float dist = Misc.getDistance(fleet, other);
510                                if (dist > 1000) continue;
511                                float otherFP = other.getFleetPoints();
512                                if (otherFP > fp * 1.2f) {
513                                        return false;
514                                }
515                        }
516                }
517                
518                FactionAPI faction = intel.getFaction();
519                if (fleet != null) {
520                        faction = fleet.getFaction();
521                }
522                boolean hostile = market.getFaction().isHostileTo(faction);
523                if (fleet != null) {
524                        hostile |= Misc.isFleetMadeHostileToFaction(fleet, market.getFaction());
525                }
526                return (params.allowNonHostileTargets || hostile) && !isActionFinished();
527        }
528        
529
530        public void performRaid(CampaignFleetAPI fleet, MarketAPI market) {
531                raidCount.add(market);
532                
533                FactionAPI faction = intel.getFaction();
534                if (fleet != null) {
535                        faction = fleet.getFaction();
536                }
537                
538                if (params.bombardment != null) {
539                        float cost = MarketCMD.getBombardmentCost(market, fleet);
540                        float bombardStr = intel.getRoute().getExtra().getStrengthModifiedByDamage() / intel.getApproximateNumberOfFleets() * 
541                                                                        Misc.FP_TO_BOMBARD_COST_APPROX_MULT;
542                        if (fleet != null) {
543                                bombardStr = fleet.getCargo().getMaxFuel() * 0.5f;
544                        }
545                        
546                        if (cost <= bombardStr) {
547                                new MarketCMD(market.getPrimaryEntity()).doBombardment(intel.getFaction(), params.bombardment);
548                                bombardCount++;
549                        } else {
550                                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_BOMBARDED, 
551                                                                           intel.getFaction().getId(), true, 30f);
552                        }
553                } else {
554                        float raidStr = intel.getRoute().getExtra().getStrengthModifiedByDamage() / intel.getApproximateNumberOfFleets() * 
555                                                        Misc.FP_TO_GROUND_RAID_STR_APPROX_MULT;
556                        if (fleet != null) {
557                                raidStr = MarketCMD.getRaidStr(fleet);
558                        }
559                        
560                        Industry industry = null;
561                        int index = raidCount.getCount(market) - 1;
562                        if (index < 0) index = 0;
563                        if (params.disrupt != null && index < params.disrupt.size()) {
564                                int count = 0;
565                                for (String industryId : params.disrupt) {
566                                        if (market.hasIndustry(industryId)) {
567                                                if (count >= index) {
568                                                        industry = market.getIndustry(industryId);
569                                                        break;
570                                                }
571                                                count++;
572                                        }
573                                }
574//                              String industryId = params.disrupt.get(index);
575//                              industry = market.getIndustry(industryId);
576                        }
577                
578                        if (intel instanceof GenericRaidFGI && ((GenericRaidFGI)intel).hasCustomRaidAction()) {
579                                ((GenericRaidFGI)intel).doCustomRaidAction(fleet, market, raidStr);
580                                Misc.setFlagWithReason(market.getMemoryWithoutUpdate(), MemFlags.RECENTLY_RAIDED, 
581                                                   faction.getId(), true, 30f);
582                                Misc.setRaidedTimestamp(market);
583                        } else if (industry != null) {
584                                float durMult = Global.getSettings().getFloat("punitiveExpeditionDisruptDurationMult");
585                                new MarketCMD(market.getPrimaryEntity()).doIndustryRaid(faction, raidStr, industry, durMult);
586                        } else {
587                                new MarketCMD(market.getPrimaryEntity()).doGenericRaid(faction,
588                                                                raidStr, params.maxStabilityLostPerRaid, params.raidsPerColony > 1);
589                        }
590                }
591        }
592
593        
594        
595        public void autoresolve() {
596                if (isActionFinished()) return;
597                
598                float str = WarSimScript.getFactionStrength(intel.getFaction(), params.where);
599                if (!intel.isSpawnedFleets() && intel.getRoute().isExpired()) {
600                        // the above doesn't pick it up
601                        OptionalFleetData data = intel.getRoute().getExtra();
602                        if (data != null) str += data.getStrengthModifiedByDamage();
603                }
604
605                boolean playerTargeted = false;
606                if (intel instanceof GenericRaidFGI) {
607                        playerTargeted = ((GenericRaidFGI)intel).getParams().playerTargeted;
608                }
609                
610                float enemyStr = WarSimScript.getEnemyStrength(intel.getFaction(), params.where, playerTargeted);
611                float origStr = str;
612                float strMult = 1f;
613                for (MarketAPI target : Misc.getMarketsInLocation(params.where)) {
614                        if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(target)) continue;
615                        if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(target.getFaction())) continue;
616                        
617                        float defensiveStr = enemyStr + WarSimScript.getStationStrength(target.getFaction(), params.where, target.getPrimaryEntity());
618                        
619                        float damage = 0.5f * defensiveStr / Math.max(str, 1f);
620                        if (damage > 0.75f) damage = 0.75f;
621                        strMult *= (1f - damage);
622                        
623                        if (defensiveStr >= str) {
624                                continue;
625                        }
626
627                        Industry station = Misc.getStationIndustry(target);
628                        if (station != null) {
629                                OrbitalStation.disrupt(station);
630                                station.reapply();
631                        }
632                        
633                        for (int i = 0; i < params.raidsPerColony; i++) {
634                                performRaid(null, target);
635                        }
636                        
637                        //str -= defensiveStr * 0.5f;
638                        str = origStr * strMult;
639                }
640                
641                if (intel.isSpawnedFleets() && strMult < 1f) {
642                        for (CampaignFleetAPI fleet : intel.getFleets()) {
643                                FleetFactoryV3.applyDamageToFleet(fleet, 1f - strMult, false, intel.getRandom());
644                        }
645                }
646                
647                if (!intel.isSpawnedFleets() && strMult < 1) {
648                        OptionalFleetData extra = intel.getRoute().getExtra();
649                        if (extra != null) {
650                                if (extra.damage == null) {
651                                        extra.damage = 0f;
652                                }
653                                extra.damage = 1f - (1f - extra.damage) * strMult;
654                                //extra.damage = 1f;
655                                if (extra.damage > 1f) extra.damage = 1f;
656                        }
657                } else if (intel.isSpawnedFleets() && getSuccessFraction() <= 0f) {
658                        intel.abort();
659                } else {
660                        // if fleets were not spawned and it needs to abort due to the damage taken,
661                        // that's handled in FleetGroupIntel
662                }
663                
664                setActionFinished(true);
665        }
666        
667        
668        
669        public String getRaidApproachText(CampaignFleetAPI fleet, MarketAPI market) {
670                if (params.raidApproachText != null) {
671                        return params.raidApproachText + " " + market.getName();
672                }
673                return null;
674        }
675
676        public String getRaidActionText(CampaignFleetAPI fleet, MarketAPI market) {
677                if (params.raidActionText != null) {
678                        return params.raidActionText + " " + market.getName();
679                }
680                return null;
681        }
682        
683        
684        // not needed and not used by the WarfleetAssignmentAI
685        public String getRaidPrepText(CampaignFleetAPI fleet, SectorEntityToken from) {
686                return null;
687        }
688
689        public String getRaidInSystemText(CampaignFleetAPI fleet) {
690                return null;
691        }
692
693        public String getRaidDefaultText(CampaignFleetAPI fleet) {
694                return null;
695        }
696
697        public CountingMap<MarketAPI> getRaidCount() {
698                return raidCount;
699        }
700        
701
702        public float getSuccessFraction() {
703                int totalGoal = params.raidsPerColony * params.allowedTargets.size();
704                if (totalGoal < 1) totalGoal = 1;
705                int achieved = raidCount.getTotal();
706                
707                if (params.bombardment != null) {
708                        achieved = bombardCount;
709                }
710                
711                return Math.max(0f, (float)achieved / (float)totalGoal);
712        }
713        
714        public Color getSystemNameHighlightColor() {
715                MarketAPI largest = null;
716                int max = 0;
717                boolean player = false;
718                for (MarketAPI target : Misc.getMarketsInLocation(params.where)) {
719                        if (!params.allowAnyHostileMarket && !params.allowedTargets.contains(target)) continue;
720                        if (!params.allowNonHostileTargets && !intel.getFaction().isHostileTo(target.getFaction())) continue;
721                        
722                        int size = target.getSize();
723                        if (size > max) {
724                                largest = target;
725                                max = size;
726                        }
727                        if (target.isPlayerOwned()) {
728                                player = true;
729                        }
730                }
731                if (player) return Misc.getBasePlayerColor();
732                if (largest != null) {
733                        return largest.getFaction().getBaseUIColor();
734                }
735                return Misc.getTextColor();
736        }
737
738        public StarSystemAPI getWhere() {
739                return params.where;
740        }
741        
742}
743
744
745
746
747
748
749
750