001/**
002 * 
003 */
004package com.fs.starfarer.api.impl.campaign.shared;
005
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.HashMap;
009import java.util.LinkedHashMap;
010import java.util.List;
011import java.util.Map;
012
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.campaign.CargoAPI;
015import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType;
016import com.fs.starfarer.api.campaign.CargoStackAPI;
017import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
018import com.fs.starfarer.api.campaign.PlayerMarketTransaction.ShipSaleInfo;
019import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI;
020import com.fs.starfarer.api.campaign.econ.MarketAPI;
021import com.fs.starfarer.api.campaign.econ.SubmarketAPI;
022import com.fs.starfarer.api.combat.MutableStatWithTempMods;
023import com.fs.starfarer.api.impl.campaign.ids.Commodities;
024import com.fs.starfarer.api.impl.campaign.ids.Conditions;
025import com.fs.starfarer.api.impl.campaign.ids.Stats;
026import com.fs.starfarer.api.impl.campaign.submarkets.BaseSubmarketPlugin.ShipSalesData;
027import com.fs.starfarer.api.util.IntervalUtil;
028import com.fs.starfarer.api.util.Misc;
029
030public class PlayerTradeDataForSubmarket {
031        
032        private Map<String, MutableStatWithTempMods> tx = new HashMap<String, MutableStatWithTempMods>();
033        
034        private CargoAPI playerBought, playerSold;
035        private float accumulatedPlayerTradeValueForPositive = 0;
036        private float accumulatedPlayerTradeValueForNegative = 0;
037        private float totalPlayerTradeValue = 0;
038        
039        private IntervalUtil tracker;
040        
041        private Map<String, ShipSalesData> playerBoughtShips = new LinkedHashMap<String, ShipSalesData>();
042        private Map<String, ShipSalesData> playerSoldShips = new LinkedHashMap<String, ShipSalesData>();
043        private MarketAPI market;
044        private SubmarketAPI submarket;
045        
046        public PlayerTradeDataForSubmarket(SubmarketAPI submarket) {
047                this.market = submarket.getMarket();
048                this.submarket = submarket;
049                
050                playerBought = Global.getFactory().createCargo(true);
051                playerSold = Global.getFactory().createCargo(true);
052                
053                tracker = Misc.createEconIntervalTracker();
054        }
055        
056        protected Object readResolve() {
057                if (tx == null) {
058                        tx = new HashMap<String, MutableStatWithTempMods>();
059                }
060                return this;
061        }
062        
063        public static String getTXId(CargoStackAPI stack) {
064                return stack.getType().name() + "_" + stack.getData().toString();
065        }
066        
067        public static String getTXId(ShipSalesData data) {
068                return data.getVariantId();
069        }
070        
071        public MutableStatWithTempMods getStat(String id) {
072                MutableStatWithTempMods stat = tx.get(id);
073                if (stat == null) {
074                        stat = new MutableStatWithTempMods(0);
075                        tx.put(id, stat);
076                }
077                return stat;
078        }
079        
080        public void advance(float days) {
081                tracker.advance(days);
082                if (tracker.intervalElapsed()) {
083                        //float factor = 1f - Misc.getGenericRollingAverageFactor();
084                        float factor = 0.5f;
085                        
086                        for (CargoStackAPI stack : playerBought.getStacksCopy()) {
087                                stack.setSize(stack.getSize() * factor);
088                                if (stack.getSize() < 10) {
089                                        stack.setSize(0);
090                                }
091                        }
092                        playerBought.removeEmptyStacks();
093                        for (CargoStackAPI stack : playerSold.getStacksCopy()) {
094                                stack.setSize(stack.getSize() * factor);
095                                if (stack.getSize() < 10) {
096                                        stack.setSize(0);
097                                }
098                        }
099                        playerSold.removeEmptyStacks();
100                        
101                        //System.out.println("Ships: " + (playerBoughtShips.size() + playerSoldShips.size()));
102//                      if (playerSold.getMothballedShips() != null) {
103//                              System.out.println("Ships: " + playerSold.getMothballedShips().getNumMembers());
104//                      }
105                        
106                        List<String> remove = new ArrayList<String>();
107                        for (ShipSalesData data : playerBoughtShips.values()) {
108                                data.setNumShips(data.getNumShips() * factor);
109                                data.setTotalValue(data.getTotalValue() * factor);
110                                if (data.getNumShips() < 0.2f) remove.add(data.getVariantId());
111                        }
112                        for (String vid : remove) {
113                                playerBoughtShips.remove(vid);
114                        }
115                        remove.clear();
116                        
117                        for (ShipSalesData data : playerSoldShips.values()) {
118                                data.setNumShips(data.getNumShips() * factor);
119                                data.setTotalValue(data.getTotalValue() * factor);
120                                if (data.getNumShips() < 0.2f) remove.add(data.getVariantId());
121                        }
122                        for (String vid : remove) {
123                                playerSoldShips.remove(vid);
124                        }
125                        remove.clear();
126                        
127                        
128                        accumulatedPlayerTradeValueForPositive *= factor;
129                        accumulatedPlayerTradeValueForNegative *= factor;
130                        totalPlayerTradeValue *= factor;
131                }
132        }
133
134        
135        public void addTransaction(PlayerMarketTransaction transaction) {
136                for (CargoStackAPI stack : transaction.getSold().getStacksCopy()) {
137                        addToTrackedPlayerSold(stack);
138                }
139                for (CargoStackAPI stack : transaction.getBought().getStacksCopy()) {
140                        addToTrackedPlayerBought(stack);
141                }
142                for (ShipSaleInfo info : transaction.getShipsBought()) {
143                        addToTrackedPlayerBought(info);
144                }
145                for (ShipSaleInfo info : transaction.getShipsSold()) {
146                        addToTrackedPlayerSold(info);
147                }
148        }
149        
150        private float getTransponderMult() {
151                boolean tOn = Global.getSector().getPlayerFleet().isTransponderOn();
152                float mult = 1f;
153                if (!tOn) {
154                        //mult = 0.25f;
155                        mult = Global.getSettings().getFloat("transponderOffMarketAwarenessMult");
156                }
157                return mult;
158        }
159        
160        public void addToTrackedPlayerBought(ShipSaleInfo info) {
161                String vid = info.getMember().getVariant().getHullSpec().getHullId();
162                ShipSalesData bought = getBoughtShipData(vid);
163                ShipSalesData sold = getSoldShipData(vid);
164                
165                float playerImpactMult = Global.getSettings().getFloat("economyPlayerTradeImpactMult");
166                
167                float fractionBought = 1f;
168                if (sold.getNumShips() > 0) {
169                        fractionBought = Math.max(0, fractionBought - sold.getNumShips());
170                        sold.setNumShips(sold.getNumShips() - 1f);
171                        sold.setTotalValue(sold.getTotalValue() - (1f - fractionBought) * info.getPrice() * playerImpactMult);
172                        if (sold.getNumShips() < 0) sold.setNumShips(0);
173                        if (sold.getTotalValue() < 0) sold.setTotalValue(0);
174                }
175                
176                if (fractionBought > 0) {
177                        accumulatedPlayerTradeValueForPositive += info.getPrice() * playerImpactMult * fractionBought * getTransponderMult();
178                        accumulatedPlayerTradeValueForNegative += info.getPrice() * playerImpactMult * fractionBought * getTransponderMult();
179                        totalPlayerTradeValue += info.getPrice() * playerImpactMult * fractionBought * getTransponderMult();
180                        bought.setNumShips(bought.getNumShips() + 1f * fractionBought);
181                        bought.setTotalValue(bought.getTotalValue() + info.getPrice() * playerImpactMult * fractionBought);
182                }
183        }
184        
185        public void addToTrackedPlayerSold(ShipSaleInfo info) {
186                String vid = info.getMember().getVariant().getHullSpec().getHullId();
187                ShipSalesData bought = getBoughtShipData(vid);
188                ShipSalesData sold = getSoldShipData(vid);
189                
190                float playerImpactMult = Global.getSettings().getFloat("economyPlayerTradeImpactMult");
191                
192                float fractionSold = 1f;
193                if (bought.getNumShips() > 0) {
194                        fractionSold = Math.max(0, fractionSold - bought.getNumShips());
195                        bought.setNumShips(bought.getNumShips() - 1f);
196                        bought.setTotalValue(bought.getTotalValue() - (1f - fractionSold) * info.getPrice() * playerImpactMult);
197                        if (bought.getNumShips() < 0) bought.setNumShips(0);
198                        if (bought.getTotalValue() < 0) bought.setTotalValue(0);
199                }
200                
201                if (fractionSold > 0) {
202                        accumulatedPlayerTradeValueForPositive += info.getPrice() * playerImpactMult * fractionSold * getTransponderMult();
203                        accumulatedPlayerTradeValueForNegative += info.getPrice() * playerImpactMult * fractionSold * getTransponderMult();
204                        totalPlayerTradeValue += info.getPrice() * playerImpactMult * fractionSold * getTransponderMult();
205                        sold.setNumShips(sold.getNumShips() + 1f * fractionSold);
206                        sold.setTotalValue(sold.getTotalValue() + info.getPrice() * playerImpactMult * fractionSold);
207                }
208        }
209        
210        public void addToTrackedPlayerBought(CargoStackAPI stack) {
211                float qty = stack.getSize() - playerSold.getQuantity(stack.getType(), stack.getData());
212                float impact = computeImpactOfHavingAlreadyBought(market, 
213                                stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
214                accumulatedPlayerTradeValueForPositive += impact * getTransponderMult();
215                accumulatedPlayerTradeValueForNegative += impact * getTransponderMult();
216                totalPlayerTradeValue += impact * getTransponderMult();
217                
218                playerBought.addItems(stack.getType(), stack.getData(), stack.getSize());
219                playerSold.removeItems(stack.getType(), stack.getData(), stack.getSize());
220                
221                if (qty >= 1 && stack.getType() == CargoItemType.RESOURCES) {
222                        PlayerTradeProfitabilityData data = SharedData.getData().getPlayerActivityTracker().getProfitabilityData();
223                        float price = computePriceOfHavingAlreadyBought(market, 
224                                                stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
225                        data.reportNetBought((String)stack.getData(), qty, price);
226                }
227        }
228
229        public void addToTrackedPlayerSold(CargoStackAPI stack) {
230                addToTrackedPlayerSold(stack, -1);
231        }
232        
233        public void addToTrackedPlayerSold(CargoStackAPI stack, float totalPriceOverride) {
234                float qty = stack.getSize() - playerBought.getQuantity(stack.getType(), stack.getData());
235                float impact = computeImpactOfHavingAlreadySold(market, 
236                                stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
237                
238                playerSold.addItems(stack.getType(), stack.getData(), stack.getSize());
239                playerBought.removeItems(stack.getType(), stack.getData(), stack.getSize());
240                
241                float overrideImpactMult = 1f;
242                if (qty >= 1 && stack.getType() == CargoItemType.RESOURCES) {
243                        PlayerTradeProfitabilityData data = SharedData.getData().getPlayerActivityTracker().getProfitabilityData();
244                        float price = computePriceOfHavingAlreadySold(market, 
245                                                stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
246                        if (totalPriceOverride > 0) {
247                                if (price > 0) {
248                                        overrideImpactMult = totalPriceOverride / price;
249                                }
250                                price = totalPriceOverride;
251                        }
252                        
253//                      String multId = Stats.getPlayerTradeImpactMultId(commodityId);
254//                      val *= market.getStats().getDynamic().getValue(multId);
255//                      multId = Stats.getPlayerSellImpactMultId(commodityId);
256//                      val *= market.getStats().getDynamic().getValue(multId);
257                        
258                        data.reportNetSold((String)stack.getData(), qty, price);
259                }
260                
261                accumulatedPlayerTradeValueForPositive += impact * getTransponderMult() * overrideImpactMult;
262                accumulatedPlayerTradeValueForNegative += impact * getTransponderMult() * overrideImpactMult;
263                totalPlayerTradeValue += impact * getTransponderMult() * overrideImpactMult;
264        }
265        
266        public static float computeImpactOfHavingAlreadySold(MarketAPI market, CargoItemType type, Object data, float baseValue, float qty) {
267                if (qty < 1) return 0;
268                
269                float playerImpactMult = Global.getSettings().getFloat("economyPlayerTradeImpactMult");
270                float illegalImpactMult = Global.getSettings().getFloat("economyPlayerSellIllegalImpactMult");
271                float militaryImpactMult = Global.getSettings().getFloat("economyPlayerSellMilitaryImpactMult");
272                
273                if (type == CargoItemType.RESOURCES) {
274                        String commodityId = (String) data;
275                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
276                        float val = market.getDemandPriceAssumingExistingTransaction(commodityId, qty, -qty * com.getUtilityOnMarket(), true);
277                        if (!market.hasCondition(Conditions.FREE_PORT) &&
278                                        market.isIllegal(commodityId)) {
279                                val *= illegalImpactMult;
280                        }
281                        val *= playerImpactMult;
282                        
283                        String multId = Stats.getPlayerTradeRepImpactMultId(commodityId);
284                        val *= market.getStats().getDynamic().getValue(multId);
285                        multId = Stats.getPlayerSellRepImpactMultId(commodityId);
286                        val *= market.getStats().getDynamic().getValue(multId);
287                        
288                        if (com.getCommodity().hasTag(Commodities.TAG_MILITARY)) {
289                                val *= militaryImpactMult;
290                        }
291                        return val;
292                } else {
293                        float val = (float) baseValue * qty * playerImpactMult;
294                        return val;
295                }
296        }
297        
298        public static float computePriceOfHavingAlreadySold(MarketAPI market, CargoItemType type, Object data, float baseValue, float qty) {
299                if (qty < 1) return 0;
300                
301                if (type == CargoItemType.RESOURCES) {
302                        String commodityId = (String) data;
303                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
304                        float val = market.getDemandPriceAssumingExistingTransaction(commodityId, qty, -qty * com.getUtilityOnMarket(), true);
305                        return val;
306                } else {
307                        float val = (float) baseValue * qty;
308                        return val;
309                }
310        }
311        
312        public static float computeImpactOfHavingAlreadyBought(MarketAPI market, CargoItemType type, Object data, float baseValue, float qty) {
313                if (qty < 1) return 0;
314                
315                float playerImpactMult = Global.getSettings().getFloat("economyPlayerTradeImpactMult");
316//              market.getSupplyPriceAssumingExistingTransaction(commodityId, qty, qty * com.getUtilityOnMarket(), true);
317//              market.getSupplyPriceAssumingExistingTransaction(commodityId, qty, 1, true);
318                if (type == CargoItemType.RESOURCES) {
319                        String commodityId = (String) data;
320                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
321                        float val = market.getSupplyPriceAssumingExistingTransaction(commodityId, qty, qty * com.getUtilityOnMarket(), true);
322                        val *= playerImpactMult;
323                        
324                        String multId = Stats.getPlayerTradeRepImpactMultId(commodityId);
325                        val *= market.getStats().getDynamic().getValue(multId);
326                        multId = Stats.getPlayerBuyRepImpactMultId(commodityId);
327                        val *= market.getStats().getDynamic().getValue(multId);
328                        return val;
329                } else {
330                        float val = (float) baseValue * qty * playerImpactMult;
331                        return val;
332                }
333        }
334        
335        public static float computePriceOfHavingAlreadyBought(MarketAPI market, CargoItemType type, Object data, float baseValue, float qty) {
336                if (qty < 1) return 0;
337                
338                if (type == CargoItemType.RESOURCES) {
339                        String commodityId = (String) data;
340                        CommodityOnMarketAPI com = market.getCommodityData(commodityId);
341                        float val = market.getSupplyPriceAssumingExistingTransaction(commodityId, qty, qty * com.getUtilityOnMarket(), true);
342                        return val;
343                } else {
344                        float val = (float) baseValue * qty;
345                        return val;
346                }
347        }
348
349        
350        public float getTotalPlayerTradeValue() {
351                return totalPlayerTradeValue;
352        }
353
354        public void setTotalPlayerTradeValue(float totalPlayerTradeValue) {
355                this.totalPlayerTradeValue = totalPlayerTradeValue;
356        }
357
358        public CargoAPI getRecentPlayerBought() {
359                return playerBought;
360        }
361
362        public CargoAPI getRecentPlayerSold() {
363                return playerSold;
364        }
365
366        public float getAccumulatedPlayerTradeValueForPositive() {
367                return accumulatedPlayerTradeValueForPositive;
368        }
369        
370        public void setAccumulatedPlayerTradeValueForPositive(float accumulatedPlayerTradeValue) {
371                this.accumulatedPlayerTradeValueForPositive = accumulatedPlayerTradeValue;
372        }
373
374        public float getAccumulatedPlayerTradeValueForNegative() {
375                return accumulatedPlayerTradeValueForNegative;
376        }
377
378        public void setAccumulatedPlayerTradeValueForNegative(
379                        float accumulatedPlayerTradeValueForNegative) {
380                this.accumulatedPlayerTradeValueForNegative = accumulatedPlayerTradeValueForNegative;
381        }
382
383        public IntervalUtil getTracker() {
384                return tracker;
385        }
386
387        public Collection<ShipSalesData> getRecentlyPlayerBoughtShips() {
388                return playerBoughtShips.values();
389        }
390
391        public Collection<ShipSalesData> getRecentlyPlayerSoldShips() {
392                return playerSoldShips.values();
393        }
394
395        public MarketAPI getMarket() {
396                return market;
397        }
398
399        public SubmarketAPI getSubmarket() {
400                return submarket;
401        }
402        
403        
404        protected ShipSalesData getSoldShipData(String vid) {
405                ShipSalesData sold = playerSoldShips.get(vid);
406                if (sold == null) {
407                        sold = new ShipSalesData();
408                        sold.setVariantId(vid);
409                        playerSoldShips.put(vid, sold);
410                }
411                return sold;
412        }
413        
414        protected ShipSalesData getBoughtShipData(String vid) {
415                ShipSalesData bought = playerBoughtShips.get(vid);
416                if (bought == null) {
417                        bought = new ShipSalesData();
418                        bought.setVariantId(vid);
419                        playerBoughtShips.put(vid, bought);
420                }
421                return bought;
422        }
423        
424        public float getRecentBaseTradeValueImpact() {
425//              float playerImpactMult = Global.getSettings().getFloat("economyPlayerTradeImpactMult");
426//              float illegalImpactMult = Global.getSettings().getFloat("economyPlayerSellIllegalImpactMult");
427                
428                float total = 0;
429                for (CargoStackAPI stack : playerBought.getStacksCopy()) {
430                        float qty = stack.getSize();
431//                      if (qty < 1) continue;
432//                      float val = (float) stack.getBaseValuePerUnit() * qty * playerImpactMult;
433                        float val = computeImpactOfHavingAlreadyBought(market, 
434                                                                        stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
435                        total += val;
436                }
437                for (CargoStackAPI stack : playerSold.getStacksCopy()) {
438                        float qty = stack.getSize();
439                        if (qty < 1) continue;
440//                      float val = (float) stack.getBaseValuePerUnit() * qty * playerImpactMult;
441//                      if (!market.hasCondition(Conditions.FREE_PORT) &&
442//                                      stack.isCommodityStack() && market.isIllegal(stack.getCommodityId())) {
443//                              val *= illegalImpactMult;
444//                      }
445                        float val = computeImpactOfHavingAlreadySold(market, 
446                                                                                stack.getType(), stack.getData(), stack.getBaseValuePerUnit(), qty);
447                        total += val;
448                }
449                return total;
450        }
451
452        public void setSubmarket(SubmarketAPI submarket) {
453                this.submarket = submarket;
454        }
455        
456}
457
458
459
460
461
462