001package com.fs.starfarer.api.impl.campaign; 002 003import java.awt.Color; 004import java.util.ArrayList; 005import java.util.LinkedHashSet; 006import java.util.List; 007 008import org.lwjgl.util.vector.Vector2f; 009 010import com.fs.starfarer.api.Global; 011import com.fs.starfarer.api.campaign.CampaignEngineLayers; 012import com.fs.starfarer.api.campaign.CampaignFleetAPI; 013import com.fs.starfarer.api.campaign.LocationAPI; 014import com.fs.starfarer.api.campaign.SectorEntityToken; 015import com.fs.starfarer.api.combat.ViewportAPI; 016import com.fs.starfarer.api.fleet.FleetMemberAPI; 017import com.fs.starfarer.api.graphics.SpriteAPI; 018import com.fs.starfarer.api.impl.campaign.ids.Stats; 019import com.fs.starfarer.api.impl.campaign.terrain.HyperspaceTerrainPlugin; 020import com.fs.starfarer.api.impl.campaign.terrain.ShoveFleetScript; 021import com.fs.starfarer.api.util.Misc; 022import com.fs.starfarer.api.util.WeightedRandomPicker; 023 024public class ExplosionEntityPlugin extends BaseCustomEntityPlugin { 025 026 public static enum ExplosionFleetDamage { 027 NONE, 028 LOW, 029 MEDIUM, 030 HIGH, 031 EXTREME, 032 } 033 public static class ExplosionParams { 034 public Color color; 035 public ExplosionFleetDamage damage = ExplosionFleetDamage.NONE; 036 public float radius; 037 public float durationMult = 1f; 038 public Vector2f loc; 039 public LocationAPI where; 040 public ExplosionParams(Color color, LocationAPI where, Vector2f loc, float radius, float durationMult) { 041 this.color = color; 042 this.where = where; 043 this.loc = loc; 044 this.radius = radius; 045 this.durationMult = durationMult; 046 } 047 } 048 049 050 public static class ParticleData { 051 public Vector2f offset = new Vector2f(); 052 public Vector2f vel = new Vector2f(); 053 public float scale = 1f; 054 public float scaleDelta = 1f; 055 public float turnDir = 1f; 056 public float angle = 1f; 057 public float size; 058 059 public float maxDur; 060 public float elapsed; 061 public float swImpact = 1f; 062 063 public int i; 064 public int j; 065 066 public Color color; 067 068 public ParticleData(Color color, float size, float maxDur, float endScale) { 069 i = Misc.random.nextInt(4); 070 j = Misc.random.nextInt(4); 071 072 this.color = color; 073 this.size = size; 074 angle = (float) Math.random() * 360f; 075 076 this.maxDur = maxDur; 077 scaleDelta = (endScale - 1f) / maxDur; 078 scale = 1f; 079 080 turnDir = Math.signum((float) Math.random() - 0.5f) * 10f * (float) Math.random(); 081 082 //turnDir = 0f; 083 } 084 085 public void setVelocity(float direction, float minSpeed, float maxSpeed) { 086 vel = Misc.getUnitVectorAtDegreeAngle(direction); 087 vel.scale(minSpeed + (maxSpeed - minSpeed) * (float) Math.random()); 088 } 089 090 public void setOffset(float direction, float minDist, float maxDist) { 091 offset = Misc.getUnitVectorAtDegreeAngle(direction); 092 offset.scale(minDist + (maxDist - minDist) * (float) Math.random()); 093 } 094 095 public void advance(float amount) { 096 scale += scaleDelta * amount; 097 if (scale < 0) scale = 0f; 098 099 offset.x += vel.x * amount; 100 offset.y += vel.y * amount; 101 102 angle += turnDir * amount; 103 104 elapsed += amount; 105 } 106 107 public float getBrightness() { 108 float b = 1f - (elapsed / maxDur); 109 if (b < 0) b = 0; 110 if (b > 1) b = 1; 111 return b; 112 } 113 } 114 115 116 protected ExplosionParams params; 117 protected List<ParticleData> particles = new ArrayList<ParticleData>(); 118 119 transient protected SpriteAPI sprite; 120 121 protected float shockwaveRadius; 122 protected float shockwaveWidth; 123 protected float shockwaveSpeed; 124 protected float shockwaveDuration; 125 protected float shockwaveAccel; 126 127 protected float maxParticleSize; 128 129 130 public void init(SectorEntityToken entity, Object pluginParams) { 131 super.init(entity, pluginParams); 132 readResolve(); 133 134 params = (ExplosionParams) pluginParams; 135 136 137 if (params.where.isCurrentLocation()) { 138 Global.getSoundPlayer().playSound("gate_explosion", 1f, 1f, params.loc, Misc.ZERO); 139 } 140 141 float baseSize = params.radius * 0.08f; 142 maxParticleSize = baseSize * 2f; 143 144 float fullArea = (float) (Math.PI * params.radius * params.radius); 145 float particleArea = (float) (Math.PI * baseSize * baseSize); 146 147 int count = (int) Math.round(fullArea / particleArea * 1f); 148 149 float durMult = 2f; 150 durMult = params.durationMult; 151 152 //baseSize *= 0.5f; 153 for (int i = 0; i < count; i++) { 154 float size = baseSize * (1f + (float) Math.random()); 155 156 Color randomColor = new Color(Misc.random.nextInt(256), 157 Misc.random.nextInt(256), Misc.random.nextInt(256), params.color.getAlpha()); 158 Color adjustedColor = Misc.interpolateColor(params.color, randomColor, 0.2f); 159 adjustedColor = params.color; 160 ParticleData data = new ParticleData(adjustedColor, size, 161 (0.25f + (float) Math.random()) * 2f * durMult, 3f); 162 163 float r = (float) Math.random(); 164 float dist = params.radius * 0.2f * (0.1f + r * 0.9f); 165 float dir = (float) Math.random() * 360f; 166 data.setOffset(dir, dist, dist); 167 168 dir = Misc.getAngleInDegrees(data.offset); 169// data.setVelocity(dir, baseSize * 0.25f, baseSize * 0.5f); 170// data.vel.scale(1f / durMult); 171 172 data.swImpact = (float) Math.random(); 173 if (i > count / 2) data.swImpact = 1; 174 175 particles.add(data); 176 } 177 178 Vector2f loc = new Vector2f(params.loc); 179 loc.x -= params.radius * 0.01f; 180 loc.y += params.radius * 0.01f; 181 182 float b = 1f; 183 params.where.addHitParticle(loc, new Vector2f(), params.radius * 1f, b, 1f * durMult, params.color); 184 loc = new Vector2f(params.loc); 185 params.where.addHitParticle(loc, new Vector2f(), params.radius * 0.4f, 0.5f, 1f * durMult, Color.white); 186 187 shockwaveAccel = baseSize * 70f / durMult; 188 //shockwaveRadius = -1500f; 189 shockwaveRadius = 0f; 190 shockwaveRadius = -params.radius * 0.5f; 191 shockwaveSpeed = params.radius * 2f / durMult; 192 shockwaveDuration = params.radius * 2f / shockwaveSpeed; 193 shockwaveWidth = params.radius * 0.5f; 194 195// shockwaveAccel = baseSize * 1500f / durMult; 196// //shockwaveRadius = -1500f; 197// shockwaveRadius = 0f; 198// shockwaveSpeed = params.radius * 4f / durMult; 199// shockwaveDuration = params.radius * 2f / shockwaveSpeed; 200// shockwaveWidth = params.radius * 0.5f; 201 202// shockwaveAccel = baseSize * 10f / durMult; 203// //shockwaveRadius = -1500f; 204// shockwaveRadius = 0f; 205// shockwaveSpeed = params.radius * 0.2f / durMult; 206// shockwaveDuration = params.radius * 2f / shockwaveSpeed; 207// shockwaveWidth = params.radius * 0.4f; 208 } 209 210 Object readResolve() { 211 sprite = Global.getSettings().getSprite("misc", "nebula_particles"); 212 return this; 213 } 214 215 216 public void advance(float amount) { 217 for (ParticleData p : new ArrayList<ParticleData>(particles)) { 218 p.advance(amount); 219 if (p.elapsed >= p.maxDur) { 220 particles.remove(p); 221 } 222 } 223 if (particles.isEmpty()) { 224 entity.setExpired(true); 225 } 226 227 applyDamageToFleets(); 228 229 if (shockwaveDuration > 0) { 230 shockwaveRadius += shockwaveSpeed * amount; 231 //shockwaveSpeed -= amount * shockwaveSpeed * 5f; 232 if (shockwaveSpeed < 0) shockwaveSpeed = 0; 233 shockwaveDuration -= amount; 234 for (ParticleData p : particles) { 235 float dist = p.offset.length(); 236 237 float impact = 0f; 238 if (dist < shockwaveRadius && dist > shockwaveRadius - shockwaveWidth) { 239 impact = 1f - (shockwaveRadius - dist) / shockwaveWidth; 240 impact = -impact; 241 } else if (dist > shockwaveRadius && dist < shockwaveRadius + shockwaveWidth) { 242 impact = 1f - (dist - shockwaveRadius) / shockwaveWidth; 243 } 244 245 float speed = p.vel.length(); 246 float dot = Vector2f.dot(p.offset, p.vel); 247 float threshold = shockwaveSpeed * 0.5f; 248 if (speed > threshold) {// && dot > 0) { 249 impact *= threshold / speed; 250 } 251 if (dot < 0) { 252 impact *= 0.2f; 253 } 254 //impact *= 0.1f + 0.9f * (1f - p.size / maxParticleSize); 255 impact *= p.swImpact; 256 257 Vector2f accel = Misc.getUnitVectorAtDegreeAngle(Misc.getAngleInDegrees(p.offset)); 258 accel.scale(impact * shockwaveAccel); 259 p.vel.x += accel.x * amount; 260 p.vel.y += accel.y * amount; 261 } 262 } 263 264 } 265 266 267 public float getRenderRange() { 268 float extra = 2000f; 269 if (params != null) extra = params.radius * 3; 270 return entity.getRadius() + extra; 271 } 272 273 public void render(CampaignEngineLayers layer, ViewportAPI viewport) { 274 float alphaMult = viewport.getAlphaMult(); 275 alphaMult *= entity.getSensorFaderBrightness(); 276 alphaMult *= entity.getSensorContactFaderBrightness(); 277 if (alphaMult <= 0) return; 278 279 float x = entity.getLocation().x; 280 float y = entity.getLocation().y; 281 282 //Color color = params.color; 283 //color = Misc.setAlpha(color, 30); 284 float b = alphaMult; 285 286 sprite.setTexWidth(0.25f); 287 sprite.setTexHeight(0.25f); 288 sprite.setAdditiveBlend(); 289 290 for (ParticleData p : particles) { 291 float size = p.size; 292 size *= p.scale; 293 294 Vector2f loc = new Vector2f(x + p.offset.x, y + p.offset.y); 295 296 float a = 1f; 297 a = 0.33f; 298 299 sprite.setTexX(p.i * 0.25f); 300 sprite.setTexY(p.j * 0.25f); 301 302 sprite.setAngle(p.angle); 303 sprite.setSize(size, size); 304 sprite.setAlphaMult(b * a * p.getBrightness()); 305 sprite.setColor(p.color); 306 sprite.renderAtCenter(loc.x, loc.y); 307 } 308 } 309 310 protected LinkedHashSet<String> damagedAlready = new LinkedHashSet<String>(); 311 public void applyDamageToFleets() { 312 if (params.damage == null || params.damage == ExplosionFleetDamage.NONE) { 313 return; 314 } 315 316 float shockwaveDist = 0f; 317 for (ParticleData p : particles) { 318 shockwaveDist = Math.max(shockwaveDist, p.offset.length()); 319 } 320 321 for (CampaignFleetAPI fleet : entity.getContainingLocation().getFleets()) { 322 String id = fleet.getId(); 323 if (damagedAlready.contains(id)) continue; 324 float dist = Misc.getDistance(fleet, entity); 325 if (dist < shockwaveDist) { 326 float damageMult = 1f - (dist / params.radius); 327 if (damageMult > 1f) damageMult = 1f; 328 if (damageMult < 0.1f) damageMult = 0.1f; 329 if (dist < entity.getRadius() + params.radius * 0.1f) damageMult = 1f; 330 331 damagedAlready.add(id); 332 applyDamageToFleet(fleet, damageMult); 333 } 334 } 335 336 } 337 338 public void applyDamageToFleet(CampaignFleetAPI fleet, float damageMult) { 339 340 List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy(); 341 if (members.isEmpty()) return; 342 343 float totalValue = 0; 344 for (FleetMemberAPI member : members) { 345 totalValue += member.getStats().getSuppliesToRecover().getModifiedValue(); 346 } 347 if (totalValue <= 0) return; 348 349 350 float damageFraction = 0f; 351 switch (params.damage) { 352 case NONE: 353 return; 354 case LOW: 355 damageFraction = 0.1f; 356 break; 357 case MEDIUM: 358 damageFraction = 0.3f; 359 break; 360 case HIGH: 361 damageFraction = 0.6f; 362 break; 363 case EXTREME: 364 damageFraction = 0.9f; 365 break; 366 } 367 368 damageFraction *= damageMult; 369 370 float shoveDir = Misc.getAngleInDegrees(entity.getLocation(), fleet.getLocation()); 371 fleet.addScript(new ShoveFleetScript(fleet, shoveDir, damageFraction)); 372 373 if (fleet.isInCurrentLocation()) { 374 float dist = Misc.getDistance(fleet, Global.getSector().getPlayerFleet()); 375 if (dist < HyperspaceTerrainPlugin.STORM_STRIKE_SOUND_RANGE) { 376 float volumeMult = 0.5f + 0.5f * damageFraction; 377 Global.getSoundPlayer().playSound("gate_explosion_fleet_impact", 1f, volumeMult, fleet.getLocation(), Misc.ZERO); 378 } 379 } 380 381 //float strikeValue = totalValue * damageFraction * (0.5f + (float) Math.random() * 0.5f); 382 383 WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>(); 384 for (FleetMemberAPI member : members) { 385 float w = 1f; 386 if (member.isFrigate()) w *= 0.1f; 387 if (member.isDestroyer()) w *= 0.2f; 388 if (member.isCruiser()) w *= 0.5f; 389 picker.add(member, w); 390 } 391 392 int numStrikes = picker.getItems().size(); 393 394 for (int i = 0; i < numStrikes; i++) { 395 FleetMemberAPI member = picker.pick(); 396 if (member == null) return; 397 398 float crPerDep = member.getDeployCost(); 399 //if (crPerDep <= 0) continue; 400 float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue(); 401 if (suppliesPerDep <= 0 || crPerDep <= 0) return; 402 float suppliesPer100CR = suppliesPerDep * 1f / Math.max(0.01f, crPerDep); 403 404 // half flat damage, half scaled based on ship supply cost cost 405 float strikeSupplies = (250f + suppliesPer100CR) * 0.5f * damageFraction; 406 //strikeSupplies = suppliesPerDep * 0.5f * damageFraction; 407 408 float strikeDamage = strikeSupplies / suppliesPer100CR * (0.75f + (float) Math.random() * 0.5f); 409 410 //float strikeDamage = damageFraction * (0.75f + (float) Math.random() * 0.5f); 411 412 float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT); 413 strikeDamage *= resistance; 414 415 if (strikeDamage > HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE) { 416 strikeDamage = HyperspaceTerrainPlugin.STORM_MAX_STRIKE_DAMAGE; 417 } 418 419 if (strikeDamage > 0) { 420 float currCR = member.getRepairTracker().getBaseCR(); 421 float crDamage = Math.min(currCR, strikeDamage); 422 423 if (crDamage > 0) { 424 member.getRepairTracker().applyCREvent(-crDamage, "explosion_" + entity.getId(), 425 "Damaged by explosion"); 426 } 427 428 float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating()); 429 //hitStrength *= strikeDamage / crPerDep; 430 int numHits = (int) (strikeDamage / 0.1f); 431 if (numHits < 1) numHits = 1; 432 for (int j = 0; j < numHits; j++) { 433 member.getStatus().applyDamage(hitStrength); 434 } 435 //member.getStatus().applyHullFractionDamage(1f); 436 if (member.getStatus().getHullFraction() < 0.01f) { 437 member.getStatus().setHullFraction(0.01f); 438 picker.remove(member); 439 } else { 440 float w = picker.getWeight(member); 441 picker.setWeight(picker.getItems().indexOf(member), w * 0.5f); 442 } 443 } 444 //picker.remove(member); 445 } 446 447 if (fleet.isPlayerFleet()) { 448 Global.getSector().getCampaignUI().addMessage( 449 "Your fleet suffers damage from being caught in an explosion", Misc.getNegativeHighlightColor()); 450 } 451 } 452 453} 454 455 456 457 458 459 460 461 462