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