001package com.fs.starfarer.api.impl.campaign.skills; 002 003import java.util.HashSet; 004import java.util.List; 005import java.util.Set; 006 007import com.fs.starfarer.api.Global; 008import com.fs.starfarer.api.characters.PersonAPI; 009import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 010import com.fs.starfarer.api.combat.BattleObjectiveAPI; 011import com.fs.starfarer.api.combat.CombatEngineAPI; 012import com.fs.starfarer.api.combat.CombatFleetManagerAPI; 013import com.fs.starfarer.api.combat.DeployedFleetMemberAPI; 014import com.fs.starfarer.api.combat.MutableStat.StatMod; 015import com.fs.starfarer.api.combat.ShipAPI; 016import com.fs.starfarer.api.combat.ViewportAPI; 017import com.fs.starfarer.api.impl.campaign.ids.BattleObjectives; 018import com.fs.starfarer.api.impl.campaign.ids.Stats; 019import com.fs.starfarer.api.input.InputEventAPI; 020 021public class CoordinatedManeuversScript extends BaseEveryFrameCombatPlugin { 022 public static final Object KEY_STATUS = new Object(); 023 024 public static final float BASE_MAXIMUM = 20; 025 public static final float PER_BUOY = 5; 026 027 public static final String BONUS_ID = "coord_maneuvers_bonus"; 028 029 private CombatEngineAPI engine; 030 public void init(CombatEngineAPI engine) { 031 this.engine = engine; 032 } 033 034 private ShipAPI prevPlayerShip = null; 035 private int skipFrames = 0; 036 private Set<CombatFleetManagerAPI> needsCleanup = new HashSet<CombatFleetManagerAPI>(); 037 public void advance(float amount, List<InputEventAPI> events) { 038 if (engine == null) return; 039 if (engine.isPaused()) return; 040 041 042 // if the player changed flagships: 043 // skip a few frames to make sure the status ends up on top of the status list 044 ShipAPI playerShip = engine.getPlayerShip(); 045 if (playerShip != prevPlayerShip) { 046 prevPlayerShip = playerShip; 047 skipFrames = 20; 048 } 049 050 if (skipFrames > 0) { 051 skipFrames--; 052 return; 053 } 054 055 updateForSide(engine.getFleetManager(0)); 056 updateForSide(engine.getFleetManager(1)); 057 058 // nothing to do with Coordinated Maneuvers, just a convenient place to add this 059 updateForceConcentration(engine.getFleetManager(0)); 060 updateForceConcentration(engine.getFleetManager(1)); 061 062 updateDPFromSupportDoctrine(); 063 } 064 065 protected ShipAPI undoDPMod = null; 066 protected void updateDPFromSupportDoctrine() { 067 // making sure that while transferring command away from something, the previous player ship 068 // does NOT receive the DP reduction until transferring command is finished 069 070 ShipAPI from = engine.getShipPlayerIsTransferringCommandFrom(); 071 String id = SupportDoctrine.SUPPORT_DOCTRINE_DP_REDUCTION_ID + "_reverse"; 072 if (from != null) { 073 StatMod bonus = from.getMutableStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).getFlatBonus(SupportDoctrine.SUPPORT_DOCTRINE_DP_REDUCTION_ID); 074 if (bonus != null && bonus.value != 0) { 075 undoDPMod = from; 076 from.getMutableStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).modifyFlat(id, -bonus.value); 077 if (from.getFleetMember() != null) { 078 from.getFleetMember().getStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).modifyFlat(id, -bonus.value); 079 } 080 } 081 } else if (undoDPMod != null) { 082 undoDPMod.getMutableStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).unmodifyFlat(id); 083 if (undoDPMod.getFleetMember() != null) { 084 undoDPMod.getFleetMember().getStats().getDynamic().getMod(Stats.DEPLOYMENT_POINTS_MOD).unmodifyFlat(id); 085 } 086 undoDPMod = null; 087 } 088 089 } 090 091 092 private void updateForSide(CombatFleetManagerAPI manager) { 093 094// PersonAPI commander = manager.getFleetCommander(); 095// if (commander == null) { 096// cleanUpIfNeeded(manager); 097// return; 098// } 099// float max = BASE_MAXIMUM + commander.getStats().getDynamic().getValue(Stats.COORDINATED_MANEUVERS_MAX, 0f); 100 101 float max = 0f; 102 for (PersonAPI commander : manager.getAllFleetCommanders()) { 103 max = Math.max(max, BASE_MAXIMUM + commander.getStats().getDynamic().getValue(Stats.COORDINATED_MANEUVERS_MAX, 0f)); 104 } 105 106 if (max <= 0f) { 107 cleanUpIfNeeded(manager); 108 return; 109 } 110 111 boolean buoysOnly = true; 112 float total = 0f; 113 List<DeployedFleetMemberAPI> deployed = manager.getDeployedCopyDFM(); 114 for (DeployedFleetMemberAPI member : deployed) { 115 if (member.isFighterWing()) continue; 116 if (member.isStationModule()) continue; 117 118 float curr = member.getShip().getMutableStats().getDynamic().getValue(Stats.COORDINATED_MANEUVERS_FLAT, 0f); 119 total += curr; 120 } 121 122 if (total > 0) buoysOnly = false; 123 124 int numBuoys = 0; 125 for (BattleObjectiveAPI obj : engine.getObjectives()) { 126 if (obj.getOwner() == manager.getOwner() && BattleObjectives.NAV_BUOY.equals(obj.getType())) { 127 total += PER_BUOY; 128 numBuoys++; 129 } 130 } 131 132 133 if (total <= 0f) { 134 cleanUpIfNeeded(manager); 135 return; 136 } 137 138 //if (total > max) total = max; 139 140 boolean includeSelf = false; 141 includeSelf = true; 142 for (DeployedFleetMemberAPI member : deployed) { 143 if (member.isFighterWing()) continue; 144 if (member.getShip() == null) continue; 145 146 float curr = member.getShip().getMutableStats().getDynamic().getValue(Stats.COORDINATED_MANEUVERS_FLAT, 0f); 147 if (includeSelf) curr = 0f; 148 149 float bonus = Math.min(max, Math.max(0f, total - curr)); 150 member.getShip().getMutableStats().getMaxSpeed().modifyPercent(BONUS_ID, bonus); 151 } 152 153 needsCleanup.add(manager); 154 155 156 if (manager.getOwner() == engine.getPlayerShip().getOwner()) { 157 //if (engine.getPlayerShip().isShuttlePod()) return; 158 159 float curr = engine.getPlayerShip().getMutableStats().getDynamic().getValue(Stats.COORDINATED_MANEUVERS_FLAT, 0f); 160 if (includeSelf) curr = 0f; 161 162 float bonus = Math.min(max, Math.max(0f, total - curr)); 163 164 String title = "Coordinated Maneuvers:" + " " + (int) Math.min(max, total) + "%"; 165 //String data = "+" + (int)bonus + "% top speed (ship: " + (int) curr + "%)"; 166 String data = "+" + (int)bonus + "% top speed"; 167 if (buoysOnly) { 168 title = "Nav Buoy"; 169 if (numBuoys > 1) { 170 title += "s"; 171 title += " (" + numBuoys + ")"; 172 } 173 data = "+" + (int)bonus + "% top speed"; 174 } 175 String icon = Global.getSettings().getSpriteName("ui", "icon_tactical_coordinated_maneuvers"); 176 engine.maintainStatusForPlayerShip(KEY_STATUS, icon, 177 title, 178 data, false); 179 } 180 } 181 182 protected void cleanUpIfNeeded(CombatFleetManagerAPI manager) { 183 if (needsCleanup.contains(manager)) { 184 needsCleanup.remove(manager); 185 List<DeployedFleetMemberAPI> deployed = manager.getDeployedCopyDFM(); 186 for (DeployedFleetMemberAPI member : deployed) { 187 if (member.isFighterWing()) continue; 188 if (member.getShip() == null) continue; 189 member.getShip().getMutableStats().getMaxSpeed().unmodify(BONUS_ID); 190 } 191 } 192 } 193 194 195 protected void updateForceConcentration(CombatFleetManagerAPI manager) { 196 if (true) return; 197 List<DeployedFleetMemberAPI> deployed = manager.getDeployedCopyDFM(); 198 for (DeployedFleetMemberAPI member : deployed) { 199 if (member.isFighterWing()) continue; 200 if (member.isStationModule()) continue; 201 if (member.getMember() == null) continue; 202 203 204 PersonAPI fc = member.getMember().getFleetCommander(); 205 if (fc == null) fc = member.getMember().getFleetCommanderForStats(); 206 ShipAPI ship = member.getShip(); 207 208 if (ship == null) continue; 209 if (fc == null) continue; 210 211 //boolean hasFC = fc.getStats().getDynamic().getMod(Stats.HAS_FORCE_CONCENTRATION_BONUS_MOD).computeEffective(0f) > 0f; 212 boolean hasFC = false; 213 214 String id = "fc_zf_bonus"; 215 if (hasFC) { 216 boolean hasZF = ship.isEngineBoostActive(); 217 if (ship.areAnyEnemiesInRange()) { 218 ship.getMutableStats().getZeroFluxSpeedBoost().modifyFlat(id, ForceConcentration.ZERO_FLUX_SPEED_BONUS_SMALL); 219 } else { 220 ship.getMutableStats().getZeroFluxSpeedBoost().modifyFlat(id, ForceConcentration.ZERO_FLUX_SPEED_BONUS); 221 } 222 223 boolean applyAccelAndTurnModifiers = !ship.areAnyEnemiesInRange() && hasZF; 224 if (applyAccelAndTurnModifiers) { 225 ship.getMutableStats().getAcceleration().modifyFlat(id, ForceConcentration.ZERO_FLUX_ACCEL_BONUS); 226 ship.getMutableStats().getDeceleration().modifyFlat(id, ForceConcentration.ZERO_FLUX_ACCEL_BONUS); 227 ship.getMutableStats().getMaxTurnRate().modifyFlat(id, ForceConcentration.ZERO_FLUX_TURN_BONUS); 228 ship.getMutableStats().getTurnAcceleration().modifyFlat(id, ForceConcentration.ZERO_FLUX_TURN_ACCEL_BONUS); 229 } else { 230 ship.getMutableStats().getAcceleration().unmodifyFlat(id); 231 ship.getMutableStats().getDeceleration().unmodifyFlat(id); 232 ship.getMutableStats().getMaxTurnRate().unmodifyFlat(id); 233 ship.getMutableStats().getTurnAcceleration().unmodifyFlat(id); 234 } 235 } 236 } 237 } 238 239 240// public static PersonAPI getCommander(CombatFleetManagerAPI manager) { 241// List<DeployedFleetMemberAPI> deployed = manager.getDeployedCopyDFM(); 242// if (deployed.isEmpty()) return null; 243// 244// PersonAPI defaultCommander = manager.getDefaultCommander(); 245// for (DeployedFleetMemberAPI member : deployed) { 246// if (member.isFighterWing()) continue; 247// FleetMemberAPI m = member.getMember(); 248// PersonAPI commander = m.getFleetCommanderForStats(); 249// if (commander == null && m.getFleetData() != null) { 250// commander = m.getFleetData().getCommander(); 251// } 252// if (commander == null) { 253// commander = defaultCommander; 254// } 255// return commander; 256// } 257// return null; 258// } 259 260 261 public void renderInUICoords(ViewportAPI viewport) { 262 } 263 264 public void renderInWorldCoords(ViewportAPI viewport) { 265 } 266 267}