001package com.fs.starfarer.api.impl.campaign.events; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.HashSet; 006import java.util.Iterator; 007import java.util.List; 008import java.util.Map; 009import java.util.Random; 010import java.util.Set; 011 012import java.awt.Color; 013 014import org.apache.log4j.Logger; 015 016import com.fs.starfarer.api.EveryFrameScript; 017import com.fs.starfarer.api.Global; 018import com.fs.starfarer.api.campaign.CampaignClockAPI; 019import com.fs.starfarer.api.campaign.CampaignFleetAPI; 020import com.fs.starfarer.api.campaign.CargoAPI; 021import com.fs.starfarer.api.campaign.FactionAPI; 022import com.fs.starfarer.api.campaign.InteractionDialogAPI; 023import com.fs.starfarer.api.campaign.PlayerMarketTransaction; 024import com.fs.starfarer.api.campaign.TextPanelAPI; 025import com.fs.starfarer.api.campaign.econ.MarketAPI; 026import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener; 027import com.fs.starfarer.api.campaign.rules.MemoryAPI; 028import com.fs.starfarer.api.characters.AdminData; 029import com.fs.starfarer.api.characters.FullName.Gender; 030import com.fs.starfarer.api.characters.MutableCharacterStatsAPI; 031import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI; 032import com.fs.starfarer.api.characters.OfficerDataAPI; 033import com.fs.starfarer.api.characters.PersonAPI; 034import com.fs.starfarer.api.characters.SkillSpecAPI; 035import com.fs.starfarer.api.impl.campaign.ids.Factions; 036import com.fs.starfarer.api.impl.campaign.ids.Personalities; 037import com.fs.starfarer.api.impl.campaign.ids.Ranks; 038import com.fs.starfarer.api.impl.campaign.ids.Skills; 039import com.fs.starfarer.api.impl.campaign.ids.Stats; 040import com.fs.starfarer.api.impl.campaign.ids.Tags; 041import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity; 042import com.fs.starfarer.api.impl.campaign.rulecmd.CallEvent.CallableEvent; 043import com.fs.starfarer.api.plugins.OfficerLevelupPlugin; 044import com.fs.starfarer.api.util.IntervalUtil; 045import com.fs.starfarer.api.util.Misc; 046import com.fs.starfarer.api.util.Misc.Token; 047import com.fs.starfarer.api.util.TimeoutTracker; 048import com.fs.starfarer.api.util.WeightedRandomPicker; 049 050/** 051 * 052 * @author Alex Mosolov 053 * 054 * extends BaseEventPlugin for in-dev savefile compatibility reasons only 055 * 056 * Copyright 2019 Fractal Softworks, LLC 057 */ 058public class OfficerManagerEvent extends BaseEventPlugin implements CallableEvent, ColonyInteractionListener, EveryFrameScript { 059 060 public static class AvailableOfficer { 061 public PersonAPI person; 062 public String marketId; 063 public int hiringBonus; 064 public int salary; 065 public float timeRemaining = 0f; 066 public AvailableOfficer(PersonAPI person, String marketId, int hiringBonus, int salary) { 067 this.person = person; 068 this.marketId = marketId; 069 this.hiringBonus = hiringBonus; 070 this.salary = salary; 071 } 072 073 } 074 075 public static Logger log = Global.getLogger(OfficerManagerEvent.class); 076 077 protected IntervalUtil removeTracker = new IntervalUtil(1f, 3f); 078 079 protected List<AvailableOfficer> available = new ArrayList<AvailableOfficer>(); 080 protected List<AvailableOfficer> availableAdmins = new ArrayList<AvailableOfficer>(); 081 082 protected TimeoutTracker<String> recentlyChecked = new TimeoutTracker<String>(); 083 084 protected long seed = 0; 085 086 public OfficerManagerEvent() { 087 readResolve(); 088 Global.getSector().getListenerManager().addListener(this); 089 } 090 091 Object readResolve() { 092 if (availableAdmins == null) { 093 availableAdmins = new ArrayList<AvailableOfficer>(); 094 } 095 if (recentlyChecked == null) { 096 recentlyChecked = new TimeoutTracker<String>(); 097 } 098 if (seed == 0) { 099 seed = Misc.random.nextLong(); 100 } 101// Global.getSector().getListenerManager().addListener(this); 102 return this; 103 } 104 105 public void reportPlayerClosedMarket(MarketAPI market) {} 106 107 public void reportPlayerOpenedMarket(MarketAPI market) { 108 if (recentlyChecked.contains(market.getId())) return; 109 110 if (market.isPlanetConditionMarketOnly()) return; 111 if (market.getFaction().isNeutralFaction()) return; 112 if (!market.isInEconomy()) return; 113 if (market.hasTag(Tags.MARKET_NO_OFFICER_SPAWN)) return; 114 115 116 pruneFromRemovedMarkets(); 117 118 float officerProb = market.getStats().getDynamic().getMod(Stats.OFFICER_PROB_MOD).computeEffective(0f); 119 float additionalProb = market.getStats().getDynamic().getMod(Stats.OFFICER_ADDITIONAL_PROB_MULT_MOD).computeEffective(0f); 120 float mercProb = market.getStats().getDynamic().getMod(Stats.OFFICER_IS_MERC_PROB_MOD).computeEffective(0f); 121 float adminProb = market.getStats().getDynamic().getMod(Stats.ADMIN_PROB_MOD).computeEffective(0f); 122 //adminProb = 1f; 123 124 log.info("Spawning officers/admins at " + market.getId()); 125 log.info(" officerProb: " + officerProb); 126 log.info(" additionalProb: " + additionalProb); 127 log.info(" mercProb: " + mercProb); 128 log.info(" adminProb: " + adminProb); 129 log.info(""); 130 131 132 CampaignClockAPI clock = Global.getSector().getClock(); 133 long mult = clock.getCycle() * 12L + clock.getMonth(); 134 135 //Random random = new Random(seed + market.getId().hashCode() * mult); 136 Random random = Misc.getRandom(seed + market.getId().hashCode() * mult, 11); 137 //random = new Random(); 138 139 float dur = getOfficerDuration(random); 140 recentlyChecked.add(market.getId(), dur * 0.5f); 141 142 if (random.nextFloat() < officerProb) { 143 boolean merc = random.nextFloat() < mercProb; 144 AvailableOfficer officer = createOfficer(merc, market, random); 145 //officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(officer.person.getFaction(), officer.person.getGender())); 146 // always independent at this point 147 officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(market.getFaction(), officer.person.getGender())); 148 officer.timeRemaining = dur; 149 addAvailable(officer); 150 log.info("Added officer at " + officer.marketId + ""); 151 152 if (random.nextFloat() < officerProb * additionalProb) { 153 merc = random.nextFloat() < mercProb; 154 officer = createOfficer(merc, market, random); 155 //officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(officer.person.getFaction(), officer.person.getGender())); 156 officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(market.getFaction(), officer.person.getGender())); 157 officer.timeRemaining = dur; 158 addAvailable(officer); 159 log.info("Added officer at [" + officer.marketId + "]"); 160 } 161 } 162 163 if (random.nextFloat() < adminProb) { 164 AvailableOfficer officer = createAdmin(market, random); 165 officer.timeRemaining = dur; 166 //officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(officer.person.getFaction(), officer.person.getGender())); 167 officer.person.setPortraitSprite(pickPortraitPreferNonDuplicate(market.getFaction(), officer.person.getGender())); 168 addAvailableAdmin(officer); 169 log.info("Added admin at [" + officer.marketId + "]"); 170 } 171 } 172 173 protected float getOfficerDuration(Random random) { 174 return 60f + 60f * random.nextFloat(); 175 } 176 177 public void advance(float amount) { 178 float days = Global.getSector().getClock().convertToDays(amount); 179 180 recentlyChecked.advance(days); 181 182// if (!Global.getSector().getListenerManager().hasListener(this)) { 183// Global.getSector().getListenerManager().addListener(this); 184// } 185 186 removeTracker.advance(days); 187 if (removeTracker.intervalElapsed()) { 188 pruneFromRemovedMarkets(); 189 190 float interval = removeTracker.getIntervalDuration(); 191 192 for (AvailableOfficer curr : new ArrayList<AvailableOfficer>(available)) { 193 curr.timeRemaining -= interval; 194 if (curr.timeRemaining <= 0) { 195 removeAvailable(curr); 196 log.info("Removed officer from [" + curr.marketId + "]"); 197 } 198 } 199 for (AvailableOfficer curr : new ArrayList<AvailableOfficer>(availableAdmins)) { 200 curr.timeRemaining -= interval; 201 if (curr.timeRemaining <= 0) { 202 removeAvailable(curr); 203 log.info("Removed freelance admin from [" + curr.marketId + "]"); 204 } 205 } 206 } 207 208 } 209 210 public void pruneFromRemovedMarkets() { 211 for (AvailableOfficer curr : new ArrayList<AvailableOfficer>(available)) { 212 if (Global.getSector().getEconomy().getMarket(curr.marketId) == null) { 213 removeAvailable(curr); 214 } 215 } 216 for (AvailableOfficer curr : new ArrayList<AvailableOfficer>(availableAdmins)) { 217 if (Global.getSector().getEconomy().getMarket(curr.marketId) == null) { 218 removeAvailable(curr); 219 } 220 } 221 } 222 223 public void addAvailable(AvailableOfficer officer) { 224 if (officer == null) return; 225 226 available.add(officer); 227 228 setEventDataAndAddToMarket(officer); 229 } 230 231 public void addAvailableAdmin(AvailableOfficer officer) { 232 if (officer == null) return; 233 234 availableAdmins.add(officer); 235 236 setEventDataAndAddToMarket(officer); 237 } 238 239 protected void setEventDataAndAddToMarket(AvailableOfficer officer) { 240 MarketAPI market = Global.getSector().getEconomy().getMarket(officer.marketId); 241 if (market == null) return; 242 market.getCommDirectory().addPerson(officer.person); 243 market.addPerson(officer.person); 244 245 officer.person.getMemoryWithoutUpdate().set("$ome_hireable", true); 246 officer.person.getMemoryWithoutUpdate().set("$ome_eventRef", this); 247 officer.person.getMemoryWithoutUpdate().set("$ome_hiringBonus", Misc.getWithDGS(officer.hiringBonus)); 248 officer.person.getMemoryWithoutUpdate().set("$ome_salary", Misc.getWithDGS(officer.salary)); 249 } 250 251 public void removeAvailable(AvailableOfficer officer) { 252 if (officer == null) return; 253 254 available.remove(officer); 255 availableAdmins.remove(officer); 256 257 MarketAPI market = Global.getSector().getEconomy().getMarket(officer.marketId); 258 if (market != null) { 259 market.getCommDirectory().removePerson(officer.person); 260 market.removePerson(officer.person); 261 } 262 263 officer.person.getMemoryWithoutUpdate().unset("$ome_hireable"); 264 officer.person.getMemoryWithoutUpdate().unset("$ome_eventRef"); 265 officer.person.getMemoryWithoutUpdate().unset("$ome_hiringBonus"); 266 officer.person.getMemoryWithoutUpdate().unset("$ome_salary"); 267 } 268 269 public static String pickPortraitPreferNonDuplicate(FactionAPI faction, Gender gender) { 270 if (faction == null) { 271 faction = Global.getSector().getFaction(Factions.INDEPENDENT); 272 } 273 WeightedRandomPicker<String> all = faction.getPortraits(gender); 274 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(); 275 276 Set<String> exclude = new HashSet<String>(); 277 exclude.add(Global.getSector().getPlayerPerson().getPortraitSprite()); 278 if (Global.getSector().getPlayerFleet() != null) { 279 for (OfficerDataAPI od : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) { 280 exclude.add(od.getPerson().getPortraitSprite()); 281 } 282 } 283 for (AdminData ad : Global.getSector().getCharacterData().getAdmins()) { 284 exclude.add(ad.getPerson().getPortraitSprite()); 285 } 286 for (String p : all.getItems()) { 287 if (exclude.contains(p)) continue; 288 picker.add(p); 289 } 290 if (picker.isEmpty()) { 291 picker = all; 292 } 293 return picker.pick(); 294 } 295 296 protected AvailableOfficer createAdmin(MarketAPI market, Random random) { 297// WeightedRandomPicker<MarketAPI> marketPicker = new WeightedRandomPicker<MarketAPI>(); 298// for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 299// marketPicker.add(market, market.getSize()); 300// } 301// MarketAPI market = marketPicker.pick(); 302 if (market == null) return null; 303 304 WeightedRandomPicker<Integer> tierPicker = new WeightedRandomPicker<Integer>(); 305 tierPicker.add(0, 60); 306 tierPicker.add(1, 40); 307 308 int tier = tierPicker.pick(); 309 310 PersonAPI person = createAdmin(market.getFaction(), tier, random); 311 person.setFaction(Factions.INDEPENDENT); 312 313 String hireKey = "adminHireTier" + tier; 314 int hiringBonus = Global.getSettings().getInt(hireKey); 315 316 int salary = (int) Misc.getAdminSalary(person); 317 318 AvailableOfficer result = new AvailableOfficer(person, market.getId(), hiringBonus, salary); 319 return result; 320 } 321 322 public static PersonAPI createAdmin(FactionAPI faction, int tier, Random random) { 323 if (random == null) random = new Random(); 324 PersonAPI person = faction.createRandomPerson(random); 325 326 person.getStats().setSkipRefresh(true); 327 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random); 328 List<String> allSkillIds = Global.getSettings().getSortedSkillIds(); 329 for (String skillId : allSkillIds) { 330 SkillSpecAPI skill = Global.getSettings().getSkillSpec(skillId); 331 if (skill.hasTag(Skills.TAG_DEPRECATED)) continue; 332 if (skill.hasTag(Skills.TAG_PLAYER_ONLY)) continue; 333 if (skill.hasTag(Skills.TAG_AI_CORE_ONLY)) continue; 334 if (skill.isAdminSkill()) { 335 picker.add(skillId); 336 } 337 } 338 339 for (int i = 0; i < tier && !picker.isEmpty(); i++) { 340 String pick = picker.pickAndRemove(); 341 person.getStats().setSkillLevel(pick, 3); 342 } 343 344 person.getMemoryWithoutUpdate().set("$ome_isAdmin", true); 345 person.getMemoryWithoutUpdate().set("$ome_adminTier", tier); 346 347 348 person.setRankId(Ranks.CITIZEN); 349 person.setPostId(Ranks.POST_FREELANCE_ADMIN); 350 351 352 WeightedRandomPicker<String> personalityPicker = faction.getPersonalityPicker().clone(); 353 354 String personality = personalityPicker.pick(); 355 person.setPersonality(personality); 356 357 person.getStats().setSkipRefresh(false); 358 person.getStats().refreshCharacterStatsEffects(); 359 360 //person.setPortraitSprite(pickPortrait(person.getFaction(), person.getGender())); 361 362 return person; 363 } 364 365 366 protected AvailableOfficer createOfficer(boolean isMerc, MarketAPI market, Random random) { 367// WeightedRandomPicker<MarketAPI> marketPicker = new WeightedRandomPicker<MarketAPI>(); 368// for (MarketAPI market : Global.getSector().getEconomy().getMarketsCopy()) { 369// marketPicker.add(market, market.getSize()); 370// } 371// MarketAPI market = marketPicker.pick(); 372 if (market == null) return null; 373 374 //FactionAPI faction = Global.getSector().getFaction(Factions.INDEPENDENT); 375 376 int level = 1; 377 if ((float) Math.random() > 0.75f) level = 2; 378 379 float payMult = 1f; 380 381 PersonAPI person = null; 382 if (isMerc) { 383 payMult = Global.getSettings().getFloat("officerMercPayMult"); 384 385 int minLevel = Global.getSettings().getInt("officerMercMinLevel"); 386 int maxLevel = Global.getSettings().getInt("officerMercMaxLevel"); 387 level = minLevel + Misc.random.nextInt(maxLevel + 1 - minLevel); 388 389 int numElite = 1; 390 if (level == maxLevel) numElite = 2; 391 person = createMercInternal(market.getFaction(), level, numElite, true, random); 392 393 person.setRankId(Ranks.SPACE_CAPTAIN); 394 person.setPostId(Ranks.POST_MERCENARY); 395 Misc.setMercenary(person, true); 396 } else { 397 person = createOfficerInternal(market.getFaction(), level, true, random); 398 person.setPostId(Ranks.POST_OFFICER_FOR_HIRE); 399 } 400 401 person.setFaction(Factions.INDEPENDENT); 402 403 404 405 int salary = (int) Misc.getOfficerSalary(person); 406 AvailableOfficer result = new AvailableOfficer(person, market.getId(), 407 (int) (person.getStats().getLevel() * 2000* payMult), salary); 408 return result; 409 } 410 411 public static PersonAPI createOfficerInternal(FactionAPI faction, int level, boolean allowNonDoctrinePersonality, Random random) { 412 return createOfficer(faction, level, SkillPickPreference.ANY, allowNonDoctrinePersonality, 413 null, false, false, -1, random); 414 } 415 416 public static PersonAPI createMercInternal(FactionAPI faction, int level, int numElite, boolean allowNonDoctrinePersonality, Random random) { 417// SkillPickPreference pref = SkillPickPreference.GENERIC; 418// float f = (float) Math.random(); 419// if (f < 0.05f) { 420// pref = SkillPickPreference.ANY; 421// } else if (f < 0.1f) { 422// pref = SkillPickPreference.PHASE; 423// } else if (f < 0.25f) { 424// pref = SkillPickPreference.CARRIER; 425// } 426 SkillPickPreference pref = SkillPickPreference.ANY; 427 return createOfficer(faction, level, pref, allowNonDoctrinePersonality, 428 null, true, true, numElite, random); 429 } 430 431 432 public static enum SkillPickPreference { 433 @Deprecated CARRIER, 434 @Deprecated GENERIC, 435 @Deprecated PHASE, 436 437 /** 438 * Passing essentially three params using this enum to maintain API backwards compability with 0.95a, sigh. 439 * It's 4 now, bigger sigh. 440 */ 441 YES_ENERGY_YES_BALLISTIC_YES_MISSILE_YES_DEFENSE, 442 YES_ENERGY_YES_BALLISTIC_NO_MISSILE_YES_DEFENSE, 443 YES_ENERGY_YES_BALLISTIC_YES_MISSILE_NO_DEFENSE, 444 YES_ENERGY_YES_BALLISTIC_NO_MISSILE_NO_DEFENSE, 445 YES_ENERGY_NO_BALLISTIC_YES_MISSILE_YES_DEFENSE, 446 YES_ENERGY_NO_BALLISTIC_NO_MISSILE_YES_DEFENSE, 447 YES_ENERGY_NO_BALLISTIC_YES_MISSILE_NO_DEFENSE, 448 YES_ENERGY_NO_BALLISTIC_NO_MISSILE_NO_DEFENSE, 449 NO_ENERGY_YES_BALLISTIC_YES_MISSILE_YES_DEFENSE, 450 NO_ENERGY_YES_BALLISTIC_NO_MISSILE_YES_DEFENSE, 451 NO_ENERGY_YES_BALLISTIC_YES_MISSILE_NO_DEFENSE, 452 NO_ENERGY_YES_BALLISTIC_NO_MISSILE_NO_DEFENSE, 453 NO_ENERGY_NO_BALLISTIC_YES_MISSILE_YES_DEFENSE, 454 NO_ENERGY_NO_BALLISTIC_NO_MISSILE_YES_DEFENSE, 455 NO_ENERGY_NO_BALLISTIC_YES_MISSILE_NO_DEFENSE, 456 NO_ENERGY_NO_BALLISTIC_NO_MISSILE_NO_DEFENSE, 457 ANY, 458 } 459 460 public static PersonAPI createOfficer(FactionAPI faction, int level) { 461 return createOfficer(faction, level, false); 462 } 463 public static PersonAPI createOfficer(FactionAPI faction, int level, boolean allowNonDoctrinePersonality) { 464 return createOfficer(faction, level, SkillPickPreference.ANY, allowNonDoctrinePersonality, 465 null, false, true, -1, null); 466 } 467 public static PersonAPI createOfficer(FactionAPI faction, int level, SkillPickPreference pref, Random random) { 468 return createOfficer(faction, level, pref, false, null, false, true, -1, random); 469 } 470 471 public static boolean DEBUG = false; 472 public static PersonAPI createOfficer(FactionAPI faction, int level, 473 SkillPickPreference pref, boolean allowNonDoctrinePersonality, 474 CampaignFleetAPI fleet, boolean allowAnyLevel, 475 boolean withEliteSkills, int eliteSkillsNumOverride, Random random) { 476 if (random == null) random = new Random(); 477 478 //DEBUG = true; 479 480 PersonAPI person = faction.createRandomPerson(random); 481 person.setFleet(fleet); 482 OfficerLevelupPlugin plugin = (OfficerLevelupPlugin) Global.getSettings().getPlugin("officerLevelUp"); 483 484 if (!allowAnyLevel) { 485 if (level > plugin.getMaxLevel(person)) level = plugin.getMaxLevel(person); 486 } 487 488 person.getStats().setSkipRefresh(true); 489 490 if (DEBUG) System.out.println("Generating officer\n"); 491 492 List<String> fixedSkills = new ArrayList<String>(faction.getDoctrine().getOfficerSkills()); 493 Iterator<String> iter = fixedSkills.iterator(); 494 while (iter.hasNext()) { 495 String id = iter.next(); 496 SkillSpecAPI spec = Global.getSettings().getSkillSpec(id); 497 if (spec != null && spec.hasTag(Skills.TAG_PLAYER_ONLY)) { 498 iter.remove(); 499 } 500 } 501 502 if (random.nextFloat() < faction.getDoctrine().getOfficerSkillsShuffleProbability()) { 503 Collections.shuffle(fixedSkills, random); 504 } 505 506 int numSpec = 0; 507 for (int i = 0; i < 1; i++) { 508 List<String> skills = plugin.pickLevelupSkills(person, random); 509 String skillId = pickSkill(person, skills, pref, numSpec, random); 510 if (!fixedSkills.isEmpty()) { 511 skillId = fixedSkills.remove(0); 512 } 513 if (skillId != null) { 514 if (DEBUG) System.out.println("Picking initial skill: " + skillId); 515 person.getStats().increaseSkill(skillId); 516 SkillSpecAPI spec = Global.getSettings().getSkillSpec(skillId); 517 if (spec.hasTag(Skills.TAG_SPEC)) numSpec++; 518 519 } 520 } 521 522// level = 20; 523// pref = SkillPickPreference.NON_CARRIER; 524 525 long xp = plugin.getXPForLevel(level); 526 OfficerDataAPI officerData = Global.getFactory().createOfficerData(person); 527 officerData.addXP(xp, null, false); 528 529 //DEBUG = true; 530 531 officerData.makeSkillPicks(random); 532 533 while (officerData.canLevelUp(allowAnyLevel)) { 534 String skillId = pickSkill(officerData.getPerson(), officerData.getSkillPicks(), pref, numSpec, random); 535 if (!fixedSkills.isEmpty()) { 536 skillId = fixedSkills.remove(0); 537 } 538 if (skillId != null) { 539 if (DEBUG) System.out.println("Leveling up " + skillId); 540 officerData.levelUp(skillId, random); 541 SkillSpecAPI spec = Global.getSettings().getSkillSpec(skillId); 542 if (spec.hasTag(Skills.TAG_SPEC)) numSpec++; 543 544 if (allowAnyLevel && officerData.getSkillPicks().isEmpty()) { 545 officerData.makeSkillPicks(random); 546 } 547 } else { 548 break; 549 } 550 } 551 552 if (withEliteSkills && eliteSkillsNumOverride != 0) { 553 int num = eliteSkillsNumOverride; 554 if (num < 0) { 555 num = plugin.getMaxEliteSkills(person); 556 if (num > 1 && faction != null && faction.getId().startsWith("tri")) { 557 //System.out.println("32fwefwe"); 558 num = plugin.getMaxEliteSkills(person); 559 } 560 } 561 addEliteSkills(person, num, random); 562 } 563 564 if (DEBUG) System.out.println("Done\n"); 565 566 person.setRankId(Ranks.SPACE_LIEUTENANT); 567 person.setPostId(Ranks.POST_OFFICER); 568 569 570 WeightedRandomPicker<String> personalityPicker = faction.getPersonalityPicker().clone(); 571 if (allowNonDoctrinePersonality) { 572 personalityPicker.add(Personalities.TIMID, 4f); 573 personalityPicker.add(Personalities.CAUTIOUS, 4f); 574 personalityPicker.add(Personalities.STEADY, 4f); 575 personalityPicker.add(Personalities.AGGRESSIVE, 4f); 576 personalityPicker.add(Personalities.RECKLESS, 4f); 577 } 578 579 String personality = personalityPicker.pick(); 580 person.setPersonality(personality); 581 582 583 person.getStats().setSkipRefresh(false); 584 person.getStats().refreshCharacterStatsEffects(); 585 586 return person; 587 } 588 589 public static void addEliteSkills(PersonAPI person, int num, Random random) { 590 if (num <= 0) return; 591 592 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random); 593 for (SkillLevelAPI sl : person.getStats().getSkillsCopy()) { 594 if (sl.getSkill().hasTag(Skills.TAG_ELITE_PLAYER_ONLY)) continue; 595 if (sl.getSkill().isAptitudeEffect()) continue; 596 if (!sl.getSkill().isCombatOfficerSkill()) continue; 597 picker.add(sl.getSkill().getId(), 1f); 598 } 599 600 for (int i = 0; i < num && !picker.isEmpty(); i++) { 601 String id = picker.pickAndRemove(); 602 if (id != null) { 603 if (DEBUG) System.out.println("Making skill elite: " + id); 604 person.getStats().increaseSkill(id); 605 } 606 } 607 } 608 609 public static String pickSkill(PersonAPI person, List<String> skills, SkillPickPreference pref, int numSpec, Random random) { 610 if (random == null) random = new Random(); 611 612 WeightedRandomPicker<String> picker = new WeightedRandomPicker<String>(random); 613 List<String> generic = new ArrayList<String>(); 614 615 boolean energy = pref.name().contains("YES_ENERGY"); // lol 616 boolean ballistic = pref.name().contains("YES_BALLISTIC"); 617 boolean missile = pref.name().contains("YES_MISSILE"); 618 boolean defense = pref.name().contains("YES_DEFENSE"); 619 620 621 for (String id : skills) { 622 SkillSpecAPI spec = Global.getSettings().getSkillSpec(id); 623// boolean carrierSkill = spec.hasTag(Skills.TAG_CARRIER); 624// boolean phaseSkill = spec.hasTag(Skills.TAG_PHASE); 625// boolean specSkill = spec.hasTag(Skills.TAG_SPEC); 626 627 boolean energySkill = spec.hasTag(Skills.TAG_ENERGY_WEAPONS); 628 boolean ballisticSkill = spec.hasTag(Skills.TAG_BALLISTIC_WEAPONS); 629 boolean missileSkill = spec.hasTag(Skills.TAG_MISSILE_WEAPONS); 630 boolean defenseSkill = spec.hasTag(Skills.TAG_ACTIVE_DEFENSES); 631 632 boolean preferred = true; 633 634 if (pref != SkillPickPreference.ANY) { 635 if (!energy && energySkill) preferred = false; 636 if (!ballistic && ballisticSkill) preferred = false; 637 if (!missile && missileSkill) preferred = false; 638 if (!defense && defenseSkill) preferred = false; 639 } 640 641// preferred |= pref == SkillPickPreference.ANY; 642// preferred |= pref == SkillPickPreference.CARRIER && carrierSkill; 643// preferred |= pref == SkillPickPreference.PHASE && phaseSkill; 644// preferred |= pref == SkillPickPreference.GENERIC && !phaseSkill && !carrierSkill; 645 646// if (specSkill && !carrierSkill && !phaseSkill && numSpec >= 1) { 647// preferred = false; 648// } 649 if (spec.hasTag(Skills.TAG_PLAYER_ONLY)) { 650 preferred = false; 651 } 652 653 if (preferred) { 654 picker.add(id); 655 } else { 656 generic.add(id); 657 } 658 659 //if ((!specSkill || numSpec < 1) && !carrierSkill && !phaseSkill) { 660 //} 661 } 662 if (picker.isEmpty()) { 663 picker.addAll(generic); 664 if (picker.isEmpty()) { 665 picker.addAll(skills); 666 } 667 } 668 669 return picker.pick(); 670 } 671 672 673 public boolean callEvent(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) { 674 String action = params.get(0).getString(memoryMap); 675 676 CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet(); 677 CargoAPI cargo = playerFleet.getCargo(); 678 679 if (action.equals("printSkills")) { 680 String personId = params.get(1).getString(memoryMap); 681 AvailableOfficer officer = getOfficer(personId); 682 boolean admin = false; 683 int adminTier = 0; 684 if (officer == null) { 685 officer = getAdmin(personId); 686 admin = true; 687 if (officer != null) { 688 adminTier = (int) officer.person.getMemoryWithoutUpdate().getFloat("$ome_adminTier"); 689 } 690 } 691 692 if (officer != null) { 693 MutableCharacterStatsAPI stats = officer.person.getStats(); 694 TextPanelAPI text = dialog.getTextPanel(); 695 696 Color hl = Misc.getHighlightColor(); 697 Color red = Misc.getNegativeHighlightColor(); 698 699 //text.addParagraph("-----------------------------------------------------------------------------"); 700 701// if (!admin) { 702// text.addParagraph("Level: " + (int) stats.getLevel()); 703// text.highlightInLastPara(hl, "" + (int) stats.getLevel()); 704// } 705// for (String skillId : Global.getSettings().getSortedSkillIds()) { 706// int level = (int) stats.getSkillLevel(skillId); 707// if (level > 0) { 708// SkillSpecAPI spec = Global.getSettings().getSkillSpec(skillId); 709// String skillName = spec.getName(); 710// if (spec.isAptitudeEffect()) { 711// skillName += " Aptitude"; 712// } 713// 714// if (level <= 1) { 715// text.addParagraph(skillName); 716// } else { 717// text.addParagraph(skillName + " (Elite)"); 718// } 719// //text.highlightInLastPara(hl, "" + level); 720// } 721// } 722 723 text.addSkillPanel(officer.person, admin); 724 725 text.setFontSmallInsignia(); 726 727 if (!admin) { 728 String personality = Misc.lcFirst(officer.person.getPersonalityAPI().getDisplayName()); 729 text.addParagraph("Personality: " + personality + ", level: " + stats.getLevel()); 730 text.highlightInLastPara(hl, personality, "" + stats.getLevel()); 731 text.addParagraph(officer.person.getPersonalityAPI().getDescription()); 732 } 733 734 //text.addParagraph("-----------------------------------------------------------------------------"); 735 736 text.setFontInsignia(); 737 } 738 } else if (action.equals("hireOfficer")) { 739 String personId = params.get(1).getString(memoryMap); 740 AvailableOfficer officer = getOfficer(personId); 741 boolean admin = false; 742 if (officer == null) { 743 officer = getAdmin(personId); 744 if (officer != null) { 745 officer.person.setPostId(Ranks.POST_ADMINISTRATOR); 746 } 747 admin = true; 748 } 749 if (officer != null) { 750 removeAvailable(officer); 751 if (admin) { 752 Global.getSector().getCharacterData().addAdmin(officer.person); 753 } else { 754 playerFleet.getFleetData().addOfficer(officer.person); 755 if (Misc.isMercenary(officer.person)) { 756 Misc.setMercHiredNow(officer.person); 757 } else { 758 officer.person.setPostId(Ranks.POST_OFFICER); 759 } 760 } 761 AddRemoveCommodity.addCreditsLossText(officer.hiringBonus, dialog.getTextPanel()); 762 if (admin) { 763 AddRemoveCommodity.addAdminGainText(officer.person, dialog.getTextPanel()); 764 } else { 765 AddRemoveCommodity.addOfficerGainText(officer.person, dialog.getTextPanel()); 766 } 767 playerFleet.getCargo().getCredits().subtract(officer.hiringBonus); 768 if (playerFleet.getCargo().getCredits().get() <= 0) { 769 playerFleet.getCargo().getCredits().set(0); 770 } 771 } 772 } else if (action.equals("atLimit")) { 773 //int max = (int) Global.getSettings().getFloat("officerPlayerMax"); 774 String personId = params.get(1).getString(memoryMap); 775 AvailableOfficer officer = getOfficer(personId); 776 boolean admin = false; 777 if (officer == null) { 778 officer = getAdmin(personId); 779 admin = true; 780 } 781 int max = playerFleet.getCommander().getStats().getOfficerNumber().getModifiedInt(); 782 if (admin) { 783 max = playerFleet.getCommander().getStats().getAdminNumber().getModifiedInt(); 784 return Global.getSector().getCharacterData().getAdmins().size() >= max; 785 } 786 787 // can hire more than max number of admins, just can't assign to govern w/o penalty 788 //if (admin) return false; 789 790 return Misc.getNumNonMercOfficers(playerFleet) >= max; 791 792 //return playerFleet.getFleetData().getOfficersCopy().size() >= max; 793 } else if (action.equals("canAfford")) { 794 String personId = params.get(1).getString(memoryMap); 795 AvailableOfficer officer = getOfficer(personId); 796 if (officer == null) { 797 officer = getAdmin(personId); 798 } 799 if (officer != null) { 800 return playerFleet.getCargo().getCredits().get() >= officer.hiringBonus; 801 } else { 802 return false; 803 } 804 } 805 806 return true; 807 } 808 809 public AvailableOfficer getOfficer(String personId) { 810 for (AvailableOfficer officer: available) { 811 if (officer.person.getId().equals(personId)) { 812 return officer; 813 } 814 } 815 return null; 816 } 817 818 public AvailableOfficer getAdmin(String personId) { 819 for (AvailableOfficer officer: availableAdmins) { 820 if (officer.person.getId().equals(personId)) { 821 return officer; 822 } 823 } 824 return null; 825 } 826 827 public void reportPlayerMarketTransaction(PlayerMarketTransaction transaction) { 828 829 } 830 831 public void reportPlayerOpenedMarketAndCargoUpdated(MarketAPI market) { 832 833 } 834 835 public boolean runWhilePaused() { 836 return false; 837 } 838 839 public boolean isDone() { 840 return false; 841 } 842} 843 844 845 846 847 848 849 850 851 852