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