001package com.fs.starfarer.api.impl.campaign.intel.contacts; 002 003import java.awt.Color; 004import java.util.Random; 005import java.util.Set; 006 007import org.lwjgl.input.Keyboard; 008 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.campaign.FactionAPI; 011import com.fs.starfarer.api.campaign.InteractionDialogAPI; 012import com.fs.starfarer.api.campaign.PersonImportance; 013import com.fs.starfarer.api.campaign.SectorEntityToken; 014import com.fs.starfarer.api.campaign.StoryPointActionDelegate; 015import com.fs.starfarer.api.campaign.TextPanelAPI; 016import com.fs.starfarer.api.campaign.comm.IntelInfoPlugin; 017import com.fs.starfarer.api.campaign.econ.MarketAPI; 018import com.fs.starfarer.api.campaign.rules.MemoryAPI; 019import com.fs.starfarer.api.characters.PersonAPI; 020import com.fs.starfarer.api.impl.campaign.DebugFlags; 021import com.fs.starfarer.api.impl.campaign.ids.Conditions; 022import com.fs.starfarer.api.impl.campaign.ids.Factions; 023import com.fs.starfarer.api.impl.campaign.ids.Sounds; 024import com.fs.starfarer.api.impl.campaign.ids.Stats; 025import com.fs.starfarer.api.impl.campaign.ids.Tags; 026import com.fs.starfarer.api.impl.campaign.intel.BaseIntelPlugin; 027import com.fs.starfarer.api.impl.campaign.missions.hub.BaseMissionHub; 028import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.BaseOptionStoryPointActionDelegate; 029import com.fs.starfarer.api.impl.campaign.rulecmd.SetStoryOption.StoryOptionParams; 030import com.fs.starfarer.api.ui.ButtonAPI; 031import com.fs.starfarer.api.ui.IntelUIAPI; 032import com.fs.starfarer.api.ui.LabelAPI; 033import com.fs.starfarer.api.ui.SectorMapAPI; 034import com.fs.starfarer.api.ui.TooltipMakerAPI; 035import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator; 036import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipLocation; 037import com.fs.starfarer.api.ui.UIComponentAPI; 038import com.fs.starfarer.api.util.IntervalUtil; 039import com.fs.starfarer.api.util.Misc; 040import com.fs.starfarer.api.util.WeightedRandomPicker; 041 042public class ContactIntel extends BaseIntelPlugin { 043 044 public static enum ContactState { 045 POTENTIAL, 046 NON_PRIORITY, 047 PRIORITY, 048 SUSPENDED, 049 LOST_CONTACT_DECIV, 050 LOST_CONTACT, 051 } 052 053 054 public static String NO_CONTACTS_ON_MARKET = "$core_noContactsOnMarket"; 055 056 public static float MAX_NUM_MISSIONS_BONUS = Global.getSettings().getFloat("priorityContactMaxNumMissionsBonus"); 057 public static float MAX_MISSION_QUALITY_BONUS = Global.getSettings().getFloat("priorityContactMaxMissionQualityBonus"); 058 public static float DEFAULT_POTENTIAL_CONTACT_PROB = Global.getSettings().getFloat("defaultPotentialContactProbability"); 059 public static float ADD_PER_FAIL = Global.getSettings().getFloat("potentialContactProbabilityAddPerFail"); 060 public static float DAYS_AT_PRIORITY_FOR_FULL_EFFECT = 30; 061 062 public static String UPDATE_RELOCATED_CONTACT = "update_relocated_contact"; 063 public static String UPDATE_LOST_CONTACT = "update_lost_contact"; 064 065 public static String BUTTON_DEVELOP = "button_develop"; 066 public static String BUTTON_SUSPEND = "button_suspend"; 067 public static String BUTTON_DELETE = "button_delete"; 068 069 public static String BUTTON_PRIORITY = "button_priority"; 070 071 public static float DURATION = 30f; 072 public static boolean POTENTIAL_EXPIRES = false; 073 074 protected ContactState state = ContactState.POTENTIAL; 075 protected PersonAPI person; 076 protected MarketAPI market; 077 protected FactionAPI marketFaction; 078 //protected CampaignFleetAPI fleet; 079 080 protected Boolean wasAddedToCommDirectory = null; 081 protected Boolean wasAddedToMarket = null; 082 083 protected boolean hadMissionHub; 084 protected boolean marketWasDeciv; 085 086 protected IntervalUtil tracker = new IntervalUtil(0.75f, 1.25f); 087 protected int numPriorityInARow = 0; 088 089 public ContactIntel(PersonAPI person, MarketAPI market) { 090 this.person = person; 091 this.market = market; 092 marketFaction = market.getFaction(); 093 //person.setMarket(market); // if set here, it can get unset in BaseHubMission.endSuccess()->abort() 094 marketWasDeciv = market.hasCondition(Conditions.DECIVILIZED); 095 if (!Global.getSector().getScripts().contains(this)) { 096 Global.getSector().addScript(this); 097 } 098 099 //setImportant(true); 100 } 101 102// public ContactIntel(PersonAPI person, CampaignFleetAPI fleet) { 103// this.person = person; 104// this.fleet = fleet; 105// if (!Global.getSector().getScripts().contains(this)) { 106// Global.getSector().addScript(this); 107// } 108// } 109 110 protected void ensureHasMissionHub() { 111 hadMissionHub = BaseMissionHub.get(person) != null; 112 if (BaseMissionHub.get(person) == null) { 113 BaseMissionHub.set(person, new BaseMissionHub(person)); 114 } 115 } 116 protected void ensureIsInCommDirectory() { 117 if (market == null) return; 118 if (market.getCommDirectory() == null) return; 119 if (market.getCommDirectory().getEntryForPerson(person) != null) return; 120 121 market.getCommDirectory().addPerson(person); 122 wasAddedToCommDirectory = true; 123 } 124 125 public void advanceImpl(float amount) { 126 float days = Global.getSector().getClock().convertToDays(amount); 127 tracker.advance(days); 128 if (tracker.intervalElapsed()) { 129 doPeriodicCheck(); 130 } 131 } 132 133 public void notifyPlayerAboutToOpenIntelScreen() { 134 doPeriodicCheck(); 135 } 136 137 protected MarketAPI findMarketToRelocateTo() { 138 if (market == null) return null; 139 140 if (person.getImportance() == PersonImportance.VERY_LOW) return null; 141 Random random = new Random(Misc.getSalvageSeed(market.getPrimaryEntity())); 142 WeightedRandomPicker<MarketAPI> picker = new WeightedRandomPicker<MarketAPI>(random); 143 for (MarketAPI curr : Global.getSector().getEconomy().getMarketsInGroup(market.getEconGroup())) { 144 if (curr == market) continue; 145 if (!curr.getFactionId().equals(market.getFactionId())) continue; 146 if (curr.hasCondition(Conditions.DECIVILIZED)) continue; 147 picker.add(curr, market.getSize()); 148 } 149 return picker.pick(); 150 } 151 152 protected void unsetFlags() { 153 MemoryAPI memory = person.getMemoryWithoutUpdate(); 154 memory.unset(BaseMissionHub.NUM_BONUS_MISSIONS); 155 memory.unset(BaseMissionHub.MISSION_QUALITY_BONUS); 156 memory.unset(BaseMissionHub.CONTACT_SUSPENDED); 157 } 158 159 public void loseContact(InteractionDialogAPI dialog) { 160 //endAfterDelay(); 161 state = ContactState.LOST_CONTACT; 162 if (dialog != null) { 163 sendUpdate(UPDATE_LOST_CONTACT, dialog.getTextPanel()); 164 } else { 165 sendUpdateIfPlayerHasIntel(UPDATE_LOST_CONTACT, false); 166 } 167 endImmediately(); 168 } 169 170 public void doPeriodicCheck() { 171 if (isEnded() || isEnding()) return; 172 173 174 MemoryAPI memory = person.getMemoryWithoutUpdate(); 175 unsetFlags(); 176 177 if (state != ContactState.LOST_CONTACT_DECIV && state != ContactState.LOST_CONTACT && market != null) { 178 if (!marketWasDeciv && (market.hasCondition(Conditions.DECIVILIZED) || !market.isInEconomy())) { 179 MarketAPI other = findMarketToRelocateTo(); 180 if (other == null) { 181 endAfterDelay(); 182 state = ContactState.LOST_CONTACT_DECIV; 183 sendUpdateIfPlayerHasIntel(UPDATE_LOST_CONTACT, false); 184 } else { 185 relocateToMarket(other, true); 186 } 187 return; 188 } 189 } 190 if (state == ContactState.LOST_CONTACT) return; 191 if (state == ContactState.POTENTIAL) return; 192 if (state == ContactState.SUSPENDED) { 193 memory.set(BaseMissionHub.CONTACT_SUSPENDED, true); 194 return; 195 } 196 197 if (state == ContactState.PRIORITY) { 198 numPriorityInARow++; 199 } else { 200 numPriorityInARow--; 201 } 202 if (numPriorityInARow < 0) { 203 numPriorityInARow = 0; 204 } else if (numPriorityInARow > DAYS_AT_PRIORITY_FOR_FULL_EFFECT) { 205 numPriorityInARow = (int) DAYS_AT_PRIORITY_FOR_FULL_EFFECT; 206 } 207 208 209 float bonusMissions = getPriorityMult() * MAX_NUM_MISSIONS_BONUS; 210 if (bonusMissions > 0) { 211 memory.set(BaseMissionHub.NUM_BONUS_MISSIONS, bonusMissions); 212 } 213 214 float bonusQuality = getPriorityMult() * MAX_MISSION_QUALITY_BONUS; 215 if (bonusQuality > 0) { 216 memory.set(BaseMissionHub.MISSION_QUALITY_BONUS, bonusMissions); 217 } 218 } 219 220 public void relocateToMarket(MarketAPI other, boolean withIntelUpdate) { 221 if (wasAddedToCommDirectory != null && wasAddedToCommDirectory && market != null && market.getCommDirectory() != null) { 222 market.getCommDirectory().removePerson(person); 223 wasAddedToCommDirectory = null; 224 } 225 market = other; 226 person.setMarket(other); 227 marketWasDeciv = other.hasCondition(Conditions.DECIVILIZED); 228 ensureIsInCommDirectory(); 229 ensureIsAddedToMarket(); 230 person.setImportance(person.getImportance().prev()); 231 if (withIntelUpdate) { 232 sendUpdateIfPlayerHasIntel(UPDATE_RELOCATED_CONTACT, false); 233 } 234 } 235 236 237 public float getPriorityMult() { 238 float priority = getPriorityContacts(); 239 if (priority < 1f) priority = 1f; 240 return ((float) numPriorityInARow / (float) DAYS_AT_PRIORITY_FOR_FULL_EFFECT) / priority; 241 } 242 243 244 @Override 245 public void reportPlayerClickedOn() { 246 super.reportPlayerClickedOn(); 247 } 248 249 250 @Override 251 protected void notifyEnding() { 252 super.notifyEnding(); 253 if (wasAddedToCommDirectory != null && wasAddedToCommDirectory && market != null && market.getCommDirectory() != null) { 254 market.getCommDirectory().removePerson(person); 255 wasAddedToCommDirectory = null; 256 } 257 if (wasAddedToMarket != null && wasAddedToMarket) { 258 market.removePerson(person); 259 } 260 261 if (!hadMissionHub) { 262 BaseMissionHub.set(person, null); 263 } 264 265 unsetFlags(); 266 } 267 268 @Override 269 public void reportRemovedIntel() { 270 super.reportRemovedIntel(); 271 Global.getSector().removeScript(this); 272 } 273 274 public boolean shouldRemoveIntel() { 275 if (isEnded()) return true; 276 if (state != ContactState.POTENTIAL) return false; 277 float days = getDaysSincePlayerVisible(); 278 if (days >= DURATION && POTENTIAL_EXPIRES) { 279 ended = true; 280 return true; 281 } 282 return false; 283 } 284 285 public String getName() { 286 if (state == ContactState.LOST_CONTACT_DECIV) { 287 return "Lost Contact: " + person.getNameString(); 288 } else if (state == ContactState.LOST_CONTACT) { 289 return "Lost Contact: " + person.getNameString(); 290 } else if (state == ContactState.POTENTIAL) { 291 return "Potential Contact: " + person.getNameString(); 292 } else if (state == ContactState.SUSPENDED) { 293 return "Suspended Contact: " + person.getNameString(); 294 } else if (state == ContactState.PRIORITY) { 295 return "Priority Contact: " + person.getNameString(); 296 } 297 return "Contact: " + person.getNameString(); 298 } 299 300 @Override 301 public String getSortString() { 302 if (state == ContactState.POTENTIAL) { 303 return "Contact2"; 304 } else if (state == ContactState.SUSPENDED) { 305 return "Contact3"; 306 } else if (state == ContactState.PRIORITY) { 307 return "Contact0"; 308 } 309 return "Contact1"; 310 } 311 312 public String getSmallDescriptionTitle() { 313 return getName(); 314 } 315 316 @Override 317 public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) { 318 Color c = getTitleColor(mode); 319 info.addPara(getName(), c, 0f); 320 addBulletPoints(info, mode); 321 } 322 323 protected void addTypePara(TooltipMakerAPI info, Color tc, float pad) { 324 Color h = Misc.getHighlightColor(); 325 String [] tags = person.getSortedContactTagStrings().toArray(new String [0]); 326 if (tags.length <= 0) return; 327 String str = "Type: "; 328 for (String tag : tags) { 329 str += tag + ", "; 330 } 331 if (tags.length > 0) { 332 str = str.substring(0, str.length() - 2); 333 } 334 info.addPara(str, pad, tc, h, tags); 335 } 336 337 protected void addFactionPara(TooltipMakerAPI info, Color tc, float pad) { 338 //String faction = Misc.ucFirst(person.getFaction().getDisplayName()); 339 String faction = person.getFaction().getDisplayName(); 340 String str = "Faction: " + faction; 341 342 info.addPara(str, pad, tc, person.getFaction().getBaseUIColor(), faction); 343 } 344 345 protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) { 346 Color h = Misc.getHighlightColor(); 347 Color g = Misc.getGrayColor(); 348 Color tc = getBulletColorForMode(mode); 349 350 float pad = 3f; 351 float opad = 10f; 352 353 float initPad = pad; 354 if (mode == ListInfoMode.IN_DESC) initPad = opad; 355 356 bullet(info); 357 358 if (getListInfoParam() == UPDATE_RELOCATED_CONTACT) { 359 info.addPara("Relocated to " + market.getName(), tc, initPad); 360 initPad = 0f; 361 info.addPara("Importance reduced to: %s", initPad, tc, h, person.getImportance().getDisplayName()); 362 initPad = 0f; 363 unindent(info); 364 return; 365 } 366 if (state == ContactState.LOST_CONTACT_DECIV) { 367 if (mode != ListInfoMode.IN_DESC) { 368 info.addPara(market.getName() + " decivilized", tc, initPad); 369 initPad = 0f; 370 } 371 unindent(info); 372 return; 373 } 374 375 if (state == ContactState.LOST_CONTACT) { 376 unindent(info); 377 return; 378 } 379 380 addFactionPara(info, tc, initPad); 381 initPad = 0f; 382 383 addTypePara(info, tc, initPad); 384 initPad = 0f; 385 386 if (mode != ListInfoMode.IN_DESC) { 387 info.addPara("Importance: %s", initPad, tc, h, person.getImportance().getDisplayName()); 388 initPad = 0f; 389 390 if (state == ContactState.PRIORITY || state == ContactState.NON_PRIORITY || state == ContactState.SUSPENDED) { 391 long ts = BaseMissionHub.getLastOpenedTimestamp(person); 392 if (ts <= Long.MIN_VALUE) { 393 //info.addPara("Never visited.", opad); 394 } else { 395 info.addPara("Last visited: %s.", initPad, tc, h, Misc.getDetailedAgoString(ts)); 396 initPad = 0f; 397 } 398 } 399 } 400 401// info.addPara("Rank: %s", initPad, tc, h, person.getRank()); 402// initPad = 0f; 403 404// info.addPara("Post: %s", initPad, tc, h, person.getPost()); 405// initPad = 0f; 406 407 if (state == ContactState.POTENTIAL && POTENTIAL_EXPIRES) { 408 if (mode != ListInfoMode.IN_DESC) { 409 float days = DURATION - getDaysSincePlayerVisible(); 410 info.addPara("%s " + getDaysString(days) + " left to develop", 411 initPad, tc, h, getDays(days)); 412 initPad = 0f; 413 } 414 } 415 416 //info.addPara("Personality: %s", initPad, tc, h, pName); 417 unindent(info); 418 } 419 420 @Override 421 public void createSmallDescription(TooltipMakerAPI info, float width, float height) { 422 String pName = Misc.getPersonalityName(person); 423 424 Color h = Misc.getHighlightColor(); 425 Color g = Misc.getGrayColor(); 426 Color tc = Misc.getTextColor(); 427 float pad = 3f; 428 float opad = 10f; 429 430 //info.addImage(person.getPortraitSprite(), width, 128, opad); 431 432 FactionAPI faction = person.getFaction(); 433 info.addImages(width, 128, opad, opad, person.getPortraitSprite(), faction.getCrest()); 434 435 float relBarWidth = 128f * 2f + 10f; 436 float importanceBarWidth = relBarWidth; 437 438 float indent = 25; 439 info.addSpacer(0).getPosition().setXAlignOffset(indent); 440 441 //info.addRelationshipBar(person, relBarWidth, opad); 442 443 relBarWidth = (relBarWidth - 10f) / 2f; 444 info.addRelationshipBar(person, relBarWidth, opad); 445 float barHeight = info.getPrev().getPosition().getHeight(); 446 info.addRelationshipBar(person.getFaction(), relBarWidth, 0f); 447 UIComponentAPI prev = info.getPrev(); 448 prev.getPosition().setYAlignOffset(barHeight); 449 prev.getPosition().setXAlignOffset(relBarWidth + 10f); 450 info.addSpacer(0f); 451 info.getPrev().getPosition().setXAlignOffset(-(relBarWidth + 10f)); 452 453 info.addImportanceIndicator(person.getImportance(), importanceBarWidth, opad); 454 addImportanceTooltip(info); 455// faction = Global.getSector().getPlayerFaction(); 456// ButtonAPI button = info.addAreaCheckbox("Priority contact", BUTTON_PRIORITY, faction.getBaseUIColor(), 457// faction.getDarkUIColor(), faction.getBrightUIColor(), relBarWidth, 25f, opad); 458// button.setChecked(state == ContactState.PRIORITY); 459// faction = person.getFaction(); 460 info.addSpacer(0).getPosition().setXAlignOffset(-indent); 461 462 if (state == ContactState.NON_PRIORITY || state == ContactState.PRIORITY) { 463 //info.addSpacer(0).getPosition().setXAlignOffset(indent); 464 faction = Global.getSector().getPlayerFaction(); 465 ButtonAPI button = info.addAreaCheckbox("Priority contact", BUTTON_PRIORITY, faction.getBaseUIColor(), 466 faction.getDarkUIColor(), faction.getBrightUIColor(), width, 25f, opad); 467 button.setChecked(state == ContactState.PRIORITY); 468 addPriorityTooltip(info); 469 faction = person.getFaction(); 470 //info.addSpacer(0).getPosition().setXAlignOffset(-indent); 471 } 472 473 if (market != null && state == ContactState.LOST_CONTACT_DECIV) { 474 info.addPara(person.getNameString() + " was " + 475 person.getPostArticle() + " " + person.getPost().toLowerCase() + 476 " " + market.getOnOrAt() + " " + market.getName() + 477 ", a colony controlled by " + marketFaction.getDisplayNameWithArticle() + ".", 478 opad, marketFaction.getBaseUIColor(), 479 Misc.ucFirst(marketFaction.getDisplayNameWithArticleWithoutArticle())); 480 info.addPara("This colony has decivilized, and you've since lost contact with " + person.getHimOrHer() + ".", opad); 481 } else if (state == ContactState.LOST_CONTACT) { 482 info.addPara("You've lost this contact.", opad); 483 } else { 484 if (market != null) { 485 LabelAPI label = info.addPara(person.getNameString() + " is " + 486 person.getPostArticle() + " " + person.getPost().toLowerCase() + 487 " and can be found " + market.getOnOrAt() + " " + market.getName() + 488 ", a size %s colony controlled by " + market.getFaction().getDisplayNameWithArticle() + ".", 489 opad, market.getFaction().getBaseUIColor(), 490 "" + (int)market.getSize(), market.getFaction().getDisplayNameWithArticleWithoutArticle()); 491 label.setHighlightColors(h, market.getFaction().getBaseUIColor()); 492// LabelAPI label = info.addPara(Misc.ucFirst(person.getPost().toLowerCase()) + 493// ", found " + market.getOnOrAt() + " " + market.getName() + 494// ", a size %s colony controlled by " + market.getFaction().getDisplayNameWithArticle() + ".", 495// opad, market.getFaction().getBaseUIColor(), 496// "" + (int)market.getSize(), Misc.ucFirst(market.getFaction().getPersonNamePrefix())); 497// label.setHighlightColors(h, market.getFaction().getBaseUIColor()); 498 } 499 } 500 501 if (state == ContactState.POTENTIAL){ 502 info.addPara("If this contact is developed, " + person.getHeOrShe() + " will periodically " + 503 "have work for you. As the relationship improves, you may gain " + 504 "access to better opportunities.", opad); 505 } else if (state == ContactState.SUSPENDED) { 506 info.addPara("Your contact with " + person.getNameString() + " is currently suspended.", Misc.getNegativeHighlightColor(), opad); 507 } 508 509 addBulletPoints(info, ListInfoMode.IN_DESC); 510 511 512 if (state == ContactState.PRIORITY || state == ContactState.NON_PRIORITY || state == ContactState.SUSPENDED) { 513 514 515 long ts = BaseMissionHub.getLastOpenedTimestamp(person); 516 if (ts <= Long.MIN_VALUE) { 517 //info.addPara("Never visited.", opad); 518 } else { 519 info.addPara("Last visited: %s.", opad, h, Misc.getDetailedAgoString(ts)); 520 } 521 522// Color color = faction.getBaseUIColor(); 523// Color dark = faction.getDarkUIColor(); 524// info.addSectionHeading("Personality traits", color, dark, Alignment.MID, opad); 525// info.addPara("Suspicous Ambitious", opad, h, "Suspicous", "Ambitious"); 526// info.addPara("Ambitious: will offer missions that further their advancement more frequently. Refusing " + 527// "these missions will damage the relationship.", opad, h, "Ambitious:"); 528// info.addPara("Suspicous: reduced reputation gains.", opad, h, "Suspicous:"); 529 530 } 531 532 533 Color color = Misc.getStoryOptionColor(); 534 Color dark = Misc.getStoryDarkColor(); 535 536 TooltipCreator noDeleteTooltip = new TooltipCreator() { 537 public boolean isTooltipExpandable(Object tooltipParam) { 538 return false; 539 } 540 public float getTooltipWidth(Object tooltipParam) { 541 return TOOLTIP_WIDTH; 542 } 543 544 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { 545 tooltip.addPara("Can not delete or suspend contact at this time.", 0f); 546 } 547 }; 548 549 if (state == ContactState.POTENTIAL || state == ContactState.SUSPENDED){ 550 if (state == ContactState.POTENTIAL && POTENTIAL_EXPIRES) { 551 float days = DURATION - getDaysSincePlayerVisible(); 552 info.addPara("The opportunity to develop this contact will be available for %s more " + getDaysString(days) + ".", 553 opad, tc, h, getDays(days)); 554 } 555 556 557 int max = getMaxContacts(); 558 int curr = getCurrentContacts(); 559 560 info.addPara("Active contacts: %s %s %s", opad, 561 h, "" + curr, "/", "" + max); 562 563 ButtonAPI develop = null; 564 String developText = "Develop contact"; 565 if (state == ContactState.SUSPENDED) developText = "Resume contact"; 566 if (curr >= max) { 567// info.addPara("Developing contacts above the maximum will " + 568// "require a story point per additional contact.", opad, 569// Misc.getStoryOptionColor(), "story point"); 570 develop = addGenericButton(info, width, color, dark, developText, BUTTON_DEVELOP); 571 addDevelopTooltip(info); 572 } else { 573 develop = addGenericButton(info, width, developText, BUTTON_DEVELOP); 574 } 575 develop.setShortcut(Keyboard.KEY_T, true); 576 } else if (state == ContactState.NON_PRIORITY || state == ContactState.PRIORITY) { 577 ButtonAPI suspend = addGenericButton(info, width, color, dark, "Suspend contact", BUTTON_SUSPEND); 578 suspend.setShortcut(Keyboard.KEY_U, true); 579 if (Global.getSector().getIntel().isInShowMap()) { 580 suspend.setEnabled(false); 581 info.addTooltipToPrevious(noDeleteTooltip, TooltipLocation.LEFT); 582 } 583 } 584 585 info.addSpacer(-10f); 586 ButtonAPI delete = addGenericButton(info, width, "Delete contact", BUTTON_DELETE); 587 if (Global.getSector().getIntel().isInShowMap()) { 588 delete.setEnabled(false); 589 info.addTooltipToPrevious(noDeleteTooltip, TooltipLocation.LEFT); 590 } 591 delete.setShortcut(Keyboard.KEY_G, true); 592 } 593 594 595 @Override 596 public void buttonPressConfirmed(Object buttonId, IntelUIAPI ui) { 597 if (buttonId == BUTTON_DEVELOP) { 598 develop(ui); 599 Global.getSoundPlayer().playUISound("ui_contact_developed", 1f, 1f); 600 } else if (buttonId == BUTTON_PRIORITY) { 601 if (state == ContactState.NON_PRIORITY) { 602 state = ContactState.PRIORITY; 603 } else if (state == ContactState.PRIORITY) { 604 state = ContactState.NON_PRIORITY; 605 } 606 ui.updateUIForItem(this); 607 } else if (buttonId == BUTTON_DELETE) { 608 endImmediately(); 609 ui.recreateIntelUI(); 610 //Global.getSector().getCampaignUI().showCoreUITab(CoreUITabId.CARGO); 611 } 612 } 613 614 public void storyActionConfirmed(Object buttonId, IntelUIAPI ui) { 615 if (buttonId == BUTTON_DEVELOP) { 616 develop(ui); 617 } else if (buttonId == BUTTON_SUSPEND) { 618 state = ContactState.SUSPENDED; 619 person.getMemoryWithoutUpdate().set(BaseMissionHub.CONTACT_SUSPENDED, true); 620 ui.updateUIForItem(this); 621 } 622 } 623 624 public void ensureIsAddedToMarket() { 625 if (market == null) return; 626 627 boolean hadPerson = market.getPeopleCopy().contains(person) || market == person.getMarket(); 628 629 if (person.getMarket() != null) { 630 person.getMarket().removePerson(person); 631 } 632 633 if (market.getPeopleCopy().contains(person)) return; 634 market.addPerson(person); 635 if (!hadPerson) wasAddedToMarket = true; 636 } 637 public void develop(IntelUIAPI ui) { 638 ensureIsInCommDirectory(); 639 ensureHasMissionHub(); 640 ensureIsAddedToMarket(); 641 642 state = ContactState.NON_PRIORITY; 643 if (ui != null) { 644 ui.updateUIForItem(this); 645 } 646 person.getMemoryWithoutUpdate().unset(BaseMissionHub.CONTACT_SUSPENDED); 647 } 648 649 @Override 650 public void createConfirmationPrompt(Object buttonId, TooltipMakerAPI prompt) { 651 prompt.setParaInsigniaLarge(); 652 if (buttonId == BUTTON_DELETE) { 653 if (state == ContactState.POTENTIAL) { 654 prompt.addPara("Are you sure? Deleting a potential contact can not be undone.", 0f); 655 } else if (state == ContactState.SUSPENDED) { 656 prompt.addPara("Are you sure? Deleting a contact can not be undone.", 0f); 657 } else { 658 prompt.addPara("Are you sure? Deleting a contact can not be undone. " + 659 "To stop receiving missions from a contact, but not lose them permanently, you can \"suspend\" the contact instead.", 0f); 660 } 661 return; 662 } else if (buttonId == BUTTON_DEVELOP) { 663 prompt.addPara("Develop a relationship with this contact?", 0f); 664 return; 665 } 666 super.createConfirmationPrompt(buttonId, prompt); 667 return; 668 } 669 670 @Override 671 public boolean doesButtonHaveConfirmDialog(Object buttonId) { 672 if (buttonId == BUTTON_DELETE) return true; 673 if (buttonId == BUTTON_DEVELOP) return true; 674 return super.doesButtonHaveConfirmDialog(buttonId); 675 } 676 677 public StoryPointActionDelegate getButtonStoryPointActionDelegate(Object buttonId) { 678 if (buttonId == BUTTON_DEVELOP && developRequiresStoryPoint()) { 679 StoryOptionParams params = new StoryOptionParams(null, 1, "developContactOverMax", 680 Sounds.STORY_POINT_SPEND, 681 "Developed additional contact (" + person.getNameString() + ")"); 682 return new BaseOptionStoryPointActionDelegate(null, params) { 683 @Override 684 public void confirm() { 685 } 686 687 @Override 688 public String getTitle() { 689 return null; 690 } 691 @Override 692 public void createDescription(TooltipMakerAPI info) { 693 //info.setParaInsigniaLarge(); 694 super.createDescription(info); 695 } 696 }; 697 } 698 if (buttonId == BUTTON_SUSPEND) { 699 StoryOptionParams params = new StoryOptionParams(null, 1, "suspendContact", 700 Sounds.STORY_POINT_SPEND, 701 "Suspended contact (" + person.getNameString() + ")"); 702 return new BaseOptionStoryPointActionDelegate(null, params) { 703 @Override 704 public void confirm() { 705 } 706 707 @Override 708 public String getTitle() { 709 return null; 710 } 711 @Override 712 public void createDescription(TooltipMakerAPI info) { 713 info.setParaInsigniaLarge(); 714 info.addPara("If suspended, this contact will not offer you missions and will not " + 715 "count against the maximum number of contacts. You will be able to re-develop this " + 716 "contact at any time.", -10f); 717 info.addSpacer(20f); 718 super.createDescription(info); 719 } 720 }; 721 } 722 return null; 723 } 724 725 726 @Override 727 public String getIcon() { 728 return person.getPortraitSprite(); 729 } 730 731 @Override 732 public Set<String> getIntelTags(SectorMapAPI map) { 733 Set<String> tags = super.getIntelTags(map); 734 tags.add(Tags.INTEL_CONTACTS); 735 return tags; 736 } 737 738 @Override 739 public String getCommMessageSound() { 740 return super.getCommMessageSound(); 741 //return getSoundMajorPosting(); 742 } 743 744 @Override 745 public FactionAPI getFactionForUIColors() { 746 return person.getFaction(); 747 //return super.getFactionForUIColors(); 748 } 749 750 public PersonAPI getPerson() { 751 return person; 752 } 753 754 public void setPerson(PersonAPI person) { 755 this.person = person; 756 } 757 758 public ContactState getState() { 759 return state; 760 } 761 762 public void setState(ContactState state) { 763 this.state = state; 764 } 765 766 protected boolean developRequiresStoryPoint() { 767 return getCurrentContacts() >= getMaxContacts(); 768 } 769 770 @Override 771 public SectorEntityToken getMapLocation(SectorMapAPI map) { 772 if (market != null) { 773 return market.getPrimaryEntity(); 774 } 775 return null; 776 } 777 778 public static float TOOLTIP_WIDTH = 400f; 779 protected void addImportanceTooltip(TooltipMakerAPI info) { 780 info.addTooltipToPrevious(new TooltipCreator() { 781 public boolean isTooltipExpandable(Object tooltipParam) { 782 return false; 783 } 784 public float getTooltipWidth(Object tooltipParam) { 785 return TOOLTIP_WIDTH; 786 } 787 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { 788 tooltip.addPara("The importance of this person in whatever hierarchy or social structure they " + 789 "belong to. A more highly placed or connected individual will have more difficult - " + 790 "and more profitable - missions and opportunities to offer.", 0f); 791 } 792 }, TooltipLocation.LEFT); 793 } 794 protected void addPriorityTooltip(TooltipMakerAPI info) { 795 info.addTooltipToPrevious(new TooltipCreator() { 796 public boolean isTooltipExpandable(Object tooltipParam) { 797 return false; 798 } 799 public float getTooltipWidth(Object tooltipParam) { 800 return TOOLTIP_WIDTH; 801 } public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { 802 float opad = 10f; 803 tooltip.addPara("Whether cultivating a relationship with this contact is a priority for you. " + 804 "Priority contacts will have more missions available, and the missions will be of a " + 805 "higher quality.", 0f); 806 tooltip.addPara("The more priority contacts you have, the less the impact there is on each individual contact.", opad); 807 tooltip.addPara("It takes about a month for changes in priority status to take full effect.", opad); 808 } 809 }, TooltipLocation.LEFT); 810 } 811 protected void addDevelopTooltip(TooltipMakerAPI info) { 812 info.addTooltipToPrevious(new TooltipCreator() { 813 public boolean isTooltipExpandable(Object tooltipParam) { 814 return false; 815 } 816 public float getTooltipWidth(Object tooltipParam) { 817 return TOOLTIP_WIDTH; 818 } 819 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) { 820 tooltip.addPara("Developing contacts above the maximum " + 821 "requires a story point for each additional contact.", 0f, 822 Misc.getStoryOptionColor(), "story point"); 823 } 824 }, TooltipLocation.LEFT); 825 } 826 827 828 public static int getMaxContacts() { 829 int base = Global.getSettings().getInt("maxContacts"); 830 int max = (int) Global.getSector().getPlayerStats().getDynamic().getMod(Stats.NUM_MAX_CONTACTS_MOD).computeEffective(base); 831 return max; 832 } 833 834 public static int getCurrentContacts() { 835 int count = 0; 836 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) { 837 if (intel.isEnding() || intel.isEnded()) continue; 838 if (((ContactIntel)intel).getState() == ContactState.POTENTIAL) continue; 839 if (((ContactIntel)intel).getState() == ContactState.SUSPENDED) continue; 840 if (((ContactIntel)intel).getState() == ContactState.LOST_CONTACT_DECIV) continue; 841 if (((ContactIntel)intel).getState() == ContactState.LOST_CONTACT) continue; 842 count++; 843 } 844 return count; 845 } 846 847 public static int getPriorityContacts() { 848 int count = 0; 849 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) { 850 if (intel.isEnding() || intel.isEnded()) continue; 851 if (((ContactIntel)intel).getState() != ContactState.PRIORITY) continue; 852 count++; 853 } 854 return count; 855 } 856 857 public static boolean playerHasIntelItemForContact(PersonAPI person) { 858 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) { 859 if (((ContactIntel)intel).getPerson() == person) { 860 return true; 861 } 862 } 863 return false; 864 } 865 866 public static boolean playerHasContact(PersonAPI person, boolean includePotential) { 867 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) { 868 if (((ContactIntel)intel).getPerson() == person) { 869 ContactState state = ((ContactIntel)intel).getState(); 870 if (state == ContactState.POTENTIAL && !includePotential) { 871 continue; 872 } 873 if (state == ContactState.LOST_CONTACT || state == ContactState.LOST_CONTACT_DECIV) { 874 continue; 875 } 876 return true; 877 } 878 } 879 return false; 880 } 881 882 public static ContactIntel getContactIntel(PersonAPI person) { 883 for (IntelInfoPlugin intel : Global.getSector().getIntelManager().getIntel(ContactIntel.class)) { 884 if (((ContactIntel)intel).getPerson() == person) return (ContactIntel)intel; 885 } 886 return null; 887 } 888 889 public static void removeContact(PersonAPI person, InteractionDialogAPI dialog) { 890 if (person == null) return; 891 ContactIntel intel = getContactIntel(person); 892 if (intel != null) { 893 intel.loseContact(dialog); 894 } 895 } 896 897 public static void addPotentialContact(PersonAPI contact, MarketAPI market, TextPanelAPI text) { 898 addPotentialContact(DEFAULT_POTENTIAL_CONTACT_PROB, contact, market, text); 899 } 900 public static void addPotentialContact(float probability, PersonAPI contact, MarketAPI market, TextPanelAPI text) { 901 if (playerHasIntelItemForContact(contact)) return; 902 if (contact.getFaction().isPlayerFaction()) return; 903 if (market == null) return; 904 if (market != null && market.getMemoryWithoutUpdate().getBoolean(NO_CONTACTS_ON_MARKET)) return; 905 if (contact != null && contact.getFaction().getCustomBoolean(Factions.CUSTOM_NO_CONTACTS)) return; 906 907 Random random = new Random(getContactRandomSeed(contact)); 908 // if the player already has some existing relationship with the person, use it to 909 // modify the probability they'll be available as a potential contact 910 probability += contact.getRelToPlayer().getRel(); 911 912 913 String key = "$potentialContactRollFails"; 914 MemoryAPI mem = Global.getSector().getMemoryWithoutUpdate(); 915 float fails = mem.getInt(key); 916 probability += ADD_PER_FAIL * fails; 917 918 if (random.nextFloat() >= probability && !DebugFlags.ALWAYS_ADD_POTENTIAL_CONTACT) { 919 fails++; 920 mem.set(key, fails); 921 return; 922 } 923 924 mem.set(key, 0); 925 926 927 ContactIntel intel = new ContactIntel(contact, market); 928 Global.getSector().getIntelManager().addIntel(intel, false, text); 929 } 930 931 932 public static long getContactRandomSeed(PersonAPI person) { 933 String id = person.getId(); 934 if (id == null) id = Misc.genUID(); 935 long seed = Misc.seedUniquifier() ^ (person.getId().hashCode() * 17000); 936 Random r = new Random(seed); 937 for (int i = 0; i < 5; i++) { 938 r.nextLong(); 939 } 940 return r.nextLong(); 941 } 942} 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959