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 org.lwjgl.util.vector.Vector2f; 011 012import com.fs.starfarer.api.Global; 013import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin; 014import com.fs.starfarer.api.combat.CollisionClass; 015import com.fs.starfarer.api.combat.CombatEngineAPI; 016import com.fs.starfarer.api.combat.DamageType; 017import com.fs.starfarer.api.combat.EmpArcEntityAPI; 018import com.fs.starfarer.api.combat.FighterLaunchBayAPI; 019import com.fs.starfarer.api.combat.GuidedMissileAI; 020import com.fs.starfarer.api.combat.MissileAPI; 021import com.fs.starfarer.api.combat.MutableShipStatsAPI; 022import com.fs.starfarer.api.combat.ShipAPI; 023import com.fs.starfarer.api.combat.ShipAPI.HullSize; 024import com.fs.starfarer.api.combat.ShipSystemAPI; 025import com.fs.starfarer.api.combat.ShipSystemAPI.SystemState; 026import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags; 027import com.fs.starfarer.api.combat.WeaponAPI; 028import com.fs.starfarer.api.util.Misc; 029 030public class DroneStrikeStats extends BaseShipSystemScript implements DroneStrikeStatsAIInfoProvider { 031 032 public static class DroneMissileScript extends BaseCombatLayeredRenderingPlugin { 033 protected ShipAPI drone; 034 protected MissileAPI missile; 035 protected boolean done; 036 037 public DroneMissileScript(ShipAPI drone, MissileAPI missile) { 038 super(); 039 this.drone = drone; 040 this.missile = missile; 041 missile.setNoFlameoutOnFizzling(true); 042 //missile.setFlightTime(missile.getMaxFlightTime() - 1f); 043 } 044 045 @Override 046 public void advance(float amount) { 047 super.advance(amount); 048 049 if (done) return; 050 051 CombatEngineAPI engine = Global.getCombatEngine(); 052 053 missile.setEccmChanceOverride(1f); 054 missile.setOwner(drone.getOriginalOwner()); 055 056 drone.getLocation().set(missile.getLocation()); 057 drone.getVelocity().set(missile.getVelocity()); 058 drone.setCollisionClass(CollisionClass.FIGHTER); 059 drone.setFacing(missile.getFacing()); 060 drone.getEngineController().fadeToOtherColor(this, new Color(0,0,0,0), new Color(0,0,0,0), 1f, 1f); 061 062 063 float dist = Misc.getDistance(missile.getLocation(), missile.getStart()); 064 float jitterFraction = dist / missile.getMaxRange(); 065 jitterFraction = Math.max(jitterFraction, missile.getFlightTime() / missile.getMaxFlightTime()); 066 067 missile.setSpriteAlphaOverride(0f); 068 float jitterMax = 1f + 10f * jitterFraction; 069 drone.setJitter(this, new Color(255,100,50, (int)(25 + 50 * jitterFraction)), 1f, 10, 1f, jitterMax); 070 071 072// if (true && !done && missile.getFlightTime() > 1f) { 073// Vector2f damageFrom = new Vector2f(drone.getLocation()); 074// damageFrom = Misc.getPointWithinRadius(damageFrom, 20); 075// engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false); 076// } 077 078 boolean droneDestroyed = drone.isHulk() || drone.getHitpoints() <= 0; 079 if (missile.isFizzling() || (missile.getHitpoints() <= 0 && !missile.didDamage()) || droneDestroyed) { 080 drone.getVelocity().set(0, 0); 081 missile.getVelocity().set(0, 0); 082 083 if (!droneDestroyed) { 084 Vector2f damageFrom = new Vector2f(drone.getLocation()); 085 damageFrom = Misc.getPointWithinRadius(damageFrom, 20); 086 engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false); 087 } 088 missile.interruptContrail(); 089 engine.removeEntity(drone); 090 engine.removeEntity(missile); 091 092 missile.explode(); 093 094 done = true; 095 return; 096 } 097 if (missile.didDamage()) { 098 drone.getVelocity().set(0, 0); 099 missile.getVelocity().set(0, 0); 100 101 Vector2f damageFrom = new Vector2f(drone.getLocation()); 102 damageFrom = Misc.getPointWithinRadius(damageFrom, 20); 103 engine.applyDamage(drone, damageFrom, 1000000f, DamageType.ENERGY, 0, true, false, drone, false); 104 missile.interruptContrail(); 105 engine.removeEntity(drone); 106 engine.removeEntity(missile); 107 done = true; 108 return; 109 } 110 111 } 112 113 @Override 114 public boolean isExpired() { 115 return done; 116 } 117 118 119 } 120 121 122 123 protected String getWeaponId() { 124 return "terminator_missile"; 125 } 126 protected int getNumToFire() { 127 return 1; 128 } 129 130 protected WeaponAPI weapon; 131 protected boolean fired = false; 132 133 public void apply(MutableShipStatsAPI stats, String id, State state, float effectLevel) { 134 ShipAPI ship = null; 135 //boolean player = false; 136 if (stats.getEntity() instanceof ShipAPI) { 137 ship = (ShipAPI) stats.getEntity(); 138 //player = ship == Global.getCombatEngine().getPlayerShip(); 139 } else { 140 return; 141 } 142 143 if (weapon == null) { 144 weapon = Global.getCombatEngine().createFakeWeapon(ship, getWeaponId()); 145 } 146 147 for (ShipAPI drone : getDrones(ship)) { 148 drone.setExplosionScale(0.67f); 149 drone.setExplosionVelocityOverride(new Vector2f()); 150 drone.setExplosionFlashColorOverride(new Color(255, 100, 50, 255)); 151 } 152 153 if (effectLevel > 0 && !fired) { 154 if (!getDrones(ship).isEmpty()) { 155 ShipAPI target = findTarget(ship); 156 convertDrones(ship, target); 157 } 158 } else if (state == State.IDLE){ 159 fired = false; 160 } 161 } 162 163 public void convertDrones(ShipAPI ship, final ShipAPI target) { 164 CombatEngineAPI engine = Global.getCombatEngine(); 165 fired = true; 166 forceNextTarget = null; 167 int num = 0; 168 169 List<ShipAPI> drones = getDrones(ship); 170 if (target != null) { 171 Collections.sort(drones, new Comparator<ShipAPI>() { 172 public int compare(ShipAPI o1, ShipAPI o2) { 173 float d1 = Misc.getDistance(o1.getLocation(), target.getLocation()); 174 float d2 = Misc.getDistance(o2.getLocation(), target.getLocation()); 175 return (int)Math.signum(d1 - d2); 176 } 177 }); 178 } else { 179 Collections.shuffle(drones); 180 } 181 182 for (ShipAPI drone : drones) { 183 if (num < getNumToFire()) { 184 MissileAPI missile = (MissileAPI) engine.spawnProjectile( 185 ship, weapon, getWeaponId(), 186 new Vector2f(drone.getLocation()), drone.getFacing(), new Vector2f(drone.getVelocity())); 187 if (target != null && missile.getAI() instanceof GuidedMissileAI) { 188 GuidedMissileAI ai = (GuidedMissileAI) missile.getAI(); 189 ai.setTarget(target); 190 } 191 //missile.setHitpoints(missile.getHitpoints() * drone.getHullLevel()); 192 missile.setEmpResistance(10000); 193 194 float base = missile.getMaxRange(); 195 float max = getMaxRange(ship); 196 missile.setMaxRange(max); 197 missile.setMaxFlightTime(missile.getMaxFlightTime() * max/base); 198 199 drone.getWing().removeMember(drone); 200 drone.setWing(null); 201 drone.setExplosionFlashColorOverride(new Color(255, 100, 50, 255)); 202 engine.addLayeredRenderingPlugin(new DroneMissileScript(drone, missile)); 203 204// engine.removeEntity(drone); 205// drone.getVelocity().set(0, 0); 206// drone.setHulk(true); 207// drone.setHitpoints(-1f); 208 209 //float thickness = 16f; 210// EmpArcParams params = new EmpArcParams(); 211// params.segmentLengthMult = 4f; 212// //params.glowSizeMult = 0.5f; 213// params.brightSpotFadeFraction = 0.33f; 214// params.brightSpotFullFraction = 1f; 215//// params.movementDurMax = 0.2f; 216// params.flickerRateMult = 0.7f; 217 218 219 float thickness = 26f; 220 float coreWidthMult = 0.67f; 221 EmpArcEntityAPI arc = engine.spawnEmpArcVisual(ship.getLocation(), ship, 222 missile.getLocation(), missile, thickness, new Color(255,100,100,255), Color.white, null); 223 arc.setCoreWidthOverride(thickness * coreWidthMult); 224 arc.setSingleFlickerMode(); 225 } else { 226 if (drone.getShipAI() != null) { 227 drone.getShipAI().cancelCurrentManeuver(); 228 } 229 } 230 num++; 231 } 232 } 233 234 235 public void unapply(MutableShipStatsAPI stats, String id) { 236 // never called 237 } 238 239 protected ShipAPI forceNextTarget = null; 240 protected ShipAPI findTarget(ShipAPI ship) { 241 if (getDrones(ship).isEmpty()) { 242 return null; 243 } 244 245 if (forceNextTarget != null && forceNextTarget.isAlive()) { 246 return forceNextTarget; 247 } 248 249 float range = getMaxRange(ship); 250 boolean player = ship == Global.getCombatEngine().getPlayerShip(); 251 ShipAPI target = ship.getShipTarget(); 252 253 // If not the player: 254 // The AI sets forceNextTarget, so if we're here, that target got destroyed in the last frame 255 // or it's using a different AI 256 // so, find *something* as a failsafe 257 258 if (!player) { 259 Object test = ship.getAIFlags().getCustom(AIFlags.MANEUVER_TARGET); 260 if (test instanceof ShipAPI) { 261 target = (ShipAPI) test; 262 float dist = Misc.getDistance(ship.getLocation(), target.getLocation()); 263 float radSum = ship.getCollisionRadius() + target.getCollisionRadius(); 264 if (dist > range + radSum) target = null; 265 } 266 if (target == null) { 267 target = Misc.findClosestShipEnemyOf(ship, ship.getMouseTarget(), HullSize.FRIGATE, range, true); 268 } 269 return target; 270 } 271 272 // Player ship 273 274 if (target != null) return target; // was set with R, so, respect that 275 276 // otherwise, find the nearest thing to the mouse cursor, regardless of if it's in range 277 278 target = Misc.findClosestShipEnemyOf(ship, ship.getMouseTarget(), HullSize.FIGHTER, Float.MAX_VALUE, true); 279 if (target != null && target.isFighter()) { 280 ShipAPI nearbyShip = Misc.findClosestShipEnemyOf(ship, target.getLocation(), HullSize.FRIGATE, 100, false); 281 if (nearbyShip != null) target = nearbyShip; 282 } 283 if (target == null) { 284 target = Misc.findClosestShipEnemyOf(ship, ship.getLocation(), HullSize.FIGHTER, range, true); 285 } 286 287 return target; 288 } 289 290 291 public StatusData getStatusData(int index, State state, float effectLevel) { 292 return null; 293 } 294 295 public List<ShipAPI> getDrones(ShipAPI ship) { 296 List<ShipAPI> result = new ArrayList<ShipAPI>(); 297 for (FighterLaunchBayAPI bay : ship.getLaunchBaysCopy()) { 298 if (bay.getWing() == null) continue; 299 for (ShipAPI drone : bay.getWing().getWingMembers()) { 300 result.add(drone); 301 } 302 } 303 return result; 304 } 305 306 @Override 307 public String getInfoText(ShipSystemAPI system, ShipAPI ship) { 308 if (system.isOutOfAmmo()) return null; 309 if (system.getState() != SystemState.IDLE) return null; 310 311 if (getDrones(ship).isEmpty()) { 312 return "NO DRONES"; 313 } 314 315 float range = getMaxRange(ship); 316 317 ShipAPI target = findTarget(ship); 318 if (target == null) { 319 if (ship.getMouseTarget() != null) { 320 float dist = Misc.getDistance(ship.getLocation(), ship.getMouseTarget()); 321 float radSum = ship.getCollisionRadius(); 322 if (dist + radSum > range) { 323 return "OUT OF RANGE"; 324 } 325 } 326 return "NO TARGET"; 327 } 328 329 float dist = Misc.getDistance(ship.getLocation(), target.getLocation()); 330 float radSum = ship.getCollisionRadius() + target.getCollisionRadius(); 331 if (dist > range + radSum) { 332 return "OUT OF RANGE"; 333 } 334 335 return "READY"; 336 } 337 338 339 @Override 340 public boolean isUsable(ShipSystemAPI system, ShipAPI ship) { 341 if (ship != null && ship.getSystem() != null && ship.getSystem().getState() != SystemState.IDLE) { 342 return true; // preventing out-of-ammo click when launching last drone 343 } 344 return !getDrones(ship).isEmpty(); 345// if (true) return true; 346// ShipAPI target = findTarget(ship); 347// return target != null && target != ship; 348 } 349 350 public float getMaxRange(ShipAPI ship) { 351 if (weapon == null) { 352 weapon = Global.getCombatEngine().createFakeWeapon(ship, getWeaponId()); 353 } 354 //return weapon.getRange(); 355 return ship.getMutableStats().getSystemRangeBonus().computeEffective(weapon.getRange()); 356 } 357 public boolean dronesUsefulAsPD() { 358 return true; 359 } 360 public boolean droneStrikeUsefulVsFighters() { 361 return false; 362 } 363 public int getMaxDrones() { 364 return 2; 365 } 366 public float getMissileSpeed() { 367 return weapon.getProjectileSpeed(); 368 } 369 public void setForceNextTarget(ShipAPI forceNextTarget) { 370 this.forceNextTarget = forceNextTarget; 371 } 372 public ShipAPI getForceNextTarget() { 373 return forceNextTarget; 374 } 375} 376 377 378 379 380 381 382 383