1package com.fs.starfarer.api.impl.campaign.terrain;
4import java.util.ArrayList;
5import java.util.EnumSet;
7import java.util.Random;
9import org.lwjgl.opengl.GL11;
10import org.lwjgl.util.vector.Vector2f;
12import com.fs.starfarer.api.Global;
13import com.fs.starfarer.api.campaign.CampaignEngineLayers;
14import com.fs.starfarer.api.campaign.CampaignFleetAPI;
15import com.fs.starfarer.api.campaign.SectorEntityToken;
16import com.fs.starfarer.api.campaign.StarSystemAPI;
17import com.fs.starfarer.api.campaign.TerrainAIFlags;
18import com.fs.starfarer.api.campaign.rules.MemoryAPI;
19import com.fs.starfarer.api.combat.ViewportAPI;
20import com.fs.starfarer.api.fleet.FleetMemberAPI;
21import com.fs.starfarer.api.fleet.FleetMemberViewAPI;
22import com.fs.starfarer.api.graphics.SpriteAPI;
23import com.fs.starfarer.api.impl.campaign.abilities.EmergencyBurnAbility;
24import com.fs.starfarer.api.impl.campaign.ids.MemFlags;
25import com.fs.starfarer.api.impl.campaign.ids.Stats;
26import com.fs.starfarer.api.impl.campaign.ids.Tags;
27import com.fs.starfarer.api.impl.combat.BattleCreationPluginImpl;
28import com.fs.starfarer.api.loading.Description.Type;
29import com.fs.starfarer.api.ui.Alignment;
30import com.fs.starfarer.api.ui.TooltipMakerAPI;
31import com.fs.starfarer.api.util.FlickerUtilV2;
32import com.fs.starfarer.api.util.Misc;
33import com.fs.starfarer.api.util.WeightedRandomPicker;
86 public static final CampaignEngineLayers
FLASH = CampaignEngineLayers.TERRAIN_6A;
87 public static final CampaignEngineLayers
FLASH_OVER = CampaignEngineLayers.TERRAIN_9;
88 public static final CampaignEngineLayers
GLOW = CampaignEngineLayers.TERRAIN_8;
89 public static final CampaignEngineLayers
BASE = CampaignEngineLayers.TERRAIN_6;
91 public static final CampaignEngineLayers
SHIVER = CampaignEngineLayers.TERRAIN_9;
92 public static final CampaignEngineLayers
BASE_OVER = CampaignEngineLayers.TERRAIN_7;
94 public static enum LocationState {
100 public static enum CellState {
107 public static class CellStateTracker {
109 public CellState state;
110 public float wait, signal, wane;
111 private float maxSignal, maxWane;
112 public FlickerUtilV2 flicker =
null;
113 public CellStateTracker(
int i,
int j,
float wait,
float signal) {
117 this.signal = signal;
118 this.maxSignal = signal;
119 state = CellState.WAIT;
123 public void advance(
float days) {
124 if (state == CellState.OFF)
return;
126 if (state == CellState.WAIT && days > 0) {
131 state = CellState.SIGNAL;
135 if (state == CellState.SIGNAL && days > 0) {
140 state = CellState.STORM;
141 flicker =
new FlickerUtilV2();
146 if (state == CellState.STORM || state == CellState.STORM_WANE) {
150 if (state == CellState.STORM_WANE && days > 0) {
155 if (flicker ==
null || flicker.getBrightness() <= 0) {
156 state = CellState.OFF;
163 if (flicker !=
null) {
164 flicker.advance(days * 7f);
168 public float getSignalBrightness() {
169 if (state == CellState.SIGNAL) {
170 return 1f - signal / maxSignal;
176 if (state == CellState.STORM) {
179 if (state == CellState.STORM_WANE) {
180 float fade = maxWane * 0.25f;
184 return Math.max(0, wane / fade);
189 public void wane(
float wane) {
192 state = CellState.STORM_WANE;
195 public boolean isOff() {
196 return state == CellState.OFF;
199 public boolean isStorming() {
200 return state == CellState.STORM || state == CellState.STORM_WANE;
203 public boolean isWaning() {
204 return state == CellState.STORM_WANE;
207 public boolean isSignaling() {
208 return state == CellState.SIGNAL;
236 this.abyssPlugin = abyssChecker;
268 if (
tiles[i][j] < 0)
continue;
271 int val = cells[i][j];
274 if (val == 1 && curr ==
null) {
275 curr =
activeCells[i][j] =
new CellStateTracker(i, j,
276 interval * 0f + interval * 1.5f * (
float) Math.random(),
277 interval * 0.5f + interval * 0.5f * (
float) Math.random());
279 float dur = (float) Math.random() * interval * 2.5f;
295 if (playerFleet ==
null)
return;
297 Vector2f test =
new Vector2f();
298 if (playerFleet !=
null) {
299 test = playerFleet.getLocationInHyperspace();
302 float x = plugin.
entity.getLocation().x;
303 float y = plugin.
entity.getLocation().y;
306 float w = plugin.tiles.length * size;
307 float h = plugin.
tiles[0].length * size;
310 int xIndex = (int) ((test.x - x) / size);
311 int yIndex = (int) ((test.y - y) / size);
312 if (xIndex < 0) xIndex = 0;
313 if (yIndex < 0) yIndex = 0;
314 if (xIndex >= plugin.
tiles.length) xIndex = plugin.tiles.length - 1;
315 if (yIndex >= plugin.
tiles[0].length) yIndex = plugin.
tiles[0].length - 1;
317 int subgridSize = (int) ((10000 / size + 1) * 2f);
319 int minX = Math.max(0, xIndex - subgridSize/2);
320 int maxX = xIndex + subgridSize/2 ;
321 int minY = Math.max(0, yIndex - subgridSize/2);
322 int maxY = yIndex + subgridSize/2;
326 int pad = Math.max(plugin.
tiles.length, plugin.
tiles[0].length) * 2;
327 for (
int i = minX - pad; i <= maxX + pad && i < plugin.
tiles.length; i++) {
328 for (
int j = minY - pad; j <= maxY + pad && j < plugin.
tiles[0].length; j++) {
329 if (i < minX || j < minY || i > maxX || j > maxY) {
330 if (i >= 0 && j >= 0) {
338 Object writeReplace() {
343 copy.savedActiveCells =
new ArrayList<CellStateTracker>();
344 for (
int i = 0; i < copy.
activeCells.length; i++) {
345 for (
int j = 0; j < copy.
activeCells[0].length; j++) {
361 protected transient float []
temp =
new float[2];
363 if (
temp ==
null)
temp =
new float[2];
366 float speedFactor = 0.5f;
369 float min = -360f * (rand.nextFloat() * 3f + 1f) * Misc.RAD_PER_DEG;
370 float max = 360f * (rand.nextFloat() * 3f + 1f) * Misc.RAD_PER_DEG;
371 float rate = (30f + 70f * rand.nextFloat()) * Misc.RAD_PER_DEG;
373 float period = 2f * (max - min) / rate;
374 float progress = rand.nextFloat() + time / period;
375 progress = progress - (int) progress;
378 if (progress < 0.5f) {
379 theta = min + (max - min) * progress * 2f;
381 theta = min + (max - min) * (1f - progress) * 2f;
386 max = (width + height) * 0.025f;
390 period = 2f * (max - min) / rate;
391 progress = rand.nextFloat() + time / period;
392 progress = progress - (int) progress;
393 if (progress < 0.5f) {
394 radius = min + (max - min) * progress * 2f;
396 radius = min + (max - min) * (1f - progress) * 2f;
415 protected void renderQuad(
int i,
int j,
float x,
float y,
float width,
float height,
416 float texX,
float texY,
float texW,
float texH,
float angle) {
418 if (currLayer ==
null) {
419 super.renderQuad(i, j, x, y, width, height, texX, texY, texW, texH, angle);
424 if (currLayer ==
SHIVER)
return;
433 if (tracker !=
null) {
434 signal = tracker.getSignalBrightness();
436 if (currLayer ==
FLASH && (tracker ==
null || tracker.flicker ==
null || tracker.flicker.getBrightness() <= 0)) {
440 if (currLayer ==
GLOW && signal <= 0) {
454 long seed = (long) (x + y *
tiles.length) * 1000000;
459 Random rand =
new Random(seed);
460 angle = rand.nextFloat() * 360f;
464 if (playerFleet !=
null) {
467 color = Misc.scaleColorOnly(color, Math.max(0f, 1f - depth));
472 float theta1 = tr[0];
473 float radius1 = tr[1];
474 float sin1 = (float) Math.sin(theta1);
475 float cos1 = (float) Math.cos(theta1);
478 float theta2 = tr[0];
479 float radius2 = tr[1];
480 float sin2 = (float) Math.sin(theta2);
481 float cos2 = (float) Math.cos(theta2);
484 float theta3 = tr[0];
485 float radius3 = tr[1];
486 float sin3 = (float) Math.sin(theta3);
487 float cos3 = (float) Math.cos(theta3);
490 float theta4 = tr[0];
491 float radius4 = tr[1];
492 float sin4 = (float) Math.sin(theta4);
493 float cos4 = (float) Math.cos(theta4);
497 float vw = width / 2f;
498 float vh = height / 2f;
507 float cos = (float) Math.cos(angle * Misc.RAD_PER_DEG);
508 float sin = (float) Math.sin(angle * Misc.RAD_PER_DEG);
510 float shiverThreshold = 0.75f;
512 boolean shiver =
false;
513 boolean flicker =
false;
517 if (tracker !=
null && tracker.flicker !=
null && tracker.flicker.getBrightness() > 0) {
520 }
else if (currLayer ==
BASE) {
521 if (!currLayerColorSet) {
522 currLayerColorSet =
true;
523 GL11.glColor4ub((
byte)color.getRed(),
524 (byte)color.getGreen(),
525 (byte)color.getBlue(),
526 (byte)((
float)color.getAlpha() * currAlpha * 1f));
529 if (!currLayerColorSet) {
530 currLayerColorSet =
true;
531 GL11.glColor4ub((
byte)color.getRed(),
532 (byte)color.getGreen(),
533 (byte)color.getBlue(),
534 (byte)((
float)color.getAlpha() * currAlpha * 1f));
536 }
else if (currLayer ==
GLOW) {
537 if (tracker !=
null && signal > 0) {
538 GL11.glColor4ub((
byte)color.getRed(),
539 (byte)color.getGreen(),
540 (byte)color.getBlue(),
541 (byte)((
float)color.getAlpha() * currAlpha * 1f * signal));
545 }
else if (currLayer ==
SHIVER) {
546 if (signal > shiverThreshold && tracker !=
null && tracker.flicker ==
null) {
557 if (currLayer ==
GLOW) iter = 1;
558 for (
int k = 0; k < iter; k++) {
559 GL11.glTexCoord2f(texX, texY);
560 GL11.glVertex2f(cx + (-vw * cos + vh * sin) + sin1 * radius1,
561 cy + (-vw * sin - vh * cos) + cos1 * radius1);
563 GL11.glTexCoord2f(texX, texY + texH);
564 GL11.glVertex2f(cx + (-vw * cos - vh * sin) + sin2 * radius2,
565 cy + (-vw * sin + vh * cos) + cos2 * radius2);
567 GL11.glTexCoord2f(texX + texW, texY + texH);
568 GL11.glVertex2f(cx + (vw * cos - vh * sin) + sin3 * radius3,
569 cy + (vw * sin + vh * cos) + cos3 * radius3);
571 GL11.glTexCoord2f(texX + texW, texY);
572 GL11.glVertex2f(cx + (vw * cos + vh * sin) + sin4 * radius4,
573 cy + (vw * sin - vh * cos) + cos4 * radius4);
578 if (flicker || shiver) {
579 if (tracker ==
null)
return;
582 float shiverBrightness = (signal - shiverThreshold) / (1f - shiverThreshold);
583 if (shiverBrightness > 0.9f) {
584 shiverBrightness = (1f - shiverBrightness) / 0.1f;
586 shiverBrightness /= 0.9f;
593 float maxJitter = 0f + 30f;
596 rand.setSeed((
long) (x + y *
tiles.length) * 1000000 +
Global.
getSector().getClock().getTimestamp());
597 maxJitter = 0f + 30f * shiverBrightness * shiverBrightness;
599 rand.setSeed((
long) (x + y *
tiles.length) * 1000000 +
600 (
long) (tracker.flicker.getAngle() * 1000));
617 float alpha = currAlpha;
621 GL11.glColor4ub((
byte)color.getRed(),
622 (byte)color.getGreen(),
623 (byte)color.getBlue(),
625 (byte)((
float)color.getAlpha() * alpha * tracker.flicker.getBrightness() * 1f));
628 GL11.glColor4ub((
byte)color.getRed(),
629 (byte)color.getGreen(),
630 (byte)color.getBlue(),
631 (byte)((
float)color.getAlpha() * currAlpha * shiverBrightness * 0.075f));
635 if (shiver) maxIter = 5;
636 for (
int iter = 0; iter < maxIter; iter++) {
637 cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
638 cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
640 GL11.glTexCoord2f(texX, texY);
641 GL11.glVertex2f(cx + (-vw * cos + vh * sin) + sin1 * radius1,
642 cy + (-vw * sin - vh * cos) + cos1 * radius1);
644 cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
645 cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
647 GL11.glTexCoord2f(texX, texY + texH);
648 GL11.glVertex2f(cx + (-vw * cos - vh * sin) + sin2 * radius2,
649 cy + (-vw * sin + vh * cos) + cos2 * radius2);
651 cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
652 cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
654 GL11.glTexCoord2f(texX + texW, texY + texH);
655 GL11.glVertex2f(cx + (vw * cos - vh * sin) + sin3 * radius3,
656 cy + (vw * sin + vh * cos) + cos3 * radius3);
658 cx = ox + rand.nextFloat() * maxJitter - maxJitter/2f;
659 cy = oy + rand.nextFloat() * maxJitter - maxJitter/2f;
661 GL11.glTexCoord2f(texX + texW, texY);
662 GL11.glVertex2f(cx + (vw * cos + vh * sin) + sin4 * radius4,
663 cy + (vw * sin - vh * cos) + cos4 * radius4);
700 super.advance(amount);
732 Vector2f test =
new Vector2f();
733 if (playerFleet !=
null) {
734 test = playerFleet.getLocationInHyperspace();
737 if (
entity.getContainingLocation() !=
null &&
738 playerFleet.getContainingLocation() ==
entity.getContainingLocation() &&
742 entity.getContainingLocation().getBackgroundColorShifter().shift(
745 entity.getContainingLocation().getBackgroundParticleColorShifter().shift(
748 float gain = (float)
getSpec().getCustom().optDouble(
"gain", 0.75f);
749 float gainHF = (float)
getSpec().getCustom().optDouble(
"gainHF", 0.1f);
750 if (gain < 1f || gainHF < 1f) {
752 Math.max(0f, 1f - (1f - gain) * depth),
753 Math.max(0f, 1f - (1f - gainHF) * depth));
762 float x = this.
entity.getLocation().x;
763 float y = this.
entity.getLocation().y;
766 float w = tiles.length * size;
767 float h =
tiles[0].length * size;
770 int xIndex = (int) ((test.x - x) / size);
771 int yIndex = (int) ((test.y - y) / size);
772 if (xIndex < 0) xIndex = 0;
773 if (yIndex < 0) yIndex = 0;
774 if (xIndex >=
tiles.length) xIndex = tiles.length - 1;
775 if (yIndex >=
tiles[0].length) yIndex =
tiles[0].length - 1;
778 float baseSubgridDist = 10000f;
780 int subgridSize = (int) ((subgridDist / size + 1) * 2f);
782 int minX = Math.max(0, xIndex - subgridSize/2);
783 int maxX = xIndex + subgridSize/2 ;
784 int minY = Math.max(0, yIndex - subgridSize/2);
785 int maxY = yIndex + subgridSize/2;
787 int baseSubgridSize = (int) ((baseSubgridDist / size + 1) * 2f);
789 int baseMinX = Math.max(0, xIndex - baseSubgridSize/2);
790 int baseMaxX = xIndex + baseSubgridSize/2 ;
791 int baseMinY = Math.max(0, yIndex - baseSubgridSize/2);
792 int baseMaxY = yIndex + baseSubgridSize/2;
797 for (
int i = minX - pad; i <= maxX + pad && i <
tiles.length; i++) {
798 for (
int j = minY - pad; j <= maxY + pad && j <
tiles[0].length; j++) {
799 if (i < minX || j < minY || i > maxX || j > maxY) {
800 if (i >= 0 && j >= 0) {
807 for (
int i = minX; i <= maxX && i <
tiles.length; i++) {
808 for (
int j = minY; j <= maxY && j <
tiles[0].length; j++) {
811 if (
tiles[i][j] < 0)
continue;
814 int val = cells[i][j];
817 if (val == 1 && curr ==
null) {
818 curr =
activeCells[i][j] =
new CellStateTracker(i, j,
819 interval * 0f + interval * 1.5f * (
float) Math.random(),
820 interval * 0.5f + interval * 0.5f * (
float) Math.random());
826 if (val != 1 && curr.isStorming() && !curr.isWaning()) {
828 curr.wane(interval * 0.5f + interval * 0.5f * (
float) Math.random());
834 if (i < baseMinX || j < baseMinY || i > baseMaxX || j > baseMaxY) {
838 curr.advance(days * timeMult);
855 if (playerFleet.getContainingLocation() !=
entity.getContainingLocation())
return;
857 Vector2f test = playerFleet.getLocation();
859 float x = this.
entity.getLocation().x;
860 float y = this.
entity.getLocation().y;
863 float w = tiles.length * size;
864 float h =
tiles[0].length * size;
869 int xIndex = (int) ((test.x - x) / size);
870 int yIndex = (int) ((test.y - y) / size);
872 if (xIndex < 0) xIndex = 0;
873 if (yIndex < 0) yIndex = 0;
875 if (xIndex >=
tiles.length) xIndex = tiles.length - 1;
876 if (yIndex >=
tiles[0].length) yIndex =
tiles[0].length - 1;
880 for (
float i = Math.max(0, xIndex - subgridSize/2); i <= xIndex + subgridSize/2 && i <
tiles.length; i++) {
881 for (
float j = Math.max(0, yIndex - subgridSize/2); j <= yIndex + subgridSize/2 && j <
tiles[0].length; j++) {
882 int texIndex =
tiles[(int) i][(
int) j];
884 float tcx = x + i * size + size/2f;
885 float tcy = y + j * size + size/2f;
886 Vector2f tileLoc =
new Vector2f(tcx, tcy);
888 CellStateTracker curr =
activeCells[(int)i][(
int)j];
889 if (curr ==
null || curr.flicker ==
null || !curr.isStorming() || !curr.flicker.isPeakFrame() || curr.flicker.getNumBursts() > 1)
continue;
891 float dist = Misc.getDistance(test, tileLoc);
897 volumeMult = (float) Math.sqrt(volumeMult);
898 if (volumeMult <= 0)
continue;
953 private transient CampaignEngineLayers currLayer =
null;
954 private transient boolean currLayerColorSet =
false;
955 private transient float currAlpha = 1f;
956 public void render(CampaignEngineLayers layer, ViewportAPI viewport) {
960 super.render(layer, viewport);
967 super.renderOnMap(factor, alphaMult);
992 float startRow,
float endRow,
float factor,
int samples,
994 super.renderSubArea(startColumn, endColumn, startRow, endRow, factor, samples, alphaMult);
998 public void preRender(CampaignEngineLayers layer,
float alphaMult) {
999 GL11.glEnable(GL11.GL_BLEND);
1004 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
1008 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
1010 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
1022 currAlpha = alphaMult;
1023 currLayerColorSet =
false;
1033 GL11.glEnable(GL11.GL_BLEND);
1034 GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
1039 currAlpha = alphaMult;
1040 currLayerColorSet =
false;
1043 GL11.glColor4ub((
byte)color.getRed(),
1044 (
byte)color.getGreen(),
1045 (
byte)color.getBlue(),
1046 (
byte)((
float)color.getAlpha() * alphaMult));
1050 public void renderOnRadar(Vector2f radarCenter,
float factor,
float alphaMult) {
1056 GL11.glPushMatrix();
1057 GL11.glTranslatef(-radarCenter.x * factor, -radarCenter.y * factor, 0);
1065 float x = this.
entity.getLocation().x;
1066 float y = this.
entity.getLocation().y;
1070 float w = tiles.length * size;
1071 float h =
tiles[0].length * size;
1074 float extra = (renderSize - size) / 2f + 100f;
1076 float llx = radarCenter.x - radius;
1077 float lly = radarCenter.y - radius;
1078 float vw = radius * 2f;
1079 float vh = radius * 2f;
1081 if (llx > x + w + extra) {
1085 if (lly > y + h + extra) {
1089 if (llx + vw + extra < x) {
1093 if (lly + vh + extra < y) {
1098 float xStart = (int)((llx - x - extra) / size);
1099 if (xStart < 0) xStart = 0;
1100 float yStart = (int)((lly - y - extra) / size);
1101 if (yStart < 0) yStart = 0;
1103 float xEnd = (int)((llx + vw - x + extra) / size) + 1;
1104 if (xEnd >=
tiles.length) xEnd = tiles.length - 1;
1105 float yEnd = (int)((lly + vw - y + extra) / size) + 1;
1106 if (yEnd >=
tiles.length) yEnd =
tiles[0].length - 1;
1108 xStart = (int) Math.floor(xStart / samples) * samples;
1109 xEnd = (int) Math.floor(xEnd / samples) * samples;
1110 yStart = (int) Math.ceil(yStart / samples) * samples;
1111 yEnd = (int) Math.ceil(yEnd / samples) * samples;
1114 GL11.glEnable(GL11.GL_TEXTURE_2D);
1115 renderSubArea(xStart, xEnd, yStart, yEnd, factor, samples, alphaMult);
1156 if (other.getContainingLocation() !=
this.entity.getContainingLocation())
return false;
1158 return super.containsPoint(other.getLocation(), other.getRadius());
1162 return super.containsPoint(test, r);
1170 float x = this.
entity.getLocation().x;
1171 float y = this.
entity.getLocation().y;
1175 float w = tiles.length * size;
1176 float h =
tiles[0].length * size;
1181 float extra = (containsSize - size) / 2f;
1183 if (test.x + r + extra < x)
return null;
1184 if (test.y + r + extra < y)
return null;
1185 if (test.x > x + w + r + extra)
return null;
1186 if (test.y > y + h + r + extra)
return null;
1188 int xIndex = (int) ((test.x - x) / size);
1189 int yIndex = (int) ((test.y - y) / size);
1191 if (xIndex < 0) xIndex = 0;
1192 if (yIndex < 0) yIndex = 0;
1194 if (xIndex >=
tiles.length) xIndex = tiles.length - 1;
1195 if (yIndex >=
tiles[0].length) yIndex =
tiles[0].length - 1;
1197 int [] found =
null;
1198 for (
float i = Math.max(0, xIndex - 1); i <= xIndex + 1 && i <
tiles.length; i++) {
1199 for (
float j = Math.max(0, yIndex - 1); j <= yIndex + 1 && j <
tiles[0].length; j++) {
1200 int texIndex =
tiles[(int) i][(
int) j];
1201 if (texIndex >= 0) {
1202 float tx = x + i * size + size/2f - containsSize/2f;
1203 float ty = y + j * size + size/2f - containsSize/2f;
1205 if (test.x + r < tx)
continue;
1206 if (test.y + r < ty)
continue;
1207 if (test.x > tx + containsSize + r)
continue;
1208 if (test.y > ty + containsSize + r)
continue;
1210 int [] curr =
new int[] {(int)i, (
int)j};
1213 CellStateTracker cell =
activeCells[(int) i][(
int) j];
1214 if (cell !=
null && cell.isStorming()) {
1217 if (found ==
null || (cell !=
null && cell.isSignaling())) {
1226 int [] tile =
getTile(location);
1227 CellStateTracker cell =
null;
1235 float x = this.
entity.getLocation().x;
1236 float y = this.
entity.getLocation().y;
1240 float w = tiles.length * size;
1241 float h =
tiles[0].length * size;
1246 float extra = (containsSize - size) / 2f;
1248 if (test.x + extra < x)
return null;
1249 if (test.y + extra < y)
return null;
1250 if (test.x > x + w + extra)
return null;
1251 if (test.y > y + h + extra)
return null;
1253 int xIndex = (int) ((test.x - x) / size);
1254 int yIndex = (int) ((test.y - y) / size);
1256 if (xIndex < 0) xIndex = 0;
1257 if (yIndex < 0) yIndex = 0;
1259 if (xIndex >=
tiles.length) xIndex = tiles.length - 1;
1260 if (yIndex >=
tiles[0].length) yIndex =
tiles[0].length - 1;
1262 return new int[] {xIndex, yIndex};
1268 CellStateTracker cell =
null;
1273 return LocationState.OPEN;
1274 }
else if (cell ==
null || !cell.isStorming()) {
1275 return LocationState.DEEP;
1277 return LocationState.DEEP_STORM;
1281 public CellStateTracker
getCellAt(Vector2f location,
float radius) {
1283 CellStateTracker cell =
null;
1292 CellStateTracker cell =
null;
1302 return super.shouldPlayLoopOne() && state == LocationState.OPEN;
1309 return super.shouldPlayLoopTwo() && state == LocationState.DEEP;
1315 return super.shouldPlayLoopThree() &&
isInAbyss(playerFleet);
1326 return super.getProximitySoundFactor();
1333 return super.shouldPlayLoopFour() && state == LocationState.DEEP_STORM;
1346 Color from = this.entity.getLightColor();
1347 if (from ==
null) from = Color.white;
1350 entity.getMemoryWithoutUpdate().set(MemFlags.LIGHT_SOURCE_COLOR_OVERRIDE, c, 0.1f);
1352 if (
entity instanceof CampaignFleetAPI) {
1353 CampaignFleetAPI fleet = (CampaignFleetAPI)
entity;
1354 for (FleetMemberViewAPI view : fleet.getViews()) {
1356 view.getContrailWidthMult().shift(
getModId(), 0.5f, 1f, 1f, depth);
1357 view.getContrailDurMult().shift(
getModId(), 1f + depth * 1f, 1f, 1f, depth);
1358 view.getEngineGlowSizeMult().shift(
getModId(), 2f, 1f, 1f, 1f - depth * 0.5f);
1359 view.getEngineGlowColor().shift(
getModId(), Color.black, 1f, 1f, depth * 0.5f);
1364 if (
entity instanceof CampaignFleetAPI) {
1365 CampaignFleetAPI fleet = (CampaignFleetAPI)
entity;
1367 boolean inAbyss = depth > 0;
1370 CellStateTracker cell =
null;
1384 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_3",
1386 fleet.getStats().getDetectedRangeMod());
1387 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_4",
1389 fleet.getStats().getSensorRangeMod());
1392 float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
1395 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_5",
1396 "In abyssal hyperspace", 1f - (1f -
ABYSS_BURN_MULT) * depth * skillMod,
1397 fleet.getStats().getFleetwideMaxBurnMod());
1401 if ((!inCloud && !inAbyss) || fleet.isInHyperspaceTransition()) {
1404 }
else if (inCloud) {
1405 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_1",
1407 fleet.getStats().getDetectedRangeMod());
1410 float penalty = Misc.getBurnMultForTerrain(fleet);
1411 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_2",
1412 "In deep hyperspace", penalty,
1413 fleet.getStats().getFleetwideMaxBurnMod());
1414 if (cell !=
null && cell.isSignaling() && cell.signal < 0.2f) {
1417 if (cell !=
null && cell.isStorming() && !Misc.isSlowMoving(fleet)) {
1420 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_storm_sensor",
1422 fleet.getStats().getSensorRangeMod());
1426 fleet.getStats().addTemporaryModFlat(0.1f,
getModId() +
"_storm_visibility",
1428 fleet.getStats().getDetectedRangeMod());
1432 fleet.getStats().addTemporaryModMult(0.1f,
getModId() +
"_storm_speed",
1434 fleet.getStats().getFleetwideMaxBurnMod());
1444 if (cell.flicker !=
null && cell.flicker.getWait() > 0) {
1445 cell.flicker.setNumBursts(0);
1446 cell.flicker.setWait(0);
1447 cell.flicker.newBurst();
1450 if (cell.flicker ==
null || !cell.flicker.isPeakFrame())
return;
1456 MemoryAPI mem = fleet.getMemoryWithoutUpdate();
1457 if (mem.contains(key))
return;
1463 List<FleetMemberAPI> members = fleet.getFleetData().getMembersListCopy();
1464 if (members.isEmpty())
return;
1466 float totalValue = 0;
1467 for (FleetMemberAPI member : members) {
1468 totalValue += member.getStats().getSuppliesToRecover().getModifiedValue();
1470 if (totalValue <= 0)
return;
1477 float ebCostThresholdMult = 4f;
1479 WeightedRandomPicker<FleetMemberAPI> picker =
new WeightedRandomPicker<FleetMemberAPI>();
1480 WeightedRandomPicker<FleetMemberAPI> preferNotTo =
new WeightedRandomPicker<FleetMemberAPI>();
1481 for (FleetMemberAPI member : members) {
1483 if (member.isMothballed()) w *= 0.1f;
1486 float ebCost = EmergencyBurnAbility.getCRCost(member, fleet);
1487 if (ebCost * ebCostThresholdMult > member.getRepairTracker().getCR()) {
1488 preferNotTo.add(member, w);
1490 picker.add(member, w);
1493 if (picker.isEmpty()) {
1494 picker.addAll(preferNotTo);
1497 FleetMemberAPI member = picker.pick();
1498 if (member ==
null)
return;
1500 float crPerDep = member.getDeployCost();
1501 float suppliesPerDep = member.getStats().getSuppliesToRecover().getModifiedValue();
1502 if (suppliesPerDep <= 0 || crPerDep <= 0)
return;
1504 float strikeDamage = crPerDep * strikeValue / suppliesPerDep;
1507 float resistance = member.getStats().getDynamic().getValue(Stats.CORONA_EFFECT_MULT);
1508 strikeDamage *= resistance;
1516 float currCR = member.getRepairTracker().getBaseCR();
1517 float crDamage = Math.min(currCR, strikeDamage);
1519 float ebCost = EmergencyBurnAbility.getCRCost(member, fleet);
1520 if (currCR >= ebCost * ebCostThresholdMult) {
1521 crDamage = Math.min(currCR - ebCost * 1.5f, crDamage);
1525 member.getRepairTracker().applyCREvent(-crDamage,
"hyperstorm",
"Hyperspace storm strike");
1528 float hitStrength = member.getStats().getArmorBonus().computeEffective(member.getHullSpec().getArmorRating());
1529 hitStrength *= strikeDamage / crPerDep;
1530 if (hitStrength > 0) {
1531 member.getStatus().applyDamage(hitStrength);
1532 if (member.getStatus().getHullFraction() < 0.01f) {
1533 member.getStatus().setHullFraction(0.01f);
1537 if (fleet.isPlayerFleet()) {
1538 String verb =
"suffers";
1539 Color c = Misc.getNegativeHighlightColor();
1540 if (hitStrength <= 0) {
1543 c = Misc.getTextColor();
1546 member.getShipName() +
" " + verb +
" damage from the storm", c);
1548 Global.
getSector().getCampaignUI().showHelpPopupIfPossible(
"chmHyperStorm");
1567 Color gray = Misc.getGrayColor();
1568 Color highlight = Misc.getHighlightColor();
1570 Color bad = Misc.getNegativeHighlightColor();
1575 boolean inAbyss = depth > 0f;
1577 CellStateTracker cell =
null;
1589 }
else if (!inCloud) {
1592 }
else if (cell ==
null || !cell.isStorming()) {
1595 }
else if (cell.isStorming()) {
1600 String fuelCost = Misc.getRoundedValueMaxOneAfterDecimal(player.getLogistics().getFuelCostPerLightYear());
1602 float nextPad = pad;
1604 tooltip.addSectionHeading(
"Travel", Alignment.MID, pad);
1607 tooltip.addPara(
"Traveling through hyperspace consumes fuel based on the distance travelled. " +
1608 "Your fleet requires %s fuel per light-year.*", nextPad,
1609 highlight, fuelCost);
1612 tooltip.addPara(
"Reduces the sensor range and sensor profile of fleets inside it by %s. "
1613 +
"Also reduces the maximum burn level by %s. The reduction is gradual and based "
1614 +
"on the \"depth\" the fleet has reached.",
1621 tooltip.addPara(
"Skill in navigation is of little use, and does not provide its "
1622 +
"normal benefit in countering terrain-specific maximum burn penalties.", pad,
1626 tooltip.addPara(
"Skill in navigation is of limited use, and only provides %s of its "
1627 +
"normal benefit in countering the maximum burn penalty.", pad,
1631 }
else if (inCloud) {
1632 tooltip.addPara(
"Reduces the range at which fleets inside can be detected by %s.",
1638 tooltip.addPara(
"Reduces the speed of fleets inside by up to %s. Larger fleets are slowed down more.",
1641 "" + (
int) Math.round((Misc.BURN_PENALTY_MULT) * 100f) +
"%"
1644 float penalty = Misc.getBurnMultForTerrain(
Global.
getSector().getPlayerFleet());
1645 tooltip.addPara(
"Your fleet's speed is reduced by %s.", pad,
1647 "" + (
int) Math.round((1f - penalty) * 100f) +
"%"
1651 tooltip.addSectionHeading(
"Hyperspace storms", Alignment.MID, pad);
1653 Color stormDescColor = Misc.getTextColor();
1654 if (cell !=
null && cell.isStorming()) {
1655 stormDescColor = bad;
1657 tooltip.addPara(
"Being caught in a storm causes storm strikes to damage ships " +
1658 "and reduce their combat readiness. " +
1659 "Larger fleets attract more damaging strikes.", stormDescColor, pad);
1661 tooltip.addPara(
"In addition, storm strikes toss the fleet's drive bubble about " +
1662 "with great violence, often causing a loss of control. " +
1663 "Some commanders are known to use these to gain additional " +
1664 "speed, and to save fuel - a practice known as \"storm riding\".", Misc.getTextColor(), pad);
1666 tooltip.addPara(
"\"Slow-moving\" fleets do not attract storm strikes.", Misc.getTextColor(), pad);
1670 tooltip.addSectionHeading(
"Combat", Alignment.MID, pad);
1679 tooltip.addPara(
"Reduces top speed of ships by up to %s, and the top speed and range "
1680 +
"of missiles by up to %s.", pad,
1682 "" + (
int) Math.round(BattleCreationPluginImpl.ABYSS_SHIP_SPEED_PENALTY) +
"%",
1683 "" + (
int) Math.round(BattleCreationPluginImpl.ABYSS_MISSILE_SPEED_PENALTY) +
"%"
1686 tooltip.addPara(
"No combat effects.", nextPad);
1690 tooltip.addPara(
"*1 light-year = 2000 units = 1 map grid cell", gray, pad);
1694 float skillMod = fleet.getCommanderStats().getDynamic().getValue(Stats.NAVIGATION_PENALTY_MULT);
1695 if (skillMod < 0) skillMod = 0;
1696 if (skillMod > 1) skillMod = 1;
1698 float penalty = 1f - baseMult;
1699 penalty *= skillMod;
1701 return 1f - penalty;
1721 CellStateTracker cell =
null;
1726 String
name =
"Hyperspace";
1728 name =
"Hyperspace (Abyssal)";
1729 }
else if (!inCloud) {
1730 }
else if (cell ==
null || !cell.isStorming()) {
1731 name =
"Hyperspace (Deep)";
1732 }
else if (cell.isStorming()) {
1733 name =
"Hyperspace (Storm)";
1740 return "dark-hyper-like";
1744 return flag == TerrainAIFlags.REDUCES_SENSOR_RANGE;
1747 public boolean hasAIFlag(Object flag, CampaignFleetAPI fleet) {
1748 if (flag == TerrainAIFlags.DANGEROUS_UNLESS_GO_SLOW) {
1750 CellStateTracker cell =
null;
1755 return cell.isStorming() || cell.isSignaling();
1786 public static void main(String[] args) {
1787 System.out.println(1.5f - (
int) 1.5f);
1795 public void setTileState(Vector2f loc,
float radius, CellState state,
float waitDur,
float signalDur) {
1796 setTileState(loc, radius, state, waitDur, signalDur, signalDur);
1798 public void setTileState(Vector2f loc,
float radius, CellState state,
float waitDur,
float minSignalDur,
float maxSignalDur) {
1799 float x = this.
entity.getLocation().x;
1800 float y = this.
entity.getLocation().y;
1804 float w = tiles.length * size;
1805 float h =
tiles[0].length * size;
1810 float extra = (containsSize - size) / 2f;
1812 if (loc.x + radius + extra < x)
return;
1813 if (loc.y + radius + extra < y)
return;
1814 if (loc.x > x + w + radius + extra)
return;
1815 if (loc.y > y + h + radius + extra)
return;
1817 int xMin = (int) ((loc.x - x - radius) / size);
1818 int yMin = (int) ((loc.y - y - radius) / size);
1819 int xMax = (int) ((loc.x - x + radius) / size);
1820 int yMax = (int) ((loc.y - y + radius) / size);
1822 if (xMin < 0) xMin = 0;
1823 if (yMin < 0) yMin = 0;
1824 if (xMin >=
tiles.length) xMin = tiles.length - 1;
1825 if (yMin >=
tiles[0].length) yMin=
tiles[0].length - 1;
1827 if (xMax < 0) xMax = 0;
1828 if (yMax < 0) yMax = 0;
1829 if (xMax >=
tiles.length) xMax = tiles.length - 1;
1830 if (yMax >=
tiles[0].length) yMax =
tiles[0].length - 1;
1832 for (
int i = xMin; i <= xMax; i++) {
1833 for (
int j = yMin; j <= yMax; j++) {
1834 int texIndex =
tiles[i][j];
1835 if (texIndex >= 0) {
1836 float tx = x + i * size + size/2f - containsSize/2f;
1837 float ty = y + j * size + size/2f - containsSize/2f;
1839 float dist = Misc.getDistance(loc.x, loc.y, tx, ty);
1840 if (dist > radius)
continue;
1848 float wait = interval * 0f + interval * 1.5f * (float) Math.random();
1849 if (waitDur >= 0f) wait = waitDur;
1850 float signal = interval * 0.5f + interval * 0.5f * (float) Math.random();
1851 if (minSignalDur >= 0f) signal = minSignalDur + (maxSignalDur - minSignalDur) * (
float) Math.random();
1853 wait *= 0.9f + (float) Math.random() * 0.2f;
1854 signal *= 0.9f + (float) Math.random() * 0.2f;
1856 if (cell ==
null && state != CellState.OFF) {
1857 cell =
activeCells[i][j] =
new CellStateTracker(i, j, wait, signal);
1859 }
else if (cell !=
null && state == CellState.OFF) {
1860 if (cell.state == CellState.STORM ||
1861 cell.state == CellState.STORM_WANE ||
1862 cell.state == CellState.SIGNAL) {
1867 if (cell.state == CellState.STORM_WANE) {
1868 dur = Math.min(cell.wane, dur);
1870 cell.maxWane = dur * 4f;
1873 cell.state = CellState.STORM_WANE;
1877 }
else if (cell !=
null) {
1879 if (state == CellState.WAIT) {
1882 if (state == CellState.SIGNAL) {
1884 cell.signal = signal;
1885 cell.maxSignal = signal;
static SettingsAPI getSettings()
static SoundPlayerAPI getSoundPlayer()
static SectorAPI getSector()
boolean isPreventedFromAffecting(SectorEntityToken other)
float getExtraSoundRadius()
transient SpriteAPI mapTexture
boolean isTileVisible(int i, int j)
IntervalUtil getInterval()
int[] getTilePreferStorm(Vector2f test, float r)
static final CampaignEngineLayers GLOW
void preMapRender(float alphaMult)
LocationState getStateAt(SectorEntityToken entity, float extraRadius)
EnumSet< CampaignEngineLayers > getActiveLayers()
boolean containsEntity(SectorEntityToken other)
static float STORM_SPEED_MULT
String getEffectCategory()
static float STORM_DAMAGE_FRACTION
void setTileState(Vector2f loc, float radius, CellState state, float waitDur, float minSignalDur, float maxSignalDur)
void renderOnRadar(Vector2f radarCenter, float factor, float alphaMult)
CellStateTracker[][] getActiveCells()
transient float stormCellTimeMultOutsideBaseArea
static void clearCellsNotNearPlayer(HyperspaceTerrainPlugin plugin)
transient float extraDistanceAroundPlayerToAdvanceStormCells
HyperspaceAbyssPlugin abyssPlugin
float getAbyssalDepth(Vector2f loc)
boolean hasAIFlag(Object flag, CampaignFleetAPI fleet)
static float VISIBLITY_MULT
String getNameForTooltip()
static float ABYSS_VISIBLITY_MULT
boolean shouldPlayLoopTwo()
static float STORM_STRIKE_SOUND_RANGE
float getAbyssalDepth(SectorEntityToken other)
void render(CampaignEngineLayers layer, ViewportAPI viewport)
void applyEffect(SectorEntityToken entity, float days)
int[] getTile(Vector2f test)
transient SpriteAPI flickerTexture
HyperspaceAbyssPlugin getAbyssPlugin()
static final CampaignEngineLayers BASE_OVER
void setAbyssPlugin(HyperspaceAbyssPlugin abyssChecker)
static float STORM_VISIBILITY_FLAT
static float ABYSS_BURN_MULT
static float STORM_MAX_TIMEOUT
void renderQuad(int i, int j, float x, float y, float width, float height, float texX, float texY, float texW, float texH, float angle)
void setStormCellTimeMultOutsideBaseArea(float stormCellTimeMultOutsideBaseArea)
void renderSubArea(float startColumn, float endColumn, float startRow, float endRow, float factor, int samples, float alphaMult)
float getTileContainsSize()
static float STORM_SENSOR_RANGE_MULT
static Color ABYSS_PARTICLE_COLOR
float getAdjustedSpeedMult(CampaignFleetAPI fleet, float baseMult)
boolean shouldPlayLoopFour()
transient boolean clearedCellsPostLoad
void setExtraDistanceAroundPlayerToAdvanceStormCells(float extraDistanceAroundPlayerToAdvanceStormCells)
boolean isInClouds(Vector2f test, float r)
void createTooltip(TooltipMakerAPI tooltip, boolean expanded)
void preRender(CampaignEngineLayers layer, float alphaMult)
static void main(String[] args)
List< StarSystemAPI > getAbyssalSystems()
SectorEntityToken abyssDarkSource
boolean isInAbyss(SectorEntityToken other)
float[] getThetaAndRadius(Random rand, float width, float height)
void applyStormStrikes(CellStateTracker cell, CampaignFleetAPI fleet, float days)
void turnOffStorms(Vector2f loc, float radius)
static final CampaignEngineLayers BASE
transient String stormSoundId
static final CampaignEngineLayers FLASH_OVER
void setTileState(Vector2f loc, float radius, CellState state, float waitDur, float signalDur)
boolean shouldPlayLoopThree()
static float ABYSS_NAVIGATION_EFFECT
static float ABYSS_SENSOR_RANGE_MULT
static final CampaignEngineLayers SHIVER
void init(String terrainId, SectorEntityToken entity, Object param)
void playStormStrikeSoundsIfNeeded()
CellStateTracker getCellAt(Vector2f location, float radius)
float getProximitySoundFactor()
void renderOnMap(float factor, float alphaMult)
static String STORM_STRIKE_TIMEOUT_KEY
void advance(float amount)
boolean isInClouds(SectorEntityToken other)
boolean shouldPlayLoopOne()
float getStormCellTimeMultOutsideBaseArea()
List< CellStateTracker > savedActiveCells
transient CellStateTracker[][] activeCells
static Color ABYSS_BACKGROUND_COLOR
static float STORM_MIN_TIMEOUT
static float STORM_MAX_STRIKE_DAMAGE
CellStateTracker getCellAt(SectorEntityToken entity, float extraRadius)
static Color ABYSS_LIGHT_COLOR
boolean isTooltipExpandable()
float getTileRenderSize()
static float STORM_MIN_STRIKE_DAMAGE
boolean hasAIFlag(Object flag)
boolean containsPoint(Vector2f test, float r)
CellStateTracker getExactCellAt(Vector2f location)
static final CampaignEngineLayers FLASH
float getExtraDistanceAroundPlayerToAdvanceStormCells()
static float ABYSS_MUSIC_SUPPRESSION
Description getDescription(String id, Type type)
String getSpriteName(String category, String id)
float getFloat(String key)
SpriteAPI getSprite(String filename)
Color getColor(String id)
void applyLowPassFilter(float gain, float gainHF)
SoundAPI playSound(String id, float pitch, float volume, Vector2f loc, Vector2f vel)
float getAbyssalDepth(Vector2f loc)
boolean isInAbyss(Vector2f loc)
List< StarSystemAPI > getAbyssalSystems()
void advance(float amount)