001package com.fs.starfarer.api.impl.campaign.abilities;
002
003import java.util.EnumSet;
004
005import java.awt.Color;
006
007import org.lwjgl.opengl.GL11;
008import org.lwjgl.util.vector.Vector2f;
009
010import com.fs.starfarer.api.Global;
011import com.fs.starfarer.api.campaign.CampaignEngineLayers;
012import com.fs.starfarer.api.campaign.CampaignFleetAPI;
013import com.fs.starfarer.api.campaign.econ.CommoditySpecAPI;
014import com.fs.starfarer.api.combat.ViewportAPI;
015import com.fs.starfarer.api.graphics.SpriteAPI;
016import com.fs.starfarer.api.impl.campaign.ids.Commodities;
017import com.fs.starfarer.api.ui.Alignment;
018import com.fs.starfarer.api.ui.LabelAPI;
019import com.fs.starfarer.api.ui.TooltipMakerAPI;
020import com.fs.starfarer.api.util.Misc;
021
022public class GraviticScanAbility extends BaseToggleAbility {
023
024        public static float SLIPSTREAM_DETECTION_RANGE = 20000f;
025        
026        public static String COMMODITY_ID = Commodities.VOLATILES;
027        public static float COMMODITY_PER_DAY = 1f;
028        
029        public static float DETECTABILITY_PERCENT = 50f;
030        
031        
032        @Override
033        protected String getActivationText() {
034                if (COMMODITY_ID != null && getFleet() != null && getFleet().getCargo().getCommodityQuantity(COMMODITY_ID) <= 0 &&
035                                (!Global.getSettings().isDevMode() || Global.getSettings().getBoolean("playtestingMode"))) {
036                        return null;
037                }
038                return "Neutrino detector activated";
039        }
040        
041        @Override
042        protected String getDeactivationText() {
043                return null;
044        }
045
046
047        @Override
048        protected void activateImpl() {
049
050        }
051
052        @Override
053        public boolean showProgressIndicator() {
054                return false;
055        }
056        
057        @Override
058        public boolean showActiveIndicator() {
059                return isActive();
060        }
061
062        
063        @Override
064        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
065                Color bad = Misc.getNegativeHighlightColor();
066                Color gray = Misc.getGrayColor();
067                Color highlight = Misc.getHighlightColor();
068                
069                String status = " (off)";
070                if (turnedOn) {
071                        status = " (on)";
072                }
073                
074                if (!Global.CODEX_TOOLTIP_MODE) {
075                        LabelAPI title = tooltip.addTitle(spec.getName() + status);
076                        title.highlightLast(status);
077                        title.setHighlightColor(gray);
078                } else {
079                        tooltip.addSpacer(-10f);
080                }
081
082                float pad = 10f;
083                
084                
085                tooltip.addPara("Reconfigures the fleet's drive field to act as a neutrino detector, " +
086                                "allowing detection of human-made artifacts - and occasionally fleets - at extreme ranges. ", pad);
087                
088                tooltip.addSectionHeading("Normal space", Alignment.MID, pad);
089                tooltip.addPara("High-emission sources such as stars, planets, jump-points, or space stations produce constant streams. " +
090                                "Average sources produce periodic bursts. Low-emission sources produce occasional bursts.", pad);
091                
092                tooltip.addPara("Notoriously unreliable and almost guaranteed to produce numerous false readings.", pad);
093
094                
095                if (COMMODITY_ID != null) {
096                        String unit = "unit";
097                        if (COMMODITY_PER_DAY != 1) unit = "units";
098                        CommoditySpecAPI spec = getCommodity();
099                        unit += " of " + spec.getName().toLowerCase();
100                        
101                        tooltip.addPara("Increases the range at which the fleet can be detected by %s and consumes %s " + unit + " per day.",
102                                        pad, highlight,
103                                        "" + (int)DETECTABILITY_PERCENT + "%",
104                                        "" + Misc.getRoundedValueMaxOneAfterDecimal(COMMODITY_PER_DAY)
105                        );
106                } else {
107                        tooltip.addPara("Increases the range at which the fleet can be detected by %s.",
108                                        pad, highlight,
109                                        "" + (int)DETECTABILITY_PERCENT + "%"
110                        );
111                }
112                
113                int maxRange = (int) Math.round(SLIPSTREAM_DETECTION_RANGE / Misc.getUnitsPerLightYear());
114                tooltip.addSectionHeading("Hyperspace", Alignment.MID, pad);
115                tooltip.addPara("Reliably detects the presence of slipstreams out to a range of %s light-years. "
116                                + "Except for abyssal areas, the background noise levels are such that it is unable to detect any other neutrino sources. "
117                                + "When the fleet is traversing a slipstream, the detector is overwhelmed and shuts down.",
118                                pad, highlight, "" + maxRange);
119                if (!Global.CODEX_TOOLTIP_MODE && Misc.isInsideSlipstream(getFleet())) {
120                        tooltip.addPara("Cannot activate while inside slipstream.", bad, pad);
121                }
122//              if (getFleet() != null && getFleet().isInHyperspace()) {
123//                      tooltip.addPara("Can not function in hyperspace.", bad, pad);
124//              } else {
125//                      tooltip.addPara("Can not function in hyperspace.", pad);
126//              }
127                
128                //tooltip.addPara("Disables the transponder when activated.", pad);
129                addIncompatibleToTooltip(tooltip, expanded);
130        }
131
132        public boolean hasTooltip() {
133                return true;
134        }
135        
136        @Override
137        public EnumSet<CampaignEngineLayers> getActiveLayers() {
138                return EnumSet.of(CampaignEngineLayers.ABOVE);
139        }
140
141
142        @Override
143        public void advance(float amount) {
144                super.advance(amount);
145                
146                if (data != null && !isActive() && getProgressFraction() <= 0f) {
147                        data = null;
148                }
149        }
150        
151
152        protected float phaseAngle;
153        protected GraviticScanData data = null;
154        @Override
155        protected void applyEffect(float amount, float level) {
156                CampaignFleetAPI fleet = getFleet();
157                if (fleet == null) return;
158                
159                //if (level < 1) level = 0;
160                
161                fleet.getStats().getDetectedRangeMod().modifyPercent(getModId(), DETECTABILITY_PERCENT * level, "Gravimetric scan");
162
163                float days = Global.getSector().getClock().convertToDays(amount);
164                phaseAngle += days * 360f * 10f;
165                phaseAngle = Misc.normalizeAngle(phaseAngle);
166                
167                if (data == null) {
168                        data = new GraviticScanData(this);
169                }
170                data.advance(days);
171                
172                if (COMMODITY_ID != null) {
173                        float cost = days * COMMODITY_PER_DAY;
174                        if (fleet.getCargo().getCommodityQuantity(COMMODITY_ID) > 0 || (Global.getSettings().isDevMode() && !Global.getSettings().getBoolean("playtestingMode"))) {
175                                fleet.getCargo().removeCommodity(COMMODITY_ID, cost);
176                        } else {
177                                CommoditySpecAPI spec = getCommodity();
178                                fleet.addFloatingText("Out of " + spec.getName().toLowerCase(), Misc.setAlpha(entity.getIndicatorColor(), 255), 0.5f);
179                                deactivate();
180                        }
181                }
182                
183                if (Misc.isInsideSlipstream(fleet)) {
184                        deactivate();
185                }
186//              if (fleet.isInHyperspace()) {
187//                      deactivate();
188//              }
189        }
190        
191        public CommoditySpecAPI getCommodity() {
192                return Global.getSettings().getCommoditySpec(COMMODITY_ID);
193        }
194        
195        @Override
196        public boolean isUsable() {
197                CampaignFleetAPI fleet = getFleet();
198                if (fleet == null) return false;
199                
200                return !Misc.isInsideSlipstream(fleet);
201                //return isActive() || !fleet.isInHyperspace();
202        }
203        
204
205        @Override
206        protected void deactivateImpl() {
207                cleanupImpl();
208        }
209        
210        @Override
211        protected void cleanupImpl() {
212                CampaignFleetAPI fleet = getFleet();
213                if (fleet == null) return;
214                
215                fleet.getStats().getDetectedRangeMod().unmodify(getModId());
216                //data = null;
217        }
218
219
220
221
222        public float getRingRadius() {
223                return getFleet().getRadius() + 75f;    
224                //return getFleet().getRadius() + 25f;  
225        }
226        
227        transient protected SpriteAPI texture;
228        @Override
229        public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
230                
231                if (data == null) return;
232                
233                float level = getProgressFraction();
234                if (level <= 0) return;
235                if (getFleet() == null) return;
236                if (!getFleet().isPlayerFleet()) return;
237                
238                float alphaMult = viewport.getAlphaMult() * level;
239                
240//              float x = getFleet().getLocation().x;
241//              float y = getFleet().getLocation().y;
242//              
243//              GL11.glPushMatrix();
244//              GL11.glTranslatef(x, y, 0);
245//              
246//              GL11.glDisable(GL11.GL_TEXTURE_2D);
247//              Misc.renderQuad(30, 30, 100, 100, Color.green, alphaMult * level);
248//              
249//              
250//              GL11.glPopMatrix();
251        
252                
253                //float noiseLevel = data.getNoiseLevel();
254                
255                float bandWidthInTexture = 256;
256                float bandIndex;
257                
258                float radStart = getRingRadius();
259                float radEnd = radStart + 75f;
260                
261                float circ = (float) (Math.PI * 2f * (radStart + radEnd) / 2f);
262                //float pixelsPerSegment = 10f;
263                float pixelsPerSegment = circ / 360f;
264                //float pixelsPerSegment = circ / 720;
265                float segments = Math.round(circ / pixelsPerSegment);
266                
267//              segments = 360;
268//              pixelsPerSegment = circ / segments;
269                //pixelsPerSegment = 10f;
270                
271                float startRad = (float) Math.toRadians(0);
272                float endRad = (float) Math.toRadians(360f);
273                float spanRad = Math.abs(endRad - startRad);
274                float anglePerSegment = spanRad / segments;
275                
276                Vector2f loc = getFleet().getLocation();
277                float x = loc.x;
278                float y = loc.y;
279
280                
281                GL11.glPushMatrix();
282                GL11.glTranslatef(x, y, 0);
283                
284                //float zoom = viewport.getViewMult();
285                //GL11.glScalef(zoom, zoom, 1);
286                
287                GL11.glEnable(GL11.GL_TEXTURE_2D);
288                
289                if (texture == null) texture = Global.getSettings().getSprite("abilities", "neutrino_detector");
290                texture.bindTexture();
291                
292                GL11.glEnable(GL11.GL_BLEND);
293                //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
294                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
295                
296                boolean outlineMode = false;
297                //outlineMode = true;
298                if (outlineMode) {
299                        GL11.glDisable(GL11.GL_TEXTURE_2D);
300                        GL11.glDisable(GL11.GL_BLEND);
301                        GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
302                        //GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
303                }
304                
305                float thickness = (radEnd - radStart) * 1f;
306                float radius = radStart;
307
308                float texProgress = 0f;
309                float texHeight = texture.getTextureHeight();
310                float imageHeight = texture.getHeight();
311                float texPerSegment = pixelsPerSegment * texHeight / imageHeight * bandWidthInTexture / thickness;
312                
313                texPerSegment *= 1f;
314                
315                float totalTex = Math.max(1f, Math.round(texPerSegment * segments));
316                texPerSegment = totalTex / segments;
317                
318                float texWidth = texture.getTextureWidth();
319                float imageWidth = texture.getWidth();
320                
321                
322                
323                Color color = new Color(25,215,255,255);
324                //Color color = new Color(255,25,255,155);
325                
326                
327                for (int iter = 0; iter < 2; iter++) {
328                        if (iter == 0) {
329                                bandIndex = 1;
330                        } else {
331                                //color = new Color(255,215,25,255);
332                                //color = new Color(25,255,215,255);
333                                bandIndex = 0;
334                                texProgress = segments/2f * texPerSegment;
335                                //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
336                        }
337                        if (iter == 1) {
338                                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
339                        }
340                        //bandIndex = 1;
341                        
342                        float leftTX = (float) bandIndex * texWidth * bandWidthInTexture / imageWidth;
343                        float rightTX = (float) (bandIndex + 1f) * texWidth * bandWidthInTexture / imageWidth - 0.001f;
344                        
345                        GL11.glBegin(GL11.GL_QUAD_STRIP);
346                        for (float i = 0; i < segments + 1; i++) {
347                                
348                                float segIndex = i % (int) segments;
349                                
350                                //float phaseAngleRad = (float) Math.toRadians(phaseAngle + segIndex * 10) + (segIndex * anglePerSegment * 10f);
351                                float phaseAngleRad;
352                                if (iter == 0) {
353                                        phaseAngleRad = (float) Math.toRadians(phaseAngle) + (segIndex * anglePerSegment * 29f);
354                                } else { //if (iter == 1) { 
355                                        phaseAngleRad = (float) Math.toRadians(-phaseAngle) + (segIndex * anglePerSegment * 17f);
356                                }
357                                
358                                
359                                float angle = (float) Math.toDegrees(segIndex * anglePerSegment);
360                                //if (iter == 1) angle += 180;
361                                
362                                
363                                float pulseSin = (float) Math.sin(phaseAngleRad);
364                                float pulseMax = thickness * 0.5f;
365
366                                pulseMax = thickness * 0.2f;
367                                pulseMax = 10f;
368                                
369                                //pulseMax *= 0.25f + 0.75f * noiseLevel;
370                                
371                                float pulseAmount = pulseSin * pulseMax;
372                                //float pulseInner = pulseAmount * 0.1f;
373                                float pulseInner = pulseAmount * 0.1f;
374                                
375                                float r = radius;
376
377//                              float thicknessMult = delegate.getAuroraThicknessMult(angle);
378//                              float thicknessFlat = delegate.getAuroraThicknessFlat(angle);
379                                
380                                float theta = anglePerSegment * segIndex;;
381                                float cos = (float) Math.cos(theta);
382                                float sin = (float) Math.sin(theta);
383                                
384                                float rInner = r - pulseInner;
385                                //if (rInner < r * 0.9f) rInner = r * 0.9f;
386                                
387                                //float rOuter = (r + thickness * thicknessMult - pulseAmount + thicknessFlat);
388                                float rOuter = r + thickness - pulseAmount;
389                                
390                                
391                                //rOuter += noiseLevel * 25f;
392                                
393                                float grav = data.getDataAt(angle);
394                                //if (grav > 500) System.out.println(grav);
395                                //if (grav > 300) grav = 300;
396                                if (grav > 750) grav = 750;
397                                grav *= 250f / 750f;
398                                grav *= level;
399                                //grav *= 0.5f;
400                                //rInner -= grav * 0.25f;
401                                
402                                //rInner -= grav * 0.1f;
403                                rOuter += grav;
404//                              rInner -= grav * 3f;
405//                              rOuter -= grav * 3f;
406                                //System.out.println(grav);
407                                
408                                float alpha = alphaMult;
409                                alpha *= 0.25f + Math.min(grav / 100, 0.75f);
410                                //alpha *= 0.75f;
411                                
412//                      
413//                              
414//                              
415//                              phaseAngleWarp = (float) Math.toRadians(phaseAngle - 180 * iter) + (segIndex * anglePerSegment * 1f);
416//                              float warpSin = (float) Math.sin(phaseAngleWarp);
417//                              rInner += thickness * 0.5f * warpSin;
418//                              rOuter += thickness * 0.5f * warpSin;
419                                
420                                
421                                
422                                float x1 = cos * rInner;
423                                float y1 = sin * rInner;
424                                float x2 = cos * rOuter;
425                                float y2 = sin * rOuter;
426                                
427                                x2 += (float) (Math.cos(phaseAngleRad) * pixelsPerSegment * 0.33f);
428                                y2 += (float) (Math.sin(phaseAngleRad) * pixelsPerSegment * 0.33f);
429                                
430                                
431                                GL11.glColor4ub((byte)color.getRed(),
432                                                (byte)color.getGreen(),
433                                                (byte)color.getBlue(),
434                                                (byte)((float) color.getAlpha() * alphaMult * alpha));
435                                
436                                GL11.glTexCoord2f(leftTX, texProgress);
437                                GL11.glVertex2f(x1, y1);
438                                GL11.glTexCoord2f(rightTX, texProgress);
439                                GL11.glVertex2f(x2, y2);
440                                
441                                texProgress += texPerSegment * 1f;
442                        }
443                        GL11.glEnd();
444                        
445                        //GL11.glRotatef(180, 0, 0, 1);
446                }
447                GL11.glPopMatrix();
448                
449                if (outlineMode) {
450                        GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
451                }
452        }
453        
454
455
456        
457        
458        
459        
460}
461
462
463
464
465