Starsector API
Loading...
Searching...
No Matches
SkillsChangeOfficerEffect.java
Go to the documentation of this file.
1package com.fs.starfarer.api.characters;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.LinkedHashMap;
6import java.util.LinkedHashSet;
7import java.util.List;
8import java.util.Map;
9import java.util.Set;
10
11import com.fs.starfarer.api.Global;
12import com.fs.starfarer.api.characters.MutableCharacterStatsAPI.SkillLevelAPI;
13import com.fs.starfarer.api.fleet.FleetMemberAPI;
14import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
15import com.fs.starfarer.api.impl.campaign.ids.Stats;
16import com.fs.starfarer.api.impl.campaign.plog.OfficerSkillGainRecord;
17import com.fs.starfarer.api.impl.campaign.plog.PlaythroughLog;
18import com.fs.starfarer.api.ui.Alignment;
19import com.fs.starfarer.api.ui.ButtonAPI;
20import com.fs.starfarer.api.ui.TooltipMakerAPI;
21import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipCreator;
22import com.fs.starfarer.api.ui.TooltipMakerAPI.TooltipLocation;
23import com.fs.starfarer.api.util.Misc;
24
26
27 public static class OfficerEffectData {
28 OfficerDataAPI data;
29 int newLevel;
30 List<String> removeSkills = new ArrayList<String>();
31 List<String> removeElite = new ArrayList<String>();
32 FleetMemberAPI member;
33 boolean unusable = false;
34 boolean makeMercenary;
35
36 ButtonAPI buttonMerc;
37 ButtonAPI buttonOther;
38
39 public boolean hasChanges() {
40 return data.getPerson().getStats().getLevel() != newLevel || unusable ||
41 !removeSkills.isEmpty() || !removeElite.isEmpty() || makeMercenary;
42 }
43 }
44
45 public static class OfficerDataMap {
46 Map<PersonAPI, OfficerEffectData> map = new LinkedHashMap<PersonAPI, SkillsChangeOfficerEffect.OfficerEffectData>();
47 }
48 public void setMap(OfficerDataMap map, Map<String, Object> dataMap) {
49 String key = getClass().getSimpleName();
50 dataMap.put(key, map);
51 }
52 public OfficerDataMap getMap(Map<String, Object> dataMap) {
53 String key = getClass().getSimpleName();
54 OfficerDataMap map = (OfficerDataMap)dataMap.get(key);
55 if (map == null) {
56 map = new OfficerDataMap();
57 dataMap.put(key, map);
58 }
59 return map;
60 }
61
62
64 int bonus = (int) stats.getDynamic().getMod(Stats.OFFICER_MAX_LEVEL_MOD).computeEffective(0);
65 return (int) Global.getSettings().getFloat("officerMaxLevel") + bonus;
66 }
67
69 int bonus = (int) stats.getDynamic().getMod(Stats.OFFICER_MAX_ELITE_SKILLS_MOD).computeEffective(0);
70 return (int) Global.getSettings().getFloat("officerMaxEliteSkills") + bonus;
71 }
72
73 public int getNumEliteSkills(PersonAPI person) {
74 int num = 0;
75 for (SkillLevelAPI sl : person.getStats().getSkillsCopy()) {
76 if (sl.getLevel() >= 2f) {
77 num++;
78 }
79 }
80 return num;
81 }
82
84 OfficerDataMap result = new OfficerDataMap();
85
86
87 int maxOfficersPre = (int) from.getOfficerNumber().getModifiedValue();
88 int maxOfficersPost = (int) to.getOfficerNumber().getModifiedValue();
89
90 Map<OfficerDataAPI, FleetMemberAPI> members = new LinkedHashMap<OfficerDataAPI, FleetMemberAPI>();
91 Set<OfficerDataAPI> unusable = new LinkedHashSet<OfficerDataAPI>();
92 if (maxOfficersPre > maxOfficersPost) {
93 int count = 0;
94 for (OfficerDataAPI officer : Global.getSector().getPlayerFleet().getFleetData().getOfficersCopy()) {
95 boolean merc = Misc.isMercenary(officer.getPerson());
96 if (!merc) {
97 count++;
98 }
99 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
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);
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);
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
static SettingsAPI getSettings()
Definition Global.java:51
static FactoryAPI getFactory()
Definition Global.java:35
static SectorAPI getSector()
Definition Global.java:59
void infoButtonPressed(ButtonAPI button, Object param, Map< String, Object > dataMap)
void setMap(OfficerDataMap map, Map< String, Object > dataMap)
OfficerDataMap getEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to)
boolean hasEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to)
void printEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to, TooltipMakerAPI info, Map< String, Object > dataMap)
void applyEffects(MutableCharacterStatsAPI from, MutableCharacterStatsAPI to, Map< String, Object > dataMap)
MutableCharacterStatsAPI getStats()