1package com.fs.starfarer.api.impl;
4import java.util.ArrayList;
5import java.util.Iterator;
9import com.fs.starfarer.api.Global;
10import com.fs.starfarer.api.campaign.CampaignFleetAPI;
11import com.fs.starfarer.api.campaign.CargoAPI;
12import com.fs.starfarer.api.campaign.CargoStackAPI;
13import com.fs.starfarer.api.campaign.GenericPluginManagerAPI;
14import com.fs.starfarer.api.campaign.InteractionDialogAPI;
15import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
16import com.fs.starfarer.api.campaign.SectorEntityToken;
17import com.fs.starfarer.api.campaign.econ.MarketAPI;
18import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
19import com.fs.starfarer.api.campaign.listeners.CargoScreenListener;
20import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener;
21import com.fs.starfarer.api.campaign.listeners.CommodityIconProvider;
22import com.fs.starfarer.api.campaign.listeners.CommodityTooltipModifier;
23import com.fs.starfarer.api.campaign.listeners.GroundRaidObjectivesListener;
24import com.fs.starfarer.api.campaign.rules.MemoryAPI;
25import com.fs.starfarer.api.combat.MutableStat;
26import com.fs.starfarer.api.fleet.MutableFleetStatsAPI;
27import com.fs.starfarer.api.impl.campaign.CargoPodsEntityPlugin;
28import com.fs.starfarer.api.impl.campaign.graid.GroundRaidObjectivePlugin;
29import com.fs.starfarer.api.impl.campaign.ids.Commodities;
30import com.fs.starfarer.api.impl.campaign.ids.Stats;
31import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
32import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.RaidType;
33import com.fs.starfarer.api.ui.Alignment;
34import com.fs.starfarer.api.ui.LabelAPI;
35import com.fs.starfarer.api.ui.PositionAPI;
36import com.fs.starfarer.api.ui.TooltipMakerAPI;
37import com.fs.starfarer.api.util.Misc;
40 GroundRaidObjectivesListener,
41 CommodityTooltipModifier,
42 CommodityIconProvider,
51 public static enum PersonnelRank {
52 REGULAR(
"Regular",
"icon_crew_green", 0.25f),
53 EXPERIENCED(
"Experienced",
"icon_crew_regular", 0.5f),
54 VETERAN(
"Veteran",
"icon_crew_veteran", 0.75f),
55 ELITE(
"Elite",
"icon_crew_elite", 1f),
58 public String iconKey;
59 public float threshold;
60 private PersonnelRank(String name, String iconKey,
float threshold) {
62 this.iconKey = iconKey;
63 this.threshold = threshold;
66 public static PersonnelRank getRankForXP(
float xp) {
69 for (PersonnelRank rank : values()) {
70 if (f < rank.threshold) {
74 return PersonnelRank.ELITE;
78 public static class CommodityIconProviderWrapper {
79 public CargoStackAPI stack;
80 public CommodityIconProviderWrapper(CargoStackAPI stack) {
84 public static class CommodityDescriptionProviderWrapper {
85 public CargoStackAPI stack;
86 public CommodityDescriptionProviderWrapper(CargoStackAPI stack) {
91 public static class PersonnelData
implements Cloneable {
95 transient public float savedNum;
96 transient public float savedXP;
97 public PersonnelData(String
id) {
101 protected PersonnelData clone() {
103 PersonnelData copy = (PersonnelData) super.clone();
104 copy.savedNum = savedNum;
105 copy.savedXP= savedXP;
107 }
catch (CloneNotSupportedException e) {
108 throw new RuntimeException(e);
112 public void add(
int add) {
116 public void remove(
int remove,
boolean removeXP) {
119 if (
remove > num)
remove = (int) num;
120 if (removeXP) xp *= (num -
remove) / Math.max(1f, num);
124 xp = Math.min(xp, maxXP);
128 public void addXP(
float xp) {
131 this.xp = Math.min(this.xp, maxXP);
133 public void removeXP(
float xp) {
138 public float clampXP() {
141 this.xp = Math.min(this.xp, maxXP);
142 return Math.max(0f, prevXP - maxXP);
145 public void numMayHaveChanged(
float newNum,
boolean keepXP) {
152 xp *= newNum / Math.max(1f, num);
158 public float getXPLevel() {
159 float f = xp / Math.max(1f, num);
165 public PersonnelRank getRank() {
166 PersonnelRank rank = PersonnelRank.getRankForXP(getXPLevel());
170 public void integrateWithCurrentLocation(PersonnelAtEntity atLocation) {
172 int numTaken = (int) Math.round(num - savedNum);
173 if (atLocation !=
null) {
177 PersonnelData copy = atLocation.data;
180 }
else if (numTaken < 0) {
188 public static class PersonnelAtEntity
implements Cloneable {
189 public PersonnelData data;
190 public SectorEntityToken entity;
191 public String submarketId;
192 public PersonnelAtEntity(SectorEntityToken entity, String commodityId, String submarketId) {
193 this.entity = entity;
194 data =
new PersonnelData(commodityId);
195 this.submarketId = submarketId;
199 protected PersonnelAtEntity clone() {
201 PersonnelAtEntity copy = (PersonnelAtEntity) super.clone();
202 copy.data = data.clone();
204 }
catch (CloneNotSupportedException e) {
205 throw new RuntimeException(e);
211 public static final String
KEY =
"$core_personnelTracker";
222 protected PersonnelData
marineData =
new PersonnelData(Commodities.MARINES);
223 protected List<PersonnelAtEntity>
droppedOff =
new ArrayList<PersonnelAtEntity>();
225 protected transient SectorEntityToken
pods =
null;
231 GenericPluginManagerAPI plugins =
Global.
getSector().getGenericPlugins();
233 plugins.addPlugin(
this,
false);
262 if (
pods ==
null && dialog !=
null) {
263 SectorEntityToken target = dialog.getInteractionTarget();
264 if (target !=
null && target.getCustomPlugin() instanceof CargoPodsEntityPlugin) {
272 if (transaction.getMarket() ==
null ||
273 transaction.getMarket().getPrimaryEntity() ==
null ||
274 transaction.getSubmarket() ==
null)
return;
275 if (!transaction.getSubmarket().getSpecId().equals(Submarkets.SUBMARKET_STORAGE)) {
277 update(
false,
true,
null);
284 if (entity ==
null)
return;
286 SubmarketAPI sub = transaction.getSubmarket();
294 for (CargoStackAPI stack : transaction.getSold().getStacksCopy()) {
295 if (!stack.isPersonnelStack())
continue;
296 if (stack.isMarineStack()) {
297 PersonnelAtEntity at =
getDroppedOffAt(stack.getCommodityId(), entity, sub,
true);
299 int num = (int) stack.getSize();
304 for (CargoStackAPI stack : transaction.getBought().getStacksCopy()) {
305 if (!stack.isPersonnelStack())
continue;
306 if (stack.isMarineStack()) {
307 PersonnelAtEntity at =
getDroppedOffAt(stack.getCommodityId(), entity, sub,
true);
309 int num = (int) stack.getSize();
318 public static void transferPersonnel(PersonnelData from, PersonnelData to,
int num, PersonnelData keepsXP) {
319 if (num > from.num) {
320 num = (int) from.num;
322 if (num <= 0)
return;
326 from.remove(num,
false);
328 float totalXP = to.xp + from.xp;
329 if (keepsXP == from) {
330 from.xp = Math.min(totalXP, from.num);
331 to.xp = Math.max(0f, totalXP - from.num);
332 }
else if (keepsXP == to) {
333 to.xp = Math.min(totalXP, to.num);
334 from.xp = Math.max(0f, totalXP - to.num);
337 float xp = from.xp * num / from.num;
342 from.remove(num,
true);
349 CargoAPI cargo = fleet.getCargo();
350 float marines = cargo.getMarines();
354 float total = marines + data.marinesLost;
355 float xpGain = 1f - data.raidEffectiveness;
358 if (xpGain < 0) xpGain = 0;
365 update(
false,
false,
null);
367 public void update(
boolean withIntegrationFromCurrentLocation,
boolean keepXP, CargoStackAPI stack) {
369 if (fleet ==
null)
return;
370 CargoAPI cargo = fleet.getCargo();
373 float marines = cargo.getMarines();
374 marineData.numMayHaveChanged(marines, keepXP);
376 if (withIntegrationFromCurrentLocation) {
379 marineData.integrateWithCurrentLocation(atLocation);
383 MutableFleetStatsAPI stats = fleet.getStats();
385 String
id =
"marineXP";
389 if (effectBonus > 0) {
391 stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).modifyPercent(
id, effectBonus, rank.name +
" marines");
394 stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).unmodifyPercent(
id);
396 if (casualtyReduction > 0) {
397 stats.getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT).modifyMult(
id, 1f - casualtyReduction * 0.01f, rank.name +
" marines");
399 stats.getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT).unmodifyMult(
id);
407 float f = data.getXPLevel();
412 float f = data.getXPLevel();
418 if (Commodities.MARINES.equals(stack.getCommodityId()) && !expanded) {
420 update(
true,
true, stack);
423 boolean nonPlayer =
false;
424 if (!stack.isInPlayerCargo()) {
442 Color h = Misc.getHighlightColor();
444 PersonnelRank rank = data.getRank();
446 LabelAPI heading = info.addSectionHeading(rank.name +
" Marines",
447 Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Alignment.MID, opad);
448 heading.autoSizeToWidth(info.getTextWidthOverride());
449 PositionAPI p = heading.getPosition();
450 p.setSize(p.getWidth(), p.getHeight() + 3f);
456 info.addPara(
"Regular marines - tough, competent, and disciplined.", opad);
458 info.addPara(
"These marines are mostly regulars and have seen some combat, " +
459 "but are not, overall, accustomed to your style of command.", opad);
464 info.addPara(
"Experienced marines with substantial training and a number of " +
465 "operations under their belts.", opad);
467 info.addPara(
"You've led these marines on several operations, and " +
468 "the experience gained by both parties is beginning to show concrete benefits.", opad);
473 info.addPara(
"These marines are veterans of many ground operations. " +
474 "Well-motivated and highly effective.", opad);
476 info.addPara(
"These marines are veterans of many ground operations under your leadership; " +
477 "the command structure is well established and highly effective.", opad);
482 info.addPara(
"These marines are an elite force, equipped, led, and motivated well " +
483 "above the standards of even the professional militaries in the Sector.", opad);
485 info.addPara(
"These marines are an elite force, equipped, led, and motivated well " +
486 "above the standards of even the professional militaries in the Sector.", opad);
494 MutableStat fake =
new MutableStat(1f);
495 fake.modifyPercentAlways(
"1", effectBonus,
"increased effectiveness of ground operations");
496 fake.modifyPercentAlways(
"2", -casualtyReduction,
"reduction to marine casualties suffered during ground operations");
497 info.addStatModGrid(width, 50f, 10f, opad, fake,
true,
null);
519 if (params instanceof CommodityIconProviderWrapper) {
520 CargoStackAPI stack = ((CommodityIconProviderWrapper) params).stack;
521 if (Commodities.MARINES.equals(stack.getCommodityId())) {
522 if (stack.isInPlayerCargo()) {
523 return GenericPluginManagerAPI.CORE_GENERAL;
528 if (atLocation !=
null) {
529 return GenericPluginManagerAPI.CORE_GENERAL;
544 if (stack.isPickedUp())
return null;
546 update(
true,
true, stack);
547 PersonnelData data =
null;
549 if (stack.isMarineStack()) {
551 if (!stack.isInPlayerCargo()) {
554 if (atLocation !=
null) {
555 data = atLocation.data;
564 if (data ==
null || data.num <= 0) {
569 PersonnelRank rank = data.getRank();
605 public void modifyRaidObjectives(MarketAPI market, SectorEntityToken entity, List<GroundRaidObjectivePlugin> objectives, RaidType type,
int marineTokens,
int priority) {
614 if (withDroppedOff) {
615 Iterator<PersonnelAtEntity> iter =
droppedOff.iterator();
616 while (iter.hasNext()) {
617 PersonnelAtEntity pae = iter.next();
618 if (!pae.entity.isAlive() || pae.data.num <= 0 || pae.data.xp <= 0) {
626 InteractionDialogAPI dialog =
Global.
getSector().getCampaignUI().getCurrentInteractionDialog();
627 SectorEntityToken entity =
null;
628 if (dialog !=
null) {
629 entity = dialog.getInteractionTarget();
630 if (entity !=
null && entity.getMarket() !=
null && entity.getMarket().getPrimaryEntity() !=
null) {
631 entity = entity.getMarket().getPrimaryEntity();
643 if (stack.getCargo() ==
null)
return null;
645 if (entity ==
null || entity.getMarket() ==
null || entity.getMarket().getSubmarketsCopy() ==
null)
return currSubmarket;
647 for (SubmarketAPI sub : entity.getMarket().getSubmarketsCopy()) {
648 if (sub.getCargo() == stack.getCargo()) {
655 public PersonnelAtEntity
getDroppedOffAt(String commodityId, SectorEntityToken entity, SubmarketAPI sub,
boolean createIfNull) {
656 String submarketId = sub ==
null ?
"" : sub.getSpecId();
658 String otherSubmarketId = pae.submarketId ==
null ?
"" : pae.submarketId;
659 if (entity == pae.entity && commodityId.equals(pae.data.id) && submarketId.equals(otherSubmarketId)) {
664 if (submarketId.isEmpty()) submarketId =
null;
665 PersonnelAtEntity pae =
new PersonnelAtEntity(entity, commodityId, submarketId);
674 PersonnelAtEntity atLocation = entity ==
null ? null :
getDroppedOffAt(commodityId, entity, sub,
false);
static SettingsAPI getSettings()
static SectorAPI getSector()
transient PersonnelData savedMarineData
void reportSubmarketOpened(SubmarketAPI submarket)
static boolean KEEP_XP_DURING_TRANSFERS
SectorEntityToken getInteractionEntity()
static float XP_PER_RAID_MULT
void reportPlayerClosedMarket(MarketAPI market)
void addSectionAfterPrice(TooltipMakerAPI info, float width, boolean expanded, CargoStackAPI stack)
PlayerFleetPersonnelTracker()
void processTransaction(PlayerMarketTransaction transaction, SectorEntityToken entity)
transient SubmarketAPI currSubmarket
float getMarineLossesReductionPercent(PersonnelData data)
transient SectorEntityToken pods
static float MAX_LOSS_REDUCTION_PERCENT
PersonnelAtEntity getDroppedOffAt(String commodityId, SectorEntityToken entity, SubmarketAPI sub, boolean createIfNull)
float getMarineEffectBonus(PersonnelData data)
static float MAX_EFFECTIVENESS_PERCENT
void modifyRaidObjectives(MarketAPI market, SectorEntityToken entity, List< GroundRaidObjectivePlugin > objectives, RaidType type, int marineTokens, int priority)
void reportCargoScreenOpened()
SubmarketAPI getSubmarketFor(CargoStackAPI stack)
String getIconName(CargoStackAPI stack)
void reportRaidObjectivesAchieved(RaidResultData data, InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
void reportPlayerLeftCargoPods(SectorEntityToken entity)
PersonnelAtEntity getPersonnelAtLocation(String commodityId, SubmarketAPI sub)
int getHandlingPriority(Object params)
String getRankIconName(CargoStackAPI stack)
transient List< PersonnelAtEntity > savedPersonnelData
void reportPlayerOpenedMarketAndCargoUpdated(MarketAPI market)
void reportPlayerOpenedMarket(MarketAPI market)
void reportPlayerNonMarketTransaction(PlayerMarketTransaction transaction, InteractionDialogAPI dialog)
List< PersonnelAtEntity > getDroppedOff()
void update(boolean withIntegrationFromCurrentLocation, boolean keepXP, CargoStackAPI stack)
void doCleanup(boolean withDroppedOff)
static void transferPersonnel(PersonnelData from, PersonnelData to, int num, PersonnelData keepsXP)
PersonnelData getMarineData()
static PlayerFleetPersonnelTracker getInstance()
void reportPlayerMarketTransaction(PlayerMarketTransaction transaction)
List< PersonnelAtEntity > droppedOff
String getSpriteName(String category, String id)