Starsector API
Loading...
Searching...
No Matches
PunitiveExpeditionManager.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.intel.punitive;
2
3import java.util.ArrayList;
4import java.util.HashSet;
5import java.util.LinkedHashMap;
6import java.util.List;
7import java.util.Map;
8import java.util.Random;
9import java.util.Set;
10
11import org.json.JSONObject;
12
13import com.fs.starfarer.api.EveryFrameScript;
14import com.fs.starfarer.api.Global;
15import com.fs.starfarer.api.campaign.FactionAPI;
16import com.fs.starfarer.api.campaign.econ.CommodityMarketDataAPI;
17import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
18import com.fs.starfarer.api.campaign.econ.Industry;
19import com.fs.starfarer.api.campaign.econ.MarketAPI;
20import com.fs.starfarer.api.impl.campaign.DebugFlags;
21import com.fs.starfarer.api.impl.campaign.ids.Factions;
22import com.fs.starfarer.api.impl.campaign.ids.Industries;
23import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
24import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin;
25import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD;
26import com.fs.starfarer.api.util.IntervalUtil;
27import com.fs.starfarer.api.util.Misc;
28import com.fs.starfarer.api.util.WeightedRandomPicker;
29
31
32 public static final String KEY = "$core_punitiveExpeditionManager";
34 Object test = Global.getSector().getMemoryWithoutUpdate().get(KEY);
35 return (PunitiveExpeditionManager) test;
36 }
37
38 public static int MAX_CONCURRENT = Global.getSettings().getInt("punExMaxConcurrent");
39 public static float PROB_TIMEOUT_PER_SENT = Global.getSettings().getFloat("punExProbTimeoutPerExpedition");
40 public static float MIN_TIMEOUT = Global.getSettings().getFloatFromArray("punExTimeoutDays", 0);
41 public static float MAX_TIMEOUT = Global.getSettings().getFloatFromArray("punExTimeoutDays", 1);
42
43 public static int MIN_COLONY_SIZE_FOR_NON_TERRITORIAL = Global.getSettings().getInt("punExMinColonySizeForNonTerritorial");
44
45
46 // if more factions send non-territorial expeditions, longer timeout
47 public static float TARGET_NUMBER_FOR_FREQUENCY = 5f;
48
49 public static float ANGER_BUILDUP_MULT = 0.5f;
50
52 //public static float PLAYER_FRACTION_TO_NOTICE = 0.33f;
53 public static float PLAYER_FRACTION_TO_NOTICE = 0.5f;
54 //public static final float MAX_THRESHOLD = 1000f;
55 public static float MAX_THRESHOLD = 600f;
56
57 public static enum PunExType {
58 ANTI_COMPETITION,
59 ANTI_FREE_PORT,
60 TERRITORIAL,
61 }
62
63 public static enum PunExGoal {
64 RAID_PRODUCTION,
65 RAID_SPACEPORT,
66 BOMBARD,
67 //EVACUATE,
68 }
69
70 public static class PunExReason {
71 public PunExType type;
72 public String commodityId;
73 public String marketId;
74 public float weight;
75 public PunExReason(PunExType type) {
76 this.type = type;
77 }
78 }
79
80 public static class PunExData {
81 public FactionAPI faction;
82 public IntervalUtil tracker = new IntervalUtil(20f, 40f);
83 public float anger = 0f;
84 public float threshold = 100f;
85 public float timeout = 0f;;
86 public BaseIntelPlugin intel;
87 public Random random = new Random();
88
89 public int numSuccesses = 0;
90 public int numAttempts = 0;
91 }
92
93 protected float timeout = 0f;
94 protected int numSentSinceTimeout = 0;
95 protected LinkedHashMap<FactionAPI, PunExData> data = new LinkedHashMap<FactionAPI, PunExData>();
96
98 Global.getSector().getMemoryWithoutUpdate().set(KEY, this);
99 }
100
101 protected Object readResolve() {
102 return this;
103 }
104
105 public PunExData getDataFor(FactionAPI faction) {
106 return data.get(faction);
107 }
108
109
110 public LinkedHashMap<FactionAPI, PunExData> getData() {
111 return data;
112 }
113
114 public void advance(float amount) {
115 //if (true) return;
116
117 float days = Misc.getDays(amount);
118
119 Set<FactionAPI> seen = new HashSet<FactionAPI>();
120 for (MarketAPI market : Global.getSector().getEconomy().getMarketsInGroup(null)) {
121// JSONObject json = market.getFaction().getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
122// boolean canSendWithoutMilitaryBase = json != null && json.optBoolean("canSendWithoutMilitaryBase", false);
123 //if (market.getMemoryWithoutUpdate().getBoolean(MemFlags.MARKET_MILITARY) || canSendWithoutMilitaryBase) {
124 if (true) {
125 FactionAPI faction = market.getFaction();
126 if (Misc.getCommissionFaction() == faction) continue;
127
128 if (seen.contains(faction) || data.containsKey(faction)) {
129 seen.add(faction);
130 continue;
131 }
132 JSONObject json = faction.getCustomJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
133 if (json != null) {
134 PunExData curr = new PunExData();
135 curr.faction = faction;
136 data.put(faction, curr);
137 seen.add(faction);
138 }
139 }
140 }
141 data.keySet().retainAll(seen);
142
143 if (timeout > 0) {
144 timeout -= days * (DebugFlags.PUNITIVE_EXPEDITION_DEBUG ? 1000f : 1f);
145 if (timeout <= 0) {
146 timeout = 0;
148 }
149 return;
150 }
151
152 boolean first = true;
153 for (PunExData curr : data.values()) {
155 days *= 1000f;
156 curr.timeout = 0f;
157 curr.anger = 1000f;
158 }
159 first = false;
160
161 if (curr.intel != null) {
162 if (curr.intel.isEnded()) {
163 curr.timeout = 100f + 100f * curr.random.nextFloat();
164
165 if (curr.intel instanceof PunitiveExpeditionIntel) {
167 if (!intel.isTerritorial()) {
168 curr.timeout += getExtraTimeout(curr);
169 }
170 }
171
172 curr.intel = null;
173 }
174 } else {
175 curr.timeout -= days;
176 if (curr.timeout <= 0) curr.timeout = 0;
177 }
178
179
180 curr.tracker.advance(days);
181 //System.out.println(curr.tracker.getElapsed());
182 if (curr.tracker.intervalElapsed() &&
183 curr.intel == null &&
184 curr.timeout <= 0) {
185 checkExpedition(curr);
186 }
187 }
188 }
189
190 public float getExtraTimeout(PunExData d) {
191 float total = 0f;
192 for (PunExData curr : data.values()) {
193 JSONObject json = curr.faction.getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
194 if (json == null) continue;
195
196 List<MarketAPI> markets = Misc.getFactionMarkets(curr.faction, null);
197 if (markets.isEmpty()) continue;
198
199 boolean vsCompetitors = json.optBoolean("vsCompetitors", false);
200 boolean vsFreePort = json.optBoolean("vsFreePort", false);
201 boolean territorial = json.optBoolean("territorial", false);
202
203 if (vsCompetitors || vsFreePort) {
204 total++;
205 }
206 }
207
208 return Math.min(10f, Math.max(0, total - TARGET_NUMBER_FOR_FREQUENCY)) *
209 (MIN_TIMEOUT * 0.9f + MIN_TIMEOUT * 0.9f * d.random.nextFloat());
210 }
211
212
213 public int getOngoing() {
214 int ongoing = 0;
215 for (PunExData d : data.values()) {
216 if (d.intel != null) {
217 ongoing++;
218 }
219 }
220 //ongoing = 0;
221 return ongoing;
222 }
223
224 protected void checkExpedition(PunExData curr) {
225 JSONObject json = curr.faction.getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
226 if (json == null) return;
227
228// if (curr.faction.getId().equals(Factions.TRITACHYON)) {
229// System.out.println("wefwefwe");
230// }
231 List<PunExReason> reasons = getExpeditionReasons(curr);
232// if (!reasons.isEmpty()) {
233// System.out.println("HERE");
234// }
235 float total = 0f;
236 for (PunExReason reason : reasons) {
237 total += reason.weight;
238 }
239
240 total *= ANGER_BUILDUP_MULT;
241
242 curr.anger += total * (0.25f + curr.random.nextFloat() * 0.75f);
243 if (curr.anger >= curr.threshold) {
244 if (getOngoing() >= MAX_CONCURRENT) {
245 curr.anger = 0;
246 } else {
247 createExpedition(curr);
248 }
249 }
250 }
251
252 public static float COMPETITION_PRODUCTION_MULT = 20f;
253 public static float ILLEGAL_GOODS_MULT = 3f;
254 public static float FREE_PORT_SIZE_MULT = 5f;
255 public static float TERRITORIAL_ANGER = 500f;
256
257 public List<PunExReason> getExpeditionReasons(PunExData curr) {
258 List<PunExReason> result = new ArrayList<PunExReason>();
259
260 JSONObject json = curr.faction.getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
261 if (json == null) return result;
262
263 List<MarketAPI> markets = Misc.getFactionMarkets(curr.faction, null);
264 if (markets.isEmpty()) return result;
265
266 boolean vsCompetitors = json.optBoolean("vsCompetitors", false);
267 boolean vsFreePort = json.optBoolean("vsFreePort", false);
268 boolean territorial = json.optBoolean("territorial", false);
269
270 MarketAPI test = markets.get(0);
271 FactionAPI player = Global.getSector().getPlayerFaction();
272
273 if (vsCompetitors) {
274 for (CommodityOnMarketAPI com : test.getAllCommodities()) {
275 if (com.isNonEcon()) continue;
276 if (curr.faction.isIllegal(com.getId())) continue;
277
278 CommodityMarketDataAPI cmd = com.getCommodityMarketData();
279 if (cmd.getMarketValue() <= 0) continue;
280
281 Map<FactionAPI, Integer> shares = cmd.getMarketSharePercentPerFaction();
282 int numHigher = 0;
283 int factionShare = shares.get(curr.faction);
284 if (factionShare <= 0) continue;
285
286 for (FactionAPI faction : shares.keySet()) {
287 if (curr.faction == faction) continue;
288 if (shares.get(faction) > factionShare) {
289 numHigher++;
290 }
291 }
292
293 if (numHigher >= FACTION_MUST_BE_IN_TOP_X_PRODUCERS) continue;
294
295 int playerShare = cmd.getMarketSharePercent(player);
296 float threshold = PLAYER_FRACTION_TO_NOTICE;
298 threshold = 0.1f;
299 }
300 if (playerShare < factionShare * threshold || playerShare <= 0) continue;
301
302 PunExReason reason = new PunExReason(PunExType.ANTI_COMPETITION);
303 reason.weight = (float)playerShare / (float)factionShare * COMPETITION_PRODUCTION_MULT;
304 reason.commodityId = com.getId();
305 result.add(reason);
306 }
307 }
308
309 if (vsFreePort) {
310 for (MarketAPI market : Global.getSector().getEconomy().getMarketsInGroup(null)) {
311 if (!market.isPlayerOwned()) continue;
312 if (!market.isFreePort()) continue;
313 if (market.isInHyperspace()) continue;
314
315 for (CommodityOnMarketAPI com : test.getAllCommodities()) {
316 if (com.isNonEcon()) continue;
317 if (!curr.faction.isIllegal(com.getId())) continue;
318
319 CommodityMarketDataAPI cmd = com.getCommodityMarketData();
320 if (cmd.getMarketValue() <= 0) continue;
321
322 int playerShare = cmd.getMarketSharePercent(player);
323 if (playerShare <= 0) continue;
324
325 PunExReason reason = new PunExReason(PunExType.ANTI_FREE_PORT);
326 reason.weight = playerShare * ILLEGAL_GOODS_MULT;
327 reason.commodityId = com.getId();
328 reason.marketId = market.getId();
329 result.add(reason);
330 }
331
332 if (market.isFreePort()) {
333 PunExReason reason = new PunExReason(PunExType.ANTI_FREE_PORT);
334 reason.weight = Math.max(1, market.getSize() - 2) * FREE_PORT_SIZE_MULT;
335 reason.marketId = market.getId();
336 result.add(reason);
337 }
338 }
339 }
340
341 if (territorial) {
342 int maxSize = MarketCMD.getBombardDestroyThreshold();
343 for (MarketAPI market : Global.getSector().getEconomy().getMarketsInGroup(null)) {
344 if (!market.isPlayerOwned()) continue;
345 if (market.isInHyperspace()) continue;
346
347 boolean destroy = market.getSize() <= maxSize;
348 if (!destroy) continue;
349
350 FactionAPI claimedBy = Misc.getClaimingFaction(market.getPrimaryEntity());
351 if (claimedBy != curr.faction) continue;
352
353 PunExReason reason = new PunExReason(PunExType.TERRITORIAL);
354 reason.weight = TERRITORIAL_ANGER;
355 reason.marketId = market.getId();
356 result.add(reason);
357 }
358 }
359
360 return result;
361 }
362
363
364 public void createExpedition(PunExData curr) {
365 createExpedition(curr, null);
366 }
367 public void createExpedition(PunExData curr, Integer fpOverride) {
368
369 JSONObject json = curr.faction.getCustom().optJSONObject(Factions.CUSTOM_PUNITIVE_EXPEDITION_DATA);
370 if (json == null) return;
371
372// boolean vsCompetitors = json.optBoolean("vsCompetitors", false);
373// boolean vsFreePort = json.optBoolean("vsFreePort", false);
374 boolean canBombard = json.optBoolean("canBombard", false);
375// boolean territorial = json.optBoolean("territorial", false);
376
377 List<PunExReason> reasons = getExpeditionReasons(curr);
378 WeightedRandomPicker<PunExReason> reasonPicker = new WeightedRandomPicker<PunExReason>(curr.random);
379 for (PunExReason r : reasons) {
380 //if (r.type == PunExType.ANTI_COMPETITION) continue;
381 reasonPicker.add(r, r.weight);
382 }
383 PunExReason reason = reasonPicker.pick();
384 if (reason == null) return;
385
386
387 WeightedRandomPicker<MarketAPI> targetPicker = new WeightedRandomPicker<MarketAPI>(curr.random);
388 //for (PunExReason reason : reasons) {
389
390 //WeightedRandomPicker<MarketAPI> picker = new WeightedRandomPicker<MarketAPI>(curr.random);
391 for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) {
392 if (!market.isPlayerOwned()) continue;
393 if (market.isInHyperspace()) continue;
394
395 float weight = 0f;
396 if (reason.type == PunExType.ANTI_COMPETITION && reason.commodityId != null) {
397 if (market.getSize() < MIN_COLONY_SIZE_FOR_NON_TERRITORIAL) continue;
398
399 CommodityOnMarketAPI com = market.getCommodityData(reason.commodityId);
400 int share = com.getCommodityMarketData().getExportMarketSharePercent(market);
401// if (share <= 0 && com.getAvailable() > 0) {
402// share = 1;
403// }
404 weight += share * share;
405 } else if (reason.type == PunExType.ANTI_FREE_PORT && market.getId().equals(reason.marketId)) {
406 if (market.getSize() < MIN_COLONY_SIZE_FOR_NON_TERRITORIAL) continue;
407
408 weight = 1f;
409 } else if (reason.type == PunExType.TERRITORIAL && market.getId().equals(reason.marketId)) {
410 weight = 1f;
411 }
412
413 targetPicker.add(market, weight);
414 }
415
416 MarketAPI target = targetPicker.pick();
417 if (target == null) return;
418
419 WeightedRandomPicker<MarketAPI> picker = new WeightedRandomPicker<MarketAPI>(curr.random);
420 for (MarketAPI market : Global.getSector().getEconomy().getMarketsInGroup(null)) {
421 boolean canSendWithoutMilitaryBase = json.optBoolean("canSendWithoutMilitaryBase", false);
422 boolean military = market.getMemoryWithoutUpdate().getBoolean(MemFlags.MARKET_MILITARY);
423 if (market.getFaction() == curr.faction &&
424 (military || canSendWithoutMilitaryBase)) {
425 float w = 1f;
426 if (military) w *= 10f;
427 picker.add(market, market.getSize() * w);
428 }
429 }
430
431 MarketAPI from = picker.pick();
432 if (from == null) return;
433
434 PunExGoal goal = null;
435 Industry industry = null;
436 if (reason.type == PunExType.ANTI_FREE_PORT) {
437 goal = PunExGoal.RAID_SPACEPORT;
438 if (canBombard && curr.numSuccesses >= 2) {
439 goal = PunExGoal.BOMBARD;
440 }
441 } else if (reason.type == PunExType.TERRITORIAL) {
442 if (canBombard || true) {
443 goal = PunExGoal.BOMBARD;
444 } else {
445 //goal = PunExGoal.EVACUATE;
446 }
447 } else {
448 goal = PunExGoal.RAID_PRODUCTION;
449 if (reason.commodityId == null || curr.numSuccesses >= 1) {
450 goal = PunExGoal.RAID_SPACEPORT;
451 }
452 if (canBombard && curr.numSuccesses >= 2) {
453 goal = PunExGoal.BOMBARD;
454 }
455 }
456
457 //goal = PunExGoal.BOMBARD;
458
459 if (goal == PunExGoal.RAID_SPACEPORT) {
460 for (Industry temp : target.getIndustries()) {
461 if (temp.getSpec().hasTag(Industries.TAG_UNRAIDABLE)) continue;
462 if (temp.getSpec().hasTag(Industries.TAG_SPACEPORT)) {
463 industry = temp;
464 break;
465 }
466 }
467 if (industry == null) return;
468 } else if (goal == PunExGoal.RAID_PRODUCTION && reason.commodityId != null) {
469 int max = 0;
470 for (Industry temp : target.getIndustries()) {
471 if (temp.getSpec().hasTag(Industries.TAG_UNRAIDABLE)) continue;
472
473 int prod = temp.getSupply(reason.commodityId).getQuantity().getModifiedInt();
474 if (prod > max) {
475 max = prod;
476 industry = temp;
477 }
478 }
479 if (industry == null) return;
480 }
481
482 //float fp = from.getSize() * 20 + threshold * 0.5f;
483 float fp = 50 + curr.threshold * 0.5f;
484 fp = Math.max(50, fp - 50);
485 //fp = 500;
486// if (from.getFaction().isHostileTo(target.getFaction())) {
487// fp *= 1.25f;
488// }
489
490 if (fpOverride != null) {
491 fp = fpOverride;
492 }
493
494
495 float totalAttempts = 0f;
496 for (PunExData d : data.values()) {
497 totalAttempts += d.numAttempts;
498 }
499 //if (totalAttempts > 10) totalAttempts = 10;
500
501 float extraMult = 0f;
502 if (totalAttempts <= 2) {
503 extraMult = 0f;
504 } else if (totalAttempts <= 4) {
505 extraMult = 1f;
506 } else if (totalAttempts <= 7) {
507 extraMult = 2f;
508 } else if (totalAttempts <= 10) {
509 extraMult = 3f;
510 } else {
511 extraMult = 4f;
512 }
513
514 float orgDur = 20f + extraMult * 10f + (10f + extraMult * 5f) * (float) Math.random();
515
516
517 curr.intel = new PunitiveExpeditionIntel(from.getFaction(), from, target, fp, orgDur,
518 goal, industry, reason);
519 if (curr.intel.isDone()) {
520 curr.intel = null;
521 timeout = orgDur + MIN_TIMEOUT + curr.random.nextFloat() * (MAX_TIMEOUT - MIN_TIMEOUT);
522 return;
523 }
524
525 if (curr.random.nextFloat() < numSentSinceTimeout * PROB_TIMEOUT_PER_SENT) {
526 timeout = orgDur + MIN_TIMEOUT + curr.random.nextFloat() * (MAX_TIMEOUT - MIN_TIMEOUT);
527 }
529
530 curr.numAttempts++;
531 curr.anger = 0f;
532 curr.threshold *= 2f;
533 if (curr.threshold > MAX_THRESHOLD) {
534 curr.threshold = MAX_THRESHOLD;
535 }
536 }
537
538
539
540 public boolean isDone() {
541 return false;
542 }
543
544 public boolean runWhilePaused() {
545 return false;
546 }
547
548}
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
static SettingsAPI getSettings()
Definition Global.java:51
static SectorAPI getSector()
Definition Global.java:59
float getFloatFromArray(String key, int index)