001package com.fs.starfarer.api.impl.combat; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.Comparator; 006import java.util.List; 007 008import java.awt.Color; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.combat.MutableShipStatsAPI; 012import com.fs.starfarer.api.combat.ShipAPI; 013import com.fs.starfarer.api.combat.WeaponAPI; 014import com.fs.starfarer.api.combat.WeaponAPI.WeaponType; 015import com.fs.starfarer.api.impl.campaign.ids.Tags; 016import com.fs.starfarer.api.util.Misc; 017 018public class LidarArrayStats extends BaseShipSystemScript { 019 020 public static String LIDAR_WINDUP = "lidar_windup"; 021 022 public static Color WEAPON_GLOW = new Color(255,50,50,155); 023 024 public static float RANGE_BONUS = 100f; 025 public static float PASSIVE_RANGE_BONUS = 35f; 026 public static float ROF_BONUS = 2f; 027 public static float RECOIL_BONUS = 75f; 028 public static float PROJECTILE_SPEED_BONUS = 50f; 029 030 031 public static class LidarDishData { 032 public float turnDir; 033 public float turnRate; 034 public float angle; 035 public float phase; 036 public float count; 037 public WeaponAPI w; 038 } 039 040 protected List<LidarDishData> dishData = new ArrayList<LidarArrayStats.LidarDishData>(); 041 protected boolean needsUnapply = false; 042 protected boolean playedWindup = false; 043 044 protected boolean inited = false; 045 public void init(ShipAPI ship) { 046 if (inited) return; 047 inited = true; 048 049 needsUnapply = true; 050 051 int turnDir = 1; 052 float index = 0f; 053 float count = 0f; 054 for (WeaponAPI w : ship.getAllWeapons()) { 055 if (w.isDecorative() && w.getSpec().hasTag(Tags.LIDAR)) { 056 count++; 057 } 058 } 059 List<WeaponAPI> lidar = new ArrayList<WeaponAPI>(); 060 for (WeaponAPI w : ship.getAllWeapons()) { 061 if (w.isDecorative() && w.getSpec().hasTag(Tags.LIDAR)) { 062 lidar.add(w); 063 } 064 } 065 Collections.sort(lidar, new Comparator<WeaponAPI>() { 066 public int compare(WeaponAPI o1, WeaponAPI o2) { 067 return (int) Math.signum(o1.getSlot().getLocation().x - o2.getSlot().getLocation().x); 068 } 069 }); 070 for (WeaponAPI w : lidar) { 071 if (w.isDecorative() && w.getSpec().hasTag(Tags.LIDAR)) { 072 w.setSuspendAutomaticTurning(true); 073 LidarDishData data = new LidarDishData(); 074 data.turnDir = Math.signum(turnDir); 075 data.turnRate = 0.5f; 076 data.turnRate = 0.1f; 077 data.w = w; 078 data.angle = 0f; 079 data.phase = index / count; 080 data.count = count; 081 dishData.add(data); 082 turnDir = -turnDir; 083 index++; 084 } 085 } 086 } 087 088 public void rotateLidarDishes(boolean active, float effectLevel) { 089 float amount = Global.getCombatEngine().getElapsedInLastFrame(); 090 091 float turnRateMult = 1f; 092 if (active) { 093 turnRateMult = 20f; 094 } 095 //turnRateMult = 0.1f; 096 //boolean first = true; 097 for (LidarDishData data : dishData) { 098 float arc = data.w.getArc(); 099 float useTurnDir = data.turnDir; 100 if (active) { 101 useTurnDir = Misc.getClosestTurnDirection(data.angle, 0f); 102 } 103 float delta = useTurnDir * amount * data.turnRate * turnRateMult * arc; 104 if (active && effectLevel > 0f && Math.abs(data.angle) < Math.abs(delta * 1.5f)) { 105 data.angle = 0f; 106 } else { 107 data.angle += delta; 108 data.phase += 1f * amount; 109 if (arc < 360f) { 110 if (data.angle > arc/2f && data.turnDir > 0f) { 111 data.angle = arc/2f; 112 data.turnDir = -1f; 113 } 114 if (data.angle < -arc/2f && data.turnDir < 0f) { 115 data.angle = -arc/2f; 116 data.turnDir = 1f; 117 } 118 } else { 119 data.angle = data.angle % 360f; 120 } 121 } 122 123 124 float facing = data.angle + data.w.getArcFacing() + data.w.getShip().getFacing(); 125 data.w.setFacing(facing); 126 data.w.updateBeamFromPoints(); 127// if (first) { 128// System.out.println("Facing: " + facing); 129// first = false; 130// } 131 } 132 } 133 134 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) { 135 ShipAPI ship = (ShipAPI)stats.getEntity(); 136 if (ship == null || ship.isHulk()) { 137 if (needsUnapply) { 138 unmodify(id, stats); 139 for (WeaponAPI w : ship.getAllWeapons()) { 140 if (!w.isDecorative() && w.getSlot().isHardpoint() && !w.isBeam() && 141 (w.getType() == WeaponType.BALLISTIC || w.getType() == WeaponType.ENERGY)) { 142 w.setGlowAmount(0, null); 143 } 144 } 145 needsUnapply = false; 146 } 147 return; 148 } 149 150 init(ship); 151 152 //lidarFacingOffset += am 153 154 boolean active = state == State.IN || state == State.ACTIVE || state == State.OUT; 155 156 rotateLidarDishes(active, effectLevel); 157 158 if (active) { 159 modify(id, stats, effectLevel); 160 needsUnapply = true; 161 } else { 162 if (needsUnapply) { 163 unmodify(id, stats); 164 for (WeaponAPI w : ship.getAllWeapons()) { 165 if (w.getSlot().isSystemSlot()) continue; 166 if (!w.isDecorative() && w.getSlot().isHardpoint() && !w.isBeam() && 167 (w.getType() == WeaponType.BALLISTIC || w.getType() == WeaponType.ENERGY)) { 168 w.setGlowAmount(0, null); 169 } 170 } 171 needsUnapply = false; 172 } 173 } 174 175 if (!active) return; 176 177 178 for (WeaponAPI w : ship.getAllWeapons()) { 179 if (w.getSlot().isSystemSlot()) continue; 180 if (w.getType() == WeaponType.MISSILE) continue; 181 if (state == State.IN) { 182 if (!(w.isDecorative() && w.getSpec().hasTag(Tags.LIDAR))) { 183 w.setForceNoFireOneFrame(true); 184 } 185 } else { 186 if (!(!w.isDecorative() && w.getSlot().isHardpoint() && !w.isBeam() && 187 (w.getType() == WeaponType.BALLISTIC || w.getType() == WeaponType.ENERGY))) { 188 w.setForceNoFireOneFrame(true); 189 } 190 } 191 } 192 193 Color glowColor = WEAPON_GLOW; 194 195 float lidarRange = 500; 196 for (WeaponAPI w : ship.getAllWeapons()) { 197 if (!w.isDecorative() && w.getSlot().isHardpoint() && !w.isBeam() && 198 (w.getType() == WeaponType.BALLISTIC || w.getType() == WeaponType.ENERGY)) { 199 lidarRange = Math.max(lidarRange, w.getRange()); 200 w.setGlowAmount(effectLevel, glowColor); 201 } 202 } 203 lidarRange += 100f; 204 stats.getBeamWeaponRangeBonus().modifyFlat("lidararray", lidarRange); 205// for (WeaponAPI w : ship.getAllWeapons()) { 206// if (w.isDecorative() && w.getSpec().hasTag(Tags.LIDAR)) { 207// if (state == State.IN) { 208// w.setForceFireOneFrame(true); 209// } 210// } 211// } 212 213 // always wait a quarter of a second before starting to fire the targeting lasers 214 // this is the worst-case turn time required for the dishes to face front 215 // doing this to keep the timing of the lidar ping sounds consistent relative 216 // to when the windup sound plays 217 float fireThreshold = 0.25f / 3.25f; 218 fireThreshold += 0.02f; // making sure there's only 4 lidar pings; lines up with the timing of the lidardish weapon 219 //fireThreshold = 0f; 220 for (LidarDishData data : dishData) { 221 boolean skip = data.phase % 1f > 1f / data.count; 222 //skip = data.phase % 1f > 0.67f; 223 skip = false; 224 if (skip) continue; 225 if (data.w.isDecorative() && data.w.getSpec().hasTag(Tags.LIDAR)) { 226 if (state == State.IN && Math.abs(data.angle) < 5f && effectLevel >= fireThreshold) { 227 data.w.setForceFireOneFrame(true); 228 } 229 } 230 } 231 232 if (((state == State.IN && effectLevel > 0.67f) || state == State.ACTIVE) && !playedWindup) { 233 Global.getSoundPlayer().playSound(LIDAR_WINDUP, 1f, 1f, ship.getLocation(), ship.getVelocity()); 234 playedWindup = true; 235 } 236 } 237 238 239 protected void modify(String id, MutableShipStatsAPI stats, float effectLevel) { 240 float mult = 1f + ROF_BONUS * effectLevel; 241 //float mult = 1f + ROF_BONUS; 242 stats.getBallisticWeaponRangeBonus().modifyPercent(id, RANGE_BONUS); 243 stats.getEnergyWeaponRangeBonus().modifyPercent(id, RANGE_BONUS); 244 stats.getBallisticRoFMult().modifyMult(id, mult); 245 stats.getEnergyRoFMult().modifyMult(id, mult); 246 //stats.getBallisticWeaponFluxCostMod().modifyMult(id, 1f - (FLUX_REDUCTION * 0.01f)); 247 stats.getMaxRecoilMult().modifyMult(id, 1f - (0.01f * RECOIL_BONUS)); 248 stats.getRecoilPerShotMult().modifyMult(id, 1f - (0.01f * RECOIL_BONUS)); 249 stats.getRecoilDecayMult().modifyMult(id, 1f - (0.01f * RECOIL_BONUS)); 250 251 stats.getBallisticProjectileSpeedMult().modifyPercent(id, PROJECTILE_SPEED_BONUS); 252 stats.getEnergyProjectileSpeedMult().modifyPercent(id, PROJECTILE_SPEED_BONUS); 253 } 254 protected void unmodify(String id, MutableShipStatsAPI stats) { 255 stats.getBallisticWeaponRangeBonus().modifyPercent(id, PASSIVE_RANGE_BONUS); 256 stats.getEnergyWeaponRangeBonus().modifyPercent(id, PASSIVE_RANGE_BONUS); 257// stats.getBallisticWeaponRangeBonus().unmodifyPercent(id); 258// stats.getEnergyWeaponRangeBonus().unmodifyPercent(id); 259 260 stats.getBallisticRoFMult().unmodifyMult(id); 261 stats.getEnergyRoFMult().unmodifyMult(id); 262 stats.getMaxRecoilMult().unmodifyMult(id); 263 stats.getRecoilPerShotMult().unmodifyMult(id); 264 stats.getRecoilDecayMult().unmodifyMult(id); 265 266 stats.getBallisticProjectileSpeedMult().unmodifyPercent(id); 267 stats.getEnergyProjectileSpeedMult().unmodifyPercent(id); 268 269 playedWindup = false; 270 } 271 272 273 public void unapply(MutableShipStatsAPI stats, String id) { 274 // never called due to runScriptWhileIdle:true in the .system file 275 } 276 277 public StatusData getStatusData(int index, State state, float effectLevel) { 278 if (state == State.IDLE || state == State.COOLDOWN) { 279 if (index == 3) { 280 return new StatusData("weapon range +" + (int) PASSIVE_RANGE_BONUS + "%", false); 281 } 282 } 283 if (effectLevel <= 0f) return null; 284 285 //float mult = 1f + ROF_BONUS; 286 float mult = 1f + ROF_BONUS; 287 float bonusPercent = (int) ((mult - 1f) * 100f); 288 if (index == 3) { 289 return new StatusData("weapon range +" + (int) RANGE_BONUS + "%", false); 290 } 291 if (index == 2) { 292 return new StatusData("rate of fire +" + (int) bonusPercent + "%", false); 293 } 294// if (index == 1) { 295// return new StatusData("ballistic flux use -" + (int) FLUX_REDUCTION + "%", false); 296// } 297 if (index == 1) { 298 return new StatusData("weapon recoil -" + (int) RECOIL_BONUS + "%", false); 299 } 300 if (index == 0 && PROJECTILE_SPEED_BONUS > 0) { 301 return new StatusData("projectile speed +" + (int) PROJECTILE_SPEED_BONUS + "%", false); 302 } 303 return null; 304 } 305 306 public String getDisplayNameOverride(State state, float effectLevel) { 307 if (state == State.IDLE || state == State.COOLDOWN) { 308 return "lidar array - passive"; 309 } 310 return null; 311 } 312 313}