001package com.fs.starfarer.api.impl.campaign.missions.cb; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.LinkedHashSet; 006import java.util.List; 007import java.util.Map; 008import java.util.Set; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.BattleAPI; 012import com.fs.starfarer.api.campaign.CampaignEventListener.FleetDespawnReason; 013import com.fs.starfarer.api.campaign.CampaignFleetAPI; 014import com.fs.starfarer.api.campaign.InteractionDialogAPI; 015import com.fs.starfarer.api.campaign.PlanetAPI; 016import com.fs.starfarer.api.campaign.SectorEntityToken; 017import com.fs.starfarer.api.campaign.econ.MarketAPI; 018import com.fs.starfarer.api.campaign.listeners.FleetEventListener; 019import com.fs.starfarer.api.campaign.rules.MemKeys; 020import com.fs.starfarer.api.campaign.rules.MemoryAPI; 021import com.fs.starfarer.api.characters.PersonAPI; 022import com.fs.starfarer.api.impl.campaign.ids.Tags; 023import com.fs.starfarer.api.impl.campaign.missions.cb.CustomBountyCreator.CustomBountyData; 024import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithBarEvent; 025import com.fs.starfarer.api.impl.campaign.rulecmd.FireBest; 026import com.fs.starfarer.api.ui.SectorMapAPI; 027import com.fs.starfarer.api.ui.TooltipMakerAPI; 028import com.fs.starfarer.api.util.Misc; 029import com.fs.starfarer.api.util.Misc.Token; 030import com.fs.starfarer.api.util.WeightedRandomPicker; 031 032public class BaseCustomBounty extends HubMissionWithBarEvent implements FleetEventListener { 033 034 public static int NUM_TO_TRACK_FOR_DIFFICULTY = 3; 035 036 public static enum Stage { 037 BOUNTY, 038 COMPLETED, 039 FAILED, 040 FAILED_NO_PENALTY, 041 } 042 043 public static enum DifficultyChoice { 044 LOW, 045 NORMAL, 046 HIGH, 047 } 048 049 public static class AggregateBountyData { 050 public List<Integer> completedDifficulty = new ArrayList<Integer>(); 051 } 052 053 public String getAggregateDataKey() { 054 return "$" + getMissionId() + "_aggregateData"; 055 } 056 public AggregateBountyData getAggregateData() { 057 //MemoryAPI memory = getPerson().getMemoryWithoutUpdate(); 058 MemoryAPI memory = Global.getSector().getMemoryWithoutUpdate(); 059 AggregateBountyData data = (AggregateBountyData) memory.get(getAggregateDataKey()); 060 if (data == null) { 061 data = new AggregateBountyData(); 062 memory.set(getAggregateDataKey(), data); 063 } 064 return data; 065 } 066 067 068 //protected FactionAPI faction; 069 protected PersonAPI target; 070 protected CustomBountyCreator creator; 071 protected CustomBountyCreator creatorLow; 072 protected CustomBountyCreator creatorNormal; 073 protected CustomBountyCreator creatorHigh; 074 protected CustomBountyData data; 075 protected CustomBountyData dataLow; 076 protected CustomBountyData dataNormal; 077 protected CustomBountyData dataHigh; 078 079 public List<CustomBountyCreator> getCreators() { 080 return new ArrayList<CustomBountyCreator>(); 081 } 082 083 protected int pickDifficulty(DifficultyChoice choice) { 084 //if (true) return 6; 085 if (difficultyOverride != null) return difficultyOverride; 086 087 AggregateBountyData d = getAggregateData(); 088 089 float total = 0f; 090 float count = 0f; 091 for (Integer diff : d.completedDifficulty) { 092 total += diff; 093 count++; 094 } 095 096 float difficulty = total; 097 if (count > 0) { 098 difficulty /= Math.max(count, NUM_TO_TRACK_FOR_DIFFICULTY); 099 difficulty = (int) difficulty; 100 } 101 102 int min = CustomBountyCreator.MIN_DIFFICULTY; 103 int max = CustomBountyCreator.MAX_DIFFICULTY; 104 105 switch (choice) { 106 case LOW: 107 min = 0; 108 max = max - 3; 109 difficulty = Math.min(difficulty - 3f, (int)(difficulty / 2f)); 110 break; 111 case NORMAL: 112 min = min + 1; 113 max = max - 1; 114 difficulty = difficulty + 1; 115 break; 116 case HIGH: 117 min = 4; 118 difficulty = difficulty + 3; 119 break; 120 } 121 122 int result = (int)Math.round(difficulty); 123 //result += genRandom.nextInt(2); 124 125 if (result < min) { 126 result = min; 127 } 128 if (result > max) { 129 result = max; 130 } 131 return result; 132 } 133 134 protected CustomBountyCreator pickCreator(int difficulty, DifficultyChoice choice) { 135 //if (true) return new CBRemnantPlus(); 136 //if (true) return new CBMercUW(); 137 if (creatorOverride != null) { 138 try { 139 return (CustomBountyCreator) creatorOverride.newInstance(); 140 } catch (Throwable t) { 141 throw new RuntimeException(t); 142 } 143 } 144 145 WeightedRandomPicker<CustomBountyCreator> picker = new WeightedRandomPicker<CustomBountyCreator>(genRandom); 146 147 float quality = getQuality(); 148 float maxDiff = CustomBountyCreator.MAX_DIFFICULTY; 149 150 for (CustomBountyCreator curr : getCreators()) { 151 if (curr.getMinDifficulty() > difficulty) continue; 152 if (curr.getMaxDifficulty() < difficulty) continue; 153 154 155 if (choice == DifficultyChoice.HIGH) { 156 int threshold = CBStats.getThresholdNotHigh(getClass()); 157 if (difficulty >= threshold) continue; 158 } 159 if (choice == DifficultyChoice.NORMAL) { 160 int threshold = CBStats.getThresholdNotNormal(getClass()); 161 if (difficulty >= threshold) continue; 162 } 163 164 float probToSkip = (1.1f - quality) * (float) curr.getMinDifficulty() / maxDiff; 165 if (rollProbability(probToSkip)) continue; 166 167 picker.add(curr, curr.getFrequency(this, difficulty)); 168 } 169 170 return picker.pick(); 171 } 172 173 protected void createBarGiver(MarketAPI createdAt) { 174 175 } 176 177 protected transient Class creatorOverride; 178 protected transient Integer difficultyOverride; 179 public void setTestMode(Class c, int difficulty) { 180 genRandom = Misc.random; 181 //genRandom = new Random(3454364663L); 182 difficultyOverride = difficulty; 183 creatorOverride = c; 184 } 185 186 187 @Override 188 protected boolean create(MarketAPI createdAt, boolean barEvent) { 189 //genRandom = Misc.random; 190 191// setTestMode(CBPirate.class, 10); 192// setTestMode(CBPather.class, 10); 193// setTestMode(CBDeserter.class, 10); 194// setTestMode(CBDerelict.class, 10); 195// setTestMode(CBMerc.class, 10); 196// setTestMode(CBRemnant.class, 10); 197// setTestMode(CBRemnantPlus.class, 10); 198// setTestMode(CBRemnantStation.class, 10); 199// setTestMode(CBTrader.class, 10); 200// setTestMode(CBPatrol.class, 10); 201// setTestMode(CBMercUW.class, 10); 202// setTestMode(CBEnemyStation.class, 10); 203 204 if (barEvent) { 205 createBarGiver(createdAt); 206 } 207 208 PersonAPI person = getPerson(); 209 if (person == null) return false; 210 211 String id = getMissionId(); 212 if (!setPersonMissionRef(person, "$" + id + "_ref")) { 213 return false; 214 } 215 216 setStartingStage(Stage.BOUNTY); 217 setSuccessStage(Stage.COMPLETED); 218 setFailureStage(Stage.FAILED); 219 addNoPenaltyFailureStages(Stage.FAILED_NO_PENALTY); 220 //setNoAbandon(); 221 222 223 connectWithMemoryFlag(Stage.BOUNTY, Stage.COMPLETED, person, "$" + id + "_completed"); 224 connectWithMemoryFlag(Stage.BOUNTY, Stage.FAILED, person, "$" + id + "_failed"); 225 226 addTag(Tags.INTEL_BOUNTY); 227 228 229 int dLow = pickDifficulty(DifficultyChoice.LOW); 230 creatorLow = pickCreator(dLow, DifficultyChoice.LOW); 231 //creatorLow = new CBDerelict(); 232 if (creatorLow != null) { 233 dataLow = creatorLow.createBounty(createdAt, this, dLow, Stage.BOUNTY); 234 } 235 if (dataLow == null || dataLow.fleet == null) return false; 236 237 int dNormal = pickDifficulty(DifficultyChoice.NORMAL); 238 creatorNormal = pickCreator(dNormal, DifficultyChoice.NORMAL); 239 if (creatorNormal != null) { 240 dataNormal = creatorNormal.createBounty(createdAt, this, dNormal, Stage.BOUNTY); 241 } 242 if (dataNormal == null || dataNormal.fleet == null) return false; 243 244 int dHigh = pickDifficulty(DifficultyChoice.HIGH); 245 creatorHigh = pickCreator(dHigh, DifficultyChoice.HIGH); 246 if (creatorHigh != null) { 247 dataHigh = creatorHigh.createBounty(createdAt, this, dHigh, Stage.BOUNTY); 248 } 249 //getPerson().getNameString() getPerson().getMarket(); 250 if (dataHigh == null || dataHigh.fleet == null) return false; 251 252 253 return true; 254 } 255 256 protected void updateInteractionDataImpl() { 257 String id = getMissionId(); 258 set("$" + id + "_barEvent", isBarEvent()); 259 set("$" + id + "_manOrWoman", getPerson().getManOrWoman()); 260 set("$" + id + "_reward", Misc.getWithDGS(getCreditsReward())); 261 set("$bcb_barEvent", isBarEvent()); 262 set("$bcb_manOrWoman", getPerson().getManOrWoman()); 263 set("$bcb_reward", Misc.getWithDGS(getCreditsReward())); 264 265 if (showData != null && showCreator != null) { 266 showCreator.updateInteractionData(this, showData); 267 268 set("$" + id + "_difficultyNum", showData.difficulty); 269 set("$bcb_difficultyNum", showData.difficulty); 270 271 if (showData.system != null) { 272 set("$" + id + "_systemName", showData.system.getNameWithLowercaseType()); 273 set("$" + id + "_dist", getDistanceLY(showData.system.getCenter())); 274 set("$bcb_systemName", showData.system.getNameWithLowercaseType()); 275 set("$bcb_dist", getDistanceLY(showData.system.getCenter())); 276 } 277 if (showData.market != null) { 278 set("$" + id + "_targetMarketName", showData.market.getName()); 279 set("$bcb_targetMarketName", showData.market.getName()); 280 set("$" + id + "_targetMarketOnOrAt", showData.market.getOnOrAt()); 281 set("$bcb_targetMarketOnOrAt", showData.market.getOnOrAt()); 282 } 283 set("$" + id + "_days", "" + (int) showCreator.getBountyDays()); 284 set("$bcb_days", "" + (int) showCreator.getBountyDays()); 285 286 if (showData.fleet != null) { 287 PersonAPI p = showData.fleet.getCommander(); 288 set("$" + id + "_targetHeOrShe", p.getHeOrShe()); 289 set("$" + id + "_targetHisOrHer", p.getHisOrHer()); 290 set("$" + id + "_targetHimOrHer", p.getHimOrHer()); 291 set("$" + id + "_targetName", p.getNameString()); 292 set("$bcb_targetHeOrShe", p.getHeOrShe()); 293 set("$bcb_targetHisOrHer", p.getHisOrHer()); 294 set("$bcb_targetHimOrHer", p.getHimOrHer()); 295 set("$bcb_targetName", p.getNameString()); 296 297 set("$" + id + "_TargetHeOrShe", Misc.ucFirst(p.getHeOrShe())); 298 set("$" + id + "_TargetHisOrHer", Misc.ucFirst(p.getHisOrHer())); 299 set("$" + id + "_TargetHimOrHer", Misc.ucFirst(p.getHimOrHer())); 300 set("$bcb_TargetHeOrShe", Misc.ucFirst(p.getHeOrShe())); 301 set("$bcb_TargetHisOrHer", Misc.ucFirst(p.getHisOrHer())); 302 set("$bcb_TargetHimOrHer", Misc.ucFirst(p.getHimOrHer())); 303 304 set("$bcb_fleetName", showData.fleet.getName()); 305 } 306 } 307 } 308 309 310 protected transient CustomBountyCreator showCreator; 311 protected transient CustomBountyData showData; 312 @Override 313 protected boolean callAction(String action, String ruleId, InteractionDialogAPI dialog, List<Token> params, 314 Map<String, MemoryAPI> memoryMap) { 315 316 if ("showBountyDetail".equals(action)) { 317 String id = getMissionId(); 318 MemoryAPI memory = memoryMap.get(MemKeys.LOCAL); 319 DifficultyChoice difficulty = (DifficultyChoice) Enum.valueOf(DifficultyChoice.class, memory.getString("$" + id + "_difficulty")); 320 showCreator = creatorLow; 321 showData = dataLow; 322 switch (difficulty) { 323 case LOW: 324 showCreator = creatorLow; 325 showData = dataLow; 326 break; 327 case NORMAL: 328 showCreator = creatorNormal; 329 showData = dataNormal; 330 break; 331 case HIGH: 332 showCreator = creatorHigh; 333 showData = dataHigh; 334 break; 335 } 336 337 setCreditRewardApplyRelMult(showData.baseReward); 338 339 updateInteractionData(dialog, memoryMap); 340 String trigger = showCreator.getId() + "OfferDesc"; 341 FireBest.fire(null, dialog, memoryMap, trigger); 342 343 if (showData != null && showData.system != null) { 344 String icon = showCreator.getIconName(); 345 if (icon == null) icon = getIcon(); 346 String text = null; 347 Set<String> tags = new LinkedHashSet<String>(); 348 tags.add(Tags.INTEL_MISSIONS); 349 Color color = Misc.getBasePlayerColor(); 350 351 if (showData.system.getCenter() != null && showData.system.getCenter().getMarket() != null) { 352 color = showData.system.getCenter().getMarket().getTextColorForFactionOrPlanet(); 353 } else if (showData.system.getCenter() instanceof PlanetAPI) { 354 color = Misc.setAlpha(((PlanetAPI)showData.system.getCenter()).getSpec().getIconColor(), 255); 355 color = Misc.setBrightness(color, 235); 356 } 357 358 dialog.getVisualPanel().showMapMarker(showData.system.getCenter(), 359 "Target: " + showData.system.getNameWithLowercaseTypeShort(), color, 360 true, icon, text, tags); 361 } 362 return true; 363 } else if ("showBountyAssessment".equals(action) && showCreator != null) { 364 showCreator.addIntelAssessment(dialog.getTextPanel(), this, showData); 365 return true; 366 } 367 368 369 return super.callAction(action, ruleId, dialog, params, memoryMap); 370 } 371 372 373 @Override 374 public void accept(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 375 String id = getMissionId(); 376 MemoryAPI memory = memoryMap.get(MemKeys.LOCAL); 377 DifficultyChoice difficulty = (DifficultyChoice) Enum.valueOf(DifficultyChoice.class, memory.getString("$" + id + "_difficulty")); 378 List<Abortable> abort = new ArrayList<Abortable>(); 379 switch (difficulty) { 380 case LOW: 381 creator = creatorLow; 382 data = dataLow; 383 abort.addAll(dataNormal.abortWhenOtherVersionAccepted); 384 abort.addAll(dataHigh.abortWhenOtherVersionAccepted); 385 break; 386 case NORMAL: 387 creator = creatorNormal; 388 data = dataNormal; 389 abort.addAll(dataLow.abortWhenOtherVersionAccepted); 390 abort.addAll(dataHigh.abortWhenOtherVersionAccepted); 391 break; 392 case HIGH: 393 creator = creatorHigh; 394 data = dataHigh; 395 abort.addAll(dataLow.abortWhenOtherVersionAccepted); 396 abort.addAll(dataNormal.abortWhenOtherVersionAccepted); 397 break; 398 } 399 400 for (Abortable curr : abort) { 401 curr.abort(this, false); 402 } 403 404 creatorLow = creatorNormal = creatorHigh = null; 405 dataLow = dataNormal = dataHigh = null; 406 407 MarketAPI createdAt = getPerson().getMarket(); 408 if (createdAt == null) createdAt = dialog.getInteractionTarget().getMarket(); 409 if (creator.getIconName() != null) { 410 setIconName(creator.getIconName()); 411 } 412 creator.notifyAccepted(createdAt, this, data); 413 414 target = data.fleet.getCommander(); 415 data.fleet.addEventListener(this); 416 makeImportant(data.fleet, "$" + id + "_target", Stage.BOUNTY); 417 418 if (data.fleet.isHidden()) { 419 MarketAPI market = Misc.getStationMarket(data.fleet); 420 if (market != null) { 421 SectorEntityToken station = Misc.getStationEntity(market, data.fleet); 422 if (station != null) { 423 makeImportant(station, "$" + id + "_target", Stage.BOUNTY); 424 } 425 } 426 } 427 428 if (!data.fleet.getFaction().isNeutralFaction()) { 429 addTag(data.fleet.getFaction().getId()); 430 } 431 432 if (creator.getBountyDays() > 0) { 433 setTimeLimit(Stage.FAILED, creator.getBountyDays(), creator.getSystemWithNoTimeLimit(data)); 434 } 435 //setTimeLimit(Stage.FAILED, 3, creator.getSystemWithNoTimeLimit(data)); 436 setCreditRewardApplyRelMult(data.baseReward); 437 setRepRewardPerson(data.repPerson); 438 setRepRewardFaction(data.repFaction); 439 440 super.accept(dialog, memoryMap); 441 } 442 443 444 @Override 445 public void acceptImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 446 447 } 448 449 450 451 @Override 452 public void addDescriptionForNonEndStage(TooltipMakerAPI info, float width, float height) { 453 float opad = 10f; 454 float pad = 3f; 455 Color h = Misc.getHighlightColor(); 456 if (currentStage == Stage.BOUNTY) { 457 if (currentStage == Stage.BOUNTY) { 458 creator.addTargetLocationAndDescription(info, width, height, this, data); 459 } 460 creator.addFleetDescription(info, width, height, this, data); 461 } 462 } 463 464 @Override 465 public boolean addNextStepText(TooltipMakerAPI info, Color tc, float pad) { 466 Color h = Misc.getHighlightColor(); 467 if (currentStage == Stage.BOUNTY) { 468 if (data.system != null) { 469 creator.addTargetLocationAndDescriptionBulletPoint(info, tc, pad, this, data); 470 //info.addPara("Target is in the " + data.system.getNameWithLowercaseTypeShort() + "", tc, pad); 471 return true; 472 } 473 } 474 return false; 475 } 476 477 478 @Override 479 public SectorEntityToken getMapLocation(SectorMapAPI map) { 480 return super.getMapLocation(map); 481 } 482 483 @Override 484 protected void notifyEnding() { 485 super.notifyEnding(); 486 } 487 488 489 @Override 490 protected void endSuccessImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 491 creator.notifyCompleted(this, data); 492 493 AggregateBountyData d = getAggregateData(); 494 d.completedDifficulty.add(data.difficulty); 495 while (d.completedDifficulty.size() > NUM_TO_TRACK_FOR_DIFFICULTY) { 496 d.completedDifficulty.remove(0); 497 } 498 } 499 500 @Override 501 protected void endFailureImpl(InteractionDialogAPI dialog, Map<String, MemoryAPI> memoryMap) { 502 creator.notifyFailed(this, data); 503 504 AggregateBountyData d = getAggregateData(); 505 d.completedDifficulty.add(0); 506 while (d.completedDifficulty.size() > NUM_TO_TRACK_FOR_DIFFICULTY) { 507 d.completedDifficulty.remove(0); 508 } 509 510 } 511 public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) { 512 if (isDone() || result != null) return; 513 514 // also credit the player if they're in the same location as the fleet and nearby 515 float distToPlayer = Misc.getDistance(fleet, Global.getSector().getPlayerFleet()); 516 boolean playerInvolved = battle.isPlayerInvolved() || (fleet.isInCurrentLocation() && distToPlayer < 2000f); 517 518 if (battle.isInvolved(fleet) && !playerInvolved) { 519 boolean cancelBounty = (fleet.isStationMode() && fleet.getFlagship() == null) || 520 (!fleet.isStationMode() && fleet.getFlagship() != null && fleet.getFlagship().getCaptain() != target); 521 if (cancelBounty) { 522 String id = getMissionId(); 523 getPerson().getMemoryWithoutUpdate().set("$" + id + "_failed", true); 524 return; 525 } 526// if (fleet.getFlagship() == null || fleet.getFlagship().getCaptain() != target) { 527// fleet.setCommander(fleet.getFaction().createRandomPerson()); 528// //latestResult = new BountyResult(BountyResultType.END_OTHER, 0, null); 529// sendUpdateIfPlayerHasIntel(result, true); 530// return; 531// } 532 } 533 534 //CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 535 if (!playerInvolved || !battle.isInvolved(fleet) || battle.onPlayerSide(fleet)) { 536 return; 537 } 538 539 if (fleet.isStationMode()) { 540 if (fleet.getFlagship() != null) return; 541 } else { 542 if (fleet.getFlagship() != null && fleet.getFlagship().getCaptain() == target) return; 543 } 544 545 String id = getMissionId(); 546 getPerson().getMemoryWithoutUpdate().set("$" + id + "_completed", true); 547 548 } 549 550 public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) { 551 if (isDone() || result != null) return; 552 553 if (this.data.fleet == fleet) { 554 String id = getMissionId(); 555 getPerson().getMemoryWithoutUpdate().set("$" + id + "_failed", true); 556 } 557 } 558 559 560 @Override 561 public String getBaseName() { 562 if (creator != null) return creator.getBaseBountyName(this, data); 563 return "Bounty"; 564 } 565 566 protected String getMissionTypeNoun() { 567 return "bounty"; 568 } 569 570 public String getPostfixForState() { 571// if (isEnding()) { 572// return super.getPostfixForState(); 573// } 574// if (true) { 575// return " - Unusual Remnant Fleet - Failed"; 576// } 577 String post = super.getPostfixForState(); 578 post = post.replaceFirst(" - ", ""); 579 if (!post.isEmpty()) post = " (" + post + ")"; 580 //if (creator != null) return creator.getBountyNamePostfix(this, data).replaceFirst(" - ", ": ") + post; 581 if (creator != null) return creator.getBountyNamePostfix(this, data) + post; 582 return super.getPostfixForState(); 583 } 584 585 @Override 586 public String getName() { 587 return super.getName(); 588 } 589 590 591 592 593} 594 595 596 597 598 599 600 601 602 603 604