001package com.fs.starfarer.api.impl.campaign.submarkets;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.Comparator;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011
012import com.fs.starfarer.api.Global;
013import com.fs.starfarer.api.campaign.CargoAPI;
014import com.fs.starfarer.api.campaign.CargoStackAPI;
015import com.fs.starfarer.api.campaign.CoreUIAPI;
016import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
017import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
018import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
019import com.fs.starfarer.api.campaign.econ.MonthlyReport;
020import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
021import com.fs.starfarer.api.campaign.econ.EconomyAPI.EconomyUpdateListener;
022import com.fs.starfarer.api.campaign.econ.MonthlyReport.FDNode;
023import com.fs.starfarer.api.campaign.listeners.EconomyTickListener;
024import com.fs.starfarer.api.combat.MutableStat;
025import com.fs.starfarer.api.combat.MutableStat.StatMod;
026import com.fs.starfarer.api.impl.campaign.econ.impl.BaseIndustry;
027import com.fs.starfarer.api.impl.campaign.ids.Commodities;
028import com.fs.starfarer.api.impl.campaign.ids.Strings;
029import com.fs.starfarer.api.impl.campaign.ids.Submarkets;
030import com.fs.starfarer.api.impl.campaign.shared.SharedData;
031import com.fs.starfarer.api.ui.Alignment;
032import com.fs.starfarer.api.ui.TooltipMakerAPI;
033import com.fs.starfarer.api.util.Misc;
034
035public class LocalResourcesSubmarketPlugin extends BaseSubmarketPlugin implements EconomyUpdateListener, EconomyTickListener {
036
037        public static float STOCKPILE_MULT_PRODUCTION = Global.getSettings().getFloat("stockpileMultProduction");
038        public static float STOCKPILE_MULT_EXCESS = Global.getSettings().getFloat("stockpileMultExcess");
039        public static float STOCKPILE_MULT_IMPORTS = Global.getSettings().getFloat("stockpileMultImports");
040        public static float STOCKPILE_MAX_MONTHS = Global.getSettings().getFloat("stockpileMaxMonths");
041        
042        public static float STOCKPILE_COST_MULT = Global.getSettings().getFloat("stockpileCostMult");
043        public static float STOCKPILE_SHORTAGE_COST_MULT = Global.getSettings().getFloat("stockpileShortageCostMult");
044        
045
046        protected CargoAPI taken;
047        protected CargoAPI left;
048        
049        protected Map<String, MutableStat> stockpilingBonus = new HashMap<String, MutableStat>();
050        
051        public LocalResourcesSubmarketPlugin() {
052                taken = Global.getFactory().createCargo(true);
053                left = Global.getFactory().createCargo(true);
054        }
055        
056        public void init(SubmarketAPI submarket) {
057                super.init(submarket);
058                Global.getSector().getEconomy().addUpdateListener(this);
059                Global.getSector().getListenerManager().addListener(this);
060        }
061
062        public boolean showInFleetScreen() {
063                return false;
064        }
065        
066        public boolean showInCargoScreen() {
067                return true;
068        }
069        
070        public boolean isEnabled(CoreUIAPI ui) {
071                return true;
072        }
073
074        @Override
075        public void advance(float amount) {
076                super.advance(amount);
077        
078                // need to add to stockpiles every frame because the player can see stockpiles
079                // in the "market info" screen and updateCargoPrePlayerInteraction() doesn't get called from there
080                addAndRemoveStockpiledResources(amount, true, false, true);
081                
082//              if (!Global.getSector().getListenerManager().hasListener(this)) {
083//                      Global.getSector().getListenerManager().addListener(this);
084//              }
085        }
086        
087        public boolean shouldHaveCommodity(CommodityOnMarketAPI com) {
088                if (market.isIllegal(com)) {
089                        if (com.getCommodityMarketData().getMarketShareData(market).isSourceIsIllegal()) {
090                                return false;
091                        }
092                        return true;
093                }
094                return true;
095        }
096        
097        @Override
098        public boolean isIllegalOnSubmarket(CargoStackAPI stack, TransferAction action) {
099                if (stack.getCommodityId() == null) return true;
100                if (stack.getResourceIfResource().hasTag(Commodities.TAG_NON_ECONOMIC)) return true;
101                return false;
102        }
103        
104        
105        @Override
106        public String getIllegalTransferText(CargoStackAPI stack, TransferAction action) {
107                return "Can only store resources";
108        }
109        
110
111        @Override
112        public int getStockpileLimit(CommodityOnMarketAPI com) {
113                
114                int demand = com.getMaxDemand();
115                
116                int shippingGlobal = com.getCommodityMarketData().getMaxShipping(com.getMarket(), false);
117                //int shippingInFaction = com.getCommodityMarketData().getMaxShipping(com.getMarket(), true);
118                
119                int available = com.getAvailable();
120                String modId = submarket.getSpecId();
121                StatMod mod = com.getAvailableStat().getFlatStatMod(modId);
122                if (mod != null) {
123                        available -= (int) mod.value;
124                        if (available < 0) available = 0;
125                }
126                
127                int production = com.getMaxSupply();
128                production = Math.min(production, available);
129                        
130                int export = 0;
131                demand = com.getMaxDemand();
132                export = (int) Math.min(production, shippingGlobal);
133                
134                int extra = available - Math.max(export, demand);
135                if (extra < 0) extra = 0;
136                
137                int deficit = demand - available;
138//              int demandMet = Math.min(available, demand);
139//              int demandMetWithLocal = Math.min(available, production) - extra;
140                int imports = available - production;
141                if (imports < 0) imports = 0; 
142                
143                production -= extra;
144                
145                float unit = com.getCommodity().getEconUnit();
146                
147                float limit = 0f;
148                limit += STOCKPILE_MULT_EXCESS * BaseIndustry.getSizeMult(extra) * unit;
149                limit += STOCKPILE_MULT_PRODUCTION * BaseIndustry.getSizeMult(production) * unit;
150                limit += STOCKPILE_MULT_IMPORTS * BaseIndustry.getSizeMult(imports) * unit;
151                
152                String cid = com.getId();
153                if (stockpilingBonus.containsKey(cid)) {
154                        limit += stockpilingBonus.get(cid).getModifiedValue() * unit;
155                }
156                
157                //limit *= com.getMarket().getStockpileMult().getModifiedValue();
158                limit *= STOCKPILE_MAX_MONTHS;
159                
160                if (deficit > 0) {
161                        limit = 0;
162                }
163                
164                if (limit < 0) limit = 0;
165                
166                return (int) limit;
167        }
168        
169        
170        
171        
172        @Override
173        public float getStockpilingAddRateMult(CommodityOnMarketAPI com) {
174//              float mult = com.getMarket().getStockpileMult().getModifiedValue();
175//              if (mult > 0) {
176//                      return 1f / mult;
177//              }
178                return 1f / STOCKPILE_MAX_MONTHS;
179        }
180        
181//      public int getApproximateStockpilingCost() {
182//              CargoAPI cargo = getCargo();
183//              
184//              float total = 0f;
185//              for (CommodityOnMarketAPI com : market.getCommoditiesCopy()) {
186//                      if (com.isNonEcon()) continue;
187//                      if (com.getCommodity().isMeta()) continue;
188//                      
189//                      int limit = getStockpileLimit(com);
190//                      if (limit <= 0) continue;
191//                      
192//                      int needed = (int) (limit - cargo.getCommodityQuantity(com.getId()));
193//                      if (needed <= 0) continue;
194//                      
195//                      float price = getStockpilingUnitPrice(com.getCommodity());
196//                      total += price * needed;
197//              }
198//              
199//              return (int)(Math.ceil(total / 1000f) * 1000f);
200//      }
201
202
203        public void commodityUpdated(String commodityId) {
204                if (Global.getSector().isPaused()) {
205                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
206                        addAndRemoveStockpiledResources(com, 0f, true, false, false);
207                }
208        }
209
210        public void economyUpdated() {
211                if (Global.getSector().isPaused()) { // to apply shortage-countering during economy steps in UI operations
212                        addAndRemoveStockpiledResources(0f, true, false, false);
213                }
214        }
215
216        public boolean isEconomyListenerExpired() {
217//              if (!market.isPlayerOwned()) {
218//                      market.removeSubmarket(submarket.getSpecId());
219//                      return true;
220//              }
221                return !market.hasSubmarket(submarket.getSpecId());
222        }
223        
224        
225
226        
227        @Override
228        public boolean isParticipatesInEconomy() {
229                return false;
230        }
231
232        @Override
233        public boolean isHidden() {
234//              if (true) return false;
235                return !market.isPlayerOwned();
236        }
237
238        public float getTariff() {
239                return 0f;
240        }
241
242        @Override
243        public boolean isFreeTransfer() {
244                return true;
245        }
246
247        protected transient CargoAPI preTransactionCargoCopy = null;
248        public void updateCargoPrePlayerInteraction() {
249                preTransactionCargoCopy = getCargo().createCopy();
250                preTransactionCargoCopy.sort();
251                getCargo().sort();
252                //sinceLastCargoUpdate = 0f;
253        }
254        
255        public void reportPlayerMarketTransaction(PlayerMarketTransaction transaction) {
256                //addAndRemoveStockpiledResources(0f, true, false, false); // not needed b/c economyUpdated() gets called
257                
258                sinceLastCargoUpdate = 0f; // to reset how long until one more unit gets added if something was drawn down to 0
259                
260                preTransactionCargoCopy = getCargo().createCopy();
261                preTransactionCargoCopy.sort();
262                
263                taken.addAll(transaction.getBought());
264                left.addAll(transaction.getSold());
265                
266                CargoAPI copy = taken.createCopy();
267                taken.removeAll(left);
268                left.removeAll(copy);
269        }
270        
271        protected Object readResolve() {
272                super.readResolve();
273                
274                if (taken == null) {
275                        taken = Global.getFactory().createCargo(true);
276                }
277                if (left == null) {
278                        left = Global.getFactory().createCargo(true);
279                }
280                if (stockpilingBonus == null) {
281                        stockpilingBonus = new HashMap<String, MutableStat>();
282                }
283                return this;
284        }
285        
286        public MutableStat getStockpilingBonus(String cid) {
287                MutableStat stat = stockpilingBonus.get(cid);
288                if (stat == null) {
289                        stat = new MutableStat(0);
290                        stockpilingBonus.put(cid, stat);
291                }
292                return stat;
293        }
294
295        public CargoAPI getLeft() {
296                return left;
297        }
298        
299        public int getEstimatedShortageCounteringCostPerMonth() {
300                List<CommodityOnMarketAPI> all = new ArrayList<CommodityOnMarketAPI>(market.getAllCommodities());
301                
302                float totalCost = 0f;
303                
304                CargoAPI cargo = getCargo();
305                
306                for (CommodityOnMarketAPI com : all) {
307                        int curr = (int) cargo.getCommodityQuantity(com.getId());
308                        if (curr <= 0) continue;
309                        
310                        float units = LocalResourcesSubmarketPlugin.getDeficitMonthlyCommodityUnits(com);
311                        units = Math.min(units, cargo.getCommodityQuantity(com.getId()));
312                        units -= taken.getCommodityQuantity(com.getId());
313                        if (units > 0) {
314                                float per = LocalResourcesSubmarketPlugin.getStockpilingUnitPrice(com.getCommodity(), true);
315                                totalCost += units * per;
316                        }
317                }
318                return (int) totalCost;
319        }
320
321        public static int getStockpilingUnitPrice(CommoditySpecAPI spec, boolean forShortageCountering) {
322                float mult = STOCKPILE_COST_MULT;
323                if (forShortageCountering) mult = STOCKPILE_SHORTAGE_COST_MULT;
324                int result = (int) Math.round((spec.getBasePrice() * mult));
325                if (result < 1) result = 1;
326                return result;
327//              float unitPrice = market.getDemandPrice(com.getId(), 1, true);
328//              if (unitPrice < 1) unitPrice = 1;
329//              return (int) unitPrice;
330        }
331        
332        public static float getDeficitMonthlyCommodityUnits(CommodityOnMarketAPI com) {
333                String modId = Submarkets.LOCAL_RESOURCES;
334                
335                StatMod mod = com.getAvailableStat().getFlatMods().get(modId);
336                float modAlready = 0;
337                if (mod != null) modAlready = mod.value;
338                
339                int demand = com.getMaxDemand();
340                int available = (int) Math.round(com.getAvailable() - modAlready);
341                
342                if (demand > available) {
343                        float deficitDrawBaseAmount = BaseIndustry.getSizeMult(demand) - BaseIndustry.getSizeMult(available);
344                        deficitDrawBaseAmount *= com.getCommodity().getEconUnit();
345                        return deficitDrawBaseAmount;
346                }
347                return 0;
348        }
349        
350        protected boolean doShortageCountering(CommodityOnMarketAPI com, float amount, boolean withShortageCountering) {
351                CargoAPI cargo = getCargo();
352                String modId = submarket.getSpecId();
353                
354                com.getAvailableStat().unmodifyFlat(modId);
355                
356                int demand = com.getMaxDemand();
357                int available = com.getAvailable();
358
359//              if (com.isIllegal() && com.getMarket().isPlayerOwned()) {
360//                      System.out.println("wefwefew");
361//              }
362                
363                if (withShortageCountering && demand > available) {
364                        // draw resources and apply bonus
365                        int deficit = demand - available;
366                        if (deficit != deficit) return false; // bug report indicates possible NaN here; not sure how
367                        
368                        float deficitDrawBaseAmount = BaseIndustry.getSizeMult(demand) - BaseIndustry.getSizeMult(available);
369                        deficitDrawBaseAmount *= com.getCommodity().getEconUnit();
370                        
371                        float days = Global.getSector().getClock().convertToDays(amount);
372                        
373                        float drawAmount = deficitDrawBaseAmount * days / 30f;
374                        float curr = cargo.getCommodityQuantity(com.getId());
375                        if (curr > 0 && deficitDrawBaseAmount > 0) {
376                                int daysLeft = (int) (curr / deficitDrawBaseAmount * 30f);
377                                String daysStr = "days";
378                                if (daysLeft <= 1) {
379                                        daysLeft = 1;
380                                        daysStr = "day";
381                                }
382                                com.getAvailableStat().modifyFlat(modId, deficit, 
383                                                        "Local resource stockpiles (" + daysLeft + " " + daysStr + " left)");
384                                
385                                float free = left.getCommodityQuantity(com.getId());
386                                free = Math.min(drawAmount, free);
387                                left.removeCommodity(com.getId(), free);
388                                if (drawAmount > 0) {
389                                        cargo.removeCommodity(com.getId(), drawAmount);
390                                }
391                                drawAmount -= free;
392                                
393                                if (market.isPlayerOwned() && drawAmount > 0) {
394                                        MonthlyReport report = SharedData.getData().getCurrentReport();
395                                        FDNode node = report.getCounterShortageNode(market);
396                                        
397                                        CargoAPI tooltipCargo = (CargoAPI) node.custom2;
398                                        float addToTooltipCargo = drawAmount;
399                                        float q = tooltipCargo.getCommodityQuantity(com.getId()) + addToTooltipCargo;
400                                        if (q < 1) {
401                                                addToTooltipCargo = 1f; // add at least 1 unit or it won't do anything
402                                        }
403                                        tooltipCargo.addCommodity(com.getId(), addToTooltipCargo);
404                                        
405                                        float unitPrice = (int) getStockpilingUnitPrice(com.getCommodity(), true);
406                                        //node.upkeep += unitPrice * addAmount;
407                                        
408                                        FDNode comNode = report.getNode(node, com.getId());
409                                                
410                                        CommoditySpecAPI spec = com.getCommodity();
411                                        comNode.icon = spec.getIconName();
412                                        comNode.upkeep += unitPrice * drawAmount;
413                                        comNode.custom = com;
414                                        
415                                        if (comNode.custom2 == null) {
416                                                comNode.custom2 = 0f;
417                                        }
418                                        comNode.custom2 = (Float)comNode.custom2 + drawAmount;
419                                        
420                                        float qty = Math.max(1, (Float) comNode.custom2);
421                                        qty = (float) Math.ceil(qty);
422                                        comNode.name = spec.getName() + " " + Strings.X + Misc.getWithDGS(qty);
423                                        comNode.tooltipCreator = report.getMonthlyReportTooltip();
424                                }
425                        }
426                        return true;
427                }
428                return false;
429        }
430        
431        
432        public void reportEconomyMonthEnd() {
433                if (isEconomyListenerExpired()) {
434                        Global.getSector().getListenerManager().removeListener(this);
435                        return;
436                }
437        }
438
439        public void reportEconomyTick(int iterIndex) {
440                if (isEconomyListenerExpired()) {
441                        Global.getSector().getListenerManager().removeListener(this);
442                        return;
443                }
444                
445                int lastIterInMonth = (int) Global.getSettings().getFloat("economyIterPerMonth") - 1;
446                if (iterIndex != lastIterInMonth) return;
447                
448                if (market.isPlayerOwned()) {
449                        CargoAPI copy = taken.createCopy();
450                        taken.removeAll(left);
451                        left.removeAll(copy);
452                        
453                        MonthlyReport report = SharedData.getData().getCurrentReport();
454                        
455                        
456                        for (CargoStackAPI stack : taken.getStacksCopy()) {
457                                if (!stack.isCommodityStack()) continue;
458                                
459                                FDNode node = report.getRestockingNode(market);
460                                CargoAPI tooltipCargo = (CargoAPI) node.custom2;
461                                
462                                float addToTooltipCargo = stack.getSize();
463                                String cid = stack.getCommodityId();
464                                float q = tooltipCargo.getCommodityQuantity(cid) + addToTooltipCargo;
465                                if (q < 1) {
466                                        addToTooltipCargo = 1f; // add at least 1 unit or it won't do anything
467                                }
468                                tooltipCargo.addCommodity(cid, addToTooltipCargo);
469                                
470                                float unitPrice = (int) getStockpilingUnitPrice(stack.getResourceIfResource(), false);
471                                //node.upkeep += unitPrice * addAmount;
472                                
473                                FDNode comNode = report.getNode(node, cid);
474                                        
475                                CommoditySpecAPI spec = stack.getResourceIfResource();
476                                comNode.icon = spec.getIconName();
477                                comNode.upkeep += unitPrice * addToTooltipCargo;
478                                comNode.custom = market.getCommodityData(cid);
479                                
480                                if (comNode.custom2 == null) {
481                                        comNode.custom2 = 0f;
482                                }
483                                comNode.custom2 = (Float)comNode.custom2 + addToTooltipCargo;
484                                
485                                float qty = Math.max(1, (Float) comNode.custom2);
486                                qty = (float) Math.ceil(qty);
487                                comNode.name = spec.getName() + " " + Strings.X + Misc.getWithDGS(qty);
488                                comNode.tooltipCreator = report.getMonthlyReportTooltip();
489                        }
490                }
491                taken.clear();
492        }
493        
494        
495        @Override
496        public String getBuyVerb() {
497                return "Take";
498        }
499
500        @Override
501        public String getSellVerb() {
502                return "Leave";
503        }
504
505        public String getTariffTextOverride() {
506                return "End of month";
507        }
508        public String getTariffValueOverride() {
509                if (preTransactionCargoCopy == null) return null; // could happen when visiting colony from colony list screen
510                CargoAPI cargo = getCargo();
511                //preTransactionCargoCopy;
512                
513                float total = 0f;
514                Set<String> seen = new HashSet<String>();
515                for (CargoStackAPI stack : preTransactionCargoCopy.getStacksCopy()) {
516                        if (!stack.isCommodityStack()) continue;
517                        
518                        String cid = stack.getCommodityId();
519                        if (seen.contains(cid)) continue;
520                        seen.add(cid);
521                        
522                        CommodityOnMarketAPI com = market.getCommodityData(cid);
523                        
524                        int pre = (int) preTransactionCargoCopy.getCommodityQuantity(cid);
525                        int post = (int) cargo.getCommodityQuantity(cid);
526                        
527                        int units = pre - post; // player taking this many units
528                        
529                        units -= left.getCommodityQuantity(cid);
530                        
531                        if (units > 0) {
532                                float price = getStockpilingUnitPrice(com.getCommodity(), false);
533                                total += price * units;
534                        }
535                }
536                
537                return Misc.getDGSCredits(total);
538        }
539        
540        public String getTotalTextOverride() {
541                return "Now";
542        }
543        public String getTotalValueOverride() {
544                return "0" + Strings.C;
545                //return "";
546        }
547        
548        
549        
550        public boolean isTooltipExpandable() {
551                return false;
552        }
553        
554        public float getTooltipWidth() {
555                return 500f;
556        }
557
558        protected void createTooltipAfterDescription(TooltipMakerAPI tooltip, boolean expanded) {
559                List<CommodityOnMarketAPI> all = new ArrayList<CommodityOnMarketAPI>(market.getAllCommodities());
560                
561                Collections.sort(all, new Comparator<CommodityOnMarketAPI>() {
562                        public int compare(CommodityOnMarketAPI o1, CommodityOnMarketAPI o2) {
563                                int limit1 = getStockpileLimit(o1);
564                                int limit2 = getStockpileLimit(o2);
565                                return limit2 - limit1;
566                        }
567                });
568
569                float opad = 10f;
570                
571                tooltip.beginGridFlipped(400f, 1, 70f, opad);
572                int j = 0;
573                for (CommodityOnMarketAPI com : all) {
574                        if (com.isNonEcon()) continue;
575                        if (com.getCommodity().isMeta()) continue;
576                        
577                        if (!shouldHaveCommodity(com)) continue;
578                        
579                        int limit = (int) Math.round(getStockpileLimit(com) * getStockpilingAddRateMult(com));
580                        if (limit <= 0) continue;
581                        
582                        tooltip.addToGrid(0, j++,
583                                        com.getCommodity().getName(),
584                                        Misc.getWithDGS(limit));
585                                                //Misc.getWithDGS(curr) + " / " + Misc.getWithDGS(limit));
586                }
587                
588                tooltip.addPara("A portion of the resources produced by the colony will be made available here. " +
589                                "These resources can be extracted from the colony's economy for a cost equal to %s of their base value. " +
590                                "This cost will be deducted at the end of the month.", opad,
591                                Misc.getHighlightColor(), "" + (int)Math.round(STOCKPILE_COST_MULT * 100f) + "%");
592                
593                tooltip.addPara("These resources can also be used to counter temporary shortages, for a " +
594                                "cost equal to %s of their base value. If additional resources are placed here, they " +
595                                "will be used as well, at no cost.", opad,
596                                Misc.getHighlightColor(), "" + (int)Math.round(STOCKPILE_SHORTAGE_COST_MULT * 100f) + "%");
597                
598                
599                tooltip.addSectionHeading("Stockpiled per month", market.getFaction().getBaseUIColor(), market.getFaction().getDarkUIColor(), Alignment.MID, opad);
600                if (j > 0) {
601                        tooltip.addGrid(opad);
602                        
603                        tooltip.addPara("Stockpiles are limited to %s the monthly rate.", opad,
604                                        Misc.getHighlightColor(), "" + (int)STOCKPILE_MAX_MONTHS + Strings.X);
605                } else {
606                        tooltip.addPara("No stockpiling.", opad);
607                }       
608        }
609        
610}
611
612
613
614