001package com.fs.starfarer.api.impl.campaign.terrain; 002 003import java.awt.Color; 004import java.util.EnumSet; 005 006import org.lwjgl.util.vector.Vector2f; 007 008import com.fs.starfarer.api.Global; 009import com.fs.starfarer.api.campaign.CampaignEngineLayers; 010import com.fs.starfarer.api.campaign.CampaignFleetAPI; 011import com.fs.starfarer.api.campaign.PlanetAPI; 012import com.fs.starfarer.api.campaign.SectorEntityToken; 013import com.fs.starfarer.api.campaign.TerrainAIFlags; 014import com.fs.starfarer.api.combat.ViewportAPI; 015import com.fs.starfarer.api.fleet.FleetMemberAPI; 016import com.fs.starfarer.api.fleet.FleetMemberViewAPI; 017import com.fs.starfarer.api.graphics.SpriteAPI; 018import com.fs.starfarer.api.impl.campaign.ids.Stats; 019import com.fs.starfarer.api.impl.campaign.terrain.PulsarRenderer.PulsarRendererDelegate; 020import com.fs.starfarer.api.impl.campaign.terrain.StarCoronaTerrainPlugin.CoronaParams; 021import com.fs.starfarer.api.loading.Description.Type; 022import com.fs.starfarer.api.ui.Alignment; 023import com.fs.starfarer.api.ui.TooltipMakerAPI; 024import com.fs.starfarer.api.util.Misc; 025 026public class PulsarBeamTerrainPlugin extends BaseRingTerrain implements PulsarRendererDelegate { 027 028 029 public static float PULSAR_ARC = 1f / ((float) Math.PI * 2f) * 360f; 030 //public static float PULSAR_ARC = 0.25f / ((float) Math.PI * 2f) * 360f; 031 032 transient protected SpriteAPI flareTexture = null; 033 transient Color color = null; 034 035 transient protected PulsarRenderer flare1, flare2; 036 protected CoronaParams params; 037 protected transient RangeBlockerUtil blocker = null; 038 039 protected float pulsarAngle = (float) Math.random() * 360f; 040 protected float pulsarRotation = -1f * (10f + (float) Math.random() * 10f); 041 042 public void init(String terrainId, SectorEntityToken entity, Object param) { 043 super.init(terrainId, entity, param); 044 params = (CoronaParams) param; 045 name = params.name; 046 if (name == null) { 047 name = "Pulsar Beam"; 048 } 049 } 050 051 public String getNameForTooltip() { 052 return "Pulsar Beam"; 053 } 054 055 @Override 056 protected Object readResolve() { 057 super.readResolve(); 058 //flareTexture = Global.getSettings().getSprite("graphics/fx/beam_weave_fringe.png"); 059 flareTexture = Global.getSettings().getSprite("terrain", "pulsar"); 060 061 layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7); 062 if (blocker == null) { 063 //blocker = new RangeBlockerUtil(360, super.params.bandWidthInEngine + 1000f); 064 blocker = new RangeBlockerUtil(720 * 2, super.params.bandWidthInEngine + 1000f); 065 } 066 067 flare1 = new PulsarRenderer(this); 068 flare2 = new PulsarRenderer(this); 069 return this; 070 } 071 072 Object writeReplace() { 073 return this; 074 } 075 076 @Override 077 protected boolean shouldPlayLoopOne() { 078 return super.shouldPlayLoopOne() && containsEntity(Global.getSector().getPlayerFleet()); 079 } 080 081 @Override 082 protected float getLoopOneVolume() { 083 float intensity = getIntensityAtPoint(Global.getSector().getPlayerFleet().getLocation()); 084 return intensity; 085 } 086 087 @Override 088 protected float getExtraSoundRadius() { 089 return 0f; 090// float base = super.getExtraSoundRadius(); 091// 092// //float angle = Misc.getAngleInDegrees(params.relatedEntity.getLocation(), Global.getSector().getPlayerFleet().getLocation()); 093// float extra = 0f; 094// 095// return base + extra; 096 } 097 098 099 transient private EnumSet<CampaignEngineLayers> layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7); 100 public EnumSet<CampaignEngineLayers> getActiveLayers() { 101 return layers; 102 } 103 104 public CoronaParams getParams() { 105 return params; 106 } 107 108 public void advance(float amount) { 109 super.advance(amount); 110 111 float days = Global.getSector().getClock().convertToDays(amount); 112 pulsarAngle += pulsarRotation * days * 1f; 113 pulsarAngle = Misc.normalizeAngle(pulsarAngle); 114 //pulsarAngle += pulsarRotation * days * 0.5f; 115 116// if (params.relatedEntity instanceof PlanetAPI) { 117// PlanetAPI planet = (PlanetAPI) params.relatedEntity; 118// planet.getSpec().setTilt(pulsarAngle); 119// planet.applySpecChanges(); 120// } 121 122 flare1.advance(amount); 123 flare2.advance(amount); 124 125 flare1.setCurrAngle(pulsarAngle); 126 flare2.setCurrAngle(pulsarAngle + 180f); 127 128 //pulsarAngle += pulsarRotation * days * 10.25f; 129 //pulsarAngle += pulsarRotation * days * 5f; 130 131 if (amount > 0 && blocker != null) { 132 blocker.updateLimits(entity, params.relatedEntity, 0.5f); 133 //blocker.sync(); 134 blocker.advance(amount, 100f, 0.5f); 135 } 136 137 138 } 139 140 public void render(CampaignEngineLayers layer, ViewportAPI viewport) { 141 if (blocker != null && !blocker.wasEverUpdated()) { 142 blocker.updateAndSync(entity, params.relatedEntity, 0.1f); 143 } 144 145 146 if (isNearViewport(pulsarAngle, viewport)) { 147 flare1.render(viewport.getAlphaMult()); 148 } 149// else { 150// System.out.println("SKIP1"); 151// } 152 153 if (isNearViewport(pulsarAngle + 180f, viewport)) { 154 flare2.render(viewport.getAlphaMult()); 155 } 156// else { 157// System.out.println("SKIP2"); 158// } 159 } 160 161 162 protected boolean isNearViewport(float angle, ViewportAPI viewport) { 163 float wClose = getPulsarInnerWidth(); 164 float wFar = getPulsarOuterWidth(); 165 float distClose = getPulsarInnerRadius(); 166 float distFar = getPulsarOuterRadius(); 167 168 float length = distFar - distClose; 169 float incr = (float) Math.ceil((distFar - distClose) / 2000f); 170 171 for (float dist = wClose; dist < distFar; dist += incr) { 172 Vector2f test = Misc.getUnitVectorAtDegreeAngle(angle); 173 test.scale(dist); 174 Vector2f.add(test, entity.getLocation(), test); 175 176 float testDist = wClose + (wFar - wClose) * (dist - distClose) / length; 177 testDist *= 0.5f; 178 if (viewport.isNearViewport(test, testDist + 500f)) { 179 return true; 180 } 181 } 182 return false; 183 } 184 185 186 @Override 187 public float getRenderRange() { 188 return getPulsarOuterRadius() + 1000f; 189 } 190 191 @Override 192 public boolean containsPoint(Vector2f point, float radius) { 193 if (blocker != null && blocker.isAnythingShortened()) { 194 float angle = Misc.getAngleInDegreesStrict(this.entity.getLocation(), point); 195 float dist = Misc.getDistance(this.entity.getLocation(), point); 196 float max = blocker.getCurrMaxAt(angle); 197 if (dist > max) return false; 198 } 199 200 if (!Misc.isInArc(pulsarAngle, PULSAR_ARC, entity.getLocation(), point) && 201 !Misc.isInArc(pulsarAngle + 180f, PULSAR_ARC, entity.getLocation(), point)) { 202 return false; 203 } 204 205 float dist = Misc.getDistance(this.entity.getLocation(), point); 206 if (dist < getPulsarInnerRadius()) return false; 207 208 return super.containsPoint(point, radius); 209 } 210 211 212 213 @Override 214 public void applyEffect(SectorEntityToken entity, float days) { 215 if (entity instanceof CampaignFleetAPI) { 216 CampaignFleetAPI fleet = (CampaignFleetAPI) entity; 217 218 float intensity = getIntensityAtPoint(fleet.getLocation()); 219 if (intensity <= 0) return; 220 221 String buffId = getModId(); 222 float buffDur = 0.1f; 223 224 // CR loss 225 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) { 226 float recoveryRate = member.getStats().getBaseCRRecoveryRatePercentPerDay().getModifiedValue(); 227 float lossRate = member.getStats().getBaseCRRecoveryRatePercentPerDay().getBaseValue(); 228 229 float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT); 230 //if (inFlare) loss *= 2f; 231 float lossMult = 1f; 232 float adjustedLossMult = (0f + params.crLossMult * intensity * resistance * lossMult * StarCoronaTerrainPlugin.CR_LOSS_MULT_GLOBAL); 233 234 float loss = (-1f * recoveryRate + -1f * lossRate * adjustedLossMult) * days * 0.01f; 235 float curr = member.getRepairTracker().getBaseCR(); 236 if (loss > curr) loss = curr; 237 238 if (resistance > 0) { 239 member.getRepairTracker().applyCREvent(loss, "corona", "Pulsar beam effect"); 240 } 241 242 float peakFraction = 1f / Math.max(1.3333f, 1f + params.crLossMult * intensity); 243 float peakLost = 1f - peakFraction; 244 peakLost *= resistance; 245 246 float degradationMult = 1f + (params.crLossMult * intensity * resistance) / 2f; 247 248 member.getBuffManager().addBuffOnlyUpdateStat(new PeakPerformanceBuff(buffId + "_1", 1f - peakLost, buffDur)); 249 member.getBuffManager().addBuffOnlyUpdateStat(new CRLossPerSecondBuff(buffId + "_2", degradationMult, buffDur)); 250 } 251 252 // "wind" effect - adjust velocity 253 float maxFleetBurn = fleet.getFleetData().getBurnLevel(); 254 float currFleetBurn = fleet.getCurrBurnLevel(); 255 256 float maxWindBurn = params.windBurnLevel; 257 258 259 float currWindBurn = intensity * maxWindBurn; 260 float maxFleetBurnIntoWind = maxFleetBurn - Math.abs(currWindBurn); 261 262 float angle = Misc.getAngleInDegreesStrict(this.entity.getLocation(), fleet.getLocation()); 263 Vector2f windDir = Misc.getUnitVectorAtDegreeAngle(angle); 264 if (currWindBurn < 0) { 265 windDir.negate(); 266 } 267 268 Vector2f velDir = Misc.normalise(new Vector2f(fleet.getVelocity())); 269 velDir.scale(currFleetBurn); 270 271 float fleetBurnAgainstWind = -1f * Vector2f.dot(windDir, velDir); 272 273 float accelMult = 0.5f; 274 if (fleetBurnAgainstWind > maxFleetBurnIntoWind) { 275 accelMult += 0.75f + 0.25f * (fleetBurnAgainstWind - maxFleetBurnIntoWind); 276 } 277 278 float seconds = days * Global.getSector().getClock().getSecondsPerDay(); 279 280 Vector2f vel = fleet.getVelocity(); 281 windDir.scale(seconds * fleet.getAcceleration() * accelMult); 282 fleet.setVelocity(vel.x + windDir.x, vel.y + windDir.y); 283 284// if (fleet.getOrbit() != null) { 285// fleet.setOrbit(null); 286// } 287 288 Color glowColor = getPulsarColorForAngle(angle); 289 int alpha = glowColor.getAlpha(); 290 if (alpha < 75) { 291 glowColor = Misc.setAlpha(glowColor, 75); 292 } 293 // visual effects - glow, tail 294 295 float durIn = 1f; 296 float durOut = 3f; 297 Misc.normalise(windDir); 298 float sizeNormal = 10f + 25f * intensity; 299 for (FleetMemberViewAPI view : fleet.getViews()) { 300 view.getWindEffectDirX().shift(getModId(), windDir.x * sizeNormal, durIn, durOut, 1f); 301 view.getWindEffectDirY().shift(getModId(), windDir.y * sizeNormal, durIn, durOut, 1f); 302 view.getWindEffectColor().shift(getModId(), glowColor, durIn, durOut, intensity); 303 } 304 } 305 } 306 307 308 309 public float getIntensityAtPoint(Vector2f point) { 310 float maxDist = params.bandWidthInEngine; 311 float minDist = params.relatedEntity.getRadius(); 312 float dist = Misc.getDistance(point, params.relatedEntity.getLocation()); 313 314 if (dist > maxDist) return 0f; 315 316 float intensity = 1f; 317 if (minDist < maxDist) { 318 intensity = 1f - (dist - minDist) / (maxDist - minDist); 319 //intensity = 0.5f + intensity * 0.5f; 320 if (intensity < 0) intensity = 0; 321 if (intensity > 1) intensity = 1; 322 } 323 324 float angle = Misc.getAngleInDegreesStrict(params.relatedEntity.getLocation(), point); 325 float diff = Misc.getAngleDiff(angle, pulsarAngle); 326 diff = Math.min(diff, Misc.getAngleDiff(angle, pulsarAngle + 180f)); 327 float maxDiff = PULSAR_ARC / 2f; 328 if (diff > maxDiff) diff = maxDiff; 329 330 if (diff > maxDiff * 0.5f) { 331 intensity *= 0.25f + 0.75f * (1f - (diff - maxDiff * 0.5f) / (maxDiff * 0.5f)); 332 } 333 334 return intensity; 335 } 336 337 338 339 @Override 340 public Color getNameColor() { 341 Color bad = Misc.getNegativeHighlightColor(); 342 Color base = super.getNameColor(); 343 //bad = Color.red; 344 return Misc.interpolateColor(base, bad, Global.getSector().getCampaignUI().getSharedFader().getBrightness() * 1f); 345 } 346 347 public boolean hasTooltip() { 348 return true; 349 } 350 351 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) { 352 float pad = 10f; 353 float small = 5f; 354 Color gray = Misc.getGrayColor(); 355 Color highlight = Misc.getHighlightColor(); 356 Color fuel = Global.getSettings().getColor("progressBarFuelColor"); 357 Color bad = Misc.getNegativeHighlightColor(); 358 359 tooltip.addTitle("Pulsar Beam"); 360 tooltip.addPara(Global.getSettings().getDescription(getTerrainId(), Type.TERRAIN).getText1(), pad); 361 362 float nextPad = pad; 363 if (expanded) { 364 tooltip.addSectionHeading("Travel", Alignment.MID, pad); 365 nextPad = small; 366 } 367 tooltip.addPara("Reduces the combat readiness of " + 368 "all ships caught in the pulsar beam at a rapid pace, and blows the fleet off-course.", nextPad); 369 tooltip.addPara("The magnitude of the effect drops off rapidly with distance from the source.", pad); 370 371 if (expanded) { 372 tooltip.addSectionHeading("Combat", Alignment.MID, pad); 373 tooltip.addPara("Reduces the peak performance time of ships and increases the rate of combat readiness degradation in protracted engagements.", small); 374 } 375 376 //tooltip.addPara("Does not stack with other similar terrain effects.", pad); 377 } 378 379 public boolean isTooltipExpandable() { 380 return true; 381 } 382 383 public float getTooltipWidth() { 384 return 350f; 385 } 386 387 public String getTerrainName() { 388 return super.getTerrainName(); 389 } 390 391 public String getEffectCategory() { 392 return null; // to ensure multiple coronas overlapping all take effect 393 //return "corona_" + (float) Math.random(); 394 } 395 396 public boolean hasAIFlag(Object flag, CampaignFleetAPI fleet) { 397 if (fleet != null && containsEntity(fleet)) { 398 return hasAIFlag(flag); 399 } 400 return false; 401 } 402 403 public boolean hasAIFlag(Object flag) { 404 return flag == TerrainAIFlags.CR_DRAIN || 405 flag == TerrainAIFlags.BREAK_OTHER_ORBITS || 406 flag == TerrainAIFlags.EFFECT_DIMINISHED_WITH_RANGE; 407 } 408 409 public float getMaxEffectRadius(Vector2f locFrom) { 410 //float angle = Misc.getAngleInDegrees(params.relatedEntity.getLocation(), locFrom); 411 float maxDist = params.bandWidthInEngine; 412 return maxDist; 413 } 414 public float getMinEffectRadius(Vector2f locFrom) { 415 return 0f; 416 } 417 418 public float getOptimalEffectRadius(Vector2f locFrom) { 419 return params.relatedEntity.getRadius(); 420 } 421 422 public boolean canPlayerHoldStationIn() { 423 return false; 424 } 425 426 427 public RangeBlockerUtil getPulsarBlocker() { 428 //return null; 429 return blocker; 430 } 431 432 public Vector2f getPulsarCenterLoc() { 433 return params.relatedEntity.getLocation(); 434 } 435 436 public Color getPulsarColorForAngle(float angle) { 437 if (color == null) { 438 Color c = Color.white; 439 if (params.relatedEntity instanceof PlanetAPI) { 440 c = ((PlanetAPI)params.relatedEntity).getSpec().getCoronaColor(); 441 } else { 442 c = Color.white; 443 } 444 float alpha = 1f; 445 color = Misc.setAlpha(c, (int) (200 * alpha)); 446 return color; 447 } else { 448 return color; 449 } 450 } 451 452 453 public float getPulsarInnerRadius() { 454 return params.relatedEntity.getRadius(); 455 } 456 457 458 public float getPulsarOuterRadius() { 459 return params.middleRadius + params.bandWidthInEngine * 0.5f; 460 } 461 462 public float getPulsarInnerWidth() { 463 //PULSAR_ARC = 1f / ((float) Math.PI * 2f) * 360f; 464 return PULSAR_ARC / 360f * 2f * (float) Math.PI * getPulsarInnerRadius(); 465 //return PULSAR_ARC / 360f * 2f * (float) Math.PI * getPulsarInnerRadius(); 466 } 467 468 public float getPulsarOuterWidth() { 469 float r1 = getPulsarInnerRadius(); 470 float r2 = getPulsarOuterRadius(); 471 return getPulsarInnerWidth() * r2 / r1; 472 } 473 474 public float getPulsarScrollSpeed() { 475 return 50f; 476 } 477 478 public SpriteAPI getPulsarTexture() { 479 return flareTexture; 480 } 481} 482 483 484 485 486