001package com.fs.starfarer.api.impl.campaign.fleets;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.Iterator;
009import java.util.LinkedHashSet;
010import java.util.List;
011import java.util.Map;
012import java.util.Random;
013import java.util.Set;
014
015import org.apache.log4j.Logger;
016
017import com.fs.starfarer.api.Global;
018import com.fs.starfarer.api.campaign.CampaignFleetAPI;
019import com.fs.starfarer.api.campaign.FactionAPI;
020import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
021import com.fs.starfarer.api.campaign.FactionAPI.ShipPickParams;
022import com.fs.starfarer.api.campaign.FactionDoctrineAPI;
023import com.fs.starfarer.api.campaign.FleetInflater;
024import com.fs.starfarer.api.campaign.SectorEntityToken;
025import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
026import com.fs.starfarer.api.campaign.econ.MarketAPI;
027import com.fs.starfarer.api.characters.MutableCharacterStatsAPI;
028import com.fs.starfarer.api.characters.PersonAPI;
029import com.fs.starfarer.api.characters.SkillSpecAPI;
030import com.fs.starfarer.api.combat.ShieldAPI.ShieldType;
031import com.fs.starfarer.api.combat.ShipAPI.HullSize;
032import com.fs.starfarer.api.combat.ShipHullSpecAPI.ShipTypeHints;
033import com.fs.starfarer.api.combat.WeaponAPI.WeaponType;
034import com.fs.starfarer.api.fleet.FleetMemberAPI;
035import com.fs.starfarer.api.fleet.FleetMemberType;
036import com.fs.starfarer.api.fleet.ShipRolePick;
037import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent;
038import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent.SkillPickPreference;
039import com.fs.starfarer.api.impl.campaign.fleets.GenerateFleetOfficersPlugin.GenerateFleetOfficersPickData;
040import com.fs.starfarer.api.impl.campaign.ids.Commodities;
041import com.fs.starfarer.api.impl.campaign.ids.Factions;
042import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
043import com.fs.starfarer.api.impl.campaign.ids.Personalities;
044import com.fs.starfarer.api.impl.campaign.ids.Ranks;
045import com.fs.starfarer.api.impl.campaign.ids.ShipRoles;
046import com.fs.starfarer.api.impl.campaign.ids.Skills;
047import com.fs.starfarer.api.impl.campaign.ids.Stats;
048import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin;
049import com.fs.starfarer.api.loading.AbilitySpecAPI;
050import com.fs.starfarer.api.loading.WeaponSlotAPI;
051import com.fs.starfarer.api.plugins.CreateFleetPlugin;
052import com.fs.starfarer.api.plugins.OfficerLevelupPlugin;
053import com.fs.starfarer.api.util.Misc;
054import com.fs.starfarer.api.util.WeightedRandomPicker;
055
056public class FleetFactoryV3 {
057
058        public static String KEY_SPAWN_FP_MULT = "$spawnFPMult";
059        
060        //public static float IMPORTED_QUALITY_PENALTY = Global.getSettings().getFloat("fleetQualityPenaltyForImports");
061        public static float BASE_QUALITY_WHEN_NO_MARKET = 0.5f;
062        
063        public static int FLEET_POINTS_THRESHOLD_FOR_ANNOYING_SHIPS = 50;
064        
065        public static float MIN_NUM_SHIPS_DEFICIT_MULT = 0.25f;
066        
067        public static int [][] BASE_COUNTS_WITH_4 = new int [][] 
068               {{9, 4, 2, 0},
069                        {7, 4, 2, 0},
070                        {4, 3, 3, 0},
071                        {1, 1, 1, 0},
072                        {1, 1, 1, 1},
073               };
074        public static int [][] MAX_EXTRA_WITH_4 = new int [][] 
075               {{3, 2, 1, 1},
076                        {2, 2, 2, 1},
077                        {2, 2, 2, 1},
078                        {2, 2, 2, 3},
079                        {1, 1, 1, 1},
080               };
081        
082        public static int [][] BASE_COUNTS_WITH_3 = new int [][] 
083           {{6, 2, 1},
084                {4, 2, 1},
085                {3, 2, 1},
086                {1, 1, 1},
087                {1, 1, 1},
088       };
089        public static int [][] MAX_EXTRA_WITH_3 = new int [][] 
090       {{2, 0, 0},
091                {2, 1, 0},
092                {2, 2, 0},
093                {2, 2, 0},
094                {1, 1, 0},
095       };
096        
097        public static Logger log = Global.getLogger(FleetFactoryV3.class);
098        
099        
100        
101        
102        public static float getShipQualityModForStability(float stability) {
103                return (stability - 5f) * 0.05f;
104        }
105        public static float getNumShipsMultForStability(float stability) {
106                return 1f + (stability - 5f) * 0.05f;
107        }
108        
109        
110        public static float getNumShipsMultForMarketSize(float marketSize) {
111                if (marketSize < 3) marketSize = 3;
112                
113//              switch ((int)marketSize) {
114//              case 3: return 0.5f;
115//              case 4: return 0.7f;
116//              case 5: return 0.85f;
117//              case 6: return 1f;
118//              case 7: return 1.15f;
119//              case 8: return 1.3f;
120//              case 9: return 1.5f;
121//              case 10: return 2f;
122//              }
123//              switch ((int)marketSize) {
124//              case 3: return 1f;
125//              case 4: return 1.25f;
126//              case 5: return 1.5f;
127//              case 6: return 1.75f;
128//              case 7: return 2.0f;
129//              case 8: return 2.25f;
130//              case 9: return 2.5f;
131//              case 10: return 3f;
132//              }
133                switch ((int)marketSize) {
134                case 3: return 0.5f;
135                case 4: return 0.75f;
136                case 5: return 1f;
137                case 6: return 1.25f;
138                case 7: return 1.5f;
139                case 8: return 1.75f;
140                case 9: return 2f;
141                case 10: return 2.5f;
142                }               
143                
144                return marketSize / 6f; 
145        }
146        public static float getDoctrineNumShipsMult(int doctrineNumShips) {
147                float max = Global.getSettings().getFloat("maxDoctrineNumShipsMult");
148                
149                return 1f + (float) (doctrineNumShips - 1f) * (max - 1f) / 4f; 
150        }
151        
152        public static CampaignFleetAPI createFleet(FleetParamsV3 params) {
153                
154                CreateFleetPlugin plugin = Global.getSector().getGenericPlugins().pickPlugin(CreateFleetPlugin.class, params);
155                if (plugin != null) {
156                        return plugin.createFleet(params);
157                }
158                
159                Global.getSettings().profilerBegin("FleetFactoryV3.createFleet()");
160                try {
161                        
162                boolean fakeMarket = false;
163                MarketAPI market = pickMarket(params);
164                if (market == null) {
165                        market = Global.getFactory().createMarket("fake", "fake", 5);
166                        market.getStability().modifyFlat("fake", 10000);
167                        market.setFactionId(params.factionId);
168                        SectorEntityToken token = Global.getSector().getHyperspace().createToken(0, 0);
169                        market.setPrimaryEntity(token);
170                        
171                        market.getStats().getDynamic().getMod(Stats.FLEET_QUALITY_MOD).modifyFlat("fake", BASE_QUALITY_WHEN_NO_MARKET);
172                        
173                        market.getStats().getDynamic().getMod(Stats.COMBAT_FLEET_SIZE_MULT).modifyFlat("fake", 1f);
174                        
175                        if (params.factionOverride != null) {
176                                market.setCachedFaction(params.factionOverride);
177                        }
178//                      CommodityOnMarketAPI com = market.getCommodityData(Commodities.SHIPS);
179//                      com.setMaxSupply(6);
180//                      com.setMaxDemand(6);
181//                      com.getAvailableStat().setBaseValue(6);
182//                      com.setSupplier(new SupplierData(6, com, false));
183                        fakeMarket = true;
184                }
185                boolean sourceWasNull = params.source == null;
186                params.source = market;
187                if (sourceWasNull && params.qualityOverride == null) { // we picked a nearby market based on location
188                        params.updateQualityAndProducerFromSourceMarket();
189                }
190                
191                //params.timestamp = Global.getSector().getClock().getTimestamp() - (long)(3600 * 24 * 1000);
192//              params.timestamp = Global.getSector().getClock().getTimestamp();
193//              if (params.forceNoTimestamp != null && params.forceNoTimestamp) {
194//                      params.timestamp = null;
195//              }
196                
197//              if (market.getName().equals("Jangala")) {
198//                      System.out.println("wfwdfwef");
199//              }
200                
201//              ShipPickMode mode = ShipPickMode.PRIORITY_THEN_OTHER;
202//              if (params.producer != null && params.producer.getFaction() != market.getFaction()) {
203//                      mode = ShipPickMode.IMPORTED;
204//              }
205//              if (params.modeOverride != null) mode = params.modeOverride;
206                
207                String factionId = params.factionId;
208                if (factionId == null) factionId = params.source.getFactionId();
209                
210                ShipPickMode mode = Misc.getShipPickMode(market, factionId);
211                if (params.modeOverride != null) mode = params.modeOverride;
212                
213                CampaignFleetAPI fleet = null;
214                
215                if (params.factionOverride != null) {
216                        fleet = Global.getFactory().createEmptyFleet(params.factionOverride, true);
217                        fleet.setName(params.factionOverride.getFleetTypeName(params.fleetType));
218                } else {
219                        fleet = createEmptyFleet(factionId, params.fleetType, market);
220                }
221                fleet.getFleetData().setOnlySyncMemberLists(true);
222                
223                Misc.getSalvageSeed(fleet); // will set it
224                
225//              if (true) {
226//                      fleet.getFleetData().setOnlySyncMemberLists(false);
227//                      fleet.getFleetData().addFleetMember("atlas_Standard");
228//                      return fleet;
229//              }
230                
231                FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine();
232                if (params.doctrineOverride != null) {
233                        doctrine = params.doctrineOverride;
234                }
235                
236                float numShipsMult = 1f;
237                if (params.ignoreMarketFleetSizeMult == null || !params.ignoreMarketFleetSizeMult) {
238                        numShipsMult = market.getStats().getDynamic().getMod(Stats.COMBAT_FLEET_SIZE_MULT).computeEffective(0f);
239                }
240                
241                float quality = params.quality + params.qualityMod;
242//              if (mode == ShipPickMode.IMPORTED) { // factored in by FleetParamsV3 calculation of quality
243//                      quality -= IMPORTED_QUALITY_PENALTY;
244//              }
245                if (params.qualityOverride != null) {
246                        quality = params.qualityOverride;
247                }
248                
249                Random random = new Random();
250                if (params.random != null) random = params.random;
251                
252                //Misc.setSpawnFPMult(fleet, numShipsMult);
253                
254                float combatPts = params.combatPts * numShipsMult;
255                
256                if (params.onlyApplyFleetSizeToCombatShips != null && params.onlyApplyFleetSizeToCombatShips) {
257                        numShipsMult = 1f;
258                }
259                
260                float freighterPts = params.freighterPts * numShipsMult;
261                float tankerPts = params.tankerPts * numShipsMult;
262                float transportPts = params.transportPts * numShipsMult;
263                float linerPts = params.linerPts * numShipsMult;
264                float utilityPts = params.utilityPts * numShipsMult;
265
266                
267                
268                if (combatPts < 10 && combatPts > 0) {
269                        combatPts = Math.max(combatPts, 5 + random.nextInt(6));
270                }
271                
272                float dW = (float) doctrine.getWarships() + random.nextInt(3) - 2;
273                float dC = (float) doctrine.getCarriers() + random.nextInt(3) - 2;
274                float dP = (float) doctrine.getPhaseShips() + random.nextInt(3) - 2;
275                
276                boolean strict = doctrine.isStrictComposition();
277                if (strict) {
278                        dW = (float) doctrine.getWarships() - 1;
279                        dC = (float) doctrine.getCarriers() - 1;
280                        dP = (float) doctrine.getPhaseShips() -1;
281                }
282                
283                if (!strict) {
284                        float r1 = random.nextFloat();
285                        float r2 = random.nextFloat();
286                        float min = Math.min(r1, r2);
287                        float max = Math.max(r1, r2);
288                        
289                        float mag = 1f;
290                        float v1 = min;
291                        float v2 = max - min;
292                        float v3 = 1f - max;
293                        
294                        v1 *= mag;
295                        v2 *= mag;
296                        v3 *= mag;
297                        
298                        v1 -= mag/3f;
299                        v2 -= mag/3f;
300                        v3 -= mag/3f;
301                        
302                        //System.out.println(v1 + "," + v2 + "," + v3);
303                        dW += v1;
304                        dC += v2;
305                        dP += v3;
306                }
307                
308                if (doctrine.getWarships() <= 0) dW = 0;
309                if (doctrine.getCarriers() <= 0) dC = 0;
310                if (doctrine.getPhaseShips() <= 0) dP = 0;
311                
312                
313//              float dW = (float) doctrine.getWarships() + random.nextInt(2) - 1;
314//              float dC = (float) doctrine.getCarriers() + random.nextInt(2) - 1;
315//              float dP = (float) doctrine.getPhaseShips() + random.nextInt(2) - 1;
316                
317                boolean banPhaseShipsEtc = !fleet.getFaction().isPlayerFaction() && 
318                                                                        combatPts < FLEET_POINTS_THRESHOLD_FOR_ANNOYING_SHIPS;
319                if (params.forceAllowPhaseShipsEtc != null && params.forceAllowPhaseShipsEtc) {
320                        banPhaseShipsEtc = !params.forceAllowPhaseShipsEtc;
321                }
322                
323                params.mode = mode;
324                params.banPhaseShipsEtc = banPhaseShipsEtc;
325
326                // with the phase AI changes: allow phase ships in smaller fleets
327                // but still ban the "etc" (i.e. hyperion, ships with damper field, etc - 
328                // anything not in the "combatSmallForSmallFleet" role
329//              if (banPhaseShipsEtc) {
330//                      dP = 0;
331//              };
332                
333                if (dW < 0) dW = 0;
334                if (dC < 0) dC = 0;
335                if (dP < 0) dP = 0;
336                
337                float extra = 7 - (dC + dP + dW);
338                if (extra < 0) extra = 0f;
339                if (doctrine.getWarships() > doctrine.getCarriers() && doctrine.getWarships() > doctrine.getPhaseShips()) {
340                        dW += extra;
341                } else if (doctrine.getCarriers() > doctrine.getWarships() && doctrine.getCarriers() > doctrine.getPhaseShips()) {
342                        dC += extra;
343                } else if (doctrine.getPhaseShips() > doctrine.getWarships() && doctrine.getPhaseShips() > doctrine.getCarriers()) {
344                        dP += extra;
345                }
346                
347                
348                float doctrineTotal = dW + dC + dP;
349                
350                //System.out.println("DW: " + dW + ", DC: " + dC + " DP: " + dP);
351                
352                combatPts = (int) combatPts;
353                int warships = (int) (combatPts * dW / doctrineTotal);
354                int carriers = (int) (combatPts * dC / doctrineTotal);
355                int phase = (int) (combatPts * dP / doctrineTotal);
356                
357                warships += (combatPts - warships - carriers - phase);
358                
359                if (params.addShips != null) {
360                        for (String variantId : params.addShips) {
361                                ShipRolePick pick = new ShipRolePick(variantId);
362                                warships -= addToFleet(pick, fleet, random);
363                        }
364                        if (warships < 0) warships = 0;
365                }
366                
367                
368                if (params.treatCombatFreighterSettingAsFraction != null && params.treatCombatFreighterSettingAsFraction) {
369                        float combatFreighters = (int) Math.min(freighterPts * 1.5f, warships * 1.5f) * doctrine.getCombatFreighterProbability();
370                        float added = addCombatFreighterFleetPoints(fleet, random, combatFreighters, params);
371                        freighterPts -= added * 0.5f;
372                        warships -= added * 0.5f;
373                } else if (freighterPts > 0 && random.nextFloat() < doctrine.getCombatFreighterProbability()) {
374                        float combatFreighters = (int) Math.min(freighterPts * 1.5f, warships * 1.5f);
375                        float added = addCombatFreighterFleetPoints(fleet, random, combatFreighters, params);
376                        freighterPts -= added * 0.5f;
377                        warships -= added * 0.5f;
378                }
379                
380                addCombatFleetPoints(fleet, random, warships, carriers, phase, params);
381                
382                
383                addFreighterFleetPoints(fleet, random, freighterPts, params);
384                addTankerFleetPoints(fleet, random, tankerPts, params);
385                addTransportFleetPoints(fleet, random, transportPts, params);
386                addLinerFleetPoints(fleet, random, linerPts, params);
387                addUtilityFleetPoints(fleet, random, utilityPts, params);
388                
389                
390                //System.out.println("FLEET POINTS: " + getFP(fleet));
391                int maxShips = Global.getSettings().getInt("maxShipsInAIFleet");
392                if (params.maxNumShips != null) {
393                        maxShips = params.maxNumShips;
394                }
395                if (fleet.getFleetData().getNumMembers() > maxShips) {
396                        if (params.doNotPrune == null || !params.doNotPrune) {
397                                float targetFP = getFP(fleet);
398                                if (params.doNotAddShipsBeforePruning == null || !params.doNotAddShipsBeforePruning) {
399                                        sizeOverride = 5;
400                                        addCombatFleetPoints(fleet, random, warships, carriers, phase, params);
401                                        addFreighterFleetPoints(fleet, random, freighterPts, params);
402                                        addTankerFleetPoints(fleet, random, tankerPts, params);
403                                        addTransportFleetPoints(fleet, random, transportPts, params);
404                                        addLinerFleetPoints(fleet, random, linerPts, params);
405                                        addUtilityFleetPoints(fleet, random, utilityPts, params);
406                                        sizeOverride = 0;
407                                }
408                        
409                                int size = doctrine.getShipSize();
410                                pruneFleet(maxShips, size, fleet, targetFP, random);
411                                
412                                float currFP = getFP(fleet);
413                                //currFP = getFP(fleet);
414//                              if (currFP < targetFP) {
415//                                      extraOfficers = (int) Math.round ((targetFP / Math.max(10f, currFP) - 1f) * 10f);
416//                                      if (extraOfficers > 30) extraOfficers = 30;
417//                                      if (extraOfficers < 0) extraOfficers = 0;
418//                              }
419                        }
420                        
421                        fleet.getFleetData().sort();
422                        
423                        //System.out.println("FLEET POINTS: " + getFP(fleet));
424                        
425                        
426                } else {
427                        fleet.getFleetData().sort();
428                }
429                
430                fleet.getFleetData().sort();
431                
432                if (params.withOfficers) {
433                        addCommanderAndOfficers(fleet, params, random);
434                }
435                
436                if (fleet.getFlagship() != null) {
437                        if (params.flagshipVariantId != null) {
438                                fleet.getFlagship().setVariant(Global.getSettings().getVariant(params.flagshipVariantId), false, true);
439                        } else if (params.flagshipVariant != null) {
440                                fleet.getFlagship().setVariant(params.flagshipVariant, false, true);
441                        }
442                }
443                
444                if (params.onlyRetainFlagship != null && params.onlyRetainFlagship) {
445                        for (FleetMemberAPI curr : fleet.getFleetData().getMembersListCopy()) {
446                                if (curr.isFlagship()) continue;
447                                fleet.getFleetData().removeFleetMember(curr);
448                        }
449                }
450                //fleet.getFlagship()
451                fleet.forceSync();
452                
453                //FleetFactoryV2.doctrine = null;
454                
455                if (fleet.getFleetData().getNumMembers() <= 0 || 
456                                fleet.getFleetData().getNumMembers() == fleet.getNumFighters()) {
457//                      if (params.allowEmptyFleet == null || !params.allowEmptyFleet){ 
458//                              return null;
459//                      }
460                }
461                
462                if (fakeMarket) {
463                        params.source = null;
464                }
465                
466                DefaultFleetInflaterParams p = new DefaultFleetInflaterParams();
467                p.quality = quality;
468                if (params.averageSMods != null) {
469                        p.averageSMods = params.averageSMods;
470                }
471                p.persistent = true;
472                p.seed = random.nextLong();
473                p.mode = mode;
474                p.timestamp = params.timestamp;
475                p.allWeapons = params.allWeapons;
476                if (params.doctrineOverride != null) {
477                        p.rProb = params.doctrineOverride.getAutofitRandomizeProbability();
478                }
479                if (params.factionId != null) {
480                        p.factionId = params.factionId;
481                }
482                
483                FleetInflater inflater = Misc.getInflater(fleet, p);
484                fleet.setInflater(inflater);
485                
486                fleet.getFleetData().setOnlySyncMemberLists(false);
487                fleet.getFleetData().sort();
488                
489                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
490                for (FleetMemberAPI member : members) {
491                        member.getRepairTracker().setCR(member.getRepairTracker().getMaxCR());
492                }
493                
494                float requestedPoints = params.getTotalPts();
495                float actualPoints = fleet.getFleetPoints();
496                
497                Misc.setSpawnFPMult(fleet, actualPoints / Math.max(1f, requestedPoints));
498                
499                
500                return fleet;
501                
502                } finally {
503                        Global.getSettings().profilerEnd();
504                }
505        }
506        
507        public static void pruneFleet(int maxShips, int doctrineSize, CampaignFleetAPI fleet, float targetFP, Random random) {
508                //int maxShips = Global.getSettings().getInt("maxShipsInAIFleet");
509                
510                float combatFP = 0;
511                float civFP = 0;
512                
513                List<FleetMemberAPI> copy = fleet.getFleetData().getMembersListCopy();
514                List<FleetMemberAPI> combat = new ArrayList<FleetMemberAPI>();
515                //List<FleetMemberAPI> civ = new ArrayList<FleetMemberAPI>();
516                List<FleetMemberAPI> tanker = new ArrayList<FleetMemberAPI>();
517                List<FleetMemberAPI> freighter = new ArrayList<FleetMemberAPI>();
518                List<FleetMemberAPI> liner = new ArrayList<FleetMemberAPI>();
519                List<FleetMemberAPI> other = new ArrayList<FleetMemberAPI>();
520                
521                for (FleetMemberAPI member : copy) {
522                        if (member.isCivilian()) {
523                                civFP += member.getFleetPointCost();
524                                //civ.add(member);
525                                
526                                if (member.getHullSpec().getHints().contains(ShipTypeHints.FREIGHTER)) {
527                                        freighter.add(member);
528                                } else if (member.getHullSpec().getHints().contains(ShipTypeHints.TANKER)) {
529                                        tanker.add(member);
530                                } else if (member.getHullSpec().getHints().contains(ShipTypeHints.TRANSPORT) ||
531                                                member.getHullSpec().getHints().contains(ShipTypeHints.LINER)) {
532                                        liner.add(member);
533                                } else {
534                                        other.add(member);
535                                }
536                                
537                        } else {
538                                combatFP += member.getFleetPointCost();
539                                combat.add(member);
540                        }
541                }
542                if (civFP < 1) civFP = 1;
543                if (combatFP < 1) combatFP = 1;
544                
545                int keepCombat = (int) ((float)maxShips * combatFP / (civFP + combatFP));
546                int keepCiv = maxShips - keepCombat;
547                if (civFP > 10 && keepCiv < 2) {
548                        keepCiv = 2;
549                        if (!freighter.isEmpty()) keepCiv++;
550                        if (!tanker.isEmpty()) keepCiv++;
551                        if (!liner.isEmpty()) keepCiv++;
552                        if (!other.isEmpty()) keepCiv++;
553                        
554                        keepCiv = maxShips - keepCiv;
555                }
556                
557                
558                float f = 0, t = 0, l = 0, o = 0;
559                float total = freighter.size() + tanker.size() + liner.size() + other.size();
560                if (total < 1) total = 1;
561                
562                f = (float) freighter.size() / total;
563                t = (float) tanker.size() / total;
564                l = (float) liner.size() / total;
565                o = (float) other.size() / total;
566                
567                f *= keepCiv;
568                t *= keepCiv;
569                l *= keepCiv;
570                o *= keepCiv;
571                
572                if (f > 0) f = Math.round(f);
573                if (t > 0) t = Math.round(t);
574                if (l > 0) l = Math.round(l);
575                if (o > 0) o = Math.round(o);
576                
577                if (freighter.size() > 0 && f < 1) f = 1;
578                if (tanker.size() > 0 && t < 1) t = 1;
579                if (liner.size() > 0 && l < 1) l = 1;
580                if (other.size() > 0 && o < 1) o = 1;
581                
582                int extra = (int) ((f + t + l + o) - keepCiv);
583                //if (extra < 0) keepCombat += Math.abs(extra);
584                if (extra > 0 && o >= 2) {
585                        extra--;
586                        o--;
587                }
588                if (extra > 0 && l >= 2) {
589                        extra--;
590                        l--;
591                }
592                if (extra > 0 && t >= 2) {
593                        extra--;
594                        t--;
595                }
596                if (extra > 0 && f >= 2) {
597                        extra--;
598                        f--;
599                }
600                
601                
602                LinkedHashSet<FleetMemberAPI> keep = new LinkedHashSet<FleetMemberAPI>();
603                
604                Comparator<FleetMemberAPI> c = new Comparator<FleetMemberAPI>() {
605                        public int compare(FleetMemberAPI o1, FleetMemberAPI o2) {
606                                return o2.getHullSpec().getHullSize().ordinal() - o1.getHullSpec().getHullSize().ordinal();
607                        }
608                };
609                Collections.sort(combat, c);
610                Collections.sort(freighter, c);
611                Collections.sort(tanker, c);
612                Collections.sort(liner, c);
613                Collections.sort(other, c);
614                
615                int [] ratio = new int [] { 4, 2, 1, 1 };
616                //int [] ratio = new int [] { 1, 2, 2, 1 };
617                
618                //doctrineSize = 2;
619//              if (doctrineSize == 4) {
620//                      ratio = new int [] { 3, 3, 1, 1 };
621//              } else if (doctrineSize == 3) {
622//                      ratio = new int [] { 2, 3, 2, 1 };
623//              } else if (doctrineSize <= 2) {
624//                      ratio = new int [] { 2, 3, 2, 1 };
625//              }
626                //ratio[3] = 0;
627                //ratio = new int [] { 4, 0, 0, 0 };
628                
629                addAll(ratio, combat, keep, keepCombat, random);
630                //addAll(ratio, civ, keep, keepCiv, random);
631                
632                addAll(ratio, freighter, keep, (int)f, random);
633                addAll(ratio, tanker, keep, (int)t, random);
634                addAll(ratio, liner, keep, (int)l, random);
635                addAll(ratio, other, keep, (int)o, random); // adds a Hermes since that's "other" but we don't really care
636                
637                for (FleetMemberAPI member : copy) {
638                        if (!keep.contains(member)) {
639                                fleet.getFleetData().removeFleetMember(member);
640                        }
641                }
642
643                float currFP = getFP(fleet);
644                if (currFP > targetFP) {
645                        fleet.getFleetData().sort();
646                        copy = fleet.getFleetData().getMembersListCopy();
647                        //Collections.reverse(copy);
648                        //Collections.shuffle(copy, random);
649                        for (int i = 0; i < copy.size()/2; i+=2) {
650                                FleetMemberAPI f1 = copy.get(i);
651                                FleetMemberAPI f2 = copy.get(copy.size() - 1 - i);
652                                copy.set(i, f2);
653                                copy.set(copy.size() - 1 - i, f1);
654                        }
655//                      
656//                      float fpGoal = currFP - targetFP;
657//                      float fpDone = 0;
658//                      for (FleetMemberAPI curr : copy) {
659//                              if (curr.isCivilian()) continue;
660//                              for (FleetMemberAPI replace : combat) {
661//                                      float fpCurr = curr.getFleetPointCost();
662//                                      float fpReplace = replace.getFleetPointCost();
663//                                      if (fpCurr > fpReplace) {
664//                                              fpDone += fpCurr - fpReplace;
665//                                              combat.remove(replace);
666//                                              fleet.getFleetData().removeFleetMember(curr);
667//                                              fleet.getFleetData().addFleetMember(replace);
668//                                              break;
669//                                      }
670//                              }
671//                              if (fpDone >= fpGoal) {
672//                                      break;
673//                              }
674//                      }
675                        
676                        float fpGoal = currFP - targetFP;
677                        float fpDone = 0;
678                        for (FleetMemberAPI curr : copy) {
679                                if (curr.isCivilian()) continue;
680                                FleetMemberAPI best = null;
681                                float bestDiff = 0f;
682                                for (FleetMemberAPI replace : combat) {
683                                        float fpCurr = curr.getFleetPointCost();
684                                        float fpReplace = replace.getFleetPointCost();
685                                        if (fpCurr > fpReplace) {
686                                                float fpDiff = fpCurr - fpReplace;
687                                                if (fpDone + fpDiff <= fpGoal) {
688                                                        best = replace;
689                                                        bestDiff = fpDiff;
690                                                        break;
691                                                } else {
692                                                        if (fpDiff < bestDiff) {
693                                                                best = replace;
694                                                                bestDiff = fpDiff;
695                                                        }
696                                                }
697                                        }
698                                }
699                                if (best != null) {
700                                        fpDone += bestDiff;
701                                        combat.remove(best);
702                                        fleet.getFleetData().removeFleetMember(curr);
703                                        fleet.getFleetData().addFleetMember(best);
704                                }
705                                if (fpDone >= fpGoal) {
706                                        break;
707                                }
708                        }
709                        
710                }
711                
712        }
713        
714        public static void addAll(int [] ratio, List<FleetMemberAPI> from, LinkedHashSet<FleetMemberAPI> to, int num, Random random) {
715                int added = 0;
716                if (num <= 5) {
717                        while (added < num && !from.isEmpty()) {
718                                to.add(from.remove(0));
719                                added++;
720                        }
721                        return;
722                }
723                
724                WeightedRandomPicker<HullSize> picker = makePicker(ratio, random);
725                for (int i = 0; i < num; i++) {
726                        if (picker.isEmpty()) picker = makePicker(ratio, random);
727                        OUTER: while (!picker.isEmpty()) {
728                                HullSize size = picker.pickAndRemove();
729                                for (FleetMemberAPI member : from) {
730                                        if (member.getHullSpec().getHullSize() == size) {
731                                                to.add(member);
732                                                from.remove(member);
733                                                added++;
734                                                break OUTER;
735                                        }
736                                }
737                        }
738                        
739                }
740                
741                // if we failed to add up to num, add the largest ships until we've got num
742                // assumes from list is sorted descending by size
743                while (added < num && !from.isEmpty()) {
744                        to.add(from.remove(0));
745                        added++;
746                }
747                
748        }
749        
750        public static WeightedRandomPicker<HullSize> makePicker(int [] ratio, Random random) {
751                WeightedRandomPicker<HullSize> picker = new WeightedRandomPicker<HullSize>(random);
752                for (int i = 0; i < ratio[0]; i++) {
753                        picker.add(HullSize.CAPITAL_SHIP);
754                }
755                for (int i = 0; i < ratio[1]; i++) {
756                        picker.add(HullSize.CRUISER);
757                }
758                for (int i = 0; i < ratio[2]; i++) {
759                        picker.add(HullSize.DESTROYER);
760                }
761                for (int i = 0; i < ratio[3]; i++) {
762                        picker.add(HullSize.FRIGATE);
763                }
764//              picker.add(HullSize.CAPITAL_SHIP, ratio[0]);
765//              picker.add(HullSize.CRUISER, ratio[1]);
766//              picker.add(HullSize.DESTROYER, ratio[2]);
767//              picker.add(HullSize.FRIGATE, ratio[3]);
768                return picker;  
769        }
770        
771        
772        public static int getFP(CampaignFleetAPI fleet) {
773                int fp = 0;
774                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
775                        fp += member.getFleetPointCost();
776                }
777                return fp;
778        }
779        
780        
781        public static List<FleetMemberAPI> getRemoveOrder(CampaignFleetAPI fleet) {
782                List<FleetMemberAPI> remove = new ArrayList<FleetMemberAPI>();
783                List<FleetMemberAPI> copy = fleet.getFleetData().getMembersListCopy();
784                
785//              Collections.sort(copy, new Comparator<FleetMemberAPI>() {
786//                      public int compare(FleetMemberAPI o1, FleetMemberAPI o2) {
787//                              int f1 = o1.getFleetPointCost();
788//                              int f2 = o2.getFleetPointCost();
789//                              
790//                              if (!o1.isCivilian()) f1 *= 
791//                              return 0;
792//                      }
793//              });
794                
795                Collections.reverse(copy);
796                
797                Iterator<FleetMemberAPI> iter;
798                
799                iter = copy.iterator();
800                while (iter.hasNext()) {
801                        FleetMemberAPI member = iter.next();
802                        if (member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.FRIGATE.ordinal()) {
803                                remove.add(member);
804                                iter.remove();
805                        }
806                }
807                
808                iter = copy.iterator();
809                while (iter.hasNext()) {
810                        FleetMemberAPI member = iter.next();
811                        if (!member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.FRIGATE.ordinal()) {
812                                remove.add(member);
813                                iter.remove();
814                        }
815                }
816                
817                iter = copy.iterator();
818                while (iter.hasNext()) {
819                        FleetMemberAPI member = iter.next();
820                        if (member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.DESTROYER.ordinal()) {
821                                remove.add(member);
822                                iter.remove();
823                        }
824                }
825                
826                iter = copy.iterator();
827                while (iter.hasNext()) {
828                        FleetMemberAPI member = iter.next();
829                        if (!member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.DESTROYER.ordinal()) {
830                                remove.add(member);
831                                iter.remove();
832                        }
833                }
834                
835                iter = copy.iterator();
836                while (iter.hasNext()) {
837                        FleetMemberAPI member = iter.next();
838                        if (member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.CRUISER.ordinal()) {
839                                remove.add(member);
840                                iter.remove();
841                        }
842                }
843                
844                iter = copy.iterator();
845                while (iter.hasNext()) {
846                        FleetMemberAPI member = iter.next();
847                        if (!member.isCivilian() && member.getHullSpec().getHullSize().ordinal() <= HullSize.CRUISER.ordinal()) {
848                                remove.add(member);
849                                iter.remove();
850                        }
851                }
852                
853                iter = copy.iterator();
854                while (iter.hasNext()) {
855                        FleetMemberAPI member = iter.next();
856                        if (member.isCivilian()) {
857                                remove.add(member);
858                                iter.remove();
859                        }
860                }
861                
862                iter = copy.iterator();
863                while (iter.hasNext()) {
864                        FleetMemberAPI member = iter.next();
865                        if (!member.isCivilian()) {
866                                remove.add(member);
867                                iter.remove();
868                        }
869                }
870                
871                return remove;
872        }
873        
874        public static void addCommanderAndOfficers(CampaignFleetAPI fleet, FleetParamsV3 params, Random random) {
875                if (true) {
876                        addCommanderAndOfficersV2(fleet, params, random);
877                        return;
878                }
879        }
880        
881        
882        public static void addCommanderAndOfficersV2(CampaignFleetAPI fleet, FleetParamsV3 params, Random random) {
883                addCommanderAndOfficersV2(fleet, params, random, false, false);
884        }
885        public static void addCommanderAndOfficersV2(CampaignFleetAPI fleet, FleetParamsV3 params, Random random, boolean simFleet, boolean putOfficersOnCivShips) {
886                
887                if (!simFleet) {
888                        GenerateFleetOfficersPickData pickData = new GenerateFleetOfficersPickData(fleet, params);
889                        GenerateFleetOfficersPlugin genPlugin = Global.getSector().getGenericPlugins().pickPlugin(GenerateFleetOfficersPlugin.class, pickData);
890                        if (genPlugin != null) {
891                                genPlugin.addCommanderAndOfficers(fleet, params, random);
892                                return;
893                        }
894                }
895
896                FactionAPI faction = fleet.getFaction();
897                FactionDoctrineAPI doctrine = faction.getDoctrine();
898                if (params.doctrineOverride != null) {
899                        doctrine = params.doctrineOverride;
900                }
901                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
902                if (members.isEmpty()) return;
903                
904                float combatPoints = 0f;
905                float combatShips = 0f;
906                for (FleetMemberAPI member : members) {
907                        if (member.isCivilian() && !putOfficersOnCivShips) continue;
908                        if (member.isFighterWing()) continue;
909                        combatPoints += member.getFleetPointCost();
910                        combatShips++;
911                }
912                if (combatPoints < 1f) combatPoints = 1f;
913                if (combatShips < 1f) combatShips = 1f;
914                
915                boolean debug = true;
916                debug = false;
917                
918                
919                int maxCommanderLevel = Global.getSettings().getInt("maxAIFleetCommanderLevel");
920                float mercMult = Global.getSettings().getFloat("officerAIMaxMercsMult");
921                //float mercFP = Global.getSettings().getFloat("officerAIMercsStartingFP");
922                int maxOfficers = Global.getSettings().getInt("officerAIMax");
923                int baseMaxOfficerLevel = Global.getSettings().getInt("officerMaxLevel");
924                OfficerLevelupPlugin plugin = (OfficerLevelupPlugin) Global.getSettings().getPlugin("officerLevelUp");
925                
926                float officerQualityMult = (doctrine.getOfficerQuality() - 1f) / 4f;
927                if (officerQualityMult > 1f) officerQualityMult = 1f;
928                
929//              float baseFPPerOfficer = Global.getSettings().getFloat("baseFPPerOfficer");
930//              float fpPerBaseOfficer = baseFPPerOfficer - (baseFPPerOfficer * 0.5f * officerQualityMult);  
931//              float fpPerExtraOfficer = fpPerBaseOfficer * 1f;
932                
933                float baseShipsForMaxOfficerLevel = Global.getSettings().getFloat("baseCombatShipsForMaxOfficerLevel");
934                float baseCombatShipsPerOfficer = Global.getSettings().getFloat("baseCombatShipsPerOfficer");
935                float combatShipsPerOfficer = baseCombatShipsPerOfficer * (1f - officerQualityMult * 0.5f);
936                
937                //float fleetSizeOfficerQualityMult = combatPoints / (fpPerBaseOfficer * maxOfficers);
938                float fleetSizeOfficerQualityMult = combatShips / (baseShipsForMaxOfficerLevel *  (1f - officerQualityMult * 0.5f));
939                if (fleetSizeOfficerQualityMult > 1) fleetSizeOfficerQualityMult = 1;
940                
941                maxOfficers += (int)((float)doctrine.getOfficerQuality() * mercMult) + params.officerNumberBonus;
942                
943                //int numOfficers = (int) (combatPoints / fpPerBaseOfficer) + params.officerNumberBonus;
944                int numOfficers = (int) Math.min(maxOfficers, combatShips / combatShipsPerOfficer);
945                //numOfficers += (int) Math.max(0, (combatPoints - mercFP) / fpPerExtraOfficer);
946                numOfficers += params.officerNumberBonus;
947                numOfficers = Math.round(numOfficers * params.officerNumberMult);
948                
949                if (debug) System.out.println("numOfficers: " + numOfficers);
950                
951                
952//              if (params.maxOfficers >= 0) maxOfficers = params.maxOfficers;
953//              if (params.minOfficers >= 0 && numOfficers < params.minOfficers) numOfficers = params.minOfficers;
954                
955                if (numOfficers > maxOfficers) numOfficers = maxOfficers;
956                
957                if (params.commander != null && params.commander.isPlayer()) {
958                        numOfficers = (int) params.commander.getStats().getOfficerNumber().getModifiedInt();
959                }
960                if (params.maxOfficersToAdd != null) {
961                        numOfficers = Math.min(numOfficers, params.maxOfficersToAdd);
962                }
963                
964                //int maxOfficerLevel = (int) Math.round((officerQualityMult * 0.75f + fleetSizeOfficerQualityMult * 1f) * (float) baseMaxOfficerLevel);
965                int maxOfficerLevel = (int)Math.round(((float)doctrine.getOfficerQuality() / 2f) +  
966                                                                (fleetSizeOfficerQualityMult * 1f) * (float) baseMaxOfficerLevel);
967                if (maxOfficerLevel < 1) maxOfficerLevel = 1;
968                maxOfficerLevel += params.officerLevelBonus;
969                if (maxOfficerLevel < 1) maxOfficerLevel = 1;
970                
971                if (debug) System.out.println("maxOfficers: " + maxOfficers);
972                if (debug) System.out.println("maxOfficerLevel: " + maxOfficerLevel);
973                
974
975                WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>(random);
976                WeightedRandomPicker<FleetMemberAPI> flagshipPicker = new WeightedRandomPicker<FleetMemberAPI>(random);
977                
978                int maxSize = 0;
979                for (FleetMemberAPI member : members) {
980                        if (member.isFighterWing()) continue;
981                        if (member.isFlagship()) continue;
982                        if (member.isCivilian() && !putOfficersOnCivShips) continue;
983                        if (!member.getCaptain().isDefault()) continue;
984                        int size = member.getHullSpec().getHullSize().ordinal();
985                        if (size > maxSize) {
986                                maxSize = size;
987                        }
988                }
989                //maxSize = 2;
990                for (FleetMemberAPI member : members) {
991                        if (member.isFighterWing()) continue;
992                        if (member.isFlagship()) continue;
993                        if (member.isCivilian() && !putOfficersOnCivShips) continue;
994                        if (!member.getCaptain().isDefault()) continue;
995                        
996                        float weight = (float) member.getFleetPointCost();
997                        int size = member.getHullSpec().getHullSize().ordinal();
998                        if (size >= maxSize) {
999                                flagshipPicker.add(member, weight);
1000                        }
1001                        
1002                        picker.add(member, weight);
1003                }
1004                
1005                if (picker.isEmpty()) {
1006                        picker.add(members.get(0), 1f);
1007                }
1008                if (flagshipPicker.isEmpty()) {
1009                        flagshipPicker.add(members.get(0), 1f);
1010                }
1011                
1012                
1013                FleetMemberAPI flagship = flagshipPicker.pickAndRemove();
1014                if (!simFleet) picker.remove(flagship);
1015                int commanderLevel = maxOfficerLevel;
1016                int commanderLevelLimit = maxCommanderLevel;
1017//              if (simFleet) {
1018//                      commanderLevelLimit = maxOfficerLevel;
1019//              }
1020//              if (commanderLevelLimit > params.officerLevelLimit) commanderLevelLimit = params.officerLevelLimit;
1021//              if (commanderLevelLimit > maxCommanderLevel) commanderLevelLimit = maxCommanderLevel;
1022                if (params.commanderLevelLimit != 0) {
1023                        commanderLevelLimit = params.commanderLevelLimit;
1024                }
1025                if (commanderLevel > commanderLevelLimit) commanderLevel = commanderLevelLimit;
1026                
1027                SkillPickPreference pref = getSkillPrefForShip(flagship);
1028                PersonAPI commander = params.commander;
1029                if (commander == null) {
1030                        commander = OfficerManagerEvent.createOfficer(fleet.getFaction(), commanderLevel, pref, false, null, true, true, -1, random);
1031                        if (commander.getPersonalityAPI().getId().equals(Personalities.TIMID)) {
1032                                commander.setPersonality(Personalities.CAUTIOUS);
1033                        }
1034                        addCommanderSkills(commander, fleet, params, random);
1035                }
1036                if (params.commander == null) {
1037                        commander.setRankId(Ranks.SPACE_COMMANDER);
1038                        commander.setPostId(Ranks.POST_FLEET_COMMANDER);
1039                }
1040                fleet.setCommander(commander);
1041                if (simFleet) {
1042                        //numOfficers++;
1043                } else {
1044                        fleet.getFleetData().setFlagship(flagship);
1045                }
1046                
1047                int commanderOfficerLevelBonus = (int) commander.getStats().getDynamic().getMod(Stats.OFFICER_MAX_LEVEL_MOD).computeEffective(0);
1048                int officerLevelLimit = plugin.getMaxLevel(null) + commanderOfficerLevelBonus;
1049                //if (officerLevelLimit > params.officerLevelLimit) officerLevelLimit = params.officerLevelLimit;
1050                if (params.officerLevelLimit != 0) {
1051                        officerLevelLimit = params.officerLevelLimit;
1052                }
1053                
1054                if (debug) {
1055                        System.out.println("Created level " + commander.getStats().getLevel() + " commander");
1056                        System.out.println("Max officer level bonus: " + commanderOfficerLevelBonus + " (due to commander skill)");
1057                        System.out.println("Adding up to " + numOfficers + " officers");
1058                }
1059                
1060                int added = 0;
1061                for (int i = 0; i < numOfficers; i++) {
1062                        FleetMemberAPI member = picker.pickAndRemove();
1063                        if (member == null) {
1064                                break; // out of ships that need officers
1065                        }
1066                        
1067                        int level = maxOfficerLevel - random.nextInt(3);
1068                        if (Misc.isEasy()) {
1069                                 level = (int) Math.ceil((float) level * Global.getSettings().getFloat("easyOfficerLevelMult"));
1070                        }
1071                        if (level < 1) level = 1;
1072//                      if (level >= 7) {
1073//                              System.out.println("4fefewfwe");
1074//                      }
1075                        if (level > officerLevelLimit) level = officerLevelLimit;
1076                        if (params.commander != null && params.commander.isPlayer()) {
1077                                level = (int) params.commander.getStats().getDynamic().getMod(Stats.OFFICER_MAX_LEVEL_MOD).computeEffective(Global.getSettings().getInt("officerMaxLevel"));
1078                        }
1079                        
1080                        pref = getSkillPrefForShip(member);
1081                        PersonAPI person = OfficerManagerEvent.createOfficer(fleet.getFaction(), level, pref, false, fleet, true, true, -1, random);
1082                        if (person.getPersonalityAPI().getId().equals(Personalities.TIMID)) {
1083                                person.setPersonality(Personalities.CAUTIOUS);
1084                        }
1085                        
1086//                      if (person.getStats().getLevel()  >= 7) {
1087//                              System.out.println("4fefewfwe");
1088//                      }
1089
1090                        if (debug) {
1091                                System.out.println("Added level " + person.getStats().getLevel() + " officer");
1092                        }
1093                        added++;
1094                        member.setCaptain(person);
1095                        
1096                        if (params.commander != null && params.commander.isPlayer()) {
1097                                fleet.getFleetData().addOfficer(person);
1098                        }
1099                }
1100                
1101                if (debug) {
1102                        System.out.println("Added " + added + " officers total");
1103                }
1104                
1105        }
1106        
1107        public static SkillPickPreference getSkillPrefForShip(FleetMemberAPI member) {
1108                float energy = 0f;
1109                float ballistic = 0f;
1110                float missile = 0f;
1111                float total = 0f;
1112                
1113                for (WeaponSlotAPI slot : member.getHullSpec().getAllWeaponSlotsCopy()) {
1114                        float w = 1f;
1115                        switch (slot.getSlotSize()) {
1116                        case LARGE: w = 4f; break;
1117                        case MEDIUM: w = 2f; break;
1118                        case SMALL: w = 1f; break;
1119                        }
1120                        WeaponType type = slot.getWeaponType();
1121                        if (type == WeaponType.BALLISTIC || type == WeaponType.HYBRID) { 
1122                                ballistic += w;
1123                                total += w;
1124                        } else if (type == WeaponType.ENERGY) { 
1125                                energy += w;
1126                                total += w;
1127                        } else if (type == WeaponType.MISSILE || type == WeaponType.SYNERGY || type == WeaponType.COMPOSITE) { 
1128                                missile += w;
1129                                total += w;
1130                        }
1131                }
1132                
1133                if (total <= 0f) total = 1f;
1134                
1135                boolean e = energy >= total * 0.33f;
1136                boolean b = ballistic >= total * 0.33f;
1137                if (b && e) {
1138                        if (ballistic * 1.5f >= energy) {
1139                                e = false;
1140                        } else {
1141                                b = false;
1142                        }
1143                }
1144                boolean m = missile >= total * 0.17f;
1145                
1146                boolean d = member.getHullSpec().getShieldType() == ShieldType.FRONT ||
1147                                        member.getHullSpec().getShieldType() == ShieldType.OMNI || 
1148                                        member.getHullSpec().isPhase();
1149                
1150                // doing things in this, ah, "elegant" way to keep method signatures the same for now...
1151                String n1 = e ? "YES_ENERGY" : "NO_ENERGY";
1152                String n2 = b ? "YES_BALLISTIC" : "NO_BALLISTIC";
1153                String n3 = m ? "YES_MISSILE" : "NO_MISSILE";
1154                String n4 = d ? "YES_DEFENSE" : "NO_DEFENSE";
1155                SkillPickPreference pref = SkillPickPreference.valueOf(n1 + "_" + n2 + "_" + n3 + "_" + n4);
1156                
1157                return pref;
1158        }
1159        
1160        
1161        public static void addCommanderSkills(PersonAPI commander, CampaignFleetAPI fleet, FleetParamsV3 params, Random random) {
1162                if (params != null && params.noCommanderSkills != null && params.noCommanderSkills) return;
1163                
1164                if (random == null) random = new Random();
1165                
1166                MutableCharacterStatsAPI stats = commander.getStats();
1167                int level = stats.getLevel();
1168                
1169                int forOne = Global.getSettings().getInt("commanderLevelForOneSkill");
1170                int forTwo = Global.getSettings().getInt("commanderLevelForTwoSkills");
1171                
1172                int numSkills = 0;
1173                if (level >= forTwo) {
1174                        numSkills = 2;
1175                } else if (level >= forOne) {
1176                        numSkills = 1;
1177                }
1178
1179                if (numSkills <= 0) return;
1180                
1181                FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine();
1182                if (params != null && params.doctrineOverride != null) {
1183                        doctrine = params.doctrineOverride;
1184                }
1185                
1186                List<String> skills = new ArrayList<String>(doctrine.getCommanderSkills());
1187                
1188                Iterator<String> iter = skills.iterator();
1189                while (iter.hasNext()) {
1190                        String id = iter.next();
1191                        SkillSpecAPI spec = Global.getSettings().getSkillSpec(id);
1192                        if (spec != null && spec.hasTag(Skills.TAG_PLAYER_ONLY)) {
1193                                iter.remove();
1194                        }
1195                }
1196                
1197                
1198                if (skills.isEmpty()) return;
1199                
1200                if (random.nextFloat() < doctrine.getCommanderSkillsShuffleProbability()) {
1201                        Collections.shuffle(skills, random);
1202                }
1203
1204                stats.setSkipRefresh(true);
1205                
1206                boolean debug = true;
1207                debug = false;
1208                if (debug) System.out.println("Generating commander skills, person level " + stats.getLevel() + ", skills: " + numSkills);
1209                int picks = 0;
1210                for (String skillId : skills) {
1211                        if (debug) System.out.println("Selected skill: [" + skillId + "]");
1212                        stats.setSkillLevel(skillId, 1);
1213                        picks++;
1214                        if (picks >= numSkills) {
1215                                break;
1216                        }
1217                }
1218                if (debug) System.out.println("Done generating commander skills\n");
1219                
1220                stats.setSkipRefresh(false);
1221                stats.refreshCharacterStatsEffects();
1222        }
1223        
1224        
1225        public static float getMemberWeight(FleetMemberAPI member) {
1226                boolean nonCombat = member.getVariant().isCivilian();
1227                float weight = 0;
1228                switch (member.getVariant().getHullSize()) {
1229                case CAPITAL_SHIP: weight += 8; break;
1230                case CRUISER: weight += 4; break;
1231                case DESTROYER: weight += 2; break;
1232                case FRIGATE: weight += 1; break;
1233                case FIGHTER: weight += 1; break;
1234                }
1235                if (nonCombat) weight *= 0.1f;
1236                return weight;
1237        }
1238
1239        
1240        
1241        
1242        public static MarketAPI pickMarket(FleetParamsV3 params) {
1243                if (params.source != null) return params.source;
1244                if (params.locInHyper == null) return null;
1245                
1246                List<MarketAPI> allMarkets = Global.getSector().getEconomy().getMarketsCopy();
1247                
1248                int size = getMinPreferredMarketSize(params);
1249                float distToClosest = Float.MAX_VALUE;
1250                MarketAPI closest = null;
1251                float distToClosestMatchingSize = Float.MAX_VALUE;
1252                MarketAPI closestMatchingSize = null;
1253                
1254                
1255                FactionAPI creationFaction = Global.getSector().getFaction(params.factionId);
1256                boolean independent = Factions.INDEPENDENT.equals(params.factionId) || 
1257                                                          Factions.SCAVENGERS.equals(params.factionId) ||
1258                                                          creationFaction.getCustomBoolean(Factions.CUSTOM_SPAWNS_AS_INDEPENDENT);
1259                
1260                for (MarketAPI market : allMarkets) {
1261                        if (market.getPrimaryEntity() == null) continue;
1262                        
1263                        if (independent) {
1264                                boolean hostileToIndependent = market.getFaction().isHostileTo(Factions.INDEPENDENT);
1265                                if (hostileToIndependent) continue;
1266                        } else {
1267                                if (!market.getFactionId().equals(params.factionId)) continue;
1268                        }
1269                        
1270                        float currDist = Misc.getDistance(market.getPrimaryEntity().getLocationInHyperspace(),
1271                                                                                          params.locInHyper);
1272                        if (currDist < distToClosest) {
1273                                distToClosest = currDist;
1274                                closest = market;
1275                        }
1276                        
1277                        if (market.getSize() >= size && currDist < distToClosestMatchingSize) {
1278                                distToClosestMatchingSize = currDist;
1279                                closestMatchingSize = market;
1280                        }
1281                }
1282                
1283                if (closestMatchingSize != null) {
1284                        return closestMatchingSize;
1285                }
1286                
1287                if (closest != null) {
1288                        return closest;
1289                }
1290                
1291//              MarketAPI temp = Global.getFactory().createMarket("temp", "Temp", size);
1292//              temp.setFactionId(params.factionId);
1293//              return temp;
1294                return null;
1295        }
1296        
1297        public static int getMinPreferredMarketSize(FleetParamsV3 params) {
1298                float fp = params.getTotalPts();
1299                
1300                if (fp <= 20) return 1;
1301                if (fp <= 50) return 3;
1302                if (fp <= 100) return 5;
1303                if (fp <= 150) return 7;
1304                
1305                return 8;
1306        }
1307        
1308        
1309        
1310        
1311        private static List<String> startingAbilities = null;
1312        public static CampaignFleetAPI createEmptyFleet(String factionId, String fleetType, MarketAPI market) {
1313                FactionAPI faction = Global.getSector().getFaction(factionId);
1314                String fleetName = faction.getFleetTypeName(fleetType); 
1315                CampaignFleetAPI fleet = Global.getFactory().createEmptyFleet(factionId, fleetName, true);
1316                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_TYPE, fleetType);
1317                
1318                if (market != null && !market.getId().equals("fake")) {
1319                        fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_SOURCE_MARKET, market.getId());
1320                }
1321                
1322                if (startingAbilities == null) {
1323                        startingAbilities = new ArrayList<String>();
1324                        for (String id : Global.getSettings().getSortedAbilityIds()) {
1325                                AbilitySpecAPI spec = Global.getSettings().getAbilitySpec(id);
1326                                if (spec.isAIDefault()) {
1327                                        startingAbilities.add(id);
1328                                }
1329                        }
1330                }
1331                
1332                for (String id : startingAbilities) {
1333                        fleet.addAbility(id);
1334                }
1335                
1336                return fleet;
1337        }
1338
1339        public static class FPRemaining {
1340                public int fp;
1341
1342                public FPRemaining(int fp) {
1343                        this.fp = fp;
1344                }
1345                public FPRemaining() {
1346                }
1347        }
1348        
1349        public static float addToFleet(String role, MarketAPI market, Random random, CampaignFleetAPI fleet, int maxFP, FleetParamsV3 params) {
1350                float total = 0f;
1351                List<ShipRolePick> picks = market.pickShipsForRole(role, fleet.getFaction().getId(), 
1352                                        new ShipPickParams(params.mode, maxFP, params.timestamp, params.blockFallback), random, null);
1353                for (ShipRolePick pick : picks) {
1354                        total += addToFleet(pick, fleet, random);
1355                }
1356                return total;
1357        }
1358        
1359        protected static float addToFleet(ShipRolePick pick, CampaignFleetAPI fleet, Random random) {
1360                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, pick.variantId);
1361                String name = fleet.getFleetData().pickShipName(member, random);
1362                member.setShipName(name);
1363                fleet.getFleetData().addFleetMember(member);
1364                return member.getFleetPointCost();
1365        }
1366        
1367//      public static float addCombatFleetPoints(CampaignFleetAPI fleet, Random random, 
1368//                                                                                                      float fp, FleetParamsV3 params) {
1369//              FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine();
1370//              if (params.doctrineOverride != null) {
1371//                      doctrine = params.doctrineOverride;
1372//              }
1373//              
1374//              int size = doctrine.getShipSize();
1375//              
1376//              boolean addedSomething = true;
1377//              FPRemaining rem = new FPRemaining();
1378//              rem.fp = (int) fp;
1379//              
1380//              String smallRole = ShipRoles.COMBAT_SMALL_FOR_SMALL_FLEET;
1381//              if (!params.banPhaseShipsEtc) {
1382//                      smallRole = ShipRoles.COMBAT_SMALL;
1383//              }
1384//              
1385//              while (addedSomething && rem.fp > 0) {
1386//                      int small = BASE_COUNTS_WITH_4[size - 1][0] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][0] + 1); 
1387//                      int medium = BASE_COUNTS_WITH_4[size - 1][1] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][1] + 1); 
1388//                      int large = BASE_COUNTS_WITH_4[size - 1][2] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][2] + 1); 
1389//                      int capital = BASE_COUNTS_WITH_4[size - 1][3] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][3] + 1); 
1390//                      
1391////                    System.out.println(String.format("Small: %s Medium: %s Large: %s Capital: %s",
1392////                                    "" + small, "" + medium, "" + large, "" + capital));
1393//                      
1394//                      if (params.maxShipSize <= 1) medium = 0;
1395//                      if (params.maxShipSize <= 2) large = 0;
1396//                      if (params.maxShipSize <= 3) capital = 0;
1397//                      
1398//                      int smallPre = small / 2;
1399//                      small -= smallPre;
1400//                      
1401//                      int mediumPre = medium / 2;
1402//                      medium -= mediumPre;
1403//                      
1404//                      addedSomething = false;
1405//                      
1406//                      addedSomething |= addShips(smallRole, smallPre, params.source, random, fleet, rem, params);
1407//                      
1408//                      addedSomething |= addShips(ShipRoles.COMBAT_MEDIUM, mediumPre, params.source, random, fleet, rem, params);
1409//                      addedSomething |= addShips(smallRole, small, params.source, random, fleet, rem, params);
1410//                      
1411//                      addedSomething |= addShips(ShipRoles.COMBAT_LARGE, large, params.source, random, fleet, rem, params);
1412//                      addedSomething |= addShips(ShipRoles.COMBAT_MEDIUM, medium, params.source, random, fleet, rem, params);
1413//                      
1414//                      addedSomething |= addShips(ShipRoles.COMBAT_CAPITAL, capital, params.source, random, fleet, rem, params);
1415//              }
1416//              
1417//              return fp - rem.fp;
1418//      }
1419        
1420        public static boolean addShips(String role, int count, MarketAPI market, Random random, CampaignFleetAPI fleet, FPRemaining rem, FleetParamsV3 params) {
1421                boolean addedSomething = false;
1422                for (int i = 0; i < count; i++) {
1423                        if (rem.fp <= 0) break;
1424                        float added = addToFleet(role, market, random, fleet, rem.fp, params);
1425                        if (added > 0) {
1426                                rem.fp -= added;
1427                                addedSomething = true;
1428                        }
1429                }
1430                return addedSomething;
1431        }
1432
1433        
1434        public static float addPhaseFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1435                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.SMALL_IS_FRIGATE,
1436                                                        ShipRoles.PHASE_SMALL, ShipRoles.PHASE_MEDIUM, ShipRoles.PHASE_LARGE);
1437//              FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine();
1438//              if (params.doctrineOverride != null) {
1439//                      doctrine = params.doctrineOverride;
1440//              }
1441//
1442//              int size = doctrine.getShipSize();
1443//
1444//              boolean addedSomething = true;
1445//              FPRemaining rem = new FPRemaining();
1446//              rem.fp = (int) fp;
1447//
1448//              while (addedSomething && rem.fp > 0) {
1449//                      int small = BASE_COUNTS_WITH_3[size - 1][0] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][0] + 1); 
1450//                      int medium = BASE_COUNTS_WITH_3[size - 1][1] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][1] + 1); 
1451//                      int large = BASE_COUNTS_WITH_3[size - 1][2] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][2] + 1); 
1452//
1453//                      //System.out.println(String.format("Small: %s Medium: %s Large: %s Capital: %s",
1454//                      //"" + small, "" + medium, "" + large, "" + capital));
1455//
1456//                      if (params.maxShipSize <= 1) medium = 0;
1457//                      if (params.maxShipSize <= 2) large = 0;
1458//
1459//                      int smallPre = small / 2;
1460//                      small -= smallPre;
1461//
1462//                      int mediumPre = medium / 2;
1463//                      medium -= mediumPre;
1464//
1465//                      addedSomething = false;
1466//
1467//                      addedSomething |= addShips(ShipRoles.PHASE_SMALL, smallPre, params.source, random, fleet, rem, params);
1468//
1469//                      addedSomething |= addShips(ShipRoles.PHASE_MEDIUM, mediumPre, params.source, random, fleet, rem, params);
1470//                      addedSomething |= addShips(ShipRoles.PHASE_SMALL, small, params.source, random, fleet, rem, params);
1471//
1472//                      addedSomething |= addShips(ShipRoles.PHASE_LARGE, large, params.source, random, fleet, rem, params);
1473//                      addedSomething |= addShips(ShipRoles.PHASE_MEDIUM, medium, params.source, random, fleet, rem, params);
1474//              }
1475//
1476//              return fp - rem.fp;
1477        }
1478        
1479        public static enum SizeFilterMode {
1480                NONE,
1481                SMALL_IS_FRIGATE,
1482                SMALL_IS_DESTROYER,
1483        }
1484        public static float addCarrierFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1485                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.SMALL_IS_DESTROYER,
1486                                                ShipRoles.CARRIER_SMALL, ShipRoles.CARRIER_MEDIUM, ShipRoles.CARRIER_LARGE);
1487        }
1488        public static float addPriorityOnlyThenAll(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params,
1489                                                                                                SizeFilterMode sizeFilterMode,
1490                                                                                                String roleSmall, String roleMedium, String roleLarge) {
1491                if (fp <= 0) return 0f;
1492                
1493                float added = 0f;
1494                if (params.mode == ShipPickMode.PRIORITY_THEN_ALL) {
1495                        int numPriority = fleet.getFaction().getNumAvailableForRole(roleSmall, ShipPickMode.PRIORITY_ONLY) +
1496                                                      fleet.getFaction().getNumAvailableForRole(roleMedium, ShipPickMode.PRIORITY_ONLY) + 
1497                                                      fleet.getFaction().getNumAvailableForRole(roleLarge, ShipPickMode.PRIORITY_ONLY);
1498                
1499                        if (numPriority > 0) {
1500                                params.mode = ShipPickMode.PRIORITY_ONLY;
1501                                added = addFleetPoints(fleet, random, fp, params, sizeFilterMode,
1502                                                roleSmall, roleMedium, roleLarge);
1503                                params.mode = ShipPickMode.PRIORITY_THEN_ALL;
1504                        } else {
1505                                params.mode = ShipPickMode.ALL;
1506                                added = addFleetPoints(fleet, random, fp, params, sizeFilterMode,
1507                                                roleSmall, roleMedium, roleLarge);
1508                                params.mode = ShipPickMode.PRIORITY_THEN_ALL;
1509                        }
1510                        // if there ARE priority ships for a 3-type category (i.e. carriers/phases/various civs,
1511                        // then ONLY use priority, and use nothing if a priority ship was not added (since that just means not enough FP 
1512                        // for likely a smaller fleet.)
1513//                      if (added <= 0) {
1514//                              added = addFleetPoints(fleet, random, fp, params, sizeFilterMode,
1515//                                              roleSmall, roleMedium, roleLarge);
1516//                      }
1517                } else {                
1518                        added = addFleetPoints(fleet, random, fp, params, sizeFilterMode,
1519                                        roleSmall, roleMedium, roleLarge);
1520                }
1521                return added;
1522        }
1523        
1524        public static float addTankerFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1525                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.SMALL_IS_DESTROYER,
1526                                ShipRoles.TANKER_SMALL, ShipRoles.TANKER_MEDIUM, ShipRoles.TANKER_LARGE);
1527        }
1528        
1529        public static float addFreighterFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1530                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.NONE, 
1531                                ShipRoles.FREIGHTER_SMALL, ShipRoles.FREIGHTER_MEDIUM, ShipRoles.FREIGHTER_LARGE);
1532        }
1533        
1534        public static float addLinerFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1535                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.NONE, 
1536                                ShipRoles.LINER_SMALL, ShipRoles.LINER_MEDIUM, ShipRoles.LINER_LARGE);
1537        }
1538        
1539        public static float addCombatFreighterFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1540                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.SMALL_IS_FRIGATE, 
1541                                ShipRoles.COMBAT_FREIGHTER_SMALL, ShipRoles.COMBAT_FREIGHTER_MEDIUM, ShipRoles.COMBAT_FREIGHTER_LARGE);
1542        }
1543        
1544        public static float addTransportFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1545                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.NONE,
1546                                ShipRoles.PERSONNEL_SMALL, ShipRoles.PERSONNEL_MEDIUM, ShipRoles.PERSONNEL_LARGE);
1547        }
1548        
1549        public static float addUtilityFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params) {
1550                return addPriorityOnlyThenAll(fleet, random, fp, params, SizeFilterMode.NONE,
1551                                ShipRoles.UTILITY, ShipRoles.UTILITY, ShipRoles.UTILITY);
1552        }
1553
1554        
1555        protected static int sizeOverride = 0;
1556        // tend towards larger ships as fleets get more members, regardless of doctrine
1557        public static int getAdjustedDoctrineSize(int size, CampaignFleetAPI fleetSoFar) {
1558                if (sizeOverride > 0) return sizeOverride;
1559                else return size;
1560                
1561//              int num = fleetSoFar.getNumMembersFast();
1562//              if (num > 8 && size <= 2) {
1563//                      size++;
1564//              }
1565//              if (num > 14 && size <= 3) {
1566//                      size++;
1567//              }
1568//              if (num > 20 && size <= 4) {
1569//                      size++;
1570//              }
1571//              if (size > 5) size = 5;
1572//              return size;
1573        }
1574
1575        
1576        public static float addFleetPoints(CampaignFleetAPI fleet, Random random, float fp, FleetParamsV3 params,
1577                                                                           SizeFilterMode sizeFilterMode,
1578                                                                                String ... roles) {
1579                FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine();
1580                if (params.doctrineOverride != null) {
1581                        doctrine = params.doctrineOverride;
1582                }
1583
1584                int size = doctrine.getShipSize();
1585                //size = getAdjustedDoctrineSize(size, fleet);
1586
1587                boolean addedSomething = true;
1588                FPRemaining rem = new FPRemaining();
1589                rem.fp = (int) fp;
1590
1591                while (addedSomething && rem.fp > 0) {
1592                        size = getAdjustedDoctrineSize(size, fleet);
1593                        
1594                        int small = BASE_COUNTS_WITH_3[size - 1][0] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][0] + 1); 
1595                        int medium = BASE_COUNTS_WITH_3[size - 1][1] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][1] + 1); 
1596                        int large = BASE_COUNTS_WITH_3[size - 1][2] + random.nextInt(MAX_EXTRA_WITH_3[size - 1][2] + 1); 
1597
1598//                      if (sizeOverride > 0) {
1599//                              small = 0;
1600//                              medium = 0;
1601//                      }
1602                        
1603                        if (sizeFilterMode == SizeFilterMode.SMALL_IS_FRIGATE) {
1604                                if (params.maxShipSize <= 1) medium = 0;
1605                                if (params.maxShipSize <= 2) large = 0;
1606                        } else if (sizeFilterMode == SizeFilterMode.SMALL_IS_DESTROYER) {
1607                                if (params.maxShipSize <= 2) medium = 0;
1608                                if (params.maxShipSize <= 3) large = 0;
1609                        }
1610                        
1611                        //System.out.println(String.format("Small: %s Medium: %s Large: %s Capital: %s",
1612                        //"" + small, "" + medium, "" + large, "" + capital));
1613
1614                        int smallPre = small / 2;
1615                        small -= smallPre;
1616
1617                        int mediumPre = medium / 2;
1618                        medium -= mediumPre;
1619
1620                        addedSomething = false;
1621
1622                        addedSomething |= addShips(roles[0], smallPre, params.source, random, fleet, rem, params);
1623
1624                        addedSomething |= addShips(roles[1], mediumPre, params.source, random, fleet, rem, params);
1625                        addedSomething |= addShips(roles[0], small, params.source, random, fleet, rem, params);
1626
1627                        addedSomething |= addShips(roles[2], large, params.source, random, fleet, rem, params);
1628                        addedSomething |= addShips(roles[1], medium, params.source, random, fleet, rem, params);
1629                }
1630
1631                return fp - rem.fp;
1632        }
1633        
1634        
1635        
1636        
1637        
1638        public static void addCombatFleetPoints(CampaignFleetAPI fleet, Random random,
1639                        float warshipFP, float carrierFP, float phaseFP, FleetParamsV3 params) {
1640                
1641                FactionAPI faction = fleet.getFaction();
1642                FactionDoctrineAPI doctrine = faction.getDoctrine();
1643                if (params.doctrineOverride != null) {
1644                        doctrine = params.doctrineOverride;
1645                }
1646
1647                WeightedRandomPicker<String> smallPicker = new WeightedRandomPicker<String>(random);
1648                WeightedRandomPicker<String> mediumPicker = new WeightedRandomPicker<String>(random);
1649                WeightedRandomPicker<String> largePicker = new WeightedRandomPicker<String>(random);
1650                WeightedRandomPicker<String> capitalPicker = new WeightedRandomPicker<String>(random);
1651                WeightedRandomPicker<String> priorityCapitalPicker = new WeightedRandomPicker<String>(random);
1652                
1653                String smallRole = ShipRoles.COMBAT_SMALL_FOR_SMALL_FLEET;
1654                if (!params.banPhaseShipsEtc) {
1655                        smallRole = ShipRoles.COMBAT_SMALL;
1656                }
1657                
1658//              if (warshipFP > 0) smallPicker.add(smallRole, 1);
1659//              if (phaseFP > 0) smallPicker.add(ShipRoles.PHASE_SMALL, 1);
1660//              
1661//              if (warshipFP > 0) mediumPicker.add(ShipRoles.COMBAT_MEDIUM, 1);
1662//              if (phaseFP > 0) mediumPicker.add(ShipRoles.PHASE_MEDIUM, 1);
1663//              if (carrierFP > 0) mediumPicker.add(ShipRoles.CARRIER_SMALL, 1);
1664//              
1665//              if (warshipFP > 0) largePicker.add(ShipRoles.COMBAT_LARGE, 1);
1666//              if (phaseFP > 0) largePicker.add(ShipRoles.PHASE_LARGE, 1);
1667//              if (carrierFP > 0) largePicker.add(ShipRoles.CARRIER_MEDIUM, 1);
1668//              
1669//              if (warshipFP > 0) capitalPicker.add(ShipRoles.COMBAT_CAPITAL, 1);
1670//              if (phaseFP > 0) capitalPicker.add(ShipRoles.PHASE_CAPITAL, 1);
1671//              if (carrierFP > 0) capitalPicker.add(ShipRoles.CARRIER_LARGE, 1);
1672                
1673                smallPicker.add(smallRole, warshipFP);
1674                smallPicker.add(ShipRoles.PHASE_SMALL, phaseFP);
1675                
1676                mediumPicker.add(ShipRoles.COMBAT_MEDIUM, warshipFP);
1677                mediumPicker.add(ShipRoles.PHASE_MEDIUM, phaseFP);
1678                mediumPicker.add(ShipRoles.CARRIER_SMALL, carrierFP);
1679                
1680                largePicker.add(ShipRoles.COMBAT_LARGE, warshipFP);
1681                largePicker.add(ShipRoles.PHASE_LARGE, phaseFP);
1682                largePicker.add(ShipRoles.CARRIER_MEDIUM, carrierFP);
1683                
1684                capitalPicker.add(ShipRoles.COMBAT_CAPITAL, warshipFP);
1685                capitalPicker.add(ShipRoles.PHASE_CAPITAL, phaseFP);
1686                capitalPicker.add(ShipRoles.CARRIER_LARGE, carrierFP);
1687                
1688                
1689                Set<String> usePriorityOnly = new HashSet<String>();
1690                
1691                if (params.mode == ShipPickMode.PRIORITY_THEN_ALL) {
1692                        float num = faction.getVariantWeightForRole(ShipRoles.COMBAT_CAPITAL, ShipPickMode.PRIORITY_ONLY);
1693                        if (num > 0) {
1694                                //priorityCapitalPicker.add(ShipRoles.COMBAT_CAPITAL, doctrine.getWarships() + 1);
1695                                priorityCapitalPicker.add(ShipRoles.COMBAT_CAPITAL, num);
1696                        }
1697                        num = faction.getVariantWeightForRole(ShipRoles.CARRIER_LARGE, ShipPickMode.PRIORITY_ONLY);
1698                        if (num > 0) {
1699                                //priorityCapitalPicker.add(ShipRoles.CARRIER_LARGE, doctrine.getCarriers() + 1);
1700                                priorityCapitalPicker.add(ShipRoles.CARRIER_LARGE, num);
1701                        }
1702                        num = faction.getVariantWeightForRole(ShipRoles.PHASE_CAPITAL, ShipPickMode.PRIORITY_ONLY);
1703                        if (num > 0) {
1704                                //priorityCapitalPicker.add(ShipRoles.PHASE_CAPITAL, doctrine.getPhaseShips() + 1);
1705                                priorityCapitalPicker.add(ShipRoles.PHASE_CAPITAL, num);
1706                        }
1707                        
1708                        if (params.mode == ShipPickMode.PRIORITY_THEN_ALL) {
1709                                addToPriorityOnlySet(fleet, usePriorityOnly, ShipRoles.PHASE_SMALL, ShipRoles.PHASE_MEDIUM, ShipRoles.PHASE_LARGE);
1710                                addToPriorityOnlySet(fleet, usePriorityOnly, ShipRoles.CARRIER_SMALL, ShipRoles.CARRIER_MEDIUM, ShipRoles.CARRIER_LARGE);
1711                        }
1712                }
1713                
1714                Map<String, FPRemaining> remaining = new HashMap<String, FPRemaining>();
1715                FPRemaining remWarship = new FPRemaining((int)warshipFP);
1716                FPRemaining remCarrier = new FPRemaining((int)carrierFP);
1717                FPRemaining remPhase = new FPRemaining((int)phaseFP);
1718                
1719                remaining.put(ShipRoles.COMBAT_SMALL_FOR_SMALL_FLEET, remWarship);
1720                remaining.put(ShipRoles.COMBAT_SMALL, remWarship);
1721                remaining.put(ShipRoles.COMBAT_MEDIUM, remWarship);
1722                remaining.put(ShipRoles.COMBAT_LARGE, remWarship);
1723                remaining.put(ShipRoles.COMBAT_CAPITAL, remWarship);
1724                
1725                remaining.put(ShipRoles.CARRIER_SMALL, remCarrier);
1726                remaining.put(ShipRoles.CARRIER_MEDIUM, remCarrier);
1727                remaining.put(ShipRoles.CARRIER_LARGE, remCarrier);
1728                
1729                remaining.put(ShipRoles.PHASE_SMALL, remPhase);
1730                remaining.put(ShipRoles.PHASE_MEDIUM, remPhase);
1731                remaining.put(ShipRoles.PHASE_LARGE, remPhase);
1732                remaining.put(ShipRoles.PHASE_CAPITAL, remPhase);
1733                
1734                
1735                if (params.maxShipSize <= 1) {
1736                        mediumPicker.clear();
1737                }
1738                if (params.maxShipSize <= 2) {
1739                        largePicker.clear();
1740                }
1741                if (params.maxShipSize <= 3) {
1742                        capitalPicker.clear();
1743                }
1744                
1745                if (params.minShipSize >= 2) {
1746                        smallPicker.clear();
1747                }
1748                if (params.minShipSize >= 3) {
1749                        mediumPicker.clear();
1750                }
1751                if (params.minShipSize >= 4) {
1752                        largePicker.clear();
1753                }
1754                
1755                
1756                int size = doctrine.getShipSize();
1757                //size = getAdjustedDoctrineSize(size, fleet);
1758
1759                int numFails = 0;
1760                while (numFails < 2) {
1761                        size = getAdjustedDoctrineSize(size, fleet);
1762                        
1763//                      if (size > 5) {
1764//                              System.out.println("wefwefe");
1765//                      }
1766                        
1767                        int small = BASE_COUNTS_WITH_4[size - 1][0] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][0] + 1); 
1768                        int medium = BASE_COUNTS_WITH_4[size - 1][1] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][1] + 1); 
1769                        int large = BASE_COUNTS_WITH_4[size - 1][2] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][2] + 1); 
1770                        int capital = BASE_COUNTS_WITH_4[size - 1][3] + random.nextInt(MAX_EXTRA_WITH_4[size - 1][3] + 1); 
1771
1772                        if (size < 5 && capital > 1) {
1773                                capital = 1;
1774                        }
1775                        
1776                        if (params.maxShipSize <= 1) medium = 0;
1777                        if (params.maxShipSize <= 2) large = 0;
1778                        if (params.maxShipSize <= 3) capital = 0;
1779                        
1780                        if (params.minShipSize >= 2) small = 0;
1781                        if (params.minShipSize >= 3) medium = 0;
1782                        if (params.minShipSize >= 4) large = 0;
1783
1784                        int smallPre = small / 2;
1785                        small -= smallPre;
1786
1787                        int mediumPre = medium / 2;
1788                        medium -= mediumPre;
1789
1790                        boolean addedSomething = false;
1791
1792                        //System.out.println("Rem carrier pre: " + remCarrier.fp);
1793                        addedSomething |= addShips(smallPicker, usePriorityOnly, remaining, null, smallPre, fleet, random, params);
1794                        //System.out.println("Rem carrier after smallPre: " + remCarrier.fp);
1795                        addedSomething |= addShips(mediumPicker, usePriorityOnly, remaining, null, mediumPre, fleet, random, params);
1796                        //System.out.println("Rem carrier after mediumPre: " + remCarrier.fp);
1797                        addedSomething |= addShips(smallPicker, usePriorityOnly, remaining, null, small, fleet, random, params);
1798                        //System.out.println("Rem carrier after small: " + remCarrier.fp);
1799                        addedSomething |= addShips(largePicker, usePriorityOnly, remaining, null, large, fleet, random, params);
1800                        //System.out.println("Rem carrier after large: " + remCarrier.fp);
1801                        addedSomething |= addShips(mediumPicker, usePriorityOnly, remaining, null, medium, fleet, random, params);
1802                        //System.out.println("Rem carrier after medium: " + remCarrier.fp);
1803                        
1804                        
1805                        if (!priorityCapitalPicker.isEmpty()) {
1806                                params.mode = ShipPickMode.PRIORITY_ONLY;
1807                                params.blockFallback = true;
1808                                FPRemaining combined = new FPRemaining(remWarship.fp + remCarrier.fp + remPhase.fp);
1809                                boolean addedCapital = addShips(priorityCapitalPicker, usePriorityOnly, remaining, combined, capital, fleet, random, params);
1810                                addedSomething |= addedCapital;
1811                                if (addedCapital) {
1812                                        redistributeFP(remWarship, remCarrier, remPhase, combined.fp);
1813                                }
1814                                params.mode = ShipPickMode.PRIORITY_THEN_ALL;
1815                                params.blockFallback = null;
1816                                //System.out.println("Rem carrier after capitals priority: " + remCarrier.fp);
1817                        } else {
1818                                addedSomething |= addShips(capitalPicker, usePriorityOnly, remaining, null, capital, fleet, random, params);
1819                                //System.out.println("Rem carrier after capitals normal: " + remCarrier.fp);
1820                        }
1821                        
1822                        if (!addedSomething) {
1823                                numFails++;
1824                                
1825                                if (numFails == 2) {
1826                                        boolean goAgain = false;
1827                                        if (remPhase.fp > 0) {
1828                                                remWarship.fp += remPhase.fp;
1829                                                remPhase.fp = 0;
1830                                                goAgain = true;
1831                                        }
1832                                        if (remCarrier.fp > 0) {
1833                                                remWarship.fp += remCarrier.fp;
1834                                                remCarrier.fp = 0;
1835                                                goAgain = true;
1836                                        }
1837                                        
1838                                        if (goAgain) {
1839                                                numFails = 0;
1840                                                smallPicker.add(smallRole, 1);
1841                                                mediumPicker.add(ShipRoles.COMBAT_MEDIUM, 1);
1842                                                largePicker.add(ShipRoles.COMBAT_LARGE, 1);
1843                                                capitalPicker.add(ShipRoles.COMBAT_CAPITAL, 1); 
1844                                        }
1845                                }
1846                        }
1847                }
1848        }
1849                        
1850        protected static void addToPriorityOnlySet(CampaignFleetAPI fleet, Set<String> set, String small, String medium, String large) {
1851                int numPriority = fleet.getFaction().getNumAvailableForRole(small, ShipPickMode.PRIORITY_ONLY) +
1852                                          fleet.getFaction().getNumAvailableForRole(medium, ShipPickMode.PRIORITY_ONLY) + 
1853                                          fleet.getFaction().getNumAvailableForRole(large, ShipPickMode.PRIORITY_ONLY);
1854                if (numPriority > 0) {
1855                        set.add(small);
1856                        set.add(medium);
1857                        set.add(large);
1858                }
1859        }
1860        
1861        protected static void redistributeFP(FPRemaining one, FPRemaining two, FPRemaining three, int newTotal) {
1862                float total = one.fp + two.fp + three.fp;
1863                if (total <= 0) return;
1864                
1865                int f1 = (int) Math.round((float)one.fp / total * newTotal);
1866                int f2 = (int) Math.round((float)two.fp / total * newTotal);
1867                int f3 = (int) Math.round((float)three.fp / total * newTotal);
1868                
1869                f1 += newTotal - f1 - f2 - f3;
1870                
1871                one.fp = f1;
1872                two.fp = f2;
1873                three.fp = f3;
1874        }
1875        
1876        public static boolean addShips(WeightedRandomPicker<String> rolePicker, Set<String> usePriorityOnly, Map<String, FPRemaining> remaining, FPRemaining remOverride, int count,
1877                                                                   CampaignFleetAPI fleet, Random random, FleetParamsV3 params) {
1878                if (rolePicker.isEmpty()) return false;
1879                
1880                boolean addedSomething = false;
1881                for (int i = 0; i < count; i++) {
1882                        String role = rolePicker.pick();
1883                        if (role == null) break;
1884                        FPRemaining rem = remaining.get(role);
1885                        FPRemaining remForProperRole = rem;
1886                        if (remOverride != null) rem = remOverride;
1887                        if (usePriorityOnly.contains(role)) {
1888                                params.mode = ShipPickMode.PRIORITY_ONLY;
1889                        }
1890                        int fpPrePick = rem.fp;
1891                        
1892                        boolean added = addShips(role, 1, params.source, random, fleet, rem, params);
1893                        
1894                        if (added && remOverride != null) {
1895                                int fpSpent = fpPrePick - rem.fp;
1896                                int maxToTakeFromProperRole = Math.min(remForProperRole.fp, fpSpent);
1897                                remForProperRole.fp -= maxToTakeFromProperRole;
1898                        }
1899                        
1900                        if (usePriorityOnly.contains(role)) {
1901                                params.mode = ShipPickMode.PRIORITY_THEN_ALL;
1902                        }
1903                        if (!added) {
1904                                rolePicker.remove(role);
1905                                i--;
1906                                if (rolePicker.isEmpty()) {
1907                                        break;
1908                                }
1909                        }
1910                        addedSomething |= added;
1911                }
1912                return addedSomething;
1913        }
1914        
1915        public static float getShipDeficitFleetSizeMult(MarketAPI market) {
1916                float mult = 1f;
1917                CommodityOnMarketAPI com = market.getCommodityData(Commodities.SHIPS);
1918                float available = com.getAvailable();
1919                float demand = com.getMaxDemand();
1920                if (demand > 0) {
1921                        float f = available / demand;
1922                        if (f < MIN_NUM_SHIPS_DEFICIT_MULT) f = MIN_NUM_SHIPS_DEFICIT_MULT;
1923                        mult *= f;
1924                }
1925                if (mult < 0) mult = 0;
1926                if (mult > 1) mult = 1;
1927                return mult;
1928        }
1929        
1930        
1931        
1932        public static void addCommanderSkills(PersonAPI commander, CampaignFleetAPI fleet, Random random) {
1933                addCommanderSkills(commander, fleet, null, random);
1934        }
1935        
1936        
1937        public static void applyDamageToFleet(CampaignFleetAPI fleet, float damage, 
1938                                                                                  boolean damageRemainingShips, Random random) {
1939                if (random == null) random = Misc.random;
1940                WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>();
1941                
1942                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
1943                for (FleetMemberAPI member : members) {
1944                        float w = 1f;
1945                        if (member.isCivilian()) w *= 0.25f;
1946                        
1947                        picker.add(member, w);
1948                }
1949                
1950                List<FleetMemberAPI> remove = new ArrayList<FleetMemberAPI>();
1951                float removedFP = 0f;
1952                float fpToRemove = fleet.getFleetPoints() * damage * 0.8f;
1953                
1954                while (removedFP < fpToRemove && remove.size() < members.size() - 1 && !picker.isEmpty()) {
1955                        FleetMemberAPI member = picker.pickAndRemove();
1956                        removedFP += member.getFleetPointCost();
1957                        remove.add(member);
1958                }
1959                
1960                for (FleetMemberAPI member : remove) {
1961                        fleet.getFleetData().removeFleetMember(member);
1962                }
1963                
1964                
1965                if (damageRemainingShips) {
1966                        int numStrikes = (int) Math.round(picker.getItems().size() * damage);
1967                        
1968                        for (int i = 0; i < numStrikes; i++) {
1969                                FleetMemberAPI member = picker.pick();
1970                                if (member == null) return;
1971                                
1972                                if (random.nextFloat() > damage) continue;
1973                                
1974                                float crPerDep = member.getDeployCost();
1975                                //if (crPerDep <= 0) continue;                  
1976                                float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue();
1977                                if (suppliesPerDep <= 0 || crPerDep <= 0) return;
1978                                float suppliesPer100CR = suppliesPerDep * 1f / Math.max(0.01f, crPerDep);
1979        
1980                                float strikeSupplies = suppliesPer100CR * damage * (0.25f + 0.75f * random.nextFloat());  
1981                                float strikeDamage = strikeSupplies / suppliesPer100CR * (0.75f + (float) Math.random() * 0.5f);
1982                                
1983                                if (strikeDamage > HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE) {
1984                                        strikeDamage = HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE;
1985                                }
1986                                
1987                                if (strikeDamage > 0) {
1988                                        float currCR = member.getRepairTracker().getBaseCR();
1989                                        float crDamage = Math.min(currCR, strikeDamage);
1990                                        
1991                                        member.getRepairTracker().setCR(currCR - crDamage);
1992                                        
1993                                        float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating());
1994                                        int numHits = (int) (strikeDamage / 0.1f);
1995                                        if (numHits < 1) numHits = 1;
1996                                        for (int j = 0; j < numHits; j++) {
1997                                                member.getStatus().applyDamage(hitStrength);
1998                                        }
1999                                        
2000                                        if (member.getStatus().getHullFraction() < 0.01f) {
2001                                                member.getStatus().setHullFraction(0.01f);
2002                                                picker.remove(member);
2003                                        } else {
2004                                                float w = picker.getWeight(member);
2005                                                picker.setWeight(picker.getItems().indexOf(member), w * 0.5f);
2006                                        }
2007                                }
2008                        }
2009                }
2010        }
2011}
2012
2013
2014
2015
2016
2017
2018
2019
2020