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