001package com.fs.starfarer.api.impl.campaign.missions;
002
003import java.awt.Color;
004import java.util.ArrayList;
005import java.util.List;
006import java.util.Map;
007import java.util.Set;
008
009import org.lwjgl.util.vector.Vector2f;
010
011import com.fs.starfarer.api.Global;
012import com.fs.starfarer.api.campaign.InteractionDialogAPI;
013import com.fs.starfarer.api.campaign.PersonImportance;
014import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
015import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
016import com.fs.starfarer.api.campaign.econ.MarketAPI;
017import com.fs.starfarer.api.campaign.econ.MonthlyReport;
018import com.fs.starfarer.api.campaign.econ.MonthlyReport.FDNode;
019import com.fs.starfarer.api.campaign.listeners.EconomyTickListener;
020import com.fs.starfarer.api.campaign.rules.MemoryAPI;
021import com.fs.starfarer.api.characters.PersonAPI;
022import com.fs.starfarer.api.impl.campaign.ids.Commodities;
023import com.fs.starfarer.api.impl.campaign.ids.Factions;
024import com.fs.starfarer.api.impl.campaign.ids.FleetTypes;
025import com.fs.starfarer.api.impl.campaign.ids.Ranks;
026import com.fs.starfarer.api.impl.campaign.ids.Tags;
027import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithBarEvent;
028import com.fs.starfarer.api.impl.campaign.shared.SharedData;
029import com.fs.starfarer.api.ui.SectorMapAPI;
030import com.fs.starfarer.api.ui.TooltipMakerAPI;
031import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator;
032import com.fs.starfarer.api.util.Misc;
033import com.fs.starfarer.api.util.WeightedRandomPicker;
034
035public class CommodityProductionMission extends HubMissionWithBarEvent implements EconomyTickListener, TooltipCreator {
036
037        public static float PROB_COMPLICATIONS = 0.5f;
038        
039        public static float PROB_UNDERWORLD_BAR = 0.25f;
040        
041        public static float MISSION_DAYS = 365f * 2f;
042        public static int MISSION_CYCLES = (int) Math.round(MISSION_DAYS / 365f);
043        public static int CONTRACT_DAYS = (int) Math.round(365f * 5f);
044        public static int CONTRACT_MONTHS = (int) Math.round(CONTRACT_DAYS * 12f / 365f);
045        public static int CONTRACT_CYCLES = (int) Math.round(CONTRACT_DAYS * 1f / 365f);
046        
047        public static float REWARD_MULT_WHEN_PRODUCING_ALREADY = 0.2f;
048        public static float REWARD_MULT_WHEN_NOT_PRODUCING_ALREADY = 1f;
049        
050        public static enum Stage {
051                WAITING,
052                PAYING,
053                COMPLETED,
054                FAILED,
055        }
056        
057        public static enum Variation {
058                PRODUCING_ALREADY,
059                NOT_PRODUCING,
060        }
061        
062        public static class CheckPlayerProduction implements ConditionChecker {
063                protected String commodityId;
064                protected int quantity;
065                public CheckPlayerProduction(String commodityId, int quantity) {
066                        this.commodityId = commodityId;
067                        this.quantity = quantity;
068                }
069                public boolean conditionsMet() {
070                        return isPlayerProducing(commodityId, quantity);
071                }
072        }
073        
074//      public static class ConditionsMet implements TriggerAction {
075//              
076//      }
077        
078        protected Variation variation;
079        protected String commodityId;
080        protected int needed;
081        protected int monthlyPayment;
082        protected int totalPayment;
083        
084        protected int monthsRemaining;
085        protected String uid;
086        
087        @Override
088        protected boolean create(MarketAPI createdAt, boolean barEvent) {
089                //genRandom = Misc.random;
090                if (barEvent) {
091                        setGiverRank(Ranks.CITIZEN);
092                        String post = pickOne(Ranks.POST_TRADER, Ranks.POST_COMMODITIES_AGENT, Ranks.POST_PORTMASTER, 
093                                                                  Ranks.POST_MERCHANT, Ranks.POST_INVESTOR, Ranks.POST_EXECUTIVE,
094                                                                  Ranks.POST_SENIOR_EXECUTIVE);
095                        setGiverPost(post);
096                        if (post.equals(Ranks.POST_SENIOR_EXECUTIVE)) {
097                                setGiverImportance(pickHighImportance());
098                        } else {
099                                setGiverImportance(pickImportance());
100                        }
101                        if (rollProbability(PROB_UNDERWORLD_BAR)) {
102                                setGiverTags(Tags.CONTACT_UNDERWORLD);
103                                setGiverFaction(Factions.PIRATES);
104                        } else {
105                                setGiverTags(Tags.CONTACT_TRADE);
106                        }
107                        findOrCreateGiver(createdAt, false, false);
108                }
109                
110                PersonAPI person = getPerson();
111                if (person == null) return false;
112                
113                if (!setPersonMissionRef(person, "$cpm_ref")) {
114                        return false;
115                }
116                
117                if (barEvent) {
118                        setGiverIsPotentialContactOnSuccess();
119                }
120                
121                PersonImportance importance = person.getImportance();
122                int minNeeded = 3;
123                int maxNeeded = 9;
124                switch (importance) {
125                case VERY_LOW:
126                        minNeeded = 2;
127                        maxNeeded = 4;
128                        break;
129                case LOW:
130                        minNeeded = 3;
131                        maxNeeded = 5;
132                        break;
133                case MEDIUM:
134                        minNeeded = 4;
135                        maxNeeded = 6;
136                        break;
137                case HIGH:
138                        minNeeded = 5;
139                        maxNeeded = 7;
140                        break;
141                case VERY_HIGH:
142                        minNeeded = 6;
143                        maxNeeded = 10;
144                        break;
145                }
146                
147                needed = minNeeded + genRandom.nextInt(maxNeeded - minNeeded + 1);
148                
149                MarketAPI market = getPerson().getMarket();
150                if (market == null) return false;
151                if (market.isPlayerOwned()) return false;
152                
153                WeightedRandomPicker<String> commoditiesPlayerIsNotProducing = new WeightedRandomPicker<String>(genRandom);
154                WeightedRandomPicker<String> commoditiesPlayerIsProducing = new WeightedRandomPicker<String>(genRandom);
155                
156                List<String> all = new ArrayList<String>();
157                for (CommoditySpecAPI spec : Global.getSettings().getAllCommoditySpecs()) {
158                        if (spec.isPersonnel()) continue;
159                        if (spec.isMeta()) continue;
160                        if (spec.isNonEcon()) continue;
161                        if (!spec.isPrimary()) continue;
162                        
163                        if (market.getCommodityData(spec.getId()).getMaxDemand() < minNeeded / 2) continue;
164                        
165                        boolean illegal = market.isIllegal(spec.getId());
166                        if (illegal && !person.hasTag(Tags.CONTACT_UNDERWORLD)) continue;
167                        if (!illegal && !person.hasTag(Tags.CONTACT_TRADE)) continue;
168                        
169                        all.add(spec.getId());
170                }
171                
172                for (String cid : all) {
173                        commoditiesPlayerIsNotProducing.add(cid);
174                        for (MarketAPI curr : Misc.getPlayerMarkets(true)) {
175                                CommodityOnMarketAPI com = curr.getCommodityData(cid);
176                                if (com.getMaxSupply() > 0) {
177                                        commoditiesPlayerIsProducing.add(cid, Math.max(1, 10 - com.getMaxSupply()));
178                                        commoditiesPlayerIsNotProducing.remove(cid);
179                                }
180                        }
181                }
182                
183                commodityId = commoditiesPlayerIsNotProducing.pick();
184                if (commodityId == null) {
185                        commodityId = commoditiesPlayerIsProducing.pick();
186                }
187                //commodityId = Commodities.VOLATILES;
188                if (commodityId == null) return false;
189                
190                if (commodityId.equals(Commodities.ORGANS)) {
191                        needed = Math.min(3, needed);
192                }
193                if (commodityId.equals(Commodities.DRUGS)) {
194                        needed = Math.min(6, needed);
195                }
196                
197                variation = commoditiesPlayerIsNotProducing.getItems().contains(commodityId) ? 
198                                                                Variation.NOT_PRODUCING : Variation.PRODUCING_ALREADY;
199                
200                
201                //float basePayment = getSpec().getExportValue();
202                float basePayment = 1000 + getSpec().getBasePrice() * 10;
203                if (variation == Variation.NOT_PRODUCING) {
204                        basePayment *= REWARD_MULT_WHEN_NOT_PRODUCING_ALREADY;
205                } else {
206                        basePayment *= REWARD_MULT_WHEN_PRODUCING_ALREADY;
207                }
208                
209                monthlyPayment = getRoundNumber(basePayment * needed);
210                totalPayment = (int) Math.round(monthlyPayment * CONTRACT_MONTHS);
211                if (monthlyPayment <= 0) return false;
212                
213                
214                setStartingStage(Stage.WAITING);
215                setSuccessStage(Stage.COMPLETED);
216                setFailureStage(Stage.FAILED);
217                
218                connectWithCustomCondition(Stage.WAITING, Stage.PAYING, new CheckPlayerProduction(commodityId, needed));
219                setTimeLimit(Stage.FAILED, MISSION_DAYS, null, Stage.PAYING);
220                
221                monthsRemaining = (int) CONTRACT_MONTHS;
222                
223                return true;
224        }
225        
226        @Override
227        public void setCurrentStage(Object next, InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
228                super.setCurrentStage(next, dialog, memoryMap);
229                
230                if (next == Stage.PAYING) {
231                        addPotentialContacts(dialog);
232                }
233        }
234
235
236
237        protected void updateInteractionDataImpl() {
238                set("$cpm_barEvent", isBarEvent());
239                set("$cpm_manOrWoman", getPerson().getManOrWoman());
240                set("$cpm_monthlyPayment", Misc.getWithDGS(monthlyPayment));
241                set("$cpm_underworld", getPerson().hasTag(Tags.CONTACT_UNDERWORLD));
242                set("$cpm_totalPayment", Misc.getWithDGS(totalPayment));
243                set("$cpm_missionCycles", MISSION_CYCLES);
244                set("$cpm_contractCycles", CONTRACT_CYCLES);
245                set("$cpm_commodityName", getSpec().getLowerCaseName());
246                set("$cpm_needed", needed);
247                set("$cpm_playerHasColony", !Misc.getPlayerMarkets(false).isEmpty());
248        }
249        
250        @Override
251        public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) {
252                float opad = 10f;
253                Color h = Misc.getHighlightColor();
254                if (currentStage == Stage.WAITING) {
255                        info.addPara("Produce at least %s units of " + getSpec().getLowerCaseName() + " at " +
256                                        "a colony under your control.", opad, h, "" + needed);
257                        info.addPara("Once these terms are met, you will receive %s per month for the next " +
258                                        "%s cycles, for a total of %s, as long as production is maintained.", opad, h,
259                                        Misc.getDGSCredits(monthlyPayment),
260                                        "" + (int)CONTRACT_CYCLES,
261                                        Misc.getDGSCredits(totalPayment));
262                        if (!playerHasAColony()) {
263                                info.addPara("You will need to survey a suitable planet and establish a colony to complete " +
264                                                         "this mission.", opad);
265                        }
266                } else if (currentStage == Stage.PAYING) {
267                        info.addPara("You've met the initial terms of the contract to produce %s units of " + 
268                                                 getSpec().getLowerCaseName() + " at " +
269                                                 "a colony under your control.", opad, h, "" + needed);
270                        info.addPara("As long these terms are met, you will receive %s per month over %s cycles for " +
271                                        "a total payout of %s, assuming there is no interruption in production.", opad, h,
272                                        Misc.getDGSCredits(monthlyPayment),
273                                        "" + (int)CONTRACT_CYCLES,
274                                        Misc.getDGSCredits(totalPayment));
275                        info.addPara("Months remaining: %s", opad, h, "" + monthsRemaining);
276                        if (isPlayerProducing(commodityId, needed)) {
277                                info.addPara("You are currently meeting the terms of the contract.", 
278                                                         Misc.getPositiveHighlightColor(), opad);
279                        } else {
280                                info.addPara("You are not currently meeting the terms of the contract.", 
281                                                         Misc.getNegativeHighlightColor(), opad);
282                        }
283                } else if (currentStage == Stage.COMPLETED) {
284                        info.addPara("The contract is completed.", opad);
285                }
286        }
287
288        @Override
289        public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) {
290                Color h = Misc.getHighlightColor();
291                if (currentStage == Stage.WAITING) {
292                        info.addPara("Produce at least %s units of " + getSpec().getLowerCaseName() + " at " +
293                                                 "a colony", pad, tc, h, "" + needed);
294                        return true;
295                } else if (currentStage == Stage.PAYING) {
296                        info.addPara("Receiving %s per month", pad, tc, h, Misc.getDGSCredits(monthlyPayment));
297                        info.addPara("Months remaining: %s", 0f, tc, h, "" + monthsRemaining);
298                        if (isPlayerProducing(commodityId, needed)) {
299                                info.addPara("Terms of contract met", tc, 0f);
300                        } else {
301                                info.addPara("Terms of contract not met", 
302                                                         Misc.getNegativeHighlightColor(), 0f);
303                        }
304                        return true;
305                }
306                return false;
307        }       
308        
309        @Override
310        public String getBaseName() {
311                return getSpec().getName() + " Production";
312        }
313        
314
315        public static boolean playerHasAColony() {
316                return !Misc.getPlayerMarkets(true).isEmpty();
317        }
318        public static boolean isPlayerProducing(String commodityId, int quantity) {
319                for (MarketAPI market : Misc.getPlayerMarkets(true)) {
320                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
321                        if (com.getMaxSupply() >= quantity) return true;
322                }
323                return false;
324        }
325
326        protected transient CommoditySpecAPI spec;
327        protected CommoditySpecAPI getSpec() {
328                if (spec == null) {
329                        spec = Global.getSettings().getCommoditySpec(commodityId);
330                }
331                return spec;
332        }
333        
334        
335        @Override
336        protected void notifyEnding() {
337                super.notifyEnding();
338                Global.getSector().getListenerManager().removeListener(this);
339        }
340
341        @Override
342        public void acceptImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) {
343                super.acceptImpl(dialog, memoryMap);
344                
345                Global.getSector().getListenerManager().addListener(this);
346                uid = Misc.genUID();
347                connectWithGlobalFlag(Stage.PAYING, Stage.COMPLETED, getCompletionFlag());
348                
349                
350                if (rollProbability(PROB_COMPLICATIONS)) {
351                        DelayedFleetEncounter e = new DelayedFleetEncounter(genRandom, getMissionId());
352                        //e.setDelay(0f);
353                        e.setDelay(MISSION_DAYS * 0.5f);
354                        e.setLocationInnerSector(true, Factions.PIRATES);
355                        //e.setEncounterInHyper();
356                        e.beginCreate();
357                        e.triggerCreateFleet(FleetSize.VERY_LARGE, FleetQuality.DEFAULT, Factions.PIRATES, FleetTypes.PATROL_LARGE, new Vector2f());
358                        e.triggerSetAdjustStrengthBasedOnQuality(true, getQuality());
359                        e.triggerSetStandardAggroPirateFlags();
360                        e.triggerSetStandardAggroInterceptFlags();
361                        e.triggerSetFleetMemoryValue("$cpm_commodityName", getSpec().getLowerCaseName());
362                        e.triggerSetFleetGenericHailPermanent("CPMPirateHail");
363                        e.endCreate();
364                }
365        }
366
367        public String getCompletionFlag() {
368                return "$" + getMissionId() + "_" + commodityId + "_" + uid + "_completed";
369        }
370        
371        
372        public void reportEconomyTick(int iterIndex) {
373                if (currentStage != Stage.PAYING) return;
374                
375                int numIter = (int) Global.getSettings().getFloat("economyIterPerMonth");
376                
377                MonthlyReport report = SharedData.getData().getCurrentReport();
378                FDNode colonyNode = report.getNode(MonthlyReport.OUTPOSTS);
379                FDNode paymentNode = report.getNode(colonyNode, getMissionId() + "_" + commodityId + "_" + uid);
380                paymentNode.income += monthlyPayment / numIter;
381                paymentNode.name = getBaseName();
382                //paymentNode.icon = Global.getSettings().getSpriteName("income_report", "generic_income");
383                paymentNode.icon = getSpec().getIconName();
384                paymentNode.tooltipCreator = this;
385        }
386        
387
388        public void reportEconomyMonthEnd() {
389                monthsRemaining--;
390                //monthsRemaining = 0;
391                if (monthsRemaining <= 0) {
392                        Global.getSector().getMemoryWithoutUpdate().set(getCompletionFlag(), true);
393                }
394        }
395
396        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
397                tooltip.addSpacer(-10f);
398                addDescriptionForNonEndStage(tooltip, getTooltipWidth(tooltipParam), 1000f);
399        }
400
401        public float getTooltipWidth(Object tooltipParam) {
402                return 450;
403        }
404
405        public boolean isTooltipExpandable(Object tooltipParam) {
406                return false;
407        }
408        
409        protected String getMissionTypeNoun() {
410                return "contract";
411        }
412
413        @Override
414        public Set<String> getIntelTags(SectorMapAPI map) {
415                Set<String> tags = super.getIntelTags(map);
416                if (currentStage == Stage.PAYING) {
417                        tags.add(Tags.INTEL_AGREEMENTS);
418                        tags.remove(Tags.INTEL_ACCEPTED);
419                }
420                return tags;
421        }
422        
423        
424}
425