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