001package com.fs.starfarer.api.impl.combat.dweller; 002 003import java.util.ArrayList; 004import java.util.EnumSet; 005import java.util.LinkedHashMap; 006import java.util.List; 007 008import java.awt.Color; 009 010import org.lwjgl.opengl.GL11; 011import org.lwjgl.opengl.GL14; 012import org.lwjgl.util.vector.Vector2f; 013 014import com.fs.starfarer.api.Global; 015import com.fs.starfarer.api.combat.BaseCombatLayeredRenderingPlugin; 016import com.fs.starfarer.api.combat.CombatEngineAPI; 017import com.fs.starfarer.api.combat.CombatEngineLayers; 018import com.fs.starfarer.api.combat.CombatEntityAPI; 019import com.fs.starfarer.api.combat.MissileAPI; 020import com.fs.starfarer.api.combat.ShipAPI; 021import com.fs.starfarer.api.combat.ViewportAPI; 022import com.fs.starfarer.api.combat.WeaponAPI; 023import com.fs.starfarer.api.combat.listeners.HullDamageAboutToBeTakenListener; 024import com.fs.starfarer.api.graphics.SpriteAPI; 025import com.fs.starfarer.api.impl.combat.ShipExplosionFlareVisual; 026import com.fs.starfarer.api.impl.combat.ShipExplosionFlareVisual.ShipExplosionFlareParams; 027import com.fs.starfarer.api.util.Misc; 028import com.fs.starfarer.api.util.MutatingValueUtil; 029 030public class DwellerCombatPlugin extends BaseCombatLayeredRenderingPlugin implements HullDamageAboutToBeTakenListener { 031 032 public static Color STANDARD_PART_GLOW_COLOR = new Color(255, 0, 50, 255); 033 034 public static String WEAPON_ACTIVATED = "weapon_activated"; 035 public static String SHIELD_ACTIVATED = "shield_activated"; 036 public static String SYSTEM_ACTIVATED = "system_activated"; 037 public static String FLUX_ACTIVATED = "flux_activated"; 038 039 public static interface DCPPlugin { 040 public void advance(DwellerCombatPlugin plugin, float amount); 041 } 042 043 044 public static class WobblyPart extends BaseDwellerShipPart { 045 public WarpingSpriteRendererUtilV2 renderer; 046 public boolean negativeBlend = false; 047 public boolean additiveBlend = false; 048 public MutatingValueUtil spin; 049 public float angle = 0f; 050 051 public WobblyPart(String spriteKey, float scale, float warpMult, Vector2f offset, float facingOffset) { 052 this(spriteKey, scale, 5, 5, warpMult, offset, facingOffset); 053 } 054 public WobblyPart(String spriteKey, float scale, int verticesWide, int verticesTall, 055 float warpMult, Vector2f offset, float facingOffset) { 056 super(offset, facingOffset); 057 058 SpriteAPI sprite = Global.getSettings().getSprite("dweller", spriteKey); 059 060 float width = sprite.getWidth() * scale; 061 float height = sprite.getHeight() * scale; 062 float warpAmt = width * 0.04f * warpMult; 063 064 sprite.setSize(width, height); 065 sprite.setCenter(width/2f, height/2f); 066 renderer = new WarpingSpriteRendererUtilV2(sprite, verticesWide, verticesTall, warpAmt, warpAmt * 1.4f, 1f); 067 068 spin = new MutatingValueUtil(0, 0, 0); 069 } 070 071 public float getAngle() { 072 return angle; 073 } 074 public void setAngle(float angle) { 075 this.angle = angle; 076 } 077 public void setSpin(float min, float max, float rate) { 078 spin = new MutatingValueUtil(min, max, rate); 079 } 080 081 public MutatingValueUtil getSpin() { 082 return spin; 083 } 084 085 public void advance(float amount) { 086 super.advance(amount); 087 spin.advance(amount); 088 this.angle += spin.getValue() * amount; 089 renderer.advance(amount); 090 } 091 092 public void renderImpl(float x, float y, float alphaMult, float angle, CombatEngineLayers layer) { 093 if (layer == CombatEngineLayers.BELOW_INDICATORS_LAYER) { 094 if (negativeBlend) { 095 GL14.glBlendEquation(GL14.GL_FUNC_REVERSE_SUBTRACT); 096 renderer.getSprite().setBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); 097 } else { 098 if (additiveBlend) { 099 renderer.getSprite().setAdditiveBlend(); 100 } else { 101 renderer.getSprite().setNormalBlend(); 102 } 103 } 104 105 renderer.getSprite().setAlphaMult(alphaMult); 106 renderer.getSprite().setColor(color); 107 renderer.getSprite().setAngle(angle + facingOffset + this.angle); 108 renderer.renderAtCenter(x, y); 109 110 if (negativeBlend) { 111 GL14.glBlendEquation(GL14.GL_FUNC_ADD); 112 } 113 } 114 } 115 } 116 117 118 public static DwellerCombatPlugin getDwellerPluginFor(CombatEntityAPI entity) { 119 if (entity == null) return null; 120 return getShipMap().get(entity); 121 } 122 123 public static String KEY_SHIP_MAP = "DwellerCombatPlugin_shipMap_key"; 124 125 @SuppressWarnings("unchecked") 126 public static LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin> getShipMap() { 127 LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin> map = 128 (LinkedHashMap<CombatEntityAPI, DwellerCombatPlugin>) Global.getCombatEngine().getCustomData().get(KEY_SHIP_MAP); 129 if (map == null) { 130 map = new LinkedHashMap<>(); 131 Global.getCombatEngine().getCustomData().put(KEY_SHIP_MAP, map); 132 } 133 return map; 134 } 135 136// @SuppressWarnings("unchecked") 137// public static ListMap<DwellerCombatPlugin> getStringToDwellerPluginMap(String key) { 138// ListMap<DwellerCombatPlugin> map = 139// (ListMap<DwellerCombatPlugin>) Global.getCombatEngine().getCustomData().get(key); 140// if (map == null) { 141// map = new ListMap<>(); 142// Global.getCombatEngine().getCustomData().put(key, map); 143// } 144// return map; 145// } 146 147 protected CombatEntityAPI attachedTo; 148 protected float elapsed = 0f; 149 150 protected List<DwellerShipPart> parts = new ArrayList<>(); 151 152 protected boolean spawnedShipExplosionParticles = false; 153 protected DCPPlugin plugin = null; 154 155 public Object custom1; 156 public Object custom2; 157 public Object custom3; 158 159 160 public DwellerCombatPlugin(CombatEntityAPI attachedTo) { 161 CombatEntityAPI e = Global.getCombatEngine().addLayeredRenderingPlugin(this); 162 e.getLocation().set(attachedTo.getLocation()); 163 164 this.attachedTo = attachedTo; 165 166 if (attachedTo instanceof ShipAPI) { 167 ShipAPI ship = (ShipAPI) attachedTo; 168 ship.addListener(this); 169 } 170 171 getShipMap().put(attachedTo, this); 172 } 173 174 public void init(CombatEntityAPI entity) { 175 super.init(entity); 176 } 177 178 public float getRenderRadius() { 179 float extra = 300f; 180 return attachedTo.getCollisionRadius() + extra; 181 } 182 183 //protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_PHASED_SHIPS_LAYER); 184 //protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.ABOVE_PARTICLES); 185 protected EnumSet<CombatEngineLayers> layers = EnumSet.of(CombatEngineLayers.BELOW_INDICATORS_LAYER); 186 @Override 187 public EnumSet<CombatEngineLayers> getActiveLayers() { 188 return layers; 189 } 190 191 192 protected float sinceTest = 10f; 193 public void advance(float amount) { 194 //if (true) return; 195 196 197 if (Global.getCombatEngine().isPaused() || entity == null || isExpired()) return; 198 199 200 entity.getLocation().set(attachedTo.getLocation()); 201 202 elapsed += amount; 203 204// sinceTest += amount; 205// if (Keyboard.isKeyDown(Keyboard.KEY_K) && sinceTest > 1f) { 206// spawnedShipExplosionParticles = false; 207// notifyAboutToTakeHullDamage(null, (ShipAPI) attachedTo, new Vector2f(), 1000000f); 208// sinceTest = 0f; 209// } 210 211// Vector2f aVel = attachedTo.getVelocity(); 212// float aSpeed = aVel.length(); 213// Vector2f facingDir = Misc.getUnitVectorAtDegreeAngle(attachedTo.getFacing()); 214// Vector2f aLoc = new Vector2f(attachedTo.getLocation()); 215 216 if (isExpired()) { 217 } 218 219 if (attachedTo instanceof ShipAPI) { 220 ShipAPI ship = (ShipAPI) attachedTo; 221 if (ship.getShield() != null) { 222 ship.setJitterShields(true); 223 ship.setCircularJitter(true); 224 Color color = new Color(255,0,50,255); 225 ship.setJitter(this, color, 1f, 3, 0f); 226 //ship.getShield().applyShieldEffects(null, null, 0f, 2f, 1f); 227 } 228 } 229 230 if (shouldDespawn()) { 231 for (DwellerShipPart part : parts) { 232 part.fadeOut(); 233 } 234 } else { 235 if (attachedTo instanceof ShipAPI) { 236 ShipAPI ship = (ShipAPI) attachedTo; 237 238 fadeOut(WEAPON_ACTIVATED, SHIELD_ACTIVATED, FLUX_ACTIVATED, SYSTEM_ACTIVATED); 239 240 boolean activeWeapons = false; 241 for (WeaponAPI w : ship.getAllWeapons()) { 242 if (w.isDecorative()) continue; 243 if (w.isFiring()) { 244 activeWeapons = true; 245 break; 246 } 247 } 248 249 if (activeWeapons) { 250 fadeIn(WEAPON_ACTIVATED); 251 } 252 253 if (ship.getShield() != null && ship.getShield().isOn()) { 254 fadeIn(SHIELD_ACTIVATED); 255 } 256 257 float systemLevel = 0f; 258 if (ship.getSystem() != null) systemLevel = ship.getSystem().getEffectLevel(); 259 if (systemLevel > 0) { 260 fadeIn(SYSTEM_ACTIVATED); 261 } 262 setBrightness(systemLevel, SYSTEM_ACTIVATED); 263 264 float fluxLevel = ship.getFluxLevel(); 265 if (fluxLevel > 0f) { 266 fadeIn(FLUX_ACTIVATED); 267 } 268 setBrightness(fluxLevel, FLUX_ACTIVATED); 269 } 270 } 271 272// float mult = 1f; 273// mult += ((ShipAPI) attachedTo).getSystem().getEffectLevel() * 20f; 274 //if (((ShipAPI) attachedTo).getSystem().isActive()) mult = 10f; 275 for (DwellerShipPart part : parts) { 276 part.advance(amount); 277 } 278 279 if (plugin != null) { 280 plugin.advance(this, amount); 281 } 282 } 283 284 285 public boolean shouldDespawn() { 286 //if (true) return false; 287 if (attachedTo instanceof ShipAPI) { 288 ShipAPI ship = (ShipAPI) attachedTo; 289 return !Global.getCombatEngine().isShipAlive(ship); 290 } 291 if (attachedTo instanceof MissileAPI) { 292 MissileAPI missile = (MissileAPI) attachedTo; 293 return !Global.getCombatEngine().isMissileAlive(missile); 294 } 295 return attachedTo.isExpired() || !Global.getCombatEngine().isEntityInPlay(attachedTo); 296 } 297 298 public boolean isExpired() { 299 boolean shouldDespawn = shouldDespawn(); 300 if (shouldDespawn) { 301 boolean allFaded = true; 302 for (DwellerShipPart part : parts) { 303 if (!part.getFader().isFadedOut() && part.getAlphaMult() > 0) { 304 allFaded = false; 305 break; 306 } 307 } 308 if (allFaded) { 309 getShipMap().remove(attachedTo); 310 return true; 311 } 312 } 313 return false; 314 } 315 316 public void render(CombatEngineLayers layer, ViewportAPI viewport) { 317 //if (true) return; 318 319 //Color color = Color.white; 320 float alphaMult = viewport.getAlphaMult(); 321 if (alphaMult <= 0f) return; 322 323 Vector2f aLoc = new Vector2f(attachedTo.getLocation()); 324 325 for (DwellerShipPart part : parts) { 326 part.render(aLoc.x, aLoc.y, alphaMult, attachedTo.getFacing() - 90f, layer); 327 } 328 } 329 330 331 public CombatEntityAPI getAttachedTo() { 332 return attachedTo; 333 } 334 335 public List<DwellerShipPart> getParts() { 336 return parts; 337 } 338 339 public DwellerShipPart getPart(String id) { 340 for (DwellerShipPart curr : parts) { 341 if (id.equals(curr.getId())) return curr; 342 } 343 return null; 344 } 345 346 public void fadeIn(String ... tags) { 347 for (DwellerShipPart part : getParts(tags)) { 348 part.fadeIn(); 349 } 350 } 351 public void fadeOut(String ... tags) { 352 for (DwellerShipPart part : getParts(tags)) { 353 part.fadeOut(); 354 } 355 } 356 public void setAlphaMult(float alphaMult, String ... tags) { 357 for (DwellerShipPart part : getParts(tags)) { 358 part.setAlphaMult(alphaMult); 359 } 360 } 361 public void setBrightness(float b, String ... tags) { 362 String key = ""; 363 for (String tag : tags) key += tag + "_"; 364 if (tags.length == 1) key = tags[0]; 365 366 for (DwellerShipPart part : getParts(tags)) { 367 part.getBrightness().shift(key, b, 0.5f, 0.5f, 1f); 368 } 369 } 370 371 public List<DwellerShipPart> getParts(String ... tags) { 372 List<DwellerShipPart> result = new ArrayList<>(); 373 374 OUTER: for (DwellerShipPart curr : parts) { 375 for (String tag : tags) { 376 if (curr.hasTag(tag)) { 377 result.add(curr); 378 continue OUTER; 379 } 380 } 381 } 382 383 return result; 384 } 385 386 387 @Override 388 public boolean notifyAboutToTakeHullDamage(Object param, ShipAPI ship, Vector2f point, float damageAmount) { 389 float hull = ship.getHitpoints(); 390 if (damageAmount >= hull && !spawnedShipExplosionParticles && ship.getExplosionScale() > 0f) { 391 int numSwirly = 11; 392 int numDark = 11; 393 //numDark = 0; 394 float size = ship.getCollisionRadius() * 0.25f; 395 if (size < 15) size = 15; 396 if (size > 50) size = 50; 397 //if (DwellerCombatStrategyAI.isMaw(ship)) { 398 float durMult = 1f; 399 float flashMult = 1f; 400 if (ship.isCapital()) { 401 size = 100; 402 durMult = 2f; 403 flashMult = 2f; 404 } 405// size = 50; 406// size = 15; 407 408 float baseSize = size; 409 410 CombatEngineAPI engine = Global.getCombatEngine(); 411 412 float rampUp = 0f; 413 414 Color color = DwellerShroud.SHROUD_COLOR; 415 416 for (int i = 0; i < numSwirly; i++) { 417 Vector2f loc = new Vector2f(ship.getLocation()); 418 //loc.x += 750f; 419 float scatterMult = 0.5f; 420 //loc = Misc.getPointWithinRadius(loc, baseSize * 1f * scatterMult); 421 loc = Misc.getPointWithinRadius(loc, size * 1f * scatterMult); 422 float s = size * 4f * (0.5f + (float) Math.random() * 0.5f); 423 424 float dur = 0.5f + (float) Math.random() * 0.5f; 425 dur *= durMult; 426 427 size *= 1.25f; 428 429 engine.addSwirlyNebulaParticle(loc, ship.getVelocity(), s, 3f, rampUp, 0f, dur, color, false); 430 } 431 432 size = baseSize; 433 for (int i = 0; i < numDark; i++) { 434 Vector2f loc = new Vector2f(ship.getLocation()); 435 //loc.x += 750f; 436 float scatterMult = 0.5f; 437 //loc = Misc.getPointWithinRadius(loc, baseSize * 1f * scatterMult); 438 loc = Misc.getPointWithinRadius(loc, size * 1f * scatterMult); 439 float s = size * 4f * (0.5f + (float) Math.random() * 0.5f); 440 441 float dur = 0.5f + (float) Math.random() * 0.5f; 442 dur *= durMult; 443 444 //size *= 1.1f; 445 size *= 1.25f; 446 //s *= 0.5f; 447 448 engine.addNegativeSwirlyNebulaParticle(loc, ship.getVelocity(), s, 3f, rampUp, 0f, dur, color); 449 } 450 451 452 Vector2f expVel = ship.getVelocity(); 453 Vector2f expLoc = ship.getShieldCenterEvenIfNoShield(); 454 expLoc = ship.getLocation(); 455 float explosionScale = 1f; 456 if (ship.isCapital()) { 457 explosionScale *= 1.7f; 458 } else if (ship.isCruiser()) { 459 explosionScale *= 1.5f; 460 } else if (ship.isDestroyer()) { 461 explosionScale *= 1.5f; 462 } 463 464 Color flashColor = new Color(255,50,100,255); 465 float b = 1f; 466 467 float glowSize = (float) Math.sqrt(ship.getCollisionRadius()) * 15f * 4f; 468 if (ship.isFighter()) glowSize *= 0.5f; 469 glowSize *= explosionScale; 470 glowSize *= flashMult; 471 472 ShipExplosionFlareParams sp = new ShipExplosionFlareParams(); 473 //sp.attachedTo = this; 474 float er = (float) Math.sqrt(ship.getCollisionRadius()) * 15f * 4.0f; 475 float mult = 475f/994f; 476 er *= mult; 477 sp.flareWidth = er * 4f * explosionScale; 478 sp.flareHeight = er * 1.6f * explosionScale; 479 sp.color = flashColor; 480 sp.fadeIn = 0.1f; 481 sp.fadeOut = 2f; 482 483 CombatEntityAPI e = engine.addLayeredRenderingPlugin(new ShipExplosionFlareVisual(sp)); 484 e.getLocation().set(expLoc); 485 e.getVelocity().set(expVel); 486 487 engine.addHitParticle(expLoc, expVel, glowSize * 1f, b, 1.5f * durMult, flashColor); 488 engine.addHitParticle(expLoc, expVel, glowSize * 0.5f, b, 1.5f * durMult, flashColor); 489// if (ship.isFrigate()) { // needs just a little extra oomph 490// engine.addHitParticle(expLoc, expVel, glowSize * 0.5f, b, 1.5f, Color.white); 491// } 492 493 spawnedShipExplosionParticles = true; 494 } 495 return false; 496 } 497 498 public DCPPlugin getPlugin() { 499 return plugin; 500 } 501 502 public void setPlugin(DCPPlugin plugin) { 503 this.plugin = plugin; 504 } 505 506} 507 508 509 510 511 512 513