001package com.fs.starfarer.api.impl.campaign.intel;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Random;
007import java.util.Set;
008
009import org.apache.log4j.Logger;
010
011import com.fs.starfarer.api.EveryFrameScript;
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.BattleAPI;
014import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
015import com.fs.starfarer.api.campaign.CampaignFleetAPI;
016import com.fs.starfarer.api.campaign.FactionAPI;
017import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
018import com.fs.starfarer.api.campaign.FleetAssignment;
019import com.fs.starfarer.api.campaign.LocationAPI;
020import com.fs.starfarer.api.campaign.PlanetAPI;
021import com.fs.starfarer.api.campaign.RepLevel;
022import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
023import com.fs.starfarer.api.campaign.SectorEntityToken;
024import com.fs.starfarer.api.campaign.StarSystemAPI;
025import com.fs.starfarer.api.campaign.econ.MarketAPI;
026import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
027import com.fs.starfarer.api.characters.FullName.Gender;
028import com.fs.starfarer.api.characters.PersonAPI;
029import com.fs.starfarer.api.fleet.FleetMemberAPI;
030import com.fs.starfarer.api.fleet.FleetMemberType;
031import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin;
032import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
033import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
034import com.fs.starfarer.api.impl.campaign.DebugFlags;
035import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent;
036import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3;
037import com.fs.starfarer.api.impl.campaign.fleets.FleetParamsV3;
038import com.fs.starfarer.api.impl.campaign.ids.Factions;
039import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
040import com.fs.starfarer.api.impl.campaign.ids.Industries;
041import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
042import com.fs.starfarer.api.impl.campaign.ids.Ranks;
043import com.fs.starfarer.api.impl.campaign.ids.Tags;
044import com.fs.starfarer.api.impl.campaign.intel.bases.PirateBaseManager;
045import com.fs.starfarer.api.impl.campaign.procgen.Constellation;
046import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial;
047import com.fs.starfarer.api.impl.campaign.shared.PersonBountyEventData;
048import com.fs.starfarer.api.impl.campaign.shared.SharedData;
049import com.fs.starfarer.api.ui.SectorMapAPI;
050import com.fs.starfarer.api.ui.TooltipMakerAPI;
051import com.fs.starfarer.api.util.Misc;
052import com.fs.starfarer.api.util.WeightedRandomPicker;
053
054public class PersonBountyIntel extends BaseIntelPlugin implements EveryFrameScript, FleetEventListener {
055        public static Logger log = Global.getLogger(PersonBountyIntel.class);
056        
057        public static enum BountyType {
058                PIRATE,
059                DESERTER,
060        }
061        
062        public static float MAX_DURATION = 90f;
063        
064        //public static int FAST_START_LEVEL_INCREASE = 1;
065        public static float MAX_TIME_BASED_ADDED_LEVEL = 3;
066        
067        private float elapsedDays = 0f;
068        private float duration = MAX_DURATION;
069        private float bountyCredits = 0;
070        
071        private FactionAPI faction;
072        private PersonAPI person;
073        private CampaignFleetAPI fleet;
074        private FleetMemberAPI flagship;
075        
076        private BountyType bountyType;
077        //private FleetType fleetType;
078        
079        private SectorEntityToken hideoutLocation = null;
080        
081        private int level = 0;
082        
083        public float getElapsedDays() {
084                return elapsedDays;
085        }
086
087        public void setElapsedDays(float elapsedDays) {
088                this.elapsedDays = elapsedDays;
089        }
090
091        public static PersonBountyEventData getSharedData() {
092                return SharedData.getData().getPersonBountyEventData();
093        }
094        
095        public PersonBountyIntel() {
096                pickLevel();
097                
098                pickFaction();
099                if (isDone()) return;
100                
101                initBountyAmount();
102                
103                pickHideoutLocation();
104                if (isDone()) return;
105                
106                pickBountyType();
107                if (bountyType == BountyType.DESERTER) {
108                        bountyCredits *= 1.5f;
109                }
110                
111                initPerson();
112                if (isDone()) return;
113                
114                spawnFleet();
115                if (isDone()) return;
116                
117                log.info(String.format("Starting person bounty by faction [%s] for person %s", faction.getDisplayName(), person.getName().getFullName()));
118                
119                Global.getSector().getIntelManager().queueIntel(this);
120        }
121        
122        public void reportMadeVisibleToPlayer() {
123                if (!isEnding() && !isEnded()) {
124                        duration = Math.max(duration * 0.5f, Math.min(duration * 2f, MAX_DURATION));
125                }
126        }
127        
128        protected void pickLevel() {
129//              if (true) {
130//                      level = 10;
131//                      //level = Misc.random.nextInt(11);
132//                      return;
133//              }
134                
135                int base = getSharedData().getLevel();
136
137//              if (Misc.isFastStart()) {
138                        float timeFactor = (PirateBaseManager.getInstance().getDaysSinceStart() - 180f) / (365f * 2f);
139                        if (timeFactor < 0) timeFactor = 0;
140                        if (timeFactor > 1) timeFactor = 1;
141                        
142                        int add = (int) Math.round(MAX_TIME_BASED_ADDED_LEVEL * timeFactor);
143                        base += add;
144                        //base += FAST_START_LEVEL_INCREASE;
145//              }
146                
147//              if (Global.getSector().getPlayerFleet() != null) {
148//                      int playerLevel = Global.getSector().getPlayerFleet().getCommander().getStats().getLevel();
149//                      base = Math.max(base, (playerLevel - 20) / 5);
150//              }
151                
152                if (base > 10) base = 10;
153                
154                boolean hasLow = false;
155                boolean hasHigh = false;
156                for (EveryFrameScript s : PersonBountyManager.getInstance().getActive()) {
157                        PersonBountyIntel bounty = (PersonBountyIntel) s;
158                        
159                        int curr = bounty.getLevel();
160                        
161                        if (curr < base || curr == 0) hasLow = true;
162                        if (curr > base) hasHigh = true;
163                }
164                
165                level = base;
166                if (!hasLow) {
167                        //level -= new Random().nextInt(2) + 1;
168                        level = 0;
169                } else if (!hasHigh) {
170                        level += new Random().nextInt(3) + 2;
171                }
172                
173                if (level < 0) level = 0;
174        }
175        
176        public int getLevel() {
177                return level;
178        }
179
180        protected void pickHideoutLocation() {
181                WeightedRandomPicker<StarSystemAPI> systemPicker = new WeightedRandomPicker<StarSystemAPI>();
182                for (StarSystemAPI system : Global.getSector().getStarSystems()) {
183                        float mult = 0f;
184                        
185                        if (system.hasPulsar()) continue;
186                        
187                        if (system.hasTag(Tags.THEME_MISC_SKIP)) {
188                                mult = 1f;
189                        } else if (system.hasTag(Tags.THEME_MISC)) {
190                                mult = 3f;
191                        } else if (system.hasTag(Tags.THEME_REMNANT_NO_FLEETS)) {
192                                mult = 3f;
193                        } else if (system.hasTag(Tags.THEME_RUINS)) {
194                                mult = 5f;
195                        } else if (system.hasTag(Tags.THEME_REMNANT_DESTROYED)) {
196                                mult = 3f;
197                        } else if (system.hasTag(Tags.THEME_CORE_UNPOPULATED)) {
198                                mult = 1f;
199                        }
200                        
201                        for (MarketAPI market : Misc.getMarketsInLocation(system)) {
202                                if (market.isHidden()) continue;
203                                mult = 0f;
204                                break;
205                        }
206                        
207                        float distToPlayer = Misc.getDistanceToPlayerLY(system.getLocation());
208                        float noSpawnRange = Global.getSettings().getFloat("personBountyNoSpawnRangeAroundPlayerLY");
209                        if (distToPlayer < noSpawnRange) mult = 0f;
210                        
211                        if (mult <= 0) continue;
212                        
213                        float weight = system.getPlanets().size();
214                        for (PlanetAPI planet : system.getPlanets()) {
215                                if (planet.isStar()) continue;
216                                if (planet.getMarket() != null) {
217                                        float h = planet.getMarket().getHazardValue();
218                                        if (h <= 0f) weight += 5f;
219                                        else if (h <= 0.25f) weight += 3f;
220                                        else if (h <= 0.5f) weight += 1f;
221                                }
222                        }
223                        
224                        float dist = system.getLocation().length();
225                        float distMult = Math.max(0, 50000f - dist);
226                        
227                        systemPicker.add(system, weight * mult * distMult);
228                }
229                
230                StarSystemAPI system = systemPicker.pick();
231                
232                if (system != null) {
233                        WeightedRandomPicker<SectorEntityToken> picker = new WeightedRandomPicker<SectorEntityToken>();
234                        for (SectorEntityToken planet : system.getPlanets()) {
235                                if (planet.isStar()) continue;
236                                if (planet.getMarket() != null && 
237                                                !planet.getMarket().isPlanetConditionMarketOnly()) continue;
238                                
239                                picker.add(planet);
240                        }
241                        hideoutLocation = picker.pick();
242                }
243                
244                
245                if (hideoutLocation == null) {
246                        endImmediately();
247                }
248        }
249        
250        
251
252        private void pickFaction() {
253                FactionAPI player = Global.getSector().getPlayerFaction();
254
255                String commFacId = Misc.getCommissionFactionId();
256                boolean forceCommissionFaction = true;
257                if (commFacId != null && getSharedData().isParticipating(commFacId)) {
258                        for (EveryFrameScript s : PersonBountyManager.getInstance().getActive()) {
259                                PersonBountyIntel bounty = (PersonBountyIntel) s;
260                                if (bounty.faction != null && bounty.faction.getId().equals(commFacId)) {
261                                        forceCommissionFaction = false;
262                                }
263                        }
264                } else {
265                        forceCommissionFaction = false;
266                }
267                
268                WeightedRandomPicker<MarketAPI> picker = new WeightedRandomPicker<MarketAPI>();
269                for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
270                        if (!getSharedData().isParticipating(market.getFactionId())) continue;
271                        if (market.getSize() < 3) continue;
272                        if (market.isHidden()) continue;
273                        if (market.getFaction().isPlayerFaction()) continue;
274                        
275                        float weight = market.getSize();
276                        if (market.hasIndustry(Industries.PATROLHQ)) weight *= 1.5f;
277                        if (market.hasIndustry(Industries.MILITARYBASE)) weight *= 3f;
278                        if (market.hasIndustry(Industries.HIGHCOMMAND)) weight *= 5f;
279
280                        if (market.getFaction() != null) {
281                                if (forceCommissionFaction && !market.getFaction().getId().equals(commFacId)) {
282                                        continue;
283                                }
284                                        
285                                if (market.getFaction().isHostileTo(player)) {
286                                        weight *= 0.5f;
287                                } else {
288                                        // turned off to vary bounties a bit more
289                                        //float rel = market.getFaction().getRelToPlayer().getRel();
290                                        //weight *= 1f + rel; // (0.5 to 2], given that it's not hostile if we're here
291                                }
292                        }
293                        
294                        if (weight > 0) {
295                                picker.add(market, weight);
296                        }
297                }
298                
299                if (picker.isEmpty()) {
300                        endImmediately();
301                        return;
302                }
303                
304                MarketAPI market = picker.pick();
305                faction = market.getFaction();
306        }
307        
308        private void initBountyAmount() {
309                //float highStabilityMult = BaseMarketConditionPlugin.getHighStabilityBonusMult(market);
310                float highStabilityMult = 1f;
311                float base = Global.getSettings().getFloat("basePersonBounty");
312                float perLevel = Global.getSettings().getFloat("personBountyPerLevel");
313                
314                float random = perLevel * (int)(Math.random() * 15) / 15f;
315                
316                bountyCredits = (int) ((base + perLevel * level + random) * highStabilityMult);
317        }
318        
319        private void initPerson() {
320                String factionId = Factions.PIRATES;
321                if (bountyType == BountyType.DESERTER) {
322                        factionId = faction.getId();
323                }
324                int personLevel = (int) (5 + level * 1.5f);
325                person = OfficerManagerEvent.createOfficer(Global.getSector().getFaction(factionId), 
326                                                                                                   personLevel, false);
327                if (level >= 7) {
328                        person.setRankId(Ranks.SPACE_ADMIRAL);
329                } else {
330                        person.setRankId(Ranks.SPACE_CAPTAIN);
331                }
332        }
333        
334        private void pickBountyType() {
335                WeightedRandomPicker<BountyType> picker = new WeightedRandomPicker<BountyType>();
336                picker.add(BountyType.PIRATE, 10f);
337                
338                //if (getSharedData().getLevel() >= 3) {
339                if (level >= 4) {
340                        picker.add(BountyType.DESERTER, 30f);
341                }
342                bountyType = picker.pick();
343        }
344        
345//      private String getTargetDesc() {
346//              //targetDesc = person.getName().getFullName() + " is known to be a highly capable combat officer in command of a sizeable fleet.";
347//              
348//              ShipHullSpecAPI spec = flagship.getVariant().getHullSpec();
349//              String shipType = spec.getHullNameWithDashClass() + " " + spec.getDesignation().toLowerCase(); 
350//
351//              String heOrShe = "he";
352//              String hisOrHer = "his";
353//              if (person.isFemale()) {
354//                      heOrShe = "she";
355//                      hisOrHer = "her";
356//              }
357//              
358//              String levelDesc = "";
359//              int personLevel = person.getStats().getLevel();
360//              if (personLevel <= 5) {
361//                      levelDesc = "an unremarkable officer";
362//              } else if (personLevel <= 10) {
363//                      levelDesc = "a capable officer";
364//              } else if (personLevel <= 15) {
365//                      levelDesc = "a highly capable officer";
366//              } else {
367//                      levelDesc = "an exceptionally capable officer";
368//              }
369//              
370//              String skillDesc = "";
371//              
372//              if (person.getStats().getSkillLevel(Skills.OFFICER_MANAGEMENT) > 0) {
373//                      skillDesc = "having a high number of skilled subordinates";
374//              } else if (person.getStats().getSkillLevel(Skills.ELECTRONIC_WARFARE) > 0) {
375//                      skillDesc = "being proficient in electronic warfare";
376//              } else if (person.getStats().getSkillLevel(Skills.CARRIER_GROUP) > 0) {
377//                      skillDesc = "a noteworthy level of skill in running carrier operations";
378//              } else if (person.getStats().getSkillLevel(Skills.COORDINATED_MANEUVERS) > 0) {
379//                      skillDesc = "a high effectiveness in coordinating the maneuvers of ships during combat";
380//              }
381//              
382//              if (!skillDesc.isEmpty() && levelDesc.contains("unremarkable")) {
383//                      levelDesc = "an otherwise unremarkable officer";
384//              }
385//              
386//              String fleetDesc = "";
387//              if (level < 3) {
388//                      fleetDesc = "small";
389//              } else if (level <= 5) {
390//                      fleetDesc = "medium-sized";
391//              } else if (level <= 8) {
392//                      fleetDesc = "large";
393//              } else {
394//                      fleetDesc = "very large";
395//              }
396//              
397//              String targetDesc = String.format("%s is in command of a %s fleet and was last seen using a %s as %s flagship.",
398//                                              person.getName().getFullName(), fleetDesc, shipType, hisOrHer);                                 
399//              
400//              if (skillDesc.isEmpty()) {
401//                      targetDesc += String.format(" %s is known to be %s.", Misc.ucFirst(heOrShe), levelDesc);
402//              } else {
403//                      targetDesc += String.format(" %s is %s known for %s.", Misc.ucFirst(heOrShe), levelDesc, skillDesc);
404//              }
405//              
406//              //targetDesc += "\n\nLevel: " + level;
407//              
408//              return targetDesc;
409//      }
410
411        @Override
412        protected void advanceImpl(float amount) {
413                float days = Global.getSector().getClock().convertToDays(amount);
414                //days *= 60f;
415                
416                elapsedDays += days;
417
418                if (elapsedDays >= duration && !isDone()) {
419                        boolean canEnd = fleet == null || !fleet.isInCurrentLocation();
420                        if (canEnd) {
421                                log.info(String.format("Ending bounty on %s by %s", person.getName().getFullName(), faction.getDisplayName()));
422                                result = new BountyResult(BountyResultType.END_TIME, 0, null);
423                                sendUpdateIfPlayerHasIntel(result, true);
424                                cleanUpFleetAndEndIfNecessary();
425                                return;
426                        }
427                }
428                
429                if (fleet == null) return;
430                
431                if (fleet.isInCurrentLocation() && !fleet.getFaction().getId().equals(Factions.PIRATES)) {
432                        fleet.setFaction(Factions.PIRATES, true);
433                } else if (!fleet.isInCurrentLocation() && !fleet.getFaction().getId().equals(Factions.NEUTRAL)) {
434                        fleet.setFaction(Factions.NEUTRAL, true);
435                }
436                
437                if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != person) {
438                        result = new BountyResult(BountyResultType.END_OTHER, 0, null);
439                        boolean current = fleet.isInCurrentLocation();
440                        sendUpdateIfPlayerHasIntel(result, !current);
441                        cleanUpFleetAndEndIfNecessary();
442                        return;
443                }
444        }
445        
446        public float getTimeRemainingFraction() {
447                float f = 1f - elapsedDays / duration;
448                return f;
449        }
450        
451        
452        protected BountyResult result = null;
453
454        
455        @Override
456        protected void notifyEnding() {
457                super.notifyEnding();
458                cleanUpFleetAndEndIfNecessary();
459        }
460        
461        protected void cleanUpFleetAndEndIfNecessary() {
462                if (fleet != null) {
463                        Misc.makeUnimportant(fleet, "pbe");
464                        fleet.clearAssignments();
465                        if (hideoutLocation != null) {
466                                fleet.getAI().addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, hideoutLocation, 1000000f, null);
467                        } else {
468                                fleet.despawn();
469                        }
470                        fleet = null; //can't null it because description uses it
471                }
472                if (!isEnding() && !isEnded()) {
473                        endAfterDelay();
474                }
475        }
476
477        protected boolean willPay() {
478                if (true) return true;
479                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
480                RepLevel level = playerFleet.getFaction().getRelationshipLevel(faction);
481                return level.isAtWorst(RepLevel.SUSPICIOUS);
482        }
483        
484        protected boolean willRepIncrease() {
485                if (true) return true;
486                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
487                RepLevel level = playerFleet.getFaction().getRelationshipLevel(faction);
488                return level.isAtWorst(RepLevel.HOSTILE);
489        }
490
491        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
492                if (isDone() || result != null) return;
493                
494                // also credit the player if they're in the same location as the fleet and nearby
495                float distToPlayer = Misc.getDistance(fleet, Global.getSector().getPlayerFleet());
496                boolean playerInvolved = battle.isPlayerInvolved() || (fleet.isInCurrentLocation() && distToPlayer < 2000f);
497                
498                if (battle.isInvolved(fleet) && !playerInvolved) {
499                        if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != person) {
500                                fleet.setCommander(fleet.getFaction().createRandomPerson());
501                                //Global.getSector().reportEventStage(this, "other_end", market.getPrimaryEntity(), messagePriority);
502                                result = new BountyResult(BountyResultType.END_OTHER, 0, null);
503                                sendUpdateIfPlayerHasIntel(result, true);
504                                cleanUpFleetAndEndIfNecessary();
505                                return;
506                        }
507                }
508                
509                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
510                if (!playerInvolved || !battle.isInvolved(fleet) || battle.onPlayerSide(fleet)) {
511                        return;
512                }
513                
514                 // didn't destroy the original flagship
515                if (fleet.getFlagship() != null && fleet.getFlagship().getCaptain() == person) return;
516                
517                //int payment = (int) (bountyCredits * battle.getPlayerInvolvementFraction());
518                int payment = (int) bountyCredits; // don't bother about reducing the payout if the player didn't do it all themselves
519                if (payment <= 0) {
520                        result = new BountyResult(BountyResultType.END_OTHER, 0, null);
521                        sendUpdateIfPlayerHasIntel(result, true);
522                        cleanUpFleetAndEndIfNecessary();
523                        return;
524                }
525                
526                if (willPay()) {
527                        log.info(String.format("Paying bounty of %d from faction [%s]", (int) payment, faction.getDisplayName()));
528                        
529                        playerFleet.getCargo().getCredits().add(payment);
530                        ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
531                                        new RepActionEnvelope(RepActions.PERSON_BOUNTY_REWARD, null, null, null, true, false), 
532                                        faction.getId());
533                        result = new BountyResult(BountyResultType.END_PLAYER_BOUNTY, payment, rep);
534                        sendUpdateIfPlayerHasIntel(result, false);
535                } else if (willRepIncrease()) {
536                        log.info(String.format("Not paying bounty, but improving rep with faction [%s]", faction.getDisplayName()));
537                        ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation(
538                                                                new RepActionEnvelope(RepActions.PERSON_BOUNTY_REWARD, null, null, null, true, false), 
539                                                                faction.getId());
540                        result = new BountyResult(BountyResultType.END_PLAYER_NO_BOUNTY, payment, rep);
541                        sendUpdateIfPlayerHasIntel(result, false);
542                } else {
543                        log.info(String.format("Not paying bounty or improving rep with faction [%s]", faction.getDisplayName()));
544                        result = new BountyResult(BountyResultType.END_PLAYER_NO_REWARD, 0, null);
545                        sendUpdateIfPlayerHasIntel(result, false);
546                }
547                
548                
549                getSharedData().reportSuccess();
550                //removeBounty();
551                
552                cleanUpFleetAndEndIfNecessary();
553        }
554
555        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
556                if (isDone() || result != null) return;
557                
558                if (this.fleet == fleet) {
559                        fleet.setCommander(fleet.getFaction().createRandomPerson());
560                        result = new BountyResult(BountyResultType.END_OTHER, 0, null);
561                        sendUpdateIfPlayerHasIntel(result, true);
562                        cleanUpFleetAndEndIfNecessary();
563                }
564        }
565        
566        
567        private void spawnFleet() {
568//              level = 10;
569//              bountyType = BountyType.PIRATE;
570                
571                String fleetFactionId = Factions.PIRATES;
572                if (bountyType == BountyType.DESERTER) {
573                        fleetFactionId = faction.getId();
574                }
575                
576                float qf = (float) level / 10f;
577                if (qf > 1) qf = 1;
578                
579                String fleetName = "";
580                
581                fleetName = person.getName().getLast() + "'s" + " Fleet";
582                
583                float fp = (5 + level * 5) * 5f;
584                fp *= 0.75f + (float) Math.random() * 0.25f;
585                
586                if (level >= 7) {
587                        fp += 20f;
588                }
589                if (level >= 8) {
590                        fp += 30f;
591                }
592                if (level >= 9) {
593                        fp += 50f;
594                }
595                if (level >= 10) {
596                        fp += 50f;
597                }
598                
599                FactionAPI faction = Global.getSector().getFaction(fleetFactionId);
600                float maxFp = faction.getApproximateMaxFPPerFleet(ShipPickMode.PRIORITY_THEN_ALL) * 1.1f;
601                if (fp > maxFp) fp = maxFp;
602                
603                FleetParamsV3 params = new FleetParamsV3(
604                                null, 
605                                hideoutLocation.getLocationInHyperspace(),
606                                fleetFactionId, 
607                                qf + 0.2f, // qualityOverride
608                                FleetTypes.PERSON_BOUNTY_FLEET,
609                                fp, // combatPts
610                                0, // freighterPts 
611                                0, // tankerPts
612                                0f, // transportPts
613                                0f, // linerPts
614                                0f, // utilityPts
615                                0f // qualityMod
616                                );
617                params.ignoreMarketFleetSizeMult = true;
618//              if (route != null) {
619//                      params.timestamp = route.getTimestamp();
620//              }
621                //params.random = random;
622                fleet = FleetFactoryV3.createFleet(params);
623                
624//              fleet.getFleetData().addFleetMember("station_small_Standard");
625//              fleet.getFleetData().sort();
626                
627                if (fleet == null || fleet.isEmpty()) {
628                        endImmediately();
629                        return;
630                }
631
632                fleet.setCommander(person);
633                fleet.getFlagship().setCaptain(person);
634//              fleet.getFleetData().setSyncNeeded();
635//              fleet.getFleetData().syncIfNeeded();
636                FleetFactoryV3.addCommanderSkills(person, fleet, null);
637                
638                //Misc.getSourceMarket(fleet).getStats().getDynamic().getValue(Stats.OFFICER_LEVEL_MULT);
639                
640                Misc.makeImportant(fleet, "pbe", duration + 20f);
641                fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_PIRATE, true);
642                fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_NO_MILITARY_RESPONSE, true);
643                
644                fleet.setNoFactionInName(true);
645                fleet.setFaction(Factions.NEUTRAL, true);
646                fleet.setName(fleetName);
647
648                fleet.addEventListener(this);
649                
650                LocationAPI location = hideoutLocation.getContainingLocation();
651                location.addEntity(fleet);
652                fleet.setLocation(hideoutLocation.getLocation().x - 500, hideoutLocation.getLocation().y + 500);
653                fleet.getAI().addAssignment(FleetAssignment.ORBIT_AGGRESSIVE, hideoutLocation, 1000000f, null);
654                
655                flagship = fleet.getFlagship();
656
657        }
658
659        public boolean runWhilePaused() {
660                return false;
661        }
662
663
664        
665        public static enum BountyResultType {
666                END_PLAYER_BOUNTY,
667                END_PLAYER_NO_BOUNTY,
668                END_PLAYER_NO_REWARD,
669                END_OTHER,
670                END_TIME,
671        }
672        
673        public static class BountyResult {
674                public BountyResultType type;
675                public int payment;
676                public ReputationAdjustmentResult rep;
677                public BountyResult(BountyResultType type, int payment, ReputationAdjustmentResult rep) {
678                        this.type = type;
679                        this.payment = payment;
680                        this.rep = rep;
681                }
682        }
683        
684        protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) {
685                
686                
687                Color h = Misc.getHighlightColor();
688                Color g = Misc.getGrayColor();
689                float pad = 3f;
690                float opad = 10f;
691                
692                float initPad = pad;
693                if (mode == ListInfoMode.IN_DESC) initPad = opad;
694                
695                Color tc = getBulletColorForMode(mode);
696                
697                bullet(info);
698                boolean isUpdate = getListInfoParam() != null;
699                
700                if (result == null) {
701//                      RepLevel level = Global.getSector().getPlayerFaction().getRelationshipLevel(faction.getId());
702//                      Color relColor = faction.getRelColor(Factions.PLAYER);
703//                      String rel = level.getDisplayName().toLowerCase();
704                        
705                        if (mode == ListInfoMode.IN_DESC) {
706//                              if (!willRepIncrease()) {
707//                                      LabelAPI label = info.addPara("No reward or rep increase (" + rel + ")", bodyColor, initPad);  
708//                                      label.setHighlight("(" + rel + ")");
709//                                      label.setHighlightColors(relColor);
710//                              } else if (!willPay()) {
711//                                      LabelAPI label = info.addPara("No reward (" + rel + ")", bodyColor, initPad);  
712//                                      label.setHighlight("(" + rel + ")");
713//                                      label.setHighlightColors(relColor);
714//                              } else {
715                                        info.addPara("%s reward", initPad, tc, h, Misc.getDGSCredits(bountyCredits));
716//                              }
717                                int days = (int) (duration - elapsedDays);
718                                if (days <= 1) {
719                                        days = 1;
720                                }
721                                addDays(info, "remaining", days, tc);
722                        } else {
723                                info.addPara("Faction: " + faction.getDisplayName(), initPad, tc,
724                                                 faction.getBaseUIColor(), faction.getDisplayName());
725                                if (!isEnding()) {
726                                        int days = (int) (duration - elapsedDays);
727                                        String daysStr = "days";
728                                        if (days <= 1) {
729                                                days = 1;
730                                                daysStr = "day";
731                                        }
732                                        info.addPara("%s reward, %s " + daysStr + " remaining", 0f, tc,
733                                                        h, Misc.getDGSCredits(bountyCredits), "" + days);
734                                }
735                        }
736                        unindent(info);
737                        return;
738                }
739                
740                switch (result.type) {
741                case END_PLAYER_BOUNTY:
742                        info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(result.payment));
743                        CoreReputationPlugin.addAdjustmentMessage(result.rep.delta, faction, null, 
744                                        null, null, info, tc, isUpdate, 0f);
745                        break;
746                case END_PLAYER_NO_BOUNTY:
747                        CoreReputationPlugin.addAdjustmentMessage(result.rep.delta, faction, null, 
748                                        null, null, info, tc, isUpdate, 0f);
749                        break;
750                case END_PLAYER_NO_REWARD:
751                        CoreReputationPlugin.addAdjustmentMessage(result.rep.delta, faction, null, 
752                                        null, null, info, tc, isUpdate, 0f);
753                        break;
754                case END_TIME:
755                        break;
756                case END_OTHER:
757                        break;
758                
759                }
760                
761                unindent(info);
762        }
763        
764        public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) {
765                Color h = Misc.getHighlightColor();
766                Color g = Misc.getGrayColor();
767                Color c = getTitleColor(mode);
768                float pad = 3f;
769                float opad = 10f;
770                
771                info.addPara(getName(), c, 0f);
772                
773                addBulletPoints(info, mode);
774                
775        }
776        
777        
778        public String getSortString() {
779                return "Personal Bounty";
780        }
781        
782        public String getName() {
783                String n = person.getName().getFullName();
784                
785                if (result != null) {
786                        switch (result.type) {
787                        case END_PLAYER_BOUNTY:
788                        case END_PLAYER_NO_BOUNTY:
789                        case END_PLAYER_NO_REWARD:
790                                return "Bounty Completed - " + n;
791                        case END_OTHER:
792                        case END_TIME:
793                                return "Bounty Ended - " + n;
794                        }
795                }
796                
797                return "Personal Bounty - " + n;
798        }
799        
800        @Override
801        public FactionAPI getFactionForUIColors() {
802                return faction;
803        }
804
805        public String getSmallDescriptionTitle() {
806                return getName();
807                //return null;
808        }
809        
810        public void createSmallDescription(TooltipMakerAPI info, float width, float height) {
811                Color h = Misc.getHighlightColor();
812                Color g = Misc.getGrayColor();
813                float pad = 3f;
814                float opad = 10f;
815
816                //info.addPara(getName(), c, 0f);
817                
818                //info.addSectionHeading(getName(), Alignment.MID, 0f);
819                
820                info.addImage(person.getPortraitSprite(), width, 128, opad);
821                
822                String type = "a notorious pirate";
823                if (bountyType == BountyType.DESERTER) type = "a deserter";
824                
825                String has = faction.getDisplayNameHasOrHave();
826                info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + has + 
827                                " posted a bounty for bringing " + person.getName().getFullName() + 
828                                ", " + type + ", to justice.",
829                                opad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
830                
831                if (result != null) {
832                        if (result.type == BountyResultType.END_PLAYER_BOUNTY) {
833                                info.addPara("You have successfully completed this bounty.", opad);
834                        } else if (result.type == BountyResultType.END_PLAYER_NO_BOUNTY) {
835                                info.addPara("You have successfully completed this bounty, but received no " +
836                                                "credit reward because of your standing with " + 
837                                                Misc.ucFirst(faction.getDisplayNameWithArticle()) + ".",
838                                                opad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
839                        } else if (result.type == BountyResultType.END_PLAYER_NO_REWARD) {
840                                info.addPara("You have successfully completed this bounty, but received no " +
841                                                "reward because of your standing with " + 
842                                                Misc.ucFirst(faction.getDisplayNameWithArticle()) + ".",
843                                                opad, faction.getBaseUIColor(), faction.getDisplayNameWithArticleWithoutArticle());
844                        } else {
845                                info.addPara("This bounty is no longer on offer.", opad);
846                        }
847                } else {
848//                      RepLevel level = Global.getSector().getPlayerFaction().getRelationshipLevel(faction.getId());
849//                      Color relColor = faction.getRelColor(Factions.PLAYER);
850//                      String rel = level.getDisplayName().toLowerCase();
851//                      if (!willRepIncrease()) {
852//                              LabelAPI label = info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " is " + rel + 
853//                                              " towards you, and you would receive no payment or reputation increase for " +
854//                                              "completing the bounty.", opad);
855//                              label.setHighlight(faction.getDisplayNameWithArticleWithoutArticle(), rel);
856//                              label.setHighlightColors(faction.getBaseUIColor(), relColor);
857//                      } else if (!willPay()) {
858//                              LabelAPI label = info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " is " + rel + 
859//                                              " towards you, and you would receive no payment for completing the bounty, but " +
860//                                              "it would improve your standing.", opad);
861//                              label.setHighlight(faction.getDisplayNameWithArticleWithoutArticle(), rel);
862//                              label.setHighlightColors(faction.getBaseUIColor(), relColor);
863//                      }
864                }
865                
866                addBulletPoints(info, ListInfoMode.IN_DESC);
867                if (result == null) {
868//                      String targetDesc = getTargetDesc();
869//                      if (targetDesc != null) {
870//                              info.addPara(targetDesc, opad);
871//                      }
872                        
873                        if (hideoutLocation != null) {
874                                SectorEntityToken fake = hideoutLocation.getContainingLocation().createToken(0, 0);
875                                fake.setOrbit(Global.getFactory().createCircularOrbit(hideoutLocation, 0, 1000, 100));
876                                
877                                String loc = BreadcrumbSpecial.getLocatedString(fake);
878                                loc = loc.replaceAll("orbiting", "hiding out near");
879                                loc = loc.replaceAll("located in", "hiding out in");
880                                String sheIs = "She is";
881                                if (person.getGender() == Gender.MALE) sheIs = "He is";
882                                info.addPara(sheIs + " rumored to be " + loc + ".", opad);
883                        }
884                        
885                        
886                        int cols = 7;
887                        float iconSize = width / cols;
888                        
889                        
890                        if (DebugFlags.PERSON_BOUNTY_DEBUG_INFO) {
891                                boolean deflate = false;
892                                if (!fleet.isInflated()) {
893                                        fleet.setFaction(Factions.PIRATES, true);
894                                        fleet.inflateIfNeeded();
895                                        deflate = true;
896                                }
897                                
898                                String her = "her";
899                                if (person.getGender() == Gender.MALE) her = "his";
900                                info.addPara("The bounty posting also contains partial intel on the ships under " + her + " command. (DEBUG: full info)", opad);
901                                info.addShipList(cols, 3, iconSize, getFactionForUIColors().getBaseUIColor(), fleet.getMembersWithFightersCopy(), opad);
902                                
903                                info.addPara("level: " + level, 3f);
904                                info.addPara("type: " + bountyType.name(), 3f);
905                                
906                                if (deflate) {
907                                        fleet.deflate();
908                                }
909                        } else {
910                                boolean deflate = false;
911                                if (!fleet.isInflated()) {
912                                        fleet.setFaction(Factions.PIRATES, true);
913                                        fleet.inflateIfNeeded();
914                                        deflate = true;
915                                }
916                                
917                                List<FleetMemberAPI> list = new ArrayList<FleetMemberAPI>();
918                                Random random = new Random(person.getNameString().hashCode() * 170000);
919                                
920//                              WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>(random);
921//                              picker.addAll(fleet.getFleetData().getMembersListCopy());
922//                              int picks = (int) Math.round(picker.getItems().size() * 0.5f);
923                                
924                                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
925                                int max = 7;
926                                for (FleetMemberAPI member : members) {
927                                        if (list.size() >= max) break;
928                                        
929                                        if (member.isFighterWing()) continue;
930                                        
931                                        float prob = (float) member.getFleetPointCost() / 20f;
932                                        prob += (float) max / (float) members.size();
933                                        if (member.isFlagship()) prob = 1f;
934                                        //if (members.size() <= max) prob = 1f;
935                                        
936                                        if (random.nextFloat() > prob) continue;
937                                        
938                                        FleetMemberAPI copy = Global.getFactory().createFleetMember(FleetMemberType.SHIP, member.getVariant());
939                                        if (member.isFlagship()) {
940                                                copy.setCaptain(person);
941                                        }
942                                        list.add(copy);
943                                }
944                                
945                                if (!list.isEmpty()) {
946                                        String her = "her";
947                                        if (person.getGender() == Gender.MALE) her = "his";
948                                        info.addPara("The bounty posting also contains partial intel on some of the ships under " + her + " command.", opad);
949                                        info.addShipList(cols, 1, iconSize, getFactionForUIColors().getBaseUIColor(), list, opad);
950                                        
951                                        int num = members.size() - list.size();
952                                        num = Math.round((float)num * (1f + random.nextFloat() * 0.5f));
953                                        
954                                        if (num < 5) num = 0;
955                                        else if (num < 10) num = 5;
956                                        else if (num < 20) num = 10;
957                                        else num = 20;
958                                        
959                                        if (num > 1) {
960                                                info.addPara("The intel assessment notes the fleet may contain upwards of %s other ships" +
961                                                                " of lesser significance.", opad, h, "" + num);
962                                        } else {
963                                                info.addPara("The intel assessment notes the fleet may contain several other ships" +
964                                                                " of lesser significance.", opad);
965                                        }
966                                }
967                                
968                                if (deflate) {
969                                        fleet.deflate();
970                                }
971                        }
972                }
973                
974                //info.addButton("Accept", "Accept", faction.getBaseUIColor(), faction.getDarkUIColor(), (int)(width/2), 20f, opad);
975
976        }
977        
978        public String getIcon() {
979                return person.getPortraitSprite();
980        }
981        
982        public Set<String> getIntelTags(SectorMapAPI map) {
983                Set<String> tags = super.getIntelTags(map);
984                tags.add(Tags.INTEL_BOUNTY);
985                tags.add(faction.getId());
986                return tags;
987        }
988
989        @Override
990        public SectorEntityToken getMapLocation(SectorMapAPI map) {
991                Constellation c = hideoutLocation.getConstellation();
992                SectorEntityToken entity = null;
993                if (c != null && map != null) {
994                        entity = map.getConstellationLabelEntity(c);
995                }
996                if (entity == null) entity = hideoutLocation;
997                return entity;
998        }
999
1000        public PersonAPI getPerson() {
1001                return person;
1002        }
1003
1004        public CampaignFleetAPI getFleet() {
1005                return fleet;
1006        }
1007
1008        public float getDuration() {
1009                return duration;
1010        }
1011
1012        public void setDuration(float duration) {
1013                this.duration = duration;
1014        }
1015
1016        public float getBountyCredits() {
1017                return bountyCredits;
1018        }
1019
1020        public void setBountyCredits(float bountyCredits) {
1021                this.bountyCredits = bountyCredits;
1022        }
1023
1024        public BountyType getBountyType() {
1025                return bountyType;
1026        }
1027
1028        public void setBountyType(BountyType bountyType) {
1029                this.bountyType = bountyType;
1030        }
1031
1032        public FactionAPI getFaction() {
1033                return faction;
1034        }
1035
1036        public FleetMemberAPI getFlagship() {
1037                return flagship;
1038        }
1039
1040        public SectorEntityToken getHideoutLocation() {
1041                return hideoutLocation;
1042        }
1043
1044        public BountyResult getResult() {
1045                return result;
1046        }
1047        
1048//      @Override
1049//      public boolean hasLargeDescription() {
1050//              return true;
1051//      }
1052        
1053        
1054}
1055
1056