001package com.fs.starfarer.api.impl.campaign.graid; 002 003import java.awt.Color; 004import java.util.Random; 005 006import com.fs.starfarer.api.Global; 007import com.fs.starfarer.api.campaign.CargoAPI; 008import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; 009import com.fs.starfarer.api.campaign.CargoStackAPI; 010import com.fs.starfarer.api.campaign.FactionAPI; 011import com.fs.starfarer.api.campaign.TextPanelAPI; 012import com.fs.starfarer.api.campaign.econ.CommodityOnMarketAPI; 013import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI; 014import com.fs.starfarer.api.campaign.econ.MarketAPI; 015import com.fs.starfarer.api.combat.WeaponAPI.AIHints; 016import com.fs.starfarer.api.impl.campaign.econ.CommodityIconCounts; 017import com.fs.starfarer.api.impl.campaign.ids.Commodities; 018import com.fs.starfarer.api.impl.campaign.ids.Tags; 019import com.fs.starfarer.api.impl.campaign.rulecmd.salvage.MarketCMD.RaidDangerLevel; 020import com.fs.starfarer.api.loading.FighterWingSpecAPI; 021import com.fs.starfarer.api.loading.HullModSpecAPI; 022import com.fs.starfarer.api.loading.WeaponSpecAPI; 023import com.fs.starfarer.api.ui.IconGroupAPI; 024import com.fs.starfarer.api.ui.IconRenderMode; 025import com.fs.starfarer.api.ui.TooltipMakerAPI; 026import com.fs.starfarer.api.util.Misc; 027import com.fs.starfarer.api.util.WeightedRandomPicker; 028 029public class ShipWeaponsGroundRaidObjectivePluginImpl extends BaseGroundRaidObjectivePluginImpl { 030 031 public static float CARGO_SPACE_PER_LARGE = 8f; 032 public static float CARGO_SPACE_PER_MEDIUM = 4f; 033 public static float CARGO_SPACE_PER_SMALL = 2f; 034 035 public static float SELL_MULT = Global.getSettings().getFloat("shipWeaponSellPriceMult"); 036 037 038 public static float VALUE_NORMAL = 1f; 039 public static float VALUE_EXCESS = 2f; 040 public static float VALUE_DEFICIT = -1f; 041 public static float VALUE_OVERALL = 1000f; 042 043 protected CommodityOnMarketAPI com; 044 045 public ShipWeaponsGroundRaidObjectivePluginImpl(MarketAPI market) { 046 super(market, Commodities.SHIPS); 047 com = market.getCommodityData(id); 048 setSource(CommodityGroundRaidObjectivePluginImpl.computeCommoditySource(market, com)); 049 } 050 051 052 public void addIcons(IconGroupAPI iconGroup) { 053 CommodityIconCounts counts = new CommodityIconCounts(com); 054 055 int deficit = counts.deficit; 056 int available = Math.max(0, counts.available - counts.extra); 057 int extra = counts.extra; 058 059 if (available > 0) { 060 iconGroup.addIconGroup(Commodities.SHIP_WEAPONS, IconRenderMode.NORMAL, available, null); 061 } 062 if (deficit > 0) { 063 iconGroup.addIconGroup(Commodities.SHIP_WEAPONS, IconRenderMode.RED, deficit, null); 064 } 065 if (extra > 0) { 066 iconGroup.addIconGroup(Commodities.SHIP_WEAPONS, IconRenderMode.GREEN, extra, null); 067 } 068 } 069 070// public int getCargoSpaceNeeded() { 071// return (int) getQuantity(getMarinesAssigned()); 072// } 073 074 public int getProjectedCreditsValue() { 075 return (int) getQuantity(getMarinesAssigned()); 076 } 077 078 public CommoditySpecAPI getWeaponsCommoditySpec() { 079 return Global.getSettings().getCommoditySpec(Commodities.SHIP_WEAPONS); 080 } 081 082 public RaidDangerLevel getDangerLevel() { 083 RaidDangerLevel danger = getWeaponsCommoditySpec().getBaseDanger(); 084 085 CommodityIconCounts counts = new CommodityIconCounts(com); 086 if (counts.production >= counts.available) { 087 danger = danger.prev(); 088 } 089 if (counts.deficit > 0) { 090 return danger.next(); 091 } 092 if (counts.extra > 0) { 093 return danger.prev(); 094 } 095 096 if (source != null) { 097 danger = source.adjustCommodityDangerLevel(id, danger); 098 } 099 100 return danger; 101 } 102 103 public float getQuantitySortValue() { 104 CommoditySpecAPI spec = getWeaponsCommoditySpec(); 105 float add = 0; 106 if (spec != null) { 107 add = spec.getOrder(); 108 } 109 return QUANTITY_SORT_TIER_1 + add; 110 } 111 112 public String getQuantityString(int marines) { 113// int value = (int) getQuantity(Math.max(1, marines)); 114// return Misc.getDGSCredits(value); 115 return ""; 116 } 117 118 @Override 119 public String getValueString(int marines) { 120 int value = (int) getQuantity(Math.max(1, marines)); 121 return Misc.getDGSCredits(value); 122 } 123 124 125 public int getValue(int marines) { 126 return (int) getQuantity(marines); 127 } 128 129 public float getQuantity(int marines) { 130 float base = Math.round(getBaseRaidValue()); 131 return base * marines; 132 } 133 134 public float getBaseRaidValue() { 135 CommodityOnMarketAPI com = market.getCommodityData(id); 136 float unit = 1f; 137 138 CommodityIconCounts counts = new CommodityIconCounts(com); 139 140 float result = 0f; 141 142 result += Math.max(0, counts.available - counts.extra) * unit * VALUE_NORMAL; 143 result += counts.extra * unit * VALUE_EXCESS; 144 result += counts.deficit * unit * VALUE_DEFICIT; 145 146 result *= VALUE_OVERALL; 147 148 if (result < 0) result = 0; 149 150 return result; 151 } 152 153 public String getName() { 154 return getWeaponsCommoditySpec().getName(); 155 } 156 157 public CargoStackAPI getStackForIcon() { 158 CargoStackAPI stack = Global.getFactory().createCargoStack(CargoItemType.RESOURCES, Commodities.SHIP_WEAPONS, null); 159 return stack; 160 } 161 162 public String getCommodityIdForDeficitIcons() { 163 return com.getId(); 164 } 165 166 protected CargoAPI looted = Global.getFactory().createCargo(true); 167 168 protected float getQMult(int tier) { 169 if (tier <= 0) return 0f; 170 if (tier <= 1) return 0.25f; 171 if (tier <= 2) return 0.5f; 172 return 0.75f; 173 } 174 175 public int performRaid(CargoAPI loot, Random random, float lootMult, TextPanelAPI text) { 176 if (marinesAssigned <= 0) return 0; 177 178 //random = new Random(); 179 180 WeightedRandomPicker<WeaponSpecAPI> pickerW = new WeightedRandomPicker<WeaponSpecAPI>(random); 181 WeightedRandomPicker<FighterWingSpecAPI> pickerF = new WeightedRandomPicker<FighterWingSpecAPI>(random); 182 WeightedRandomPicker<HullModSpecAPI> pickerH = new WeightedRandomPicker<HullModSpecAPI>(random); 183 184 WeightedRandomPicker<WeaponSpecAPI> weaponSubset = new WeightedRandomPicker<WeaponSpecAPI>(random); 185 WeightedRandomPicker<FighterWingSpecAPI> fighterSubset = new WeightedRandomPicker<FighterWingSpecAPI>(random); 186 187 String factionId = market.getFactionId(); 188 float quality = Misc.getShipQuality(market, factionId); 189 FactionAPI faction = Global.getSector().getFaction(factionId); 190 191 int maxTier = 0; 192 if (market.getSize() >= 6) { 193 maxTier = 1; 194 } 195 if (Misc.hasHeavyIndustry(market) || Misc.isMilitary(market)) { 196 maxTier = 1000; 197 } 198 199 float numSmall = 0; 200 float numMedium = 0; 201 float numLarge = 0; 202 for (String id : faction.getKnownWeapons()) { 203 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(id); 204 switch (spec.getSize()) { 205 case LARGE: 206 numLarge++; 207 break; 208 case MEDIUM: 209 numMedium++; 210 break; 211 case SMALL: 212 numSmall++; 213 break; 214 } 215 } 216 float numTotal = numSmall + numMedium + numLarge + 1f; 217 218 for (String id : faction.getKnownWeapons()) { 219 WeaponSpecAPI spec = Global.getSettings().getWeaponSpec(id); 220 if (spec.getAIHints().contains(AIHints.SYSTEM)) continue; 221 if (spec.getTier() > maxTier) continue; 222 223 float p = 1f * spec.getRarity() + quality * getQMult(spec.getTier()); 224 switch (spec.getSize()) { 225 case LARGE: 226 p *= 1f - numLarge / numTotal; 227 p *= 2f; 228 break; 229 case MEDIUM: 230 p *= 1f - numMedium / numTotal; 231 p *= 3f; 232 break; 233 case SMALL: 234 p *= 1f - numSmall / numTotal; 235 p *= 4f; 236 break; 237 } 238 pickerW.add(spec, p); 239 } 240 for (int i = 0; i < 4 + marinesAssigned; i++) { 241 WeaponSpecAPI spec = pickerW.pick(); 242 if (spec != null) { 243 float w = pickerW.getWeight(spec); 244 weaponSubset.add(spec, w); 245 pickerW.remove(spec); 246 } 247 } 248 249 for (String id : faction.getKnownFighters()) { 250 FighterWingSpecAPI spec = Global.getSettings().getFighterWingSpec(id); 251 if (spec.getTier() > maxTier) continue; 252 253 float p = 1f * spec.getRarity() + quality * getQMult(spec.getTier()); 254 pickerF.add(spec, p); 255 } 256 for (int i = 0; i < 2 + marinesAssigned/2; i++) { 257 FighterWingSpecAPI spec = pickerF.pick(); 258 if (spec != null) { 259 float w = pickerF.getWeight(spec); 260 fighterSubset.add(spec, w); 261 pickerF.remove(spec); 262 } 263 } 264 265 for (String id : faction.getKnownHullMods()) { 266 HullModSpecAPI spec = Global.getSettings().getHullModSpec(id); 267 if (spec.isHidden()) continue; 268 if (spec.isAlwaysUnlocked()) continue; 269 if (spec.hasTag(Tags.NO_DROP)) continue; 270 if (spec.getTier() > maxTier) continue; 271 272 float p = 1f * spec.getRarity(); 273 if (Global.getSector().getPlayerFaction().knowsHullMod(id)) { 274 p *= 0.2f; 275 } 276 pickerH.add(spec, p); 277 } 278 279 280 float targetValue = getQuantity(marinesAssigned); 281 targetValue *= lootMult; 282 float mult = 0.9f + random.nextFloat() * 0.2f; 283 targetValue *= mult; 284 285 quantityLooted = (int) targetValue; 286 287 float weaponWeight = faction.getDoctrine().getWarships() + faction.getDoctrine().getPhaseShips(); 288 float fighterWeight = 1f + faction.getDoctrine().getCarriers(); 289 float hullmodWeight = 1f + quality * 1f; 290 float totalWeight = weaponWeight + fighterWeight + hullmodWeight; 291 292 float weaponValue = targetValue * weaponWeight / totalWeight; 293 float fighterValue = targetValue * fighterWeight / totalWeight; 294 float hullmodValue = targetValue * hullmodWeight / totalWeight; 295 296 float totalValue = 0; 297 298 looted.clear(); 299 300 int tries = 0; 301 while (weaponValue > 0 && tries < 100) { 302 tries++; 303 WeaponSpecAPI weapon = weaponSubset.pick(); 304 if (weapon != null) { 305 int min = 1, max = 2; 306 // don't do this since the odds of rolling large are smaller than medium smaller than small etc 307// switch (weapon.getSize()) { 308// case LARGE: min = 1; max = 2; break; 309// case MEDIUM: min = 1; max = 4; break; 310// case SMALL: min = 2; max = 6; break; 311// } 312 float val = weapon.getBaseValue() * SELL_MULT; 313 int num = min + random.nextInt(max - min + 1); 314 num = (int) Math.min(num, weaponValue / val); 315 if (num == 0) { 316 if (random.nextFloat() < weaponValue / val) num = 1; 317 } 318 if (num > 0) { 319 looted.addWeapons(weapon.getWeaponId(), num); 320 weaponValue -= val * num; 321 totalValue += val * num; 322 } else { 323 break; 324 } 325 } else { 326 break; 327 } 328 } 329 330 fighterValue += Math.max(0, weaponValue); 331 332 tries = 0; 333 while (fighterValue > 0 && tries < 100) { 334 tries++; 335 FighterWingSpecAPI fighter = fighterSubset.pick(); 336 if (fighter != null) { 337 int min = 1, max = 2; 338 switch (fighter.getRole()) { 339 case ASSAULT: 340 case BOMBER: 341 case SUPPORT: 342 min = 1; max = 2; 343 break; 344 case FIGHTER: 345 min = 1; max = 3; 346 break; 347 case INTERCEPTOR: 348 min = 1; max = 4; 349 break; 350 } 351 float val = fighter.getBaseValue() * SELL_MULT; 352 int num = min + random.nextInt(max - min + 1); 353 num = (int) Math.min(num, fighterValue / val); 354 if (num == 0) { 355 if (random.nextFloat() < fighterValue / val) num = 1; 356 } 357 if (num > 0) { 358 looted.addFighters(fighter.getId(), num); 359 fighterValue -= val * num; 360 totalValue += val * num; 361 } else { 362 break; 363 } 364 } else { 365 break; 366 } 367 } 368 369 hullmodValue += Math.max(0, fighterValue); 370 371 tries = 0; 372 while (hullmodValue > 0 && tries < 100) { 373 tries++; 374 HullModSpecAPI mod = pickerH.pickAndRemove(); 375 if (mod != null) { 376 float val = mod.getBaseValue(); 377 int num = 0; 378 if (random.nextFloat() < hullmodValue / val) num = 1; 379 if (num > 0) { 380 looted.addHullmods(mod.getId(), num); 381 hullmodValue -= val * num; 382 totalValue += val * num; 383 } else { 384 break; 385 } 386 } else { 387 break; 388 } 389 } 390 391 loot.addAll(looted); 392 393 xpGained = (int) (totalValue * XP_GAIN_VALUE_MULT); 394 return xpGained; 395 } 396 397 398 399 @Override 400 public boolean hasTooltip() { 401 return true; 402 } 403 404 @Override 405 public void createTooltip(TooltipMakerAPI t, boolean expanded) { 406 float opad = 10f; 407 float pad = 3f; 408 Color h = Misc.getHighlightColor(); 409 Color bad = Misc.getNegativeHighlightColor(); 410 Color good = Misc.getPositiveHighlightColor(); 411 412 CommodityOnMarketAPI com = market.getCommodityData(Commodities.SHIPS); 413 414 t.addPara("Ship weapons, fighter LPCs, and hullmod specs. Availability is based on the " + 415 "\"" + com.getCommodity().getName() + "\" commodity.", 0f); 416 t.addPara("The colony faction's doctrine affects the number of weapons vs fighter LPCs acquired. Higher ship quality " + 417 "increases the probability of finding modspecs..", opad); 418 //t.addPara("The \"projected value\" is the typical sell value of the equipment acquired.", opad); 419 420 if (Misc.hasHeavyIndustry(market) || Misc.isMilitary(market)) { 421 if (Misc.hasHeavyIndustry(market)) { 422 t.addPara("This colony has heavy industry and high-tier equipment may be found.", good, opad); 423 } else if (Misc.isMilitary(market)) { 424 t.addPara("This colony has a military presence and high-tier equipment may be found.", good, opad); 425 } 426 } else { 427 t.addPara("This colony does not have heavy industry or a military presence and has no access to high-tier ship equipment.", bad, opad); 428 } 429 430 // weapons fighters and hullmods 431 // based on S&W 432 // impact of doctrine 433 // impact of ship quality 434 // how hullmods get rolled 435 // impact of assigning more marines, explain that more marines = more stuff despite "variable" 436 // whether it has military or production, or size >= 6 437 } 438 439 440 441 442 443 public CargoAPI getLooted() { 444 return looted; 445 } 446 447 448 449} 450 451 452 453 454 455 456 457