001package com.fs.starfarer.api.impl.campaign.terrain;
002
003import java.util.ArrayList;
004import java.util.EnumSet;
005import java.util.List;
006import java.util.Random;
007
008import java.awt.Color;
009
010import org.lwjgl.opengl.GL11;
011import org.lwjgl.util.vector.Vector2f;
012
013import com.fs.starfarer.api.Global;
014import com.fs.starfarer.api.campaign.CampaignEngineLayers;
015import com.fs.starfarer.api.campaign.CampaignFleetAPI;
016import com.fs.starfarer.api.campaign.SectorEntityToken;
017import com.fs.starfarer.api.campaign.StarSystemAPI;
018import com.fs.starfarer.api.campaign.TerrainAIFlags;
019import com.fs.starfarer.api.campaign.rules.MemoryAPI;
020import com.fs.starfarer.api.combat.ViewportAPI;
021import com.fs.starfarer.api.fleet.FleetMemberAPI;
022import com.fs.starfarer.api.fleet.FleetMemberViewAPI;
023import com.fs.starfarer.api.graphics.SpriteAPI;
024import com.fs.starfarer.api.impl.campaign.abilities.EmergencyBurnAbility;
025import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
026import com.fs.starfarer.api.impl.campaign.ids.Stats;
027import com.fs.starfarer.api.impl.campaign.ids.Tags;
028import com.fs.starfarer.api.impl.combat.BattleCreationPluginImpl;
029import com.fs.starfarer.api.loading.Description.Type;
030import com.fs.starfarer.api.ui.Alignment;
031import com.fs.starfarer.api.ui.TooltipMakerAPI;
032import com.fs.starfarer.api.util.FlickerUtilV2;
033import com.fs.starfarer.api.util.Misc;
034import com.fs.starfarer.api.util.WeightedRandomPicker;
035
036public class HyperspaceTerrainPlugin extends BaseTiledTerrain { // implements NebulaTextureProvider {
037
038        public static float ABYSS_MUSIC_SUPPRESSION = 1f;
039        
040//      public static class AbyssalMusicDamper implements EveryFrameScript {
041//              public boolean isDone() {
042//                      return false;
043//              }
044//              public boolean runWhilePaused() {
045//                      return true;
046//              }
047//      
048//              public void advance(float amount) {
049//                      
050//              }
051//      }
052        
053
054        public static float ABYSS_VISIBLITY_MULT = 0.25f;
055        public static float ABYSS_SENSOR_RANGE_MULT = 0.25f;
056        public static float ABYSS_BURN_MULT = 0.25f;
057        
058        public static float ABYSS_NAVIGATION_EFFECT = 0;
059        
060        
061        public static Color ABYSS_BACKGROUND_COLOR = new Color(0, 0, 0, 255);
062        public static Color ABYSS_PARTICLE_COLOR = new Color(0, 0, 0, 0);
063        public static Color ABYSS_LIGHT_COLOR = new Color(170, 170, 170, 255);
064        
065        
066        
067        public static String STORM_STRIKE_TIMEOUT_KEY = "$stormStrikeTimeout";
068        
069        public static float VISIBLITY_MULT = 0.5f;
070        
071        
072        public static float STORM_STRIKE_SOUND_RANGE = 1500f;
073        
074        public static float STORM_MIN_TIMEOUT = 0.4f;
075        public static float STORM_MAX_TIMEOUT = 0.6f;
076        public static float STORM_DAMAGE_FRACTION = 0.3f;
077        public static float STORM_MIN_STRIKE_DAMAGE = 0.05f;
078        public static float STORM_MAX_STRIKE_DAMAGE = 0.95f;
079        
080        public static float STORM_SPEED_MULT = 1f;
081        public static float STORM_SENSOR_RANGE_MULT = 1f;
082        public static float STORM_VISIBILITY_FLAT = 0f;
083        
084        
085        public static float TILE_SIZE = 200;
086        
087        public static final CampaignEngineLayers FLASH = CampaignEngineLayers.TERRAIN_6A;
088        public static final CampaignEngineLayers FLASH_OVER = CampaignEngineLayers.TERRAIN_9;
089        public static final CampaignEngineLayers GLOW = CampaignEngineLayers.TERRAIN_8;
090        public static final CampaignEngineLayers BASE = CampaignEngineLayers.TERRAIN_6;
091        //public static final CampaignEngineLayers OVER = CampaignEngineLayers.TERRAIN_6B;
092        public static final CampaignEngineLayers SHIVER = CampaignEngineLayers.TERRAIN_9;
093        public static final CampaignEngineLayers BASE_OVER = CampaignEngineLayers.TERRAIN_7;
094
095        public static enum LocationState {
096                OPEN,
097                DEEP,
098                DEEP_STORM,
099        }
100        
101        public static enum CellState {
102                OFF,
103                WAIT,
104                SIGNAL,
105                STORM,
106                STORM_WANE,
107        }
108        public static class CellStateTracker {
109                public int i, j;
110                public CellState state;
111                public float wait, signal, wane;
112                private float maxSignal, maxWane;
113                public FlickerUtilV2 flicker = null;
114                public CellStateTracker(int i, int j, float wait, float signal) {
115                        this.i = i;
116                        this.j = j;
117                        this.wait = wait;
118                        this.signal = signal;
119                        this.maxSignal = signal;
120                        state = CellState.WAIT;
121                }
122
123                
124                public void advance(float days) {
125                        if (state == CellState.OFF) return;
126                        
127                        if (state == CellState.WAIT  && days > 0) {
128                                wait -= days;
129                                if (wait <= 0) {
130                                        days = -wait;
131                                        wait = 0;
132                                        state = CellState.SIGNAL;
133                                }
134                        }
135                        
136                        if (state == CellState.SIGNAL && days > 0) {
137                                signal -= days;
138                                if (signal <= 0) {
139                                        days = -signal;
140                                        signal = 0;
141                                        state = CellState.STORM;
142                                        flicker = new FlickerUtilV2();
143                                        flicker.newBurst();
144                                }
145                        }
146                        
147                        if (state == CellState.STORM || state == CellState.STORM_WANE) {
148                                signal -= days; // needed for signal brightness to fade
149                        }
150
151                        if (state == CellState.STORM_WANE && days > 0) {
152                                wane -= days;
153                                if (wane <= 0) {
154                                        days = -wane;
155                                        wane = 0;
156                                        if (flicker == null || flicker.getBrightness() <= 0) {
157                                                state = CellState.OFF;
158                                        } else {
159                                                flicker.stop();
160                                        }
161                                }
162                        }
163                        
164                        if (flicker != null) {
165                                flicker.advance(days * 7f);
166                        }
167                }
168                
169                public float getSignalBrightness() {
170                        if (state == CellState.SIGNAL) {
171                                return 1f - signal / maxSignal;
172                        }
173//                      if (state == CellState.STORM || state == CellState.STORM_WANE) {
174//                              //System.out.println(Math.max(0, 1 + signal * 10));
175//                              return Math.max(0, 1 + signal * 10); // fade out over 1 second, signal is negative here
176//                      }
177                        if (state == CellState.STORM) {
178                                return 1f;
179                        }
180                        if (state == CellState.STORM_WANE) {
181                                float fade = maxWane * 0.25f;
182                                if (wane > fade) {
183                                        return 1f;
184                                }
185                                return Math.max(0, wane / fade);
186                        }
187                        return 0f;
188                }
189
190                public void wane(float wane) {
191                        this.wane = wane;
192                        this.maxWane = wane;
193                        state = CellState.STORM_WANE;
194                }
195                
196                public boolean isOff() {
197                        return state == CellState.OFF;
198                }
199                
200                public boolean isStorming() {
201                        return state == CellState.STORM || state == CellState.STORM_WANE;
202                }
203                
204                public boolean isWaning() {
205                        return state == CellState.STORM_WANE;
206                }
207                
208                public boolean isSignaling() {
209                        return state == CellState.SIGNAL;
210                }
211        }
212        
213        protected transient SpriteAPI flickerTexture;
214        
215        protected transient CellStateTracker [][] activeCells;
216        protected List<CellStateTracker> savedActiveCells = new ArrayList<CellStateTracker>();
217        
218        protected HyperspaceAutomaton auto;
219        
220        protected transient String stormSoundId = null;
221        protected HyperspaceAbyssPlugin abyssPlugin;
222        
223        protected SectorEntityToken abyssDarkSource = null;
224        
225        public void init(String terrainId, SectorEntityToken entity, Object param) {
226                super.init(terrainId, entity, param);
227        }
228        
229        public HyperspaceAbyssPlugin getAbyssPlugin() {
230                if (abyssPlugin == null) {
231                        abyssPlugin = new HyperspaceAbyssPluginImpl();
232                }
233                return abyssPlugin;
234        }
235
236        public void setAbyssPlugin(HyperspaceAbyssPlugin abyssChecker) {
237                this.abyssPlugin = abyssChecker;
238        }
239
240        protected Object readResolve() {
241                super.readResolve();
242                layers = EnumSet.of(BASE, FLASH, GLOW, SHIVER, BASE_OVER, FLASH_OVER);
243                
244                if (abyssPlugin == null) {
245                        abyssPlugin = new HyperspaceAbyssPluginImpl();
246                }
247                
248                if (auto == null) {
249                        //auto = new HyperspaceAutomaton(params.w, params.h, 0.75f, 1.25f);
250                        auto = new HyperspaceAutomaton(params.w, params.h, 1.5f, 2.5f);
251                }
252                
253                flickerTexture = Global.getSettings().getSprite(params.cat, params.key + "_glow");
254                if (activeCells == null) {
255                        activeCells = new CellStateTracker[params.w][params.h];
256                        
257                        if (savedActiveCells != null) {
258                                for (CellStateTracker curr : savedActiveCells) {
259                                        activeCells[curr.i][curr.j] = curr; 
260                                }
261                        }
262                }
263                
264                // init cells to random mid-storm state where appropriate
265                int [][] cells = auto.getCells();
266                
267                for (int i = 0; i < activeCells.length; i++) {
268                        for (int j = 0; j < activeCells[0].length; j++) {
269                                if (tiles[i][j] < 0) continue;
270                                
271                                CellStateTracker curr = activeCells[i][j];
272                                int val = cells[i][j];
273                                float interval = auto.getInterval().getIntervalDuration();
274                                
275                                if (val == 1 && curr == null) {
276                                        curr = activeCells[i][j] = new CellStateTracker(i, j, 
277                                                        interval * 0f + interval * 1.5f * (float) Math.random(),
278                                                        interval * 0.5f + interval * 0.5f * (float) Math.random());
279                                        
280                                        float dur = (float) Math.random() * interval * 2.5f;
281                                        curr.advance(dur);
282                                }
283                        }
284                }
285                        
286                stormSoundId = getSpec().getCustom().optString("stormSound", null);
287                return this;
288        }
289        
290        public CellStateTracker[][] getActiveCells() {
291                return activeCells;
292        }
293        
294        protected static void clearCellsNotNearPlayer(HyperspaceTerrainPlugin plugin) {
295                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
296                if (playerFleet == null) return;
297                
298                Vector2f test = new Vector2f();
299                if (playerFleet != null) {
300                        test = playerFleet.getLocationInHyperspace();
301                }
302                
303                float x = plugin.entity.getLocation().x;
304                float y = plugin.entity.getLocation().y;
305                float size = plugin.getTileSize();
306                
307                float w = plugin.tiles.length * size;
308                float h = plugin.tiles[0].length * size;
309                x -= w/2f;
310                y -= h/2f;
311                int xIndex = (int) ((test.x - x) / size);
312                int yIndex = (int) ((test.y - y) / size);
313                if (xIndex < 0) xIndex = 0;
314                if (yIndex < 0) yIndex = 0;
315                if (xIndex >= plugin.tiles.length) xIndex = plugin.tiles.length - 1;
316                if (yIndex >= plugin.tiles[0].length) yIndex = plugin.tiles[0].length - 1;
317                
318                int subgridSize = (int) ((10000 / size + 1) * 2f);
319                
320                int minX = Math.max(0, xIndex - subgridSize/2);
321                int maxX = xIndex + subgridSize/2 ;
322                int minY = Math.max(0, yIndex - subgridSize/2);
323                int maxY = yIndex + subgridSize/2;
324                
325                // clean up area around the "active" area so that as the player moves around,
326                // they don't leave frozen storm cells behind (which would then make it into the savefile)
327                int pad = Math.max(plugin.tiles.length, plugin.tiles[0].length) * 2;
328                for (int i = minX - pad; i <= maxX + pad && i < plugin.tiles.length; i++) {
329                        for (int j = minY - pad; j <= maxY + pad && j < plugin.tiles[0].length; j++) {
330                                if (i < minX || j < minY || i > maxX || j > maxY) {
331                                        if (i >= 0 && j >= 0) {
332                                                plugin.activeCells[i][j] = null;
333                                        }
334                                }
335                        }
336                }
337        }
338        
339        Object writeReplace() {
340                HyperspaceTerrainPlugin copy = (HyperspaceTerrainPlugin) super.writeReplace();
341                
342                clearCellsNotNearPlayer(copy);
343                
344                copy.savedActiveCells = new ArrayList<CellStateTracker>();
345                for (int i = 0; i < copy.activeCells.length; i++) {
346                        for (int j = 0; j < copy.activeCells[0].length; j++) {
347                                CellStateTracker curr = copy.activeCells[i][j];
348                                if (curr != null && isTileVisible(i, j)) {
349                                        copy.savedActiveCells.add(curr);
350                                }
351                        }
352                }
353                return copy;
354        }
355        
356        transient private EnumSet<CampaignEngineLayers> layers = EnumSet.of(BASE, FLASH, GLOW, SHIVER, BASE_OVER, FLASH_OVER);
357        public EnumSet<CampaignEngineLayers> getActiveLayers() {
358                return layers;
359        }
360        
361        
362        protected transient float [] temp = new float[2];
363        protected float[] getThetaAndRadius(Random rand, float width, float height) {
364                if (temp == null) temp = new float[2];
365                
366                //if (true) return temp;
367                float speedFactor = 0.5f;
368                
369                float time = elapsed * Global.getSector().getClock().getSecondsPerDay();
370                float min = -360f * (rand.nextFloat() * 3f + 1f) * Misc.RAD_PER_DEG;
371                float max = 360f * (rand.nextFloat() * 3f + 1f) * Misc.RAD_PER_DEG;
372                float rate = (30f + 70f * rand.nextFloat()) * Misc.RAD_PER_DEG;
373                rate *= speedFactor;
374                float period = 2f * (max - min) / rate;
375                float progress = rand.nextFloat() + time / period;
376                progress = progress - (int) progress;
377                
378                float theta, radius;
379                if (progress < 0.5f) {
380                        theta = min + (max - min) * progress * 2f;
381                } else {
382                        theta = min + (max - min) * (1f - progress) * 2f;
383                }
384                temp[0] = theta;
385                
386                min = 0f;
387                max = (width + height) * 0.025f;
388                rate = max * 0.5f;
389                rate *= speedFactor;
390                
391                period = 2f * (max - min) / rate;
392                progress = rand.nextFloat() + time / period;
393                progress = progress - (int) progress;
394                if (progress < 0.5f) {
395                        radius = min + (max - min) * progress * 2f;
396                } else {
397                        radius = min + (max - min) * (1f - progress) * 2f;
398                }
399                temp[1] = radius;
400                
401                return temp;
402                
403//              float twoPI = (float) Math.PI * 2f;
404//              float maxRad = (width + height) * 0.025f;
405//              float speedFactor = 0.5f;
406//              float sign1 = rand.nextFloat() > 0.5f ? 1f : -1f;
407//              float speed1 = (0.5f + rand.nextFloat()) * twoPI * speedFactor; 
408//              float theta1 = rand.nextFloat() * twoPI + speed1 * elapsed * sign1;
409//              float radius1 = (0.5f + rand.nextFloat()) * maxRad;
410//              temp[0] = theta1;
411//              temp[1] = radius1;
412//              return temp;
413        }
414        
415        @Override
416        protected void renderQuad(int i, int j, float x, float y, float width, float height,
417                                                          float texX, float texY, float texW, float texH, float angle) {
418                
419                if (currLayer == null) {
420                        super.renderQuad(i, j, x, y, width, height, texX, texY, texW, texH, angle);
421                        return;
422                }
423                
424                if (currLayer == FLASH_OVER) return;
425                if (currLayer == SHIVER) return;
426                //if (currLayer == BASE) return;
427                //if (currLayer == BASE_OVER) return;
428                //if (currLayer == FLASH) return;
429                //if (currLayer == GLOW) return;
430                
431
432                CellStateTracker tracker = activeCells[i][j];
433                float signal = 0f;
434                if (tracker != null) {
435                        signal = tracker.getSignalBrightness();
436                }
437                if (currLayer == FLASH && (tracker == null || tracker.flicker == null || tracker.flicker.getBrightness() <= 0)) {
438                        return;
439                }
440                
441                if (currLayer == GLOW && signal <= 0) {
442                        return;
443                }
444                
445                
446//              if (currLayer != HIGHLIGHT) return;
447//              if (currLayer != UNDER) return;
448//              if (currLayer == HIGHLIGHT) return;
449//              if (currLayer != GLOW) return;
450//              if (currLayer != OVER) return;
451                
452                //if (currLayer != BASE) return;
453                //if (currLayer != BASE && currLayer != BASE_OVER) return;
454                
455                long seed = (long) (x + y * tiles.length) * 1000000;
456//              if (currLayer == BASE_OVER) {
457//                      seed /= (long) 4123;
458//              }
459                
460                Random rand = new Random(seed);
461                angle = rand.nextFloat() * 360f;
462                
463                Color color = getRenderColor();
464                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
465                if (playerFleet != null) {
466                        float depth = getAbyssalDepth(playerFleet);
467                        if (depth > 0) {
468                                color = Misc.scaleColorOnly(color, Math.max(0f, 1f - depth));
469                        }
470                }
471                
472                float [] tr = getThetaAndRadius(rand, width, height);
473                float theta1 = tr[0];
474                float radius1 = tr[1];
475                float sin1 = (float) Math.sin(theta1);
476                float cos1 = (float) Math.cos(theta1);
477                
478                tr = getThetaAndRadius(rand, width, height);
479                float theta2 = tr[0];
480                float radius2 = tr[1];
481                float sin2 = (float) Math.sin(theta2);
482                float cos2 = (float) Math.cos(theta2);
483                
484                tr = getThetaAndRadius(rand, width, height);
485                float theta3 = tr[0];
486                float radius3 = tr[1];
487                float sin3 = (float) Math.sin(theta3);
488                float cos3 = (float) Math.cos(theta3);
489                
490                tr = getThetaAndRadius(rand, width, height);
491                float theta4 = tr[0];
492                float radius4 = tr[1];
493                float sin4 = (float) Math.sin(theta4);
494                float cos4 = (float) Math.cos(theta4);
495                
496                
497                
498                float vw = width / 2f;
499                float vh = height / 2f;
500                
501                
502                float cx = x + vw;
503                float cy = y + vh;
504                
505//              vw *= 0.5f;
506//              vh *= 0.5f;
507                
508                float cos = (float) Math.cos(angle * Misc.RAD_PER_DEG);
509                float sin = (float) Math.sin(angle * Misc.RAD_PER_DEG);
510
511                float shiverThreshold = 0.75f;
512                
513                boolean shiver = false;
514                boolean flicker = false;
515                
516                //System.out.println("Layer: " + currLayer);
517                if (currLayer == FLASH || currLayer == FLASH_OVER) {
518                        if (tracker != null && tracker.flicker != null && tracker.flicker.getBrightness() > 0) {
519                                flicker = true;
520                        }
521                } else if (currLayer == BASE) {
522                        if (!currLayerColorSet) {
523                                currLayerColorSet = true;
524                                GL11.glColor4ub((byte)color.getRed(),
525                                                (byte)color.getGreen(),
526                                                (byte)color.getBlue(),
527                                                (byte)((float)color.getAlpha() * currAlpha * 1f));
528                        }
529                } else if (currLayer == BASE_OVER) {
530                        if (!currLayerColorSet) {
531                                currLayerColorSet = true;
532                                GL11.glColor4ub((byte)color.getRed(),
533                                                (byte)color.getGreen(),
534                                                (byte)color.getBlue(),
535                                                (byte)((float)color.getAlpha() * currAlpha * 1f));                      
536                        }
537                } else if (currLayer == GLOW) {
538                        if (tracker != null && signal > 0) {
539                                GL11.glColor4ub((byte)color.getRed(),
540                                                (byte)color.getGreen(),
541                                                (byte)color.getBlue(),
542                                                (byte)((float)color.getAlpha() * currAlpha * 1f * signal));
543                        } else {
544                                return;
545                        }
546                } else if (currLayer == SHIVER) {
547                        if (signal > shiverThreshold && tracker != null && tracker.flicker == null) {
548                                shiver = true;
549                        }
550                } else {
551                        return; // under layer, but not "live"
552                }
553                
554                if (currLayer == GLOW || currLayer == BASE || currLayer == BASE_OVER) {
555                        //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
556                        //if (true) return;
557                        int iter = 1;
558                        if (currLayer == GLOW) iter = 1;
559                        for (int k = 0; k < iter; k++) {
560                                GL11.glTexCoord2f(texX, texY);
561                                GL11.glVertex2f(cx + (-vw * cos + vh * sin) + sin1 * radius1,
562                                                                cy + (-vw * sin - vh * cos) + cos1 * radius1);
563                
564                                GL11.glTexCoord2f(texX, texY + texH);
565                                GL11.glVertex2f(cx + (-vw * cos - vh * sin) + sin2 * radius2, 
566                                                                cy + (-vw * sin + vh * cos) + cos2 * radius2);
567                                
568                                GL11.glTexCoord2f(texX + texW, texY + texH);
569                                GL11.glVertex2f(cx + (vw * cos - vh * sin) + sin3 * radius3,
570                                                                cy + (vw * sin + vh * cos) + cos3 * radius3);
571                
572                                GL11.glTexCoord2f(texX + texW, texY);
573                                GL11.glVertex2f(cx + (vw * cos + vh * sin) + sin4 * radius4,
574                                                                cy + (vw * sin - vh * cos) + cos4 * radius4);
575                        }
576                }
577                
578
579                if (flicker || shiver) {
580                        if (tracker == null) return;
581                        if (shiver) return;
582                        //float shiverBrightness = tracker.getBrightness();
583                        float shiverBrightness = (signal - shiverThreshold) / (1f - shiverThreshold);
584                        if (shiverBrightness > 0.9f) {
585                                shiverBrightness = (1f - shiverBrightness) / 0.1f;
586                        } else {
587                                shiverBrightness /= 0.9f;
588                        }
589                        //shiverBrightness *= shiverBrightness;
590                        //shiverBrightness = 1f;
591                        float ox = cx;
592                        float oy = cy;
593                        //float maxJitter = 0f + 30f * shiverBrightness * shiverBrightness;
594                        float maxJitter = 0f + 30f;
595                        //maxJitter = 0f;
596                        if (shiver) {
597                                rand.setSeed((long) (x + y * tiles.length) * 1000000 + Global.getSector().getClock().getTimestamp());
598                                maxJitter = 0f + 30f * shiverBrightness * shiverBrightness;
599                        } else {
600                                rand.setSeed((long) (x + y * tiles.length) * 1000000 + 
601                                                (long) (tracker.flicker.getAngle() * 1000));
602                        }
603                        maxJitter *= 5f;
604                        if (shiver) {
605//                              vw *= 0.75f;
606//                              vh *= 0.75f;
607                        }
608                        if (flicker) {
609                                //maxJitter = 0f;
610                                //maxJitter *= 0.5f;
611                                vw *= 1.5f;
612                                vh *= 1.5f;
613                        }
614        
615                        //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
616                        //flickerTexture.bindTexture();
617                        if (flicker) {
618                                float alpha = currAlpha;
619                                if (currLayer == FLASH_OVER) {
620                                        alpha *= 0.25f;
621                                }
622                                GL11.glColor4ub((byte)color.getRed(),
623                                                (byte)color.getGreen(),
624                                                (byte)color.getBlue(),
625                                                //(byte)((float)color.getAlpha() * currAlpha * shiverBrightness * 0.5f));
626                                                (byte)((float)color.getAlpha() * alpha * tracker.flicker.getBrightness() * 1f));
627                                //System.out.println(tracker.flicker.getBrightness());
628                        } else if (shiver) {
629                                GL11.glColor4ub((byte)color.getRed(),
630                                                (byte)color.getGreen(),
631                                                (byte)color.getBlue(),
632                                                (byte)((float)color.getAlpha() * currAlpha * shiverBrightness * 0.075f));
633                        }
634                        
635                        int maxIter = 1;
636                        if (shiver) maxIter = 5;
637                        for (int iter = 0; iter < maxIter; iter++) {
638                                cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
639                                cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
640                                
641                                GL11.glTexCoord2f(texX, texY);
642                                GL11.glVertex2f(cx + (-vw * cos + vh * sin) + sin1 * radius1,
643                                                                cy + (-vw * sin - vh * cos) + cos1 * radius1);
644                                
645                                cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
646                                cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
647                
648                                GL11.glTexCoord2f(texX, texY + texH);
649                                GL11.glVertex2f(cx + (-vw * cos - vh * sin) + sin2 * radius2, 
650                                                                cy + (-vw * sin + vh * cos) + cos2 * radius2);
651                                
652                                cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
653                                cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
654                
655                                GL11.glTexCoord2f(texX + texW, texY + texH);
656                                GL11.glVertex2f(cx + (vw * cos - vh * sin) + sin3 * radius3,
657                                                                cy + (vw * sin + vh * cos) + cos3 * radius3);
658                                
659                                cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
660                                cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
661                
662                                GL11.glTexCoord2f(texX + texW, texY);
663                                GL11.glVertex2f(cx + (vw * cos + vh * sin) + sin4 * radius4,
664                                                                cy + (vw * sin - vh * cos) + cos4 * radius4);
665                        }
666                }
667        }
668        
669        
670
671        public String getNebulaMapTex() {
672                return Global.getSettings().getSpriteName(params.cat, params.key + "_map");
673        }
674
675        public String getNebulaTex() {
676                return Global.getSettings().getSpriteName(params.cat, params.key);
677        }
678        
679        protected transient boolean clearedCellsPostLoad = false;
680        
681        protected transient float stormCellTimeMultOutsideBaseArea = 0f;
682        public float getStormCellTimeMultOutsideBaseArea() {
683                return stormCellTimeMultOutsideBaseArea;
684        }
685
686        public void setStormCellTimeMultOutsideBaseArea(float stormCellTimeMultOutsideBaseArea) {
687                this.stormCellTimeMultOutsideBaseArea = stormCellTimeMultOutsideBaseArea;
688        }
689        protected transient float extraDistanceAroundPlayerToAdvanceStormCells = 0f;
690        public float getExtraDistanceAroundPlayerToAdvanceStormCells() {
691                return extraDistanceAroundPlayerToAdvanceStormCells;
692        }
693
694        public void setExtraDistanceAroundPlayerToAdvanceStormCells(float extraDistanceAroundPlayerToAdvanceStormCells) {
695                this.extraDistanceAroundPlayerToAdvanceStormCells = extraDistanceAroundPlayerToAdvanceStormCells;
696        }
697        
698
699        public void advance(float amount) {
700                //if (true) return;
701                super.advance(amount);
702                
703                getAbyssPlugin().advance(amount);
704                
705                if (!clearedCellsPostLoad && Global.getSector().getPlayerFleet() != null) {
706                        clearCellsNotNearPlayer(this);
707                        clearedCellsPostLoad = true;
708                }
709
710                playStormStrikeSoundsIfNeeded();
711                
712                float days = Global.getSector().getClock().convertToDays(amount);
713//              for (int i = 0; i < 100; i++) {
714//                      auto.advance(days * 10f);
715//              }
716                auto.advance(days * 1f);
717                
718                int [][] cells = auto.getCells();
719                
720//              int count = 0;
721//              for (int i = 0; i < activeCells.length; i++) {
722//                      for (int j = 0; j < activeCells[0].length; j++) {
723//                              if (tiles[i][j] < 0) continue;
724//                              CellStateTracker curr = activeCells[i][j];
725//                              if (curr != null) {
726//                                      count++;
727//                              }
728//                      }
729//              }       
730//              System.out.println("Count: " + count + "(out of " + (activeCells.length * activeCells[0].length));
731                
732                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
733                Vector2f test = new Vector2f();
734                if (playerFleet != null) {
735                        test = playerFleet.getLocationInHyperspace();
736                }
737                
738                if (entity.getContainingLocation() != null &&
739                                playerFleet.getContainingLocation() == entity.getContainingLocation() &&
740                                isInAbyss(playerFleet)) {
741                        
742                        float depth = getAbyssalDepth(playerFleet);
743                        entity.getContainingLocation().getBackgroundColorShifter().shift(
744                                        "abyss_color", ABYSS_BACKGROUND_COLOR, 1f, 1f, depth);
745                        
746                        entity.getContainingLocation().getBackgroundParticleColorShifter().shift(
747                                        "abyss_color", ABYSS_PARTICLE_COLOR, 1f, 1f, depth);
748                        
749                        float gain = (float) getSpec().getCustom().optDouble("gain", 0.75f);
750                        float gainHF = (float) getSpec().getCustom().optDouble("gainHF", 0.1f);
751                        if (gain < 1f || gainHF < 1f) {
752                                Global.getSoundPlayer().applyLowPassFilter(
753                                                                        Math.max(0f, 1f - (1f - gain) * depth),
754                                                                        Math.max(0f, 1f - (1f - gainHF) * depth));
755                        }
756                        
757//                      if (ABYSS_MUSIC_SUPPRESSION > 0f) {
758//                              Global.getSector().getCampaignUI().suppressMusic(ABYSS_MUSIC_SUPPRESSION * depth);
759//                      }
760                }
761                
762                
763                float x = this.entity.getLocation().x;
764                float y = this.entity.getLocation().y;
765                float size = getTileSize();
766                
767                float w = tiles.length * size;
768                float h = tiles[0].length * size;
769                x -= w/2f;
770                y -= h/2f;
771                int xIndex = (int) ((test.x - x) / size);
772                int yIndex = (int) ((test.y - y) / size);
773                if (xIndex < 0) xIndex = 0;
774                if (yIndex < 0) yIndex = 0;
775                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
776                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
777                
778                float subgridDist = 10000f + extraDistanceAroundPlayerToAdvanceStormCells;
779                float baseSubgridDist = 10000f;
780                
781                int subgridSize = (int) ((subgridDist / size + 1) * 2f);
782                
783                int minX = Math.max(0, xIndex - subgridSize/2);
784                int maxX = xIndex + subgridSize/2 ;
785                int minY = Math.max(0, yIndex - subgridSize/2);
786                int maxY = yIndex + subgridSize/2;
787                
788                int baseSubgridSize = (int) ((baseSubgridDist / size + 1) * 2f);
789                
790                int baseMinX = Math.max(0, xIndex - baseSubgridSize/2);
791                int baseMaxX = xIndex + baseSubgridSize/2 ;
792                int baseMinY = Math.max(0, yIndex - baseSubgridSize/2);
793                int baseMaxY = yIndex + baseSubgridSize/2;
794                
795                // clean up area around the "active" area so that as the player moves around,
796                // they don't leave frozen storm cells behind (which would then make it into the savefile)
797                int pad = 4;
798                for (int i = minX - pad; i <= maxX + pad && i < tiles.length; i++) {
799                        for (int j = minY - pad; j <= maxY + pad && j < tiles[0].length; j++) {
800                                if (i < minX || j < minY || i > maxX || j > maxY) {
801                                        if (i >= 0 && j >= 0) {
802                                                activeCells[i][j] = null;
803                                        }
804                                }
805                        }
806                }
807                
808                for (int i = minX; i <= maxX && i < tiles.length; i++) {
809                        for (int j = minY; j <= maxY && j < tiles[0].length; j++) {
810//              for (int i = 0; i < activeCells.length; i++) {
811//                      for (int j = 0; j < activeCells[0].length; j++) {
812                                if (tiles[i][j] < 0) continue;
813                                
814                                CellStateTracker curr = activeCells[i][j];
815                                int val = cells[i][j];
816                                float interval = auto.getInterval().getIntervalDuration();
817                                
818                                if (val == 1 && curr == null) {
819                                        curr = activeCells[i][j] = new CellStateTracker(i, j, 
820                                                        interval * 0f + interval * 1.5f * (float) Math.random(),
821                                                        interval * 0.5f + interval * 0.5f * (float) Math.random());
822//                                                      interval * 0f + interval * 0.5f * (float) Math.random(),
823//                                                      interval * 0.25f + interval * 0.25f * (float) Math.random());
824                                }
825                                
826                                if (curr != null) {
827                                        if (val != 1 && curr.isStorming() && !curr.isWaning()) {
828                                                //curr.wane(interval * 0.25f + interval * 0.25f * (float) Math.random());
829                                                curr.wane(interval * 0.5f + interval * 0.5f * (float) Math.random());
830//                                              curr.wane(interval * 0.5f * (float) Math.random() + 
831//                                                                interval * 0.25f + interval * 0.25f * (float) Math.random());
832                                        }
833                                        float timeMult = 1f;
834                                        if (extraDistanceAroundPlayerToAdvanceStormCells > 0 && stormCellTimeMultOutsideBaseArea > 0) {
835                                                if (i < baseMinX || j < baseMinY || i > baseMaxX || j > baseMaxY) {
836                                                        timeMult = stormCellTimeMultOutsideBaseArea;
837                                                }
838                                        }
839                                        curr.advance(days * timeMult);
840                                        if (curr.isOff()) {
841                                                activeCells[i][j] = null;
842                                        }
843                                }
844                        }
845                }
846                
847                stormCellTimeMultOutsideBaseArea = 0f;
848                extraDistanceAroundPlayerToAdvanceStormCells = 0f;
849        }
850        
851        
852        protected void playStormStrikeSoundsIfNeeded() {
853                if (stormSoundId == null) return;
854                
855                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
856                if (playerFleet.getContainingLocation() != entity.getContainingLocation()) return;
857                
858                Vector2f test = playerFleet.getLocation();
859                
860                float x = this.entity.getLocation().x;
861                float y = this.entity.getLocation().y;
862                float size = getTileSize();
863                
864                float w = tiles.length * size;
865                float h = tiles[0].length * size;
866
867                x -= w/2f;
868                y -= h/2f;
869                
870                int xIndex = (int) ((test.x - x) / size);
871                int yIndex = (int) ((test.y - y) / size);
872                
873                if (xIndex < 0) xIndex = 0;
874                if (yIndex < 0) yIndex = 0;
875                
876                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
877                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
878                
879                int subgridSize = (int) ((STORM_STRIKE_SOUND_RANGE / size + 1) * 2f);
880                
881                for (float i = Math.max(0, xIndex - subgridSize/2); i <= xIndex + subgridSize/2 && i < tiles.length; i++) {
882                        for (float j = Math.max(0, yIndex - subgridSize/2); j <= yIndex + subgridSize/2 && j < tiles[0].length; j++) {
883                                int texIndex = tiles[(int) i][(int) j];
884                                if (texIndex >= 0) {
885                                        float tcx = x + i * size + size/2f;
886                                        float tcy = y + j * size + size/2f;
887                                        Vector2f tileLoc = new Vector2f(tcx, tcy);
888                                
889                                        CellStateTracker curr = activeCells[(int)i][(int)j];
890                                        if (curr == null || curr.flicker == null || !curr.isStorming() || !curr.flicker.isPeakFrame() || curr.flicker.getNumBursts() > 1) continue;
891                                        
892                                        float dist = Misc.getDistance(test, tileLoc);
893                                        if (dist > STORM_STRIKE_SOUND_RANGE) continue;
894
895                                        // will be attenuated without this, but there's "too much" lightning sound without
896                                        // this additional attenuation
897                                        float volumeMult = 1f - (dist / STORM_STRIKE_SOUND_RANGE);
898                                        volumeMult = (float) Math.sqrt(volumeMult);
899                                        if (volumeMult <= 0) continue;
900                                        //float volumeMult = 1f;
901                                        //volumeMult *= 0.67f;
902                                        Global.getSoundPlayer().playSound(stormSoundId, 1f, 1f * volumeMult, tileLoc, Misc.ZERO);
903                                }
904                        }
905                }
906                
907        }
908        
909//      protected void spawnWavefront(int i, int j) {
910//              if (true) return;
911//              float [] center = getTileCenter(i, j);
912//              
913//              float angle;
914//              float spread = 90f;
915//              int yLoc = (int) center[1];
916//              yLoc = yLoc / 4000;
917//              if (yLoc % 2 == 0) {
918//                      angle = 0 - spread / 2f + (float) Math.random() * spread; 
919//              } else {
920//                      angle = 180 - spread / 2f + (float) Math.random() * spread;
921//              }
922//      
923//              float initialRange = 400f;
924//              Vector2f loc = Misc.getUnitVectorAtDegreeAngle(angle);
925//              loc.scale(50f);
926//              loc.x += center[0];
927//              loc.y += center[1];
928//              
929//              float burnLevel = (float) (7f + 8f * Math.random());
930//              burnLevel = Math.round(burnLevel);
931//              
932//              float r = (float) Math.random();
933//              r *= r;
934//              float durDays = 1f + r * 2f;
935//              float width = 300f + 500f * (float) Math.random();
936//              SectorEntityToken wave = entity.getContainingLocation().addTerrain(
937//                              Terrain.WAVEFRONT,
938//                              new WavefrontParams(burnLevel, // burn level
939//                                              1f, // CR loss multiplier
940//                                              initialRange, // "origin" range, controls curve of wave
941//                                              width, 100, // width and width expansion
942//                                              200f, 10, // thickness and thickness expansion
943//                                              durDays, // duration days
944//                                              angle // angle
945//                              ) {
946//
947//                              });
948//              wave.getLocation().set(loc.x, loc.y);
949//      }
950        
951        
952                
953
954        private transient CampaignEngineLayers currLayer = null;
955        private transient boolean currLayerColorSet = false;
956        private transient float currAlpha = 1f;
957        public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
958                //if (true) return;
959                currLayer = layer;
960                //currLayerColorSet = false;
961                super.render(layer, viewport);
962        }
963        
964        @Override
965        public void renderOnMap(float factor, float alphaMult) {
966                currLayer = null;
967                //currLayerColorSet = false;
968                super.renderOnMap(factor, alphaMult);
969        }
970
971        
972
973        @Override
974        public float getTileRenderSize() {
975                //return TILE_SIZE + 300f;
976                //return TILE_SIZE + 600f;
977                return TILE_SIZE * 2.5f;
978        }
979        
980        @Override
981        public float getTileContainsSize() {
982                //return TILE_SIZE + 200f;
983                return TILE_SIZE * 1.5f;
984        }
985
986        @Override
987        public float getTileSize() {
988                return TILE_SIZE;
989        }
990        
991        @Override
992        protected void renderSubArea(float startColumn, float endColumn,
993                        float startRow, float endRow, float factor, int samples,
994                        float alphaMult) {
995                super.renderSubArea(startColumn, endColumn, startRow, endRow, factor, samples, alphaMult);
996        }
997
998        @Override
999        public void preRender(CampaignEngineLayers layer, float alphaMult) {
1000                GL11.glEnable(GL11.GL_BLEND);
1001                
1002                //System.out.println("Layer: " + layer);
1003                
1004                if (layer == FLASH || layer == FLASH_OVER) {
1005                        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
1006                        flickerTexture.bindTexture();
1007                } else {
1008                        if (layer == GLOW || layer == SHIVER || layer == BASE) {
1009                                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
1010                        } else {
1011                                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
1012                        }
1013                        if (layer == SHIVER) {
1014                                flickerTexture.bindTexture();
1015                        }
1016                }
1017                //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
1018                
1019//              if (layer == UPPER) {
1020//                      alphaMult *= 0.30f;
1021//              }
1022                
1023                currAlpha = alphaMult;
1024                currLayerColorSet = false;
1025//              Color color = getRenderColor();
1026//              GL11.glColor4ub((byte)color.getRed(),
1027//                              (byte)color.getGreen(),
1028//                              (byte)color.getBlue(),
1029//                              (byte)((float)color.getAlpha() * alphaMult));
1030        }
1031        
1032        @Override
1033        public void preMapRender(float alphaMult) {
1034                GL11.glEnable(GL11.GL_BLEND);
1035                GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
1036                //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
1037                
1038                //Color color = new Color(125,125,200,255);
1039                //Color color = new Color(100,100,150,255);
1040                currAlpha = alphaMult;
1041                currLayerColorSet = false;
1042                
1043                Color color = getRenderColor();
1044                GL11.glColor4ub((byte)color.getRed(),
1045                                (byte)color.getGreen(),
1046                                (byte)color.getBlue(),
1047                                (byte)((float)color.getAlpha() * alphaMult));
1048        }
1049        
1050        
1051        public void renderOnRadar(Vector2f radarCenter, float factor, float alphaMult) {
1052                currLayer = null;
1053                //if (true) return;
1054                
1055                float radius = Global.getSettings().getFloat("campaignRadarRadius") + 2000;
1056                
1057                GL11.glPushMatrix();
1058                GL11.glTranslatef(-radarCenter.x * factor, -radarCenter.y * factor, 0);
1059                //super.renderOnMap(factor, alphaMult);
1060                
1061                preMapRender(alphaMult);
1062                //GL11.glDisable(GL11.GL_TEXTURE_2D);
1063                
1064                int samples = 10;
1065                
1066                float x = this.entity.getLocation().x;
1067                float y = this.entity.getLocation().y;
1068                float size = getTileSize();
1069                float renderSize = getTileRenderSize();
1070                
1071                float w = tiles.length * size;
1072                float h = tiles[0].length * size;
1073                x -= w/2f;
1074                y -= h/2f;
1075                float extra = (renderSize - size) / 2f + 100f;
1076                
1077                float llx = radarCenter.x - radius;
1078                float lly = radarCenter.y - radius;
1079                float vw = radius * 2f;
1080                float vh = radius * 2f;
1081                
1082                if (llx > x + w + extra) {
1083                        GL11.glPopMatrix();
1084                        return;
1085                }
1086                if (lly > y + h + extra) {
1087                        GL11.glPopMatrix();
1088                        return;
1089                }
1090                if (llx + vw + extra < x) {
1091                        GL11.glPopMatrix();
1092                        return;
1093                }
1094                if (lly + vh + extra < y) {
1095                        GL11.glPopMatrix();
1096                        return;
1097                }
1098                
1099                float xStart = (int)((llx - x - extra) / size);
1100                if (xStart < 0) xStart = 0;
1101                float yStart = (int)((lly - y - extra) / size);
1102                if (yStart < 0) yStart = 0;
1103                
1104                float xEnd = (int)((llx + vw - x + extra) / size) + 1;
1105                if (xEnd >= tiles.length) xEnd = tiles.length - 1;
1106                float yEnd = (int)((lly + vw - y + extra) / size) + 1;
1107                if (yEnd >= tiles.length) yEnd = tiles[0].length - 1;
1108                
1109                xStart = (int) Math.floor(xStart / samples) * samples;
1110                xEnd = (int) Math.floor(xEnd / samples) * samples;
1111                yStart = (int) Math.ceil(yStart / samples) * samples;
1112                yEnd = (int) Math.ceil(yEnd / samples) * samples;
1113                
1114                mapTexture.bindTexture();
1115                GL11.glEnable(GL11.GL_TEXTURE_2D);
1116                renderSubArea(xStart, xEnd, yStart, yEnd, factor, samples, alphaMult);
1117                
1118                GL11.glPopMatrix();
1119        }
1120
1121        
1122        @Override
1123        public Color getRenderColor() {
1124                return Color.white;
1125                //return Misc.scaleColorOnly(Color.white, 0.67f);
1126        }
1127        
1128        @Override
1129        public boolean containsEntity(SectorEntityToken other) {
1130                //if (!isPreventedFromAffecting(other)) return false;
1131//              if (isPreventedFromAffecting(other)) {
1132//                      return !isInClouds(other);
1133//              }
1134                return true;
1135        }
1136        
1137        @Override
1138        public float getRenderRange() {
1139                return Float.MAX_VALUE;
1140        }
1141
1142        @Override
1143        public boolean containsPoint(Vector2f test, float r) {
1144                return true;
1145        }
1146        
1147        public float getAbyssalDepth(Vector2f loc) {
1148                return getAbyssPlugin().getAbyssalDepth(loc);
1149        }
1150        public float getAbyssalDepth(Vector2f loc, boolean uncapped) {
1151                return getAbyssPlugin().getAbyssalDepth(loc, uncapped);
1152        }
1153        public float getAbyssalDepth(SectorEntityToken other) {
1154                return getAbyssPlugin().getAbyssalDepth(other);
1155        }
1156        public float getAbyssalDepth(SectorEntityToken other, boolean uncapped) {
1157                return getAbyssPlugin().getAbyssalDepth(other, uncapped);
1158        }
1159        public boolean isInAbyss(SectorEntityToken other) {
1160                return getAbyssPlugin().isInAbyss(other);
1161        }
1162        
1163        public List<StarSystemAPI> getAbyssalSystems() {
1164                return getAbyssPlugin().getAbyssalSystems();
1165        }
1166        
1167        public boolean isInClouds(SectorEntityToken other) {
1168                if (other == null) return false;
1169                if (other.getContainingLocation() != this.entity.getContainingLocation()) return false;
1170                if (isPreventedFromAffecting(other)) return false;
1171                return super.containsPoint(other.getLocation(), other.getRadius());
1172        }
1173        
1174        public boolean isInClouds(Vector2f test, float r) {
1175                return super.containsPoint(test, r);
1176        }
1177
1178        public int [] getTilePreferStorm(Vector2f test, float r) {
1179                // tiles exist outside render range now
1180                //float dist = Misc.getDistance(this.entity.getLocation(), test) - r;
1181                //if (dist > getRenderRange()) return null;
1182                
1183                float x = this.entity.getLocation().x;
1184                float y = this.entity.getLocation().y;
1185                float size = getTileSize();
1186                float containsSize = getTileContainsSize();
1187                
1188                float w = tiles.length * size;
1189                float h = tiles[0].length * size;
1190
1191                x -= w/2f;
1192                y -= h/2f;
1193                
1194                float extra = (containsSize - size) / 2f;
1195                
1196                if (test.x + r + extra < x) return null;
1197                if (test.y + r + extra < y) return null;
1198                if (test.x > x + w + r + extra) return null;
1199                if (test.y > y + h + r + extra) return null;
1200                
1201                int xIndex = (int) ((test.x - x) / size);
1202                int yIndex = (int) ((test.y - y) / size);
1203                
1204                if (xIndex < 0) xIndex = 0;
1205                if (yIndex < 0) yIndex = 0;
1206                
1207                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
1208                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
1209                
1210                int [] found = null;
1211                for (float i = Math.max(0, xIndex - 1); i <= xIndex + 1 && i < tiles.length; i++) {
1212                        for (float j = Math.max(0, yIndex - 1); j <= yIndex + 1 && j < tiles[0].length; j++) {
1213                                int texIndex = tiles[(int) i][(int) j];
1214                                if (texIndex >= 0) {
1215                                        float tx = x + i * size + size/2f - containsSize/2f;
1216                                        float ty = y + j * size + size/2f - containsSize/2f;
1217                                         
1218                                        if (test.x + r < tx) continue;
1219                                        if (test.y + r < ty) continue;
1220                                        if (test.x > tx + containsSize + r) continue;
1221                                        if (test.y > ty + containsSize + r) continue;
1222                                        //return true;
1223                                        int [] curr = new int[] {(int)i, (int)j};
1224                                        //int val = auto.getCells()[(int) i][(int) j];
1225                                        //if (val == 1) {
1226                                        CellStateTracker cell = activeCells[(int) i][(int) j];
1227                                        if (cell != null && cell.isStorming()) {
1228                                                return curr;
1229                                        }
1230                                        if (found == null || (cell != null && cell.isSignaling())) {
1231                                                found = curr;
1232                                        }
1233                                }
1234                        }
1235                }
1236                return found;
1237        }
1238        public CellStateTracker getExactCellAt(Vector2f location) {
1239                int [] tile = getTile(location);
1240                CellStateTracker cell = null; 
1241                if (tile != null) {
1242                        cell = activeCells[tile[0]][tile[1]];
1243                }
1244                return cell;
1245        }
1246        
1247        public int [] getTile(Vector2f test) {
1248                float x = this.entity.getLocation().x;
1249                float y = this.entity.getLocation().y;
1250                float size = getTileSize();
1251                float containsSize = getTileContainsSize();
1252                
1253                float w = tiles.length * size;
1254                float h = tiles[0].length * size;
1255
1256                x -= w/2f;
1257                y -= h/2f;
1258                
1259                float extra = (containsSize - size) / 2f;
1260                
1261                if (test.x + extra < x) return null;
1262                if (test.y + extra < y) return null;
1263                if (test.x > x + w + extra) return null;
1264                if (test.y > y + h + extra) return null;
1265                
1266                int xIndex = (int) ((test.x - x) / size);
1267                int yIndex = (int) ((test.y - y) / size);
1268                
1269                if (xIndex < 0) xIndex = 0;
1270                if (yIndex < 0) yIndex = 0;
1271                
1272                if (xIndex >= tiles.length) xIndex = tiles.length - 1;
1273                if (yIndex >= tiles[0].length) yIndex = tiles[0].length - 1;
1274                
1275                return new int[] {xIndex, yIndex};
1276        }
1277        
1278        public LocationState getStateAt(SectorEntityToken entity, float extraRadius) {
1279                boolean inCloud = isInClouds(entity);
1280                int [] tile = getTilePreferStorm(entity.getLocation(), entity.getRadius() + extraRadius);
1281                CellStateTracker cell = null; 
1282                if (tile != null) {
1283                        cell = activeCells[tile[0]][tile[1]];
1284                }
1285                if (!inCloud) {
1286                        return LocationState.OPEN;
1287                } else if (cell == null || !cell.isStorming()) {
1288                        return LocationState.DEEP;
1289                } else { //if (cell.isStorming()) {
1290                        return LocationState.DEEP_STORM;
1291                }
1292        }
1293        
1294        public CellStateTracker getCellAt(Vector2f location, float radius) {
1295                int [] tile = getTilePreferStorm(location, radius);
1296                CellStateTracker cell = null; 
1297                if (tile != null) {
1298                        cell = activeCells[tile[0]][tile[1]];
1299                }
1300                return cell;
1301        }
1302        
1303        public CellStateTracker getCellAt(SectorEntityToken entity, float extraRadius) {
1304                int [] tile = getTilePreferStorm(entity.getLocation(), entity.getRadius() + extraRadius);
1305                CellStateTracker cell = null; 
1306                if (tile != null) {
1307                        cell = activeCells[tile[0]][tile[1]];
1308                }
1309                return cell;
1310        }
1311        
1312        @Override
1313        protected boolean shouldPlayLoopOne() {
1314                LocationState state = getStateAt(Global.getSector().getPlayerFleet(), getExtraSoundRadius());
1315                return super.shouldPlayLoopOne() && state == LocationState.OPEN;
1316        }
1317
1318        @Override
1319        protected boolean shouldPlayLoopTwo() {
1320                LocationState state = getStateAt(Global.getSector().getPlayerFleet(), getExtraSoundRadius());
1321                //System.out.println("Two: " + (super.shouldPlayLoopTwo() && state == LocationState.DEEP));
1322                return super.shouldPlayLoopTwo() && state == LocationState.DEEP;
1323        }
1324        
1325        @Override
1326        protected boolean shouldPlayLoopThree() {
1327                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
1328                return super.shouldPlayLoopThree() && isInAbyss(playerFleet);
1329        }
1330        
1331        @Override
1332        public float getProximitySoundFactor() {
1333                CampaignFleetAPI playerFleet = Global.getSector().getPlayerFleet();
1334                float depth = getAbyssalDepth(playerFleet);
1335                if (depth > 0f) {
1336                        return depth;
1337                }
1338                
1339                return super.getProximitySoundFactor();
1340        }
1341
1342        @Override
1343        protected boolean shouldPlayLoopFour() {
1344                LocationState state = getStateAt(Global.getSector().getPlayerFleet(), getExtraSoundRadius());
1345                //System.out.println("Four: " + (super.shouldPlayLoopFour() && state == LocationState.DEEP_STORM));
1346                return super.shouldPlayLoopFour() && state == LocationState.DEEP_STORM;
1347        }
1348
1349
1350        @Override
1351        public void applyEffect(SectorEntityToken entity, float days) {
1352                float depth = getAbyssalDepth(entity);
1353                if (depth > 0) {
1354                        if (abyssDarkSource == null) {
1355                                abyssDarkSource = entity.getContainingLocation().createToken(0, 0);
1356                                abyssDarkSource.addTag(Tags.AMBIENT_LS);
1357                        }
1358                        
1359                        Color from = this.entity.getLightColor();
1360                        if (from == null) from = Color.white;
1361                        Color c = Misc.interpolateColor(from, ABYSS_LIGHT_COLOR, depth);
1362                        entity.getMemoryWithoutUpdate().set(MemFlags.LIGHT_SOURCE_OVERRIDE, abyssDarkSource, 0.1f);
1363                        entity.getMemoryWithoutUpdate().set(MemFlags.LIGHT_SOURCE_COLOR_OVERRIDE, c, 0.1f);
1364                        
1365                        if (entity instanceof CampaignFleetAPI) {
1366                                CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
1367                                for (FleetMemberViewAPI view : fleet.getViews()) {
1368                                        //view.getContrailColor().shift(getModId(), Misc.zeroColor, 1f, 1f, depth * 0.25f);
1369                                        view.getContrailWidthMult().shift(getModId(), 0.5f, 1f, 1f, depth);
1370                                        view.getContrailDurMult().shift(getModId(), 1f + depth * 1f, 1f, 1f, depth);
1371                                        view.getEngineGlowSizeMult().shift(getModId(), 2f, 1f, 1f, 1f - depth * 0.5f);
1372                                        view.getEngineGlowColor().shift(getModId(), Color.black, 1f, 1f, depth * 0.5f);
1373                                }
1374                        }
1375                }
1376                
1377                if (entity instanceof CampaignFleetAPI) {
1378                        CampaignFleetAPI fleet = (CampaignFleetAPI) entity;
1379                        
1380                        boolean inAbyss = depth > 0;
1381                        boolean inCloud = isInClouds(fleet);
1382                        int [] tile = getTilePreferStorm(fleet.getLocation(), fleet.getRadius());
1383                        CellStateTracker cell = null; 
1384                        if (tile != null) {
1385                                cell = activeCells[tile[0]][tile[1]];
1386                        }
1387                        
1388//                      if (cell == null) {
1389//                              inCloud = false;
1390//                      }
1391                        
1392//                      fleet.getStats().addTemporaryModFlat(0.1f, getModId() + "_fuel",
1393//                                      "In hyperspace", FUEL_USE_FRACTION, 
1394//                                      fleet.getStats().getFuelUseHyperMult());
1395                        
1396                        if (inAbyss) {
1397                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_3",
1398                                                "In abyssal hyperspace", 1f - (1f - ABYSS_VISIBLITY_MULT) * depth, 
1399                                                fleet.getStats().getDetectedRangeMod());
1400                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_4",
1401                                                "In abyssal hyperspace", 1f - (1f - ABYSS_SENSOR_RANGE_MULT) * depth, 
1402                                                fleet.getStats().getSensorRangeMod());
1403                                
1404                                //ABYSS_NAVIGATION_EFFECT = 0.25f;
1405                                float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
1406                                //skillMod = 1f;
1407                                skillMod = skillMod + (1f - skillMod) * (1f - ABYSS_NAVIGATION_EFFECT);
1408                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_5",
1409                                                "In abyssal hyperspace", 1f - (1f - ABYSS_BURN_MULT) * depth * skillMod, 
1410                                                fleet.getStats().getFleetwideMaxBurnMod());
1411                        }
1412                        
1413                        
1414                        if ((!inCloud && !inAbyss) || fleet.isInHyperspaceTransition()) {
1415                                // open, do nothing
1416                        //} else if (cell == null || !cell.isStorming()) {
1417                        } else if (inCloud) {
1418                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_1",
1419                                                "In deep hyperspace", VISIBLITY_MULT, 
1420                                                fleet.getStats().getDetectedRangeMod());
1421        
1422                                //float penalty = getBurnPenalty(fleet);
1423                                float penalty = Misc.getBurnMultForTerrain(fleet);
1424                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_2",
1425                                                "In deep hyperspace", penalty, 
1426                                                fleet.getStats().getFleetwideMaxBurnMod());
1427                                if (cell != null && cell.isSignaling() && cell.signal < 0.2f) {
1428                                        cell.signal = 0; // go to storm as soon as a fleet enters, if it's close to storming already
1429                                }
1430                                if (cell != null && cell.isStorming() && !Misc.isSlowMoving(fleet) && fleet.getBattle() == null) {
1431                                        // storm
1432                                        if (STORM_SENSOR_RANGE_MULT != 1) {
1433                                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_storm_sensor",
1434                                                                "In deep hyperspace (storm)", STORM_SENSOR_RANGE_MULT, 
1435                                                                fleet.getStats().getSensorRangeMod());
1436                                        }
1437                                        
1438                                        if (STORM_VISIBILITY_FLAT != 0) {
1439                                                fleet.getStats().addTemporaryModFlat(0.1f, getModId() + "_storm_visibility",
1440                                                                "In deep hyperspace (storm)", STORM_VISIBILITY_FLAT,
1441                                                                fleet.getStats().getDetectedRangeMod());
1442                                        }
1443                                        
1444                                        if (STORM_SPEED_MULT != 1) {
1445                                                fleet.getStats().addTemporaryModMult(0.1f, getModId() + "_storm_speed",
1446                                                                "In deep hyperspace (storm)", getAdjustedSpeedMult(fleet, STORM_SPEED_MULT), 
1447                                                                fleet.getStats().getFleetwideMaxBurnMod());                             
1448                                        }
1449                                        applyStormStrikes(cell, fleet, days);
1450                                }
1451                        }
1452                }
1453        }
1454        
1455        protected void applyStormStrikes(CellStateTracker cell, CampaignFleetAPI fleet, float days) {
1456                
1457                if (cell.flicker != null && cell.flicker.getWait() > 0) {
1458                        cell.flicker.setNumBursts(0);
1459                        cell.flicker.setWait(0);
1460                        cell.flicker.newBurst();
1461                }
1462                
1463                if (cell.flicker == null || !cell.flicker.isPeakFrame()) return;
1464
1465                
1466                fleet.addScript(new HyperStormBoost(cell, fleet));
1467                
1468                String key = STORM_STRIKE_TIMEOUT_KEY;
1469                MemoryAPI mem = fleet.getMemoryWithoutUpdate();
1470                if (mem.contains(key)) return;
1471                //boolean canDamage = !mem.contains(key);
1472                mem.set(key, true, (float) (STORM_MIN_TIMEOUT + (STORM_MAX_TIMEOUT - STORM_MIN_TIMEOUT) * Math.random()));
1473                
1474                //if ((float) Math.random() > STORM_STRIKE_CHANCE && false) return;
1475                
1476                List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
1477                if (members.isEmpty()) return;
1478                
1479                float totalValue = 0;
1480                for (FleetMemberAPI member : members) {
1481                        totalValue += member.getStats().getSuppliesToRecover().getModifiedValue();
1482                }
1483                if (totalValue <= 0) return;
1484                
1485                float strikeValue = totalValue * STORM_DAMAGE_FRACTION * (0.5f + (float) Math.random() * 0.5f);
1486                
1487//              int index = Misc.random.nextInt(members.size());
1488//              FleetMemberAPI member = members.get(index);
1489                
1490                float ebCostThresholdMult = 4f;
1491                
1492                WeightedRandomPicker<FleetMemberAPI> picker = new WeightedRandomPicker<FleetMemberAPI>();
1493                WeightedRandomPicker<FleetMemberAPI> preferNotTo = new WeightedRandomPicker<FleetMemberAPI>();
1494                for (FleetMemberAPI member : members) {
1495                        float w = 1f;
1496                        if (member.isMothballed()) w *= 0.1f;
1497                        
1498                        
1499                        float ebCost = EmergencyBurnAbility.getCRCost(member, fleet);
1500                        if (ebCost * ebCostThresholdMult > member.getRepairTracker().getCR()) {
1501                                preferNotTo.add(member, w);
1502                        } else {
1503                                picker.add(member, w);
1504                        }
1505                }
1506                if (picker.isEmpty()) {
1507                        picker.addAll(preferNotTo);
1508                }
1509                
1510                FleetMemberAPI member = picker.pick();
1511                if (member == null) return;
1512                
1513                float crPerDep = member.getDeployCost();
1514                float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue();
1515                if (suppliesPerDep <= 0 || crPerDep <= 0) return;
1516                
1517                float strikeDamage = crPerDep * strikeValue / suppliesPerDep;
1518                if (strikeDamage < STORM_MIN_STRIKE_DAMAGE) strikeDamage = STORM_MIN_STRIKE_DAMAGE;
1519                
1520                float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT);
1521                strikeDamage *= resistance;
1522                
1523                if (strikeDamage > STORM_MAX_STRIKE_DAMAGE) strikeDamage = STORM_MAX_STRIKE_DAMAGE;
1524                
1525//              if (fleet.isPlayerFleet()) {
1526//                      System.out.println("wefw34gerg");
1527//              }
1528                
1529                float currCR = member.getRepairTracker().getBaseCR();
1530                float crDamage = Math.min(currCR, strikeDamage);
1531                
1532                float ebCost = EmergencyBurnAbility.getCRCost(member, fleet);
1533                if (currCR >= ebCost * ebCostThresholdMult) {
1534                        crDamage = Math.min(currCR - ebCost * 1.5f, crDamage);
1535                }
1536                
1537                if (crDamage > 0) {
1538                        member.getRepairTracker().applyCREvent(-crDamage, "hyperstorm", "Hyperspace storm strike");
1539                }
1540                
1541                float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating());
1542                hitStrength *= strikeDamage / crPerDep;
1543                if (hitStrength > 0) {
1544                        member.getStatus().applyDamage(hitStrength);
1545                        if (member.getStatus().getHullFraction() < 0.01f) {
1546                                member.getStatus().setHullFraction(0.01f);
1547                        }
1548                }
1549                
1550                if (fleet.isPlayerFleet()) {
1551                        String verb = "suffers";
1552                        Color c = Misc.getNegativeHighlightColor();
1553                        if (hitStrength <= 0) {
1554                                verb = "avoids";
1555                                //c = Misc.getPositiveHighlightColor();
1556                                c = Misc.getTextColor();
1557                        }
1558                        Global.getSector().getCampaignUI().addMessage(
1559                                        member.getShipName() + " " + verb + " damage from the storm", c);
1560                        
1561                        Global.getSector().getCampaignUI().showHelpPopupIfPossible("chmHyperStorm");
1562                }
1563        }
1564
1565        public String getStormSoundId() {
1566                return stormSoundId;
1567        }
1568
1569        public boolean hasTooltip() {
1570                return true;
1571        }
1572        
1573        public String getNameForTooltip() {
1574                return getTerrainName();
1575        }
1576        
1577        public void createTooltip(TooltipMakerAPI tooltip, boolean expanded) {
1578                float pad = 10f;
1579                float small = 5f;
1580                Color gray = Misc.getGrayColor();
1581                Color highlight = Misc.getHighlightColor();
1582                Color fuel = Global.getSettings().getColor("progressBarFuelColor");
1583                Color bad = Misc.getNegativeHighlightColor();
1584                
1585                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
1586                boolean inCloud = isInClouds(player);
1587                float depth = getAbyssalDepth(player);
1588                boolean inAbyss = depth > 0f;
1589                int [] tile = getTilePreferStorm(player.getLocation(), player.getRadius());
1590                CellStateTracker cell = null; 
1591                if (tile != null) {
1592                        cell = activeCells[tile[0]][tile[1]];
1593                }
1594//              if (cell == null) {
1595//                      inCloud = false;
1596//              }
1597                
1598                tooltip.addTitle(getTerrainName());
1599                if (inAbyss) {
1600                        //abyssal
1601                        tooltip.addPara(Global.getSettings().getDescription(getTerrainId() + "_abyssal", Type.TERRAIN).getText1(), pad);
1602                } else if (!inCloud) {
1603                        // open
1604                        tooltip.addPara(Global.getSettings().getDescription(getTerrainId() + "_normal", Type.TERRAIN).getText1(), pad);
1605                } else if (cell == null || !cell.isStorming()) {
1606                        // deep
1607                        tooltip.addPara(Global.getSettings().getDescription(getTerrainId() + "_deep", Type.TERRAIN).getText1(), pad);
1608                } else if (cell.isStorming()) {
1609                        // storm
1610                        tooltip.addPara(Global.getSettings().getDescription(getTerrainId() + "_storm", Type.TERRAIN).getText1(), pad);
1611                }
1612                
1613                String fuelCost = Misc.getRoundedValueMaxOneAfterDecimal(player.getLogistics().getFuelCostPerLightYear());
1614                
1615                float nextPad = pad;
1616                if (expanded) {
1617                        tooltip.addSectionHeading("Travel", Alignment.MID, pad);
1618                        nextPad = small;
1619                }
1620                tooltip.addPara("Traveling through hyperspace consumes fuel based on the distance travelled. " +
1621                                "Your fleet requires %s fuel per light-year.*", nextPad,
1622                                highlight, fuelCost);
1623
1624                if (inAbyss) {
1625                        tooltip.addPara("Reduces the sensor range and sensor profile of fleets inside it by %s. "
1626                                        + "Also reduces the maximum burn level by %s. The reduction is gradual and based "
1627                                        + "on the \"depth\" the fleet has reached.",
1628                                        pad,
1629                                        highlight,
1630                                        "" + (int) Math.round((1f - ABYSS_VISIBLITY_MULT) * 100f) + "%",
1631                                        "" + (int) Math.round((1f - ABYSS_BURN_MULT) * 100f) + "%"
1632                        );
1633                        if (ABYSS_NAVIGATION_EFFECT <= 0) {
1634                                tooltip.addPara("Skill in navigation is of little use, and does not provide its "
1635                                                + "normal benefit in countering terrain-specific maximum burn penalties.", pad,
1636                                                highlight,
1637                                                "" + Math.round(ABYSS_NAVIGATION_EFFECT * 100f) + "%");                         
1638                        } else {
1639                                tooltip.addPara("Skill in navigation is of limited use, and only provides %s of its "
1640                                                + "normal benefit in countering the maximum burn penalty.", pad,
1641                                                highlight,
1642                                                "" + Math.round(ABYSS_NAVIGATION_EFFECT * 100f) + "%");
1643                        }
1644                } else if (inCloud) {
1645                        tooltip.addPara("Reduces the range at which fleets inside can be detected by %s.",
1646                                        pad,
1647                                        highlight,
1648                                        "" + (int) Math.round((1f - VISIBLITY_MULT) * 100f) + "%"
1649                        );
1650                        
1651                        tooltip.addPara("Reduces the speed of fleets inside by up to %s. Larger fleets are slowed down more.", 
1652                                        nextPad,
1653                                        highlight, 
1654                                        "" + (int) Math.round((Misc.BURN_PENALTY_MULT) * 100f) + "%"
1655                        );
1656                
1657                        float penalty = Misc.getBurnMultForTerrain(Global.getSector().getPlayerFleet());
1658                        tooltip.addPara("Your fleet's speed is reduced by %s.", pad,
1659                                        highlight,
1660                                        "" + (int) Math.round((1f - penalty) * 100f) + "%"
1661                                        //Strings.X + penaltyStr
1662                        );
1663                        
1664                        tooltip.addSectionHeading("Hyperspace storms", Alignment.MID, pad);
1665                        
1666                        Color stormDescColor = Misc.getTextColor();
1667                        if (cell != null && cell.isStorming()) {
1668                                stormDescColor = bad;
1669                        }
1670                        tooltip.addPara("Being caught in a storm causes storm strikes to damage ships " +
1671                                                    "and reduce their combat readiness. " +
1672                                                    "Larger fleets attract more damaging strikes.", stormDescColor, pad);
1673                        
1674                        tooltip.addPara("In addition, storm strikes toss the fleet's drive bubble about " +
1675                                                    "with great violence, often causing a loss of control. " +
1676                                                    "Some commanders are known to use these to gain additional " +
1677                                                    "speed, and to save fuel - a practice known as \"storm riding\".", Misc.getTextColor(), pad);
1678                        
1679                        tooltip.addPara("\"Slow-moving\" fleets do not attract storm strikes.", Misc.getTextColor(), pad);
1680                }
1681                
1682                if (expanded) {
1683                        tooltip.addSectionHeading("Combat", Alignment.MID, pad);
1684//                      if (inCloud) {
1685//                              tooltip.addPara("Numerous patches of nebula-like hyperfragments present on the battlefield, slowing ships down to a percentage of their top speed.", small);
1686//                      } else {
1687//                              tooltip.addPara("No effect.", small);
1688//                      }
1689                        if (inAbyss) {
1690//                              public static float ABYSS_SHIP_SPEED_PENALTY = 20f;
1691//                              public static float ABYSS_MISSILE_SPEED_PENALTY = 20f;
1692                                tooltip.addPara("Reduces top speed of ships by up to %s, and the top speed and range "
1693                                                + "of missiles by up to %s.", pad,
1694                                                highlight,
1695                                                "" + (int) Math.round(BattleCreationPluginImpl.ABYSS_SHIP_SPEED_PENALTY) + "%",
1696                                                "" + (int) Math.round(BattleCreationPluginImpl.ABYSS_MISSILE_SPEED_PENALTY) + "%"
1697                                );
1698                        } else {
1699                                tooltip.addPara("No combat effects.", nextPad);
1700                        }
1701                }
1702                
1703                tooltip.addPara("*1 light-year = 2000 units = 1 map grid cell", gray, pad);
1704        }
1705        
1706        protected float getAdjustedSpeedMult(CampaignFleetAPI fleet, float baseMult) {
1707                float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
1708                if (skillMod < 0) skillMod = 0;
1709                if (skillMod > 1) skillMod = 1;
1710                
1711                float penalty = 1f - baseMult;
1712                penalty *= skillMod;
1713                
1714                return 1f - penalty;
1715        }
1716        
1717        public boolean isTooltipExpandable() {
1718//              CampaignFleetAPI player = Global.getSector().getPlayerFleet();
1719//              boolean inCloud = isInClouds(player);
1720//              return inCloud;
1721                return true;
1722        }
1723        
1724        public float getTooltipWidth() {
1725                return 375f;
1726        }
1727        
1728        public String getTerrainName() {
1729                CampaignFleetAPI player = Global.getSector().getPlayerFleet();
1730                boolean inCloud = isInClouds(player);
1731                boolean inAbyss = isInAbyss(player);
1732                int [] tile = getTilePreferStorm(player.getLocation(), player.getRadius());
1733                int val = 0;
1734                CellStateTracker cell = null;
1735                if (tile != null) {
1736                        cell = activeCells[tile[0]][tile[1]];
1737                }
1738                
1739                String name = "Hyperspace";
1740                if (inAbyss) {
1741                        name = "Hyperspace (Abyssal)";
1742                } else if (!inCloud) {
1743                } else if (cell == null || !cell.isStorming()) {
1744                        name = "Hyperspace (Deep)";
1745                } else if (cell.isStorming()) {
1746                        name = "Hyperspace (Storm)";
1747                }
1748                return name;
1749        }
1750        
1751        
1752        public String getEffectCategory() {
1753                return "dark-hyper-like";
1754        }
1755
1756        public boolean hasAIFlag(Object flag) {
1757                return flag == TerrainAIFlags.REDUCES_SENSOR_RANGE;
1758        }
1759        
1760        public boolean hasAIFlag(Object flag, CampaignFleetAPI fleet) {
1761                if (flag == TerrainAIFlags.DANGEROUS_UNLESS_GO_SLOW) {
1762                        int [] tile = getTilePreferStorm(fleet.getLocation(), fleet.getRadius() + 100f);
1763                        CellStateTracker cell = null; 
1764                        if (tile != null) {
1765                                cell = activeCells[tile[0]][tile[1]];
1766                        }
1767                        if (cell != null) {
1768                                return cell.isStorming() || cell.isSignaling();
1769                        }
1770                }
1771                return hasAIFlag(flag);
1772        }
1773        
1774        @Override
1775        public int getNumMapSamples() {
1776                return 10;
1777        }
1778        
1779        public boolean isUseSampleCache() {
1780                return true;
1781        }
1782        
1783        
1784//      public static float getBurnPenalty(CampaignFleetAPI fleet) {
1785//              AsteroidBeltTerrainPlugin.getFleetRadiusTerrainEffectMult(fleet);
1786//              
1787//              float min = Global.getSettings().getBaseFleetSelectionRadius() + Global.getSettings().getFleetSelectionRadiusPerUnitSize();
1788//              float max = Global.getSettings().getMaxFleetSelectionRadius();
1789//              float radius = fleet.getRadius();
1790//
1791//              float penalty = 1f - (radius - min) / (max - min);
1792//              if (penalty > 1) penalty = 1;
1793//              if (penalty < 0) penalty = 0;
1794//              penalty = MIN_BURN_PENALTY + penalty * BURN_PENALTY_RANGE;
1795//
1796//              float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
1797//              penalty *= skillMod;
1798//              
1799//              return penalty;
1800//      }
1801
1802        public static void main(String[] args) {
1803                System.out.println(1.5f - (int) 1.5f);
1804        }
1805        
1806        
1807        public void turnOffStorms(Vector2f loc, float radius) {
1808                setTileState(loc, radius, CellState.OFF, -1f, -1f);
1809        }
1810        
1811        public void setTileState(Vector2f loc, float radius, CellState state, float waitDur, float signalDur) {
1812                setTileState(loc, radius, state, waitDur, signalDur, signalDur);
1813        }
1814        public void setTileState(Vector2f loc, float radius, CellState state, float waitDur, float minSignalDur, float maxSignalDur) {
1815                float x = this.entity.getLocation().x;
1816                float y = this.entity.getLocation().y;
1817                float size = getTileSize();
1818                float containsSize = getTileContainsSize();
1819                
1820                float w = tiles.length * size;
1821                float h = tiles[0].length * size;
1822
1823                x -= w/2f;
1824                y -= h/2f;
1825                
1826                float extra = (containsSize - size) / 2f;
1827                
1828                if (loc.x + radius + extra < x) return;
1829                if (loc.y + radius + extra < y) return;
1830                if (loc.x > x + w + radius + extra) return;
1831                if (loc.y > y + h + radius + extra) return;
1832                
1833                int xMin = (int) ((loc.x - x - radius) / size);
1834                int yMin = (int) ((loc.y - y - radius) / size);
1835                int xMax = (int) ((loc.x - x + radius) / size);
1836                int yMax = (int) ((loc.y - y + radius) / size);
1837                
1838                if (xMin < 0) xMin = 0;
1839                if (yMin < 0) yMin = 0;
1840                if (xMin >= tiles.length) xMin = tiles.length - 1;
1841                if (yMin >= tiles[0].length) yMin= tiles[0].length - 1;
1842                
1843                if (xMax < 0) xMax = 0;
1844                if (yMax < 0) yMax = 0;
1845                if (xMax >= tiles.length) xMax = tiles.length - 1;
1846                if (yMax >= tiles[0].length) yMax = tiles[0].length - 1;
1847                
1848                for (int i = xMin; i <= xMax; i++) {
1849                        for (int j = yMin; j <= yMax; j++) {
1850                                int texIndex = tiles[i][j];
1851                                if (texIndex >= 0) {
1852                                        float tx = x + i * size + size/2f - containsSize/2f;
1853                                        float ty = y + j * size + size/2f - containsSize/2f;
1854                                         
1855                                        float dist = Misc.getDistance(loc.x, loc.y, tx, ty);
1856                                        if (dist > radius) continue;
1857//                                      if (loc.x + radius < tx) continue;
1858//                                      if (loc.y + radius < ty) continue;
1859//                                      if (loc.x > tx + containsSize + radius) continue;
1860//                                      if (loc.y > ty + containsSize + radius) continue;
1861                                        //
1862                                        CellStateTracker cell = activeCells[i][j];
1863                                        float interval = auto.getInterval().getIntervalDuration();
1864                                        float wait = interval * 0f + interval * 1.5f * (float) Math.random();
1865                                        if (waitDur >= 0f) wait = waitDur;
1866                                        float signal = interval * 0.5f + interval * 0.5f * (float) Math.random();
1867                                        if (minSignalDur >= 0f) signal = minSignalDur + (maxSignalDur - minSignalDur) * (float) Math.random();
1868                                        
1869                                        wait *= 0.9f + (float) Math.random() * 0.2f;
1870                                        signal *= 0.9f + (float) Math.random() * 0.2f;
1871                                        
1872                                        if (cell == null && state != CellState.OFF) {
1873                                                cell = activeCells[i][j] = new CellStateTracker(i, j, wait, signal);
1874                                                cell.state = state;
1875                                        } else if (cell != null && state == CellState.OFF) {
1876                                                if (cell.state == CellState.STORM || 
1877                                                                cell.state == CellState.STORM_WANE ||
1878                                                                cell.state == CellState.SIGNAL) {
1879                                                        float dur = 0.1f;
1880                                                        if (wait >= 0f) {
1881                                                                dur *= wait;
1882                                                        }
1883                                                        if (cell.state == CellState.STORM_WANE) {
1884                                                                dur = Math.min(cell.wane, dur);
1885                                                        } else {
1886                                                                cell.maxWane = dur * 4f;
1887                                                        }
1888                                                        cell.wane = dur;
1889                                                        cell.state = CellState.STORM_WANE;
1890                                                } else {
1891                                                        activeCells[i][j] = null;
1892                                                }
1893                                        } else if (cell != null) {
1894                                                cell.state = state;
1895                                                if (state == CellState.WAIT) {
1896                                                        cell.wait = wait;
1897                                                }
1898                                                if (state == CellState.SIGNAL) {
1899                                                        //signal = Math.min(cell.signal, signal);
1900                                                        cell.signal = signal;
1901                                                        cell.maxSignal = signal;
1902                                                }
1903                                        }
1904                                        
1905                                }
1906                        }
1907                }
1908        }
1909}
1910
1911
1912
1913
1914
1915
1916
1917