1package com.fs.starfarer.api.impl.campaign.events;
4import java.util.ArrayList;
5import java.util.Collections;
6import java.util.HashSet;
7import java.util.Iterator;
10import java.util.Random;
13import org.apache.log4j.Logger;
15import com.fs.starfarer.api.EveryFrameScript;
16import com.fs.starfarer.api.Global;
17import com.fs.starfarer.api.campaign.CampaignClockAPI;
18import com.fs.starfarer.api.campaign.CampaignFleetAPI;
19import com.fs.starfarer.api.campaign.CargoAPI;
20import com.fs.starfarer.api.campaign.FactionAPI;
21import com.fs.starfarer.api.campaign.InteractionDialogAPI;
22import com.fs.starfarer.api.campaign.PlayerMarketTransaction;
23import com.fs.starfarer.api.campaign.TextPanelAPI;
24import com.fs.starfarer.api.campaign.econ.MarketAPI;
25import com.fs.starfarer.api.campaign.listeners.ColonyInteractionListener;
26import com.fs.starfarer.api.campaign.rules.MemoryAPI;
27import com.fs.starfarer.api.characters.AdminData;
28import com.fs.starfarer.api.characters.FullName.Gender;
29import com.fs.starfarer.api.characters.MutableCharacterStatsAPI;
30import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
31import com.fs.starfarer.api.characters.OfficerDataAPI;
32import com.fs.starfarer.api.characters.PersonAPI;
33import com.fs.starfarer.api.characters.SkillSpecAPI;
34import com.fs.starfarer.api.impl.campaign.ids.Factions;
35import com.fs.starfarer.api.impl.campaign.ids.Personalities;
36import com.fs.starfarer.api.impl.campaign.ids.Ranks;
37import com.fs.starfarer.api.impl.campaign.ids.Skills;
38import com.fs.starfarer.api.impl.campaign.ids.Stats;
39import com.fs.starfarer.api.impl.campaign.ids.Tags;
40import com.fs.starfarer.api.impl.campaign.rulecmd.AddRemoveCommodity;
41import com.fs.starfarer.api.impl.campaign.rulecmd.CallEvent.CallableEvent;
42import com.fs.starfarer.api.plugins.OfficerLevelupPlugin;
43import com.fs.starfarer.api.util.IntervalUtil;
44import com.fs.starfarer.api.util.Misc;
45import com.fs.starfarer.api.util.Misc.Token;
46import com.fs.starfarer.api.util.TimeoutTracker;
47import com.fs.starfarer.api.util.WeightedRandomPicker;
59 public static class AvailableOfficer {
60 public PersonAPI person;
61 public String marketId;
62 public int hiringBonus;
64 public float timeRemaining = 0f;
65 public AvailableOfficer(PersonAPI person, String marketId,
int hiringBonus,
int salary) {
67 this.marketId = marketId;
68 this.hiringBonus = hiringBonus;
78 protected List<AvailableOfficer>
available =
new ArrayList<AvailableOfficer>();
79 protected List<AvailableOfficer>
availableAdmins =
new ArrayList<AvailableOfficer>();
90 Object readResolve() {
98 seed = Misc.random.nextLong();
109 if (
market.isPlanetConditionMarketOnly())
return;
110 if (
market.getFaction().isNeutralFaction())
return;
111 if (!
market.isInEconomy())
return;
112 if (
market.hasTag(Tags.MARKET_NO_OFFICER_SPAWN))
return;
117 float officerProb =
market.getStats().getDynamic().getMod(Stats.OFFICER_PROB_MOD).computeEffective(0f);
118 float additionalProb =
market.getStats().getDynamic().getMod(Stats.OFFICER_ADDITIONAL_PROB_MULT_MOD).computeEffective(0f);
119 float mercProb =
market.getStats().getDynamic().getMod(Stats.OFFICER_IS_MERC_PROB_MOD).computeEffective(0f);
120 float adminProb =
market.getStats().getDynamic().getMod(Stats.ADMIN_PROB_MOD).computeEffective(0f);
123 log.info(
"Spawning officers/admins at " +
market.getId());
124 log.info(
" officerProb: " + officerProb);
125 log.info(
" additionalProb: " + additionalProb);
126 log.info(
" mercProb: " + mercProb);
127 log.info(
" adminProb: " + adminProb);
132 long mult = clock.getCycle() * 12L + clock.getMonth();
135 Random random = Misc.getRandom(
seed +
market.getId().hashCode() * mult, 11);
141 if (random.nextFloat() < officerProb) {
142 boolean merc = random.nextFloat() < mercProb;
147 officer.timeRemaining = dur;
149 log.info(
"Added officer at " + officer.marketId +
"");
151 if (random.nextFloat() < officerProb * additionalProb) {
152 merc = random.nextFloat() < mercProb;
156 officer.timeRemaining = dur;
158 log.info(
"Added officer at [" + officer.marketId +
"]");
162 if (random.nextFloat() < adminProb) {
164 officer.timeRemaining = dur;
168 log.info(
"Added admin at [" + officer.marketId +
"]");
173 return 60f + 60f * random.nextFloat();
191 for (AvailableOfficer curr :
new ArrayList<AvailableOfficer>(
available)) {
192 curr.timeRemaining -= interval;
193 if (curr.timeRemaining <= 0) {
195 log.info(
"Removed officer from [" + curr.marketId +
"]");
198 for (AvailableOfficer curr :
new ArrayList<AvailableOfficer>(
availableAdmins)) {
199 curr.timeRemaining -= interval;
200 if (curr.timeRemaining <= 0) {
202 log.info(
"Removed freelance admin from [" + curr.marketId +
"]");
210 for (AvailableOfficer curr :
new ArrayList<AvailableOfficer>(
available)) {
211 if (
Global.
getSector().getEconomy().getMarket(curr.marketId) ==
null) {
215 for (AvailableOfficer curr :
new ArrayList<AvailableOfficer>(
availableAdmins)) {
216 if (
Global.
getSector().getEconomy().getMarket(curr.marketId) ==
null) {
223 if (officer ==
null)
return;
231 if (officer ==
null)
return;
240 if (
market ==
null)
return;
241 market.getCommDirectory().addPerson(officer.person);
242 market.addPerson(officer.person);
244 officer.person.getMemoryWithoutUpdate().set(
"$ome_hireable",
true);
245 officer.person.getMemoryWithoutUpdate().set(
"$ome_eventRef",
this);
246 officer.person.getMemoryWithoutUpdate().set(
"$ome_hiringBonus", Misc.getWithDGS(officer.hiringBonus));
247 officer.person.getMemoryWithoutUpdate().set(
"$ome_salary", Misc.getWithDGS(officer.salary));
251 if (officer ==
null)
return;
258 market.getCommDirectory().removePerson(officer.person);
259 market.removePerson(officer.person);
262 officer.person.getMemoryWithoutUpdate().unset(
"$ome_hireable");
263 officer.person.getMemoryWithoutUpdate().unset(
"$ome_eventRef");
264 officer.person.getMemoryWithoutUpdate().unset(
"$ome_hiringBonus");
265 officer.person.getMemoryWithoutUpdate().unset(
"$ome_salary");
272 WeightedRandomPicker<String> all =
faction.getPortraits(gender);
273 WeightedRandomPicker<String> picker =
new WeightedRandomPicker<String>();
275 Set<String> exclude =
new HashSet<String>();
278 for (OfficerDataAPI od :
Global.
getSector().getPlayerFleet().getFleetData().getOfficersCopy()) {
279 exclude.add(od.getPerson().getPortraitSprite());
282 for (AdminData ad :
Global.
getSector().getCharacterData().getAdmins()) {
283 exclude.add(ad.getPerson().getPortraitSprite());
285 for (String p : all.getItems()) {
286 if (exclude.contains(p))
continue;
289 if (picker.isEmpty()) {
292 return picker.pick();
301 if (
market ==
null)
return null;
303 WeightedRandomPicker<Integer> tierPicker =
new WeightedRandomPicker<Integer>();
304 tierPicker.add(0, 60);
305 tierPicker.add(1, 40);
307 int tier = tierPicker.pick();
310 person.setFaction(Factions.INDEPENDENT);
312 String hireKey =
"adminHireTier" + tier;
315 int salary = (int) Misc.getAdminSalary(person);
317 AvailableOfficer result =
new AvailableOfficer(person,
market.getId(), hiringBonus, salary);
322 if (random ==
null) random =
new Random();
323 PersonAPI person =
faction.createRandomPerson(random);
325 person.getStats().setSkipRefresh(
true);
326 WeightedRandomPicker<String> picker =
new WeightedRandomPicker<String>(random);
328 for (String skillId : allSkillIds) {
330 if (skill.hasTag(Skills.TAG_DEPRECATED))
continue;
331 if (skill.hasTag(Skills.TAG_PLAYER_ONLY))
continue;
332 if (skill.hasTag(Skills.TAG_AI_CORE_ONLY))
continue;
333 if (skill.isAdminSkill()) {
338 for (
int i = 0; i < tier && !picker.isEmpty(); i++) {
339 String pick = picker.pickAndRemove();
340 person.getStats().setSkillLevel(pick, 3);
343 person.getMemoryWithoutUpdate().set(
"$ome_isAdmin",
true);
344 person.getMemoryWithoutUpdate().set(
"$ome_adminTier", tier);
347 person.setRankId(Ranks.CITIZEN);
348 person.setPostId(Ranks.POST_FREELANCE_ADMIN);
351 WeightedRandomPicker<String> personalityPicker =
faction.getPersonalityPicker().clone();
353 String personality = personalityPicker.pick();
354 person.setPersonality(personality);
356 person.getStats().setSkipRefresh(
false);
357 person.getStats().refreshCharacterStatsEffects();
371 if (
market ==
null)
return null;
376 if ((
float) Math.random() > 0.75f) level = 2;
380 PersonAPI person =
null;
386 level = minLevel + Misc.random.nextInt(maxLevel + 1 - minLevel);
389 if (level == maxLevel) numElite = 2;
392 person.setRankId(Ranks.SPACE_CAPTAIN);
393 person.setPostId(Ranks.POST_MERCENARY);
394 Misc.setMercenary(person,
true);
397 person.setPostId(Ranks.POST_OFFICER_FOR_HIRE);
400 person.setFaction(Factions.INDEPENDENT);
404 int salary = (int) Misc.getOfficerSalary(person);
405 AvailableOfficer result =
new AvailableOfficer(person,
market.getId(),
406 (
int) (person.getStats().getLevel() * 2000* payMult), salary);
412 null,
false,
false, -1, random);
415 public static PersonAPI
createMercInternal(FactionAPI
faction,
int level,
int numElite,
boolean allowNonDoctrinePersonality, Random random) {
425 SkillPickPreference pref = SkillPickPreference.ANY;
427 null,
true,
true, numElite, random);
431 public static enum SkillPickPreference {
440 YES_ENERGY_YES_BALLISTIC_YES_MISSILE_YES_DEFENSE,
441 YES_ENERGY_YES_BALLISTIC_NO_MISSILE_YES_DEFENSE,
442 YES_ENERGY_YES_BALLISTIC_YES_MISSILE_NO_DEFENSE,
443 YES_ENERGY_YES_BALLISTIC_NO_MISSILE_NO_DEFENSE,
444 YES_ENERGY_NO_BALLISTIC_YES_MISSILE_YES_DEFENSE,
445 YES_ENERGY_NO_BALLISTIC_NO_MISSILE_YES_DEFENSE,
446 YES_ENERGY_NO_BALLISTIC_YES_MISSILE_NO_DEFENSE,
447 YES_ENERGY_NO_BALLISTIC_NO_MISSILE_NO_DEFENSE,
448 NO_ENERGY_YES_BALLISTIC_YES_MISSILE_YES_DEFENSE,
449 NO_ENERGY_YES_BALLISTIC_NO_MISSILE_YES_DEFENSE,
450 NO_ENERGY_YES_BALLISTIC_YES_MISSILE_NO_DEFENSE,
451 NO_ENERGY_YES_BALLISTIC_NO_MISSILE_NO_DEFENSE,
452 NO_ENERGY_NO_BALLISTIC_YES_MISSILE_YES_DEFENSE,
453 NO_ENERGY_NO_BALLISTIC_NO_MISSILE_YES_DEFENSE,
454 NO_ENERGY_NO_BALLISTIC_YES_MISSILE_NO_DEFENSE,
455 NO_ENERGY_NO_BALLISTIC_NO_MISSILE_NO_DEFENSE,
464 null,
false,
true, -1,
null);
466 public static PersonAPI
createOfficer(FactionAPI
faction,
int level, SkillPickPreference pref, Random random) {
470 public static boolean DEBUG =
false;
472 SkillPickPreference pref,
boolean allowNonDoctrinePersonality,
473 CampaignFleetAPI fleet,
boolean allowAnyLevel,
474 boolean withEliteSkills,
int eliteSkillsNumOverride, Random random) {
475 if (random ==
null) random =
new Random();
479 PersonAPI person =
faction.createRandomPerson(random);
480 person.setFleet(fleet);
483 if (!allowAnyLevel) {
484 if (level > plugin.getMaxLevel(person)) level = plugin.getMaxLevel(person);
487 person.getStats().setSkipRefresh(
true);
489 if (
DEBUG) System.out.println(
"Generating officer\n");
491 List<String> fixedSkills =
new ArrayList<String>(
faction.getDoctrine().getOfficerSkills());
492 Iterator<String> iter = fixedSkills.iterator();
493 while (iter.hasNext()) {
494 String
id = iter.next();
496 if (spec !=
null && spec.hasTag(Skills.TAG_PLAYER_ONLY)) {
501 if (random.nextFloat() <
faction.getDoctrine().getOfficerSkillsShuffleProbability()) {
502 Collections.shuffle(fixedSkills, random);
506 for (
int i = 0; i < 1; i++) {
507 List<String> skills = plugin.pickLevelupSkills(person, random);
508 String skillId =
pickSkill(person, skills, pref, numSpec, random);
509 if (!fixedSkills.isEmpty()) {
510 skillId = fixedSkills.remove(0);
512 if (skillId !=
null) {
513 if (
DEBUG) System.out.println(
"Picking initial skill: " + skillId);
514 person.getStats().increaseSkill(skillId);
516 if (spec.hasTag(Skills.TAG_SPEC)) numSpec++;
524 long xp = plugin.getXPForLevel(level);
526 officerData.addXP(xp,
null,
false);
530 officerData.makeSkillPicks(random);
532 while (officerData.canLevelUp(allowAnyLevel)) {
533 String skillId =
pickSkill(officerData.getPerson(), officerData.getSkillPicks(), pref, numSpec, random);
534 if (!fixedSkills.isEmpty()) {
535 skillId = fixedSkills.remove(0);
537 if (skillId !=
null) {
538 if (
DEBUG) System.out.println(
"Leveling up " + skillId);
539 officerData.levelUp(skillId, random);
541 if (spec.hasTag(Skills.TAG_SPEC)) numSpec++;
543 if (allowAnyLevel && officerData.getSkillPicks().isEmpty()) {
544 officerData.makeSkillPicks(random);
551 if (withEliteSkills && eliteSkillsNumOverride != 0) {
552 int num = eliteSkillsNumOverride;
554 num = plugin.getMaxEliteSkills(person);
559 if (
DEBUG) System.out.println(
"Done\n");
561 person.setRankId(Ranks.SPACE_LIEUTENANT);
562 person.setPostId(Ranks.POST_OFFICER);
565 WeightedRandomPicker<String> personalityPicker =
faction.getPersonalityPicker().clone();
566 if (allowNonDoctrinePersonality) {
567 personalityPicker.add(Personalities.TIMID, 4f);
568 personalityPicker.add(Personalities.CAUTIOUS, 4f);
569 personalityPicker.add(Personalities.STEADY, 4f);
570 personalityPicker.add(Personalities.AGGRESSIVE, 4f);
571 personalityPicker.add(Personalities.RECKLESS, 4f);
574 String personality = personalityPicker.pick();
575 person.setPersonality(personality);
578 person.getStats().setSkipRefresh(
false);
579 person.getStats().refreshCharacterStatsEffects();
585 if (num <= 0)
return;
587 WeightedRandomPicker<String> picker =
new WeightedRandomPicker<String>(random);
588 for (SkillLevelAPI sl : person.getStats().getSkillsCopy()) {
589 if (sl.getSkill().hasTag(Skills.TAG_ELITE_PLAYER_ONLY))
continue;
590 if (sl.getSkill().isAptitudeEffect())
continue;
591 if (!sl.getSkill().isCombatOfficerSkill())
continue;
592 picker.add(sl.getSkill().getId(), 1f);
595 for (
int i = 0; i < num && !picker.isEmpty(); i++) {
596 String
id = picker.pickAndRemove();
598 if (
DEBUG) System.out.println(
"Making skill elite: " +
id);
599 person.getStats().increaseSkill(
id);
604 public static String
pickSkill(PersonAPI person, List<String> skills, SkillPickPreference pref,
int numSpec, Random random) {
605 if (random ==
null) random =
new Random();
607 WeightedRandomPicker<String> picker =
new WeightedRandomPicker<String>(random);
608 List<String>
generic =
new ArrayList<String>();
610 boolean energy = pref.name().contains(
"YES_ENERGY");
611 boolean ballistic = pref.name().contains(
"YES_BALLISTIC");
612 boolean missile = pref.name().contains(
"YES_MISSILE");
613 boolean defense = pref.name().contains(
"YES_DEFENSE");
616 for (String
id : skills) {
622 boolean energySkill = spec.hasTag(Skills.TAG_ENERGY_WEAPONS);
623 boolean ballisticSkill = spec.hasTag(Skills.TAG_BALLISTIC_WEAPONS);
624 boolean missileSkill = spec.hasTag(Skills.TAG_MISSILE_WEAPONS);
625 boolean defenseSkill = spec.hasTag(Skills.TAG_ACTIVE_DEFENSES);
627 boolean preferred =
true;
629 if (pref != SkillPickPreference.ANY) {
630 if (!energy && energySkill) preferred =
false;
631 if (!ballistic && ballisticSkill) preferred =
false;
632 if (!missile && missileSkill) preferred =
false;
633 if (!defense && defenseSkill) preferred =
false;
644 if (spec.hasTag(Skills.TAG_PLAYER_ONLY)) {
657 if (picker.isEmpty()) {
658 picker.addAll(
generic);
659 if (picker.isEmpty()) {
660 picker.addAll(skills);
664 return picker.pick();
668 public boolean callEvent(String ruleId, InteractionDialogAPI dialog, List<Token> params, Map<String, MemoryAPI> memoryMap) {
669 String action = params.get(0).getString(memoryMap);
672 CargoAPI cargo = playerFleet.getCargo();
674 if (action.equals(
"printSkills")) {
675 String personId = params.get(1).getString(memoryMap);
676 AvailableOfficer officer =
getOfficer(personId);
677 boolean admin =
false;
679 if (officer ==
null) {
682 if (officer !=
null) {
683 adminTier = (int) officer.person.getMemoryWithoutUpdate().getFloat(
"$ome_adminTier");
687 if (officer !=
null) {
688 MutableCharacterStatsAPI stats = officer.person.getStats();
689 TextPanelAPI text = dialog.getTextPanel();
691 Color hl = Misc.getHighlightColor();
692 Color red = Misc.getNegativeHighlightColor();
718 text.addSkillPanel(officer.person, admin);
720 text.setFontSmallInsignia();
723 String personality = Misc.lcFirst(officer.person.getPersonalityAPI().getDisplayName());
724 text.addParagraph(
"Personality: " + personality +
", level: " + stats.getLevel());
725 text.highlightInLastPara(hl, personality,
"" + stats.getLevel());
726 text.addParagraph(officer.person.getPersonalityAPI().getDescription());
731 text.setFontInsignia();
733 }
else if (action.equals(
"hireOfficer")) {
734 String personId = params.get(1).getString(memoryMap);
735 AvailableOfficer officer =
getOfficer(personId);
736 boolean admin =
false;
737 if (officer ==
null) {
739 if (officer !=
null) {
740 officer.person.setPostId(Ranks.POST_ADMINISTRATOR);
744 if (officer !=
null) {
749 playerFleet.getFleetData().addOfficer(officer.person);
750 if (Misc.isMercenary(officer.person)) {
751 Misc.setMercHiredNow(officer.person);
753 officer.person.setPostId(Ranks.POST_OFFICER);
756 AddRemoveCommodity.addCreditsLossText(officer.hiringBonus, dialog.getTextPanel());
758 AddRemoveCommodity.addAdminGainText(officer.person, dialog.getTextPanel());
760 AddRemoveCommodity.addOfficerGainText(officer.person, dialog.getTextPanel());
762 playerFleet.getCargo().getCredits().subtract(officer.hiringBonus);
763 if (playerFleet.getCargo().getCredits().get() <= 0) {
764 playerFleet.getCargo().getCredits().set(0);
767 }
else if (action.equals(
"atLimit")) {
769 String personId = params.get(1).getString(memoryMap);
770 AvailableOfficer officer =
getOfficer(personId);
771 boolean admin =
false;
772 if (officer ==
null) {
776 int max = playerFleet.getCommander().getStats().getOfficerNumber().getModifiedInt();
778 max = playerFleet.getCommander().getStats().getAdminNumber().getModifiedInt();
779 return Global.
getSector().getCharacterData().getAdmins().size() >= max;
785 return Misc.getNumNonMercOfficers(playerFleet) >= max;
788 }
else if (action.equals(
"canAfford")) {
789 String personId = params.get(1).getString(memoryMap);
790 AvailableOfficer officer =
getOfficer(personId);
791 if (officer ==
null) {
794 if (officer !=
null) {
795 return playerFleet.getCargo().getCredits().get() >= officer.hiringBonus;
805 for (AvailableOfficer officer:
available) {
806 if (officer.person.getId().equals(personId)) {
813 public AvailableOfficer
getAdmin(String personId) {
815 if (officer.person.getId().equals(personId)) {
static SettingsAPI getSettings()
static FactoryAPI getFactory()
static Logger getLogger(Class c)
static SectorAPI getSector()
AvailableOfficer getAdmin(String personId)
void reportPlayerMarketTransaction(PlayerMarketTransaction transaction)
boolean callEvent(String ruleId, InteractionDialogAPI dialog, List< Token > params, Map< String, MemoryAPI > memoryMap)
void reportPlayerClosedMarket(MarketAPI market)
void addAvailable(AvailableOfficer officer)
static String pickSkill(PersonAPI person, List< String > skills, SkillPickPreference pref, int numSpec, Random random)
float getOfficerDuration(Random random)
List< AvailableOfficer > availableAdmins
static String pickPortraitPreferNonDuplicate(FactionAPI faction, Gender gender)
void removeAvailable(AvailableOfficer officer)
void addAvailableAdmin(AvailableOfficer officer)
static PersonAPI createOfficer(FactionAPI faction, int level, SkillPickPreference pref, Random random)
AvailableOfficer getOfficer(String personId)
void pruneFromRemovedMarkets()
IntervalUtil removeTracker
static PersonAPI createAdmin(FactionAPI faction, int tier, Random random)
static PersonAPI createOfficer(FactionAPI faction, int level, boolean allowNonDoctrinePersonality)
static PersonAPI createOfficerInternal(FactionAPI faction, int level, boolean allowNonDoctrinePersonality, Random random)
void reportPlayerOpenedMarketAndCargoUpdated(MarketAPI market)
AvailableOfficer createOfficer(boolean isMerc, MarketAPI market, Random random)
List< AvailableOfficer > available
AvailableOfficer createAdmin(MarketAPI market, Random random)
TimeoutTracker< String > recentlyChecked
static PersonAPI createOfficer(FactionAPI faction, int level)
void setEventDataAndAddToMarket(AvailableOfficer officer)
void advance(float amount)
static PersonAPI createMercInternal(FactionAPI faction, int level, int numElite, boolean allowNonDoctrinePersonality, Random random)
static PersonAPI createOfficer(FactionAPI faction, int level, SkillPickPreference pref, boolean allowNonDoctrinePersonality, CampaignFleetAPI fleet, boolean allowAnyLevel, boolean withEliteSkills, int eliteSkillsNumOverride, Random random)
static void addEliteSkills(PersonAPI person, int num, Random random)
void reportPlayerOpenedMarket(MarketAPI market)
OfficerDataAPI createOfficerData(PersonAPI person)
SkillSpecAPI getSkillSpec(String skillId)
List< String > getSortedSkillIds()
Object getPlugin(String id)
float getFloat(String key)