001package com.fs.starfarer.api.impl.campaign.procgen.themes; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.HashMap; 006import java.util.List; 007import java.util.Map; 008import java.util.Random; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.AICoreOfficerPlugin; 012import com.fs.starfarer.api.campaign.CampaignFleetAPI; 013import com.fs.starfarer.api.campaign.FactionAPI; 014import com.fs.starfarer.api.campaign.FactionDoctrineAPI; 015import com.fs.starfarer.api.campaign.GenericPluginManagerAPI; 016import com.fs.starfarer.api.characters.MutableCharacterStatsAPI; 017import com.fs.starfarer.api.characters.PersonAPI; 018import com.fs.starfarer.api.combat.WeaponAPI.AIHints; 019import com.fs.starfarer.api.combat.WeaponAPI.WeaponSize; 020import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 021import com.fs.starfarer.api.fleet.FleetMemberAPI; 022import com.fs.starfarer.api.impl.campaign.events.OfficerManagerEvent.SkillPickPreference; 023import com.fs.starfarer.api.impl.campaign.fleets.BaseGenerateFleetOfficersPlugin; 024import com.fs.starfarer.api.impl.campaign.fleets.FleetFactoryV3; 025import com.fs.starfarer.api.impl.campaign.fleets.FleetParamsV3; 026import com.fs.starfarer.api.impl.campaign.ids.Commodities; 027import com.fs.starfarer.api.impl.campaign.ids.Factions; 028import com.fs.starfarer.api.impl.campaign.ids.Ranks; 029import com.fs.starfarer.api.impl.campaign.ids.Skills; 030import com.fs.starfarer.api.impl.campaign.missions.hub.HubMissionWithTriggers.OfficerQuality; 031import com.fs.starfarer.api.loading.WeaponSpecAPI; 032import com.fs.starfarer.api.util.Misc; 033import com.fs.starfarer.api.util.WeightedRandomPicker; 034 035public class RemnantOfficerGeneratorPlugin extends BaseGenerateFleetOfficersPlugin { 036 037 protected boolean putCoresOnCivShips = false; 038 protected boolean forceIntegrateCores = false; 039 protected boolean forceNoCommander = false; 040 protected boolean derelictMode = false; 041 protected float coreMult = 1f; 042 043 public RemnantOfficerGeneratorPlugin() { 044 } 045 046 public RemnantOfficerGeneratorPlugin(boolean derelictMode, float coreMult) { 047 this.derelictMode = derelictMode; 048 this.coreMult = coreMult; 049 } 050 051 052 053 public boolean isForceNoCommander() { 054 return forceNoCommander; 055 } 056 057 public void setForceNoCommander(boolean forceNoCommander) { 058 this.forceNoCommander = forceNoCommander; 059 } 060 061 public boolean isPutCoresOnCivShips() { 062 return putCoresOnCivShips; 063 } 064 065 public void setPutCoresOnCivShips(boolean putCoresOnCivShips) { 066 this.putCoresOnCivShips = putCoresOnCivShips; 067 } 068 069 public boolean isForceIntegrateCores() { 070 return forceIntegrateCores; 071 } 072 073 public void setForceIntegrateCores(boolean forceIntegrateCores) { 074 this.forceIntegrateCores = forceIntegrateCores; 075 } 076 077 @Override 078 public int getHandlingPriority(Object params) { 079 if (!(params instanceof GenerateFleetOfficersPickData)) return -1; 080 081 GenerateFleetOfficersPickData data = (GenerateFleetOfficersPickData) params; 082 083 if (data.params != null && !data.params.withOfficers) return -1; 084 085 if (data.params.aiCores != null) return GenericPluginManagerAPI.CORE_SUBSET; 086 087 if (data.fleet == null || !data.fleet.getFaction().getId().equals(Factions.REMNANTS)) return -1; 088 089 return GenericPluginManagerAPI.CORE_SUBSET; 090 } 091 092 093 @Override 094 public void addCommanderAndOfficers(CampaignFleetAPI fleet, FleetParamsV3 params, Random random) { 095 if (random == null) random = Misc.random; 096 FactionAPI faction = fleet.getFaction(); 097 FactionDoctrineAPI doctrine = faction.getDoctrine(); 098 if (!derelictMode && params != null && params.doctrineOverride != null) { 099 doctrine = params.doctrineOverride; 100 } 101 List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy(); 102 if (members.isEmpty()) return; 103 104 Map<String, AICoreOfficerPlugin> plugins = new HashMap<String, AICoreOfficerPlugin>(); 105 106 plugins.put(Commodities.OMEGA_CORE, Misc.getAICoreOfficerPlugin(Commodities.OMEGA_CORE)); 107 plugins.put(Commodities.ALPHA_CORE, Misc.getAICoreOfficerPlugin(Commodities.ALPHA_CORE)); 108 plugins.put(Commodities.BETA_CORE, Misc.getAICoreOfficerPlugin(Commodities.BETA_CORE)); 109 plugins.put(Commodities.GAMMA_CORE, Misc.getAICoreOfficerPlugin(Commodities.GAMMA_CORE)); 110 String nothing = "nothing"; 111 112 float fleetFP = 0f; //fleet.getFleetPoints(); <- doesn't work here, requires a call to fleet.forceSync() 113 for (FleetMemberAPI member : members) { 114 fleetFP += member.getFleetPointCost(); 115 } 116 boolean allowAlphaAnywhere = fleetFP > 150f; 117 boolean allowBetaAnywhere = fleetFP > 75f; 118 119 //boolean integrate = fleetFP > 200f || params.forceIntegrateAICores; 120 boolean integrate = params != null && !params.doNotIntegrateAICores; 121 integrate |= forceIntegrateCores; 122 123 int numCommanderSkills = 0; 124 if (allowBetaAnywhere) numCommanderSkills++; 125 if (allowAlphaAnywhere) numCommanderSkills++; 126 if (params != null && params.noCommanderSkills != null && params.noCommanderSkills) numCommanderSkills = 0; 127 128 129 //float fpPerCore = 20f; 130 float fpPerCore = Global.getSettings().getFloat("baseFPPerAICore"); 131 132 if (derelictMode) { 133 fpPerCore = 30 - 20f * coreMult; 134 } 135 136 int minCores = (int) (fleetFP / fpPerCore * (params != null ? params.officerNumberMult : 1f)); 137 if (params != null) { 138 minCores += params.officerNumberBonus; 139 } 140 if (minCores < 1) minCores = 1; 141 142 boolean debug = true; 143 debug = false; 144 145 WeightedRandomPicker<FleetMemberAPI> withOfficers = new WeightedRandomPicker<FleetMemberAPI>(random); 146 147 int maxSize = 0; 148 for (FleetMemberAPI member : members) { 149 if (!Misc.isAutomated(member)) continue; 150 if (member.isFighterWing()) continue; 151 if (member.isCivilian() && !putCoresOnCivShips) continue; 152 int size = member.getHullSpec().getHullSize().ordinal(); 153 if (size > maxSize) { 154 maxSize = size; 155 } 156 } 157 158 List<FleetMemberAPI> allWithOfficers = new ArrayList<FleetMemberAPI>(); 159 int addedCores = 0; 160 for (FleetMemberAPI member : members) { 161 if (!Misc.isAutomated(member)) continue; 162 if (member.isCivilian() && !putCoresOnCivShips) continue; 163 if (member.isFighterWing()) continue; 164 165 float fp = member.getFleetPointCost(); 166 167 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random); 168 169 if (params != null && params.aiCores == OfficerQuality.AI_GAMMA) { 170 picker.add(Commodities.GAMMA_CORE, fp); 171 } else if (params != null && params.aiCores == OfficerQuality.AI_BETA) { 172 picker.add(Commodities.BETA_CORE, fp); 173 } else if (params != null && params.aiCores == OfficerQuality.AI_ALPHA) { 174 picker.add(Commodities.ALPHA_CORE, fp); 175 } else if (params != null && params.aiCores == OfficerQuality.AI_OMEGA) { 176 picker.add(Commodities.OMEGA_CORE, fp); 177 } else if (params != null && params.aiCores == OfficerQuality.AI_BETA_OR_GAMMA) { 178 if (member.isCapital() || member.isCruiser()) { 179 picker.add(Commodities.BETA_CORE, fp); 180 } else if (allowAlphaAnywhere) { 181 picker.add(Commodities.BETA_CORE, fp); 182 } else { 183 picker.add(Commodities.BETA_CORE, fp/2f); 184 } 185 picker.add(Commodities.GAMMA_CORE, fp); 186 } else { 187 if (derelictMode) { 188 picker.add(Commodities.GAMMA_CORE, fp); 189 } else { 190 if (member.isCapital() || member.isCruiser()) { 191 picker.add(Commodities.ALPHA_CORE, fp); 192 } else if (allowAlphaAnywhere) { 193 picker.add(Commodities.ALPHA_CORE, fp); 194 } 195 196 if (member.isCruiser() || member.isDestroyer()) { 197 picker.add(Commodities.BETA_CORE, fp/2f); 198 } else if (allowBetaAnywhere && member.isFrigate()) { 199 picker.add(Commodities.BETA_CORE, fp); 200 } 201 202 if (member.isDestroyer() || member.isFrigate()) { 203 picker.add(Commodities.GAMMA_CORE, fp); 204 } 205 } 206 } 207 208 if (addedCores >= minCores) { 209 picker.add(nothing, 10f * picker.getTotal()/fp); 210 } 211 212 String pick = picker.pick(); 213 if (debug) { 214 System.out.println("Picked [" + pick + "] for " + member.getHullId()); 215 } 216 AICoreOfficerPlugin plugin = plugins.get(pick); 217 if (plugin != null) { 218 addedCores++; 219 220 PersonAPI person = plugin.createPerson(pick, fleet.getFaction().getId(), random); 221 member.setCaptain(person); 222 if (integrate && !Commodities.OMEGA_CORE.equals(pick)) { 223 integrateAndAdaptCoreForAIFleet(member); 224 } 225 226 if (!member.isFighterWing() && !member.isCivilian()) { 227 withOfficers.add(member, fp); 228 } 229 230 allWithOfficers.add(member); 231 } 232 233 if (addedCores > 0 && params != null && params.officerNumberMult <= 0) { 234 break; // only want to add the fleet commander 235 } 236 } 237 238 if (withOfficers.isEmpty() && !allWithOfficers.isEmpty()) { 239 withOfficers.add(allWithOfficers.get(0), 1f); 240 } 241 242 243 FleetMemberAPI flagship = withOfficers.pick(); 244 if (!derelictMode && !forceNoCommander && flagship != null) { 245 PersonAPI commander = flagship.getCaptain(); 246 commander.setRankId(Ranks.SPACE_COMMANDER); 247 commander.setPostId(Ranks.POST_FLEET_COMMANDER); 248 fleet.setCommander(commander); 249 fleet.getFleetData().setFlagship(flagship); 250 addCommanderSkills(commander, fleet, params, numCommanderSkills, random); 251 } 252 } 253 254 public static void integrateAndAdaptCoreForAIFleet(FleetMemberAPI member) { 255 PersonAPI person = member.getCaptain(); 256 if (!person.isAICore()) return; 257 258 person.getStats().setLevel(person.getStats().getLevel() + 1); 259 260 person.getStats().setSkipRefresh(true); 261 262// if (member.isCarrier()) { 263// person.getStats().setSkillLevel(Skills.STRIKE_COMMANDER, 2); 264// if (person.getStats().getSkillLevel(Skills.POINT_DEFENSE) <= 0) { 265// person.getStats().setSkillLevel(Skills.POINT_DEFENSE, 2); 266// person.getStats().setSkillLevel(Skills.RELIABILITY_ENGINEERING, 0); 267// } 268// } else { 269 if (member.getVariant() != null && member.getVariant().getWeaponGroups() != null) { 270 float weight = 0f; 271 float pdWeight = 0f; 272 float missileWeight = 0f; 273 for (String slotId : member.getVariant().getFittedWeaponSlots()) { 274 WeaponSpecAPI spec = member.getVariant().getWeaponSpec(slotId); 275 if (spec == null) continue; 276 float w = 1f; 277 if (spec.getSize() == WeaponSize.MEDIUM) w = 2f; 278 if (spec.getSize() == WeaponSize.LARGE) w = 4f; 279 weight += w; 280 if (spec.getAIHints().contains(AIHints.PD)) { 281 pdWeight += w; 282 } 283 if (spec.getType() == WeaponType.MISSILE) { 284 missileWeight += w; 285 } 286 287 } 288 289 float decks = member.getNumFlightDecks(); 290 if (decks > 0) { 291 weight += decks * 4f; 292 pdWeight += decks * 4f; 293 } 294 295 boolean hasUsefulPD = pdWeight > weight * 0.25f; 296 boolean hasEnoughMissiles = missileWeight > weight * 0.2f; 297 298 299 if (hasUsefulPD && !hasEnoughMissiles) { 300 person.getStats().setSkillLevel(Skills.POINT_DEFENSE, 2); 301 person.getStats().setSkipRefresh(false); 302 return; 303 } 304 } 305 306 307 if (member.getHullSpec() != null && member.getHullSpec().hasTag(Factions.DERELICT) && 308 person.getStats().getSkillLevel(Skills.BALLISTIC_MASTERY) <= 0) { 309 person.getStats().setSkillLevel(Skills.BALLISTIC_MASTERY, 2); 310 } else { 311 if (person.getStats().getSkillLevel(Skills.ENERGY_WEAPON_MASTERY) <= 0) { 312 person.getStats().setSkillLevel(Skills.ENERGY_WEAPON_MASTERY, 2); 313 } else { 314 person.getStats().setSkillLevel(Skills.MISSILE_SPECIALIZATION, 2); 315 } 316 } 317 318 if (member.isCapital() || member.isStation()) { 319 if (person.getStats().getSkillLevel(Skills.POLARIZED_ARMOR) <= 0) { 320 person.getStats().setSkillLevel(Skills.COMBAT_ENDURANCE, 0); 321 person.getStats().setSkillLevel(Skills.POLARIZED_ARMOR, 2); 322 } 323 } 324 //} 325 326 person.getStats().setSkipRefresh(false); 327 } 328 329 330 public static SkillPickPreference getSkillPrefForShip(FleetMemberAPI member) { 331 return FleetFactoryV3.getSkillPrefForShip(member); 332// float weight = FleetFactoryV3.getMemberWeight(member); 333// float fighters = member.getVariant().getFittedWings().size(); 334// boolean wantCarrierSkills = weight > 0 && fighters / weight >= 0.5f; 335// SkillPickPreference pref = SkillPickPreference.GENERIC; 336// if (wantCarrierSkills) { 337// pref = SkillPickPreference.CARRIER; 338// } else if (member.isPhaseShip()) { 339// pref = SkillPickPreference.PHASE; 340// } 341// 342// return pref; 343 } 344 345 346 public static void addCommanderSkills(PersonAPI commander, CampaignFleetAPI fleet, FleetParamsV3 params, int numSkills, Random random) { 347 if (random == null) random = new Random(); 348 if (numSkills <= 0) return; 349 350 MutableCharacterStatsAPI stats = commander.getStats(); 351 352 FactionDoctrineAPI doctrine = fleet.getFaction().getDoctrine(); 353 if (params != null && params.doctrineOverride != null) { 354 doctrine = params.doctrineOverride; 355 } 356 357 List<String> skills = new ArrayList<String>(doctrine.getCommanderSkills()); 358 if (skills.isEmpty()) return; 359 360 if (random.nextFloat() < doctrine.getCommanderSkillsShuffleProbability()) { 361 Collections.shuffle(skills, random); 362 } 363 364 stats.setSkipRefresh(true); 365 366 boolean debug = true; 367 debug = false; 368 if (debug) System.out.println("Generating commander skills, person level " + stats.getLevel() + ", skills: " + numSkills); 369 int picks = 0; 370 for (String skillId : skills) { 371 if (debug) System.out.println("Selected skill: [" + skillId + "]"); 372 stats.setSkillLevel(skillId, 1); 373 picks++; 374 if (picks >= numSkills) { 375 break; 376 } 377 } 378 if (debug) System.out.println("Done generating commander skills\n"); 379 380 stats.setSkipRefresh(false); 381 stats.refreshCharacterStatsEffects(); 382 } 383 384 385 386 387 388} 389 390 391 392 393 394 395 396 397 398 399 400