001package com.fs.starfarer.api.impl.combat; 002 003import java.util.ArrayList; 004import java.util.EnumSet; 005import java.util.Iterator; 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.CombatEngineAPI; 015import com.fs.starfarer.api.combat.CombatEngineLayers; 016import com.fs.starfarer.api.combat.CombatEntityAPI; 017import com.fs.starfarer.api.combat.DamagingProjectileAPI; 018import com.fs.starfarer.api.combat.EveryFrameWeaponEffectPlugin; 019import com.fs.starfarer.api.combat.OnFireEffectPlugin; 020import com.fs.starfarer.api.combat.OnHitEffectPlugin; 021import com.fs.starfarer.api.combat.ShipAPI; 022import com.fs.starfarer.api.combat.ViewportAPI; 023import com.fs.starfarer.api.combat.WeaponAPI; 024import com.fs.starfarer.api.combat.listeners.ApplyDamageResultAPI; 025import com.fs.starfarer.api.graphics.SpriteAPI; 026import com.fs.starfarer.api.util.FaderUtil; 027import com.fs.starfarer.api.util.Misc; 028 029/** 030 * The effect for the Cryoflamer, which was at one point called "Cryoflux...". 031 * Not related to the Cryoflux Transducer ship system. 032 * 033 * IMPORTANT: will be multiple instances of this, as this doubles as the every frame effect and the on fire effect (same instance) 034 * But also as the visual for each individual shot (created via onFire, using the non-default constructor) 035 */ 036public class CryofluxTransducerEffect extends BaseCombatLayeredRenderingPlugin implements OnFireEffectPlugin, 037 OnHitEffectPlugin, 038 EveryFrameWeaponEffectPlugin { 039 040 public CryofluxTransducerEffect() { 041 } 042 043 protected String getLoopId() { 044 return "cryoflamer_loop"; 045 } 046 047 protected void playImpactSound(ApplyDamageResultAPI damageResult, Vector2f point, Vector2f vel) { 048 Misc.playSound(damageResult, point, vel, 049 "cryoflamer_hit_shield_light", 050 "cryoflamer_hit_shield_solid", 051 "cryoflamer_hit_shield_heavy", 052 "cryoflamer_hit_light", 053 "cryoflamer_hit_solid", 054 "cryoflamer_hit_heavy"); 055 } 056 057 protected String getParticleSpriteCat() { 058 return "misc"; 059 } 060 protected String getParticleSpriteKey() { 061 return "nebula_particles"; 062 } 063 protected float getParticleScale() { 064 return 1f; 065 } 066 protected float getParticleScaleIncreaseRateMult() { 067 return 1f; 068 } 069 protected int getNumParticles() { 070 return 7; 071 } 072 protected float getThresholdDist() { 073 return 20f; 074 } 075 076 protected List<CryofluxTransducerEffect> trails; 077 public void advance(float amount, CombatEngineAPI engine, WeaponAPI weapon) { 078 if (trails == null) return; 079 080 Iterator<CryofluxTransducerEffect> iter = trails.iterator(); 081 while (iter.hasNext()) { 082 if (iter.next().isExpired()) iter.remove(); 083 } 084 085 // sound loop playback 086 if (weapon.getShip() != null) { 087 float maxRange = weapon.getRange(); 088 ShipAPI ship = weapon.getShip(); 089 Vector2f com = new Vector2f(); 090 float weight = 0f; 091 float totalDist = 0f; 092 Vector2f source = weapon.getLocation(); 093 for (CryofluxTransducerEffect curr : trails) { 094 if (curr.proj != null) { 095 Vector2f.add(com, curr.proj.getLocation(), com); 096 weight += curr.proj.getBrightness(); 097 totalDist += Misc.getDistance(source, curr.proj.getLocation()); 098 } 099 } 100 if (weight > 0.1f) { 101 com.scale(1f / weight); 102 float volume = Math.min(weight, 1f); 103 if (trails.size() > 0) { 104 totalDist /= (float) trails.size(); 105 float mult = totalDist / Math.max(maxRange, 1f); 106 mult = 1f - mult; 107 if (mult > 1f) mult = 1f; 108 if (mult < 0f) mult = 0f; 109 mult = (float) Math.sqrt(mult); 110 volume *= mult; 111 } 112 Global.getSoundPlayer().playLoop(getLoopId(), ship, 1f, volume, com, ship.getVelocity()); 113 } 114 } 115 116 117 //System.out.println("Trails: " + trails.size()); 118 float numIter = 1f; // more doesn't actually change anything 119 amount /= numIter; 120 float thresholdDist = getThresholdDist(); 121 //thresholdDist = 50f; 122 // drag along the previous projectile, starting with the most recently launched; new ones are added at the start 123 // note: prev is fired before and so is in front of proj 124 for (int i = 0; i < numIter; i++) { 125 for (CryofluxTransducerEffect trail : trails) { 126 //trail.proj.setFacing(trail.proj.getFacing() + 180f * amount); 127 if (trail.prev != null && !trail.prev.isExpired() && Global.getCombatEngine().isEntityInPlay(trail.prev)) { 128 float dist1 = Misc.getDistance(trail.prev.getLocation(), trail.proj.getLocation()); 129 if (dist1 < trail.proj.getProjectileSpec().getLength() * 1f) { 130 float maxSpeed = trail.prev.getMoveSpeed() * 0.5f;// * Math.max(0.5f, 1f - trail.prev.getElapsed() * 0.5f); 131 // goal here is to prevent longer shot series (e.g. from Paragon) from moving too unnaturally 132 float e = trail.prev.getElapsed(); 133 float t = 0.5f; 134 if (e > t) { 135 maxSpeed *= Math.max(0.25f, 1f - (e - t) * 0.5f); 136 } 137 if (dist1 < thresholdDist && e > t) { 138 maxSpeed *= dist1 / thresholdDist; 139 } 140 141 Vector2f driftTo = Misc.closestPointOnLineToPoint(trail.proj.getLocation(), trail.proj.getTailEnd(), trail.prev.getLocation()); 142 float dist = Misc.getDistance(driftTo, trail.prev.getLocation()); 143 Vector2f diff = Vector2f.sub(driftTo, trail.prev.getLocation(), new Vector2f()); 144 diff = Misc.normalise(diff); 145 diff.scale(Math.min(dist, maxSpeed * amount)); 146 Vector2f.add(trail.prev.getLocation(), diff, trail.prev.getLocation()); 147 Vector2f.add(trail.prev.getTailEnd(), diff, trail.prev.getTailEnd()); 148 } 149 } 150 } 151 } 152 } 153 154 155 public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, Vector2f point, boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) { 156 Color color = projectile.getProjectileSpec().getFringeColor(); 157// Color inverted = NSLanceEffect.getColorForDarkening(color); 158// inverted = Misc.setAlpha(inverted, 50); 159// Color inverted = new Color(255, 255, 100, 50); 160 161 Vector2f vel = new Vector2f(); 162 if (target instanceof ShipAPI) { 163 vel.set(target.getVelocity()); 164 } 165 166 float size = projectile.getProjectileSpec().getWidth() * 1f; 167 //size = Misc.getHitGlowSize(size, projectile.getDamage().getBaseDamage(), damageResult); 168 float sizeMult = Misc.getHitGlowSize(100f, projectile.getDamage().getBaseDamage(), damageResult) / 100f; 169// sizeMult = 1.5f; 170// System.out.println(sizeMult); 171 float dur = 1f; 172 float rampUp = 0f; 173 Color c = Misc.scaleAlpha(color, projectile.getBrightness()); 174 engine.addNebulaParticle(point, vel, size, 5f + 3f * sizeMult, 175 rampUp, 0f, dur, c); 176// engine.addNegativeNebulaParticle(point, vel, size, 2f, 177// rampUp, 0f, dur, inverted); 178// engine.addNegativeParticle(point, vel, size, 179// rampUp, dur, inverted); 180 181 playImpactSound(damageResult, point, vel); 182 } 183 184 public void onFire(DamagingProjectileAPI projectile, WeaponAPI weapon, CombatEngineAPI engine) { 185 String prevKey = "cryo_prev_" + weapon.getShip().getId() + "_" + weapon.getSlot().getId(); 186 DamagingProjectileAPI prev = (DamagingProjectileAPI) engine.getCustomData().get(prevKey); 187 188 CryofluxTransducerEffect trail = createTrail(projectile, prev); 189 CombatEntityAPI e = engine.addLayeredRenderingPlugin(trail); 190 e.getLocation().set(projectile.getLocation()); 191 192 engine.getCustomData().put(prevKey, projectile); 193 194 if (trails == null) { 195 trails = new ArrayList<CryofluxTransducerEffect>(); 196 } 197 trails.add(0, trail); 198 } 199 200 protected CryofluxTransducerEffect createTrail(DamagingProjectileAPI projectile, DamagingProjectileAPI prev) { 201 return new CryofluxTransducerEffect(projectile, prev); 202 } 203 204 205 public static class ParticleData { 206 public SpriteAPI sprite; 207 public Vector2f offset = new Vector2f(); 208 public Vector2f vel = new Vector2f(); 209 public float scale = 1f; 210 public DamagingProjectileAPI proj; 211 public float scaleIncreaseRate = 1f; 212 public float turnDir = 1f; 213 public float angle = 1f; 214 public FaderUtil fader; 215 216 public ParticleData(DamagingProjectileAPI proj, CryofluxTransducerEffect effect) { 217 this.proj = proj; 218 sprite = Global.getSettings().getSprite(effect.getParticleSpriteCat(), effect.getParticleSpriteKey()); 219 //sprite = Global.getSettings().getSprite("misc", "dust_particles"); 220 float i = Misc.random.nextInt(4); 221 float j = Misc.random.nextInt(4); 222 sprite.setTexWidth(0.25f); 223 sprite.setTexHeight(0.25f); 224 sprite.setTexX(i * 0.25f); 225 sprite.setTexY(j * 0.25f); 226 sprite.setAdditiveBlend(); 227 228 angle = (float) Math.random() * 360f; 229 230 float maxDur = proj.getWeapon().getRange() / proj.getWeapon().getProjectileSpeed(); 231 scaleIncreaseRate = 2f / maxDur; 232 scaleIncreaseRate *= effect.getParticleScaleIncreaseRateMult(); 233 scale = effect.getParticleScale(); 234 //scale = 2f; 235// scale = 0.1f; 236// scaleIncreaseRate = 2.9f / maxDur; 237// scale = 0.1f; 238// scaleIncreaseRate = 2.5f / maxDur; 239// scale = 0.5f; 240 241 turnDir = Math.signum((float) Math.random() - 0.5f) * 60f * (float) Math.random(); 242 //turnDir = 0f; 243 244 float driftDir = (float) Math.random() * 360f; 245 vel = Misc.getUnitVectorAtDegreeAngle(driftDir); 246 vel.scale(proj.getProjectileSpec().getWidth() / maxDur * 0.33f); 247 248// offset.x += vel.x * 1f; 249// offset.y += vel.y * 1f; 250 fader = new FaderUtil(0f, 0.25f, 0.5f); 251 fader.fadeIn(); 252 } 253 254 public void advance(float amount) { 255 scale += scaleIncreaseRate * amount; 256 if (scale < 1f) { 257 scale += scaleIncreaseRate * amount * 1f; 258 } 259 260 offset.x += vel.x * amount; 261 offset.y += vel.y * amount; 262 263 angle += turnDir * amount; 264 265 fader.advance(amount); 266 } 267 } 268 269 protected List<ParticleData> particles = new ArrayList<ParticleData>(); 270 271 protected DamagingProjectileAPI proj; 272 protected DamagingProjectileAPI prev; 273 protected float baseFacing = 0f; 274 public CryofluxTransducerEffect(DamagingProjectileAPI proj, DamagingProjectileAPI prev) { 275 this.proj = proj; 276 this.prev = prev; 277 278 baseFacing = proj.getFacing(); 279 280 int num = getNumParticles(); 281 for (int i = 0; i < num; i++) { 282 particles.add(new ParticleData(proj, this)); 283 } 284 285 float length = proj.getProjectileSpec().getLength(); 286 float width = proj.getProjectileSpec().getWidth(); 287 288 float index = 0; 289 for (ParticleData p : particles) { 290 float f = index / (particles.size() - 1); 291 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(proj.getFacing() + 180f); 292 dir.scale(length * f); 293 Vector2f.add(p.offset, dir, p.offset); 294 295 p.offset = Misc.getPointWithinRadius(p.offset, width * 0.5f); 296 //p.scale = 0.25f + 0.75f * (1 - f); 297 298 index++; 299 } 300 } 301 302 public float getRenderRadius() { 303 return 300f; 304 } 305 306 307 protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.ABOVE_SHIPS_AND_MISSILES_LAYER); 308 @Override 309 public EnumSet<CombatEngineLayers> getActiveLayers() { 310 return layers; 311 } 312 313 314 public void init(CombatEntityAPI entity) { 315 super.init(entity); 316 } 317 318 public void advance(float amount) { 319 if (Global.getCombatEngine().isPaused()) return; 320 321 entity.getLocation().set(proj.getLocation()); 322 323 for (ParticleData p : particles) { 324 p.advance(amount); 325 } 326 } 327 328 329 public boolean isExpired() { 330 return proj.isExpired() || !Global.getCombatEngine().isEntityInPlay(proj); 331 } 332 333 public Color getParticleColor() { 334 Color color = proj.getProjectileSpec().getFringeColor(); 335 color = Misc.setAlpha(color, 50); 336 return color; 337 } 338 339 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 340 float x = entity.getLocation().x; 341 float y = entity.getLocation().y; 342 343 //Color color = new Color(100,150,255,50); 344 Color color = getParticleColor(); 345 float b = proj.getBrightness(); 346 b *= viewport.getAlphaMult(); 347 348 for (ParticleData p : particles) { 349 float size = proj.getProjectileSpec().getWidth() * 0.6f; 350 size *= p.scale; 351 352 float alphaMult = 1f; 353 Vector2f offset = p.offset; 354 float diff = Misc.getAngleDiff(baseFacing, proj.getFacing()); 355 if (Math.abs(diff) > 0.1f) { 356 offset = Misc.rotateAroundOrigin(offset, diff); 357 } 358 Vector2f loc = new Vector2f(x + offset.x, y + offset.y); 359 360 p.sprite.setAngle(p.angle); 361 p.sprite.setSize(size, size); 362 p.sprite.setAlphaMult(b * alphaMult * p.fader.getBrightness()); 363 p.sprite.setColor(color); 364 p.sprite.renderAtCenter(loc.x, loc.y); 365 } 366 } 367 368} 369 370 371 372