package data.scripts.campaign.intel;

import com.fs.starfarer.api.EveryFrameScript;
import com.fs.starfarer.api.Global;
import com.fs.starfarer.api.campaign.BattleAPI;
import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
import com.fs.starfarer.api.campaign.CampaignFleetAPI;
import com.fs.starfarer.api.campaign.CargoAPI;
import com.fs.starfarer.api.campaign.CargoStackAPI;
import com.fs.starfarer.api.campaign.FactionAPI;
import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
import com.fs.starfarer.api.campaign.FactionDoctrineAPI;
import com.fs.starfarer.api.campaign.FleetAssignment;
import com.fs.starfarer.api.campaign.FleetDataAPI;
import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.DataForEncounterSide;
import com.fs.starfarer.api.campaign.FleetEncounterContextPlugin.FleetMemberData;
import com.fs.starfarer.api.campaign.InteractionDialogAPI;
import com.fs.starfarer.api.campaign.LocationAPI;
import com.fs.starfarer.api.campaign.RepLevel;
import com.fs.starfarer.api.campaign.SectorEntityToken;
import com.fs.starfarer.api.campaign.StarSystemAPI;
import com.fs.starfarer.api.campaign.econ.MarketAPI;
import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
import com.fs.starfarer.api.characters.FullName.Gender;
import com.fs.starfarer.api.characters.OfficerDataAPI;
import com.fs.starfarer.api.characters.PersonAPI;
import com.fs.starfarer.api.combat.BattleCreationContext;
import com.fs.starfarer.api.fleet.FleetMemberAPI;
import com.fs.starfarer.api.fleet.FleetMemberType;
import com.fs.starfarer.api.impl.campaign.FleetEncounterContext;
import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDConfig;
import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDConfigGen;
import com.fs.starfarer.api.impl.campaign.FleetInteractionDialogPluginImpl.FIDDelegate;
import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent;
import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent.SkillPickPreference;
import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3;
import com.fs.starfarer.api.impl.campaign.fleets.FleetParamsV3;
import com.fs.starfarer.api.impl.campaign.ids.Abilities;
import com.fs.starfarer.api.impl.campaign.ids.Drops;
import com.fs.starfarer.api.impl.campaign.ids.Factions;
import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
import com.fs.starfarer.api.impl.campaign.ids.Personalities;
import com.fs.starfarer.api.impl.campaign.ids.Ranks;
import com.fs.starfarer.api.impl.campaign.ids.Tags;
import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
import com.fs.starfarer.api.impl.campaign.procgen.Constellation;
import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.SalvageEntity;
import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial;
import com.fs.starfarer.api.plugins.OfficerLevelupPlugin;
import com.fs.starfarer.api.ui.SectorMapAPI;
import com.fs.starfarer.api.ui.TooltipMakerAPI;
import com.fs.starfarer.api.util.IntervalUtil;
import com.fs.starfarer.api.util.Misc;
import com.fs.starfarer.api.util.WeightedRandomPicker;
import data.scripts.util.SWP_Util;
import data.scripts.util.SWP_Util.RequiredFaction;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.log4j.Logger;
import org.lazywizard.lazylib.MathUtils;

public class SWP_IBBIntel extends BaseIntelPlugin implements EveryFrameScript, FleetEventListener {

    public static final Set<String> BANNED_SYSTEMS = new HashSet<>(1);
    public static final Set<String> IBB_FACTIONS = new LinkedHashSet<>(6);
    public static final int MAX_BOUNTY_STAGE = FamousBountyStage.values().length - 1;
    public static Logger log = Global.getLogger(SWP_IBBIntel.class);

    static {
        IBB_FACTIONS.add(Factions.HEGEMONY);
        IBB_FACTIONS.add(Factions.INDEPENDENT);
        IBB_FACTIONS.add(Factions.PERSEAN);
        IBB_FACTIONS.add("interstellarimperium");
        IBB_FACTIONS.add("shadow_industry");
        IBB_FACTIONS.add("tiandong");
        IBB_FACTIONS.add("ORA");
    }

