1package com.fs.starfarer.api.impl.campaign.intel;
4import java.util.ArrayList;
6import java.util.Random;
9import org.apache.log4j.Logger;
11import com.fs.starfarer.api.EveryFrameScript;
12import com.fs.starfarer.api.Global;
13import com.fs.starfarer.api.campaign.BattleAPI;
14import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
15import com.fs.starfarer.api.campaign.CampaignFleetAPI;
16import com.fs.starfarer.api.campaign.FactionAPI;
17import com.fs.starfarer.api.campaign.FactionAPI.ShipPickMode;
18import com.fs.starfarer.api.campaign.FleetAssignment;
19import com.fs.starfarer.api.campaign.LocationAPI;
20import com.fs.starfarer.api.campaign.PlanetAPI;
21import com.fs.starfarer.api.campaign.RepLevel;
22import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult;
23import com.fs.starfarer.api.campaign.SectorEntityToken;
24import com.fs.starfarer.api.campaign.StarSystemAPI;
25import com.fs.starfarer.api.campaign.econ.MarketAPI;
26import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
27import com.fs.starfarer.api.characters.FullName.Gender;
28import com.fs.starfarer.api.characters.PersonAPI;
29import com.fs.starfarer.api.fleet.FleetMemberAPI;
30import com.fs.starfarer.api.fleet.FleetMemberType;
31import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin;
32import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope;
33import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions;
34import com.fs.starfarer.api.impl.campaign.DebugFlags;
35import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent;
36import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3;
37import com.fs.starfarer.api.impl.campaign.fleets.FleetParamsV3;
38import com.fs.starfarer.api.impl.campaign.ids.Factions;
39import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
40import com.fs.starfarer.api.impl.campaign.ids.Industries;
41import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
42import com.fs.starfarer.api.impl.campaign.ids.Ranks;
43import com.fs.starfarer.api.impl.campaign.ids.Tags;
44import com.fs.starfarer.api.impl.campaign.intel.bases.PirateBaseManager;
45import com.fs.starfarer.api.impl.campaign.procgen.Constellation;
46import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BreadcrumbSpecial;
47import com.fs.starfarer.api.impl.campaign.shared.PersonBountyEventData;
48import com.fs.starfarer.api.impl.campaign.shared.SharedData;
49import com.fs.starfarer.api.ui.SectorMapAPI;
50import com.fs.starfarer.api.ui.TooltipMakerAPI;
51import com.fs.starfarer.api.util.Misc;
52import com.fs.starfarer.api.util.WeightedRandomPicker;
57 public static enum BountyType {
67 private float elapsedDays = 0f;
69 private float bountyCredits = 0;
71 private FactionAPI faction;
72 private PersonAPI person;
73 private CampaignFleetAPI fleet;
74 private FleetMemberAPI flagship;
76 private BountyType bountyType;
79 private SectorEntityToken hideoutLocation =
null;
81 private int level = 0;
88 this.elapsedDays = elapsedDays;
92 return SharedData.getData().getPersonBountyEventData();
107 if (bountyType == BountyType.DESERTER) {
108 bountyCredits *= 1.5f;
117 log.info(String.format(
"Starting person bounty by faction [%s] for person %s", faction.getDisplayName(), person.getName().getFullName()));
124 duration = Math.max(duration * 0.5f, Math.min(duration * 2f,
MAX_DURATION));
138 float timeFactor = (PirateBaseManager.getInstance().getDaysSinceStart() - 180f) / (365f * 2f);
139 if (timeFactor < 0) timeFactor = 0;
140 if (timeFactor > 1) timeFactor = 1;
152 if (base > 10) base = 10;
154 boolean hasLow =
false;
155 boolean hasHigh =
false;
161 if (curr < base || curr == 0) hasLow =
true;
162 if (curr > base) hasHigh =
true;
169 }
else if (!hasHigh) {
170 level +=
new Random().nextInt(3) + 2;
173 if (level < 0) level = 0;
181 WeightedRandomPicker<StarSystemAPI> systemPicker =
new WeightedRandomPicker<StarSystemAPI>();
185 if (system.hasPulsar())
continue;
187 if (system.hasTag(Tags.THEME_MISC_SKIP)) {
189 }
else if (system.hasTag(Tags.THEME_MISC)) {
191 }
else if (system.hasTag(Tags.THEME_REMNANT_NO_FLEETS)) {
193 }
else if (system.hasTag(Tags.THEME_RUINS)) {
195 }
else if (system.hasTag(Tags.THEME_REMNANT_DESTROYED)) {
197 }
else if (system.hasTag(Tags.THEME_CORE_UNPOPULATED)) {
201 for (MarketAPI market : Misc.getMarketsInLocation(system)) {
202 if (market.isHidden())
continue;
207 float distToPlayer = Misc.getDistanceToPlayerLY(system.getLocation());
209 if (distToPlayer < noSpawnRange) mult = 0f;
211 if (mult <= 0)
continue;
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;
224 float dist = system.getLocation().length();
225 float distMult = Math.max(0, 50000f - dist);
227 systemPicker.add(system, weight * mult * distMult);
230 StarSystemAPI system = systemPicker.pick();
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;
241 hideoutLocation = picker.pick();
245 if (hideoutLocation ==
null) {
252 private void pickFaction() {
255 String commFacId = Misc.getCommissionFactionId();
256 boolean forceCommissionFaction =
true;
257 if (commFacId !=
null &&
getSharedData().isParticipating(commFacId)) {
260 if (bounty.faction !=
null && bounty.faction.getId().equals(commFacId)) {
261 forceCommissionFaction =
false;
265 forceCommissionFaction =
false;
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;
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;
280 if (market.getFaction() !=
null) {
281 if (forceCommissionFaction && !market.getFaction().getId().equals(commFacId)) {
285 if (market.getFaction().isHostileTo(player)) {
295 picker.add(market, weight);
299 if (picker.isEmpty()) {
304 MarketAPI market = picker.pick();
305 faction = market.getFaction();
308 private void initBountyAmount() {
310 float highStabilityMult = 1f;
311 float base = Global.getSettings().getFloat(
"basePersonBounty");
312 float perLevel = Global.getSettings().getFloat(
"personBountyPerLevel");
314 float random = perLevel * (int)(Math.random() * 15) / 15f;
316 bountyCredits = (int) ((base + perLevel * level + random) * highStabilityMult);
319 private void initPerson() {
320 String factionId = Factions.PIRATES;
321 if (bountyType == BountyType.DESERTER) {
322 factionId = faction.getId();
324 int personLevel = (int) (5 + level * 1.5f);
325 person = OfficerManagerEvent.createOfficer(Global.getSector().getFaction(factionId),
328 person.setRankId(Ranks.SPACE_ADMIRAL);
330 person.setRankId(Ranks.SPACE_CAPTAIN);
334 private void pickBountyType() {
335 WeightedRandomPicker<BountyType> picker =
new WeightedRandomPicker<BountyType>();
336 picker.add(BountyType.PIRATE, 10f);
340 picker.add(BountyType.DESERTER, 30f);
342 bountyType = picker.pick();
418 if (elapsedDays >= duration && !
isDone()) {
419 boolean canEnd = fleet ==
null || !fleet.isInCurrentLocation();
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);
429 if (fleet ==
null)
return;
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);
437 if (fleet.getFlagship() ==
null || fleet.getFlagship().getCaptain() != person) {
438 result =
new BountyResult(BountyResultType.END_OTHER, 0,
null);
439 boolean current = fleet.isInCurrentLocation();
447 float f = 1f - elapsedDays / duration;
457 super.notifyEnding();
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);
478 if (
true)
return true;
480 RepLevel level = playerFleet.getFaction().getRelationshipLevel(faction);
481 return level.isAtWorst(RepLevel.SUSPICIOUS);
485 if (
true)
return true;
487 RepLevel level = playerFleet.getFaction().getRelationshipLevel(faction);
488 return level.isAtWorst(RepLevel.HOSTILE);
495 float distToPlayer = Misc.getDistance(fleet,
Global.
getSector().getPlayerFleet());
496 boolean playerInvolved = battle.isPlayerInvolved() || (fleet.isInCurrentLocation() && distToPlayer < 2000f);
498 if (battle.isInvolved(fleet) && !playerInvolved) {
499 if (fleet.getFlagship() ==
null || fleet.getFlagship().getCaptain() != person) {
500 fleet.setCommander(fleet.getFaction().createRandomPerson());
502 result =
new BountyResult(BountyResultType.END_OTHER, 0,
null);
510 if (!playerInvolved || !battle.isInvolved(fleet) || battle.onPlayerSide(fleet)) {
515 if (fleet.getFlagship() !=
null && fleet.getFlagship().getCaptain() == person)
return;
518 int payment = (int) bountyCredits;
520 result =
new BountyResult(BountyResultType.END_OTHER, 0,
null);
527 log.info(String.format(
"Paying bounty of %d from faction [%s]", (
int) payment, faction.getDisplayName()));
529 playerFleet.getCargo().getCredits().add(payment);
530 ReputationAdjustmentResult rep =
Global.
getSector().adjustPlayerReputation(
531 new RepActionEnvelope(RepActions.PERSON_BOUNTY_REWARD,
null,
null,
null,
true,
false),
533 result =
new BountyResult(BountyResultType.END_PLAYER_BOUNTY, payment, rep);
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),
540 result =
new BountyResult(BountyResultType.END_PLAYER_NO_BOUNTY, payment, rep);
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);
558 if (this.fleet == fleet) {
559 fleet.setCommander(fleet.getFaction().createRandomPerson());
560 result =
new BountyResult(BountyResultType.END_OTHER, 0,
null);
567 private void spawnFleet() {
571 String fleetFactionId = Factions.PIRATES;
572 if (bountyType == BountyType.DESERTER) {
573 fleetFactionId = faction.getId();
576 float qf = (float) level / 10f;
579 String fleetName =
"";
581 fleetName = person.getName().getLast() +
"'s" +
" Fleet";
583 float fp = (5 + level * 5) * 5f;
584 fp *= 0.75f + (float) Math.random() * 0.25f;
599 FactionAPI faction = Global.getSector().getFaction(fleetFactionId);
600 float maxFp = faction.getApproximateMaxFPPerFleet(ShipPickMode.PRIORITY_THEN_ALL) * 1.1f;
601 if (fp > maxFp) fp = maxFp;
603 FleetParamsV3 params =
new FleetParamsV3(
605 hideoutLocation.getLocationInHyperspace(),
608 FleetTypes.PERSON_BOUNTY_FLEET,
617 params.ignoreMarketFleetSizeMult =
true;
622 fleet = FleetFactoryV3.createFleet(params);
627 if (fleet ==
null || fleet.isEmpty()) {
632 fleet.setCommander(person);
633 fleet.getFlagship().setCaptain(person);
636 FleetFactoryV3.addCommanderSkills(person, fleet,
null);
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);
644 fleet.setNoFactionInName(
true);
645 fleet.setFaction(Factions.NEUTRAL,
true);
646 fleet.setName(fleetName);
648 fleet.addEventListener(
this);
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);
655 flagship = fleet.getFlagship();
665 public static enum BountyResultType {
667 END_PLAYER_NO_BOUNTY,
668 END_PLAYER_NO_REWARD,
673 public static class BountyResult {
674 public BountyResultType type;
676 public ReputationAdjustmentResult rep;
677 public BountyResult(BountyResultType type,
int payment, ReputationAdjustmentResult rep) {
679 this.payment = payment;
687 Color h = Misc.getHighlightColor();
688 Color g = Misc.getGrayColor();
693 if (mode == ListInfoMode.IN_DESC) initPad = opad;
705 if (mode == ListInfoMode.IN_DESC) {
715 info.addPara(
"%s reward", initPad, tc, h, Misc.getDGSCredits(bountyCredits));
717 int days = (int) (duration - elapsedDays);
721 addDays(info,
"remaining", days, tc);
723 info.addPara(
"Faction: " + faction.getDisplayName(), initPad, tc,
724 faction.getBaseUIColor(), faction.getDisplayName());
726 int days = (int) (duration - elapsedDays);
727 String daysStr =
"days";
732 info.addPara(
"%s reward, %s " + daysStr +
" remaining", 0f, tc,
733 h, Misc.getDGSCredits(bountyCredits),
"" + days);
741 case END_PLAYER_BOUNTY:
742 info.addPara(
"%s received", initPad, tc, h, Misc.getDGSCredits(
result.payment));
744 null,
null, info, tc, isUpdate, 0f);
746 case END_PLAYER_NO_BOUNTY:
748 null,
null, info, tc, isUpdate, 0f);
750 case END_PLAYER_NO_REWARD:
752 null,
null, info, tc, isUpdate, 0f);
765 Color h = Misc.getHighlightColor();
766 Color g = Misc.getGrayColor();
771 info.addPara(
getName(), c, 0f);
779 return "Personal Bounty";
783 String n = person.getName().getFullName();
787 case END_PLAYER_BOUNTY:
788 case END_PLAYER_NO_BOUNTY:
789 case END_PLAYER_NO_REWARD:
790 return "Bounty Completed - " + n;
793 return "Bounty Ended - " + n;
797 return "Personal Bounty - " + n;
811 Color h = Misc.getHighlightColor();
812 Color g = Misc.getGrayColor();
820 info.addImage(person.getPortraitSprite(), width, 128, opad);
822 String type =
"a notorious pirate";
823 if (bountyType == BountyType.DESERTER) type =
"a deserter";
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());
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());
845 info.addPara(
"This bounty is no longer on offer.", opad);
873 if (hideoutLocation !=
null) {
874 SectorEntityToken fake = hideoutLocation.getContainingLocation().createToken(0, 0);
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);
887 float iconSize = width / cols;
891 boolean deflate =
false;
892 if (!fleet.isInflated()) {
893 fleet.setFaction(Factions.PIRATES,
true);
894 fleet.inflateIfNeeded();
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);
903 info.addPara(
"level: " + level, 3f);
904 info.addPara(
"type: " + bountyType.name(), 3f);
910 boolean deflate =
false;
911 if (!fleet.isInflated()) {
912 fleet.setFaction(Factions.PIRATES,
true);
913 fleet.inflateIfNeeded();
917 List<FleetMemberAPI> list =
new ArrayList<FleetMemberAPI>();
918 Random random =
new Random(person.getNameString().hashCode() * 170000);
924 List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
926 for (FleetMemberAPI member : members) {
927 if (list.size() >= max)
break;
929 if (member.isFighterWing())
continue;
931 float prob = (float) member.getFleetPointCost() / 20f;
932 prob += (float) max / (
float) members.size();
933 if (member.isFlagship()) prob = 1f;
936 if (random.nextFloat() > prob)
continue;
939 if (member.isFlagship()) {
940 copy.setCaptain(person);
945 if (!list.isEmpty()) {
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);
951 int num = members.size() - list.size();
952 num = Math.round((
float)num * (1f + random.nextFloat() * 0.5f));
954 if (num < 5) num = 0;
955 else if (num < 10) num = 5;
956 else if (num < 20) num = 10;
960 info.addPara(
"The intel assessment notes the fleet may contain upwards of %s other ships" +
961 " of lesser significance.", opad, h,
"" + num);
963 info.addPara(
"The intel assessment notes the fleet may contain several other ships" +
964 " of lesser significance.", opad);
979 return person.getPortraitSprite();
983 Set<String> tags = super.getIntelTags(map);
984 tags.add(Tags.INTEL_BOUNTY);
985 tags.add(faction.getId());
991 Constellation c = hideoutLocation.getConstellation();
992 SectorEntityToken entity =
null;
993 if (c !=
null && map !=
null) {
994 entity = map.getConstellationLabelEntity(c);
996 if (entity ==
null) entity = hideoutLocation;
1013 this.duration = duration;
1017 return bountyCredits;
1021 this.bountyCredits = bountyCredits;
1029 this.bountyType = bountyType;
1041 return hideoutLocation;
static SettingsAPI getSettings()
static FactoryAPI getFactory()
static Logger getLogger(Class c)
static SectorAPI getSector()
static void addAdjustmentMessage(float delta, FactionAPI faction, PersonAPI person, TextPanelAPI panel, TooltipMakerAPI info, Color tc, boolean withCurrent, float pad)
static boolean PERSON_BOUNTY_DEBUG_INFO
List< EveryFrameScript > getActive()
void unindent(TooltipMakerAPI info)
void addDays(TooltipMakerAPI info, String after, float days)
void sendUpdateIfPlayerHasIntel(Object listInfoParam, TextPanelAPI textPanel)
Object getListInfoParam()
Color getBulletColorForMode(ListInfoMode mode)
void bullet(TooltipMakerAPI info)
Color getTitleColor(ListInfoMode mode)
void setDuration(float duration)
CampaignFleetAPI getFleet()
FleetMemberAPI getFlagship()
void reportMadeVisibleToPlayer()
void advanceImpl(float amount)
static PersonBountyEventData getSharedData()
SectorEntityToken getHideoutLocation()
void setBountyCredits(float bountyCredits)
void setBountyType(BountyType bountyType)
void setElapsedDays(float elapsedDays)
void cleanUpFleetAndEndIfNecessary()
BountyType getBountyType()
void pickHideoutLocation()
static float MAX_TIME_BASED_ADDED_LEVEL
boolean willRepIncrease()
void createSmallDescription(TooltipMakerAPI info, float width, float height)
void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode)
void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode)
String getSmallDescriptionTitle()
void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle)
void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param)
SectorEntityToken getMapLocation(SectorMapAPI map)
Set< String > getIntelTags(SectorMapAPI map)
float getTimeRemainingFraction()
static float MAX_DURATION
FactionAPI getFactionForUIColors()
static PersonBountyManager getInstance()
FleetMemberAPI createFleetMember(FleetMemberType type, String variantOrWingId)
OrbitAPI createCircularOrbit(SectorEntityToken focus, float angle, float orbitRadius, float orbitDays)
float getFloat(String key)