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