    public static int calculatePowerLevel(FamousBountyStage stage) {
        float pointFactor = stage.qf / 4f + 0.875f;
        if (stage.fleetFaction.contentEquals(Factions.TRITACHYON)) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals(Factions.REMNANTS)) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals(Factions.LIONS_GUARD)) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals(Factions.PIRATES)) {
            pointFactor *= 0.85f;
        }
        if (stage.fleetFaction.contentEquals(Factions.DERELICT)) {
            pointFactor *= 0.6f;
        }
        if (stage.fleetFaction.contentEquals("shadow_industry")) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals("domain")) {
            pointFactor *= 1.05f;
        }
        if (stage.fleetFaction.contentEquals("sector")) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals("everything")) {
            pointFactor *= 1.15f;
        }
        if (stage.fleetFaction.contentEquals("templars")) {
            pointFactor *= 1.5f;
        }
        if (stage.fleetFaction.contentEquals("cabal")) {
            pointFactor *= 1.15f;
        }
        if (stage.fleetFaction.contentEquals("diableavionics")) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals("ORA")) {
            pointFactor *= 1.1f;
        }
        if (stage.fleetFaction.contentEquals("ii_imperial_guard")) {
            pointFactor *= 1.1f;
        }
        pointFactor *= (1f + stage.opBonus / 100f);
        int power = Math.round((stage.maxPts * pointFactor) + stage.ptsFromBoss);
        int offLvl = stage.officerCount * stage.officerLevel;
        int cdrLvl = stage.level;
        power *= cdrLvl / 200f + 1f;
        power += cdrLvl + offLvl;
        return power;
    }

    public static MarketAPI findNearestMarket(SectorEntityToken token, float maxDist) {
        List<MarketAPI> localMarkets = Misc.getMarketsInLocation(token.getContainingLocation());
        float distToLocalMarket = Float.MAX_VALUE;
        MarketAPI closest = null;
        for (MarketAPI market : localMarkets) {
            if (market.isPlanetConditionMarketOnly()) {
                continue;
            }
            if (market.getPrimaryEntity() == null) {
                continue;
            }
            if (market.getPrimaryEntity().getContainingLocation() != token.getContainingLocation()) {
                continue;
            }

            float currDist = Misc.getDistance(market.getPrimaryEntity().getLocation(), token.getLocation());
            if (currDist > maxDist) {
                continue;
            }
            if (currDist < distToLocalMarket) {
                distToLocalMarket = currDist;
                closest = market;
            }
        }
        return closest;
    }

    private FamousBountyStage thisStage;

    private final FactionAPI faction = Global.getSector().getFaction("famous_bounty");
    private String flagshipName;
    private CampaignFleetAPI fleet;
    private SectorEntityToken hideoutLocation = null;

    private final IntervalUtil checkInterval = new IntervalUtil(0.5f, 1f);

    private PersonAPI person;
    private boolean it = false;
    private String targetDescLong;

    private float bountyCredits = 0;
    private float payment = 0;

    protected IBBResult result = null;

    @Override
    public void advanceImpl(float amount) {
        if (isDone()) {
            return;
        }

        float days = Global.getSector().getClock().convertToDays(amount);

        checkInterval.advance(days);
        if (checkInterval.intervalElapsed()) {
            for (FactionAPI fac : Global.getSector().getAllFactions()) {
                if (fac != Global.getSector().getFaction(Factions.PLAYER)) {
                    fac.setRelationship("famous_bounty", RepLevel.INHOSPITABLE);
                }
            }
            if ((fleet != null) && fleet.isAlive()) {
                fleet.setFaction(faction.getId());
            }
        }

        if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != person) {
            result = new IBBResult(IBBResultType.END_OTHER, 0);
            sendUpdateIfPlayerHasIntel(result, false);
            endMission(false);
        }
    }

    @Override
    public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
        if (isDone()) {
            return;
        }

        if (this.fleet == fleet) {
            fleet.setCommander(fleet.getFaction().createRandomPerson());
            result = new IBBResult(IBBResultType.END_OTHER, 0);
            sendUpdateIfPlayerHasIntel(result, false);
            endMission(false);
        }
    }

    @Override
    public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
        if (isDone()) {
            return;
        }

        if (battle.isInvolved(fleet) && !battle.isPlayerInvolved()) {
            if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != person) {
                fleet.setCommander(fleet.getFaction().createRandomPerson());
                result = new IBBResult(IBBResultType.END_TAKEN, 0);
                sendUpdateIfPlayerHasIntel(result, false);
                endMission(false);
                return;
            }
        }

        if (!battle.isPlayerInvolved() || !battle.isInvolved(fleet) || battle.onPlayerSide(fleet)) {
            return;
        }

        // didn't destroy the original flagship
        if (fleet.getFlagship() != null && fleet.getFlagship().getCaptain() == person) {
            return;
        }

        payment = (int) (bountyCredits * battle.getPlayerInvolvementFraction());
        if (payment <= 0) {
            result = new IBBResult(IBBResultType.END_TAKEN, 0);
            sendUpdateIfPlayerHasIntel(result, false);
            endMission(false);
            return;
        }

        log.info(String.format("Paying bounty of %f from the International Bounty Board", payment));
        Global.getSector().getPlayerFleet().getCargo().getCredits().add(payment);
        result = new IBBResult(IBBResultType.END_PLAYER_BOUNTY, (int) payment);
        sendUpdateIfPlayerHasIntel(result, false);

        /* Mark bounty as completed */
        endMission(false);
    }

    @Override
    protected void notifyEnding() {
        super.notifyEnding();
        endMission(false);
    }

    public void endMission(boolean expire) {
        if (ended == null || !ended) {
            if (expire) {
                SWP_IBBTracker.getTracker().reportStageExpired(thisStage);
            } else {
                SWP_IBBTracker.getTracker().reportStageCompleted(thisStage);
            }
            setImportant(false);
        }
        ended = true;

        if (fleet != null) {
            fleet.getMemoryWithoutUpdate().set("$stillAlive", false);
            fleet.getMemoryWithoutUpdate().unset(MemFlags.MEMORY_KEY_MAKE_AGGRESSIVE);
            fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_NON_AGGRESSIVE, true);
            fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_ALLOW_DISENGAGE, true);
            Misc.makeUnimportant(fleet, "swp_ibb");
            fleet.clearAssignments();
            if (hideoutLocation != null) {
                fleet.getAI().addAssignment(FleetAssignment.GO_TO_LOCATION_AND_DESPAWN, hideoutLocation, 1000000f, null);
            } else {
                fleet.despawn();
            }
        }

        if (!isEnding() && !isEnded()) {
            endAfterDelay();
        }
    }

    public FamousBountyStage getStage() {
        return thisStage;
    }

    public String getTargetDesc() {
        return targetDescLong;
    }

    public PersonAPI getPerson() {
        return person;
    }

    public void init() {
        thisStage = SWP_IBBTracker.getTracker().getLowestIncompleteStage();
        if (thisStage == null) {
            log.info(String.format("No incomplete stages"));
            ended = true;
            return;
        }

        if (SWP_IBBTracker.getTracker().isStagePosted(thisStage) || SWP_IBBTracker.getTracker().isStageBegun(thisStage)) {
            float powerLevel = SWP_Util.calculatePowerLevel(Global.getSector().getPlayerFleet());
            int matchingStageNum = 0;
            for (int i = 0; i < FamousBountyStage.values().length; i++) {
                FamousBountyStage stage = SWP_IBBTracker.getStage(i);
                if (calculatePowerLevel(stage) * (2f / 3f) <= powerLevel) {
                    matchingStageNum = i;
                } else {
                    break;
                }
            }

            int stageNum;
            int currentStageNum = thisStage.ordinal();
            if (matchingStageNum <= currentStageNum) {
                stageNum = currentStageNum + Math.round(Math.abs((float) MathUtils.getRandom().nextGaussian() * 2f));
            } else {
                stageNum = matchingStageNum + Math.round(Math.abs((float) MathUtils.getRandom().nextGaussian() * 3f));
            }
            stageNum = Math.max(currentStageNum, stageNum);

            while (stageNum > currentStageNum) {
                FamousBountyStage stage = SWP_IBBTracker.getStage(stageNum);
                if (SWP_IBBTracker.getTracker().isStageAvailable(stage)) {
                    break;
                } else {
                    stageNum--;
                }
            }

            thisStage = SWP_IBBTracker.getStage(stageNum);
            if (thisStage == null) {
                log.info(String.format("No compatible stage"));
                ended = true;
                return;
            }
        }

        if (!SWP_IBBTracker.getTracker().isStageAvailable(thisStage)) {
            log.info(String.format("Stage %s is not available", thisStage.name()));
            thisStage = null;
            ended = true;
            return;
        }

        person = initPerson(faction);

        bountyCredits = thisStage.reward;
        payment = bountyCredits;

        it = thisStage.machine;
        switch (thisStage) {
            case STAGE_UNDINE: {
                targetDescLong = "%s, known to possess a rag-tag fleet of renegade pirates. The primary danger is "
                        + person.getName().getLast() + "'s pair of unique Lashers, which " + getHeOrShe()
                        + " has put to devastating use raiding supply lines.";
                break;
            }
            case STAGE_HADES: {
                targetDescLong = "%s, who commands a converted trade fleet, largely purchased privately. "
                        + person.getName().getLast() + " is in possession of four unique Cerberi, which " + getHeOrShe()
                        + " stole from an IBB shareholder.";
                break;
            }
            case STAGE_PONY: {
                targetDescLong = "%s, whose private flotilla is helmed by a pair of unique ships. "
                        + "One of these is a unique Mule, which " + person.getName().getLast()
                        + " recently used to steal a unique Tarsus. " + Misc.ucFirst(getHeOrShe())
                        + " is a dangerous criminal who will stop at nothing to bring chaos to the Sector.";
                break;
            }
            case STAGE_ELDER_ORB: {
                targetDescLong = "%s, wanted dead for arson, murder, and invasion of privacy. " + Misc.ucFirst(getHeOrShe())
                        + " is a dangerous sexual deviant, known to perform bizarre and horrifying experiments on those "
                        + getHeOrShe() + " captures. " + person.getName().getLast() + " currently has a unique Beholder at "
                        + getHisOrHer() + " beck and call.";
                break;
            }
            case STAGE_FRACTURE: {
                targetDescLong = "a former Lion's Guard officer named %s, who officially declared war against... "
                        + "'everyone'. As a result, " + person.getName().getLast() + " is wanted dead by nearly every government. "
                        + Misc.ucFirst(getHeOrShe()) + " possesses a unique Sunder and a unique Hammerhead, which " + getHeOrShe()
                        + " has used to great effect in the past.";
                break;
            }
            case STAGE_LIBERTY: {
                targetDescLong = "%s, once an esteemed leader. Now, " + getHeOrShe()
                        + " has lost every trace of sanity, carrying on against the Sector in the name of a fictional empire. "
                        + person.getName().getLast() + " commands a unique Wuzhang, and leads a band of indoctrinated cronies.";
                break;
            }
            case STAGE_RAPTOR: {
                targetDescLong = "%s, the daughter of a retired admiral. Using the privilege " + getHeOrShe() + " received, "
                        + person.getName().getLast() + " acquired a unique Falcon and a unique Medusa. Unfortunately, "
                        + getHeOrShe() + " went on a romp, carving a war-path of destruction in " + getHisOrHer() + " wake.";
                break;
            }
            case STAGE_BULLSEYE: {
                targetDescLong = "a P.A.C.K admiral by the name of %s, purportedly spliced with canine genes by "
                        + getHisOrHer() + " own request. The operation drove " + person.getName().getLast()
                        + " insane, as evidenced by the fact that " + getHeOrShe() + " proceeded to swipe a group of "
                        + "cutting-edge P.A.C.K. warships in order to raid various colonies indiscriminately.";
                break;
            }
            case STAGE_APEX: {
                targetDescLong = "%s, a famed war hero known for singlehandedly stopping the Mazalot Rebellion. However, "
                        + getHeOrShe() + " later fell victim to a neurological disease, eventually summoning the most loyal members of "
                        + getHisOrHer() + " old fleet and going on a rampage. Despite " + person.getName().getLast() + "'s mental disorder, "
                        + getHeOrShe() + " remains a skilled commander, and is in possession of a unique Eagle.";
                break;
            }
            case STAGE_EMPEROR: {
                targetDescLong = "%s. " + Misc.ucFirst(getHeOrShe()) + " was once a double-agent, working for the Tri-Tachyon in secret "
                        + "while posing as a Hegemony commander. " + Misc.ucFirst(getHeOrShe()) + " eventually betrayed both parties, "
                        + " in the process becoming wanted dead in duplicate. " + person.getName().getLast() + " still possesses "
                        + getHisOrHer() + " loyal fleet, and has a unique Dominator as well as four unique Brawlers under "
                        + getHisOrHer() + " command.";
                break;
            }
            case STAGE_GULF: {
                targetDescLong = "%s, wanted on charges of treason by the Diable Corporation. " + Misc.ucFirst(getHeOrShe())
                        + " thereafter carved a war-path through allied territory, costing local polities millions in damage. As such, "
                        + getHeOrShe() + " is officially wanted dead by all IBB member polities. " + person.getName().getLast()
                        + " commands a large Diable fleet from the helm of " + getHisOrHer() + " unique Gust.";
                break;
            }
            case STAGE_YAMATO: {
                targetDescLong = "%s, a renowned war veteran. For unknown reasons, " + getHeOrShe()
                        + " rebelled against the Sector's established governments. " + person.getName().getLast()
                        + " now brings " + getHisOrHer() + " private fleet to bear against us. " + Misc.ucFirst(getHeOrShe())
                        + " commands a unique Dominus, a famous ship that has seen many great battles over its long career.";
                break;
            }
            case STAGE_FRAMEBREAKER: {
                targetDescLong = "%s, \"The King\", who fashioned " + getHimOrHerself()
                        + " after the legendary prophet Ludd to acquire a fleet of followers. " + Misc.ucFirst(getHisOrHer())
                        + " numerous acts of terrorism have left a long trail of bodies. " + "King " + person.getName().getLast()
                        + " commands a fleet of fanatics from a unique Onslaught. " + Misc.ucFirst(getHisOrHer())
                        + " fanatics are also in possession of a pair of unique Dominators.";
                break;
            }
            case STAGE_LEVIATHAN: {
                targetDescLong = "%s, formerly a multi-millionaire trade magnate. However, " + getHeOrShe()
                        + " recently fell afoul of the authorities after inciting and exploiting several food shortages. "
                        + person.getName().getLast() + " is currently on the run, commanding a large privateer armada "
                        + "from " + getHisOrHer() + " massive unique Atlas and a pair of unique Phaetons.";
                break;
            }
            case STAGE_POSEIDON: {
                targetDescLong = "%s, a known terrorist at large. Calls for mercenaries and bounty hunters to take this "
                        + " dangerous individual down have been met with failure. " + person.getName().getLast()
                        + " owns a unique Charybdis, which " + getHeOrShe() + " reportedly plundered from the Anar shipyards.";
                break;
            }
            case STAGE_ILIAD: {
                targetDescLong = "%s, a well-known admiral, famously wheelchair-bound due to injuries sustained during "
                        + getHisOrHer() + " final battle. Now, " + getHeOrShe() + " seeks to regain " + getHisOrHer()
                        + " former glory by taking " + getHisOrHer() + " armada on a crash course through the sector, "
                        + "purportedly causing as much damage as possible. " + person.getName().getLast() + " commands "
                        + getHisOrHer() + " signature unique Odyssey and unique Aurora.";
                break;
            }
            case STAGE_FRANKENSTEIN: {
                targetDescLong = "Sir %s, who once inherited a tremendous fortune from " + getHisOrHer()
                        + " father, including a unique hull vaguely describable as an unholy amalgamation of dead ships. While "
                        + person.getName().getLast() + " was once considered a respected knight, " + getHeOrShe()
                        + " has since let " + getHisOrHer() + " vast wealth get to " + getHisOrHer() + " head, engaging in a "
                        + "rampaging joyride, causing countless deaths along the way.";
                break;
            }
            case STAGE_LUCIFER: {
                targetDescLong = "%s, a failed Tri-Tachyon Alpha AI experiment. After falling rampant, " + getHeOrShe()
                        + " stole a set of Domain-era schematics for prototype phase coils. We recently discovered that "
                        + person.getName().getLast() + " put this technology to use, creating a powerful fleet consisting "
                        + "entirely of unique phase ships. " + Misc.ucFirst(getHisOrHer()) + " continued existence cannot be allowed.";
                break;
            }
            case STAGE_ODIN: {
                targetDescLong = "an artificial entity, designated %s. Being an ancient AI, " + getHeOrShe()
                        + " predates the Collapse. For reasons of international security, this AI must be destroyed as soon as possible. "
                        + person.getName().getLast() + " was last seen controlling a unique Mimir and six unique Potnia-bisses.";
                break;
            }
            case STAGE_EUPHORIA: {
                targetDescLong = "famed drug magnate %s, wanted on charges of terrorism and crimes against humanity. "
                        + Misc.ucFirst(getHeOrShe()) + " is known to have uploaded his mind into an Alpha core, using " + getHisOrHer()
                        + " incredible mental capacity to throw several economies into chaos. " + person.getName().getLast()
                        + " is also known to have connections to the Starlight Cabal, having quickly acquired a fleet of "
                        + "high-end warships via mysterious means. " + person.getName().getLast()
                        + " was last seen commanding a unique Astral.";
                break;
            }
            case STAGE_CLERIC: {
                targetDescLong = "%s, an estranged Templar leader. " + person.getName().getLast() + " seeks to destroy everything "
                        + getHeOrShe() + " can, so the IBB has decided that this indivudal must be stopped. " + person.getName().getLast()
                        + "'s fleet contains a unique Paladin of unknown origin.";
                break;
            }
            case STAGE_TITAN_X: {
                targetDescLong = "a deranged madman by the name of %s. This monster has been capturing various faction officials, "
                        + "committing psychological torture of the most vile sort, no doubt toward some nefarious ends. "
                        + "Worse still, every fleet we've sent to deal with " + getHimOrHer()
                        + " has mysteriously vanished without a trace. Whole fleets just... missing. Commander, " + person.getName().getLast()
                        + " must be stopped, but be careful; " + getHisOrHer() + " fleet contains three unique Excelsiors of Imperial design.";
                break;
            }
            case STAGE_SPORESHIP: {
                targetDescLong = "%s, an unknown AI entity, theorized to be a cluster of Alphas working in tandem. " + Misc.ucFirst(getHeOrShe())
                        + " has somehow obtained a Domain-era sporeship, a legendary type of vessel. " + person.getName().getLast()
                        + "'s intentions are unknown at this point, but " + getHeOrShe() + " appears to be building up a "
                        + "fleet of automated ships. This development is a threat to the Persean Sector at large.";
                break;
            }
            case STAGE_POPE: {
                targetDescLong = "%s. We have little information on the target. The presumption is that " + getHeOrShe()
                        + " is aligned with the Knights Templar in some way, judging by " + person.getName().getLast()
                        + "'s unique Archbishop of immense power and accompanying fleet of cult followers.";
                break;
            }
            case STAGE_ZEUS: {
                targetDescLong = "IBB founder %s, who has gone AWOL. Before going rampant, " + getHeOrShe()
                        + " obtained a unique Paragon, a unique Conquest, and a unique Onslaught. Be advised: "
                        + person.getName().getLast() + " is a gifted tactician and a master of starship modification.";
                break;
            }
            default:
                log.info(String.format("No definition for stage %s", thisStage.name()));
                thisStage = null;
                ended = true;
                break;
        }
    }

    public void start() {
        if (person == null) {
            log.error("Failed to start: no person");
            endImmediately();
            return;
        }

        if (isDone()) {
            log.error("Failed to start: ended itself prematurely");
            return;
        }

        pickHideoutLocation();

        if (isDone()) {
            log.error("Failed to start: hideout location invalid");
            return;
        }

        spawnFleet();

        if (!isDone()) {
            if (!Global.getSector().getScripts().contains(this)) {
                Global.getSector().addScript(this);
            }
        }

        SWP_IBBTracker.getTracker().reportStageBegan(thisStage);

//        Global.getSector().reportEventStage(this, "start", null, messagePriority, new BaseOnMessageDeliveryScript() {
//            @Override
//            public void beforeDelivery(CommMessageAPI message) {
//                if (hideoutLocation.getContainingLocation() instanceof StarSystemAPI) {
//                    message.setStarSystemId(hideoutLocation.getContainingLocation().getId());
//                }
//            }
//        });
        log.info(String.format("Starting famous bounty for person %s at %s", person.getName().getFullName(), hideoutLocation.getContainingLocation().getName()));
    }

    @Override
    public boolean runWhilePaused() {
        return false;
    }

    @Override
    public void reportRemovedIntel() {
        super.reportRemovedIntel();
        Global.getSector().removeScript(this);
    }

    private void pickHideoutLocation() {
        float emptyWeight = 1f;
        float occupiedWeight = 0.2f;

        float multMod = 1f;
        while (hideoutLocation == null && multMod <= 5f) {
            WeightedRandomPicker<StarSystemAPI> systemPicker = new WeightedRandomPicker<>();
            for (StarSystemAPI system : Global.getSector().getStarSystems()) {
                float weight = system.getPlanets().size();
                float mult = 0f;

                if (system.hasPulsar() || system.hasTag("ix_fortified") || system.hasTag("oci_theme")
                        || system.hasTag("theme_breakers_suppressed") || system.hasTag("theme_breakers_resurgent")) {
                    continue;
                }

                if (system.hasTag(Tags.THEME_MISC_SKIP)) {
                    mult = 1f;
                } else if (system.hasTag(Tags.THEME_MISC)) {
                    mult = 3f;
                } else if (system.hasTag(Tags.THEME_REMNANT_NO_FLEETS)) {
                    mult = 3f;
                } else if (system.hasTag(Tags.THEME_RUINS)) {
                    mult = 5f;
                } else if (system.hasTag(Tags.THEME_REMNANT_DESTROYED)) {
                    mult = 3f;
                } else if (system.hasTag(Tags.THEME_CORE_UNPOPULATED)) {
                    mult = 1f;
                } else if (system.hasTag("theme_breakers_destroyed")) {
                    mult = 3f;
                }

                if (system.getConstellation() == null) {
                    mult *= 0.01f;
                }

                boolean viablePlanets = false;
                for (SectorEntityToken planet : system.getPlanets()) {
                    if (planet.isStar()) {
                        continue;
                    }
                    MarketAPI nearestMarket = findNearestMarket(planet, 3000f);
                    if (nearestMarket != null) {
                        continue;
                    }
                    viablePlanets = true;
                    break;
                }
                if (!viablePlanets) {
                    continue;
                }

                boolean empty = true;
                for (SectorEntityToken planet : system.getPlanets()) {
                    MarketAPI nearestMarket = findNearestMarket(planet, 3000f);
                    if (nearestMarket != null) {
                        continue;
                    }

                    if (planet.getMarket() != null && !planet.getMarket().isPlanetConditionMarketOnly()) {
                        empty = false;
                        break;
                    }
                }

                if (empty) {
                    mult *= emptyWeight;
                } else {
                    mult *= occupiedWeight;
                }

                if (mult <= 0) {
                    continue;
                }
                float minDist = 3000f + (thisStage.ordinal() + 1) * 200f;
                float maxDist = 30000f + (thisStage.ordinal() + 1) * 1000f;
                maxDist *= multMod;
                minDist /= multMod;
                float dist = system.getLocation().length();
                float distMult = (Math.max(0, maxDist - dist) / maxDist) * (Math.max(0, dist - minDist) / minDist);

                systemPicker.add(system, weight * mult * distMult);
            }

            StarSystemAPI system = systemPicker.pick();

            if (system != null) {
                WeightedRandomPicker<SectorEntityToken> picker = new WeightedRandomPicker<>();
                for (SectorEntityToken planet : system.getPlanets()) {
                    if (planet.isStar()) {
                        continue;
                    }
                    MarketAPI nearestMarket = findNearestMarket(planet, 3000f);
                    if (nearestMarket != null) {
                        continue;
                    }

                    picker.add(planet);
                }
                hideoutLocation = picker.pick();
            }

            multMod += 1f;
        }

        if (hideoutLocation == null) {
            log.info(String.format("Failed to find hideout for person %s", person.getName().getFullName()));
            endImmediately();
        }
    }

    protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) {
        Color h = Misc.getHighlightColor();
        Color g = Misc.getGrayColor();
        float pad = 3f;
        float opad = 10f;

        float initPad = pad;
        if (mode == ListInfoMode.IN_DESC) {
            initPad = opad;
        }

        Color tc = getBulletColorForMode(mode);

        bullet(info);
        boolean isUpdate = getListInfoParam() != null;

        if (result == null) {
            if (mode == ListInfoMode.IN_DESC) {
                info.addPara("%s reward", initPad, tc, h, Misc.getDGSCredits(bountyCredits));
            } else if (!isEnding()) {
                info.addPara("%s reward", 0f, tc, h, Misc.getDGSCredits(bountyCredits));
            }
            unindent(info);
            return;
        }

        switch (result.type) {
            case END_PLAYER_BOUNTY:
                info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(result.payment));
                break;
            case END_TAKEN:
            case END_OTHER:
                break;

        }

        unindent(info);
    }

    @Override
    public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) {
        Color h = Misc.getHighlightColor();
        Color g = Misc.getGrayColor();
        Color c = getTitleColor(mode);
        float pad = 3f;
        float opad = 10f;

        info.addPara(getName(), c, 0f);

        addBulletPoints(info, mode);

    }

    @Override
    public String getSortString() {
        return "IBB Mission";
    }

    public String getName() {
        String n = person.getName().getFullName();

        if (result != null) {
            switch (result.type) {
                case END_PLAYER_BOUNTY:
                    return "IBB Mission Completed - " + n;
                case END_OTHER:
                case END_TAKEN:
                    return "IBB Mission Ended - " + n;
            }
        }

        return "IBB Mission - " + n;
    }

    @Override
    public FactionAPI getFactionForUIColors() {
        return faction;
    }

    @Override
    public String getSmallDescriptionTitle() {
        return getName();
    }

    @Override
    public String getImportantIcon() {
        return Global.getSettings().getSpriteName("intel", "important_accepted_mission");
    }

    @Override
    public void createSmallDescription(TooltipMakerAPI info, float width, float height) {
        Color h = Misc.getHighlightColor();
        Color g = Misc.getGrayColor();
        float pad = 3f;
        float opad = 10f;

        String portraitSprite = person.getPortraitSprite();
        if (portraitSprite.contentEquals("graphics/imperium/portraits/ii_helmutreal.png")) {
            portraitSprite = "graphics/imperium/portraits/ii_helmut.png";
        }
        info.addImage(portraitSprite, width, 128, opad);

        info.addPara("Find and eliminate " + person.getName().getFullName() + " for the International Bounty Board.", opad, faction.getBaseUIColor());

        if (result != null) {
            if (result.type == IBBResultType.END_PLAYER_BOUNTY) {
                info.addPara("You have successfully completed this International Bounty Board mission.", opad);
            } else {
                info.addPara("This International Bounty Board mission is no longer on offer.", opad);
            }
        }

        addBulletPoints(info, ListInfoMode.IN_DESC);
        if (result == null) {
            if (hideoutLocation != null) {
                SectorEntityToken fake = hideoutLocation.getContainingLocation().createToken(0, 0);
                fake.setOrbit(Global.getFactory().createCircularOrbit(hideoutLocation, 0, 1000, 100));

                String loc = BreadcrumbSpecial.getLocatedString(fake);
                loc = loc.replaceAll("orbiting", "hiding out near");
                loc = loc.replaceAll("located in", "hiding out in");
                String sheWas = "She was";
                if (person.getGender() == Gender.MALE) {
                    sheWas = "He was";
                }
                info.addPara(sheWas + " last seen " + loc + ".", opad);
            }

            int cols = 7;
            float iconSize = width / cols;

            if (Global.getSettings().isDevMode()) {
                boolean deflate = false;
                if (!fleet.isInflated()) {
                    fleet.setFaction(faction.getId(), true);
                    fleet.inflateIfNeeded();
                    deflate = true;
                }

                String her = "her";
                if (person.getGender() == Gender.MALE) {
                    her = "his";
                }
                info.addPara("The IBB has disseminated partial intel on some of the ships in " + her + " fleet. (DEBUG: full info)", opad);
                info.addShipList(cols, (fleet.getMembersWithFightersCopy().size() + 6) / 7, iconSize, getFactionForUIColors().getBaseUIColor(), fleet.getMembersWithFightersCopy(), opad);

                if (deflate) {
                    fleet.deflate();
                }
            } else {
                boolean deflate = false;
                if (!fleet.isInflated()) {
                    fleet.setFaction(faction.getId(), true);
                    fleet.inflateIfNeeded();
                    deflate = true;
                }

                List<FleetMemberAPI> list = new ArrayList<>();
                Random random = new Random(person.getNameString().hashCode() * 170000);
                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();

                if (thisStage == FamousBountyStage.STAGE_TITAN_X) {
                    boolean flagshipPicked = false;
                    for (FleetMemberAPI member : members) {
                        if (!SWP_Util.SPECIAL_SHIPS.contains(member.getHullId())) {
                            continue;
                        }
                        if (member.getHullId().contentEquals("ii_boss_titanx")) {
                            continue;
                        }

                        PersonAPI fakePerson = Global.getFactory().createPerson();
                        fakePerson.setFaction(person.getFaction().getId());
                        fakePerson.setGender(person.getGender());
                        fakePerson.getName().setFirst(person.getName().getFirst());
                        fakePerson.getName().setLast(person.getName().getLast());
                        fakePerson.setPortraitSprite("graphics/imperium/portraits/ii_helmut.png");
                        FleetMemberAPI copy = Global.getFactory().createFleetMember(FleetMemberType.SHIP, member.getVariant());
                        if (!flagshipPicked) {
                            copy.setCaptain(fakePerson);
                            flagshipPicked = true;
                        }
                        list.add(copy);
                    }
                } else {
                    for (FleetMemberAPI member : members) {
                        if (!SWP_Util.SPECIAL_SHIPS.contains(member.getHullId())) {
                            continue;
                        }

                        FleetMemberAPI copy = Global.getFactory().createFleetMember(FleetMemberType.SHIP, member.getVariant());
                        if (member.isFlagship()) {
                            copy.setCaptain(person);
                        }
                        list.add(copy);
                    }
                }

                int max = 5 + list.size();
                for (FleetMemberAPI member : members) {
                    if (list.size() >= max || SWP_Util.SPECIAL_SHIPS.contains(member.getHullId())) {
                        continue;
                    }

                    if (member.isFighterWing()) {
                        continue;
                    }

                    float prob = (float) member.getFleetPointCost() / 20f;
                    prob += (float) max / (float) members.size();
                    if (member.isFlagship()) {
                        prob = 1f;
                    }

                    if (random.nextFloat() > prob) {
                        continue;
                    }

                    FleetMemberAPI copy = Global.getFactory().createFleetMember(FleetMemberType.SHIP, member.getVariant());
                    if (member.isFlagship()) {
                        copy.setCaptain(person);
                    }
                    list.add(copy);
                }

                if (!list.isEmpty()) {
                    String her = "her";
                    if (person.getGender() == Gender.MALE) {
                        her = "his";
                    }
                    info.addPara("The IBB has disseminated partial intel on some of the ships in " + her + " fleet.", opad);
                    info.addShipList(cols, Math.max(1, (max + 6) / 7), iconSize, getFactionForUIColors().getBaseUIColor(), list, opad);

                    int num = members.size() - list.size();
                    num = Math.round((float) num * (1f + random.nextFloat() * 0.5f));

                    if (num < 5) {
                        num = 0;
                    } else if (num < 10) {
                        num = 5;
                    } else if (num < 20) {
                        num = 10;
                    } else {
                        num = 20;
                    }

                    if (num > 1) {
                        info.addPara("The intel assessment notes the fleet may contain upwards of %s other ships"
                                + " of lesser significance.", opad, h, "" + num);
                    } else {
                        info.addPara("The intel assessment notes the fleet may contain several other ships"
                                + " of lesser significance.", opad);
                    }
                }

                if (deflate) {
                    fleet.deflate();
                }
            }
        }
    }

    @Override
    public String getIcon() {
        String portraitSprite = person.getPortraitSprite();
        if (portraitSprite.contentEquals("graphics/imperium/portraits/ii_helmutreal.png")) {
            portraitSprite = "graphics/imperium/portraits/ii_helmut.png";
        }
        return portraitSprite;
    }

    @Override
    public Set<String> getIntelTags(SectorMapAPI map) {
        Set<String> tags = super.getIntelTags(map);
        tags.add(Tags.INTEL_BOUNTY);
        tags.add(Tags.INTEL_MISSIONS);
        return tags;
    }

    @Override
    public SectorEntityToken getMapLocation(SectorMapAPI map) {
        Constellation c = hideoutLocation.getConstellation();
        SectorEntityToken entity = null;
        if (c != null && map != null) {
            entity = map.getConstellationLabelEntity(c);
        }
        if (entity == null) {
            entity = hideoutLocation;
        }
        return entity;
    }

    private void spawnFleet() {
        float qf = thisStage.qf;
        float commanderLevel = thisStage.level;
        int officerLevel = thisStage.officerLevel;
        int officerCount = thisStage.officerCount;
        int maxPts = thisStage.maxPts;
        int ptsFromBoss = thisStage.ptsFromBoss;
        String fleetFactionId = thisStage.fleetFaction;

        FactionDoctrineAPI doctrine = Global.getSector().getFaction(fleetFactionId).getDoctrine().clone();
        switch (thisStage) {
            case STAGE_LUCIFER:
                doctrine.setWarships(1);
                doctrine.setCarriers(1);
                doctrine.setPhaseShips(5);
                doctrine.setAggression(5);
                break;
            case STAGE_ZEUS:
                doctrine.setWarships(4);
                doctrine.setCarriers(2);
                doctrine.setPhaseShips(1);
                doctrine.setShipSize(4);
                break;
            default:
                break;
        }

        /* Trend taller */
        if ((maxPts >= 50) && (doctrine.getShipSize() <= 1)) {
            doctrine.setShipSize(doctrine.getShipSize() + 1);
            if (maxPts >= 250) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
            if (maxPts >= 500) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
            if (maxPts >= 800) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
        } else if ((maxPts >= 200) && (doctrine.getShipSize() <= 2)) {
            doctrine.setShipSize(doctrine.getShipSize() + 1);
            if (maxPts >= 400) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
            if (maxPts >= 650) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
        } else if ((maxPts >= 350) && (doctrine.getShipSize() <= 3)) {
            doctrine.setShipSize(doctrine.getShipSize() + 1);
            if (maxPts >= 550) {
                doctrine.setShipSize(doctrine.getShipSize() + 1);
            }
        } else if ((maxPts >= 500) && (doctrine.getShipSize() <= 4)) {
            doctrine.setShipSize(doctrine.getShipSize() + 1);
        }

        final FleetParamsV3 params = new FleetParamsV3(
                null,
                hideoutLocation.getLocationInHyperspace(),
                fleetFactionId,
                qf, // qualityOverride
                "famous_bounty",
                Math.max(maxPts, 10), // combatPts
                0f, // freighterPts
                0f, // tankerPts
                0f, // transportPts
                0f, // linerPts
                0f, // utilityPts
                0f); // qualityMod

        params.doctrineOverride = doctrine;
        params.commander = person;
        params.withOfficers = false;
        params.ignoreMarketFleetSizeMult = true;
        params.forceAllowPhaseShipsEtc = true;
        params.modeOverride = ShipPickMode.PRIORITY_THEN_ALL;
        params.doNotPrune = true;

        if (thisStage == FamousBountyStage.STAGE_SPORESHIP) {
            params.combatPts *= 0.67;
        }
        fleet = FleetFactoryV3.createFleet(params);
        if (fleet == null || fleet.isEmpty()) {
            endImmediately();
            return;
        }

        String fleetName = person.getName().getFirst() + " " + person.getName().getLast() + " (IBB)";
        fleet.setName(fleetName);

        FleetMemberAPI flagship = Global.getFactory().createFleetMember(FleetMemberType.SHIP, thisStage.flagship);
        flagshipName = thisStage.flagshipName;
        person.setPersonality(thisStage.personality);

        fleet.getMemoryWithoutUpdate().set("$banterText", thisStage.banterText);

        /* Bounty definitions */
        List<FleetMemberAPI> specialShips = new ArrayList<>(10);
        switch (thisStage) {
            case STAGE_UNDINE: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_lasher_r_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Salamander");
                specialShips.add(member);
                break;
            }
            case STAGE_HADES: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_cerberus_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Dis");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_cerberus_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Sheol");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_cerberus_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Hell");
                specialShips.add(member);
                break;
            }
            case STAGE_PONY: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_tarsus_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Carpal");
                specialShips.add(member);
                break;
            }
            case STAGE_FRACTURE: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_hammerhead_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Phillipshead");
                specialShips.add(member);
                break;
            }
            case STAGE_RAPTOR: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_medusa_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Gorgon");
                specialShips.add(member);
                break;
            }
            case STAGE_BULLSEYE: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "pack_bulldog_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Charlie");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_pitbull_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Buddy");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_pitbull_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Rocky");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_komondor_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Jake");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_komondor_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Jack");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_schnauzer_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Toby");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_schnauzer_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Cody");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_schnauzer_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Buster");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "pack_schnauzer_bullseye_Bullseye");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Duke");
                specialShips.add(member);
                break;
            }
            case STAGE_FRAMEBREAKER: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_dominator_luddic_path_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Ludd's Hammer");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_dominator_luddic_path_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Ludd's Wrath");
                specialShips.add(member);
                break;
            }
            case STAGE_EMPEROR: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_brawler_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Boxer");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_brawler_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Fighter");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_brawler_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Fencer");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_brawler_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Wrestler");
                specialShips.add(member);
                break;
            }
            case STAGE_LEVIATHAN: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_phaeton_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Clymene");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_phaeton_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Tethys");
                specialShips.add(member);
                break;
            }
            case STAGE_ILIAD: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_aurora_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Dawnstar");
                specialShips.add(member);
                break;
            }
            case STAGE_LUCIFER: {
                FleetMemberAPI member
                        = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_doom_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Baphomet");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_euryale_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Euryale");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_euryale_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Moloch");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_euryale_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Asmodeus");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_euryale_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Mammon");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_afflictor_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Tormentor");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_afflictor_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Iblis");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_afflictor_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Dagon");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_afflictor_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Beelzebub");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_shade_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Phantom");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_shade_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Azazel");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_shade_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Apollyon");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_shade_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Samael");
                specialShips.add(member);
                break;
            }
            case STAGE_ODIN: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Maleficent");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Malevolent");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Malicious");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Nefarious");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Vicious");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "msp_boss_potniaBis_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Wicked");
                specialShips.add(member);
                break;
            }
            case STAGE_TITAN_X: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_excelsior_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Royco");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_excelsior_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Upward");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_excelsior_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Ascension");
                specialShips.add(member);
                break;
            }
            case STAGE_SPORESHIP: {
                params.factionId = Factions.DERELICT;
                CampaignFleetAPI temp = FleetFactoryV3.createFleet(params);
                for (FleetMemberAPI member : temp.getFleetData().getMembersListCopy()) {
                    fleet.getFleetData().addFleetMember(member);
                }
                break;
            }
            case STAGE_ZEUS: {
                FleetMemberAPI member = Global.getFactory().createFleetMember(FleetMemberType.SHIP,
                        "swp_boss_conquest_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Nike");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_onslaught_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Ares");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_hyperion_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Helios");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_hyperion_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Solar");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_hyperion_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Planetar");
                specialShips.add(member);

                member = Global.getFactory().createFleetMember(FleetMemberType.SHIP, "swp_boss_hyperion_cus");
                fleet.getFleetData().addFleetMember(member);
                member.setShipName("Divinity");
                specialShips.add(member);
                break;
            }
            default: {
                break;
            }
        }

        flagship.setShipName(flagshipName);
        fleet.getFleetData().addFleetMember(flagship);
        fleet.getFleetData().setFlagship(flagship);
        fleet.setCommander(person);
        flagship.setCaptain(person);
        fleet.setTransponderOn(true);
        fleet.removeAbility(Abilities.GO_DARK);
        fleet.removeAbility(Abilities.TRANSPONDER);

        fleet.forceSync();

        addOfficersFamous(fleet, officerCount, officerLevel, specialShips, new Random());

        fleet.forceSync();

        /* We do this deferred level-up so that the commander's skills reflect the flagship type */
        float weight = FleetFactoryV3.getMemberWeight(fleet.getFlagship());
        float fighters = fleet.getFlagship().getVariant().getFittedWings().size();
        boolean wantCarrierSkills = weight > 0 && fighters / weight >= 0.5f;
        SkillPickPreference pref = SkillPickPreference.NON_CARRIER;
        if (wantCarrierSkills) {
            pref = SkillPickPreference.CARRIER;
        }
        levelOfficer(person, fleet.getFleetData(), thisStage.level, true, pref, new Random());

        /* Add in the commander skills, at last */
        FleetFactoryV3.addCommanderSkills(person, fleet, new Random());
        SWP_Util.addMoreCommanderSkills(person, fleet, null, new Random());

        person.setRankId(Ranks.SPACE_ADMIRAL);
        person.setPostId(Ranks.POST_FLEET_COMMANDER);
        person.getStats().getShipOrdnancePointBonus().modifyPercent("ibb_bonus", thisStage.opBonus);

        fleet.getFleetData().sort();
        fleet.forceSync();
        fleet.updateCounts();

        Map<String, String> archetypeOverride = new HashMap<>(specialShips.size());
        for (FleetMemberAPI member : specialShips) {
            archetypeOverride.put(member.getId(), "ULTIMATE");
        }

        List<String> noRandomize = new ArrayList<>(1);
        noRandomize.add(flagship.getId());

        for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
            member.getRepairTracker().setCR(member.getRepairTracker().getMaxCR());
        }

        Misc.makeImportant(fleet, "swp_ibb");
        fleet.getMemoryWithoutUpdate().set("$level", commanderLevel);
        fleet.getMemoryWithoutUpdate().set("$opBonus", thisStage.opBonus);
        fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_FLEET_TYPE, "famousBounty");
        fleet.getMemoryWithoutUpdate().set("$famousBountyFaction", fleetFactionId);
        fleet.getMemoryWithoutUpdate().set("$stillAlive", true);
        fleet.getMemoryWithoutUpdate().set("$ds_archetype_override", archetypeOverride);
        fleet.getMemoryWithoutUpdate().set("$ds_do_not_randomize", noRandomize);
        fleet.getMemoryWithoutUpdate().set("$stageName", thisStage.name());
        fleet.getMemoryWithoutUpdate().set(MemFlags.MEMORY_KEY_MAKE_AGGRESSIVE, true);
        fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_INTERACTION_DIALOG_CONFIG_OVERRIDE_GEN,
                new IBBInteractionConfigGen());
        fleet.getMemoryWithoutUpdate().set(MemFlags.FLEET_NO_MILITARY_RESPONSE, true);

        fleet.setFaction(faction.getId(), true);
        fleet.setNoFactionInName(true);

        fleet.addEventListener(this);

        fleet.forceSync();

        LocationAPI location = hideoutLocation.getContainingLocation();
        location.addEntity(fleet);
        fleet.setLocation(hideoutLocation.getLocation().x - 500, hideoutLocation.getLocation().y + 500);
        fleet.getAI().addAssignment(FleetAssignment.ORBIT_AGGRESSIVE, hideoutLocation, 1000000f, null);
    }

    public static void addOfficersFamous(CampaignFleetAPI fleet, int numOfficers, int officerLevel, Collection<FleetMemberAPI> specialShips, Random random) {
        List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();

        WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<>(random);

        for (FleetMemberAPI member : members) {
            if (member.isFighterWing()) {
                continue;
            }
            if (member.isFlagship()) {
                continue;
            }
            if (!member.getCaptain().isDefault()) {
                continue;
            }

            float q = 1f;
            if (member.isCivilian()) {
                q *= 0.0001f;
            }
            for (FleetMemberAPI special : specialShips) {
                if (special.getId().contentEquals(member.getId())) {
                    q += 1000f;
                    break;
                }
            }
            float weight = member.getFleetPointCost() * q;
            picker.add(member, weight);
        }

        for (int i = 0; i < numOfficers; i++) {
            FleetMemberAPI member = picker.pickAndRemove();

            if (member == null) {
                break; // out of ships that need officers
            }

            float weight = FleetFactoryV3.getMemberWeight(member);
            float fighters = member.getVariant().getFittedWings().size();
            boolean wantCarrierSkills = weight > 0 && fighters / weight >= 0.5f;
            SkillPickPreference pref = SkillPickPreference.NON_CARRIER;
            if (wantCarrierSkills) {
                pref = SkillPickPreference.CARRIER;
            }

            PersonAPI person = OfficerManagerEvent.createOfficer(fleet.getFaction(), officerLevel, true, pref, random);
            if (person.getPersonalityAPI().getId().equals(Personalities.TIMID)) {
                person.setPersonality(Personalities.CAUTIOUS);
            }

            member.setCaptain(person);
        }
    }

    public static void levelOfficer(PersonAPI person, FleetDataAPI fleetData, int toLevel, boolean alwaysPickHigherSkill,
            SkillPickPreference pref, Random random) {
        if (random == null) {
            random = new Random();
        }

        OfficerLevelupPlugin plugin = (OfficerLevelupPlugin) Global.getSettings().getPlugin("officerLevelUp");
        if (toLevel > plugin.getMaxLevel(person)) {
            toLevel = plugin.getMaxLevel(person);
        }
        person.getStats().setSkipRefresh(true);

        OfficerDataAPI officerData = fleetData.getOfficerData(person);
        if (officerData == null) {
            officerData = Global.getFactory().createOfficerData(person);
        }

        long xp = plugin.getXPForLevel(toLevel);
        officerData.addXP(xp - person.getStats().getXP());
        while (officerData.canLevelUp()) {
            String skillId
                    = OfficerManagerEvent.pickSkill(officerData.getPerson(), officerData.getSkillPicks(),
                            alwaysPickHigherSkill, pref, random);
            if (skillId != null) {
                officerData.levelUp(skillId);
            }
        }

        person.getStats().setSkipRefresh(false);
        person.getStats().refreshCharacterStatsEffects();
    }

    public PersonAPI initPerson(FactionAPI bountyFaction) {
        PersonAPI bountyPerson = OfficerManagerEvent.createOfficer(bountyFaction, 1, true);
        bountyPerson.getName().setFirst(thisStage.firstName);
        bountyPerson.getName().setLast(thisStage.lastName);
        bountyPerson.getName().setGender(thisStage.gender);
        try {
            bountyPerson.setPortraitSprite(thisStage.portrait);
        } catch (Exception e) {
        }
        return bountyPerson;
    }

    public String fleetSizeString() {
        int pts = thisStage.maxPts + thisStage.ptsFromBoss;
//        if (pts < 50) {
//            return "very small";
//        } else if (pts < 100) {
//            return "small";
//        } else if (pts < 200) {
//            return "medium";
//        } else if (pts < 300) {
//            return "large";
//        } else if (pts < 450) {
//            return "very large";
//        } else if (pts < 600) {
//            return "huge";
//        } else {
//            return "gigantic";
//        }
        if (pts < 50) {
            return "modest";
        } else if (pts < 100) {
            return "fairly small";
        } else if (pts < 200) {
            return "decently-sized";
        } else if (pts < 300) {
            return "rather large";
        } else if (pts < 450) {
            return "particularly big";
        } else if (pts < 600) {
            return "enormous";
        } else {
            return "vast";
        }
    }

    private String getHeOrShe() {
        if (it) {
            return "it";
        } else if (person.getGender() == Gender.MALE) {
            return "he";
        } else {
            return "she";
        }
    }

    private String getHimOrHerself() {
        if (it) {
            return "itself";
        } else if (person.getGender() == Gender.MALE) {
            return "himself";
        } else {
            return "herself";
        }
    }

    private String getHisOrHer() {
        if (it) {
            return "its";
        } else if (person.getGender() == Gender.MALE) {
            return "his";
        } else {
            return "her";
        }
    }

    private String getHimOrHer() {
        if (it) {
            return "it";
        } else if (person.getGender() == Gender.MALE) {
            return "him";
        } else {
            return "her";
        }
    }

    public static enum IBBResultType {
        END_PLAYER_BOUNTY,
        END_OTHER,
        END_TAKEN,
    }

    public static class IBBResult {

        public IBBResultType type;
        public int payment;

        public IBBResult(IBBResultType type, int payment) {
            this.type = type;
            this.payment = payment;
        }
    }

    public static enum FamousBountyStage {

        STAGE_UNDINE("Francis", "Butler", Gender.MALE, false, "graphics/swp/portraits/ibb_butler.png",
                RequiredFaction.NONE, 53000,
                "swp_boss_lasher_b_cus", "Undine", "reckless",
                0.75f, 8, 6, 4, 30, 0f, 13, Factions.PIRATES,
                "So, you've come to claim our heads. You won't find us to be easy prey!"), // 0
        STAGE_HADES("Ryx", "Barlow", Gender.FEMALE, false, "graphics/swp/portraits/ibb_ryx.png",
                RequiredFaction.NONE, 80000,
                "swp_boss_cerberus_cus", "Hades", "steady",
                0.75f, 12, 7, 4, 45, 0f, 24, Factions.INDEPENDENT,
                "These ships are mine. You'll never take them from me!"), // 1
        STAGE_PONY("Lisa", "Nanao", Gender.FEMALE, false, "graphics/swp/portraits/ibb_nanao.png",
                RequiredFaction.NONE, 110000,
                "swp_boss_mule_cus", "Pony", "cautious",
                1f, 13, 8, 6, 55, 0f, 22, "domain",
                "We're done playing games. Prepare to die!"), // 2
        STAGE_ELDER_ORB("Ocula", "Gazer", Gender.FEMALE, false, "graphics/swp/portraits/ibb_gazer.png",
                RequiredFaction.NONE, 150000,
                "swp_boss_beholder_cus", "Elder Orb", "steady",
                1f, 14, 10, 7, 80, 0f, 18, Factions.DIKTAT,
                "Wow, would you look at the time? It's death o'clock!"), // 3
        STAGE_FRACTURE("Jaine", "Meredith", Gender.FEMALE, false, "graphics/swp/portraits/ibb_meredith.png",
                RequiredFaction.NONE, 180000,
                "swp_boss_sunder_cus", "Fracture", "steady",
                0.75f, 15, 10, 6, 115, 0f, 27, Factions.LIONS_GUARD,
                "I hereby declare war against you, commander. En garde!"), // 4
        STAGE_LIBERTY("Abrahamo", "Lincolni", Gender.MALE, false,
                "graphics/tiandong/portraits/tiandong_boss_abrahamo_lincolni.png",
                RequiredFaction.TIANDONG, 160000,
                "tiandong_boss_wuzhang_Standard", "Liberty", "reckless",
                0.75f, 16, 11, 6, 85, 0f, 18, "tiandong",
                "You look like you could use an extra large serving of freedom!"), // 5
        STAGE_RAPTOR("Blaize", "Rex", Gender.FEMALE, false, "graphics/swp/portraits/ibb_rex.png",
                RequiredFaction.NONE, 240000,
                "swp_boss_falcon_cus", "Raptor", "steady",
                1f, 15, 12, 8, 150, 0f, 28, "sector",
                "Those are some nice ships you've got there. It would be a shame if something were to happen to them..."), // 6
        STAGE_APEX("Mackie", "Corbin", Gender.MALE, false, "graphics/swp/portraits/ibb_corbin.png",
                RequiredFaction.NONE, 220000,
                "swp_boss_eagle_cus", "Apex", "aggressive",
                1f, 18, 13, 7, 120, 0f, 18, Factions.DIKTAT,
                "You're with THEM! The enemy! Call to arms, men! Make ready for war!"), // 7
        STAGE_BULLSEYE("Max", "Bailey", Gender.MALE, false, "graphics/portraits/portrait_hegemony06.png",
                RequiredFaction.JUNK_PIRATES, 310000,
                "pack_bulldog_bullseye_Bullseye", "Bullseye", "steady",
                1f, 16, 11, 10, 180, 0f, 85, "pack",
                "Woof woof, motherfucker!"), // 8
        STAGE_FRAMEBREAKER("Ned", "Ludd", Gender.MALE, false, "graphics/portraits/portrait_luddic00.png",
                RequiredFaction.NONE, 350000,
                "swp_boss_onslaught_luddic_path_cus", "Framebreaker", "reckless",
                0.5f, 17, 13, 9, 270, 0f, 62, Factions.LUDDIC_PATH,
                "Lo, the Lord hath fortold this Apocalypse, a great event done unto us an aeon ago. And how the fires hath henceforth swept the "
                + "black-as-sin void! Providence has brought another faithless heathen to me, here, today. The age of repentance is over; now, you "
                + "shall burn!"), // 9
        STAGE_EMPEROR("Johnny", "Major", Gender.MALE, false, "graphics/swp/portraits/ibb_major.png",
                RequiredFaction.NONE, 390000,
                "swp_boss_dominator_cus", "Emperor", "aggressive",
                0.75f, 18, 14, 12, 275, 0f, 48, Factions.HEGEMONY,
                "You're here for me, too? Well, I won't go down without a fight! Come get some!"), // 10
        STAGE_GULF("Teresa", "Terror", Gender.FEMALE, false, "graphics/swp/portraits/ibb_terror.png",
                RequiredFaction.DIABLE, 420000,
                "diableavionics_IBBgulf_fang", "Gulf", "steady",
                1f, 20, 15, 11, 295, 0f, 17, "diableavionics",
                "You call THAT a fleet? Hah! I will crush you like a bug under my heel."), // 11
        STAGE_LEVIATHAN("Elaine", "Megas", Gender.FEMALE, false, "graphics/swp/portraits/ibb_megas.png",
                RequiredFaction.NONE, 470000,
                "swp_boss_atlas_cus", "Leviathan", "cautious",
                0.5f, 19, 14, 15, 420, 0f, 42, Factions.PIRATES,
                "I was just trying to trade! This is bullshit!"), // 12
        STAGE_POSEIDON("Marjatta", "Nemo", Gender.FEMALE, false, "graphics/portraits/ms_portrait_001.png",
                RequiredFaction.SHADOWYARDS, 500000,
                "ms_boss_charybdis_cus", "Poseidon", "steady",
                1.25f, 20, 16, 12, 335, 0f, 20, "shadow_industry",
                "Do you have something I want? Hmm... Nah. I'll just blow you up."), // 13
        STAGE_YAMATO("Katsu", "Okita", Gender.MALE, false, "graphics/imperium/portraits/ii_okita.png",
                RequiredFaction.IMPERIUM, 420000,
                "ii_boss_dominus_cus", "Yamato", "aggressive",
                1f, 20, 16, 10, 255, 0f, 32, "interstellarimperium",
                "You won't stop me from saving my people!"), // 14
        STAGE_ILIAD("Clade", "Midnight", Gender.MALE, false, "graphics/swp/portraits/ibb_midnight.png",
                RequiredFaction.NONE, 460000,
                "swp_boss_odyssey_cus", "Iliad", "steady",
                1.25f, 20, 18, 10, 230, 0f, 47, Factions.TRITACHYON,
                "Sorry, commander. It's nothing personal, but I really want a good fight. Prepare yourself!"), // 15
        STAGE_FRANKENSTEIN("Xenno", "Aura", Gender.MALE, false, "graphics/swp/portraits/ibb_aura.png",
                RequiredFaction.NONE, 500000,
                "swp_boss_frankenstein_cus", "Frankenstein", "cautious",
                1f, 20, 20, 14, 215, 10f, 20, "domain",
                "Oops! XD"), // 16
        STAGE_ODIN("RA-002", "SHADOW", Gender.MALE, true, "graphics/portraits/ms_portrait_006.png",
                RequiredFaction.SHADOWYARDS, 660000,
                "ms_boss_mimir_cus", "Odin", "aggressive",
                0.75f, 20, 20, 14, 385, 10f, 96, "sector",
                "PURPOSE."), // 17
        STAGE_LUCIFER("TTX", "LaCROIX", Gender.FEMALE, true, "graphics/swp/portraits/ibb_lacroix.png",
                RequiredFaction.NONE, 570000,
                "swp_boss_doom_cus", "Lucifer", "reckless",
                1.25f, 20, 20, 15, 90, 10f, 232, Factions.TRITACHYON,
                "I am sorry."), // 18
        STAGE_EUPHORIA("Trilby", "Enlightenment", Gender.MALE, false, "graphics/uw/portraits/uw_in_this_moment_i_am_euphoric.png",
                RequiredFaction.CABAL, 750000,
                "uw_boss_astral_cus", "False God", "steady",
                1.25f, 20, 20, 15, 435, 10f, 35, "cabal",
                "I am at a higher state of being than you, enlightened by my own intelligence. I reject the blessings of your phony gods. In this moment, I am truly..."), // 19
        STAGE_CLERIC("Mathis", "Adelier", Gender.MALE, false, "graphics/templars/portraits/tem_portrait1.png",
                RequiredFaction.TEMPLARS, 790000,
                "tem_boss_paladin_cus", "Cleric", "reckless",
                1.25f, 20, 20, 15, 355, 10f, 45, "templars",
                "The time of forbearance has finished. The hour of victory, of justice, has come. And now, you shall join the fire."), // 20
        STAGE_TITAN_X("Helmut", "Pulmenti", Gender.MALE, false, "graphics/imperium/portraits/ii_helmutreal.png",
                RequiredFaction.IMPERIUM, 660000,
                "ii_boss_titanx_cus", "Hot Potato", "reckless",
                1.25f, 20, 20, 15, 260, 10f, 86, "ii_imperial_guard",
                "Hello! You've seen too much, my friend! So you need to die!"), // 21
        STAGE_POPE("Adam", "Tipper", Gender.MALE, true, "graphics/swp/portraits/ibb_tipper.png",
                RequiredFaction.TEMPLARS, 860000,
                "tem_boss_archbishop_cus", "Pope", "aggressive",
                1.25f, 20, 20, 18, 460, 15f, 60, "everything",
                "I. AM. ETERNAL."), // 22
        STAGE_SPORESHIP("El", "Psi", Gender.MALE, true, "graphics/swp/portraits/ibb_psi.png",
                RequiredFaction.NONE, 920000,
                "swp_boss_sporeship_cus", "DES S-02", "aggressive",
                1.25f, 20, 20, 20, 440, 15f, 150, Factions.REMNANTS,
                "You are not omega."), // 23
        STAGE_ZEUS("Gabriel", "Mosolov", Gender.MALE, false, "graphics/portraits/portrait_hegemony05.png",
                RequiredFaction.NONE, 1400000,
                "swp_boss_paragon_cus", "Zeus", "aggressive",
                1.25f, 20, 20, 20, 560, 20f, 161, "everything",
                "It was nice of you to kill off all of my competitors for me. You have my sincerest thanks. Now, you die."); // 24

        public final String firstName;
        public final String lastName;
        public final Gender gender;
        public final boolean machine;
        public final String portrait;

        public final RequiredFaction mod;
        public final int reward;

        public final String flagship;
        public final String flagshipName;
        public final String personality;

        public final float qf;
        public final int level;
        public final int officerLevel;
        public final int officerCount;
        public final int maxPts;
        public final float opBonus;
        public final int ptsFromBoss;

        public final String fleetFaction;

        public final String banterText;

        private FamousBountyStage(String firstName, String lastName, Gender gender, boolean machine, String portrait,
                RequiredFaction mod, int reward, String flagship, String flagshipName,
                String personality, float qf, int level, int officerLevel, int officerCount,
                int maxPts, float opBonus, int ptsFromBoss, String fleetFaction, String banterText) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.gender = gender;
            this.machine = machine;
            this.portrait = portrait;

            this.mod = mod;
            this.reward = reward;

            this.flagship = flagship;
            this.flagshipName = flagshipName;
            this.personality = personality;

            this.qf = qf;
            this.level = level;
            this.officerLevel = officerLevel;
            this.officerCount = officerCount;
            this.maxPts = maxPts;
            this.opBonus = opBonus;
            this.ptsFromBoss = ptsFromBoss;

            this.fleetFaction = fleetFaction;

            this.banterText = banterText;
        }
    }

    public static class IBBInteractionConfigGen implements FIDConfigGen {

        @Override
        public FIDConfig createConfig() {
            FIDConfig config = new FIDConfig();
            config.showWarningDialogWhenNotHostile = false;
            config.impactsEnemyReputation = false;
            config.showFleetAttitude = false;
            config.pullInAllies = false;
            config.pullInEnemies = false;

            config.delegate = new FIDDelegate() {
                @Override
                public void battleContextCreated(InteractionDialogAPI dialog, BattleCreationContext bcc) {
                    bcc.enemyDeployAll = true;
                }

                @Override
                public void notifyLeave(InteractionDialogAPI dialog) {
                }

                @Override
                public void postPlayerSalvageGeneration(InteractionDialogAPI dialog, FleetEncounterContext context,
                        CargoAPI salvage) {
                    if (!(dialog.getInteractionTarget() instanceof CampaignFleetAPI)) {
                        return;
                    }

                    CampaignFleetAPI fleet = (CampaignFleetAPI) dialog.getInteractionTarget();
                    String stageName = (String) fleet.getMemoryWithoutUpdate().get("$stageName");

                    if (stageName.contentEquals(FamousBountyStage.STAGE_SPORESHIP.name())) {
                        DataForEncounterSide data = context.getDataFor(fleet);
                        List<FleetMemberAPI> losses = new ArrayList<>();
                        for (FleetMemberData fmd : data.getOwnCasualties()) {
                            losses.add(fmd.getMember());
                        }

                        List<DropData> dropRandom = new ArrayList<>();

                        int[] counts = new int[5];
                        String[] groups = new String[]{Drops.REM_FRIGATE, Drops.REM_DESTROYER,
                            Drops.REM_CRUISER, Drops.REM_CAPITAL, Drops.GUARANTEED_ALPHA};

                        for (FleetMemberAPI member : losses) {
                            if (SWP_Util.getNonDHullId(member.getHullSpec()).contentEquals("swp_boss_sporeship")) {
                                counts[4] += 2;
                                counts[3] += 1;
                            } else if (member.getHullSpec().hasTag("remnant")) {
                                if (member.isCapital()) {
                                    counts[3] += 1;
                                } else if (member.isCruiser()) {
                                    counts[2] += 1;
                                } else if (member.isDestroyer()) {
                                    counts[1] += 1;
                                } else if (member.isFrigate()) {
                                    counts[0] += 1;
                                }
                            }
                        }

                        for (int i = 0; i < counts.length; i++) {
                            int count = counts[i];
                            if (count <= 0) {
                                continue;
                            }

                            DropData d = new DropData();
                            d.group = groups[i];
                            d.chances = (int) Math.ceil(count * 1f);
                            dropRandom.add(d);
                        }

                        Random salvageRandom = new Random(Misc.getSalvageSeed(fleet));
                        CargoAPI extra = SalvageEntity.generateSalvage(salvageRandom, 1f, 1f, 1f, 1f, null, dropRandom);
                        for (CargoStackAPI stack : extra.getStacksCopy()) {
                            salvage.addFromStack(stack);
                        }
                    }
                }
            };

            return config;
        }
    }
}
