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