001package com.fs.starfarer.api.impl.campaign.intel; 002 003import java.awt.Color; 004import java.util.List; 005import java.util.Set; 006 007import org.apache.log4j.Logger; 008 009import com.fs.starfarer.api.EveryFrameScript; 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.FactionAPI; 015import com.fs.starfarer.api.campaign.LocationAPI; 016import com.fs.starfarer.api.campaign.ReputationActionResponsePlugin.ReputationAdjustmentResult; 017import com.fs.starfarer.api.campaign.SectorEntityToken; 018import com.fs.starfarer.api.campaign.StarSystemAPI; 019import com.fs.starfarer.api.campaign.ai.CampaignFleetAIAPI.ActionType; 020import com.fs.starfarer.api.campaign.econ.MarketAPI; 021import com.fs.starfarer.api.campaign.listeners.FleetEventListener; 022import com.fs.starfarer.api.fleet.FleetMemberAPI; 023import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin; 024import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActionEnvelope; 025import com.fs.starfarer.api.impl.campaign.CoreReputationPlugin.RepActions; 026import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript; 027import com.fs.starfarer.api.impl.campaign.MilitaryResponseScript.MilitaryResponseParams; 028import com.fs.starfarer.api.impl.campaign.econ.BaseMarketConditionPlugin; 029import com.fs.starfarer.api.impl.campaign.ids.Factions; 030import com.fs.starfarer.api.impl.campaign.ids.Tags; 031import com.fs.starfarer.api.ui.SectorMapAPI; 032import com.fs.starfarer.api.ui.TooltipMakerAPI; 033import com.fs.starfarer.api.util.Misc; 034 035public class SystemBountyIntel extends BaseIntelPlugin implements EveryFrameScript, FleetEventListener { 036 public static Logger log = Global.getLogger(SystemBountyIntel.class); 037 038 public static float MAX_DURATION = 60; 039 040 protected MarketAPI market; 041 protected LocationAPI location; 042 protected float elapsedDays = 0f; 043 protected float duration = MAX_DURATION; 044 045 protected float baseBounty = 0; 046 047 protected FactionAPI faction = null; 048 protected FactionAPI enemyFaction = null; 049 protected SystemBountyResult latestResult; 050 051 protected boolean commerceMode = false; // due to Commerce industry in player system 052 053 protected MilitaryResponseScript script; 054 055 public SystemBountyIntel(MarketAPI market) { 056 this(market, -1, false); 057 } 058 public SystemBountyIntel(MarketAPI market, int baseReward, boolean commerceMode) { 059 this.market = market; 060 this.commerceMode = commerceMode; 061 062 location = market.getContainingLocation(); 063 faction = market.getFaction(); 064 if (commerceMode) { 065 faction = Global.getSector().getFaction(Factions.INDEPENDENT); 066 } 067 068 if (!commerceMode && market.getFaction().isPlayerFaction()) { 069 endImmediately(); 070 return; 071 } 072 073 baseBounty = Global.getSettings().getFloat("baseSystemBounty"); 074 float marketSize = market.getSize(); 075 076 baseBounty *= (marketSize + 5f) / 10f; 077 078 //float lowStabilityMult = BaseMarketConditionPlugin.getLowStabilityPenaltyMult(market); 079 float highStabilityMult = BaseMarketConditionPlugin.getHighStabilityBonusMult(market); 080 highStabilityMult = 1f + (highStabilityMult - 1f) * 0.5f; 081 082 //baseBounty *= lowStabilityMult; 083 baseBounty *= highStabilityMult; 084 085 baseBounty = (int) baseBounty; 086 if (baseReward > 0) { 087 baseBounty = baseReward; 088 } 089 090 log.info(String.format("Starting bounty at market [%s], %d credits per frigate", market.getName(), (int) baseBounty)); 091 092 updateLikelyCauseFaction(); 093 094 095 //conditionToken = market.addCondition(Conditions.EVENT_SYSTEM_BOUNTY, this); 096 097 if (commerceMode) { 098 Global.getSector().getIntelManager().addIntel(this); 099 } else { 100 Global.getSector().getIntelManager().queueIntel(this); 101 } 102 103 Global.getSector().getListenerManager().addListener(this); 104 105 if (!commerceMode) { 106 MilitaryResponseParams params = new MilitaryResponseParams(ActionType.HOSTILE, 107 "system_bounty_" + market.getId(), 108 getFactionForUIColors(), 109 market.getPrimaryEntity(), 110 0.75f, 111 duration); 112 script = new MilitaryResponseScript(params); 113 location.addScript(script); 114 } 115 } 116 117 public void reportMadeVisibleToPlayer() { 118 if (!isEnding() && !isEnded()) { 119 duration = Math.max(duration * 0.5f, Math.min(duration * 2f, MAX_DURATION)); 120 } 121 } 122 123 public float getElapsedDays() { 124 return elapsedDays; 125 } 126 127 public void setElapsedDays(float elapsedDays) { 128 this.elapsedDays = elapsedDays; 129 } 130 131 public void reset() { 132 elapsedDays = 0f; 133 endingTimeRemaining = null; 134 ending = null; 135 ended = null; 136 script.setElapsed(0f); 137 if (!Global.getSector().getListenerManager().hasListener(this)) { 138 Global.getSector().getListenerManager().addListener(this); 139 } 140 } 141 142 143 144 public MarketAPI getMarket() { 145 return market; 146 } 147 148 149 private void updateLikelyCauseFaction() { 150 int maxSize = 0; 151 MarketAPI maxOther = null; 152 for (MarketAPI other : Misc.getNearbyMarkets(market.getLocationInHyperspace(), 0f)) { 153 if (market.getFaction() == other.getFaction()) continue; 154 if (!market.getFaction().isHostileTo(other.getFaction())) continue; 155 156 int size = other.getSize(); 157 if (size > maxSize) { 158 maxSize = size; 159 maxOther = other; 160 } 161 } 162 163 if (maxOther != null) { 164 enemyFaction = maxOther.getFaction(); 165 } else { 166 enemyFaction = Global.getSector().getFaction(Factions.PIRATES); 167 } 168 169 } 170 171 @Override 172 protected void advanceImpl(float amount) { 173 float days = Global.getSector().getClock().convertToDays(amount); 174 175 elapsedDays += days; 176 177 if (elapsedDays >= duration && !isDone() && !commerceMode) { 178 endAfterDelay(); 179 boolean current = market.getContainingLocation() == Global.getSector().getCurrentLocation(); 180 sendUpdateIfPlayerHasIntel(new Object(), !current); 181 return; 182 } 183 if (faction != market.getFaction() || !market.isInEconomy()) { 184 endAfterDelay(); 185 boolean current = market.getContainingLocation() == Global.getSector().getCurrentLocation(); 186 sendUpdateIfPlayerHasIntel(new Object(), !current); 187 return; 188 } 189 } 190 191 public float getTimeRemainingFraction() { 192 if (commerceMode) return 1f; 193 float f = 1f - elapsedDays / duration; 194 return f; 195 } 196 197 198 199 @Override 200 protected void notifyEnding() { 201 super.notifyEnding(); 202 log.info(String.format("Ending bounty at market [%s]", market.getName())); 203 204 Global.getSector().getListenerManager().removeListener(this); 205 206 location.removeScript(script); 207 208 //market.removeSpecificCondition(conditionToken); 209 } 210 211 212 public static class SystemBountyResult { 213 public int payment; 214 public float fraction; 215 public ReputationAdjustmentResult rep; 216 public SystemBountyResult(int payment, float fraction, ReputationAdjustmentResult rep) { 217 this.payment = payment; 218 this.fraction = fraction; 219 this.rep = rep; 220 } 221 222 } 223 224 public void reportFleetDespawnedToListener(CampaignFleetAPI fleet, FleetDespawnReason reason, Object param) { 225 } 226 227 public void reportBattleOccurred(CampaignFleetAPI fleet, CampaignFleetAPI primaryWinner, BattleAPI battle) { 228 if (isEnded() || isEnding()) return; 229 230 if (!battle.isPlayerInvolved()) return; 231 232 if (!Misc.isNear(primaryWinner, market.getLocationInHyperspace())) return; 233 234 int payment = 0; 235 float fpDestroyed = 0; 236 for (CampaignFleetAPI otherFleet : battle.getNonPlayerSideSnapshot()) { 237 if (commerceMode) { 238 if (!market.getFaction().isHostileTo(otherFleet.getFaction()) && 239 !otherFleet.getFaction().isHostileTo(Factions.INDEPENDENT)) continue; 240 241 if (Misc.isTrader(otherFleet)) continue; 242 if (Factions.INDEPENDENT.equals(otherFleet.getFaction().getId())) continue; 243 } else { 244 if (!market.getFaction().isHostileTo(otherFleet.getFaction())) continue; 245 } 246 247 float bounty = 0; 248 for (FleetMemberAPI loss : Misc.getSnapshotMembersLost(otherFleet)) { 249 float mult = Misc.getSizeNum(loss.getHullSpec().getHullSize()); 250 bounty += mult * baseBounty; 251 fpDestroyed += loss.getFleetPointCost(); 252 } 253 254 payment += (int) (bounty * battle.getPlayerInvolvementFraction()); 255 } 256 257 if (payment > 0) { 258 Global.getSector().getPlayerFleet().getCargo().getCredits().add(payment); 259 260 float repFP = (int)(fpDestroyed * battle.getPlayerInvolvementFraction()); 261 ReputationAdjustmentResult rep = Global.getSector().adjustPlayerReputation( 262 new RepActionEnvelope(RepActions.SYSTEM_BOUNTY_REWARD, new Float(repFP), null, null, true, false), 263 market.getFaction().getId()); 264 latestResult = new SystemBountyResult(payment, battle.getPlayerInvolvementFraction(), rep); 265 sendUpdateIfPlayerHasIntel(latestResult, false); 266 } 267 } 268 269 public boolean runWhilePaused() { 270 return false; 271 } 272 protected void addBulletPoints(TooltipMakerAPI info, ListInfoMode mode) { 273 274 Color h = Misc.getHighlightColor(); 275 Color g = Misc.getGrayColor(); 276 float pad = 3f; 277 float opad = 10f; 278 279 float initPad = pad; 280 if (mode == ListInfoMode.IN_DESC) initPad = opad; 281 282 Color tc = getBulletColorForMode(mode); 283 284 bullet(info); 285 boolean isUpdate = getListInfoParam() != null; 286 287 if (isEnding() && isUpdate) { 288 //info.addPara("Over", initPad); 289 } else { 290 if (isUpdate && latestResult != null) { 291 info.addPara("%s received", initPad, tc, h, Misc.getDGSCredits(latestResult.payment)); 292 if (Math.round(latestResult.fraction * 100f) < 100f) { 293 info.addPara("%s share based on damage dealt", 0f, tc, h, 294 "" + (int) Math.round(latestResult.fraction * 100f) + "%"); 295 } 296 CoreReputationPlugin.addAdjustmentMessage(latestResult.rep.delta, faction, null, 297 null, null, info, tc, isUpdate, 0f); 298 } else if (mode == ListInfoMode.IN_DESC) { 299 info.addPara("%s base reward per frigate", initPad, tc, h, Misc.getDGSCredits(baseBounty)); 300 if (!commerceMode) { 301 addDays(info, "remaining", duration - elapsedDays, tc); 302 } 303 } else { 304 if (!isEnding()) { 305 info.addPara("Faction: " + faction.getDisplayName(), initPad, tc, 306 faction.getBaseUIColor(), faction.getDisplayName()); 307 info.addPara("%s base reward per frigate", 0f, tc, h, Misc.getDGSCredits(baseBounty)); 308 if (!commerceMode) { 309 addDays(info, "remaining", duration - elapsedDays, tc); 310 } 311 } 312 } 313 } 314 unindent(info); 315 } 316 317 @Override 318 public void createIntelInfo(TooltipMakerAPI info, ListInfoMode mode) { 319 Color h = Misc.getHighlightColor(); 320 Color g = Misc.getGrayColor(); 321 Color c = getTitleColor(mode); 322 float pad = 3f; 323 float opad = 10f; 324 325 info.addPara(getName(), c, 0f); 326 327 addBulletPoints(info, mode); 328 } 329 330 public String getSortString() { 331 return "System Bounty"; 332 } 333 334 public String getName() { 335 String name = market.getName(); 336 StarSystemAPI system = market.getStarSystem(); 337 if (system != null) { 338 name = system.getBaseName(); 339 } 340 if (isEnding()) { 341 return "Bounty Ended - " + name; 342 } 343 return "System Bounty - " + name; 344 } 345 346 @Override 347 public FactionAPI getFactionForUIColors() { 348 return faction; 349 } 350 351 public String getSmallDescriptionTitle() { 352 return getName(); 353 //return null; 354 } 355 356 public void createSmallDescription(TooltipMakerAPI info, float width, float height) { 357 createSmallDescription(info, width, height, false); 358 } 359 public void createSmallDescription(TooltipMakerAPI info, float width, float height, 360 boolean forMarketConditionTooltip) { 361 Color h = Misc.getHighlightColor(); 362 Color g = Misc.getGrayColor(); 363 Color tc = Misc.getTextColor(); 364 float pad = 3f; 365 float opad = 10f; 366 367 //info.addPara(getName(), c, 0f); 368 369 //info.addSectionHeading(getName(), Alignment.MID, 0f); 370 371 if (!forMarketConditionTooltip) { 372 info.addImage(faction.getLogo(), width, 128, opad); 373 } 374 375 String locStr = "near " + market.getName(); 376 if (market.getStarSystem() != null) { 377 locStr = "in or near the " + market.getStarSystem().getNameWithLowercaseType(); 378 } 379 380 if (commerceMode) { 381 info.addPara("%s commercial concerns " + market.getOnOrAt() + " " + market.getName() + 382 " have banded together and posted a modest but long-term bounty on all " 383 + "hostile fleets " + locStr + ".", 384 opad, faction.getBaseUIColor(), Misc.ucFirst(faction.getPersonNamePrefix())); 385 info.addPara("The bounty stipulates that trade fleets are an exception - " 386 + "attacking them will not result in a reward, regardless of their faction.", opad); 387 } else { 388 info.addPara("%s authorities " + market.getOnOrAt() + " " + market.getName() + 389 " have posted a bounty on all hostile fleets " + locStr + ".", 390 opad, faction.getBaseUIColor(), Misc.ucFirst(faction.getPersonNamePrefix())); 391 } 392 393 if (isEnding()) { 394 info.addPara("This bounty is no longer on offer.", opad); 395 return; 396 } 397 398// if (!Global.getSector().getListenerManager().hasListener(this)) { 399// Global.getSector().getListenerManager().addListener(this); 400// info.addPara("Listener not registered!", opad); 401// } 402 403 addBulletPoints(info, ListInfoMode.IN_DESC); 404 405 if (!commerceMode) { 406 if (enemyFaction != null) { 407 info.addPara("Likely triggered by %s activity.", 408 opad, enemyFaction.getBaseUIColor(), enemyFaction.getPersonNamePrefix()); 409 } 410 } else { 411 info.addPara("Triggered by the presence of Commerce on one of your colonies in-system, and by the " 412 + "level of hostile activity.", opad); 413 } 414 415 416 info.addPara("Payment depends on the number and size of ships destroyed. " + 417 "Standing with " + faction.getDisplayNameWithArticle() + " may also improve.", 418 opad); 419// opad, faction.getBaseUIColor(), 420// faction.getDisplayNameWithArticleWithoutArticle()); 421 422 423 if (!commerceMode) { 424 String isOrAre = faction.getDisplayNameIsOrAre(); 425 FactionCommissionIntel temp = new FactionCommissionIntel(faction); 426 List<FactionAPI> hostile = temp.getHostileFactions(); 427 if (hostile.isEmpty()) { 428 info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + isOrAre + " not currently hostile to any major factions.", 0f); 429 } else { 430 info.addPara(Misc.ucFirst(faction.getDisplayNameWithArticle()) + " " + isOrAre + " currently hostile to:", opad); 431 432 info.setParaFontDefault(); 433 434 info.setBulletedListMode(BaseIntelPlugin.INDENT); 435 float initPad = pad; 436 for (FactionAPI other : hostile) { 437 info.addPara(Misc.ucFirst(other.getDisplayName()), other.getBaseUIColor(), initPad); 438 initPad = 0f; 439 } 440 info.setBulletedListMode(null); 441 } 442 } 443 444 if (latestResult != null) { 445 //Color color = faction.getBaseUIColor(); 446 //Color dark = faction.getDarkUIColor(); 447 //info.addSectionHeading("Most Recent Reward", color, dark, Alignment.MID, opad); 448 info.addPara("Most recent bounty:", opad); 449 bullet(info); 450 info.addPara("%s received", pad, tc, h, Misc.getDGSCredits(latestResult.payment)); 451 if (Math.round(latestResult.fraction * 100f) < 100f) { 452 info.addPara("%s share based on damage dealt", 0f, tc, h, 453 "" + (int) Math.round(latestResult.fraction * 100f) + "%"); 454 } 455 CoreReputationPlugin.addAdjustmentMessage(latestResult.rep.delta, faction, null, 456 null, null, info, tc, false, 0f); 457 unindent(info); 458 } 459 460 } 461 462 public String getIcon() { 463 return Global.getSettings().getSpriteName("intel", "system_bounty"); 464 } 465 466 public Set<String> getIntelTags(SectorMapAPI map) { 467 Set<String> tags = super.getIntelTags(map); 468 tags.add(Tags.INTEL_BOUNTY); 469 tags.add(faction.getId()); 470 return tags; 471 } 472 473 @Override 474 public SectorEntityToken getMapLocation(SectorMapAPI map) { 475 return market.getPrimaryEntity(); 476 } 477 478 479 public float getDuration() { 480 return duration; 481 } 482 483 public void setDuration(float duration) { 484 this.duration = duration; 485 } 486 487 public float getBaseBounty() { 488 return baseBounty; 489 } 490 491 public void setBaseBounty(float baseBounty) { 492 this.baseBounty = baseBounty; 493 } 494 public boolean isCommerceMode() { 495 return commerceMode; 496 } 497 498 public void setCommerceMode(boolean commerceMode) { 499 this.commerceMode = commerceMode; 500 } 501 public LocationAPI getLocation() { 502 return location; 503 } 504 505} 506 507