Starsector API
Loading...
Searching...
No Matches
BaseMissionHub.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.missions.hub;
2
3import java.util.ArrayList;
4import java.util.HashSet;
5import java.util.LinkedHashSet;
6import java.util.List;
7import java.util.Map;
8import java.util.Random;
9import java.util.Set;
10
11import org.lwjgl.util.vector.Vector2f;
12
13import com.fs.starfarer.api.Global;
14import com.fs.starfarer.api.campaign.InteractionDialogAPI;
15import com.fs.starfarer.api.campaign.SectorEntityToken;
16import com.fs.starfarer.api.campaign.StarSystemAPI;
17import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin;
18import com.fs.starfarer.api.campaign.econ.MarketAPI;
19import com.fs.starfarer.api.campaign.rules.MemoryAPI;
20import com.fs.starfarer.api.characters.ImportantPeopleAPI;
21import com.fs.starfarer.api.characters.PersonAPI;
22import com.fs.starfarer.api.impl.campaign.DebugFlags;
23import com.fs.starfarer.api.impl.campaign.DevMenuOptions;
24import com.fs.starfarer.api.impl.campaign.ids.Tags;
25import com.fs.starfarer.api.impl.campaign.intel.bar.events.BarEventManager;
26import com.fs.starfarer.api.impl.campaign.intel.contacts.ContactIntel;
27import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithSearch.StarSystemUnexploredReq;
28import com.fs.starfarer.api.impl.campaign.rulecmd.CallEvent.CallableEvent;
29import com.fs.starfarer.api.impl.campaign.rulecmd.FireAll;
30import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest;
31import com.fs.starfarer.api.loading.PersonMissionSpec;
32import com.fs.starfarer.api.util.Misc;
33import com.fs.starfarer.api.util.Misc.Token;
34import com.fs.starfarer.api.util.TimeoutTracker;
35import com.fs.starfarer.api.util.WeightedRandomPicker;
36
37public class BaseMissionHub implements MissionHub, CallableEvent {
38
39 public static float UPDATE_INTERVAL = Global.getSettings().getFloat("contactMissionUpdateIntervalDays");
40 public static int MIN_TO_SHOW = Global.getSettings().getInt("contactMinMissions");
41 public static int MAX_TO_SHOW = Global.getSettings().getInt("contactMaxMissions");
42 public static int MAX_TO_SHOW_WITH_BONUS = Global.getSettings().getInt("contactMaxMissionsWithPriorityBonus");
43
44
45 public static String CONTACT_SUSPENDED = "$mHub_contactSuspended";
46 public static String NUM_BONUS_MISSIONS = "$mHub_numBonusMissions";
47 public static String MISSION_QUALITY_BONUS = "$mHub_missionQualityBonus";
48 public static String LAST_OPENED = "$mHub_lastOpenedTimestamp";
49
50 public static String KEY = "$mHub";
51 public static void set(PersonAPI person, MissionHub hub) {
52 if (hub == null) {
53 person.getMemoryWithoutUpdate().unset(KEY);
54 } else {
55 person.getMemoryWithoutUpdate().set(KEY, hub);
56 }
57 }
58 public static MissionHub get(PersonAPI person) {
59 if (person == null) return null;
60 if (person.getMemoryWithoutUpdate().contains(KEY)) {
61 return (MissionHub) person.getMemoryWithoutUpdate().get(KEY);
62 }
63 return null;
64 }
65
66 public static float getDaysSinceLastOpened(PersonAPI person) {
67 if (!person.getMemoryWithoutUpdate().contains(LAST_OPENED)) {
68 return -1;
69 }
70 long ts = person.getMemoryWithoutUpdate().getLong(LAST_OPENED);
71 return Global.getSector().getClock().getElapsedDaysSince(ts);
72 }
73 public static long getLastOpenedTimestamp(PersonAPI person) {
74 if (!person.getMemoryWithoutUpdate().contains(LAST_OPENED)) {
75 return Long.MIN_VALUE;
76 }
77 return person.getMemoryWithoutUpdate().getLong(LAST_OPENED);
78 }
79
80 public static void setDaysSinceLastOpened(PersonAPI person) {
81 person.getMemoryWithoutUpdate().set(LAST_OPENED, Global.getSector().getClock().getTimestamp());
82 }
83
84
85 //protected List<MHMission> missions = new ArrayList<MHMission>();
86 //protected TimeoutTracker<HubMissionCreator> timeout = new TimeoutTracker<HubMissionCreator>();
87
88 protected TimeoutTracker<String> timeout = new TimeoutTracker<String>();
89 protected TimeoutTracker<String> recentlyAcceptedTimeout = new TimeoutTracker<String>();
90 protected List<HubMissionCreator> creators = new ArrayList<HubMissionCreator>();
91 protected transient List<HubMission> offered = new ArrayList<HubMission>();
92
93 protected PersonAPI person;
94
95 public BaseMissionHub(PersonAPI person) {
96 this.person = person;
97
98 //creators.add(new GADataFromRuinsCreator());
100 }
101
103 List<PersonMissionSpec> specs = getMissionsForPerson(person);
104 Set<String> validMissions = new HashSet<String>();
105 Set<String> alreadyHaveCreatorsFor = new HashSet<String>();
106 for (PersonMissionSpec spec : specs) {
107 validMissions.add(spec.getMissionId());
108 }
109
110 for (HubMissionCreator curr : creators) {
111 if (!curr.wasAutoAdded()) continue;
112
113 if (!validMissions.contains(curr.getSpecId())) {
114 curr.setActive(false);
115 //System.out.println("blahsdf");
116 } else {
117 curr.setActive(true);
118 alreadyHaveCreatorsFor.add(curr.getSpecId());
119 }
120 }
121
122 for (PersonMissionSpec spec : specs) {
123 if (!alreadyHaveCreatorsFor.contains(spec.getMissionId())) {
125 curr.setWasAutoAdded(true);
126 curr.setActive(true);
127 creators.add(curr);
128 }
129 }
130 }
131
132 protected Object readResolve() {
133 if (recentlyAcceptedTimeout == null) {
134 recentlyAcceptedTimeout = new TimeoutTracker<String>();
135 }
136 //updateMissionCreatorsFromSpecs();
137 return this;
138 }
139
140
141 public boolean callEvent(String ruleId, InteractionDialogAPI dialog,
142 List<Token> params, Map<String, MemoryAPI> memoryMap) {
143 String action = params.get(0).getString(memoryMap);
144 if (action.equals("setMHOptionText")) {
145 person.getMemoryWithoutUpdate().set("$mh_openOptionText", getOpenOptionText(), 0);
146 } else if (action.equals("prepare")) {
147 prepare(dialog, memoryMap);
148 } else if (action.equals("listMissions")) {
149 listMissions(dialog, memoryMap, true);
150 } else if (action.equals("returnToList")) {
151 listMissions(dialog, memoryMap, false);
152 } else if (action.equals("doCleanup")) {
153 doCleanup(dialog, memoryMap);
154 } else if (action.equals("accept")) {
155 String missionId = params.get(1).getString(memoryMap);
156 accept(dialog, memoryMap, missionId);
157 } else {
158 throw new RuntimeException("Unhandled action [" + action + "] in " + getClass().getSimpleName() +
159 " for rule [" + ruleId + "], params:[" + params + "]");
160 }
161 return true;
162 }
163
164
165 public void accept(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap, String missionId) {
166 for (HubMission curr : getOfferedMissions()) {
167 if (curr.getMissionId().equals(missionId)) {
168 curr.accept(dialog, memoryMap);
169 getOfferedMissions().remove(curr);
170
171 float dur = curr.getCreator().getAcceptedTimeoutDuration();
172 timeout.add(curr.getCreator().getSpecId(), dur);
173 recentlyAcceptedTimeout.add(curr.getCreator().getSpecId(), getUpdateInterval());
174 break;
175 }
176 }
177 }
178
179 public void prepare(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
181 updateOfferedMissions(dialog, memoryMap);
182 //offered.clear();
183 updateCountAndFirstInlineBlurb(dialog, memoryMap);
184 }
185
186 public void doCleanup(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
187 //PersonAPI person = dialog.getInteractionTarget().getActivePerson();
188 for (HubMission curr : getOfferedMissions()) {
189 curr.abort();
190 }
191 offered = new ArrayList<HubMission>();
192 }
193
194 protected void updateCountAndFirstInlineBlurb(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
195 MemoryAPI pMem = dialog.getInteractionTarget().getActivePerson().getMemoryWithoutUpdate();
196
197 int count = 0;
198 String firstInlineBlurb = null;
199 for (HubMission curr : getOfferedMissions()) {
200 count++;
201 if (firstInlineBlurb == null) firstInlineBlurb = curr.getBlurbText();
202 }
203
204 pMem.set("$mh_firstInlineBlurb", firstInlineBlurb, 0);
205 pMem.set("$mh_count", count, 0);
206 }
207
208 public void listMissions(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap, boolean withBlurbs) {
209 if (dialog != null && dialog.getVisualPanel() != null) {
210 dialog.getVisualPanel().removeMapMarkerFromPersonInfo();
211 }
212 MemoryAPI pMem = dialog.getInteractionTarget().getActivePerson().getMemoryWithoutUpdate();
213 updateCountAndFirstInlineBlurb(dialog, memoryMap);
214
215 if (pMem.getFloat("$mh_count") <= 0) {
216 FireAll.fire(null, dialog, memoryMap, "PopulateOptions");
217 return;
218 }
219
220 dialog.getOptionPanel().clearOptions();
221
222 String blurb = "\"";
223 boolean hasCommonBlurb = false;
224 int count = 0;
225 int skipped = 0;
226 for (HubMission curr : getOfferedMissions()) {
227 if (curr.getBlurbText() != null) {
228 blurb += curr.getBlurbText() + " ";
229 FireBest.fire(null, dialog, memoryMap, curr.getTriggerPrefix() + "_option true");
230 hasCommonBlurb = true;
231 } else {
232 skipped++;
233 }
234 count++;
235 //if (count >= MAX_TO_SHOW) break;
236 }
237
238 count -= skipped;
239
240 // so that the blurbs and the options are in the same order
241 for (HubMission curr : getOfferedMissions()) {
242 //if (count >= MAX_TO_SHOW) break;
243 count++;
244
245 if (curr.getBlurbText() == null) {
246 if (withBlurbs) {
247 if (!FireBest.fire(null, dialog, memoryMap, curr.getTriggerPrefix() + "_blurb true")) {
248 dialog.getTextPanel().addPara("No blurb found for " + curr.getTriggerPrefix(), Misc.getNegativeHighlightColor());
249 }
250 }
251 if (!FireBest.fire(null, dialog, memoryMap, curr.getTriggerPrefix() + "_option true")) {
252 dialog.getTextPanel().addPara("No option found for " + curr.getTriggerPrefix(), Misc.getNegativeHighlightColor());
253 }
254 }
255 }
256
257 FireBest.fire(null, dialog, memoryMap, "AddMHCloseOption true");
258
259 if (withBlurbs && hasCommonBlurb && !pMem.getBoolean("$mh_doNotPrintBlurbs")) {
260 blurb = blurb.trim();
261 blurb += "\"";
262 dialog.getTextPanel().addPara(blurb);
263 }
264
265 if (withBlurbs) {
266 FireBest.fire(null, dialog, memoryMap, "MHPostMissionListText");
267 }
268
269 if (Global.getSettings().isDevMode()) {
271 }
272 }
273
274 public String getOpenOptionText() {
275 //Inquire about available jobs
276 return "\"Do you have any work for me?\"";
277 }
278
279
280 protected float getUpdateInterval() {
281 return UPDATE_INTERVAL;
282 }
283
284 public List<HubMission> getOfferedMissions() {
285 return offered;
286 }
287
288
289 protected transient Random missionGenRandom = new Random();
290 protected long seed = 0;
291 protected long lastUpdated = Long.MIN_VALUE;
292 protected long lastUpdatedSeeds = 0;
293 protected float daysSinceLastUpdate = 0f;
294 public void updateOfferedMissions(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
296
297 float daysElapsed = Global.getSector().getClock().getElapsedDaysSince(lastUpdated);
298 if (lastUpdated <= Long.MIN_VALUE) daysElapsed = getUpdateInterval();
299 daysSinceLastUpdate += daysElapsed;
300 lastUpdated = Global.getSector().getClock().getTimestamp();
301
302 timeout.advance(daysElapsed);
303
306 //seed = Misc.genRandomSeed();
307 seed = BarEventManager.getInstance().getSeed(null, person, "" + lastUpdatedSeeds);
308
310 for (HubMissionCreator creator : creators) {
311 //creator.updateSeed();
312 creator.setSeed(BarEventManager.getInstance().getSeed(null, person,
313 creator.getSpecId() + "" + lastUpdatedSeeds));
314 }
315 lastUpdatedSeeds = Global.getSector().getClock().getTimestamp();
316 }
317
318 missionGenRandom = new Random(seed);
319
320 //missionGenRandom = Misc.random;
321
322
323 WeightedRandomPicker<HubMissionCreator> picker = new WeightedRandomPicker<HubMissionCreator>(missionGenRandom);
324 WeightedRandomPicker<HubMissionCreator> priority = new WeightedRandomPicker<HubMissionCreator>(missionGenRandom);
325 float rel = person.getRelToPlayer().getRel();
326
327 Set<String> completed = new LinkedHashSet<String>();
328 for (HubMissionCreator creator : creators) {
329 if (creator.getNumCompleted() > 0) completed.add(creator.getSpecId());
330 }
331
332 for (HubMissionCreator creator : creators) {
333// if (creator.getSpecId().equals("mcb")) {
334// System.out.println("fweefwew");
335// }
336 // keep timeout missions so that after the player accepts a mission
337 // the re-generated set using missionGenRandom remains the same (minus the accepted mission)
338 if (timeout.contains(creator.getSpecId()) &&
339 !recentlyAcceptedTimeout.contains(creator.getSpecId())) continue;
340
341 if (!creator.isActive()) continue;
342
343 if (creator.getSpec().hasTag(Tags.MISSION_NON_REPEATABLE) &&
344 creator.getNumCompleted() > 0) {
345 continue;
346 }
347
348 if (!DebugFlags.ALLOW_ALL_CONTACT_MISSIONS && !creator.getSpec().completedMissionsMatch(completed)) {
349 continue;
350 }
351
352
354 if (!creator.matchesRep(rel)) continue;
356 if (person.getImportance().ordinal() < creator.getSpec().getImportance().ordinal()) continue;
357 }
358 }
359
360 float w = creator.getFrequencyWeight();
361 if (creator.isPriority()) {
362 priority.add(creator, w);
363 } else {
364 picker.add(creator, w);
365 }
366 }
367
368
369 int bonusMissions = 0;
370 if (person.getMemoryWithoutUpdate().contains(NUM_BONUS_MISSIONS)) {
371 float bonus = person.getMemoryWithoutUpdate().getFloat(NUM_BONUS_MISSIONS);
372 float rem = bonus - (int) bonus;
373 bonusMissions = (int) bonus;
374 if (missionGenRandom.nextFloat() < rem) {
375 bonusMissions++;
376 }
377 }
378
379 int num = MIN_TO_SHOW + missionGenRandom.nextInt(MAX_TO_SHOW - MIN_TO_SHOW + 1) + bonusMissions;
381 if (num < 1 && MIN_TO_SHOW > 0) num = 1;
382 if (DebugFlags.BAR_DEBUG) num = 8;
383 //num = 5;
384
385 if (person.getMemoryWithoutUpdate().getBoolean(CONTACT_SUSPENDED)) {
386 num = 0;
387 }
388
389 offered = new ArrayList<HubMission>();
390
391// resetMissionAngle(person, person.getMarket());
392// getMissionAngle(person, person.getMarket(), missionGenRandom);
393
394 ImportantPeopleAPI ip = Global.getSector().getImportantPeople();
395 ip.resetExcludeFromGetPerson();
396 // existing contacts don't get picked as targets for missions
397 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) {
398 ip.excludeFromGetPerson(((ContactIntel)intel).getPerson());
399 }
400
401 while ((!picker.isEmpty() || !priority.isEmpty()) && offered.size() < num) {
402 HubMissionCreator creator = priority.pickAndRemove();
403 if (creator == null) {
404 creator = picker.pickAndRemove();
405 }
406
407 // so that if a player accepted a mission, overall set will be the same, minus the accepted missions
408 if (recentlyAcceptedTimeout.contains(creator.getSpecId())) {
409 num--;
410 continue;
411 }
412
413 creator.updateRandom();
414 HubMission mission = creator.createHubMission(this);
415 if (mission != null) {
416 mission.setHub(this);
417 mission.setCreator(creator);
418 mission.setGenRandom(creator.getGenRandom());
419 //mission.setGenRandom(Misc.random);
420 mission.createAndAbortIfFailed(getPerson().getMarket(), false);
421 //mission.setGenRandom(null);
422 }
423 if (mission == null || mission.isMissionCreationAborted()) continue;
424 offered.add(mission);
425 mission.updateInteractionData(dialog, memoryMap);
426
427 float dur = creator.getWasShownTimeoutDuration();
428 timeout.add(creator.getSpecId(), dur);
429
430 //getCreatedMissionsList(person, person.getMarket()).add((BaseHubMission) mission);
431 }
432
433 ip.resetExcludeFromGetPerson();
434
435 //clearCreatedMissionsList(person, person.getMarket());
436 }
437
438 public PersonAPI getPerson() {
439 return person;
440 }
441 public void setPerson(PersonAPI person) {
442 this.person = person;
443 }
444
445
446 public static List<PersonMissionSpec> getMissionsForPerson(PersonAPI person) {
447 List<PersonMissionSpec> result = new ArrayList<PersonMissionSpec>();
448
449 Set<String> personTags = new HashSet<String>(person.getTags());
450 personTags.add(person.getFaction().getId());
451
452 for (PersonMissionSpec spec : Global.getSettings().getAllMissionSpecs()) {
453 if (spec.getPersonId() != null && !spec.getPersonId().equals(person.getId())) continue;
454 if (!spec.tagsMatch(personTags)) continue;
455
456 if (spec.getPersonId() == null && spec.getTagsAll().isEmpty() &&
457 spec.getTagsAny().isEmpty() && spec.getTagsNotAny().isEmpty()) continue;
458
459 result.add(spec);
460 }
461 return result;
462 }
463
464
465 public static String MISSION_ANGLE_KEY = "$core_missionAngle";
466
467// public static void resetMissionAngle(PersonAPI person, MarketAPI market) {
468// MemoryAPI mem;
469// if (market != null) {
470// mem = market.getMemoryWithoutUpdate();
471// } else if (person!= null) {
472// mem = person.getMemoryWithoutUpdate();
473// } else {
474// return;
475// }
476// mem.unset(MISSION_ANGLE_KEY);
477// }
478
479 //public static float getMissionAngle(PersonAPI person, MarketAPI market, Random random) {
480 public static float getMissionAngle(PersonAPI person, MarketAPI market) {
481 MemoryAPI mem;
482 if (market != null) {
483 mem = market.getMemoryWithoutUpdate();
484 } else if (person!= null) {
485 mem = person.getMemoryWithoutUpdate();
486 } else {
487 Random random = Misc.getRandom(BarEventManager.getInstance().getSeed(null, null, null), 11);
488 return random.nextFloat() * 360f;
489 }
490
491 float angle;
492 if (mem.contains(MISSION_ANGLE_KEY)) {
493 angle = mem.getFloat(MISSION_ANGLE_KEY);
494 } else {
495 StarSystemUnexploredReq unexplored = new StarSystemUnexploredReq();
496 Vector2f loc = Global.getSector().getPlayerFleet().getLocationInHyperspace();
497
498 SectorEntityToken entity = null;
499 if (market != null) entity = market.getPrimaryEntity();
500 if (entity == null && person != null && person.getMarket() != null) {
501 entity = person.getMarket().getPrimaryEntity();
502 }
503 Random random = Misc.getRandom(BarEventManager.getInstance().getSeed(entity, person, null), 11);
504 WeightedRandomPicker<Float> picker = new WeightedRandomPicker<Float>(random);
505 for (StarSystemAPI system : Global.getSector().getStarSystems()) {
506 float dir = Misc.getAngleInDegrees(loc, system.getLocation());
507 if (unexplored.systemMatchesRequirement(system)) {
508 picker.add(dir, 1f);
509 } else {
510 float days = system.getDaysSinceLastPlayerVisit();
511 float weight = days / 1000f;
512 if (weight < 0.01f) weight = 0.01f;
513 if (weight > 1f) weight = 1f;
514 picker.add(dir, weight * 0.01f);
515 }
516 }
517
518 angle = picker.pick();
519
520 mem.set(MISSION_ANGLE_KEY, angle, 0f);
521 }
522 return angle;
523 }
524
525
526// public static String CREATED_MISSIONS_KEY = "$core_createdMissions";
527// @SuppressWarnings("unchecked")
528// public static List<BaseHubMission> getCreatedMissionsList(PersonAPI person, MarketAPI market) {
529// MemoryAPI mem;
530// if (market != null) {
531// mem = market.getMemoryWithoutUpdate();
532// } else if (person!= null) {
533// mem = person.getMemoryWithoutUpdate();
534// } else {
535// return new ArrayList<BaseHubMission>();
536// }
537// List<BaseHubMission> list = (List<BaseHubMission>) mem.get(CREATED_MISSIONS_KEY);
538// if (list == null) {
539// list = new ArrayList<BaseHubMission>();
540// mem.set(CREATED_MISSIONS_KEY, list);
541// }
542// return list;
543// }
544//
545// public static void clearCreatedMissionsList(PersonAPI person, MarketAPI market) {
546// MemoryAPI mem;
547// if (market != null) {
548// mem = market.getMemoryWithoutUpdate();
549// } else if (person!= null) {
550// mem = person.getMemoryWithoutUpdate();
551// } else {
552// return;
553// }
554// mem.unset(CREATED_MISSIONS_KEY);
555// }
556}
557
558
559
560
561
562
563
564
565
static SettingsAPI getSettings()
Definition Global.java:51
static SectorAPI getSector()
Definition Global.java:59
static void addOptions(InteractionDialogAPI dialog)
void prepare(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
void updateCountAndFirstInlineBlurb(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
static float getMissionAngle(PersonAPI person, MarketAPI market)
void updateOfferedMissions(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
void accept(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap, String missionId)
static List< PersonMissionSpec > getMissionsForPerson(PersonAPI person)
boolean callEvent(String ruleId, InteractionDialogAPI dialog, List< Token > params, Map< String, MemoryAPI > memoryMap)
void doCleanup(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
void listMissions(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap, boolean withBlurbs)
List< PersonMissionSpec > getAllMissionSpecs()
void updateInteractionData(InteractionDialogAPI dialog, Map< String, MemoryAPI > memoryMap)
void createAndAbortIfFailed(MarketAPI market, boolean barEvent)