Starsector API
Loading...
Searching...
No Matches
StarCoronaTerrainPlugin.java
Go to the documentation of this file.
1package com.fs.starfarer.api.impl.campaign.terrain;
2
3import java.awt.Color;
4import java.util.ArrayList;
5import java.util.EnumSet;
6import java.util.List;
7
8import org.lwjgl.util.vector.Vector2f;
9
10import com.fs.starfarer.api.Global;
11import com.fs.starfarer.api.campaign.CampaignEngineLayers;
12import com.fs.starfarer.api.campaign.CampaignFleetAPI;
13import com.fs.starfarer.api.campaign.PlanetAPI;
14import com.fs.starfarer.api.campaign.SectorEntityToken;
15import com.fs.starfarer.api.campaign.TerrainAIFlags;
16import com.fs.starfarer.api.combat.ViewportAPI;
17import com.fs.starfarer.api.fleet.FleetMemberAPI;
18import com.fs.starfarer.api.fleet.FleetMemberViewAPI;
19import com.fs.starfarer.api.graphics.SpriteAPI;
20import com.fs.starfarer.api.impl.campaign.ids.Stats;
21import com.fs.starfarer.api.impl.campaign.ids.Tags;
22import com.fs.starfarer.api.impl.campaign.terrain.AuroraRenderer.AuroraRendererDelegate;
23import com.fs.starfarer.api.impl.campaign.terrain.FlareManager.Flare;
24import com.fs.starfarer.api.impl.campaign.terrain.FlareManager.FlareManagerDelegate;
25import com.fs.starfarer.api.loading.Description.Type;
26import com.fs.starfarer.api.ui.Alignment;
27import com.fs.starfarer.api.ui.TooltipMakerAPI;
28import com.fs.starfarer.api.util.Misc;
29
30public class StarCoronaTerrainPlugin extends BaseRingTerrain implements AuroraRendererDelegate, FlareManagerDelegate {
31
32 public static final float CR_LOSS_MULT_GLOBAL = 0.25f;
33
34 public static class CoronaParams extends RingParams {
35 public float windBurnLevel;
36 public float flareProbability;
37 public float crLossMult;
38
39 public CoronaParams(float bandWidthInEngine, float middleRadius,
40 SectorEntityToken relatedEntity,
41 float windBurnLevel, float flareProbability, float crLossMult) {
42 super(bandWidthInEngine, middleRadius, relatedEntity);
43 this.windBurnLevel = windBurnLevel;
44 this.flareProbability = flareProbability;
45 this.crLossMult = crLossMult;
46 }
47 }
48
49 transient protected SpriteAPI texture = null;
50 transient protected Color color;
51
54 protected CoronaParams params;
55
56 protected transient RangeBlockerUtil blocker = null;
57
58 public void init(String terrainId, SectorEntityToken entity, Object param) {
59 super.init(terrainId, entity, param);
60 params = (CoronaParams) param;
61 name = params.name;
62 if (name == null) {
63 name = "Corona";
64 }
65 }
66
67 public String getNameForTooltip() {
68 return "Corona";
69 }
70
71 @Override
72 protected Object readResolve() {
73 super.readResolve();
74 texture = Global.getSettings().getSprite("terrain", "aurora");
75 layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
76 if (renderer == null) {
77 renderer = new AuroraRenderer(this);
78 }
79 if (flareManager == null) {
80 flareManager = new FlareManager(this);
81 }
82 if (blocker == null) {
83 blocker = new RangeBlockerUtil(360, super.params.bandWidthInEngine + 1000f);
84 }
85 return this;
86 }
87
88 Object writeReplace() {
89 return this;
90 }
91
92 @Override
93 protected boolean shouldPlayLoopOne() {
94 return super.shouldPlayLoopOne() && !flareManager.isInActiveFlareArc(Global.getSector().getPlayerFleet());
95 }
96
97 @Override
98 protected boolean shouldPlayLoopTwo() {
99 return super.shouldPlayLoopTwo() && flareManager.isInActiveFlareArc(Global.getSector().getPlayerFleet());
100 }
101
102
103
104 transient private EnumSet<CampaignEngineLayers> layers = EnumSet.of(CampaignEngineLayers.TERRAIN_7);
105 public EnumSet<CampaignEngineLayers> getActiveLayers() {
106 return layers;
107 }
108
109 public CoronaParams getParams() {
110 return params;
111 }
112
113 public void advance(float amount) {
114 super.advance(amount);
115 renderer.advance(amount);
116 flareManager.advance(amount);
117
118 if (amount > 0 && blocker != null) {
119 blocker.updateLimits(entity, params.relatedEntity, 0.5f);
120 blocker.advance(amount, 100f, 0.5f);
121 }
122 }
123
124 public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
125 if (blocker != null && !blocker.wasEverUpdated()) {
126 blocker.updateAndSync(entity, params.relatedEntity, 0.5f);
127 }
128 renderer.render(viewport.getAlphaMult());
129 }
130
131 @Override
132 public float getRenderRange() {
133 Flare curr = flareManager.getActiveFlare();
134 if (curr != null) {
135 float outerRadiusWithFlare = computeRadiusWithFlare(flareManager.getActiveFlare());
136 return outerRadiusWithFlare + 200f;
137 }
138 return super.getRenderRange();
139 }
140
141 @Override
142 public boolean containsPoint(Vector2f point, float radius) {
143 if (blocker != null && blocker.isAnythingShortened()) {
144 float angle = Misc.getAngleInDegrees(this.entity.getLocation(), point);
145 float dist = Misc.getDistance(this.entity.getLocation(), point);
146 float max = blocker.getCurrMaxAt(angle);
147 if (dist > max) return false;
148 }
149
150 if (flareManager.isInActiveFlareArc(point)) {
151 float outerRadiusWithFlare = computeRadiusWithFlare(flareManager.getActiveFlare());
152 float dist = Misc.getDistance(this.entity.getLocation(), point);
153 if (dist > outerRadiusWithFlare + radius) return false;
154 if (dist + radius < params.middleRadius - params.bandWidthInEngine / 2f) return false;
155 return true;
156 }
157 return super.containsPoint(point, radius);
158 }
159
160 protected float computeRadiusWithFlare(Flare flare) {
161 float inner = getAuroraInnerRadius();
162 float outer = params.middleRadius + params.bandWidthInEngine * 0.5f;
163 float thickness = outer - inner;
164
165 thickness *= flare.extraLengthMult;
166 thickness += flare.extraLengthFlat;
167
168 return inner + thickness;
169 }
170
171 @Override
172 protected float getExtraSoundRadius() {
173 float base = super.getExtraSoundRadius();
174
175 float angle = Misc.getAngleInDegrees(params.relatedEntity.getLocation(), Global.getSector().getPlayerFleet().getLocation());
176 float extra = 0f;
177 if (flareManager.isInActiveFlareArc(angle)) {
178 extra = computeRadiusWithFlare(flareManager.getActiveFlare()) - params.bandWidthInEngine;
179 }
180 //System.out.println("Extra: " + extra);
181 return base + extra;
182 }
183
184
185 @Override
186 public void applyEffect(SectorEntityToken entity, float days) {
187 if (entity instanceof CampaignFleetAPI) {
188
189 // larger sim step when not current location means fleets tend to get trapped in black holes
190 // so: just don't apply its effects
191 if (!entity.isInCurrentLocation() && this instanceof EventHorizonPlugin) {
192 return;
193 }
194
195 CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
196
197 boolean inFlare = false;
198 if (flareManager.isInActiveFlareArc(fleet)) {
199 inFlare = true;
200 }
201
202 float intensity = getIntensityAtPoint(fleet.getLocation());
203 if (intensity <= 0) return;
204
205 String buffId = getModId();
206 float buffDur = 0.1f;
207
208 boolean protectedFromCorona = false;
209 if (fleet.isInCurrentLocation() &&
210 Misc.getDistance(fleet, Global.getSector().getPlayerFleet()) < 500) {
211 for (SectorEntityToken curr : fleet.getContainingLocation().getCustomEntitiesWithTag(Tags.PROTECTS_FROM_CORONA_IN_BATTLE)) {
212 float dist = Misc.getDistance(curr, fleet);
213 if (dist < curr.getRadius() + fleet.getRadius() + 10f) {
214 protectedFromCorona = true;
215 break;
216 }
217 }
218 }
219
220 // CR loss and peak time reduction
221 for (FleetMemberAPI member : fleet.getFleetData().getMembersListCopy()) {
222 float recoveryRate = member.getStats().getBaseCRRecoveryRatePercentPerDay().getModifiedValue();
223 float lossRate = member.getStats().getBaseCRRecoveryRatePercentPerDay().getBaseValue();
224
225 float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT);
226 if (protectedFromCorona) resistance = 0f;
227 //if (inFlare) loss *= 2f;
228 float lossMult = 1f;
229 if (inFlare) lossMult = 2f;
230 float adjustedLossMult = (0f + params.crLossMult * intensity * resistance * lossMult * CR_LOSS_MULT_GLOBAL);
231
232 float loss = (-1f * recoveryRate + -1f * lossRate * adjustedLossMult) * days * 0.01f;
233 float curr = member.getRepairTracker().getBaseCR();
234 if (loss > curr) loss = curr;
235 if (resistance > 0) { // not actually resistance, the opposite
236 if (inFlare) {
237 member.getRepairTracker().applyCREvent(loss, "flare", "Solar flare effect");
238 } else {
239 member.getRepairTracker().applyCREvent(loss, "corona", "Star corona effect");
240 }
241 }
242
243 // needs to be applied when resistance is 0 to immediately cancel out the debuffs (by setting them to 0)
244 float peakFraction = 1f / Math.max(1.3333f, 1f + params.crLossMult * intensity);
245 float peakLost = 1f - peakFraction;
246 peakLost *= resistance;
247 float degradationMult = 1f + (params.crLossMult * intensity * resistance) / 2f;
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 if (inFlare) {
258 maxWindBurn *= 2f;
259 }
260
261
262 float currWindBurn = intensity * maxWindBurn;
263 float maxFleetBurnIntoWind = maxFleetBurn - Math.abs(currWindBurn);
264
265 float angle = Misc.getAngleInDegreesStrict(this.entity.getLocation(), fleet.getLocation());
266 Vector2f windDir = Misc.getUnitVectorAtDegreeAngle(angle);
267 if (currWindBurn < 0) {
268 windDir.negate();
269 }
270
271 Vector2f velDir = Misc.normalise(new Vector2f(fleet.getVelocity()));
272 velDir.scale(currFleetBurn);
273
274 float fleetBurnAgainstWind = -1f * Vector2f.dot(windDir, velDir);
275
276 float accelMult = 0.5f;
277 if (fleetBurnAgainstWind > maxFleetBurnIntoWind) {
278 accelMult += 0.75f + 0.25f * (fleetBurnAgainstWind - maxFleetBurnIntoWind);
279 }
280 float fleetAccelMult = fleet.getStats().getAccelerationMult().getModifiedValue();
281 if (fleetAccelMult > 0) {// && fleetAccelMult < 1) {
282 accelMult /= fleetAccelMult;
283 }
284
285 float seconds = days * Global.getSector().getClock().getSecondsPerDay();
286
287 Vector2f vel = fleet.getVelocity();
288 windDir.scale(seconds * fleet.getAcceleration() * accelMult);
289 fleet.setVelocity(vel.x + windDir.x, vel.y + windDir.y);
290
291 Color glowColor = getAuroraColorForAngle(angle);
292 int alpha = glowColor.getAlpha();
293 if (alpha < 75) {
294 glowColor = Misc.setAlpha(glowColor, 75);
295 }
296 // visual effects - glow, tail
297
298
299 float dist = Misc.getDistance(this.entity.getLocation(), fleet.getLocation());
300 float check = 100f;
301 if (params.relatedEntity != null) check = params.relatedEntity.getRadius() * 0.5f;
302 if (dist > check) {
303 float durIn = 1f;
304 float durOut = 10f;
305 Misc.normalise(windDir);
306 float sizeNormal = 5f + 10f * intensity;
307 float sizeFlare = 10f + 15f * intensity;
308 for (FleetMemberViewAPI view : fleet.getViews()) {
309 if (inFlare) {
310 view.getWindEffectDirX().shift(getModId() + "_flare", windDir.x * sizeFlare, durIn, durOut, 1f);
311 view.getWindEffectDirY().shift(getModId() + "_flare", windDir.y * sizeFlare, durIn, durOut, 1f);
312 view.getWindEffectColor().shift(getModId() + "_flare", glowColor, durIn, durOut, intensity);
313 } else {
314 view.getWindEffectDirX().shift(getModId(), windDir.x * sizeNormal, durIn, durOut, 1f);
315 view.getWindEffectDirY().shift(getModId(), windDir.y * sizeNormal, durIn, durOut, 1f);
316 view.getWindEffectColor().shift(getModId(), glowColor, durIn, durOut, intensity);
317 }
318 }
319 }
320 }
321 }
322
323 public float getIntensityAtPoint(Vector2f point) {
324 float angle = Misc.getAngleInDegrees(params.relatedEntity.getLocation(), point);
325 float maxDist = params.bandWidthInEngine;
326 if (flareManager.isInActiveFlareArc(angle)) {
328 }
329 float minDist = params.relatedEntity.getRadius();
330 float dist = Misc.getDistance(point, params.relatedEntity.getLocation());
331
332 if (dist > maxDist) return 0f;
333
334 float intensity = 1f;
335 if (minDist < maxDist) {
336 intensity = 1f - (dist - minDist) / (maxDist - minDist);
337 //intensity = 0.5f + intensity * 0.5f;
338 if (intensity < 0) intensity = 0;
339 if (intensity > 1) intensity = 1;
340 }
341
342 return intensity;
343 }
344
345
346
347 @Override
348 public Color getNameColor() {
349 Color bad = Misc.getNegativeHighlightColor();
350 Color base = super.getNameColor();
351 //bad = Color.red;
352 return Misc.interpolateColor(base, bad, Global.getSector().getCampaignUI().getSharedFader().getBrightness() * 1f);
353 }
354
355 public boolean hasTooltip() {
356 return true;
357 }
358
359 public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
360 float pad = 10f;
361 float small = 5f;
362 Color gray = Misc.getGrayColor();
363 Color highlight = Misc.getHighlightColor();
364 Color fuel = Global.getSettings().getColor("progressBarFuelColor");
365 Color bad = Misc.getNegativeHighlightColor();
366
367 tooltip.addTitle(name);
368 tooltip.addPara(Global.getSettings().getDescription(getTerrainId(), Type.TERRAIN).getText1(), pad);
369
370 float nextPad = pad;
371 if (expanded) {
372 tooltip.addSectionHeading("Travel", Alignment.MID, pad);
373 nextPad = small;
374 }
375 tooltip.addPara("Reduces the combat readiness of " +
376 "all ships in the corona at a steady pace.", nextPad);
377 tooltip.addPara("The heavy solar wind also makes the star difficult to approach.", pad);
378 tooltip.addPara("Occasional solar flare activity takes these effects to even more dangerous levels.", pad);
379
380 if (expanded) {
381 tooltip.addSectionHeading("Combat", Alignment.MID, pad);
382 tooltip.addPara("Reduces the peak performance time of ships and increases the rate of combat readiness degradation in protracted engagements.", small);
383 }
384
385 //tooltip.addPara("Does not stack with other similar terrain effects.", pad);
386 }
387
388 public boolean isTooltipExpandable() {
389 return true;
390 }
391
392 public float getTooltipWidth() {
393 return 350f;
394 }
395
396 public String getTerrainName() {
397 if (flareManager.isInActiveFlareArc(Global.getSector().getPlayerFleet())) {
398 return "Solar Flare";
399 }
400 return super.getTerrainName();
401 }
402
403 public String getEffectCategory() {
404 return null; // to ensure multiple coronas overlapping all take effect
405 //return "corona_" + (float) Math.random();
406 }
407
408 public float getAuroraAlphaMultForAngle(float angle) {
409 return 1f;
410 }
411
413 return 256f;
414 //return 512f;
415 }
416
418 return 1f;
419 //return 2f;
420 }
421
422 public Vector2f getAuroraCenterLoc() {
423 return params.relatedEntity.getLocation();
424 }
425
426 public Color getAuroraColorForAngle(float angle) {
427 if (color == null) {
428 if (params.relatedEntity instanceof PlanetAPI) {
429 color = ((PlanetAPI)params.relatedEntity).getSpec().getCoronaColor();
430 //color = Misc.interpolateColor(color, Color.white, 0.5f);
431 } else {
432 color = Color.white;
433 }
434 color = Misc.setAlpha(color, 25);
435 }
436 if (flareManager.isInActiveFlareArc(angle)) {
437 return flareManager.getColorForAngle(color, angle);
438 }
439 return color;
440 }
441
442 public float getAuroraInnerRadius() {
443 return params.relatedEntity.getRadius() + 50f;
444 }
445
446 public float getAuroraOuterRadius() {
447 return params.middleRadius + params.bandWidthInEngine * 0.5f;
448 }
449
450 public float getAuroraShortenMult(float angle) {
451 return 0.85f + flareManager.getShortenMod(angle);
452 }
453
454 public float getAuroraInnerOffsetMult(float angle) {
455 return flareManager.getInnerOffsetMult(angle);
456 }
457
458 public SpriteAPI getAuroraTexture() {
459 return texture;
460 }
461
463 return blocker;
464 }
465
466 public float getAuroraThicknessFlat(float angle) {
467// float shorten = blocker.getShortenAmountAt(angle);
468// if (shorten > 0) return -shorten;
469// if (true) return -4000f;
470
471 if (flareManager.isInActiveFlareArc(angle)) {
472 return flareManager.getExtraLengthFlat(angle);
473 }
474 return 0;
475 }
476
477 public float getAuroraThicknessMult(float angle) {
478 if (flareManager.isInActiveFlareArc(angle)) {
479 return flareManager.getExtraLengthMult(angle);
480 }
481 return 1f;
482 }
483
484
485
486
487
488 public List<Color> getFlareColorRange() {
489 List<Color> result = new ArrayList<Color>();
490
491 if (params.relatedEntity instanceof PlanetAPI) {
492 Color color = ((PlanetAPI)params.relatedEntity).getSpec().getCoronaColor();
493 result.add(Misc.setAlpha(color, 255));
494 } else {
495 result.add(Color.white);
496 }
497 //result.add(Misc.setAlpha(getAuroraColorForAngle(0), 127));
498 return result;
499 }
500
501 public float getFlareArcMax() {
502 return 60;
503 }
504
505 public float getFlareArcMin() {
506 return 30;
507 }
508
510 return 500;
511 }
512
514 return 200;
515 }
516
518 return 1.5f;
519 }
520
522 return 1;
523 }
524
525 public float getFlareFadeInMax() {
526 return 10f;
527 }
528
529 public float getFlareFadeInMin() {
530 return 3f;
531 }
532
533 public float getFlareFadeOutMax() {
534 return 10f;
535 }
536
537 public float getFlareFadeOutMin() {
538 return 3f;
539 }
540
541 public float getFlareOccurrenceAngle() {
542 return 0;
543 }
544
545 public float getFlareOccurrenceArc() {
546 return 360f;
547 }
548
549 public float getFlareProbability() {
550 return params.flareProbability;
551 }
552
553 public float getFlareSmallArcMax() {
554 return 20;
555 }
556
557 public float getFlareSmallArcMin() {
558 return 10;
559 }
560
562 return 100;
563 }
564
566 return 50;
567 }
568
570 return 1.05f;
571 }
572
574 return 1;
575 }
576
577 public float getFlareSmallFadeInMax() {
578 return 2f;
579 }
580
581 public float getFlareSmallFadeInMin() {
582 return 1f;
583 }
584
585 public float getFlareSmallFadeOutMax() {
586 return 2f;
587 }
588
589 public float getFlareSmallFadeOutMin() {
590 return 1f;
591 }
592
594 return 0.05f;
595 }
596
598 return 0.05f;
599 }
600
602 return 0.05f;
603 }
604
606 return 0.05f;
607 }
608
610 return 3;
611 }
612
614 return 5;
615 }
616
618 return 0f;
619 }
620
621 public SectorEntityToken getFlareCenterEntity() {
622 return this.entity;
623 }
624
625 public boolean hasAIFlag(Object flag) {
626 return flag == TerrainAIFlags.CR_DRAIN ||
627 flag == TerrainAIFlags.BREAK_OTHER_ORBITS ||
628 flag == TerrainAIFlags.EFFECT_DIMINISHED_WITH_RANGE;
629 }
630
631 public float getMaxEffectRadius(Vector2f locFrom) {
632 float angle = Misc.getAngleInDegrees(params.relatedEntity.getLocation(), locFrom);
633 float maxDist = params.bandWidthInEngine;
634 if (flareManager.isInActiveFlareArc(angle)) {
636 }
637 return maxDist;
638 }
639 public float getMinEffectRadius(Vector2f locFrom) {
640 return 0f;
641 }
642
643 public float getOptimalEffectRadius(Vector2f locFrom) {
644 return params.relatedEntity.getRadius();
645 }
646
647 public boolean canPlayerHoldStationIn() {
648 return false;
649 }
650
652 return flareManager;
653 }
654
655}
656
657
658
659
660
static SettingsAPI getSettings()
Definition Global.java:51
static SectorAPI getSector()
Definition Global.java:59
Color getColorForAngle(Color baseColor, float angle)
void advance(float amount, float minApproachSpeed, float diffMult)
void updateLimits(SectorEntityToken entity, SectorEntityToken exclude, float diffMult)
void updateAndSync(SectorEntityToken entity, SectorEntityToken exclude, float diffMult)
void render(CampaignEngineLayers layer, ViewportAPI viewport)
void init(String terrainId, SectorEntityToken entity, Object param)
Description getDescription(String id, Type type)
SpriteAPI getSprite(String filename)