001package com.fs.starfarer.api.impl.hullmods;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import java.awt.Color;
007
008import com.fs.starfarer.api.GameState;
009import com.fs.starfarer.api.Global;
010import com.fs.starfarer.api.campaign.CampaignFleetAPI;
011import com.fs.starfarer.api.combat.BaseHullMod;
012import com.fs.starfarer.api.combat.HullModFleetEffect;
013import com.fs.starfarer.api.combat.MutableShipStatsAPI;
014import com.fs.starfarer.api.combat.ShipAPI;
015import com.fs.starfarer.api.combat.ShipAPI.HullSize;
016import com.fs.starfarer.api.fleet.FleetMemberAPI;
017import com.fs.starfarer.api.impl.campaign.ids.HullMods;
018import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
019import com.fs.starfarer.api.impl.campaign.ids.Strings;
020import com.fs.starfarer.api.ui.LabelAPI;
021import com.fs.starfarer.api.ui.TooltipMakerAPI;
022import com.fs.starfarer.api.util.Misc;
023
024@SuppressWarnings("unchecked")
025public class PhaseField extends BaseHullMod implements HullModFleetEffect {
026
027//      private static Map mag = new HashMap();
028//      static {
029//              mag.put(HullSize.FRIGATE, Global.getSettings().getFloat("baseSensorFrigate"));
030//              mag.put(HullSize.DESTROYER, Global.getSettings().getFloat("baseSensorDestroyer"));
031//              mag.put(HullSize.CRUISER, Global.getSettings().getFloat("baseSensorCruiser"));
032//              mag.put(HullSize.CAPITAL_SHIP, Global.getSettings().getFloat("baseSensorCapital"));
033//      }
034        
035        public static float MIN_CR = 0.1f;
036        public static String MOD_KEY = "core_PhaseField";
037        
038        public static float PROFILE_MULT = 0.5f;
039        
040        public static float MIN_FIELD_MULT = 0.25f;
041        
042        public void applyEffectsBeforeShipCreation(HullSize hullSize, MutableShipStatsAPI stats, String id) {
043                stats.getSensorProfile().modifyMult(id, PROFILE_MULT);
044                //stats.getDynamic().getMod(Stats.PHASE_FIELD_SENSOR_PROFILE_MOD).modifyFlat(id, (Float) mag.get(hullSize));
045        }
046        
047        public String getDescriptionParam(int index, HullSize hullSize) {
048                if (index == 0) return "" + (int) ((1f - PROFILE_MULT) * 100f) + "%";
049                return null;
050        }
051
052        public void advanceInCampaign(CampaignFleetAPI fleet) {
053                //if (fleet.isPlayerFleet() && fleet.getMemoryWithoutUpdate() != null &&
054                String key = "$updatedPhaseFieldModifier";
055                if (fleet.isPlayerFleet() && fleet.getMemoryWithoutUpdate() != null &&
056                                !fleet.getMemoryWithoutUpdate().getBoolean(key) &&
057                                fleet.getMemoryWithoutUpdate().getBoolean(MemFlags.JUST_TOGGLED_TRANSPONDER)) {
058                        onFleetSync(fleet);
059                        fleet.getMemoryWithoutUpdate().set(key, true, 0.1f);
060                }
061        }
062
063        public boolean withAdvanceInCampaign() {
064                return true;
065        }
066        public boolean withOnFleetSync() {
067                return true;
068        }
069
070        public void onFleetSync(CampaignFleetAPI fleet) {
071                float mult = getPhaseFieldMultBaseProfileAndTotal(fleet, null, 0f, 0f)[0];
072                if (fleet.isTransponderOn()) mult = 1f;
073                if (mult <= 0) {
074                        fleet.getDetectedRangeMod().unmodifyMult(MOD_KEY);
075                } else {
076                        fleet.getDetectedRangeMod().modifyMult(MOD_KEY, mult, "Phase ships in fleet");
077                }
078        }
079        
080        @Override
081        public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) {
082                return true;
083        }
084
085        @Override
086        public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, ShipAPI ship, float width, boolean isForModSpec) {
087                float pad = 3f;
088                float opad = 10f;
089                Color h = Misc.getHighlightColor();
090                Color bad = Misc.getNegativeHighlightColor();
091                
092                int numProfileShips = Global.getSettings().getInt("maxSensorShips");
093                tooltip.addPara("In addition, the fleet's detected-at range is reduced by a multiplier based on the total "
094                                + "sensor profile of the %s highest-profile ships in the fleet, and the total sensor strength of the %s "
095                                + "phase ships with the highest sensor strength values. This effect only applies when the "
096                                + "fleet's transponder is turned off.", opad,
097                                h,
098                                "" + numProfileShips, "" + numProfileShips);
099                
100                tooltip.addPara("Fleetwide sensor strength increases - such as from High Resolution Sensors - do not factor into "
101                                + "this calculation.", opad);
102                
103                if (isForModSpec || ship == null) return;
104                if (Global.getSettings().getCurrentState() == GameState.TITLE) return;
105                
106                
107                CampaignFleetAPI fleet = Global.getSector().getPlayerFleet();
108                float [] data = getPhaseFieldMultBaseProfileAndTotal(fleet, null, 0f, 0f);
109                float [] dataWithOneMore = getPhaseFieldMultBaseProfileAndTotal(fleet, null,
110                                ship.getMutableStats().getSensorProfile().getModifiedValue(),
111                                ship.getMutableStats().getSensorStrength().getModifiedValue());
112                float [] dataWithOneLess = getPhaseFieldMultBaseProfileAndTotal(fleet, ship.getFleetMemberId(), 0f, 0f);
113                
114                float mult = data[0];
115                float profile = data[1];
116                float sensors = data[2];
117                
118                tooltip.addPara("The sensor profile of the %s top ships in your fleet is %s. The sensor strength of the top %s phase ships "
119                                + "is %s.", opad, h,
120                                "" + numProfileShips,
121                                "" + (int)Math.round(profile),
122                                "" + numProfileShips,
123                                "" + (int)Math.round(sensors)
124                                );
125                
126                tooltip.addPara("The detected-at range multiplier for your fleet is %s. The fleet's transponder must be off "
127                                + "for the multiplier to be applied.",
128                                opad, h,
129                                Strings.X + Misc.getRoundedValueFloat(mult),
130                                "transponder must be off");
131                
132                float cr = ship.getCurrentCR();
133                for (FleetMemberAPI member : Global.getSector().getPlayerFleet().getFleetData().getMembersListCopy()) {
134                        if (member.getId().equals(ship.getFleetMemberId())) {
135                                cr = member.getRepairTracker().getCR();
136                        }
137                }
138                
139                if (cr < MIN_CR) {
140                        LabelAPI label = tooltip.addPara("This ship's combat readiness is below %s " +
141                                        "and the phase field's fleetwide effect can not be utilized. Bringing this ship into readiness " +
142                                        "would improve the multiplier to %s.",
143                                        opad, h,
144                                        "" + (int) Math.round(MIN_CR * 100f) + "%",
145                                        Strings.X + Misc.getRoundedValueFloat(dataWithOneMore[0]));
146                        label.setHighlightColors(bad, h);
147                        label.setHighlight("" + (int) Math.round(MIN_CR * 100f) + "%", Strings.X + Misc.getRoundedValueFloat(dataWithOneMore[0]));
148                } else {
149                        tooltip.addPara("Removing this ship would change the multiplier to %s. Adding another ship with the same sensor strength " +
150                                        "would improve it to %s.", opad, h,
151                                        Strings.X + Misc.getRoundedValueFloat(dataWithOneLess[0]),
152                                        Strings.X + Misc.getRoundedValueFloat(dataWithOneMore[0]));
153                }
154        }
155
156//      public static float getAdjustedPhaseFieldModifier(CampaignFleetAPI fleet, String skipId, float add) {
157//              float max = 0f;
158//              float total = 0f;
159//              for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
160//                      if (member.isMothballed()) continue;
161//                      if (member.getRepairTracker().getCR() < MIN_CR) continue;
162//                      
163//                      if (member.getId().equals(skipId)) { 
164//                              continue;
165//                      }
166//                      float v = member.getStats().getDynamic().getMod(Stats.PHASE_FIELD_SENSOR_PROFILE_MOD).computeEffective(0f);
167//                      if (v <= 0) continue;
168//                      
169//                      if (v > max) max = v;
170//                      total += v;
171//              }
172//              if (add > max) max = add;
173//              total += add;
174//              
175//              if (max <= 0) return 0f;
176//              float units = total / max;
177//              if (units <= 1) return max;
178//              float mult = Misc.logOfBase(2.5f, units) + 1f;
179//              float result = total * mult / units;
180//              if (result <= 0) {
181//                      result = 0;
182//              } else {
183//                      result = Math.round(result * 100f) / 100f;
184//                      result = Math.max(result, 1f);
185//              }
186//              return result;
187//      }
188        
189        
190        public static float [] getPhaseFieldMultBaseProfileAndTotal(CampaignFleetAPI fleet, String skipId, float addProfile, float addSensor) {
191                List<FleetMemberAPI> members = new ArrayList<FleetMemberAPI>();
192                List<FleetMemberAPI> phase = new ArrayList<FleetMemberAPI>();
193                for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
194                        if (member.getId().equals(skipId)) { 
195                                continue;
196                        }
197                        members.add(member);
198                        
199                        if (member.isMothballed()) continue;
200                        if (member.getRepairTracker().getCR() < MIN_CR) continue;
201                        if (member.getVariant().hasHullMod(HullMods.PHASE_FIELD)) {
202                                phase.add(member);
203                        }
204                }
205                
206                float [] profiles = new float [members.size()];
207                if (addProfile <= 0) {
208                        profiles = new float [members.size()];
209                } else {
210                        profiles = new float [members.size() + 1];
211                }
212                float [] phaseSensors;
213                if (addSensor <= 0) {
214                        phaseSensors = new float [phase.size()];
215                } else {
216                        phaseSensors = new float [phase.size() + 1];
217                }
218                
219                int i = 0;
220                for (FleetMemberAPI member : members) {
221                        profiles[i] = member.getStats().getSensorProfile().getModifiedValue();
222                        i++;
223                }
224                if (addProfile > 0) profiles[i] = addProfile;
225                i = 0;
226                for (FleetMemberAPI member : phase) {
227                        phaseSensors[i] = member.getStats().getSensorStrength().getModifiedValue();
228                        i++;
229                }
230                if (addSensor > 0) phaseSensors[i] = addSensor;
231                
232                int numProfileShips = Global.getSettings().getInt("maxSensorShips");
233                int numPhaseShips = numProfileShips;
234                
235                float totalProfile = getTopKValuesSum(profiles, numProfileShips);
236                float totalPhaseSensors = getTopKValuesSum(phaseSensors, numPhaseShips);
237                
238                float total = Math.max(totalProfile + totalPhaseSensors, 1f);
239                
240                float mult = totalProfile / total;
241                if (totalPhaseSensors <= 0f) mult = 1f;
242                
243                if (mult < MIN_FIELD_MULT) mult = MIN_FIELD_MULT;
244                if (mult > 1f) mult = 1f;
245                
246                return new float[] {mult, totalProfile, totalPhaseSensors};
247        }
248        
249
250        public static float getTopKValuesSum(float [] arr, int k) {
251                k = Math.min(k, arr.length);
252                
253                float kVal = Misc.findKth(arr, arr.length - k);
254                float total = 0;
255                int found = 0;
256                for (int i = 0; i < arr.length; i++) {
257                        if (arr[i] > kVal) {
258                                found++;
259                                total += arr[i];
260                        }
261                }
262                if (k > found) {
263                        total += (k - found) * kVal;
264                }
265                return total;
266        }
267}
268
269
270