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