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