001package com.fs.starfarer.api.impl.combat; 002 003import java.util.ArrayList; 004import java.util.EnumSet; 005import java.util.List; 006 007import java.awt.Color; 008 009import org.lwjgl.opengl.GL14; 010import org.lwjgl.util.vector.Vector2f; 011 012import com.fs.starfarer.api.Global; 013import com.fs.starfarer.api.combat.ArmorGridAPI; 014import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin; 015import com.fs.starfarer.api.combat.CombatEngineAPI; 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.DamagingProjectileAPI; 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.listeners.ApplyDamageResultAPI; 024import com.fs.starfarer.api.graphics.SpriteAPI; 025import com.fs.starfarer.api.util.FaderUtil; 026import com.fs.starfarer.api.util.IntervalUtil; 027import com.fs.starfarer.api.util.Misc; 028 029public class DisintegratorEffect extends BaseCombatLayeredRenderingPlugin implements OnHitEffectPlugin { 030 031 // each tick is on average .9 seconds 032 // ticks can't be longer than a second or floating damage numbers separate 033 //public static int NUM_TICKS = 22; 034 public static int NUM_TICKS = 11; 035 public static float TOTAL_DAMAGE = 1000; 036 037 public DisintegratorEffect() { 038 } 039 040 protected float getTotalDamage() { 041 return TOTAL_DAMAGE; 042 } 043 protected int getNumTicks() { 044 return NUM_TICKS; 045 } 046 protected boolean canDamageHull() { 047 return false; 048 } 049 050 public void onHit(DamagingProjectileAPI projectile, CombatEntityAPI target, Vector2f point, boolean shieldHit, ApplyDamageResultAPI damageResult, CombatEngineAPI engine) { 051 if (shieldHit) return; 052 if (projectile.isFading()) return; 053 if (!(target instanceof ShipAPI)) return; 054 055 Vector2f offset = Vector2f.sub(point, target.getLocation(), new Vector2f()); 056 offset = Misc.rotateAroundOrigin(offset, -target.getFacing()); 057 058 DisintegratorEffect effect = new DisintegratorEffect(projectile, (ShipAPI) target, offset); 059 CombatEntityAPI e = engine.addLayeredRenderingPlugin(effect); 060 e.getLocation().set(projectile.getLocation()); 061 } 062 063 public static class ParticleData { 064 public SpriteAPI sprite; 065 public Vector2f offset = new Vector2f(); 066 public Vector2f vel = new Vector2f(); 067 public float scale = 1f; 068 public float scaleIncreaseRate = 1f; 069 public float turnDir = 1f; 070 public float angle = 1f; 071 072 public float maxDur; 073 public FaderUtil fader; 074 public float elapsed = 0f; 075 public float baseSize; 076 077 public Color color = new Color(100,150,255,35); 078 079 public ParticleData(float baseSize, float maxDur, float endSizeMult) { 080 sprite = Global.getSettings().getSprite("misc", "nebula_particles"); 081 //sprite = Global.getSettings().getSprite("misc", "dust_particles"); 082 float i = Misc.random.nextInt(4); 083 float j = Misc.random.nextInt(4); 084 sprite.setTexWidth(0.25f); 085 sprite.setTexHeight(0.25f); 086 sprite.setTexX(i * 0.25f); 087 sprite.setTexY(j * 0.25f); 088 sprite.setAdditiveBlend(); 089 090 angle = (float) Math.random() * 360f; 091 092 this.maxDur = maxDur; 093 scaleIncreaseRate = endSizeMult / maxDur; 094 if (endSizeMult < 1f) { 095 scaleIncreaseRate = -1f * endSizeMult; 096 } 097 scale = 1f; 098 099 this.baseSize = baseSize; 100 turnDir = Math.signum((float) Math.random() - 0.5f) * 20f * (float) Math.random(); 101 //turnDir = 0f; 102 103 float driftDir = (float) Math.random() * 360f; 104 vel = Misc.getUnitVectorAtDegreeAngle(driftDir); 105 //vel.scale(proj.getProjectileSpec().getLength() / maxDur * (0f + (float) Math.random() * 3f)); 106 vel.scale(0.25f * baseSize / maxDur * (1f + (float) Math.random() * 1f)); 107 108 fader = new FaderUtil(0f, 0.5f, 0.5f); 109 fader.forceOut(); 110 fader.fadeIn(); 111 } 112 113 public void advance(float amount) { 114 scale += scaleIncreaseRate * amount; 115 116 offset.x += vel.x * amount; 117 offset.y += vel.y * amount; 118 119 angle += turnDir * amount; 120 121 elapsed += amount; 122 if (maxDur - elapsed <= fader.getDurationOut() + 0.1f) { 123 fader.fadeOut(); 124 } 125 fader.advance(amount); 126 } 127 } 128 129 protected List<ParticleData> particles = new ArrayList<ParticleData>(); 130 protected DamagingProjectileAPI proj; 131 protected ShipAPI target; 132 protected Vector2f offset; 133 protected int ticks = 0; 134 protected IntervalUtil interval; 135 protected FaderUtil fader = new FaderUtil(1f, 0.5f, 0.5f); 136 137 public DisintegratorEffect(DamagingProjectileAPI proj, ShipAPI target, Vector2f offset) { 138 this.proj = proj; 139 this.target = target; 140 this.offset = offset; 141 142 interval = new IntervalUtil(0.8f, 1f); 143 interval.forceIntervalElapsed(); 144 } 145 146 public float getRenderRadius() { 147 return 500f; 148 } 149 150 151 protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_INDICATORS_LAYER); 152 @Override 153 public EnumSet<CombatEngineLayers> getActiveLayers() { 154 return layers; 155 } 156 157 public void init(CombatEntityAPI entity) { 158 super.init(entity); 159 } 160 161 public void advance(float amount) { 162 if (Global.getCombatEngine().isPaused()) return; 163 164 Vector2f loc = new Vector2f(offset); 165 loc = Misc.rotateAroundOrigin(loc, target.getFacing()); 166 Vector2f.add(target.getLocation(), loc, loc); 167 entity.getLocation().set(loc); 168 169 List<ParticleData> remove = new ArrayList<ParticleData>(); 170 for (ParticleData p : particles) { 171 p.advance(amount); 172 if (p.elapsed >= p.maxDur) { 173 remove.add(p); 174 } 175 } 176 particles.removeAll(remove); 177 178 float volume = 1f; 179 if (ticks >= getNumTicks() || !target.isAlive() || !Global.getCombatEngine().isEntityInPlay(target)) { 180 fader.fadeOut(); 181 fader.advance(amount); 182 volume = fader.getBrightness(); 183 } 184 Global.getSoundPlayer().playLoop(getSoundLoopId(), target, 1f, volume, loc, target.getVelocity()); 185 186 187 interval.advance(amount); 188 if (interval.intervalElapsed() && ticks < getNumTicks()) { 189 dealDamage(); 190 ticks++; 191 } 192 } 193 194 protected String getSoundLoopId() { 195 return "disintegrator_loop"; 196 } 197 198 protected int getNumParticlesPerTick() { 199 return 3; 200 } 201 202 protected void addParticle() { 203 ParticleData p = new ParticleData(30f, 3f + (float) Math.random() * 2f, 2f); 204 particles.add(p); 205 p.offset = Misc.getPointWithinRadius(p.offset, 20f); 206 } 207 208 protected void damageDealt(Vector2f loc, float hullDamage, float armorDamage) { 209 210 } 211 212 protected void dealDamage() { 213 CombatEngineAPI engine = Global.getCombatEngine(); 214 215 int num = getNumParticlesPerTick(); 216 for (int i = 0; i < num; i++) { 217 addParticle(); 218 } 219 220 221 Vector2f point = new Vector2f(entity.getLocation()); 222 223 // maximum armor in a cell is 1/15th of the ship's stated armor rating 224 225 ArmorGridAPI grid = target.getArmorGrid(); 226 int[] cell = grid.getCellAtLocation(point); 227 if (cell == null) return; 228 229 int gridWidth = grid.getGrid().length; 230 int gridHeight = grid.getGrid()[0].length; 231 232 float damageTypeMult = getDamageTypeMult(proj.getSource(), target); 233 234 float damagePerTick = (float) getTotalDamage() / (float) getNumTicks(); 235 float damageDealt = 0f; 236 float hullDamage = 0f; 237 for (int i = -2; i <= 2; i++) { 238 for (int j = -2; j <= 2; j++) { 239 if ((i == 2 || i == -2) && (j == 2 || j == -2)) continue; // skip corners 240 241 int cx = cell[0] + i; 242 int cy = cell[1] + j; 243 244 if (cx < 0 || cx >= gridWidth || cy < 0 || cy >= gridHeight) continue; 245 246 float damMult = 1/30f; 247 if (i == 0 && j == 0) { 248 damMult = 1/15f; 249 } else if (i <= 1 && i >= -1 && j <= 1 && j >= -1) { // S hits 250 damMult = 1/15f; 251 } else { // T hits 252 damMult = 1/30f; 253 } 254 255 float armorInCell = grid.getArmorValue(cx, cy); 256 float damage = damagePerTick * damMult * damageTypeMult; 257 if (damage > armorInCell && canDamageHull()) { 258 hullDamage += damage - armorInCell; 259 } 260 damage = Math.min(damage, armorInCell); 261 if (damage <= 0) continue; 262 263 target.getArmorGrid().setArmorValue(cx, cy, Math.max(0, armorInCell - damage)); 264 damageDealt += damage; 265 } 266 } 267 268 if (damageDealt > 0) { 269 if (Misc.shouldShowDamageFloaty(proj.getSource(), target)) { 270 engine.addFloatingDamageText(point, damageDealt, 0f, Misc.FLOATY_ARMOR_DAMAGE_COLOR, target, proj.getSource()); 271 } 272 target.syncWithArmorGridState(); 273 } 274 275 if (hullDamage > 1f) { 276 float showHullDamage = Math.min(hullDamage, target.getHitpoints()); 277 if (showHullDamage >= 0) { 278 target.setHitpoints(target.getHitpoints() - hullDamage); 279 if (target.getHitpoints() <= 0f && !target.isHulk()) { 280 target.setSpawnDebris(false); 281 engine.applyDamage(target, point, 100f, DamageType.ENERGY, 0f, true, false, proj.getSource(), false); 282 } 283 if (Misc.shouldShowDamageFloaty(proj.getSource(), target)) { 284 Vector2f p2 = new Vector2f(point); 285 p2.y += 20f; 286 engine.addFloatingDamageText(p2, hullDamage, 0f, Misc.FLOATY_HULL_DAMAGE_COLOR, target, proj.getSource()); 287 } 288// String key = "wfewfewf"; 289// Float total = (Float) engine.getCustomData().get(key); 290// if (total == null) total = 0f; 291// total += hullDamage; 292// engine.getCustomData().put(key, total); 293// System.out.println("Total hull damage dealt: " + total); 294 } 295 } 296 297 damageDealt(point, hullDamage, damageDealt); 298 299 } 300 301 public boolean isExpired() { 302 return particles.isEmpty() && 303 (ticks >= getNumTicks() || !target.isAlive() || !Global.getCombatEngine().isEntityInPlay(target)); 304 } 305 306 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 307 float x = entity.getLocation().x; 308 float y = entity.getLocation().y; 309 310 //Color color = new Color(100,150,255,35); 311 float b = viewport.getAlphaMult(); 312 313 GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT); 314 315 for (ParticleData p : particles) { 316 //float size = proj.getProjectileSpec().getWidth() * 0.6f; 317 float size = p.baseSize * p.scale; 318 319 Vector2f loc = new Vector2f(x + p.offset.x, y + p.offset.y); 320 321 float alphaMult = 1f; 322 323 p.sprite.setAngle(p.angle); 324 p.sprite.setSize(size, size); 325 p.sprite.setAlphaMult(b * alphaMult * p.fader.getBrightness()); 326 p.sprite.setColor(p.color); 327 p.sprite.renderAtCenter(loc.x, loc.y); 328 } 329 330 GL14.glBlendEquation(GL14.GL_FUNC_ADD); 331 } 332 333 334 public static float getDamageTypeMult(ShipAPI source, ShipAPI target) { 335 if (source == null || target == null) return 1f; 336 337 float damageTypeMult = target.getMutableStats().getArmorDamageTakenMult().getModifiedValue(); 338 switch (target.getHullSize()) { 339 case CAPITAL_SHIP: 340 damageTypeMult *= source.getMutableStats().getDamageToCapital().getModifiedValue(); 341 break; 342 case CRUISER: 343 damageTypeMult *= source.getMutableStats().getDamageToCruisers().getModifiedValue(); 344 break; 345 case DESTROYER: 346 damageTypeMult *= source.getMutableStats().getDamageToDestroyers().getModifiedValue(); 347 break; 348 case FRIGATE: 349 damageTypeMult *= source.getMutableStats().getDamageToFrigates().getModifiedValue(); 350 break; 351 case FIGHTER: 352 damageTypeMult *= source.getMutableStats().getDamageToFighters().getModifiedValue(); 353 break; 354 } 355 return damageTypeMult; 356 } 357 358 public Vector2f getOffset() { 359 return offset; 360 } 361 362 public void setOffset(Vector2f offset) { 363 this.offset = offset; 364 } 365 366} 367 368 369 370