001package com.fs.starfarer.api.impl.combat.dweller; 002 003import java.util.Iterator; 004 005import java.awt.Color; 006 007import org.lwjgl.util.vector.Vector2f; 008 009import com.fs.starfarer.api.Global; 010import com.fs.starfarer.api.campaign.CargoAPI.CargoItemType; 011import com.fs.starfarer.api.campaign.CargoStackAPI; 012import com.fs.starfarer.api.campaign.SpecialItemData; 013import com.fs.starfarer.api.combat.CollisionClass; 014import com.fs.starfarer.api.combat.CombatEngineAPI; 015import com.fs.starfarer.api.combat.CombatEntityAPI; 016import com.fs.starfarer.api.combat.DamageType; 017import com.fs.starfarer.api.combat.DamagingProjectileAPI; 018import com.fs.starfarer.api.combat.EmpArcEntityAPI; 019import com.fs.starfarer.api.combat.EmpArcEntityAPI.EmpArcParams; 020import com.fs.starfarer.api.combat.MissileAPI; 021import com.fs.starfarer.api.combat.ShipAPI; 022import com.fs.starfarer.api.combat.ShipAPI.HullSize; 023import com.fs.starfarer.api.impl.campaign.ids.Items; 024import com.fs.starfarer.api.impl.combat.NegativeExplosionVisual.NEParams; 025import com.fs.starfarer.api.impl.combat.RiftCascadeMineExplosion; 026import com.fs.starfarer.api.impl.combat.dweller.DwellerShroud.DwellerShroudParams; 027import com.fs.starfarer.api.loading.DamagingExplosionSpec; 028import com.fs.starfarer.api.ui.Alignment; 029import com.fs.starfarer.api.ui.TooltipMakerAPI; 030import com.fs.starfarer.api.util.Misc; 031import com.fs.starfarer.api.util.WeightedRandomPicker; 032 033public class ShroudedLensHullmod extends HumanShipShroudedHullmod { 034 035 public static float MAX_RANGE = 400f; 036 public static float RADIUS = 50f; 037 038 public static float MIN_REFIRE_DELAY = 0.9f; 039 public static float MAX_REFIRE_DELAY = 1.1f; 040 041 public static float FLUX_PER_DAMAGE = 1f; 042 043 public static float DAMAGE = 75f; 044 public static float MIN_ROF_MULT = 1f; 045 public static float MAX_ROF_MULT = 4f; 046 047 public String getDescriptionParam(int index, HullSize hullSize) { 048 return null; 049 } 050 051 @Override 052 public CargoStackAPI getRequiredItem() { 053 return Global.getSettings().createCargoStack(CargoItemType.SPECIAL, 054 new SpecialItemData(Items.SHROUDED_LENS, null), null); 055 } 056 057 public static String DATA_KEY = "core_ShroudedLensHullmod_data_key"; 058 059 public static class ShroudedLensHullmodData { 060 //IntervalUtil interval = new IntervalUtil(0.75f, 1.25f); 061 float untilAttack = 0f; 062 float sinceAttack = 1000f; 063 } 064 065 public static ShroudedLensHullmodData getData(ShipAPI ship) { 066 CombatEngineAPI engine = Global.getCombatEngine(); 067 String key = DATA_KEY + "_" + ship.getId(); 068 ShroudedLensHullmodData data = (ShroudedLensHullmodData) engine.getCustomData().get(key); 069 if (data == null) { 070 data = new ShroudedLensHullmodData(); 071 engine.getCustomData().put(key, data); 072 } 073 return data; 074 } 075 076 @Override 077 public void advanceInCombat(ShipAPI ship, float amount) { 078 super.advanceInCombat(ship, amount); 079 080 if (!ship.isAlive()) return; 081 if (amount <= 0f) return; 082 083 ShroudedLensHullmodData data = getData(ship); 084 data.untilAttack -= amount * getRoF(ship.getHullSize()); 085 if (data.untilAttack <= 0f) { 086 CombatEntityAPI target = findTarget(ship); 087 if (target != null) { 088 spawnExplosion(ship, target); 089 } 090 data.untilAttack = MIN_REFIRE_DELAY + (float) Math.random() * (MAX_REFIRE_DELAY - MIN_REFIRE_DELAY); 091 } 092 } 093 094 public static float getPowerMult(HullSize size) { 095 switch (size) { 096 case CAPITAL_SHIP: return 1f; 097 case CRUISER: return 0.6666666667f; 098 case DESTROYER: return 0.3333333333f; 099 case FIGHTER: 100 case FRIGATE: 101 return 0f; 102 } 103 return 1f; 104 } 105 106 public static float getRoF(HullSize size) { 107 float mult = getPowerMult(size); 108 return MIN_ROF_MULT + (MAX_ROF_MULT - MIN_ROF_MULT) * mult; 109 } 110 public static float getFluxCost(HullSize size) { 111 return DAMAGE * FLUX_PER_DAMAGE; 112 } 113 public static float getDamage(HullSize size) { 114 return DAMAGE; 115 } 116 117 public void spawnExplosion(ShipAPI ship, CombatEntityAPI target) { 118 CombatEngineAPI engine = Global.getCombatEngine(); 119 120 121 float angle = Misc.getAngleInDegrees(target.getLocation(), ship.getLocation()); 122 angle += 45f - 90f * (float) Math.random(); 123 Vector2f from = Misc.getUnitVectorAtDegreeAngle(angle); 124 from.scale(10000f); 125 126 float targetRadius = Misc.getTargetingRadius(from, target, false); 127 Vector2f point = Misc.getUnitVector(target.getLocation(), from); 128 point.scale(targetRadius * (0.8f + (float) Math.random() * 0.4f)); 129 Vector2f.add(target.getLocation(), point, point); 130 131 float dist = Misc.getDistance(from, point); 132 133 //float mult = getPowerMult(ship.getHullSize()); 134 float damage = getDamage(ship.getHullSize()); 135 136 if (FLUX_PER_DAMAGE > 0f) { 137 float fluxCost = getFluxCost(ship.getHullSize()); 138 //if (!ship.getFluxTracker().increaseFlux(fluxCost, false)) { 139 if (!deductFlux(ship, fluxCost)) { 140 return; 141 } 142 } 143 144 DwellerShroud shroud = DwellerShroud.getShroudFor(ship); 145 if (shroud != null) { 146 angle = Misc.getAngleInDegrees(ship.getLocation(), point); 147 from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random()); 148 from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset * shroud.getShroudParams().overloadArcOffsetMult); 149 Vector2f.add(ship.getLocation(), from, from); 150 } 151 152 Color color = RiftLightningEffect.RIFT_LIGHTNING_COLOR; 153 154 if (shroud != null) { 155 DwellerShroudParams shroudParams = shroud.getShroudParams(); 156 EmpArcParams params = new EmpArcParams(); 157 params.segmentLengthMult = 4f; 158 params.glowSizeMult = 4f; 159 params.flickerRateMult = 0.5f + (float) Math.random() * 0.5f; 160 params.flickerRateMult *= 1.5f; 161 162 //Color fringe = shroudParams.overloadArcFringeColor; 163 Color fringe = color; 164 Color core = Color.white; 165 166 float thickness = shroudParams.overloadArcThickness; 167 168 //Vector2f to = Misc.getPointAtRadius(from, 1f); 169 170 angle = Misc.getAngleInDegrees(from, ship.getLocation()); 171 angle = angle + 90f * ((float) Math.random() - 0.5f); 172 Vector2f dir = Misc.getUnitVectorAtDegreeAngle(angle); 173 dist = shroudParams.maxOffset * shroud.getShroudParams().overloadArcOffsetMult; 174 dist = dist * 0.5f + dist * 0.5f * (float) Math.random(); 175 //dist *= 1.5f; 176 dist *= 0.5f; 177 dir.scale(dist); 178 Vector2f to = Vector2f.add(from, dir, new Vector2f()); 179 180 EmpArcEntityAPI arc = (EmpArcEntityAPI)engine.spawnEmpArcVisual( 181 from, ship, to, ship, thickness, fringe, core, params); 182 183 arc.setCoreWidthOverride(shroudParams.overloadArcCoreThickness); 184 arc.setSingleFlickerMode(false); 185 //arc.setRenderGlowAtStart(false); 186 } 187 188 //float explosionRadius = 40f + mult * 40f; 189 float explosionRadius = RADIUS; 190 191 DamagingExplosionSpec spec = new DamagingExplosionSpec( 192 0.1f, // duration 193 explosionRadius, // radius 194 explosionRadius * 0.5f, // coreRadius 195 damage, // maxDamage 196 damage, // / 2f, // minDamage - no damage dropoff with range 197 CollisionClass.PROJECTILE_NO_FF, // collisionClass 198 CollisionClass.PROJECTILE_FIGHTER, // collisionClassByFighter - using to flag it as from this effect 199 3f, // particleSizeMin 200 3f, // particleSizeRange 201 0.5f, // particleDuration 202 0, // particleCount 203 new Color(255,255,255,0), // particleColor 204 new Color(255,100,100,0) // explosionColor 205 ); 206 207 spec.setDamageType(DamageType.ENERGY); 208 spec.setUseDetailedExplosion(false); 209 spec.setSoundSetId("abyssal_glare_explosion"); 210 //spec.setSoundVolume(0.5f + 0.5f * mult); 211 spec.setSoundVolume(0.33f); 212 213 DamagingProjectileAPI explosion = engine.spawnDamagingExplosion(spec, ship, point); 214 215 //explosion.addDamagedAlready(target); 216 //color = new Color(255,75,75,255); 217 218 float baseSize = 7f; 219 220 NEParams p = RiftCascadeMineExplosion.createStandardRiftParams( 221 color, baseSize); 222 //p.hitGlowSizeMult = 0.5f; 223 p.noiseMult = 6f; 224 p.thickness = 25f; 225 p.fadeOut = 0.5f; 226 p.spawnHitGlowAt = 1f; 227 p.additiveBlend = true; 228 p.blackColor = Color.white; 229 p.underglow = null; 230 p.withNegativeParticles = false; 231 p.withHitGlow = false; 232 p.fadeIn = 0f; 233 //p.numRiftsToSpawn = 1; 234 235 RiftCascadeMineExplosion.spawnStandardRift(explosion, p); 236 237 238 // the "beam" 239 240 float thickness = 30f; 241 //Color color = weapon.getSpec().getGlowColor(); 242 Color coreColor = Color.white; 243 coreColor = Misc.zeroColor; 244 coreColor = color; 245 246 color = new Color(255,0,30,255); 247 coreColor = new Color(255,10,255,255); 248 249 color = DwellerShroud.SHROUD_GLOW_COLOR; 250 coreColor = color; 251 //coreColor = Color.white; 252 253 float coreWidthMult = 1f; 254 255 256// from = ship.getLocation(); 257// if (shroud != null) { 258// angle = Misc.getAngleInDegrees(ship.getLocation(), target.getLocation()); 259// from = Misc.getUnitVectorAtDegreeAngle(angle + 90f - 180f * (float) Math.random()); 260// from.scale((0.5f + (float) Math.random() * 0.25f) * shroud.getShroudParams().maxOffset); 261// Vector2f.add(ship.getLocation(), from, from); 262// } 263 264 EmpArcParams params = new EmpArcParams(); 265 //params.segmentLengthMult = 10000f; 266 params.segmentLengthMult = 4f; 267 268// params.maxZigZagMult = 0f; 269// params.zigZagReductionFactor = 1f; 270 271 params.maxZigZagMult = 0.25f; 272 //params.maxZigZagMult = 0f; 273 params.zigZagReductionFactor = 1f; 274 275 //params.glowColorOverride = new Color(255,10,155,255); 276 277 //params.zigZagReductionFactor = 0.25f; 278 //params.maxZigZagMult = 0f; 279 //params.flickerRateMult = 0.75f; 280// params.flickerRateMult = 1f; 281// params.flickerRateMult = 0.75f; 282 params.flickerRateMult = 0.75f + 0.25f * (float) Math.random(); 283 284 params.fadeOutDist = 150f; 285 params.minFadeOutMult = 5f; 286 287 params.glowSizeMult = 0.5f; 288 289 //params.glowAlphaMult = 0.5f; 290 //params.flamesOutMissiles = false; 291 292// params.movementDurOverride = 0.1f; 293// params.flickerRateMult = 0.5f; 294// params.glowSizeMult = 1f; 295// params.brightSpotFadeFraction = 0.1f; 296// params.brightSpotFullFraction = 0.9f; 297 298// params.maxZigZagMult = 1f; 299// params.zigZagReductionFactor = 0f; 300// params.flickerRateMult = 0.25f; 301 302 Vector2f to = point; 303 EmpArcEntityAPI arc = engine.spawnEmpArcVisual(from, ship, to, explosion, thickness, color, coreColor, params); 304 arc.setCoreWidthOverride(thickness * coreWidthMult); 305 arc.setSingleFlickerMode(); 306 arc.setRenderGlowAtStart(false); 307 if (shroud != null) { 308 arc.setFadedOutAtStart(true); 309 } 310 arc.setWarping(0.2f); 311 312 } 313 314 @Override 315 public boolean shouldAddDescriptionToTooltip(HullSize hullSize, ShipAPI ship, boolean isForModSpec) { 316 return false; 317 } 318 319 @Override 320 public void addPostDescriptionSection(TooltipMakerAPI tooltip, HullSize hullSize, final ShipAPI ship, float width, boolean isForModSpec) { 321 float pad = 3f; 322 float opad = 10f; 323 Color h = Misc.getHighlightColor(); 324 Color bad = Misc.getNegativeHighlightColor(); 325 326 327 tooltip.addPara("The \"lens\" (in the loosest sense of the word) focuses on nearby objects, " 328 + "seemingly at random. Deals %s damage and generates %s flux.", opad, h, 329 "" + (int) getDamage(hullSize) + " Energy", "" + (int) getFluxCost(hullSize)); 330 331 tooltip.addPara("The rate of fire depends on the size of the ship the " 332 + "hullmod is installed on. Can not be turned off during combat operations, and continues to " 333 + "function even if the ship is venting flux or overloaded.", opad); 334 335 //tooltip.addSectionHeading("Reload capacity", Alignment.MID, opad); 336 337 if (isForModSpec || (ship == null && !Global.CODEX_TOOLTIP_MODE)) return; 338 339 tooltip.setBgAlpha(0.9f); 340 341 HullSize [] sizes = new HullSize[] {HullSize.FRIGATE, HullSize.DESTROYER, HullSize.CRUISER, HullSize.CAPITAL_SHIP}; 342 343 float rofW = 130f; 344 float fluxW = 130f; 345 float sizeW = width - rofW - fluxW - 10f; 346 tooltip.beginTable(Misc.getBasePlayerColor(), Misc.getDarkPlayerColor(), Misc.getBrightPlayerColor(), 347 20f, true, true, 348 new Object [] {"Ship size", sizeW, "Attacks / sec", rofW, "Flux / sec", fluxW}); 349 350 for (HullSize size : sizes) { 351 float rof = getRoF(size); 352 float flux = getFluxCost(size) * rof; 353 Color c = Misc.getGrayColor(); 354 if (size == hullSize || Global.CODEX_TOOLTIP_MODE) { 355 c = Misc.getHighlightColor(); 356 } 357 tooltip.addRow(Alignment.MID, c, Misc.getHullSizeStr(size), 358 Alignment.MID, c, "" + (int) Misc.getRoundedValueFloat(rof) + "", 359 Alignment.MID, c, "" + (int) Misc.getRoundedValueFloat(flux) + ""); 360 } 361 tooltip.addTable("", 0, opad); 362 363 tooltip.addSpacer(5f); 364 365 addCrewCasualties(tooltip, opad); 366 367// if (Global.CODEX_TOOLTIP_MODE) { 368// return; 369// } 370 } 371 372 373// @Override 374// public String getSModDescriptionParam(int index, HullSize hullSize, ShipAPI ship) { 375// if (index == 0) return "" + (int) Math.round(SMOD_CR_PENALTY * 100f) + "%"; 376// if (index == 1) return "" + (int) Math.round(SMOD_MAINTENANCE_PENALTY) + "%"; 377// return null; 378// } 379// 380// @Override 381// public boolean isSModEffectAPenalty() { 382// return true; 383// } 384 385 386 public CombatEntityAPI findTarget(ShipAPI ship) { 387 float range = MAX_RANGE; 388 Vector2f from = ship.getLocation(); 389 390 Iterator<Object> iter = Global.getCombatEngine().getAllObjectGrid().getCheckIterator(from, 391 range * 2f, range * 2f); 392 int owner = ship.getOwner(); 393 CombatEntityAPI best = null; 394 float minScore = Float.MAX_VALUE; 395 396 //boolean ignoreFlares = ship != null && ship.getMutableStats().getDynamic().getValue(Stats.PD_IGNORES_FLARES, 0) >= 1; 397 //ignoreFlares |= weapon.hasAIHint(AIHints.IGNORES_FLARES); 398 boolean ignoreFlares = false; // doesn't care one way or another 399 400 WeightedRandomPicker<CombatEntityAPI> picker = new WeightedRandomPicker<>(); 401 402 while (iter.hasNext()) { 403 Object o = iter.next(); 404 if (!(o instanceof MissileAPI) && 405 //!(o instanceof CombatAsteroidAPI) && 406 !(o instanceof ShipAPI)) continue; 407 CombatEntityAPI other = (CombatEntityAPI) o; 408 if (other.getOwner() == owner) continue; 409 410 if (other instanceof ShipAPI) { 411 ShipAPI otherShip = (ShipAPI) other; 412 //if (otherShip.isHulk()) continue; 413 //if (!otherShip.isAlive()) continue; 414 if (otherShip.isPhased()) continue; 415 if (!otherShip.isTargetable()) continue; 416 } 417 418 if (other.getCollisionClass() == CollisionClass.NONE) continue; 419 420 if (ignoreFlares && other instanceof MissileAPI) { 421 MissileAPI missile = (MissileAPI) other; 422 if (missile.isFlare()) continue; 423 } 424 425 float targetRadius = Misc.getTargetingRadius(from, other, false); 426 float shipRadius = Misc.getTargetingRadius(other.getLocation(), ship, false); 427 float dist = Misc.getDistance(from, other.getLocation()) - targetRadius - shipRadius; 428 if (dist > range) continue; 429 430 float score = dist; 431 432 if (score < minScore) { 433 minScore = score; 434 best = other; 435 } 436 437 picker.add(other, 100f / Math.max(100f, score)); 438 } 439 440 //return best; 441 return picker.pick(); 442 } 443} 444 445 446 447 448 449 450 451 452 453 454 455 456 457