001package com.fs.starfarer.api.characters;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.LinkedHashMap;
006import java.util.LinkedHashSet;
007import java.util.List;
008import java.util.Map;
009import java.util.Set;
010
011import com.fs.starfarer.api.Global;
012import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
013import com.fs.starfarer.api.fleet.FleetMemberAPI;
014import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
015import com.fs.starfarer.api.impl.campaign.ids.Stats;
016import com.fs.starfarer.api.impl.campaign.plog.OfficerSkillGainRecord;
017import com.fs.starfarer.api.impl.campaign.plog.PlaythroughLog;
018import com.fs.starfarer.api.ui.Alignment;
019import com.fs.starfarer.api.ui.ButtonAPI;
020import com.fs.starfarer.api.ui.TooltipMakerAPI;
021import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator;
022import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipLocation;
023import com.fs.starfarer.api.util.Misc;
024
025public class SkillsChangeOfficerEffect extends BaseSkillsChangeEffect {
026
027        public static class OfficerEffectData {
028                OfficerDataAPI data;
029                int newLevel;
030                List<String> removeSkills = new ArrayList<String>();
031                List<String> removeElite = new ArrayList<String>();
032                FleetMemberAPI member;
033                boolean unusable = false;
034                boolean makeMercenary;
035                
036                ButtonAPI buttonMerc;
037                ButtonAPI buttonOther;
038                
039                public boolean hasChanges() {
040                        return data.getPerson().getStats().getLevel() != newLevel || unusable ||
041                                        !removeSkills.isEmpty() || !removeElite.isEmpty() || makeMercenary;
042                }
043        }
044        
045        public static class OfficerDataMap {
046                Map<PersonAPI, OfficerEffectData> map = new LinkedHashMap<PersonAPI, SkillsChangeOfficerEffect.OfficerEffectData>();
047        }
048        public void setMap(OfficerDataMap map, Map<String, Object> dataMap) {
049                String key = getClass().getSimpleName();
050                dataMap.put(key, map);
051        }
052        public OfficerDataMap getMap(Map<String, Object> dataMap) {
053                String key = getClass().getSimpleName();
054                OfficerDataMap map = (OfficerDataMap)dataMap.get(key);
055                if (map == null) {
056                        map = new OfficerDataMap();
057                        dataMap.put(key, map);
058                }
059                return map;
060        }
061        
062        
063        public int getMaxLevel(MutableCharacterStatsAPI stats) {
064                int bonus = (int) stats.getDynamic().getMod(Stats.OFFICER_MAX_LEVEL_MOD).computeEffective(0);
065                return (int) Global.getSettings().getFloat("officerMaxLevel") + bonus;
066        }
067        
068        public int getMaxEliteSkills(MutableCharacterStatsAPI stats) {
069                int bonus = (int) stats.getDynamic().getMod(Stats.OFFICER_MAX_ELITE_SKILLS_MOD).computeEffective(0);
070                return (int) Global.getSettings().getFloat("officerMaxEliteSkills") + bonus;
071        }
072        
073        public int getNumEliteSkills(PersonAPI person) {
074                int num = 0;
075                for (SkillLevelAPI sl : person.getStats().getSkillsCopy()) {
076                        if (sl.getLevel() >= 2f) {
077                                num++;
078                        }
079                }
080                return num;
081        }
082        
083        public OfficerDataMap getEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to) {
084                OfficerDataMap result = new OfficerDataMap();
085                
086                
087                int maxOfficersPre = (int) from.getOfficerNumber().getModifiedValue();
088                int maxOfficersPost = (int) to.getOfficerNumber().getModifiedValue();
089
090                Map<OfficerDataAPI, FleetMemberAPI> members = new LinkedHashMap<OfficerDataAPI, FleetMemberAPI>();
091                Set<OfficerDataAPI> unusable = new LinkedHashSet<OfficerDataAPI>();
092                if (maxOfficersPre > maxOfficersPost) {
093                        int count = 0;
094                        for (OfficerDataAPI officer : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) {
095                                boolean merc = Misc.isMercenary(officer.getPerson());
096                                if (!merc) {
097                                        count++;
098                                }
099                                if (count > maxOfficersPost && !merc) {
100                                        unusable.add(officer);
101                                }
102                        }
103                }
104                
105                for (OfficerDataAPI officer : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) {
106                        for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) {
107                                if (member.getCaptain() == officer.getPerson()) {
108                                        members.put(officer, member);
109                                        break;
110                                }
111                        }
112                }
113                
114                int maxLevelPre = getMaxLevel(from);
115                int maxLevelPost = getMaxLevel(to);
116                
117                int maxElitePre = getMaxEliteSkills(from);
118                int maxElitePost = getMaxEliteSkills(to);
119                
120                for (OfficerDataAPI data : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) {
121                        PersonAPI person = data.getPerson();
122                        MutableCharacterStatsAPI stats = person.getStats();
123                        
124                        if (Misc.isMercenary(person)) {
125                                continue;
126                        }
127                        
128                        boolean pods = person.getMemoryWithoutUpdate().getBoolean(MemFlags.EXCEPTIONAL_SLEEPER_POD_OFFICER);
129                        if (pods) continue;
130                        
131                        OfficerEffectData effect = new OfficerEffectData();
132                        effect.data = data;
133                        effect.newLevel = stats.getLevel();
134                        
135                        if (members.containsKey(data)) {
136                                effect.member = members.get(data);
137                        }
138                        if (unusable.contains(data)) {
139                                effect.unusable = true;
140                        }
141                        
142                        if (stats.getLevel() > maxLevelPost && maxLevelPost < maxLevelPre) {
143                                final List<String> skillsInOrderOfGain = new ArrayList<String>();
144                                for (OfficerSkillGainRecord rec : PlaythroughLog.getInstance().getOfficerSkillsLearned()) {
145                                        if (rec.getPersonId().equals(person.getId()) && !rec.isElite() &&
146                                                        stats.getSkillLevel(rec.getSkillId()) > 0) {
147                                                skillsInOrderOfGain.add(rec.getSkillId());
148                                        }
149                                }
150                                Collections.reverse(skillsInOrderOfGain);
151                                
152                                // if the skill records aren't present, assume it's a cryopods officer
153                                // it can also be an officer from 0.95a before officer skill gain was tracked
154                                // so, erring on the side of not nuking skills needlessly
155                                int skillsToRemove = stats.getLevel() - maxLevelPost;
156                                for (int i = 0; i < skillsToRemove && !skillsInOrderOfGain.isEmpty(); i++) {
157                                        effect.removeSkills.add(skillsInOrderOfGain.remove(0));
158                                }
159                                if (!effect.removeSkills.isEmpty()) {
160                                        effect.newLevel = maxLevelPost;
161                                }
162                        }
163                        
164                        int numElite = getNumEliteSkills(person);
165                        if (numElite > maxElitePost && maxElitePost < maxElitePre) {
166                                final List<String> eliteInOrderOfGain = new ArrayList<String>();
167                                for (OfficerSkillGainRecord rec : PlaythroughLog.getInstance().getOfficerSkillsLearned()) {
168                                        if (rec.getPersonId().equals(person.getId()) && rec.isElite() &&
169                                                        stats.getSkillLevel(rec.getSkillId()) >= 2 &&
170                                                        !effect.removeSkills.contains(rec.getSkillId())) {
171                                                eliteInOrderOfGain.add(rec.getSkillId());
172                                        }
173                                }
174                                Collections.reverse(eliteInOrderOfGain);
175                                
176                                int eliteToRemove = numElite - maxElitePost;
177                                for (int i = 0; i < eliteToRemove && !eliteInOrderOfGain.isEmpty(); i++) {
178                                        effect.removeElite.add(eliteInOrderOfGain.remove(0));
179                                }
180                        }
181                        
182                        
183                        if (effect.hasChanges()) {
184                                result.map.put(person, effect);
185                        }
186                }
187                return result;
188        }
189        
190        
191        @Override
192        public boolean hasEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to) {
193                return !getEffects(from, to).map.isEmpty();
194        }
195
196        @Override
197        public void printEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to, TooltipMakerAPI info, Map<String, Object> dataMap) {
198                super.prepare();
199                
200                OfficerDataMap map = getEffects(from, to);
201                setMap(map, dataMap);
202                
203                float bw = 470;
204                float bh = 25;
205                float pad = 3f;
206                float opad = 10f;
207                
208                
209                info.addSectionHeading("Officers", base, dark, Alignment.MID, 15f);
210                
211                info.addPara("Officers whose skills that exceed the new limits will have their excess skills removed, or become mercenaries on a temporary contract. "
212                                + "Officers that already had those kinds of skills when acquired will not be affected.", opad,
213                                Misc.getNegativeHighlightColor(),
214                                "excess skills removed");
215                float initPad = opad;
216                for (final OfficerEffectData data : map.map.values()) {
217                        final PersonAPI person = data.data.getPerson();
218                        String str = person.getRank() + " " + person.getNameString();
219                        if (data.member == null) {
220                                str += ", unassigned, will...";
221                        } else {
222                                str += ", commanding the " + data.member.getShipName() +
223                                                " (" + data.member.getHullSpec().getHullNameWithDashClass() + "), will...";
224                        }
225
226                        String mercText = "Become a mercenary, drawing higher pay on a new contract";
227                        String otherText = "";
228                        
229                        if (data.unusable && data.removeElite.isEmpty() && data.removeSkills.isEmpty()) {
230                                if (data.member == null) {
231                                        otherText = "Become unable to be assigned command of a ship";
232                                } else {
233                                        otherText = "Be relieved of command, and unable to be assigned command of a ship";
234                                }
235                        } else {
236                                String part1 = "";
237                                if (data.unusable) {
238                                        part1 = "Be relieved of command, and";
239                                }
240                                String part2 = "";
241                                if (!data.removeElite.isEmpty() && data.removeSkills.isEmpty()) {
242                                        part2 = " lose excess elite skill effects";
243                                } else if (data.removeElite.isEmpty() && !data.removeSkills.isEmpty()) {
244                                        part2 = " lose excess skills";
245                                } else {
246                                        part2 = " lose excess skills and elite skill effects";
247                                }
248                                if (part1.isEmpty()) {
249                                        part2 = part2.trim();
250                                        otherText = Misc.ucFirst(part2);
251                                } else {
252                                        otherText = part1 + part2;
253                                }
254                        }
255                        
256                        info.addPara(str, opad + 5f);
257                
258                        float indent = 40f;
259                        data.buttonOther = info.addAreaCheckbox(otherText, new Object(), base, dark, bright, bw, bh, opad, true);
260                        data.buttonOther.getPosition().setXAlignOffset(indent);
261                        
262                        info.addTooltipToPrevious(new TooltipCreator() {
263                                public boolean isTooltipExpandable(Object tooltipParam) {
264                                        return false;
265                                }
266                                public float getTooltipWidth(Object tooltipParam) {
267                                        return 450;
268                                }
269                                public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
270                                        float opad = 10f;
271                                        float pad = 3f;
272                                        tooltip.addPara("This officer has the following skills:", 0f);
273                                        tooltip.addSkillPanel(person, pad);
274                                        
275                                        if (!data.removeSkills.isEmpty()) {
276                                                tooltip.addPara("They will lose the following skills:", opad);
277                                                PersonAPI fake = Global.getFactory().createPerson();
278                                                for (String skillId : data.removeSkills) {
279                                                        fake.getStats().setSkillLevel(skillId, person.getStats().getSkillLevel(skillId));
280                                                }
281                                                tooltip.addSkillPanel(fake, pad);
282                                        }
283                                        if (!data.removeElite.isEmpty()) {
284                                                tooltip.addPara("They will lose the elite effects of the following skills:", opad);
285                                                PersonAPI fake = Global.getFactory().createPerson();
286                                                for (String skillId : data.removeElite) {
287                                                        fake.getStats().setSkillLevel(skillId, person.getStats().getSkillLevel(skillId));
288                                                }
289                                                tooltip.addSkillPanel(fake, pad);
290                                        }
291                                }
292                        }, TooltipLocation.RIGHT);
293                        
294                        //data.buttonMerc = info.addAreaCheckbox(mercText, new Object(), base, dark, bright, bw, bh, pad, true);
295                        data.buttonMerc = info.addAreaCheckbox(mercText, new Object(), sBase, sDark, sBright, bw, bh, pad, true);
296                        data.buttonOther.setChecked(true);
297                        data.buttonMerc.setChecked(false);
298                        
299                        info.addTooltipToPrevious(new TooltipCreator() {
300                                public boolean isTooltipExpandable(Object tooltipParam) {
301                                        return false;
302                                }
303                                public float getTooltipWidth(Object tooltipParam) {
304                                        return 450;
305                                }
306                                public void createTooltip(TooltipMakerAPI tooltip, boolean expanded, Object tooltipParam) {
307                                        int payPre = (int) Misc.getOfficerSalary(person, false);
308                                        int payPost = (int) Misc.getOfficerSalary(person, true);
309                                        int contractDur = (int) Global.getSettings().getFloat("officerMercContractDur");
310                                        tooltip.addPara("This officer's pay will be increased from %s to %s per month,"
311                                                        + " and they will want to leave after %s days, when their contract expires.", 0f,
312                                                        Misc.getHighlightColor(), 
313                                                        Misc.getDGSCredits(payPre),
314                                                        Misc.getDGSCredits(payPost),
315                                                        "" + contractDur
316                                                        );
317                                        tooltip.addPara("Extending the contract beyond that will require a %s.", 10f,
318                                                        Misc.getStoryOptionColor(), Misc.STORY + " point");
319                                }
320                        }, TooltipLocation.RIGHT);
321                        
322                        info.addSpacer(0).getPosition().setXAlignOffset(-indent);
323                }
324        }
325        
326        @Override
327        public void infoButtonPressed(ButtonAPI button, Object param, Map<String, Object> dataMap) {
328                OfficerDataMap map = getMap(dataMap);
329                
330                for (OfficerEffectData data : map.map.values()) {
331                        if (data.buttonMerc == button) {
332                                data.makeMercenary = true;
333                                data.buttonMerc.setChecked(true);
334                                data.buttonOther.setChecked(false);
335                                //System.out.println("Merc button, " + data.data.getPerson().getNameString());
336                        } else if (data.buttonOther == button) {
337                                data.makeMercenary = false;
338                                data.buttonMerc.setChecked(false);
339                                data.buttonOther.setChecked(true);
340                                //System.out.println("Other button, " + data.data.getPerson().getNameString());
341                        }
342                }
343        }
344        
345        @Override
346        public void applyEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to, Map<String, Object> dataMap) {
347                OfficerDataMap map = getMap(dataMap);
348                
349                for (OfficerEffectData data : map.map.values()) {
350                        if (data.makeMercenary) {
351                                Misc.setMercenary(data.data.getPerson(), true);
352                                Misc.setMercHiredNow(data.data.getPerson());
353                        } else {
354                                if (data.unusable && data.member != null) {
355                                        data.member.setCaptain(Global.getFactory().createPerson());
356                                }
357                                MutableCharacterStatsAPI stats = data.data.getPerson().getStats();
358                                for (String id : data.removeElite) {
359                                        stats.setSkillLevel(id, 1);
360                                        PlaythroughLog.getInstance().removeOfficerSkillRecord(data.data.getPerson().getId(), id, true);
361                                }
362                                for (String id : data.removeSkills) {
363                                        stats.setSkillLevel(id, 0);
364                                        PlaythroughLog.getInstance().removeOfficerSkillRecord(data.data.getPerson().getId(), id, false);
365                                        PlaythroughLog.getInstance().removeOfficerSkillRecord(data.data.getPerson().getId(), id, true);
366                                }
367                                if (data.newLevel != stats.getLevel()) {
368                                        stats.setLevel(data.newLevel);
369                                }
370                        }
371                        
372                }
373        }
374
375        
376}
377
378
379