001package com.fs.starfarer.api.impl.campaign.missions.cb;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.LinkedHashSet;
006import java.util.List;
007import java.util.Map;
008import java.util.Set;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.BattleAPI;
012import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason;
013import com.fs.starfarer.api.campaign.CampaignFleetAPI;
014import com.fs.starfarer.api.campaign.InteractionDialogAPI;
015import com.fs.starfarer.api.campaign.PlanetAPI;
016import com.fs.starfarer.api.campaign.SectorEntityToken;
017import com.fs.starfarer.api.campaign.econ.MarketAPI;
018import com.fs.starfarer.api.campaign.listeners.FleetEventListener;
019import com.fs.starfarer.api.campaign.rules.MemKeys;
020import com.fs.starfarer.api.campaign.rules.MemoryAPI;
021import com.fs.starfarer.api.characters.PersonAPI;
022import com.fs.starfarer.api.impl.campaign.ids.Tags;
023import com.fs.starfarer.api.impl.campaign.missions.cb.CustomBountyCreator.CustomBountyData;
024import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithBarEvent;
025import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest;
026import com.fs.starfarer.api.ui.SectorMapAPI;
027import com.fs.starfarer.api.ui.TooltipMakerAPI;
028import com.fs.starfarer.api.util.Misc;
029import com.fs.starfarer.api.util.Misc.Token;
030import com.fs.starfarer.api.util.WeightedRandomPicker;
031
032public class BaseCustomBounty extends HubMissionWithBarEvent implements FleetEventListener {
033
034        public static int NUM_TO_TRACK_FOR_DIFFICULTY = 3;
035        
036        public static enum Stage {
037                BOUNTY,
038                COMPLETED,
039                FAILED,
040                FAILED_NO_PENALTY,
041        }
042        
043        public static enum DifficultyChoice {
044                LOW,
045                NORMAL,
046                HIGH,
047        }
048        
049        public static class AggregateBountyData {
050                public List<Integer> completedDifficulty = new ArrayList<Integer>();
051        }
052        
053        public String getAggregateDataKey() {
054                return "$" + getMissionId() + "_aggregateData";
055        }
056        public AggregateBountyData getAggregateData() {
057                //MemoryAPI memory = getPerson().getMemoryWithoutUpdate();
058                MemoryAPI memory = Global.getSector().getMemoryWithoutUpdate();
059                AggregateBountyData data = (AggregateBountyData) memory.get(getAggregateDataKey());
060                if (data == null) {
061                        data = new AggregateBountyData();
062                        memory.set(getAggregateDataKey(), data);
063                }
064                return data;
065        }
066        
067
068        //protected FactionAPI faction;
069        protected PersonAPI target;
070        protected CustomBountyCreator creator;
071        protected CustomBountyCreator creatorLow;
072        protected CustomBountyCreator creatorNormal;
073        protected CustomBountyCreator creatorHigh;
074        protected CustomBountyData data;
075        protected CustomBountyData dataLow;
076        protected CustomBountyData dataNormal;
077        protected CustomBountyData dataHigh;
078        
079        public List<CustomBountyCreator> getCreators() {
080                return new ArrayList<CustomBountyCreator>();
081        }
082        
083        protected int pickDifficulty(DifficultyChoice choice) {
084                //if (true) return 6;
085                if (difficultyOverride != null) return difficultyOverride;
086                
087                AggregateBountyData d = getAggregateData();
088                
089                float total = 0f;
090                float count = 0f;
091                for (Integer diff : d.completedDifficulty) {
092                        total += diff;
093                        count++;
094                }
095                
096                float difficulty = total;
097                if (count > 0) {
098                        difficulty /= Math.max(count, NUM_TO_TRACK_FOR_DIFFICULTY);
099                        difficulty = (int) difficulty;
100                }
101                
102                int min = CustomBountyCreator.MIN_DIFFICULTY;
103                int max = CustomBountyCreator.MAX_DIFFICULTY;
104                
105                switch (choice) {
106                case LOW:
107                        min = 0;
108                        max = max - 3;
109                        difficulty = Math.min(difficulty - 3f, (int)(difficulty / 2f));
110                        break;
111                case NORMAL:
112                        min = min + 1;
113                        max = max - 1;
114                        difficulty = difficulty + 1;
115                        break;
116                case HIGH:
117                        min = 4;
118                        difficulty = difficulty + 3;
119                        break;
120                }
121                
122                int result = (int)Math.round(difficulty);
123                //result += genRandom.nextInt(2);
124                                
125                if (result < min) {
126                        result = min;
127                }
128                if (result > max) {
129                        result = max;
130                }
131                return result;
132        }
133        
134        protected CustomBountyCreator pickCreator(int difficulty, DifficultyChoice choice) {
135                //if (true) return new CBRemnantPlus();
136                //if (true) return new CBMercUW();
137                if (creatorOverride != null) {
138                        try {
139                                return (CustomBountyCreator) creatorOverride.newInstance();
140                        } catch (Throwable t) {
141                                throw new RuntimeException(t);
142                        }
143                }
144                
145                WeightedRandomPicker<CustomBountyCreator> picker = new WeightedRandomPicker<CustomBountyCreator>(genRandom);
146                
147                float quality = getQuality();
148                float maxDiff = CustomBountyCreator.MAX_DIFFICULTY;
149                
150                for (CustomBountyCreator curr : getCreators()) {
151                        if (curr.getMinDifficulty() > difficulty) continue;
152                        if (curr.getMaxDifficulty() < difficulty) continue;
153                        
154                        
155                        if (choice == DifficultyChoice.HIGH) {
156                                int threshold = CBStats.getThresholdNotHigh(getClass());
157                                if (difficulty >= threshold) continue;
158                        }
159                        if (choice == DifficultyChoice.NORMAL) {
160                                int threshold = CBStats.getThresholdNotNormal(getClass());
161                                if (difficulty >= threshold) continue;
162                        }
163                        
164                        float probToSkip = (1.1f - quality) * (float) curr.getMinDifficulty() / maxDiff;
165                        if (rollProbability(probToSkip)) continue;
166                        
167                        picker.add(curr, curr.getFrequency(this, difficulty));
168                }
169                
170                return picker.pick();
171        }
172        
173        protected void createBarGiver(MarketAPI createdAt) {
174                
175        }
176        
177        protected transient Class creatorOverride;
178        protected transient Integer difficultyOverride;
179        public void setTestMode(Class c, int difficulty) {
180                genRandom = Misc.random;
181                //genRandom = new Random(3454364663L);
182                difficultyOverride = difficulty;
183                creatorOverride = c;
184        }
185        
186        
187        @Override
188        protected boolean create(MarketAPI createdAt, boolean barEvent) {
189                //genRandom = Misc.random;
190                
191//              setTestMode(CBPirate.class, 10);
192//              setTestMode(CBPather.class, 10);
193//              setTestMode(CBDeserter.class, 10);
194//              setTestMode(CBDerelict.class, 10);
195//              setTestMode(CBMerc.class, 10);
196//              setTestMode(CBRemnant.class, 10);
197//              setTestMode(CBRemnantPlus.class, 10);
198//              setTestMode(CBRemnantStation.class, 10);
199//              setTestMode(CBTrader.class, 10);
200//              setTestMode(CBPatrol.class, 10);
201//              setTestMode(CBMercUW.class, 10);
202//              setTestMode(CBEnemyStation.class, 10);
203                
204                if (barEvent) {
205                        createBarGiver(createdAt);
206                }
207                
208                PersonAPI person = getPerson();
209                if (person == null) return false;
210                
211                String id = getMissionId();
212                if (!setPersonMissionRef(person, "$" + id + "_ref")) {
213                        return false;
214                }
215
216                setStartingStage(Stage.BOUNTY);
217                setSuccessStage(Stage.COMPLETED);
218                setFailureStage(Stage.FAILED);
219                addNoPenaltyFailureStages(Stage.FAILED_NO_PENALTY);
220                //setNoAbandon();
221                
222                
223                connectWithMemoryFlag(Stage.BOUNTY, Stage.COMPLETED, person, "$" + id + "_completed");
224                connectWithMemoryFlag(Stage.BOUNTY, Stage.FAILED, person, "$" + id + "_failed");
225                
226                addTag(Tags.INTEL_BOUNTY);
227                
228                
229                int dLow = pickDifficulty(DifficultyChoice.LOW);
230                creatorLow = pickCreator(dLow, DifficultyChoice.LOW);
231                //creatorLow = new CBDerelict();
232                if (creatorLow != null) {
233                        dataLow = creatorLow.createBounty(createdAt, this, dLow, Stage.BOUNTY);
234                }
235                if (dataLow == null || dataLow.fleet == null) return false;
236                
237                int dNormal = pickDifficulty(DifficultyChoice.NORMAL);
238                creatorNormal = pickCreator(dNormal, DifficultyChoice.NORMAL);
239                if (creatorNormal != null) {
240                        dataNormal = creatorNormal.createBounty(createdAt, this, dNormal, Stage.BOUNTY);
241                }
242                if (dataNormal == null || dataNormal.fleet == null) return false;
243                
244                int dHigh = pickDifficulty(DifficultyChoice.HIGH);
245                creatorHigh = pickCreator(dHigh, DifficultyChoice.HIGH);
246                if (creatorHigh != null) {
247                        dataHigh = creatorHigh.createBounty(createdAt, this, dHigh, Stage.BOUNTY);
248                }
249                //getPerson().getNameString() getPerson().getMarket();
250                if (dataHigh == null || dataHigh.fleet == null) return false;
251
252                
253                return true;
254        }
255        
256        protected void updateInteractionDataImpl() {
257                String id = getMissionId();
258                set("$" + id + "_barEvent", isBarEvent());
259                set("$" + id + "_manOrWoman", getPerson().getManOrWoman());
260                set("$" + id + "_reward", Misc.getWithDGS(getCreditsReward()));
261                set("$bcb_barEvent", isBarEvent());
262                set("$bcb_manOrWoman", getPerson().getManOrWoman());
263                set("$bcb_reward", Misc.getWithDGS(getCreditsReward()));
264                
265                if (showData != null && showCreator != null) {
266                        showCreator.updateInteractionData(this, showData);
267                        
268                        set("$" + id + "_difficultyNum", showData.difficulty);
269                        set("$bcb_difficultyNum", showData.difficulty);
270                        
271                        if (showData.system != null) {
272                                set("$" + id + "_systemName", showData.system.getNameWithLowercaseType());
273                                set("$" + id + "_dist", getDistanceLY(showData.system.getCenter()));
274                                set("$bcb_systemName", showData.system.getNameWithLowercaseType());
275                                set("$bcb_dist", getDistanceLY(showData.system.getCenter()));
276                        }
277                        if (showData.market != null) {
278                                set("$" + id + "_targetMarketName", showData.market.getName());
279                                set("$bcb_targetMarketName", showData.market.getName());
280                                set("$" + id + "_targetMarketOnOrAt", showData.market.getOnOrAt());
281                                set("$bcb_targetMarketOnOrAt", showData.market.getOnOrAt());
282                        }
283                        set("$" + id + "_days", "" + (int) showCreator.getBountyDays());
284                        set("$bcb_days", "" + (int) showCreator.getBountyDays());
285                        
286                        if (showData.fleet != null) {
287                                PersonAPI p = showData.fleet.getCommander();
288                                set("$" + id + "_targetHeOrShe", p.getHeOrShe());
289                                set("$" + id + "_targetHisOrHer", p.getHisOrHer());
290                                set("$" + id + "_targetHimOrHer", p.getHimOrHer());
291                                set("$" + id + "_targetName", p.getNameString());
292                                set("$bcb_targetHeOrShe", p.getHeOrShe());
293                                set("$bcb_targetHisOrHer", p.getHisOrHer());
294                                set("$bcb_targetHimOrHer", p.getHimOrHer());
295                                set("$bcb_targetName", p.getNameString());
296                                
297                                set("$" + id + "_TargetHeOrShe", Misc.ucFirst(p.getHeOrShe()));
298                                set("$" + id + "_TargetHisOrHer", Misc.ucFirst(p.getHisOrHer()));
299                                set("$" + id + "_TargetHimOrHer", Misc.ucFirst(p.getHimOrHer()));
300                                set("$bcb_TargetHeOrShe", Misc.ucFirst(p.getHeOrShe()));
301                                set("$bcb_TargetHisOrHer", Misc.ucFirst(p.getHisOrHer()));
302                                set("$bcb_TargetHimOrHer", Misc.ucFirst(p.getHimOrHer()));
303                                
304                                set("$bcb_fleetName", showData.fleet.getName());
305                        }
306                }
307        }
308        
309        
310        protected transient CustomBountyCreator showCreator;
311        protected transient CustomBountyData showData;
312        @Override
313        protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params,
314                                                                 Map<String, MemoryAPI> memoryMap) {
315                
316                if ("showBountyDetail".equals(action)) {
317                        String id = getMissionId();
318                        MemoryAPI memory = memoryMap.get(MemKeys.LOCAL);
319                        DifficultyChoice difficulty = (DifficultyChoice) Enum.valueOf(DifficultyChoice.class, memory.getString("$" + id + "_difficulty"));
320                        showCreator = creatorLow;
321                        showData = dataLow;
322                        switch (difficulty) {
323                        case LOW:
324                                showCreator = creatorLow;
325                                showData = dataLow;
326                                break;
327                        case NORMAL:
328                                showCreator = creatorNormal;
329                                showData = dataNormal;
330                                break;
331                        case HIGH:
332                                showCreator = creatorHigh;
333                                showData = dataHigh;
334                                break;
335                        }
336                        
337                        setCreditRewardApplyRelMult(showData.baseReward);
338                        
339                        updateInteractionData(dialog, memoryMap);
340                        String trigger = showCreator.getId() + "OfferDesc";
341                        FireBest.fire(null, dialog, memoryMap, trigger);
342                        
343                        if (showData != null && showData.system != null) {
344                                String icon = showCreator.getIconName();
345                                if (icon == null) icon = getIcon();
346                                String text = null;
347                                Set<String> tags = new LinkedHashSet<String>();
348                                tags.add(Tags.INTEL_MISSIONS);
349                                Color color = Misc.getBasePlayerColor();
350                        
351                                if (showData.system.getCenter() != null && showData.system.getCenter().getMarket() != null) {
352                                        color = showData.system.getCenter().getMarket().getTextColorForFactionOrPlanet();
353                                } else if (showData.system.getCenter() instanceof PlanetAPI) {
354                                        color = Misc.setAlpha(((PlanetAPI)showData.system.getCenter()).getSpec().getIconColor(), 255);
355                                        color = Misc.setBrightness(color, 235);
356                                }
357                        
358                                dialog.getVisualPanel().showMapMarker(showData.system.getCenter(), 
359                                                        "Target: " + showData.system.getNameWithLowercaseTypeShort(), color, 
360                                                        true, icon, text, tags);
361                        }
362                        return true;
363                } else if ("showBountyAssessment".equals(action) && showCreator != null) {
364                        showCreator.addIntelAssessment(dialog.getTextPanel(), this, showData);
365                        return true;
366                }
367                
368
369                return super.callAction(action, ruleId, dialog, params, memoryMap);
370        }
371        
372        
373        @Override
374        public void accept(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
375                String id = getMissionId();
376                MemoryAPI memory = memoryMap.get(MemKeys.LOCAL);
377                DifficultyChoice difficulty = (DifficultyChoice) Enum.valueOf(DifficultyChoice.class, memory.getString("$" + id + "_difficulty"));
378                List<Abortable> abort = new ArrayList<Abortable>();
379                switch (difficulty) {
380                case LOW:
381                        creator = creatorLow;
382                        data = dataLow;
383                        abort.addAll(dataNormal.abortWhenOtherVersionAccepted);
384                        abort.addAll(dataHigh.abortWhenOtherVersionAccepted);
385                        break;
386                case NORMAL:
387                        creator = creatorNormal;
388                        data = dataNormal;
389                        abort.addAll(dataLow.abortWhenOtherVersionAccepted);
390                        abort.addAll(dataHigh.abortWhenOtherVersionAccepted);
391                        break;
392                case HIGH:
393                        creator = creatorHigh;
394                        data = dataHigh;
395                        abort.addAll(dataLow.abortWhenOtherVersionAccepted);
396                        abort.addAll(dataNormal.abortWhenOtherVersionAccepted);
397                        break;
398                }
399                
400                for (Abortable curr : abort) {
401                        curr.abort(this, false);
402                }
403                
404                creatorLow = creatorNormal = creatorHigh = null;
405                dataLow = dataNormal = dataHigh = null;
406                
407                MarketAPI createdAt = getPerson().getMarket();
408                if (createdAt == null) createdAt = dialog.getInteractionTarget().getMarket();
409                if (creator.getIconName() != null) {
410                        setIconName(creator.getIconName());
411                }
412                creator.notifyAccepted(createdAt, this, data);
413                
414                target = data.fleet.getCommander();
415                data.fleet.addEventListener(this);
416                makeImportant(data.fleet, "$" + id + "_target", Stage.BOUNTY);
417
418                if (data.fleet.isHidden()) {
419                        MarketAPI market = Misc.getStationMarket(data.fleet);
420                        if (market != null) {
421                                SectorEntityToken station = Misc.getStationEntity(market, data.fleet);
422                                if (station != null) {
423                                        makeImportant(station, "$" + id + "_target", Stage.BOUNTY);
424                                }
425                        }
426                }
427                
428                if (!data.fleet.getFaction().isNeutralFaction()) {
429                        addTag(data.fleet.getFaction().getId());
430                }
431                
432                if (creator.getBountyDays() > 0) {
433                        setTimeLimit(Stage.FAILED, creator.getBountyDays(), creator.getSystemWithNoTimeLimit(data));
434                }
435                //setTimeLimit(Stage.FAILED, 3, creator.getSystemWithNoTimeLimit(data));
436                setCreditRewardApplyRelMult(data.baseReward);
437                setRepRewardPerson(data.repPerson);
438                setRepRewardFaction(data.repFaction);
439                
440                super.accept(dialog, memoryMap);
441        }
442        
443        
444        @Override
445        public void acceptImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
446                
447        }
448        
449        
450        
451        @Override
452        public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {
453                float opad = 10f;
454                float pad = 3f;
455                Color h = Misc.getHighlightColor();
456                if (currentStage == Stage.BOUNTY) {
457                        if (currentStage == Stage.BOUNTY) {
458                                creator.addTargetLocationAndDescription(info, width, height, this, data);
459                        }
460                        creator.addFleetDescription(info, width, height, this, data);
461                }
462        }
463        
464        @Override
465        public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {
466                Color h = Misc.getHighlightColor();
467                if (currentStage == Stage.BOUNTY) {
468                        if (data.system != null) {
469                                creator.addTargetLocationAndDescriptionBulletPoint(info, tc, pad, this, data);
470                                //info.addPara("Target is in the " + data.system.getNameWithLowercaseTypeShort() + "", tc, pad);
471                                return true;
472                        }
473                }
474                return false;
475        }
476        
477
478        @Override
479        public SectorEntityToken getMapLocation(SectorMapAPI map) {
480                return super.getMapLocation(map);
481        }
482        
483        @Override
484        protected void notifyEnding() {
485                super.notifyEnding();
486        }
487        
488        
489        @Override
490        protected void endSuccessImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
491                creator.notifyCompleted(this, data);
492                
493                AggregateBountyData d = getAggregateData();
494                d.completedDifficulty.add(data.difficulty);
495                while (d.completedDifficulty.size() > NUM_TO_TRACK_FOR_DIFFICULTY) {
496                        d.completedDifficulty.remove(0);
497                }
498        }
499        
500        @Override
501        protected void endFailureImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
502                creator.notifyFailed(this, data);
503                
504                AggregateBountyData d = getAggregateData();
505                d.completedDifficulty.add(0);
506                while (d.completedDifficulty.size() > NUM_TO_TRACK_FOR_DIFFICULTY) {
507                        d.completedDifficulty.remove(0);
508                }
509                
510        }
511        public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) {
512                if (isDone() || result != null) return;
513                
514                // also credit the player if they're in the same location as the fleet and nearby
515                float distToPlayer = Misc.getDistance(fleet, Global.getSector().getPlayerFleet());
516                boolean playerInvolved = battle.isPlayerInvolved() || (fleet.isInCurrentLocation() && distToPlayer < 2000f);
517                
518                if (battle.isInvolved(fleet) && !playerInvolved) {
519                        boolean cancelBounty = (fleet.isStationMode() && fleet.getFlagship() == null) ||
520                                        (!fleet.isStationMode() && fleet.getFlagship() != null && fleet.getFlagship().getCaptain() != target);
521                        if (cancelBounty) {
522                                String id = getMissionId();
523                                getPerson().getMemoryWithoutUpdate().set("$" + id + "_failed", true);
524                                return;
525                        }
526//                      if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != target) {
527//                              fleet.setCommander(fleet.getFaction().createRandomPerson());
528//                              //latestResult = new BountyResult(BountyResultType.END_OTHER, 0, null);
529//                              sendUpdateIfPlayerHasIntel(result, true);
530//                              return;
531//                      }
532                }
533                
534                //CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
535                if (!playerInvolved || !battle.isInvolved(fleet) || battle.onPlayerSide(fleet)) {
536                        return;
537                }
538                
539                if (fleet.isStationMode()) {
540                        if (fleet.getFlagship() != null) return;
541                } else {
542                        if (fleet.getFlagship() != null && fleet.getFlagship().getCaptain() == target) return;
543                }
544                
545                String id = getMissionId();
546                getPerson().getMemoryWithoutUpdate().set("$" + id + "_completed", true);
547                
548        }
549
550        public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) {
551                if (isDone() || result != null) return;
552                
553                if (this.data.fleet == fleet) {
554                        String id = getMissionId();
555                        getPerson().getMemoryWithoutUpdate().set("$" + id + "_failed", true);
556                }
557        }
558        
559        
560        @Override
561        public String getBaseName() {
562                if (creator != null) return creator.getBaseBountyName(this, data);
563                return "Bounty";
564        }
565        
566        protected String getMissionTypeNoun() {
567                return "bounty";
568        }
569        
570        public String getPostfixForState() {
571//              if (isEnding()) {
572//                      return super.getPostfixForState();
573//              }
574//              if (true) {
575//                      return " - Unusual Remnant Fleet - Failed";
576//              }
577                String post = super.getPostfixForState();
578                post = post.replaceFirst(" - ", "");
579                if (!post.isEmpty()) post = " (" + post + ")";
580                //if (creator != null) return creator.getBountyNamePostfix(this, data).replaceFirst(" - ", ": ") + post;
581                if (creator != null) return creator.getBountyNamePostfix(this, data) + post;
582                return super.getPostfixForState();
583        }
584        
585        @Override
586        public String getName() {
587                return super.getName();
588        }
589        
590        
591        
592        
593}
594
595
596
597
598
599
600
601
602
603
604