001package com.fs.starfarer.api.impl.combat.dem; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import java.awt.Color; 007 008import org.json.JSONArray; 009import org.json.JSONException; 010import org.json.JSONObject; 011import org.lwjgl.util.vector.Vector2f; 012 013import com.fs.starfarer.api.Global; 014import com.fs.starfarer.api.combat.BaseEveryFrameCombatPlugin; 015import com.fs.starfarer.api.combat.CollisionClass; 016import com.fs.starfarer.api.combat.CombatEngineLayers; 017import com.fs.starfarer.api.combat.CombatEntityAPI; 018import com.fs.starfarer.api.combat.DamageType; 019import com.fs.starfarer.api.combat.GuidedMissileAI; 020import com.fs.starfarer.api.combat.MissileAIPlugin; 021import com.fs.starfarer.api.combat.MissileAPI; 022import com.fs.starfarer.api.combat.ShipAPI; 023import com.fs.starfarer.api.combat.ShipCommand; 024import com.fs.starfarer.api.combat.ShipHullSpecAPI; 025import com.fs.starfarer.api.combat.ShipVariantAPI; 026import com.fs.starfarer.api.combat.ShipwideAIFlags.AIFlags; 027import com.fs.starfarer.api.combat.WeaponAPI; 028import com.fs.starfarer.api.input.InputEventAPI; 029import com.fs.starfarer.api.loading.WeaponGroupSpec; 030import com.fs.starfarer.api.loading.WeaponGroupType; 031import com.fs.starfarer.api.util.Misc; 032 033/** 034 * 035 */ 036public class DEMScript extends BaseEveryFrameCombatPlugin implements MissileAIPlugin { 037 038 public static enum State { 039 WAIT, 040 TURN_TO_TARGET, 041 SIGNAL, 042 FIRE, 043 DONE, 044 } 045 046 protected State state = State.WAIT; 047 protected MissileAPI missile; 048 protected ShipAPI ship; 049 protected WeaponAPI weapon; 050 protected CombatEntityAPI fireTarget; 051 protected ShipAPI demDrone; 052 053 //protected Vector2f targetingLaserFireOffset = new Vector2f(); 054 protected List<Vector2f> targetingLaserFireOffset = new ArrayList<Vector2f>(); 055 protected List<Vector2f> targetingLaserSweepAngles = new ArrayList<Vector2f>(); 056 protected List<Vector2f> payloadSweepAngles = new ArrayList<Vector2f>(); 057 protected List<Float> payloadSweepPhaseShift = new ArrayList<Float>(); 058 protected float minDelayBeforeTriggering = 0f; 059 protected boolean useTriggerAngle = false; 060 protected float triggerAngle = 0f; 061 protected float allowedDriftFraction = 0f; 062 protected float triggerDistance = 0f; 063 protected float turnRateBoost = 0f; 064 protected float turnRateMultOnSignal = 1f; 065 protected float targetingLaserArc = 0f; 066 protected float targetingTime = 1f; 067 protected float firingTime = 1f; 068 protected String targetingLaserId; 069 protected String payloadWeaponId; 070 protected float preferredMinFireDistance; 071 protected float preferredMaxFireDistance; 072 protected float targetingLaserRange; 073 protected float payloadSweepRateMult; 074 protected boolean bombPumped; 075 protected boolean fadeOutEngineWhenFiring; 076 protected boolean destroyMissleWhenDoneFiring; 077 protected boolean randomStrafe; 078 protected boolean randomPayloadSweepPhaseShift; 079 protected boolean payloadCenterSweepOnOriginalOffset; 080 protected boolean snapFacingToTargetIfCloseEnough = true; 081 protected Color destroyedExplosionColor; 082 083 protected float elapsedWaiting = 0f; 084 protected float elapsedTargeting = 0f; 085 protected float elapsedFiring = 0f; 086 //protected DamagingProjectileAPI explosion; 087 protected int explosionDelayFrames = 0; 088 protected float strafeDur = 0f; 089 protected float strafeDir = 0f; 090 protected boolean exploded = false; 091 092 protected ShapedExplosionParams p; 093 094 public DEMScript(MissileAPI missile, ShipAPI ship, WeaponAPI weapon) { 095 this.missile = missile; 096 this.ship = ship; 097 this.weapon = weapon; 098 099 JSONObject json = missile.getSpec().getBehaviorJSON(); 100 //minDelayBeforeTriggering = (float) json.optDouble("minDelayBeforeTriggering", 1f); 101 minDelayBeforeTriggering = getValue(json, "minDelayBeforeTriggering", 1f); 102 allowedDriftFraction = (float) json.optDouble("allowedDriftFraction", 0.33f); 103 //triggerDistance = (float) json.optDouble("triggerDistance", 500f); 104 //preferredMinFireDistance = (float) json.optDouble("preferredMinFireDistance", 0f); 105 triggerDistance = getValue(json, "triggerDistance", 500f); 106 107 try { 108 if (json.optBoolean("withShapedExplosion")) { 109 p = new ShapedExplosionParams(); 110 p.load(json);; 111 } 112 } catch (Exception e) { 113 throw new RuntimeException(e); 114 } 115 116 snapFacingToTargetIfCloseEnough = json.optBoolean("snapFacingToTargetIfCloseEnough", false); 117 118 if (json.has("triggerAngle")) { 119 useTriggerAngle = true; 120 triggerAngle = getValue(json, "triggerAngle", 0f); 121 } 122 123 preferredMaxFireDistance = getValue(json, "preferredMaxFireDistance", triggerDistance); 124 preferredMinFireDistance = getValue(json, "preferredMinFireDistance", 0f); 125 if (json.has("targetingLaserRange")) { 126 targetingLaserRange = (float) json.optDouble("targetingLaserRange", 600f); 127 } else { 128 targetingLaserRange = Math.max(triggerDistance, preferredMinFireDistance) + 200f; 129 } 130 turnRateBoost = (float) json.optDouble("turnRateBoost", 100f); 131 turnRateMultOnSignal = (float) json.optDouble("turnRateMultOnSignal", 1f); 132 //targetingTime = (float) json.optDouble("targetingTime", 1f); 133 targetingTime = getValue(json, "targetingTime", 1f); 134 firingTime = (float) json.optDouble("firingTime", 1.25f); 135 targetingLaserId = json.optString("targetingLaserId", null); 136 payloadWeaponId = json.optString("payloadWeaponId", null); 137 targetingLaserArc = (float) json.optDouble("targetingLaserArc", 10f); 138 payloadSweepRateMult = (float) json.optDouble("payloadSweepRateMult", 1f); 139 bombPumped = json.optBoolean("bombPumped", false); 140 fadeOutEngineWhenFiring = json.optBoolean("fadeOutEngineWhenFiring", false); 141 destroyMissleWhenDoneFiring = json.optBoolean("destroyMissleWhenDoneFiring", false); 142 randomStrafe = json.optBoolean("randomStrafe", false); 143 randomPayloadSweepPhaseShift = json.optBoolean("randomPayloadSweepPhaseShift", false); 144 payloadCenterSweepOnOriginalOffset = json.optBoolean("payloadCenterSweepOnOriginalOffset", false); 145 if (json.has("destroyedExplosionColor")) { 146 try { 147 destroyedExplosionColor = Misc.optColor(json, "destroyedExplosionColor", null); 148 } catch (Exception e) { 149 throw new RuntimeException(e); 150 } 151 } 152 153 JSONArray arr = json.optJSONArray("targetingLaserFireOffset"); 154 if (arr != null) { 155 for (int i = 0; i < arr.length(); i += 2) { 156 Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1)); 157 targetingLaserFireOffset.add(v); 158 } 159 } 160 arr = json.optJSONArray("targetingLaserSweepAngles"); 161 if (arr != null) { 162 for (int i = 0; i < arr.length(); i += 2) { 163 Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1)); 164 targetingLaserSweepAngles.add(v); 165 } 166 } 167 arr = json.optJSONArray("payloadSweepAngles"); 168 if (arr != null) { 169 for (int i = 0; i < arr.length(); i += 2) { 170 Vector2f v = new Vector2f((float) arr.optDouble(i), (float) arr.optDouble(i + 1)); 171 172 if (payloadCenterSweepOnOriginalOffset) { 173 float orig = Global.getSettings().getWeaponSpec(payloadWeaponId).getTurretAngleOffsets().get(i/2); 174 v.x += orig; 175 v.y += orig; 176 } 177 178 payloadSweepAngles.add(v); 179 } 180 } 181 if (randomPayloadSweepPhaseShift) { 182 for (int i = 0; i < payloadSweepAngles.size(); i++) { 183 payloadSweepPhaseShift.add((float) Math.random()); 184 } 185 } 186 float maxSpeed = Math.max(50f, missile.getMaxSpeed()); 187 float etaMod = -1f * triggerDistance / maxSpeed; 188 missile.setEtaModifier(etaMod); 189 } 190 191 public static float getValue(JSONObject json, String key, float defaultValue) { 192 JSONArray arr = json.optJSONArray(key); 193 if (arr != null) { 194 Vector2f v = new Vector2f((float) arr.optDouble(0), (float) arr.optDouble(1)); 195 return v.x + (v.y - v.x) * (float) Math.random(); 196 } 197 return (float) json.optDouble(key, defaultValue); 198 } 199 200 201 @Override 202 public void advance(float amount, List<InputEventAPI> events) { 203 if (Global.getCombatEngine().isPaused()) return; 204 205 // so that the AI doesn't treat fizzled missiles as a threat due to the drone still being there 206 if (missile.isFizzling()) { 207 if (demDrone != null) { 208 Global.getCombatEngine().removeEntity(demDrone); 209 } 210 } 211 212 boolean doCleanup = state == State.DONE || 213 (!bombPumped || state.ordinal() < State.FIRE.ordinal()) && 214 (missile.isExpired() || missile.didDamage() || 215 !Global.getCombatEngine().isEntityInPlay(missile)); 216 if (doCleanup) { 217 if (demDrone != null) { 218 Global.getCombatEngine().removeEntity(demDrone); 219 } 220 Global.getCombatEngine().removePlugin(this); 221 return; 222 } 223 224 if (state == State.WAIT && missile.isArmed() && !missile.isFizzling() && !missile.isFading()) { 225 CombatEntityAPI target = null; 226 if (missile.getAI() instanceof GuidedMissileAI) { 227 GuidedMissileAI ai = (GuidedMissileAI) missile.getAI(); 228 target = ai.getTarget(); 229 } 230 elapsedWaiting += amount; 231 232 if (useTriggerAngle && target != null) { 233 Vector2f from = target.getLocation(); 234 if (target instanceof ShipAPI) { 235 from = ((ShipAPI) target).getShieldCenterEvenIfNoShield(); 236 } 237 float toMissile = Misc.getAngleInDegrees(from, missile.getLocation()); 238 //float diff = Misc.getAngleDiff(target.getFacing(), toMissile); 239 //float toShip = Misc.getAngleInDegrees(from, ship.getLocation()); 240 float toShip = Misc.getAngleInDegrees(from, missile.getSpawnLocation()); 241 float diff = Misc.getAngleDiff(toShip, toMissile); 242 if (diff >= triggerAngle) { 243 elapsedWaiting = minDelayBeforeTriggering; 244 } 245 } 246 247 if (target != null && elapsedWaiting >= minDelayBeforeTriggering) { 248 float dist = Misc.getDistance(target.getLocation(), missile.getLocation()); 249 dist -= Global.getSettings().getTargetingRadius(missile.getLocation(), target, false); 250 251 if (dist < triggerDistance) { 252 missile.setMaxFlightTime(10000f); 253 state = State.TURN_TO_TARGET; 254 fireTarget = target; 255 256 // turn off the normal missile AI; this script is taking over 257 missile.setMissileAI(this); 258 259 missile.getEngineStats().getMaxTurnRate().modifyFlat("dem", turnRateBoost); 260 missile.getEngineStats().getTurnAcceleration().modifyFlat("dem", turnRateBoost * 2f); 261 262 ShipHullSpecAPI spec = Global.getSettings().getHullSpec("dem_drone"); 263 ShipVariantAPI v = Global.getSettings().createEmptyVariant("dem_drone", spec); 264 v.addWeapon("WS 000", targetingLaserId); 265 WeaponGroupSpec g = new WeaponGroupSpec(WeaponGroupType.LINKED); 266 g.addSlot("WS 000"); 267 v.addWeaponGroup(g); 268 v.addWeapon("WS 001", payloadWeaponId); 269 g = new WeaponGroupSpec(WeaponGroupType.LINKED); 270 g.addSlot("WS 001"); 271 v.addWeaponGroup(g); 272 273 demDrone = Global.getCombatEngine().createFXDrone(v); 274 demDrone.setLayer(CombatEngineLayers.ABOVE_SHIPS_AND_MISSILES_LAYER); 275 demDrone.setOwner(ship.getOriginalOwner()); 276 demDrone.getMutableStats().getBeamWeaponRangeBonus().modifyFlat("dem", targetingLaserRange); 277 demDrone.getMutableStats().getHullDamageTakenMult().modifyMult("dem", 0f); // so it's non-targetable 278 demDrone.setDrone(true); 279 demDrone.getAIFlags().setFlag(AIFlags.DRONE_MOTHERSHIP, 100000f, ship); 280 demDrone.getMutableStats().getEnergyWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult()); 281 demDrone.getMutableStats().getMissileWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult()); 282 demDrone.getMutableStats().getBallisticWeaponDamageMult().applyMods(ship.getMutableStats().getMissileWeaponDamageMult()); 283 284 demDrone.getMutableStats().getDamageToCapital().applyMods(ship.getMutableStats().getDamageToCapital()); 285 demDrone.getMutableStats().getDamageToCruisers().applyMods(ship.getMutableStats().getDamageToCruisers()); 286 demDrone.getMutableStats().getDamageToDestroyers().applyMods(ship.getMutableStats().getDamageToDestroyers()); 287 demDrone.getMutableStats().getDamageToFrigates().applyMods(ship.getMutableStats().getDamageToFrigates()); 288 demDrone.getMutableStats().getDamageToFighters().applyMods(ship.getMutableStats().getDamageToFighters()); 289 demDrone.getMutableStats().getDamageToMissiles().applyMods(ship.getMutableStats().getDamageToMissiles()); 290 demDrone.getMutableStats().getDamageToTargetEnginesMult().applyMods(ship.getMutableStats().getDamageToTargetEnginesMult()); 291 demDrone.getMutableStats().getDamageToTargetHullMult().applyMods(ship.getMutableStats().getDamageToTargetHullMult()); 292 demDrone.getMutableStats().getDamageToTargetShieldsMult().applyMods(ship.getMutableStats().getDamageToTargetShieldsMult()); 293 demDrone.getMutableStats().getDamageToTargetWeaponsMult().applyMods(ship.getMutableStats().getDamageToTargetWeaponsMult()); 294 295 demDrone.setCollisionClass(CollisionClass.NONE); 296 demDrone.giveCommand(ShipCommand.SELECT_GROUP, null, 0); 297 Global.getCombatEngine().addEntity(demDrone); 298 299 if (targetingLaserFireOffset.size() > 0) { 300 WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0); 301 tLaser.ensureClonedSpec(); 302 tLaser.getSpec().getTurretFireOffsets().clear(); 303 tLaser.getSpec().getTurretFireOffsets().addAll(targetingLaserFireOffset); 304 } 305 } 306 } 307 } else if (state == State.TURN_TO_TARGET) { 308 float angle = Misc.getAngleInDegrees(missile.getLocation(), fireTarget.getLocation()); 309 310 if (Misc.isInArc(missile.getFacing(), targetingLaserArc, angle)) { 311 missile.getEngineStats().getMaxTurnRate().modifyMult("dem_mult", turnRateMultOnSignal); 312 //missile.getEngineStats().getTurnAcceleration().modifyMult("dem_mult", turnRateMultOnSignal); 313 314 state = State.SIGNAL; 315 } 316 } else if (state == State.SIGNAL) { 317 318 if (targetingLaserSweepAngles.size() > 0) { 319 float progress = elapsedTargeting / targetingTime; 320 WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0); 321 tLaser.ensureClonedSpec(); 322 tLaser.getSpec().getTurretAngleOffsets().clear(); 323 for (Vector2f curr : targetingLaserSweepAngles) { 324 float angle = 0f; 325 if (progress < 0.5f) { 326 angle = curr.x + (curr.y - curr.x) * progress * 2f; 327 } else { 328 angle = curr.x + (curr.y - curr.x) * (1f - progress) * 2f; 329 } 330 tLaser.getSpec().getTurretAngleOffsets().add(angle); 331 } 332 } 333 334 if (targetingLaserRange > 0 && targetingTime > 0) { 335 demDrone.giveCommand(ShipCommand.FIRE, fireTarget.getLocation(), 0); 336 } 337 338 elapsedTargeting += amount; 339 if (elapsedTargeting >= targetingTime) { 340 state = State.FIRE; 341 demDrone.giveCommand(ShipCommand.SELECT_GROUP, null, 1); 342 343 if (!bombPumped) { 344 //missile.flameOut(); 345 missile.setFlightTime(0f); 346 missile.setMaxFlightTime(firingTime); 347 missile.setNoFlameoutOnFizzling(true); 348 missile.setNoGlowTime(0f); 349 missile.setFizzleTime(0.5f); 350 missile.setFadeTime(0.5f); 351 missile.setEtaModifier(0f); 352 } 353 } 354 } else if (state == State.FIRE) { 355 //Global.getCombatEngine().setPaused(true); 356 357 if (bombPumped && !exploded && explosionDelayFrames >= 1) { 358 missile.explode(); 359 Global.getCombatEngine().removeEntity(missile); 360 361// ShapedExplosionParams p = new ShapedExplosionParams(); 362// p.shapedExplosionNumParticles = 50; 363// p.shapedExplosionMinParticleDur = 0.7f; 364// p.shapedExplosionMaxParticleDur = 1.1f; 365// p.shapedExplosionMinParticleSize = 50f; 366// p.shapedExplosionMaxParticleSize = 70f; 367// p.shapedExplosionColor = new Color(255,40,40,155); 368// p.shapedExplosionArc = 45f; 369// p.shapedExplosionMinParticleVel = 50f; 370// p.shapedExplosionMaxParticleVel = 250f; 371// 372//// p.shapedExplosionMinParticleDur = 1f; 373//// p.shapedExplosionMaxParticleDur = 3f; 374// 375// float speedMult = 1f; 376// p.shapedExplosionMinParticleVel *= speedMult; 377// p.shapedExplosionMaxParticleVel *= speedMult; 378// p.shapedExplosionMinParticleDur /= speedMult; 379// p.shapedExplosionMaxParticleDur /= speedMult; 380 if (p != null) { 381 spawnShapedExplosion(missile.getLocation(), missile.getFacing(), p); 382 } 383 exploded = true; 384 } 385 explosionDelayFrames++; 386 387 if (fadeOutEngineWhenFiring) { 388 float progress = elapsedFiring / firingTime; 389 progress *= 2f; 390 if (progress > 1f) progress = 1f; 391 missile.getEngineController().fadeToOtherColor(this, Misc.zeroColor, Misc.zeroColor, progress, 1f); 392 } 393 394 if (payloadSweepAngles.size() > 0) { 395 WeaponAPI payload = demDrone.getWeaponGroupsCopy().get(1).getWeaponsCopy().get(0); 396 payload.ensureClonedSpec(); 397 payload.getSpec().getTurretAngleOffsets().clear(); 398 int index = 0; 399 for (Vector2f curr : payloadSweepAngles) { 400 float angle = 0f; 401 float progress = elapsedFiring / firingTime; 402 if (progress < 0.5f) { 403 angle = curr.x + (curr.y - curr.x) * progress * 2f; 404 } else { 405 angle = curr.x + (curr.y - curr.x) * (1f - progress) * 2f; 406 } 407 if (randomPayloadSweepPhaseShift) { 408 progress += payloadSweepPhaseShift.get(index); 409 progress = (float) Math.sin(progress * Math.PI * payloadSweepRateMult); 410 progress = Math.abs(progress); 411 412 angle = curr.x + (curr.y - curr.x) * progress; 413 } 414 415 416 payload.getSpec().getTurretAngleOffsets().add(angle); 417 index++; 418 } 419 } 420 421 422 // use payload's normal range as defined in weapon_data.csv 423 demDrone.getMutableStats().getBeamWeaponRangeBonus().unmodifyFlat("dem"); 424 demDrone.giveCommand(ShipCommand.FIRE, fireTarget.getLocation(), 0); 425 426 elapsedFiring += amount; 427 if (elapsedFiring >= firingTime) { 428 missile.setNoGlowTime(10f); 429 state = State.DONE; 430 431 if (destroyMissleWhenDoneFiring) { 432 missile.getVelocity().set(0, 0); 433 if (destroyedExplosionColor != null) { 434 missile.setDestroyedExplosionColorOverride(destroyedExplosionColor); 435 } 436 Global.getCombatEngine().applyDamage(missile, missile.getLocation(), 100000f, DamageType.ENERGY, 0f, false, false, demDrone, false); 437 } else { 438 missile.setFizzleTime(1f); 439 missile.setArmedWhileFizzling(false); 440 } 441 } 442 } 443 444 doMissileControl(amount); 445 updateDroneState(amount); 446 447 } 448 449 protected void updateDroneState(float amount) { 450 if (demDrone != null) { 451 //System.out.println("FIRE FACING: " + missile.getFacing()); 452 //if (explosion == null) { 453 demDrone.setOwner(missile.getOwner()); 454 demDrone.getLocation().set(missile.getLocation()); 455 demDrone.setFacing(missile.getFacing()); 456 demDrone.getVelocity().set(missile.getVelocity()); 457 demDrone.setAngularVelocity(missile.getAngularVelocity()); 458 //demDrone.getMouseTarget().set(fireTarget.getLocation()); 459 //} 460 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(missile.getFacing()); 461 dir.scale(1000f); 462 Vector2f.add(dir, missile.getLocation(), dir); 463 demDrone.getMouseTarget().set(dir); 464 465 //demDrone.getMutableStats().getWeaponTurnRateBonus().modifyMult("dem", 0f); 466 467 WeaponAPI tLaser = demDrone.getWeaponGroupsCopy().get(0).getWeaponsCopy().get(0); 468 WeaponAPI payload = demDrone.getWeaponGroupsCopy().get(1).getWeaponsCopy().get(0); 469 tLaser.setFacing(missile.getFacing()); 470 payload.setFacing(missile.getFacing()); 471 tLaser.setKeepBeamTargetWhileChargingDown(true); 472 payload.setKeepBeamTargetWhileChargingDown(true); 473 tLaser.setScaleBeamGlowBasedOnDamageEffectiveness(false); 474 if (firingTime <= 2f) { 475 payload.setScaleBeamGlowBasedOnDamageEffectiveness(false); 476 } 477 tLaser.updateBeamFromPoints(); 478 payload.updateBeamFromPoints(); 479 } 480 } 481 482 protected void doMissileControl(float amount) { 483 if (state == State.TURN_TO_TARGET || state == State.SIGNAL || 484 (state == State.FIRE && !bombPumped && !fadeOutEngineWhenFiring)) { 485 486 float dist = Misc.getDistance(fireTarget.getLocation(), missile.getLocation()); 487 dist -= Global.getSettings().getTargetingRadius(missile.getLocation(), fireTarget, false); 488 if (dist < preferredMinFireDistance) { 489 missile.giveCommand(ShipCommand.ACCELERATE_BACKWARDS); 490 } else if (dist > preferredMaxFireDistance) { 491 missile.giveCommand(ShipCommand.ACCELERATE); 492 } else if (missile.getVelocity().length() > missile.getMaxSpeed() * allowedDriftFraction) { 493 missile.giveCommand(ShipCommand.DECELERATE); 494 } 495 float dir = Misc.getAngleInDegrees(missile.getLocation(), fireTarget.getLocation()); 496 float diff = Misc.getAngleDiff(missile.getFacing(), dir); 497 float rate = missile.getMaxTurnRate() * amount; 498// float turnDir1 = Misc.getClosestTurnDirection(missile.getFacing(), dir); 499// boolean turningTowardsDesiredFacing = Math.signum(turnDir1) == Math.signum(missile.getAngularVelocity()); 500 boolean turningTowardsDesiredFacing = true; 501 //snapFacingToTargetIfCloseEnough = true; 502 boolean phased = fireTarget instanceof ShipAPI && ((ShipAPI)fireTarget).isPhased(); 503 if (!phased) { 504 if (diff <= rate * 0.25f && turningTowardsDesiredFacing && snapFacingToTargetIfCloseEnough) { 505 missile.setFacing(dir); 506 } else { 507 Misc.turnTowardsPointV2(missile, fireTarget.getLocation(), 0f); 508 } 509 } 510 511 if (randomStrafe) { 512 if (strafeDur <= 0) { 513 float r = (float) Math.random(); 514 515 if (strafeDir == 0) { 516 if (r < 0.4f) { 517 strafeDir = 1f; 518 } else if (r < 0.8f) { 519 strafeDir = -1f; 520 } else { 521 strafeDir = 0f; 522 } 523 } else { 524 if (r < 0.8f) { 525 strafeDir = -strafeDir; 526 } else { 527 strafeDir = 0f; 528 } 529 } 530 531 strafeDur = 0.5f + (float) Math.random() * 0.5f; 532 //strafeDur *= 0.5f; 533 } 534 535 Vector2f driftDir = Misc.getUnitVectorAtDegreeAngle(missile.getFacing() + 90f); 536 if (strafeDir == 1f) driftDir.negate(); 537 538 float distToShip = Misc.getDistance(ship.getLocation(), missile.getLocation()); 539 float shipToFireTarget = Misc.getDistance(ship.getLocation(), fireTarget.getLocation()); 540 float extra = 0f; 541 if (dist > shipToFireTarget) extra = dist - shipToFireTarget; 542 if (distToShip < ship.getCollisionRadius() * 1f + extra) { 543 float away = Misc.getAngleInDegrees(ship.getLocation(), missile.getLocation()); 544 float turnDir = Misc.getClosestTurnDirection(away, missile.getFacing()); 545 strafeDir = turnDir; 546 } 547 548 float maxDrift = missile.getMaxSpeed() * allowedDriftFraction; 549 float speedInDir = Vector2f.dot(driftDir, missile.getVelocity()); 550 551 if (speedInDir < maxDrift) { 552 if (strafeDir == 1f) { 553 missile.giveCommand(ShipCommand.STRAFE_RIGHT); 554 } else if (strafeDir == -1f) { 555 missile.giveCommand(ShipCommand.STRAFE_LEFT); 556 } 557 } 558 559 strafeDur -= amount; 560 } 561 } 562 } 563 564 public void advance(float amount) { 565 // MissileAIPlugin.advance() 566 // unused, but just want the missile to have a non-null AI 567 } 568 569 570 571 public static class ShapedExplosionParams { 572 public float shapedExplosionEndSizeMin = 1f; 573 public float shapedExplosionEndSizeMax = 2f; 574 public Color shapedExplosionColor = new Color(255,150,130,155); 575 public int shapedExplosionNumParticles = 200; 576 public float shapedExplosionMinParticleSize = 80; 577 public float shapedExplosionMaxParticleSize = 100; 578 public float shapedExplosionScatter = 100f; 579 public float shapedExplosionMinParticleVel = 100; 580 public float shapedExplosionMaxParticleVel = 350f; 581 public float shapedExplosionMinParticleDur = 1f; 582 public float shapedExplosionMaxParticleDur = 2f; 583 public float shapedExplosionArc = 90f; 584 585 public void load(JSONObject json) throws JSONException { 586 shapedExplosionEndSizeMin = (float)json.optDouble("shapedExplosionEndSizeMin", 1f); 587 shapedExplosionEndSizeMax = (float)json.optDouble("shapedExplosionEndSizeMax", 2f); 588 shapedExplosionNumParticles = json.optInt("shapedExplosionNumParticles"); 589 shapedExplosionMinParticleSize = (float)json.optDouble("shapedExplosionMinParticleSize", 80f); 590 shapedExplosionMaxParticleSize = (float)json.optDouble("shapedExplosionMaxParticleSize", 100f); 591 shapedExplosionScatter = (float)json.optDouble("shapedExplosionScatter", 100f); 592 shapedExplosionMinParticleVel = (float)json.optDouble("shapedExplosionMinParticleVel", 100f); 593 shapedExplosionMaxParticleVel = (float)json.optDouble("shapedExplosionMaxParticleVel", 350f); 594 shapedExplosionMinParticleDur = (float)json.optDouble("shapedExplosionMinParticleDur", 1f); 595 shapedExplosionMaxParticleDur = (float)json.optDouble("shapedExplosionMaxParticleDur", 2f); 596 shapedExplosionArc = (float)json.optDouble("shapedExplosionArc", 90f); 597 shapedExplosionColor = Misc.optColor(json, "shapedExplosionColor", null); 598 } 599 } 600 601 public void spawnShapedExplosion(Vector2f loc, float angle, ShapedExplosionParams p) { 602 603 if (Global.getCombatEngine().getViewport().isNearViewport(ship.getLocation(), 800f)) { 604 int numParticles = p.shapedExplosionNumParticles; 605 float minSize = p.shapedExplosionMinParticleSize; 606 float maxSize = p.shapedExplosionMaxParticleSize; 607 Color pc = p.shapedExplosionColor; 608 609 float minDur = p.shapedExplosionMinParticleDur; 610 float maxDur = p.shapedExplosionMaxParticleDur; 611 612 float arc = p.shapedExplosionArc; 613 float scatter = p.shapedExplosionScatter; 614 float minVel = p.shapedExplosionMinParticleVel; 615 float maxVel = p.shapedExplosionMaxParticleVel; 616 617 float endSizeMin = p.shapedExplosionEndSizeMin; 618 float endSizeMax = p.shapedExplosionEndSizeMax; 619 620 Vector2f spawnPoint = new Vector2f(loc); 621 for (int i = 0; i < numParticles; i++) { 622 //p.setMaxAge(500 + (int)(Math.random() * 1000f)); 623 float angleOffset = (float) Math.random(); 624 if (angleOffset > 0.2f) { 625 angleOffset *= angleOffset; 626 } 627 float speedMult = 1f - angleOffset; 628 speedMult = 0.5f + speedMult * 0.5f; 629 angleOffset *= Math.signum((float) Math.random() - 0.5f); 630 angleOffset *= arc/2f; 631 float theta = (float) Math.toRadians(angle + angleOffset); 632 float r = (float) (Math.random() * Math.random() * scatter); 633 float x = (float)Math.cos(theta) * r; 634 float y = (float)Math.sin(theta) * r; 635 Vector2f pLoc = new Vector2f(spawnPoint.x + x, spawnPoint.y + y); 636 637 float speed = minVel + (maxVel - minVel) * (float) Math.random(); 638 speed *= speedMult; 639 640 Vector2f pVel = Misc.getUnitVectorAtDegreeAngle((float) Math.toDegrees(theta)); 641 pVel.scale(speed); 642 643 float pSize = minSize + (maxSize - minSize) * (float) Math.random(); 644 float pDur = minDur + (maxDur - minDur) * (float) Math.random(); 645 float endSize = endSizeMin + (endSizeMax - endSizeMin) * (float) Math.random(); 646 //Global.getCombatEngine().addSmoothParticle(pLoc, pVel, pSize, 1f, pDur, pc); 647 Global.getCombatEngine().addNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc); 648 //Global.getCombatEngine().addNebulaSmoothParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc); 649 //Global.getCombatEngine().addSwirlyNebulaParticle(pLoc, pVel, pSize, endSize, 0.1f, 0.5f, pDur, pc, false); 650 } 651 } 652 } 653} 654 655 656 657 658 659 660 661