Starsector API
Loading...
Searching...
No Matches
DeliveryBarEvent.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.intel.bar.events;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.LinkedHashSet;
6import java.util.List;
7import java.util.Random;
8import java.util.Set;
9
10import com.fs.starfarer.api.Global;
11import com.fs.starfarer.api.campaign.CargoAPI;
12import com.fs.starfarer.api.campaign.FactionAPI;
13import com.fs.starfarer.api.campaign.OptionPanelAPI;
14import com.fs.starfarer.api.campaign.TextPanelAPI;
15import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
16import com.fs.starfarer.api.campaign.econ.MarketAPI;
17import com.fs.starfarer.api.characters.PersonAPI;
18import com.fs.starfarer.api.impl.campaign.ids.Conditions;
19import com.fs.starfarer.api.impl.campaign.ids.Factions;
20import com.fs.starfarer.api.impl.campaign.ids.Ranks;
21import com.fs.starfarer.api.impl.campaign.ids.Sounds;
22import com.fs.starfarer.api.impl.campaign.ids.Tags;
23import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
24import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption;
25import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.BaseOptionStoryPointActionDelegate;
26import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.StoryOptionParams;
27import com.fs.starfarer.api.ui.TooltipMakerAPI;
28import com.fs.starfarer.api.util.Misc;
29import com.fs.starfarer.api.util.WeightedRandomPicker;
30
32
33 public static String KEY_FAILED_RECENTLY = "$core_dmi_failedRecently";
34
35 public static String KEY_SAW_DELIVERY_EVENT_RECENTLY = "$core_dmi_sawRecently";
36 public static String KEY_ACCEPTED_AT_THIS_MARKET_RECENTLY = "$core_dmi_acceptedAtThisMarket";
37
38 public static float PROB_HIGHER_CAPACITY = 0.25f;
39
40 public static float FAILED_RECENTLY_DURATION = 365f;
41 public static float SAW_RECENTLY_DURATION = 180f;
42 public static float ACCEPTED_AT_THIS_MARKET_DURATION = 90f;
43
44 public static float PROB_TO_SHOW = 0.5f;
45
46 protected MarketAPI destination;
47 protected int reward;
48 protected int escrow;
49 protected float duration;
50 protected FactionAPI faction;
51 protected int playerCargoCap = 0;
52 protected int playerFuelCap = 0;
53
54 protected DestinationData data;
55
57 super();
58 }
59
60 public boolean shouldShowAtMarket(MarketAPI market) {
61 //if (true) return true;
62 if (!super.shouldShowAtMarket(market)) return false;
63
64 if (market.getFactionId().equals(Factions.PIRATES)) return false;
65 if (market.getFactionId().equals(Factions.LUDDIC_PATH)) return false;
66
67 // what we want:
68 // 1) don't show at the same market for a while
69 // 2) randomly don't show at any particular market,
70 // unless the player hasn't seen any delivery events in a while
71
72 if (market.getMemoryWithoutUpdate().getBoolean(KEY_ACCEPTED_AT_THIS_MARKET_RECENTLY)) {
73 return false;
74 }
75
77
78 if (destination == null) return false;
79
80 if (escrow > 0 && market.isPlayerOwned()) return false;
81
82 if (Global.getSector().getMemoryWithoutUpdate().getBoolean(KEY_SAW_DELIVERY_EVENT_RECENTLY)
83 && shownAt != market) {
84 if (random.nextFloat() > PROB_TO_SHOW) return false;
85 }
86
87 Global.getSector().getMemoryWithoutUpdate().set(KEY_SAW_DELIVERY_EVENT_RECENTLY, true,
89
90 return true;
91 }
92
93 @Override
94 protected void regen(MarketAPI market) {
95 //if (this.market == market) return;
96 if (this.market != market) {
98 playerFuelCap = 0;
99 }
100
101 this.market = market;
102 random = new Random(seed + market.getId().hashCode());
103 //random = Misc.random;
104
106
107 if (destination != null) {
109 }
110 }
111
112 public static class DestinationData {
113 public MarketAPI dest;
114 public CommodityOnMarketAPI comFrom;
115 public CommodityOnMarketAPI com;
116 public float distLY;
117
118 public boolean fromHasPA = false;
119 public boolean fromHasCells = false;
120
121 public boolean hasPA = false;
122 public boolean hasCells = false;
123
124 public boolean illegal = false;
125
126 public float score = 0;
127
128 public DestinationData(MarketAPI from, MarketAPI dest,
129 CommodityOnMarketAPI comFrom, CommodityOnMarketAPI comDest) {
130 this.dest = dest;
131 this.comFrom = comFrom;
132 this.com = comDest;
133 distLY = Misc.getDistanceLY(from.getLocationInHyperspace(), dest.getLocationInHyperspace());
134
135 fromHasPA = from.hasCondition(Conditions.PIRATE_ACTIVITY);
136 fromHasCells = from.hasCondition(Conditions.PATHER_CELLS);
137 hasPA = dest.hasCondition(Conditions.PIRATE_ACTIVITY);
138 hasCells = dest.hasCondition(Conditions.PATHER_CELLS);
139
140 illegal = dest.isIllegal(com.getId());
141
142 score += Math.min(distLY, 10f);
143
144 if (fromHasPA) score += 10f;
145 if (fromHasCells) score += 5f;
146 if (hasPA) score += 10f;
147 if (hasCells) score += 5f;
148
149// score += (comFrom.getAvailable() + comFrom.getMaxSupply()) * 0.5f;
150// score += comDest.getMaxDemand();
151
152 }
153 }
154
155 protected void computeData(MarketAPI market) {
156
157 data = null;
158 destination = null;
159 reward = 0;
160 duration = 0;
161 faction = null;
162 quantity = 0;
163 commodity = null;
164
165 List<CommodityOnMarketAPI> commodities = new ArrayList<CommodityOnMarketAPI>();
166 for (CommodityOnMarketAPI com : market.getCommoditiesCopy()) {
167 if (com.isNonEcon()) continue;
168 if (com.isMeta()) continue;
169 if (com.isPersonnel()) continue;
170 if (com.isIllegal()) continue;
171
172 if (com.getAvailable() <= 0) continue;
173 if (com.getMaxSupply() <= 0) continue;
174
175 commodities.add(com);
176 }
177
178 List<DestinationData> potential = new ArrayList<DestinationData>();
179
180 float maxScore = 0;
181 float maxDist = 0;
182 for (MarketAPI other : Global.getSector().getEconomy().getMarketsCopy()) {
183 if (other == market) continue;
184 if (other.isHidden()) continue;
185 if (other.isInvalidMissionTarget()) continue;
186
187 if (other.getEconGroup() == null && market.getEconGroup() != null) continue;
188 if (other.getEconGroup() != null && !other.getEconGroup().equals(market.getEconGroup())) continue;
189
190 if (other.getStarSystem() == null) continue;
191
192 //WeightedRandomPicker<T>
193 for (CommodityOnMarketAPI com : commodities) {
194 //CommodityOnMarketAPI otherCom = other.getCommodityData(com.getId());
195 CommodityOnMarketAPI otherCom = other.getCommodityData(com.getDemandClass());
196 if (otherCom.getMaxDemand() <= 0) continue;
197
198 DestinationData data = new DestinationData(market, other, com, otherCom);
199 if (data.illegal) continue;
200 if (data.score > maxScore) {
201 maxScore = data.score;
202 }
203 if (data.distLY > maxDist) {
204 maxDist = data.distLY;
205 }
206 potential.add(data);
207 }
208 }
209 if (maxDist > 10) maxDist = 10;
210
211 WeightedRandomPicker<DestinationData> picker = new WeightedRandomPicker<DestinationData>(random);
212 for (int i = 0; i < potential.size(); i++) {
213 DestinationData d = potential.get(i);
214 if (d.score > maxScore * 0.5f && d.distLY > maxDist * 0.5f) {
215 picker.add(d, d.score * d.score * d.score);
216 }
217 }
218
219// Collections.sort(potential, new Comparator<DestinationData>() {
220// public int compare(DestinationData o1, DestinationData o2) {
221// return (int) Math.signum(o2.score - o1.score);
222// }
223// });
224//
225//
226// WeightedRandomPicker<DestinationData> picker = new WeightedRandomPicker<DestinationData>(random);
227// for (int i = 0; i < potential.size() && i < 5; i++) {
228// DestinationData d = potential.get(i);
229// picker.add(d, d.score * d.score * d.score);
230// }
231
232 DestinationData pick = picker.pick();
233
234 if (pick == null) return;
235
236 destination = pick.dest;
237 duration = pick.distLY * 5 + 50;
238 duration = (int)duration / 10 * 10;
239
240 CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo();
241 quantity = (int) cargo.getMaxCapacity();
242 if (pick.com.isFuel()) {
243 quantity = (int) cargo.getMaxFuel();
244 }
245
246 if (random.nextFloat() < PROB_HIGHER_CAPACITY) {
247 quantity *= 1f + random.nextFloat() * 3f;
248 quantity = (int) quantity;
249 }
250
251 // don't want mission at market to update quantity as player changes their fleet up
252 if (pick.com.isFuel()) {
253 if (playerFuelCap == 0) {
255 } else {
257 }
258 } else {
259 if (playerCargoCap == 0) {
261 } else {
263 }
264 }
265
266 quantity *= 0.5f + 0.25f * random.nextFloat();
267
268 // somewhat less of the more valuable stuff
269 quantity *= Math.min(1f, 200f / pick.comFrom.getCommodity().getBasePrice());
270
271 int limit = (int) (pick.comFrom.getAvailable() * pick.comFrom.getCommodity().getEconUnit());
272 limit *= 0.75f + 0.5f * random.nextFloat();
273 //if (quantity > 5000) quantity = 5000;
274 if (quantity > limit) quantity = limit;
275
276
277 if (quantity > 10000) quantity = quantity / 1000 * 1000;
278 else if (quantity > 100) quantity = quantity / 10 * 10;
279 else if (quantity > 10) quantity = quantity / 10 * 10;
280
281 if (quantity < 10) quantity = 10;
282
283
284 //float base = pick.com.getMarket().getSupplyPrice(pick.com.getId(), 1, true);
285 float base = pick.comFrom.getMarket().getSupplyPrice(pick.comFrom.getId(), 1, true);
286
287 if (quantity * base < 4000) {
288 base = Math.min(100, 4000 / quantity);
289 }
290
291// float minBase = 100;
292// if (quantity > 500) {
293// minBase = 50;
294// }
295 float minBase = 100f - 50f * Math.min(1f, quantity / 500f);
296 minBase = (base + minBase) * 0.75f;
297
298 if (base < minBase) base = minBase;
299
300 //float extra = 2000;
301
302 float mult = pick.score / 30f;
303 //if (market.isPlayerOwned() && mult > 2f) mult = 2f;
304 //if (market.isPlayerOwned()) mult *= 0.75f;
305
306 if (mult < 0.75f) mult = 0.75f;
307 //if (mult > 2) mult = 2;
308 reward = (int) (base * mult * quantity);
309
310// float minPerUnit = 50;
311// if (reward / quantity < minPerUnit) {
312// reward = (int) (minPerUnit * quantity);
313// }
314
315 reward = reward / 1000 * 1000;
316 if (reward < 4000) reward = 4000;
317
318
319 if (Global.getSector().getMemoryWithoutUpdate().getBoolean(KEY_FAILED_RECENTLY)) {
320 escrow = (int) (quantity * pick.comFrom.getCommodity().getBasePrice());
321 }
322
323
324 if (market.getFaction() == pick.dest.getFaction()) {
325 faction = market.getFaction();
326 } else {
327 faction = Global.getSector().getFaction(Factions.INDEPENDENT);
328 if (faction == null) faction = market.getFaction();
329 }
330
331 commodity = pick.comFrom.getId();
332
333 data = pick;
334 }
335
336 protected int getNegotiatedAmount() {
337 return (int) (reward * 1.5f);
338 }
339
340 protected void addStoryOption() {
341 String id = "negotiate_id";
342 options.addOption("Negotiate a higher fee for the delivery", id);
343
344 StoryOptionParams params = new StoryOptionParams(id, 1, "negotiateDeliveryFee", Sounds.STORY_POINT_SPEND_INDUSTRY,
345 "Negotiated higher fee for delivery of " + data.comFrom.getCommodity().getLowerCaseName() + " to " + data.dest.getName());
346
347 SetStoryOption.set(dialog, params,
348 new BaseOptionStoryPointActionDelegate(dialog, params) {
349
350 @Override
351 public void confirm() {
352 super.confirm();
354 dialog.getTextPanel().addPara(getNegotiatedText());
355 OptionPanelAPI options = dialog.getOptionPanel();
356 options.clearOptions();
357 options.addOption("Continue", OPTION_CONFIRM);
358 //optionSelected(null, OPTION_CONFIRM);
359 }
360
361 @Override
362 public String getTitle() {
363 //return "Negotiating delivery fee";
364 return null;
365 }
366
367 @Override
368 public void createDescription(TooltipMakerAPI info) {
369 float opad = 10f;
370
371 info.addSpacer(-opad);
372
373 info.setParaInsigniaLarge();
374 info.addPara("You're able to negotiate the delivery fee from %s up to " +
375 "%s.", 0f, Misc.getHighlightColor(),
376 Misc.getDGSCredits(reward),
377 Misc.getDGSCredits(getNegotiatedAmount()));
378
379 info.addSpacer(opad * 2f);
380 addActionCostSection(info);
381 }
382
383 });
384 }
385
386 @Override
387 protected boolean canAccept() {
388 if (escrow <= 0) return true;
389 float credits = Global.getSector().getPlayerFleet().getCargo().getCredits().get();
390 boolean canAfford = credits >= escrow;
391 return canAfford;
392 }
393
394 @Override
395 protected void doStandardConfirmActions() {
396 CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo();
397 TextPanelAPI text = dialog.getTextPanel();
398
399 cargo.addCommodity(commodity, quantity);
400 AddRemoveCommodity.addCommodityGainText(commodity, quantity, text);
401
402 if (escrow > 0) {
403 cargo.getCredits().subtract(escrow);
404 AddRemoveCommodity.addCreditsLossText(escrow, text);
405 }
406
407 createIntel();
408 }
409
410 protected void createIntel() {
412
413 market.getMemoryWithoutUpdate().set(KEY_ACCEPTED_AT_THIS_MARKET_RECENTLY, true,
414 ACCEPTED_AT_THIS_MARKET_DURATION * (0.75f + random.nextFloat() * 0.5f));
415 }
416
417 @Override
418 protected void adjustPerson(PersonAPI person) {
419 super.adjustPerson(person);
420 person.setImportanceAndVoice(pickImportance(), random);
421 person.addTag(Tags.CONTACT_TRADE);
422 }
423
424 @Override
425 protected String getPersonFaction() {
426 return faction.getId();
427 }
428
429 @Override
430 protected String getPersonRank() {
431 return Ranks.CITIZEN;
432 }
433
434 @Override
435 protected String getPersonPost() {
436 //return Ranks.CITIZEN;
437 return pickOne(Ranks.POST_TRADER, Ranks.POST_COMMODITIES_AGENT,
438 Ranks.POST_MERCHANT, Ranks.POST_INVESTOR);
439 }
440
441 @Override
442 protected float getPriceMult() {
443 return 0;
444 }
445
446 @Override
447 protected String getPrompt() {
448 if (faction.getId().equals(Factions.INDEPENDENT)) {
449 return "At a corner table, a concerned-looking " + getManOrWoman() +
450 " glumly examines " + getHisOrHer() + " TriPad.";
451 } else {
452 return "At a corner table, a concerned-looking " + getManOrWoman() +
453 " in " + faction.getPersonNamePrefixAOrAn() + " " + faction.getPersonNamePrefix() + " uniform " +
454 " glumly examines " + getHisOrHer() + " TriPad.";
455 }
456 }
457
458 @Override
459 protected String getOptionText() {
460 return "Nod to the concerned " + getManOrWoman() + " and walk over to " + getHisOrHer() + " table";
461 }
462
463 @Override
464 protected String getMainText() {
465 String str = "";
466 if (market.isPlayerOwned()) {
467 String sir = "sir";
468 if (Global.getSector().getPlayerPerson().isFemale()) sir = "ma'am";
469 str = "\"Oh, it's you, " + sir + "!\", " + getHeOrShe() +
470 " exclaims. Taking a moment to recover " + getHisOrHer() + " composure, " + getHeOrShe() + " says \"We've got a little logistical problem that could use " +
471 "your personal touch. " +
472 "There are %s units of " + data.comFrom.getCommodity().getLowerCaseName() + " that urgently need to be delivered " +
473 " to %s" +
474 ", in the " + data.dest.getStarSystem().getNameWithLowercaseType() + ". ";
475 if (data.fromHasPA || data.hasPA) {
476 str += "However, recent pirate activity has been making that difficult, and the regular trade fleets " +
477 "aren't quite up to the task.\"";
478 } else if (data.fromHasCells || data.hasCells) {
479 str += "However, recent Pather cell activity has been making that difficult, and the regular trade fleets " +
480 "aren't quite up to the task.\"";
481 } else {
482 str += "But, well, you know what trader captains are like. " +
483 "There have been some disagreements over hazard pay, and it's left us in the lurch.\"";
484 }
485 } else {
486 str = "After a brief introduction, " + getHeOrShe() + " wastes no time in getting to the point.\n\n" +
487 "\"I've got %s units of " + data.comFrom.getCommodity().getLowerCaseName() + " that urgently need to be delivered " +
488 " to %s" +
489 ", in the " + data.dest.getStarSystem().getNameWithLowercaseType() + ". ";
490 if (data.fromHasPA || data.hasPA) {
491 str += "Recent pirate activity has been making that difficult, but you look like someone that could " +
492 "get the job done.\"";
493 } else if (data.fromHasCells || data.hasCells) {
494 str += "Recent Pather cell activity has been making that difficult, but I'm sure you can handle " +
495 "any trouble.\"";
496 } else {
497 str += "We've had some disputes with the regular shipping company, and it's left us in the lurch. " +
498 "Should be a milk run for someone like you, though.\"";
499 }
500 }
501
502 //str += "\n\nYou recall that " + data.dest.getName() + " is under %s control, and %s light-years away. ";
503
504 String where = "located in hyperspace,";
505 if (data.dest.getStarSystem() != null) {
506 //where = "located in the " + data.dest.getStarSystem().getNameWithLowercaseType() + ", which is";
507 where = "located in the " + data.dest.getStarSystem().getNameWithLowercaseType() + "";
508 }
509 //str += "\n\nYou recall that " + data.dest.getName() + " is under %s control, and " + where + " %s light-years away. ";
510 str += "\n\nYou recall that " + data.dest.getName() + " is under %s control, and " + where + ".";
511
512 CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo();
513 if (data.comFrom.isFuel()) {
514 int cap = cargo.getFreeFuelSpace();
515 if (cap > 1) {
516 str += " Your fleet's fuel tanks can hold an additional %s units of fuel.";
517 } else {
518 str += "%sYour fleet's fuel tanks are currently full."; // %s - need to have same number of highlights
519 }
520 } else {
521 int cap = (int) cargo.getSpaceLeft();
522 if (cap > 1) {
523 str += " Your fleet's holds can accommodate an additional %s units of cargo.";
524 } else {
525 str += "%sYour fleet's cargo holds are currently full.";
526 }
527 }
528
529 if (market.isPlayerOwned()) {
530 str += "\n\n" + Misc.ucFirst(getHeOrShe()) + " double-checks something on " + getHisOrHer() + " pad. " +
531 "\"The customer will pay %s upon delivery within %s days. Can you take this on?\"";
532 } else {
533 if (escrow > 0) {
534 str += "\n\n" + Misc.ucFirst(getHeOrShe()) + " double-checks something on " + getHisOrHer() + " pad. " +
535 "\"The offer is %s credits, payable upon delivery within %s days. You'll also have to " +
536 "transfer %s to an escrow account. This will be returned to you " +
537 "when you complete the delivery - " +
538 "standard insurance procedure, you understand.\"";
539 } else {
540 str += "\n\n" + Misc.ucFirst(getHeOrShe()) + " double-checks something on " + getHisOrHer() + " pad. " +
541 "\"The offer is %s credits, payable upon delivery within %s days. You in?\"";
542 }
543 }
544
545 return str;
546 }
547
548 @Override
549 protected String [] getMainTextTokens() {
550 CargoAPI cargo = Global.getSector().getPlayerFleet().getCargo();
551 int cap = 0;
552 if (data.comFrom.isFuel()) {
553 cap = cargo.getFreeFuelSpace();
554 } else {
555 cap = (int) cargo.getSpaceLeft();
556 }
557 return new String [] { Misc.getWithDGS(quantity), data.dest.getName(),
558 data.dest.getFaction().getPersonNamePrefix(),
559 //Misc.getRoundedValueMaxOneAfterDecimal(data.distLY),
560 cap > 1 ? Misc.getWithDGS(cap) : " ",
561 Misc.getDGSCredits(reward),
562 Misc.getWithDGS(duration),
563 Misc.getDGSCredits(escrow) };
564 }
565 @Override
566 protected Color [] getMainTextColors() {
567 return new Color [] { Misc.getHighlightColor(),
568 //data.dest.getFaction().getBaseUIColor(),
569 Misc.getTextColor(),
570 data.dest.getFaction().getBaseUIColor(),
571 //Misc.getHighlightColor(),
572 Misc.getHighlightColor(),
573 Misc.getHighlightColor(),
574 Misc.getHighlightColor(),
575 Misc.getHighlightColor() };
576 }
577
578 @Override
579 protected String getConfirmText() {
580 if (market.isPlayerOwned()) {
581 return "Agree to handle the contract";
582 }
583 return "Accept the delivery contract";
584 }
585
586 protected String getNegotiatedText() {
587 return "\"You drive a hard bargain! Very well, it's a deal.\" " + Misc.ucFirst(getHeOrShe()) +
588 " does not appear too displeased. You consider that the initial offer " +
589 "was probably on the low side.";
590 }
591
592 @Override
593 protected String getCancelText() {
594 if (market.isPlayerOwned()) {
595 return "Decline, explaining that you've got other urgent business to attend to";
596 }
597 return "Decline the offer, explaining that you've got other plans";
598 }
599
600 @Override
601 protected String getAcceptText() {
602 return "You receive authorization codes to access a dockside warehouse, and " +
603 "contact your quartermaster with instructions to begin loading the cargo.";
604 }
605
606
607 public MarketAPI getDestination() {
608 return destination;
609 }
610
611 public int getReward() {
612 return reward;
613 }
614
615 public float getDuration() {
616 return duration;
617 }
618
619 public FactionAPI getFaction() {
620 return faction;
621 }
622
623 public DestinationData getData() {
624 return data;
625 }
626
627 public int getQuantity() {
628 return quantity;
629 }
630
631 public int getEscrow() {
632 return escrow;
633 }
634
635 protected boolean showCargoCap() {
636 return false;
637 }
638
639 @Override
640 protected void showTotalAndOptions() {
641 super.showTotalAndOptions();
642
643 String icon = Global.getSettings().getCommoditySpec(commodity).getIconName();
644 String text = null;
645 Set<String> tags = new LinkedHashSet<String>();
646 tags.add(Tags.INTEL_MISSIONS);
647
648 dialog.getVisualPanel().showMapMarker(getDestination().getPrimaryEntity(),
649 "Destination: " + getDestination().getName(), getDestination().getFaction().getBaseUIColor(),
650 true, icon, text, tags);
651 }
652
653
654
655}
656
657
658
static SettingsAPI getSettings()
Definition Global.java:51
static SectorAPI getSector()
Definition Global.java:59
CommoditySpecAPI getCommoditySpec(String commodityId)