Starsector API
Loading...
Searching...
No Matches
PlayerFleetPersonnelTracker.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl;
2
3import java.util.ArrayList;
4import java.util.Iterator;
5import java.util.List;
6import java.util.Map;
7
8import java.awt.Color;
9
10import com.fs.starfarer.api.Global;
11import com.fs.starfarer.api.campaign.CampaignFleetAPI;
12import com.fs.starfarer.api.campaign.CargoAPI;
13import com.fs.starfarer.api.campaign.CargoStackAPI;
14import com.fs.starfarer.api.campaign.GenericPluginManagerAPI;
15import com.fs.starfarer.api.campaign.InteractionDialogAPI;
16import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
17import com.fs.starfarer.api.campaign.SectorEntityToken;
18import com.fs.starfarer.api.campaign.econ.MarketAPI;
19import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
20import com.fs.starfarer.api.campaign.listeners.CargoScreenListener;
21import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener;
22import com.fs.starfarer.api.campaign.listeners.CommodityIconProvider;
23import com.fs.starfarer.api.campaign.listeners.CommodityTooltipModifier;
24import com.fs.starfarer.api.campaign.listeners.GroundRaidObjectivesListener;
25import com.fs.starfarer.api.campaign.rules.MemoryAPI;
26import com.fs.starfarer.api.combat.MutableStat;
27import com.fs.starfarer.api.fleet.MutableFleetStatsAPI;
28import com.fs.starfarer.api.impl.campaign.CargoPodsEntityPlugin;
29import com.fs.starfarer.api.impl.campaign.graid.GroundRaidObjectivePlugin;
30import com.fs.starfarer.api.impl.campaign.ids.Commodities;
31import com.fs.starfarer.api.impl.campaign.ids.Stats;
32import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
33import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.RaidType;
34import com.fs.starfarer.api.ui.Alignment;
35import com.fs.starfarer.api.ui.LabelAPI;
36import com.fs.starfarer.api.ui.PositionAPI;
37import com.fs.starfarer.api.ui.TooltipMakerAPI;
38import com.fs.starfarer.api.util.Misc;
39
45
46 public static float XP_PER_RAID_MULT = 0.2f;
47 public static float MAX_EFFECTIVENESS_PERCENT = 100f;
48 public static float MAX_LOSS_REDUCTION_PERCENT = 50f;
49
50 public static boolean KEEP_XP_DURING_TRANSFERS = true;
51
52 public static enum PersonnelRank {
53 REGULAR("Regular", "icon_crew_green", 0.25f),
54 EXPERIENCED("Experienced", "icon_crew_regular", 0.5f),
55 VETERAN("Veteran", "icon_crew_veteran", 0.75f),
56 ELITE("Elite", "icon_crew_elite", 1f),
57 ;
58 public String name;
59 public String iconKey;
60 public float threshold;
61 private PersonnelRank(String name, String iconKey, float threshold) {
62 this.name = name;
63 this.iconKey = iconKey;
64 this.threshold = threshold;
65 }
66
67 public static PersonnelRank getRankForXP(float xp) {
68 //float f = xp /MAX_XP_LEVEL;
69 float f = xp;
70 for (PersonnelRank rank : values()) {
71 if (f < rank.threshold) {
72 return rank;
73 }
74 }
75 return PersonnelRank.ELITE;
76 }
77 }
78
79 public static class CommodityIconProviderWrapper {
80 public CargoStackAPI stack;
81 public CommodityIconProviderWrapper(CargoStackAPI stack) {
82 this.stack = stack;
83 }
84 }
85 public static class CommodityDescriptionProviderWrapper {
86 public CargoStackAPI stack;
87 public CommodityDescriptionProviderWrapper(CargoStackAPI stack) {
88 this.stack = stack;
89 }
90 }
91
92 public static class PersonnelData implements Cloneable {
93 public String id;
94 public float xp;
95 public float num;
96 transient public float savedNum;
97 transient public float savedXP;
98 public PersonnelData(String id) {
99 this.id = id;
100 }
101 @Override
102 protected PersonnelData clone() {
103 try {
104 PersonnelData copy = (PersonnelData) super.clone();
105 copy.savedNum = savedNum;
106 copy.savedXP = savedXP;
107 return copy;
108 } catch (CloneNotSupportedException e) {
109 throw new RuntimeException(e);
110 }
111 }
112
113 public void add(int add) {
114 num += add;
115 }
116
117 public void remove(int remove, boolean removeXP) {
118 if (!KEEP_XP_DURING_TRANSFERS) removeXP = true;
119
120 if (remove > num) remove = (int) num;
121 if (removeXP) xp *= (num - remove) / Math.max(1f, num);
122 num -= remove;
123 if (removeXP) {
124 float maxXP = num;
125 xp = Math.min(xp, maxXP);
126 }
127 }
128
129 public void addXP(float xp) {
130 this.xp += xp;
131 float maxXP = num;
132 this.xp = Math.min(this.xp, maxXP);
133 }
134 public void removeXP(float xp) {
135 this.xp -= xp;
136 if (xp < 0) xp = 0;
137 }
138
139 public float clampXP() {
140 float maxXP = num;
141 float prevXP = xp;
142 this.xp = Math.min(this.xp, maxXP);
143 return Math.max(0f, prevXP - maxXP);
144 }
145
146 public void numMayHaveChanged(float newNum, boolean keepXP) {
147 // if the number was reduced in some way (i.e. picking up a stack, or lost via code, w/e
148 // then adjust XP downwards in same proportion
149 if (num > newNum) {
150 if (keepXP) {
151 clampXP();
152 } else {
153 xp *= newNum / Math.max(1f, num);
154 }
155 }
156 num = newNum;
157 }
158
159 public float getXPLevel() {
160 float f = xp / Math.max(1f, num);
161 if (f < 0) f = 0;
162 if (f > 1f) f = 1f;
163 return f;
164 }
165
166 public PersonnelRank getRank() {
167 PersonnelRank rank = PersonnelRank.getRankForXP(getXPLevel());
168 return rank;
169 }
170
171 public void integrateWithCurrentLocation(PersonnelAtEntity atLocation) {
172 //int numTaken = (int) Math.max(0, num - savedNum);
173 int numTaken = (int) Math.round(num - savedNum);
174 if (atLocation != null) {// && numTaken > 0) {
175 num = savedNum;
176 xp = savedXP;
177 //PersonnelData copy = atLocation.data.clone();
178 PersonnelData copy = atLocation.data;
179 if (numTaken > 0) {
180 transferPersonnel(copy, this, numTaken, this);
181 } else if (numTaken < 0) {
182 transferPersonnel(this, copy, -numTaken, this);
183 }
184 }
185 }
186 }
187
188
189 public static class PersonnelAtEntity implements Cloneable {
190 public PersonnelData data;
191 public SectorEntityToken entity;
192 public String submarketId;
193 public PersonnelAtEntity(SectorEntityToken entity, String commodityId, String submarketId) {
194 this.entity = entity;
195 data = new PersonnelData(commodityId);
196 this.submarketId = submarketId;
197 }
198
199 @Override
200 protected PersonnelAtEntity clone() {
201 try {
202 PersonnelAtEntity copy = (PersonnelAtEntity) super.clone();
203 copy.data = data.clone();
204 return copy;
205 } catch (CloneNotSupportedException e) {
206 throw new RuntimeException(e);
207 }
208 }
209 }
210
211
212 public static final String KEY = "$core_personnelTracker";
213
215 Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
216 if (test == null) {// || true) {
217 test = new PlayerFleetPersonnelTracker();
219 }
220 return (PlayerFleetPersonnelTracker) test;
221 }
222
223 protected PersonnelData marineData = new PersonnelData(Commodities.MARINES);
224 protected List<PersonnelAtEntity> droppedOff = new ArrayList<PersonnelAtEntity>();
225
226 protected transient SectorEntityToken pods = null;
227 protected transient SubmarketAPI currSubmarket = null;
228
230 super();
231
233 //if (!plugins.hasPlugin(PlayerFleetPersonnelTracker.class)) {
234 plugins.addPlugin(this, false);
235 //}
236
237 //Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
239
240 //marineData.xp = 2600 * 0.7f;
241 //marineData.num = 2600;
242 update();
243 }
244
246 doCleanup(true);
247 update();
248 currSubmarket = null;
249
250 //marineData.xp = marineData.num * 0.7f;
251 }
252
253 public void reportSubmarketOpened(SubmarketAPI submarket) {
254 doCleanup(false);
255 currSubmarket = submarket;
256 }
257
259 pods = entity;
260 }
261
263 if (pods == null && dialog != null) {
264 SectorEntityToken target = dialog.getInteractionTarget();
265 if (target != null && target.getCustomPlugin() instanceof CargoPodsEntityPlugin) {
266 pods = target;
267 }
268 }
269 processTransaction(transaction, pods);
270 }
271
273 if (transaction.getMarket() == null ||
274 transaction.getMarket().getPrimaryEntity() == null ||
275 transaction.getSubmarket() == null) return;
276 if (!transaction.getSubmarket().getSpecId().equals(Submarkets.SUBMARKET_STORAGE)) {
277 doCleanup(true);
278 update(false, true, null);
279 return;
280 }
281 processTransaction(transaction, transaction.getMarket().getPrimaryEntity());
282 }
283
285 if (entity == null) return;
286
287 SubmarketAPI sub = transaction.getSubmarket();
288
289// // when ejecting cargo, there's a fake "storage" submarket, but when interacting with the pods, there's
290// // no submarket - so for pods to display rank correctly, set the submarket when dropping off pods to null
291// if (pods != null) {
292// sub = null;
293// }
294
295 for (CargoStackAPI stack : transaction.getSold().getStacksCopy()) {
296 if (!stack.isPersonnelStack()) continue;
297 if (stack.isMarineStack()) {
298 PersonnelAtEntity at = getDroppedOffAt(stack.getCommodityId(), entity, sub, true);
299
300 int num = (int) stack.getSize();
302 }
303 }
304
305 for (CargoStackAPI stack : transaction.getBought().getStacksCopy()) {
306 if (!stack.isPersonnelStack()) continue;
307 if (stack.isMarineStack()) {
308 PersonnelAtEntity at = getDroppedOffAt(stack.getCommodityId(), entity, sub, true);
309
310 int num = (int) stack.getSize();
312 }
313 }
314
315 doCleanup(true);
316 update();
317 }
318
319 public static void transferPersonnel(PersonnelData from, PersonnelData to, int num, PersonnelData keepsXP) {
320 if (num > from.num) {
321 num = (int) from.num;
322 }
323 if (num <= 0) return;
324
325 if (KEEP_XP_DURING_TRANSFERS && keepsXP != null) {
326 to.add(num);
327 from.remove(num, false);
328
329 float totalXP = to.xp + from.xp;
330 if (keepsXP == from) {
331 from.xp = Math.min(totalXP, from.num);
332 to.xp = Math.max(0f, totalXP - from.num);
333 } else if (keepsXP == to) {
334 to.xp = Math.min(totalXP, to.num);
335 from.xp = Math.max(0f, totalXP - to.num);
336 }
337 } else {
338 float xp = from.xp * num / from.num;
339
340 to.add(num);
341 to.addXP(xp);
342
343 from.remove(num, true); // also removes XP
344 }
345 }
346
347
348 public void reportRaidObjectivesAchieved(RaidResultData data, InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
350 CargoAPI cargo = fleet.getCargo();
351 float marines = cargo.getMarines();
352
353 marineData.remove(data.marinesLost, true);
354
355 float total = marines + data.marinesLost;
356 float xpGain = 1f - data.raidEffectiveness;
357 xpGain *= total;
358 xpGain *= XP_PER_RAID_MULT;
359 if (xpGain < 0) xpGain = 0;
360 marineData.addXP(xpGain);
361
362 update();
363 }
364
365 public void update() {
366 update(false, false, null);
367 }
368 public void update(boolean withIntegrationFromCurrentLocation, boolean keepXP, CargoStackAPI stack) {
370 if (fleet == null) return;
371 CargoAPI cargo = fleet.getCargo();
372
373
374 float marines = cargo.getMarines();
375 marineData.numMayHaveChanged(marines, keepXP);
376
377 if (withIntegrationFromCurrentLocation) {
378 //getDroppedOffAt(Commodities.MARINES, getInteractionEntity(), currSubmarket, true);
379 PersonnelAtEntity atLocation = getPersonnelAtLocation(Commodities.MARINES, currSubmarket);
380 marineData.integrateWithCurrentLocation(atLocation);
381 }
382
383
384 MutableFleetStatsAPI stats = fleet.getStats();
385
386 String id = "marineXP";
387 PersonnelRank rank = marineData.getRank();
388 float effectBonus = getMarineEffectBonus(marineData);
389 float casualtyReduction = getMarineLossesReductionPercent(marineData);
390 if (effectBonus > 0) {
391 //stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).modifyMult(id, 1f + effectBonus * 0.01f, rank.name + " marines");
392 stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).modifyPercent(id, effectBonus, rank.name + " marines");
393 } else {
394 //stats.getDynamic().getMod(Stats.PLANETARY_OPERATIONS_MOD).unmodifyMult(id);
396 }
397 if (casualtyReduction > 0) {
398 stats.getDynamic().getStat(Stats.PLANETARY_OPERATIONS_CASUALTIES_MULT).modifyMult(id, 1f - casualtyReduction * 0.01f, rank.name + " marines");
399 } else {
401 }
402 }
403
404
405
406
407 public static float getMarineEffectBonus(PersonnelData data) {
408 float f = data.getXPLevel();
409 //if (true) return 30f;
410 return Math.round(f * MAX_EFFECTIVENESS_PERCENT);
411 }
412 public static float getMarineLossesReductionPercent(PersonnelData data) {
413 float f = data.getXPLevel();
414 //if (true) return 30f;
415 return Math.round(f * MAX_LOSS_REDUCTION_PERCENT);
416 }
417
418 public void addSectionAfterPrice(TooltipMakerAPI info, float width, boolean expanded, CargoStackAPI stack) {
419 if (Commodities.MARINES.equals(stack.getCommodityId()) && !expanded) {
420 saveData();
421 update(true, true, stack);
422
423 PersonnelData data = marineData;
424 boolean nonPlayer = false;
425 if (!stack.isInPlayerCargo()) {
426 nonPlayer = true;
427 PersonnelAtEntity atLoc = getPersonnelAtLocation(stack.getCommodityId(), getSubmarketFor(stack));
428 if (atLoc != null) {
429 data = atLoc.data;
430 } else {
431 data = null;
432 }
433 }
434 //if (stack.isInPlayerCargo()) {
435 if (data != null) {
436 if (data.num <= 0) {
437 restoreData();
438 return;
439 }
440
441 float opad = 10f;
442 float pad = 3f;
443 Color h = Misc.getHighlightColor();
444
445 PersonnelRank rank = data.getRank();
446
447 LabelAPI heading = info.addSectionHeading(rank.name + " marines",
450 PositionAPI p = heading.getPosition();
451 p.setSize(p.getWidth(), p.getHeight() + 3f);
452
453
454 switch (rank) {
455 case REGULAR:
456 if (nonPlayer) {
457 info.addPara("Regular marines - tough, competent, and disciplined.", opad);
458 } else {
459 info.addPara("These marines are mostly regulars and have seen some combat, " +
460 "but are not, overall, accustomed to your style of command.", opad);
461 }
462 break;
463 case EXPERIENCED:
464 if (nonPlayer) {
465 info.addPara("Experienced marines with substantial training and a number of " +
466 "operations under their belts.", opad);
467 } else {
468 info.addPara("You've led these marines on several operations, and " +
469 "the experience gained by both parties is beginning to show concrete benefits.", opad);
470 }
471 break;
472 case VETERAN:
473 if (nonPlayer) {
474 info.addPara("These marines are veterans of many ground operations. " +
475 "Well-motivated and highly effective.", opad);
476 } else {
477 info.addPara("These marines are veterans of many ground operations under your leadership; " +
478 "the command structure is well established and highly effective.", opad);
479 }
480 break;
481 case ELITE:
482 if (nonPlayer) {
483 info.addPara("These marines are an elite force, equipped, led, and motivated well " +
484 "above the standards of even the professional militaries in the Sector.", opad);
485 } else {
486 info.addPara("These marines are an elite force, equipped, led, and motivated well " +
487 "above the standards of even the professional militaries in the Sector.", opad);
488 }
489 break;
490
491 }
492
493 float effectBonus = getMarineEffectBonus(data);
494 float casualtyReduction = getMarineLossesReductionPercent(data);
495 MutableStat fake = new MutableStat(1f);
496 fake.modifyPercentAlways("1", effectBonus, "increased effectiveness of ground operations");
497 fake.modifyPercentAlways("2", -casualtyReduction, "reduction to marine casualties suffered during ground operations");
498 info.addStatModGrid(width, 50f, 10f, opad, fake, true, null);
499
500 }
501 restoreData();
502 }
503 }
504
505
507 update();
508 }
510 update();
511 }
512
513
514 public String getIconName() {
515 return null;
516 }
517
518
519 public int getHandlingPriority(Object params) {
520 if (params instanceof CommodityIconProviderWrapper) {
521 CargoStackAPI stack = ((CommodityIconProviderWrapper) params).stack;
522 if (Commodities.MARINES.equals(stack.getCommodityId())) {
523 if (stack.isInPlayerCargo()) {
525 }
526
527 SubmarketAPI sub = getSubmarketFor(stack);
528 PersonnelAtEntity atLocation = getPersonnelAtLocation(stack.getCommodityId(), sub);
529 if (atLocation != null) {
531 }
532 }
533 }
534 return -1;
535 }
536
537// public PersonnelRank getFleetMarineRank() {
538// PersonnelAtEntity atLocation = getPersonnelAtLocation(Commodities.MARINES);
539// PersonnelRank rank = marineData.getRank(atLocation);
540// return rank;
541// }
542
543
544 public String getRankIconName(CargoStackAPI stack) {
545 if (stack.isPickedUp()) return null;
546 saveData();
547 update(true, true, stack);
548 PersonnelData data = null;
549
550 if (stack.isMarineStack()) {
551 data = marineData;
552 if (!stack.isInPlayerCargo()) {
553 SubmarketAPI sub = getSubmarketFor(stack);
554 PersonnelAtEntity atLocation = getPersonnelAtLocation(stack.getCommodityId(), sub);
555 if (atLocation != null) {
556 data = atLocation.data;
557 } else {
558 restoreData();
559 return null;
560 }
561 }
562 }
563
564
565 if (data == null || data.num <= 0) {
566 restoreData();
567 return null;
568 }
569
570 PersonnelRank rank = data.getRank();
571 restoreData();
572 return Global.getSettings().getSpriteName("ui", rank.iconKey);
573 }
574
575 public String getIconName(CargoStackAPI stack) {
576 return null;
577 }
578
579
580 protected transient PersonnelData savedMarineData;
581 protected transient List<PersonnelAtEntity> savedPersonnelData = new ArrayList<PersonnelAtEntity>();
582
583 public void saveData() {
585 marineData = marineData.clone();
586
587 savedPersonnelData = new ArrayList<PersonnelAtEntity>();
588 for (PersonnelAtEntity curr : droppedOff) {
589 savedPersonnelData.add(curr.clone());
590 }
591 }
592
593 public void restoreData() {
595 savedMarineData = null;
596
597 droppedOff.clear();
599 savedPersonnelData.clear();
600 }
601
602
605
606 public void modifyRaidObjectives(MarketAPI market, SectorEntityToken entity, List<GroundRaidObjectivePlugin> objectives, RaidType type, int marineTokens, int priority) {
607
608 }
609
610 protected void doCleanup(boolean withDroppedOff) {
611 marineData.savedNum = marineData.num;
612 marineData.savedXP = marineData.xp;
613 pods = null;
614
615 if (withDroppedOff) {
616 Iterator<PersonnelAtEntity> iter = droppedOff.iterator();
617 while (iter.hasNext()) {
618 PersonnelAtEntity pae = iter.next();
619 if (!pae.entity.isAlive() || pae.data.num <= 0 || pae.data.xp <= 0) {
620 iter.remove();
621 }
622 }
623 }
624 }
625
628 SectorEntityToken entity = null;
629 if (dialog != null) {
630 entity = dialog.getInteractionTarget();
631 if (entity != null && entity.getMarket() != null && entity.getMarket().getPrimaryEntity() != null) {
632 entity = entity.getMarket().getPrimaryEntity();
633 }
634 }
635 return entity;
636 }
637
644 if (stack.getCargo() == null) return null;
646 if (entity == null || entity.getMarket() == null || entity.getMarket().getSubmarketsCopy() == null) return currSubmarket;
647
648 for (SubmarketAPI sub : entity.getMarket().getSubmarketsCopy()) {
649 if (sub.getCargo() == stack.getCargo()) {
650 return sub;
651 }
652 }
653 return currSubmarket;
654 }
655
656 public PersonnelAtEntity getDroppedOffAt(String commodityId, SectorEntityToken entity, SubmarketAPI sub, boolean createIfNull) {
657 String submarketId = sub == null ? "" : sub.getSpecId();
658 for (PersonnelAtEntity pae : droppedOff) {
659 String otherSubmarketId = pae.submarketId == null ? "" : pae.submarketId;
660 if (entity == pae.entity && commodityId.equals(pae.data.id) && submarketId.equals(otherSubmarketId)) {
661 return pae;
662 }
663 }
664 if (createIfNull) {
665 if (submarketId.isEmpty()) submarketId = null;
666 PersonnelAtEntity pae = new PersonnelAtEntity(entity, commodityId, submarketId);
667 droppedOff.add(pae);
668 return pae;
669 }
670 return null;
671 }
672
673 public PersonnelAtEntity getPersonnelAtLocation(String commodityId, SubmarketAPI sub) {
675 PersonnelAtEntity atLocation = entity == null ? null : getDroppedOffAt(commodityId, entity, sub, false);
676 return atLocation;
677 }
678
679 public PersonnelData getMarineData() {
680 return marineData;
681 }
682
683 public List<PersonnelAtEntity> getDroppedOff() {
684 return droppedOff;
685 }
686
687
688}
689
690
691
692
693
694
695
static SettingsAPI getSettings()
Definition Global.java:57
static SectorAPI getSector()
Definition Global.java:65
void modifyMult(String source, float value)
void modifyPercentAlways(String source, float value, String desc)
void modifyPercent(String source, float value)
void addSectionAfterPrice(TooltipMakerAPI info, float width, boolean expanded, CargoStackAPI stack)
void processTransaction(PlayerMarketTransaction transaction, SectorEntityToken entity)
PersonnelAtEntity getDroppedOffAt(String commodityId, SectorEntityToken entity, SubmarketAPI sub, boolean createIfNull)
void modifyRaidObjectives(MarketAPI market, SectorEntityToken entity, List< GroundRaidObjectivePlugin > objectives, RaidType type, int marineTokens, int priority)
void reportRaidObjectivesAchieved(RaidResultData data, InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
PersonnelAtEntity getPersonnelAtLocation(String commodityId, SubmarketAPI sub)
void reportPlayerNonMarketTransaction(PlayerMarketTransaction transaction, InteractionDialogAPI dialog)
void update(boolean withIntegrationFromCurrentLocation, boolean keepXP, CargoStackAPI stack)
static void transferPersonnel(PersonnelData from, PersonnelData to, int num, PersonnelData keepsXP)
void reportPlayerMarketTransaction(PlayerMarketTransaction transaction)
static final String PLANETARY_OPERATIONS_MOD
Definition Stats.java:65
static final String PLANETARY_OPERATIONS_CASUALTIES_MULT
Definition Stats.java:66
static Color getBasePlayerColor()
Definition Misc.java:833
static Color getHighlightColor()
Definition Misc.java:792
static Color getDarkPlayerColor()
Definition Misc.java:836
String getSpriteName(String category, String id)
InteractionDialogAPI getCurrentInteractionDialog()
List< CargoStackAPI > getStacksCopy()
GenericPluginManagerAPI getGenericPlugins()
ListenerManagerAPI getListenerManager()
CustomCampaignEntityPlugin getCustomPlugin()
void set(String key, Object value)
PositionAPI autoSizeToWidth(float width)
PositionAPI setSize(float width, float height)
void addStatModGrid(float width, float valueWidth, float valuePad, float pad, MutableStat stat)
LabelAPI addPara(String format, float pad, Color hl, String... highlights)
LabelAPI addSectionHeading(String str, Alignment align, float pad)