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