Starsector API
Loading...
Searching...
No Matches
SalvageEntity.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.rulecmd.salvage;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.LinkedHashMap;
6import java.util.List;
7import java.util.Map;
8import java.util.Random;
9
10import org.lwjgl.input.Keyboard;
11import org.lwjgl.util.vector.Vector2f;
12
13import com.fs.starfarer.api.Global;
14import com.fs.starfarer.api.campaign.CampaignFleetAPI;
15import com.fs.starfarer.api.campaign.CargoAPI;
16import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
17import com.fs.starfarer.api.campaign.CargoStackAPI;
18import com.fs.starfarer.api.campaign.CoreInteractionListener;
19import com.fs.starfarer.api.campaign.FactionAPI;
20import com.fs.starfarer.api.campaign.InteractionDialogAPI;
21import com.fs.starfarer.api.campaign.OptionPanelAPI;
22import com.fs.starfarer.api.campaign.ResourceCostPanelAPI;
23import com.fs.starfarer.api.campaign.SectorEntityToken;
24import com.fs.starfarer.api.campaign.SpecialItemData;
25import com.fs.starfarer.api.campaign.TextPanelAPI;
26import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
27import com.fs.starfarer.api.campaign.listeners.ListenerUtil;
28import com.fs.starfarer.api.campaign.rules.MemoryAPI;
29import com.fs.starfarer.api.combat.MutableStat;
30import com.fs.starfarer.api.combat.MutableStat.StatMod;
31import com.fs.starfarer.api.combat.ShipVariantAPI;
32import com.fs.starfarer.api.impl.campaign.DerelictShipEntityPlugin;
33import com.fs.starfarer.api.impl.campaign.RepairGantry;
34import com.fs.starfarer.api.impl.campaign.ids.Commodities;
35import com.fs.starfarer.api.impl.campaign.ids.Entities;
36import com.fs.starfarer.api.impl.campaign.ids.Items;
37import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
38import com.fs.starfarer.api.impl.campaign.ids.Stats;
39import com.fs.starfarer.api.impl.campaign.ids.Tags;
40import com.fs.starfarer.api.impl.campaign.procgen.DropGroupRow;
41import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec;
42import com.fs.starfarer.api.impl.campaign.procgen.SalvageEntityGenDataSpec.DropData;
43import com.fs.starfarer.api.impl.campaign.procgen.StarSystemGenerator;
44import com.fs.starfarer.api.impl.campaign.procgen.themes.SalvageEntityGeneratorOld;
45import com.fs.starfarer.api.impl.campaign.rulecmd.BaseCommandPlugin;
46import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest;
47import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.BaseSalvageSpecial;
48import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.special.ShipRecoverySpecial.ShipRecoverySpecialData;
49import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin;
50import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldParams;
51import com.fs.starfarer.api.impl.campaign.terrain.DebrisFieldTerrainPlugin.DebrisFieldSource;
52import com.fs.starfarer.api.ui.Alignment;
53import com.fs.starfarer.api.ui.TooltipMakerAPI;
54import com.fs.starfarer.api.ui.TooltipMakerAPI.StatModValueGetter;
55import com.fs.starfarer.api.util.Misc;
56import com.fs.starfarer.api.util.Misc.Token;
57import com.fs.starfarer.api.util.WeightedRandomPicker;
58
63public class SalvageEntity extends BaseCommandPlugin {
64
65 public static float SALVAGE_DETECTION_MOD_FLAT = 1000;
66
67 public static int FIELD_RADIUS_FOR_BASE_REQ = 200;
68 public static int FIELD_RADIUS_FOR_MAX_REQ = 1000;
69 public static int FIELD_RADIUS_MAX_REQ_MULT = 10;
70 public static float FIELD_MIN_SALVAGE_MULT = 0.01f;
71
72
73
74 //public static float FIELD_SALVAGE_FRACTION_PER_ATTEMPT = 0.5f;
75 public static float FIELD_SALVAGE_FRACTION_PER_ATTEMPT = 1f;
76
77 public static float FIELD_CONTENT_MULTIPLIER_AFTER_SALVAGE = 0.25f;
78 //public static float FIELD_CONTENT_MULTIPLIER_AFTER_DEMOLITION = 0.65f;
80
81 public static int BASE_MACHINERY = 10;
82 public static int BASE_CREW = 30;
83 public static int MIN_MACHINERY = 5;
84
85 public static float COST_HEIGHT = 67;
86
87
92 protected TextPanelAPI text;
95 protected CargoAPI cargo;
96 protected MemoryAPI memory;
98 private DebrisFieldTerrainPlugin debris;
99 private Map<String, MemoryAPI> memoryMap;
100
101
102 public boolean execute(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
103
104 this.dialog = dialog;
105 this.memoryMap = memoryMap;
106
107 String command = params.get(0).getString(memoryMap);
108 if (command == null) return false;
109
110 memory = getEntityMemory(memoryMap);
111
113
114 String specId = entity.getCustomEntityType();
117 }
119
122
125
128
130 if (test instanceof DebrisFieldTerrainPlugin) {
131 debris = (DebrisFieldTerrainPlugin) test;
132 }
133
134 if (command.equals("showCost")) {
135 if (debris == null) {
136 showCost();
137 } else {
138 //showCost();
140 }
141 } else if (command.equals("performSalvage")) {
143 } else if (command.equals("descDebris")) {
144 showDebrisDescription();
145 } else if (command.equals("checkAccidents")) {
146 checkAccidents();
147 } else if (command.equals("demolish")) {
148 demolish();
149 } else if (command.equals("canBeMadeRecoverable")) {
150 return canBeMadeRecoverable();
151 } else if (command.equals("showRecoverable")) {
153 }
154
155 return true;
156 }
157
158 private void demolish() {
159 boolean isDebrisField = Entities.DEBRIS_FIELD_SHARED.equals(entity.getCustomEntityType());
160 if (!isDebrisField) {
162
163 Global.getSoundPlayer().playSound("hit_heavy", 1, 1, Global.getSoundPlayer().getListenerPos(), new Vector2f());
164
165 dialog.dismiss();
166
167// text.addParagraph("Salvage crews set targeting beacons at key points in the structure, " +
168// "and you give the order to fire once everyone is safely off.");
169// text.addParagraph("Salvage crews set targeting beacons at key points in the structure.");
170// options.clearOptions();
171// options.addOption("Leave", "defaultLeave");
172// options.setShortcut("defaultLeave", Keyboard.KEY_ESCAPE, false, false, false, true);
173 }
174 }
175
176 private float getAccidentProbability() {
177 if (debris == null) return 0f;
178 float accidentProbability = 0.2f + 0.8f * (1f - debris.getParams().density);
179 if (accidentProbability > 0.9f) accidentProbability = 0.9f;
180 return accidentProbability;
181 }
182
183 private void checkAccidents() {
184 if (debris == null) {
185 memory.set("$option", "salPerform");
186 FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
187 return;
188 }
189
190 float accidentProbability = getAccidentProbability();
191 //accidentProbability = 1f;
192
193 long seed = memory.getLong(MemFlags.SALVAGE_SEED);
194 Random random = Misc.getRandom(seed, 175);
195
196 if (random.nextFloat() > accidentProbability) {
197 memory.set("$option", "salPerform");
198 FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
199 return;
200 }
201
202 Color color = playerFaction.getColor();
203 Color bad = Misc.getNegativeHighlightColor();
204 Color highlight = Misc.getHighlightColor();
205
206 Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
207 float reqCrew = (int) requiredRes.get(Commodities.CREW);
208 float reqMachinery = (int) requiredRes.get(Commodities.HEAVY_MACHINERY);
209
210 float crew = playerFleet.getCargo().getCrew();
211 float machinery = playerFleet.getCargo().getCommodityQuantity(Commodities.HEAVY_MACHINERY);
212 float fCrew = crew / reqCrew;
213 if (fCrew < 0) fCrew = 0;
214 if (fCrew > 1) fCrew = 1;
215
216 float fMachinery = machinery / reqMachinery;
217 if (fMachinery < 0) fMachinery = 0;
218 if (fMachinery > 1) fMachinery = 1;
219
220
221// CommoditySpecAPI crewSpec = Global.getSector().getEconomy().getCommoditySpec(Commodities.CREW);
222// CommoditySpecAPI machinerySpec = Global.getSector().getEconomy().getCommoditySpec(Commodities.HEAVY_MACHINERY);
223
224 float lossValue = reqCrew * fCrew * 5f;
225 lossValue += (1f - debris.getParams().density / debris.getParams().baseDensity) * 500f;
226 lossValue *= 0.5f + random.nextFloat();
227 //lossValue *= StarSystemGenerator.getNormalRandom(random, 0.5f, 1.5f);
228
229 WeightedRandomPicker<String> lossPicker = new WeightedRandomPicker<String>(random);
230 lossPicker.add(Commodities.CREW, 10f + 100f * (1f - fMachinery));
231 lossPicker.add(Commodities.HEAVY_MACHINERY, 10f + 100f * fMachinery);
232
233 CargoAPI losses = Global.getFactory().createCargo(true);
234 float loss = 0;
235 while (loss < lossValue) {
236 String id = lossPicker.pick();
237 CommoditySpecAPI spec = Global.getSector().getEconomy().getCommoditySpec(id);
238 loss += spec.getBasePrice();
239 losses.addCommodity(id, 1f);
240 }
241 losses.sort();
242
243 int crewLost = losses.getCrew();
244 if (crewLost > 0) {
245 losses.removeCrew(crewLost);
246 crewLost *= playerFleet.getStats().getDynamic().getValue(Stats.NON_COMBAT_CREW_LOSS_MULT);
247 if (crewLost < 1) crewLost = 1;
248 losses.addCrew(crewLost);
249 }
250
251 int machineryLost = (int) losses.getCommodityQuantity(Commodities.HEAVY_MACHINERY);
252 if (crewLost > crew) crewLost = (int) crew;
253 if (machineryLost > machinery) machineryLost = (int) machinery;
254
255 if (crewLost <= 0 && machineryLost <= 0) {
256 memory.set("$option", "salPerform");
257 FireBest.fire(null, dialog, memoryMap, "DialogOptionSelected");
258 }
259
260
261 for (CargoStackAPI stack : losses.getStacksCopy()) {
262 cargo.removeCommodity(stack.getCommodityId(), stack.getSize());
263 }
264
265
266
268 text.addParagraph("An accident during the operation has resulted in the loss of ");
269
270 if (crewLost <= 0) {
271 text.appendToLastParagraph("" + machineryLost + " heavy machinery.");
272 text.highlightInLastPara(highlight, "" + machineryLost);
273 } else if (machineryLost <= 0) {
274 text.appendToLastParagraph("" + crewLost + " crew.");
275 text.highlightInLastPara(highlight, "" + crewLost);
276 } else {
277 text.appendToLastParagraph("" + crewLost + " crew and " + machineryLost + " heavy machinery.");
278 text.highlightInLastPara(highlight, "" + crewLost, "" + machineryLost);
279 }
280
281
282 Global.getSoundPlayer().playSound("hit_solid", 1, 1, Global.getSoundPlayer().getListenerPos(), new Vector2f());
283
285 options.addOption("Continue", "salPerform");
286 //FireBest.fire(null, dialog, memoryMap, "PerformSalvage");
287 //FireBest.fire(null, dialog, memoryMap, "PerformSalvage");
288 }
289
290 private void showDebrisDescription() {
291 if (debris == null) return;
292
293 float daysLeft = debris.getDaysLeft();
294 if (daysLeft >= 1000) {
295 text.addParagraph("The field appears stable and will not drift apart any time soon.");
296 } else {
297 String atLeastTime = Misc.getAtLeastStringForDays((int) daysLeft);
298 text.addParagraph("The field is unstable, but should not drift apart for " + atLeastTime + ".");
299 }
300
301// boolean stillHot = debris.getGlowDaysLeft() > 0;
302// switch (debris.getParams().source) {
303// case BATTLE:
304// text.addParagraph("Pieces of ships, weapons, and escape pods litter the starscape.");
305// break;
306// case MIXED:
307// text.addParagraph("Pieces of ships, weapons, and escape pods litter the starscape.");
308// break;
309// case PLAYER_SALVAGE:
310// break;
311// case SALVAGE:
312// break;
313// }
314
315// if (stillHot) {
316// text.appendToLastParagraph(" Some of the pieces of debris are still radiating heat, making any salvage operations more dangerous.");
317// }
318
319 float lootValue = 0;
320 for (DropData data : debris.getEntity().getDropValue()) {
321 lootValue += data.value;
322 }
323 for (DropData data : debris.getEntity().getDropRandom()) {
324 if (data.value > 0) {
325 lootValue += data.value;
326 } else {
327 lootValue += 500; // close enough
328 }
329 }
330 float d = debris.getParams().density;
331
332 lootValue *= d;
333
334 // doesn't work because "extra" expires
335// ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
336// if (extra != null) {
337// for (CargoStackAPI stack : extra.cargo.getStacksCopy()) {
338// lootValue += stack.getBaseValuePerUnit() * stack.getSize();
339// }
340// }
341
342 if (lootValue < 500) {
343 text.appendToLastParagraph(" Long-range scans indicate it's unlikely anything of much value would be found inside.");
344 text.highlightLastInLastPara("unlikely", Misc.getNegativeHighlightColor());
345 } else if (lootValue < 2500) {
346 text.appendToLastParagraph(" Long-range scans indicate it's possible something of value could be found inside.");
347 text.highlightLastInLastPara("possible", Misc.getHighlightColor());
348 } else {
349 text.appendToLastParagraph(" Long-range scans indicate it's likely something of value could be found inside.");
350 text.highlightLastInLastPara("likely", Misc.getPositiveHighlightColor());
351 }
352
353 float accidentProbability = getAccidentProbability();
354 if (accidentProbability <= 0.2f) {
355 //text.addParagraph("There are indications of some easy pickings to be had, and the risk of an accident during a salvage operation is low.");
356 text.addPara("There are indications of some easy pickings to be had, and the risk of an accident during a salvage operation is low.",
357 Misc.getPositiveHighlightColor(), "low");
358 } else if (accidentProbability < 0.7f) {
359 text.addPara("There are indications that what salvage is to be had may not be easy to get to, " +
360 "and there's %s risk involved in running a salvage operation.", Misc.getHighlightColor(), "significant");
361 } else {
362 text.addPara("The salvage that remains is extremely difficult to get to, " +
363 "and there's %s risk involved in running a salvage operation.", Misc.getNegativeHighlightColor(), "high");
364 }
365 }
366
367 public static Map<String, Integer> computeRequiredToSalvage(SectorEntityToken entity) {
368 Map<String, Integer> result = new LinkedHashMap<String, Integer>();
369
370 String specId = entity.getCustomEntityType();
373 }
375 float mult = 1f + spec.getSalvageRating() * 9f;
376
378 if (test instanceof DebrisFieldTerrainPlugin) {
380 mult = getDebrisReqMult(debris);
381 }
382
383 int crew = Math.round((int) (BASE_CREW * mult) / 10f) * 10;
384 int machinery = Math.round((int) (BASE_MACHINERY * mult) / 10f) * 10;
385
386 result.put(Commodities.CREW, crew);
387 result.put(Commodities.HEAVY_MACHINERY, machinery);
388
389 return result;
390 }
391
392 protected MutableStat getValueRecoveryStat(boolean withSkillMultForRares) {
393 Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
394 MutableStat valueRecovery = new MutableStat(1f);
395 int i = 0;
396
397 float machineryContrib = 0.75f;
398 valueRecovery.modifyPercent("base", -100f);
399 if (machineryContrib < 1f) {
400 valueRecovery.modifyPercent("base_positive", (int) Math.round(100f - 100f * machineryContrib), "Base effectiveness");
401 }
402 //valueRecovery.modifyPercent("base", -75f);
403
404 float per = 0.5f;
405 per = 1f;
406 for (String commodityId : requiredRes.keySet()) {
407 float required = requiredRes.get(commodityId);
408 float available = (int) cargo.getCommodityQuantity(commodityId);
409 if (required <= 0) continue;
411
412 float val = Math.min(available / required, 1f) * per;
413 int percent = (int) Math.round(val * 100f);
414 //valueRecovery.modifyPercent("" + i++, percent, Misc.ucFirst(spec.getLowerCaseName()) + " requirements met");
415 if (Commodities.HEAVY_MACHINERY.equals(commodityId)) {
416 val = Math.min(available / required, machineryContrib) * per;
417 percent = (int) Math.round(val * 100f);
418 valueRecovery.modifyPercentAlways("" + i++, percent, Misc.ucFirst(spec.getLowerCaseName()) + " available");
419 } else {
420 valueRecovery.modifyMultAlways("" + i++, val, Misc.ucFirst(spec.getLowerCaseName()) + " available");
421 }
422// float val = Math.max(1f - available / required, 0f) * per;
423// int percent = -1 * (int) Math.round(val * 100f);
424// valueRecovery.modifyPercent("" + i++, percent, "Insufficient " + spec.getLowerCaseName());
425 }
426
427 boolean modified = false;
428 if (withSkillMultForRares) {
430 modified = true;
431 valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
432 }
433 }
434
435 {
437 modified = true;
438 valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
439 }
440 }
441 if (!modified) {
442 valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(0f), "Salvaging skill");
443 }
444
445 float fleetSalvageShips = getPlayerShipsSalvageModUncapped();
446 valueRecovery.modifyPercentAlways("" + i++, (int) Math.round(fleetSalvageShips * 100f), "Fleetwide salvaging capability");
447
448 return valueRecovery;
449 }
450
451// protected StatBonus getRareRecoveryStat() {
452// StatBonus rareRecovery = new StatBonus();
453// int i = 0;
454// for (StatMod mod : playerFleet.getStats().getDynamic().getMod(Stats.SALVAGE_MAX_RATING).getFlatBonuses().values()) {
455// rareRecovery.modifyPercent("" + i++, (int) Math.round(mod.value * 100f), mod.desc);
456// }
457// return rareRecovery;
458// }
459
460 public void showCost() {
461 Color color = playerFaction.getColor();
462 Color bad = Misc.getNegativeHighlightColor();
463 Color highlight = Misc.getHighlightColor();
464
465 float pad = 3f;
466 float opad = 10f;
467 float small = 5f;
468
469 Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
470
471 text.addParagraph("You receive a preliminary assessment of a potential salvage operation from the exploration crews.");
472
473 ResourceCostPanelAPI cost = text.addCostPanel("Crew & machinery: required (available)", COST_HEIGHT,
475 cost.setNumberOnlyMode(true);
476 cost.setWithBorder(false);
478
479 for (String commodityId : requiredRes.keySet()) {
480 int required = requiredRes.get(commodityId);
481 int available = (int) cargo.getCommodityQuantity(commodityId);
482 Color curr = color;
483 if (required > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
484 curr = bad;
485 }
486 cost.addCost(commodityId, "" + required + " (" + available + ")", curr);
487 }
488 cost.update();
489
490
491 MutableStat valueRecovery = getValueRecoveryStat(true);
492
493 //rareRecovery.unmodify();
494 int valuePercent = (int)Math.round(valueRecovery.getModifiedValue() * 100f);
495 if (valuePercent < 0) valuePercent = 0;
496 String valueString = "" + valuePercent + "%";
497 Color valueColor = highlight;
498
499 if (valuePercent < 100) {
500 valueColor = bad;
501 }
502
505 info.addPara("Resource recovery effectiveness: %s", 0f, valueColor, valueString);
506 if (!valueRecovery.isUnmodified()) {
507 info.addStatModGrid(300, 50, opad, small, valueRecovery, true, getModPrinter());
508 }
510
512 }
513
515 return new StatModValueGetter() {
516 boolean percent = false;
517 public String getPercentValue(StatMod mod) {
518 percent = true;
519
520 // should make it not shown; it's a "base" value that has to be applied to make the calculations work with multipliers
521 if (mod.desc == null || mod.desc.isEmpty()) return "";
522
523 String prefix = mod.getValue() >= 0 ? "+" : "";
524 return prefix + (int)(mod.getValue()) + "%";
525 }
526 public String getMultValue(StatMod mod) {percent = false; return null;}
527 public String getFlatValue(StatMod mod) {percent = false; return null;}
528 public Color getModColor(StatMod mod) {
529 if ((!percent && mod.getValue() < 1f) || mod.getValue() < 0) return Misc.getNegativeHighlightColor();
530 return null;
531 }
532 };
533 }
534
535 protected void printSalvageModifiers() {
536
538 String fuelStr = "" + (int)Math.round((fuelMult - 1f) * 100f) + "%";
539
541 String rareStr = "" + (int)Math.round((rareMult - 1f) * 100f) + "%";
542
543 if (fuelMult > 1f && rareMult > 1f) {
544 text.addPara("Your fleet also has a %s bonus to the amount of fuel recovered, and " +
545 "a %s bonus to the number of rare items found.",
546 Misc.getHighlightColor(), fuelStr, rareStr);
547 } else if (fuelMult > 1) {
548 text.addPara("Your fleet also has a %s bonus to the amount of fuel recovered.",
549 Misc.getHighlightColor(), fuelStr);
550 } else if (rareMult > 1) {
551 text.addPara("Your fleet also has a %s bonus to the number of rare items found.",
552 Misc.getHighlightColor(), rareStr);
553 }
554
555 if (debris != null) {
556 text.addParagraph("The density of the debris field affects both the amount of resources and the number of rare items found.");
557 } else {
558 text.addPara("The recovery effectiveness does not affect the chance of finding rare and valuable items.");
559 }
560
561 }
562
563 public void showCostDebrisField() {
564 Color color = playerFaction.getColor();
565 Color bad = Misc.getNegativeHighlightColor();
566 Color highlight = Misc.getHighlightColor();
567
568 float pad = 3f;
569 float opad = 10f;
570 float small = 5f;
571
572 Map<String, Integer> requiredRes = computeRequiredToSalvage(entity);
573
574 //text.addParagraph("You receive a preliminary assessment of a potential salvage operation from the exploration crews.");
575
576 ResourceCostPanelAPI cost = text.addCostPanel("Crew & machinery: required (available)", COST_HEIGHT,
578 cost.setNumberOnlyMode(true);
579 cost.setWithBorder(false);
581
582 for (String commodityId : requiredRes.keySet()) {
583 int required = requiredRes.get(commodityId);
584 int available = (int) cargo.getCommodityQuantity(commodityId);
585 Color curr = color;
586 if (required > cargo.getQuantity(CargoItemType.RESOURCES, commodityId)) {
587 curr = bad;
588 }
589 cost.addCost(commodityId, "" + required + " (" + available + ")", curr);
590 }
591 cost.update();
592
593
594 MutableStat valueRecovery = getValueRecoveryStat(true);
595 float overallMult = computeOverallMultForDebrisField();
596 valueRecovery.modifyMult("debris_mult", overallMult, "Debris field density");
597 //rareRecovery.unmodify();
598 int valuePercent = (int)Math.round(valueRecovery.getModifiedValue() * 100f);
599 if (valuePercent < 0) valuePercent = 0;
600 String valueString = "" + valuePercent + "%";
601 Color valueColor = highlight;
602
603 if (valuePercent < 100) {
604 valueColor = bad;
605 }
606
609 info.addPara("Scavenging effectiveness: %s", 0f, valueColor, valueString);
610 if (!valueRecovery.isUnmodified()) {
611 info.addStatModGrid(300, 50, opad, small, valueRecovery, true, getModPrinter());
612 }
614
615// text.addParagraph("The density of the debris field affects both the amount resources and the number of rare items found.");
616
617// text.addParagraph("It's possible to scavenge using fewer crew and less machinery than required, but using fewer crew will reduce " +
618// "the amount of salvage recovered, while having less machinery will increase the danger to crew.");
619
621
622 }
623
625 float overallMult = 1f;
626 if (debris != null) {
627// Map<String, Integer> reqs = computeRequiredToSalvage(entity);
628// float crewMax = 1f;
629// if (reqs.get(Commodities.CREW) != null) {
630// crewMax = reqs.get(Commodities.CREW);
631// }
632// float crew = playerFleet.getCargo().getCrew();
633// float f = crew / crewMax;
634// if (f < 0) f = 0;
635// if (f > 1) f = 1;
636//
637// //if (Global.getSettings().isDevMode()) f = 1f;
638
639 float f = 1f;
640 DebrisFieldParams params = debris.getParams();
641 if (params.baseDensity > 0) {
642 overallMult = params.density / params.baseDensity * f * FIELD_SALVAGE_FRACTION_PER_ATTEMPT;
643 } else {
644 overallMult = 0f;
645 }
646 if (overallMult < FIELD_MIN_SALVAGE_MULT) overallMult = FIELD_MIN_SALVAGE_MULT;
647 }
648 return overallMult;
649 }
650
651
652 public void performSalvage() {
653 long seed = memory.getLong(MemFlags.SALVAGE_SEED);
654 Random random = Misc.getRandom(seed, 100);
655
657
658// if (Global.getSettings().isDevMode()) {
659// random = Misc.random;
660// }
661
662// float salvageRating = spec.getSalvageRating();
663// float valueMultFleet = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE);
664// float valueModShips = getPlayerShipsSalvageMod(salvageRating);
665
666 MutableStat valueRecovery = getValueRecoveryStat(true);
667 float valueMultFleet = valueRecovery.getModifiedValue();
669
670 List<DropData> dropValue = new ArrayList<DropData>(spec.getDropValue());
671 List<DropData> dropRandom = new ArrayList<DropData>(spec.getDropRandom());
672 dropValue.addAll(entity.getDropValue());
673 dropRandom.addAll(entity.getDropRandom());
674
675// DropData d = new DropData();
676// d.group = "misc_test";
677// d.chances = 1500;
678// dropRandom.add(d);
679
680
681 float overallMult = computeOverallMultForDebrisField();
682 if (debris != null) {
683 // to avoid same special triggering over and over while scavenging through
684 // the same debris field repeatedly
686 }
687
689 CargoAPI salvage = generateSalvage(random, valueMultFleet, rareItemSkillMult, overallMult, fuelMult, dropValue, dropRandom);
690
691 //ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
693 salvage.addAll(extra);
695 if (!extra.isEmpty()) {
697 }
698
699 //salvage.addCommodity(Commodities.ALPHA_CORE, 1);
700
701 if (debris != null) {
702 debris.getParams().density -= overallMult;
703 if (debris.getParams().density < 0) debris.getParams().density = 0;
704
705 debris.getEntity().getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, random.nextLong());
706 //System.out.println("Post-salvage density: " + debris.getParams().density);
707 debris.setScavenged(true);
708 }
709
710 //if (loot)
711 if (!salvage.isEmpty()) {
712 dialog.getVisualPanel().showLoot("Salvaged", salvage, false, true, true, new CoreInteractionListener() {
713 public void coreUIDismissed() {
714 long xp = 0;
715 if (entity.hasSalvageXP()) {
716 xp = (long) (float) entity.getSalvageXP();
717 } else if (spec != null && spec.getXpSalvage() > 0) {
718 xp = (long) spec.getXpSalvage();
719 }
720 if (!memory.contains("$doNotDismissDialogAfterSalvage")) {
721 dialog.dismiss();
724
725 if (xp > 0) {
727 }
728 } else {
729 if (xp > 0) {
731 }
732 }
733// if (entity.hasSalvageXP()) {
734// Global.getSector().getPlayerPerson().getStats().addXP((long) (float) entity.getSalvageXP());
735// } else if (spec != null && spec.getXpSalvage() > 0) {
736// Global.getSector().getPlayerPerson().getStats().addXP((long) spec.getXpSalvage());
737// }
738 //Global.getSector().setPaused(false);
739 }
740 });
743 } else {
744 text.addParagraph("Operations conclude with nothing of value found.");
746 String leave = "Leave";
747 if (memory.contains("$salvageLeaveText")) {
748 leave = memory.getString("$salvageLeaveText");
749 }
750 options.addOption(leave, "defaultLeave");
751 options.setShortcut("defaultLeave", Keyboard.KEY_ESCAPE, false, false, false, true);
752 }
753
754
755 boolean isDebrisField = Entities.DEBRIS_FIELD_SHARED.equals(entity.getCustomEntityType());
756 if (!isDebrisField) {
759 } else {
762 }
763 }
764 }
765
766 if (playerFleet != null) {
767 playerFleet.getStats().addTemporaryModFlat(0.25f, "salvage_ops",
768 "Recent salvage operation", SALVAGE_DETECTION_MOD_FLAT,
770 Global.getSector().addPing(playerFleet, "noticed_player");
771 }
772 }
773
774
775 public void convertToDebrisField(float valueMult) {
776 convertToDebrisField(null, valueMult);
777 }
778
779 public void convertToDebrisField(Random random, float valueMult) {
780 if (random == null) random = new Random();
781
783
784 float salvageRating = spec.getSalvageRating();
785 //entity.addTag(Tags.NON_CLICKABLE);
786
787 float debrisFieldRadius = 200f + salvageRating * 400f;
788
789 float density = 0.5f + salvageRating * 0.5f;
790 density = 1f;
792 density = 0.5f + salvageRating * 0.5f;
793 }
794
795 float duration = 10f + salvageRating * 20f;
796
797 DebrisFieldParams params = new DebrisFieldParams(debrisFieldRadius, density, duration, duration * 0.5f);
798 params.source = DebrisFieldSource.PLAYER_SALVAGE;
799
800// params.minSize = 12;
801// params.maxSize = 16;
802// params.defenderProb = 1;
803// params.minStr = 20;
804// params.maxStr = 30;
805// params.maxDefenderSize = 1;
806
807 float xp = spec.getXpSalvage() * 0.25f;
808 if (entity.hasSalvageXP()) {
809 xp = entity.getSalvageXP() * 0.25f;
810 }
811 if (xp >= 10) {
812 params.baseSalvageXP = (long) xp;
813 }
814
816
817 //ExtraSalvage extra = BaseSalvageSpecial.getExtraSalvage(memoryMap);
819 if (extra != null && !extra.isEmpty()) {
820 // don't prune extra cargo - it could have come from not recovering ships,
821 // and so could've been gotten by recovering and then stripping/scuttling them
822 // so shouldn't punish shortcutting that process
823 // (this can happen when "pound into scrap" vs ship derelict)
824// CargoAPI extraCopy = Global.getFactory().createCargo(true);
825// for (CargoStackAPI stack : extra.cargo.getStacksCopy()) {
826// float qty = stack.getSize();
827// qty *= valueMult;
828// if (qty < 1) {
829// if (random.nextFloat() >= qty) continue;
830// qty = 1;
831// } else {
832// qty = (int) qty;
833// }
834// extraCopy.addItems(stack.getType(), stack.getData(), qty);
835// }
836// BaseSalvageSpecial.setExtraSalvage(extraCopy, debris.getMemoryWithoutUpdate(), -1f);
837 //BaseSalvageSpecial.addExtraSalvage(extra.cargo, debris.getMemoryWithoutUpdate(), -1f);
839 }
840
841// int count = 0;
842// for (CampaignTerrainAPI curr : entity.getContainingLocation().getTerrainCopy()) {
843// if (curr.getPlugin() instanceof DebrisFieldTerrainPlugin) {
844// count++;
845// }
846// }
847 //System.out.println("DEBRIS: " + count);
848
849 debris.setSensorProfile(null);
850 debris.setDiscoverable(null);
851 //debris.setDiscoveryXP(123f);
852
853 debris.setFaction(entity.getFaction().getId());
854
855 debris.getDropValue().clear();
856 debris.getDropRandom().clear();
857
858 for (DropData data : spec.getDropValue()) {
859 DropData copy = data.clone();
860 copy.valueMult = valueMult;
861 debris.addDropValue(data.clone());
862 }
863 for (DropData data : spec.getDropRandom()) {
864 DropData copy = data.clone();
865 copy.valueMult = valueMult;
866 debris.addDropRandom(copy);
867 }
868
869 for (DropData data : entity.getDropValue()) {
870 DropData copy = data.clone();
871 copy.valueMult = valueMult;
872 debris.addDropValue(data.clone());
873 }
874 for (DropData data : entity.getDropRandom()) {
875 DropData copy = data.clone();
876 copy.valueMult = valueMult;
877 debris.addDropRandom(copy);
878 }
879 //debris.addDropRandom("weapons_test", 10);
880
881 if (entity.getOrbit() != null) {
882 debris.setOrbit(entity.getOrbit().makeCopy());
883 } else {
884 debris.getLocation().set(entity.getLocation());
885 }
886
887 long seed = memory.getLong(MemFlags.SALVAGE_SEED);
888 if (seed != 0) {
889 debris.getMemoryWithoutUpdate().set(MemFlags.SALVAGE_SEED, Misc.getRandom(seed, 150).nextLong());
890 }
891 }
892
893
894
895
896 public static float getPlayerShipsSalvageModUncapped() {
898 //float valueModShips = Misc.getFleetwideTotalMod(playerFleet, Stats.SALVAGE_VALUE_MULT_MOD, 0f);
899 float valueModShips = RepairGantry.getAdjustedGantryModifier(playerFleet, null, 0);
900 return valueModShips;
901 }
902// public static float getPlayerShipsSalvageMod(float salvageRating) {
903// CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
904// float valueModShips = Misc.getFleetwideTotalMod(playerFleet, Stats.SALVAGE_VALUE_MULT_MOD, 0f);
905// if (valueModShips > salvageRating) {
906// valueModShips = salvageRating;
907// }
908// return valueModShips;
909// }
910
911 public static float getDebrisReqMult(DebrisFieldTerrainPlugin field) {
912// public static int FIELD_RADIUS_FOR_BASE_REQ = 200;
913// public static int FIELD_RADIUS_FOR_MAX_REQ = 1000;
914// public static int FIELD_RADIUS_MAX_REQ_MULT = 10;
915 float r = field.getParams().bandWidthInEngine;
917 if (f < 0) f = 0;
918 if (f > 1) f = 1;
919
920 float mult = 1f + (FIELD_RADIUS_MAX_REQ_MULT - 1f) * f;
921 return mult;
922 }
923
924// public static CargoAPI generateSalvage(Random random, float valueMult, List<DropData> dropValue, List<DropData> dropRandom) {
925// return generateSalvage(random, valueMult, 1f, dropValue, dropRandom);
926// }
927 public static CargoAPI generateSalvage(Random random, float valueMult, float overallMult, float fuelMult, List<DropData> dropValue, List<DropData> dropRandom) {
928 return generateSalvage(random, valueMult, 1f, overallMult, fuelMult, dropValue, dropRandom);
929 }
930 public static CargoAPI generateSalvage(Random random, float valueMult, float randomMult,
931 float overallMult, float fuelMult, List<DropData> dropValue, List<DropData> dropRandom) {
932 if (random == null) random = new Random();
933 CargoAPI result = Global.getFactory().createCargo(true);
934
935
936 if (Misc.isEasy()) {
937 overallMult *= Global.getSettings().getFloat("easySalvageMult");
938 }
939// CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
940
941 //overallMult = 1f;
942
943// float valueMultFleet = playerFleet.getStats().getDynamic().getValue(Stats.SALVAGE_VALUE_MULT_FLEET);
944// float valueModShips = getPlayerShipsSalvageMod(salvageRating);
945
946 // check dropRandom first so that changing the drop value by dropping off crew/machinery
947 // does not change the RNG for dropRandom
948 if (dropRandom != null) {
949 for (DropData data : dropRandom) {
950 //if (random.nextFloat() < data.valueMult) continue;
951
952 int chances = data.chances;
953 if (data.maxChances > chances) {
954 chances = chances + random.nextInt(data.maxChances - chances + 1);
955 }
956// if (data.group.endsWith("misc_test")) {
957// System.out.println("fewfwefwe");
958// }
959 //WeightedRandomPicker<DropGroupRow> picker = DropGroupRow.getPicker(data.group);
960
961 float modifiedChances = chances;
962 modifiedChances *= overallMult;
963 if (data.value <= 0) {
964 modifiedChances *= randomMult;
965 }
966 modifiedChances *= data.valueMult;
967 float rem = modifiedChances - (int) modifiedChances;
968
969 chances = (int) modifiedChances + (random.nextFloat() < rem ? 1 : 0);
970
971 WeightedRandomPicker<DropGroupRow> picker = data.getCustom();
972 if (picker == null && data.group == null) continue; // meant for custom, but empty
973 if (picker == null) {
974 picker = DropGroupRow.getPicker(data.group);
975 }
976
977 Random innerRandom = Misc.getRandom(random.nextLong(), 5);
978 //innerRandom = random;
979 picker.setRandom(innerRandom);
980 for (int i = 0; i < chances; i++) {
981// if (random.nextFloat() > overallMult) continue;
982// if (random.nextFloat() > data.valueMult) continue;
983
984 DropGroupRow row = picker.pick();
985 if (row.isMultiValued()) {
986 row = row.resolveToSpecificItem(innerRandom);
987 }
988
989 if (row.isNothing()) continue;
990
991 float baseUnitValue = row.getBaseUnitValue();
992
993 float qty = 1f;
994 if (data.value > 0) {
995 float randMult = StarSystemGenerator.getNormalRandom(innerRandom, 0.5f, 1.5f);
996 //qty = (data.value * randMult * valueMult * overallMult) / baseUnitValue;
997 // valueMult and overallMult are considered in figuring out number of chances to roll
998 qty = (data.value * valueMult * randMult) / baseUnitValue;
999 qty = (int) qty;
1000 if (valueMult <= 0) continue;
1001 if (qty < 1) qty = 1;
1002 }
1003
1004
1005 if (row.isWeapon()) {
1006 result.addWeapons(row.getWeaponId(), (int) qty);
1007// } else if (row.isHullMod()) {
1008// result.addItems(CargoItemType.MOD_SPEC, row.getHullModId(), (int) qty);
1009 } else if (row.isFighterWing()) {
1010 result.addItems(CargoItemType.FIGHTER_CHIP, row.getFighterWingId(), (int) qty);
1011 } else if (row.isSpecialItem()) {
1012 if (Items.TAG_MODSPEC.equals(row.getSpecialItemId()) &&
1013 result.getQuantity(CargoItemType.SPECIAL,
1014 new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData())) > 0) {
1015 continue;
1016 }
1017 result.addItems(CargoItemType.SPECIAL,
1018 new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData()), (int) qty);
1019 } else {
1020 result.addCommodity(row.getCommodity(), qty);
1021 }
1022 }
1023 }
1024 }
1025
1026
1027 if (dropValue != null) {
1028
1029 for (DropData data : dropValue) {
1030 //if (random.nextFloat() < data.valueMult) continue;
1031
1032 float maxValue = data.value;
1033
1034 // if value is 1, it's a "guaranteed pick one out of this usually-dropRandom group"
1035 // so still allow it even if valueMult is 0 due to a lack of heavy machinery
1036 // since dropRandom works w/ no machinery, too
1037 if (data.value > 1) {
1038 maxValue *= valueMult;
1039 }
1040
1041 maxValue *= overallMult;
1042 maxValue *= data.valueMult;
1043
1044 float randMult = StarSystemGenerator.getNormalRandom(random, 0.5f, 1.5f);
1045 maxValue *= randMult;
1046
1047
1048 WeightedRandomPicker<DropGroupRow> picker = data.getCustom();
1049 if (picker == null && data.group == null) continue; // meant for custom, but empty
1050 if (picker == null) {
1051 picker = DropGroupRow.getPicker(data.group);
1052 }
1053 picker.setRandom(random);
1054 float value = 0f;
1055 int nothingInARow = 0;
1056 while (value < maxValue && nothingInARow < 10) {
1057 DropGroupRow row = picker.pick();
1058 if (row.isMultiValued()) {
1059 row = row.resolveToSpecificItem(random);
1060 }
1061 if (row.isNothing()) {
1062 nothingInARow++;
1063 continue;
1064 } else {
1065 nothingInARow = 0;
1066 }
1067 //System.out.println(nothingInARow);
1068
1069 float baseUnitValue = row.getBaseUnitValue();
1070
1071 float qty = 1f;
1072 float currValue = baseUnitValue * qty;
1073 value += currValue;
1074
1075 if (row.isWeapon()) {
1076 if (value <= maxValue) {
1077 result.addWeapons(row.getWeaponId(), (int) qty);
1078 }
1079// } else if (row.isHullMod()) {
1080// if (value <= maxValue) {
1081// result.addHullmods(row.getHullModId(), (int) qty);
1082// }
1083 } else if (row.isFighterWing()) {
1084 if (value <= maxValue) {
1085 result.addItems(CargoItemType.FIGHTER_CHIP, row.getFighterWingId(), (int) qty);
1086 }
1087 } else if (row.isSpecialItem()) {
1088 if (Items.TAG_MODSPEC.equals(row.getSpecialItemId()) &&
1089 result.getQuantity(CargoItemType.SPECIAL,
1090 new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData())) > 0) {
1091 continue;
1092 }
1093 result.addItems(CargoItemType.SPECIAL,
1094 new SpecialItemData(row.getSpecialItemId(), row.getSpecialItemData()), (int) qty);
1095 } else {
1096 if (value <= maxValue) {
1097 result.addCommodity(row.getCommodity(), qty);
1098 }
1099 }
1100 }
1101 }
1102 }
1103
1104
1105 float fuel = result.getFuel();
1106 if (fuelMult > 1f) {
1107 result.addFuel((int) Math.round(fuel * (fuelMult - 1f)));
1108 }
1109
1110 result.sort();
1111
1112 return result;
1113 }
1114
1115
1116 public boolean canBeMadeRecoverable() {
1118
1119 //if (Misc.getSalvageSpecial(entity) != null) return false;
1120
1121 if (Misc.getSalvageSpecial(entity) instanceof ShipRecoverySpecialData) {
1122 return false;
1123 }
1125 return false;
1126 }
1127
1128// int room = Global.getSettings().getMaxShipsInFleet() -
1129// Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy().size();
1130// if (room < 1) return false;
1131
1133 ShipVariantAPI variant = plugin.getData().ship.getVariant();
1134 if (variant != null && !Misc.isUnboardable(variant.getHullSpec())) {
1135 return true;
1136 }
1137 }
1138 return false;
1139 }
1140
1141
1142 public void showRecoverable() {
1143
1144 Object prev = Misc.getSalvageSpecial(entity);
1145 if (prev != null) {
1147 }
1148
1149 ShipRecoverySpecialData data = new ShipRecoverySpecialData(null);
1151 data.addShip(plugin.getData().ship.clone());
1152 data.storyPointRecovery = true;
1154
1155 long seed = Misc.getSalvageSeed(entity);
1157 }
1158
1159}
1160
1161
1162
1163
1164
1165
1166
static SettingsAPI getSettings()
Definition Global.java:57
static SoundPlayerAPI getSoundPlayer()
Definition Global.java:49
static FactoryAPI getFactory()
Definition Global.java:41
static SectorAPI getSector()
Definition Global.java:65
static void reportExtraSalvageShown(SectorEntityToken entity)
void modifyPercent(String source, float value)
void modifyMult(String source, float value)
HashMap< String, StatMod > getFlatMods()
void modifyPercentAlways(String source, float value, String desc)
void modifyMultAlways(String source, float value, String desc)
static float getAdjustedGantryModifier(CampaignFleetAPI fleet, String skipId, float add)
static final String SALVAGE_VALUE_MULT_FLEET_NOT_RARE
Definition Stats.java:83
static final String SALVAGE_VALUE_MULT_FLEET_INCLUDES_RARE
Definition Stats.java:82
static final String FUEL_SALVAGE_VALUE_MULT_FLEET
Definition Stats.java:81
static final String SALVAGE_ENTITY_NO_DEBRIS
Definition Tags.java:191
static final String SALVAGE_ENTITY_NO_REMOVE
Definition Tags.java:190
static WeightedRandomPicker< DropGroupRow > getPicker(String group)
static MemoryAPI getEntityMemory(Map< String, MemoryAPI > memoryMap)
static CargoAPI generateSalvage(Random random, float valueMult, float randomMult, float overallMult, float fuelMult, List< DropData > dropValue, List< DropData > dropRandom)
static CargoAPI generateSalvage(Random random, float valueMult, float overallMult, float fuelMult, List< DropData > dropValue, List< DropData > dropRandom)
static float getDebrisReqMult(DebrisFieldTerrainPlugin field)
boolean execute(String ruleId, InteractionDialogAPI dialog, List< Token > params, Map< String, MemoryAPI > memoryMap)
MutableStat getValueRecoveryStat(boolean withSkillMultForRares)
static Map< String, Integer > computeRequiredToSalvage(SectorEntityToken entity)
static void addExtraSalvage(SectorEntityToken entity, CargoAPI cargo)
static CargoAPI getCombinedExtraSalvage(Map< String, MemoryAPI > memoryMap)
static SectorEntityToken addDebrisField(LocationAPI loc, DebrisFieldParams params, Random random)
Definition Misc.java:3262
static String ucFirst(String str)
Definition Misc.java:559
static Color getNegativeHighlightColor()
Definition Misc.java:802
static Object getSalvageSpecial(SectorEntityToken entity)
Definition Misc.java:3644
static Random getRandom(long seed, int level)
Definition Misc.java:2973
static void setPrevSalvageSpecial(SectorEntityToken entity, Object data)
Definition Misc.java:3640
static void fadeAndExpire(SectorEntityToken entity)
Definition Misc.java:3133
static long getSalvageSeed(SectorEntityToken entity)
Definition Misc.java:4129
static void setSalvageSpecial(SectorEntityToken entity, Object data)
Definition Misc.java:3633
static Color getHighlightColor()
Definition Misc.java:792
static void stopPlayerFleet()
Definition Misc.java:3987
static boolean isUnboardable(FleetMemberAPI member)
Definition Misc.java:3293
static boolean isEasy()
Definition Misc.java:2284
CargoAPI createCargo(boolean unlimitedStacks)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
float getQuantity(CargoAPI.CargoItemType type, Object data)
void addWeapons(String id, int count)
void removeCommodity(String id, float quantity)
void addItems(CargoAPI.CargoItemType itemType, Object data, float quantity)
void addCommodity(String commodityId, float quantity)
void addOption(String text, Object data)
void setShortcut(Object data, int code, boolean ctrl, boolean alt, boolean shift, boolean putLast)
void setNumberOnlyMode(boolean numberOnlyMode)
void addCost(String commodityId, int quantity, Color color)
EveryFrameScript addPing(SectorEntityToken entity, String pingType)
CustomCampaignEntityPlugin getCustomPlugin()
void addDropRandom(String group, int chances)
void addDropValue(String group, int value)
void setDiscoverable(Boolean discoverable)
ResourceCostPanelAPI addCostPanel(String title, float height, Color color, Color dark)
void highlightInLastPara(Color color, String ...strings)
void highlightLastInLastPara(String text, Color color)
void showLoot(String title, CargoAPI otherCargo, boolean generatePods, CoreInteractionListener listener)
CommoditySpecAPI getCommoditySpec(String commodityId)
void set(String key, Object value)
void addXP(long xp, TextPanelAPI textPanel, boolean withMessage, boolean allowBonusXP, boolean withLevelUp)
MutableCharacterStatsAPI getStats()
void addTemporaryModFlat(float durInDays, String source, float value, StatBonus stat)
void addStatModGrid(float width, float valueWidth, float valuePad, float pad, MutableStat stat)
LabelAPI addPara(String format, float pad, Color hl, String... highlights)