From d0ee5c8d7e1b8357c4db84cb03a9d968f45bd9bf Mon Sep 17 00:00:00 2001 From: Floris Date: Sat, 11 Jul 2026 14:59:46 +0200 Subject: [PATCH] seismic pings: optimization + only show for cloaked units + dont show when terrain obstructs + flat on ground + pip shows them again --- luarules/gadgets/unit_seismic_ping.lua | 357 ++++++++---------- luarules/images/seismic_ping/center_dot.png | Bin 0 -> 1351 bytes .../images/seismic_ping/inner_outline.png | Bin 0 -> 2625 bytes luarules/images/seismic_ping/inner_ring.png | Bin 0 -> 2542 bytes .../images/seismic_ping/middle_outline.png | Bin 0 -> 3318 bytes luarules/images/seismic_ping/middle_ring.png | Bin 0 -> 3199 bytes .../images/seismic_ping/outer_outline.png | Bin 0 -> 4274 bytes luarules/images/seismic_ping/outer_ring.png | Bin 0 -> 4245 bytes .../images/seismic_ping/seismic_atlas.png | Bin 0 -> 24106 bytes luaui/Widgets/gui_pip.lua | 218 +++-------- 10 files changed, 206 insertions(+), 369 deletions(-) create mode 100644 luarules/images/seismic_ping/center_dot.png create mode 100644 luarules/images/seismic_ping/inner_outline.png create mode 100644 luarules/images/seismic_ping/inner_ring.png create mode 100644 luarules/images/seismic_ping/middle_outline.png create mode 100644 luarules/images/seismic_ping/middle_ring.png create mode 100644 luarules/images/seismic_ping/outer_outline.png create mode 100644 luarules/images/seismic_ping/outer_ring.png create mode 100644 luarules/images/seismic_ping/seismic_atlas.png diff --git a/luarules/gadgets/unit_seismic_ping.lua b/luarules/gadgets/unit_seismic_ping.lua index 0d9c42fb173..e602f970177 100644 --- a/luarules/gadgets/unit_seismic_ping.lua +++ b/luarules/gadgets/unit_seismic_ping.lua @@ -23,7 +23,11 @@ end local spGetSpectatingState = Spring.GetSpectatingState local spGetMyAllyTeamID = Spring.GetMyAllyTeamID local spGetGroundHeight = Spring.GetGroundHeight -local spGetViewGeometry = Spring.GetViewGeometry +local spGetCameraPosition = Spring.GetCameraPosition +local spGetUnitAllyTeam = Spring.GetUnitAllyTeam +local spGetUnitIsCloaked = Spring.GetUnitIsCloaked +local spIsGUIHidden = Spring.IsGUIHidden +local spIsSphereInView = Spring.IsSphereInView local glColor = gl.Color local glPushMatrix = gl.PushMatrix @@ -34,159 +38,98 @@ local glScale = gl.Scale local glBlending = gl.Blending local glDepthTest = gl.DepthTest local glBillboard = gl.Billboard -local glCallList = gl.CallList -local glCreateList = gl.CreateList -local glDeleteList = gl.DeleteList -local glBeginEnd = gl.BeginEnd -local glVertex = gl.Vertex +local glTexture = gl.Texture +local glTexRect = gl.TexRect local GL_SRC_ALPHA = GL.SRC_ALPHA local GL_ONE = GL.ONE local GL_ONE_MINUS_SRC_ALPHA = GL.ONE_MINUS_SRC_ALPHA -local GL_QUADS = GL.QUADS -local sin = math.sin -local cos = math.cos -local pi = math.pi -local pi2 = pi * 2 local max = math.max local min = math.min +local sqrt = math.sqrt +local floor = math.floor -------------------------------------------------------------------------------- -- Config -------------------------------------------------------------------------------- local pingLifetime = 0.95 -local baseRadius = 16 -local maxRadius = 22 -local baseThickness = 2.4 +local baseRadius = 15 +local maxRadius = 19 +local onlyCloakedUnits = true +local drawFlatOnMap = true +local hideWhenTerrainOccluded = true +local terrainOcclusionSamples = 6 +local terrainOcclusionMargin = 8 +local pingHeightOffset = drawFlatOnMap and 1.5 or 5 -------------------------------------------------------------------------------- -- State -------------------------------------------------------------------------------- local pings = {} +local pingCount = 0 +local pingPool = {} +local pingPoolCount = 0 local gameTime = 0 local thicknessScale = 1.2 -------------------------------------------------------------------------------- --- Display lists for arc geometry (pre-generated at unit radius with proportional thickness) +-- Textures -------------------------------------------------------------------------------- -local displayLists = { - outerArcs = {}, -- 4 arcs at 60 degrees each - middleArcs = {}, -- 3 arcs at 80 degrees each - innerArcs = {}, -- 2 arcs at 120 degrees each - centerCircle = nil, -- Full circle - -- Outlines (slightly larger versions for dark border) - outerOutlines = {}, - middleOutlines = {}, - innerOutlines = {}, +local atlasTexture = "LuaRules/Images/seismic_ping/seismic_atlas.png" +local atlasSprites = { + outerRing = { 0 / 7, 0, 1 / 7, 1 }, + outerOutline = { 1 / 7, 0, 2 / 7, 1 }, + middleRing = { 2 / 7, 0, 3 / 7, 1 }, + middleOutline = { 3 / 7, 0, 4 / 7, 1 }, + innerRing = { 4 / 7, 0, 5 / 7, 1 }, + innerOutline = { 5 / 7, 0, 6 / 7, 1 }, + centerDot = { 6 / 7, 0, 7 / 7, 1 }, } --- Proportional thicknesses (relative to unit radius 1.0) -local outerThicknessRatio = baseThickness * 1.05 / baseRadius -local middleThicknessRatio = baseThickness * 0.8 / baseRadius -local innerThicknessRatio = baseThickness * 1 / baseRadius -local centerThicknessRatio = baseThickness * 1.8 / baseRadius -local outlineExtra = 0.02 -- How much larger the outline is on each side +-- Texture radius in source image does not fill full quad, so apply scale compensation. +local outerQuadScale = 1.17 +local middleQuadScale = 1.18 +local innerQuadScale = 1.24 +local centerQuadScale = 2.2 -------------------------------------------------------------------------------- --- Helper: Draw a thick arc as geometry (for display list creation) +-- Draw a textured billboard quad -------------------------------------------------------------------------------- -local function DrawThickArcVertices(innerRadius, outerRadius, startAngle, endAngle, segments) - local angleStep = (endAngle - startAngle) / segments - for i = 0, segments - 1 do - local angle1 = startAngle + i * angleStep - local angle2 = startAngle + (i + 1) * angleStep - local cos1, sin1 = cos(angle1), sin(angle1) - local cos2, sin2 = cos(angle2), sin(angle2) - glVertex(cos1 * innerRadius, sin1 * innerRadius, 0) - glVertex(cos1 * outerRadius, sin1 * outerRadius, 0) - glVertex(cos2 * outerRadius, sin2 * outerRadius, 0) - glVertex(cos2 * innerRadius, sin2 * innerRadius, 0) +local function DrawTexturedQuad(sprite, scale, rotation) + local s1, t1, s2, t2 = sprite[1], sprite[2], sprite[3], sprite[4] + glPushMatrix() + if rotation then + glRotate(rotation, 0, 0, 1) end + glScale(scale, scale, 1) + glTexRect(-1, -1, 1, 1, s1, t1, s2, t2) + glPopMatrix() end --------------------------------------------------------------------------------- --- Create display lists for all arc types --------------------------------------------------------------------------------- -local function CreateDisplayLists() - -- Outer arcs: 4 arcs, 60 degrees each, at unit radius with proportional thickness - local outerInner = 1.08 - outerThicknessRatio / 2 - local outerOuter = 1.08 + outerThicknessRatio / 2 - for i = 0, 3 do - local startAngle = (i * 90) * pi / 180 - local arcLength = 60 * pi / 180 - -- Outline (slightly larger) - displayLists.outerOutlines[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, outerInner - outlineExtra, outerOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 12) - end) - -- Main arc - displayLists.outerArcs[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, outerInner, outerOuter, startAngle, startAngle + arcLength, 12) - end) - end - - -- Middle arcs: 3 arcs, 80 degrees each, at 0.85 of unit radius - local middleRadiusRatio = 0.85 - local middleInner = middleRadiusRatio - middleThicknessRatio / 2 - local middleOuter = middleRadiusRatio + middleThicknessRatio / 2 - for i = 0, 2 do - local startAngle = (i * 120) * pi / 180 - local arcLength = 80 * pi / 180 - -- Outline (slightly larger) - displayLists.middleOutlines[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, middleInner - outlineExtra, middleOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 12) - end) - -- Main arc - displayLists.middleArcs[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, middleInner, middleOuter, startAngle, startAngle + arcLength, 12) - end) - end - - -- Inner arcs: 2 arcs, 120 degrees each, at 0.66 of unit radius - local innerRadiusRatio = 0.66 - local innerInner = innerRadiusRatio - innerThicknessRatio / 2 - local innerOuter = innerRadiusRatio + innerThicknessRatio / 2 - for i = 0, 1 do - local startAngle = (i * 180) * pi / 180 - local arcLength = 120 * pi / 180 - -- Outline (slightly larger) - displayLists.innerOutlines[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, innerInner - outlineExtra, innerOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 16) - end) - -- Main arc - displayLists.innerArcs[i] = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, innerInner, innerOuter, startAngle, startAngle + arcLength, 16) - end) +-- Cheap terrain line test: if terrain crosses camera->ping center line, hide full ping. +local function IsTerrainOccluded(camX, camY, camZ, pingX, pingY, pingZ) + local dx = pingX - camX + local dy = pingY - camY + local dz = pingZ - camZ + local distance = sqrt(dx * dx + dy * dy + dz * dz) + local samples = terrainOcclusionSamples + min(8, floor(distance / 700)) + local step = 1 / (samples + 1) + + for i = 1, samples do + local t = i * step + local sx = camX + dx * t + local sy = camY + dy * t + local sz = camZ + dz * t + if spGetGroundHeight(sx, sz) > sy + terrainOcclusionMargin then + return true + end end - -- Center circle: full circle at unit radius with proportional thickness - local centerInner = 1 - centerThicknessRatio / 1.3 - local centerOuter = 1.25 + centerThicknessRatio / 1.3 - displayLists.centerCircle = glCreateList(function() - glBeginEnd(GL_QUADS, DrawThickArcVertices, centerInner, centerOuter, 0, pi2, 20) - end) -end - --------------------------------------------------------------------------------- --- Delete display lists --------------------------------------------------------------------------------- -local function DeleteDisplayLists() - for i = 0, 3 do - if displayLists.outerArcs[i] then glDeleteList(displayLists.outerArcs[i]) end - if displayLists.outerOutlines[i] then glDeleteList(displayLists.outerOutlines[i]) end - end - for i = 0, 2 do - if displayLists.middleArcs[i] then glDeleteList(displayLists.middleArcs[i]) end - if displayLists.middleOutlines[i] then glDeleteList(displayLists.middleOutlines[i]) end - end - for i = 0, 1 do - if displayLists.innerArcs[i] then glDeleteList(displayLists.innerArcs[i]) end - if displayLists.innerOutlines[i] then glDeleteList(displayLists.innerOutlines[i]) end - end - if displayLists.centerCircle then glDeleteList(displayLists.centerCircle) end + return false end -------------------------------------------------------------------------------- --- Draw a single seismic ping with rotating arcs using display lists +-- Draw a single seismic ping with rotating textured rings -------------------------------------------------------------------------------- local function DrawPing(ping, currentTime, cameraDistance) local age = currentTime - ping.startTime @@ -199,8 +142,13 @@ local function DrawPing(ping, currentTime, cameraDistance) local wx, wy, wz = ping.x, ping.y, ping.z glPushMatrix() - glTranslate(wx, wy + 5, wz) - glBillboard() + glTranslate(wx, wy + pingHeightOffset, wz) + if drawFlatOnMap then + glRotate(90, 1, 0, 0) + else + glBillboard() + end + glTexture(atlasTexture) -- Calculate all progress/alpha values local rotation1 = currentTime * 70 @@ -223,33 +171,21 @@ local function DrawPing(ping, currentTime, cameraDistance) glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) -- Outer outlines - glColor(0.09, 0, 0, outerAlpha * 0.25) - for i = 0, 3 do - glPushMatrix() - glRotate(rotation1, 0, 0, 1) - glScale(outerRadius, outerRadius, 1) - glCallList(displayLists.outerOutlines[i]) - glPopMatrix() + if outerAlpha > 0.001 then + glColor(0.09, 0, 0, outerAlpha * 0.25) + DrawTexturedQuad(atlasSprites.outerOutline, outerRadius * outerQuadScale, rotation1) end -- Middle outlines - glColor(0.09, 0, 0, middleAlpha * 0.25) - for i = 0, 2 do - glPushMatrix() - glRotate(rotation2, 0, 0, 1) - glScale(middleRadius, middleRadius, 1) - glCallList(displayLists.middleOutlines[i]) - glPopMatrix() + if middleAlpha > 0.001 then + glColor(0.09, 0, 0, middleAlpha * 0.25) + DrawTexturedQuad(atlasSprites.middleOutline, middleRadius * middleQuadScale, rotation2) end -- Inner outlines - glColor(0.07, 0, 0, innerAlpha * 0.25) - for i = 0, 1 do - glPushMatrix() - glRotate(rotation3, 0, 0, 1) - glScale(innerRadius, innerRadius, 1) - glCallList(displayLists.innerOutlines[i]) - glPopMatrix() + if innerAlpha > 0.001 then + glColor(0.07, 0, 0, innerAlpha * 0.25) + DrawTexturedQuad(atlasSprites.innerOutline, innerRadius * innerQuadScale, rotation3) end end @@ -257,33 +193,21 @@ local function DrawPing(ping, currentTime, cameraDistance) glBlending(GL_SRC_ALPHA, GL_ONE) -- Outer ring - 4 arcs rotating clockwise - glColor(1, 0.1, 0.09, outerAlpha) - for i = 0, 3 do - glPushMatrix() - glRotate(rotation1, 0, 0, 1) - glScale(outerRadius, outerRadius, 1) - glCallList(displayLists.outerArcs[i]) - glPopMatrix() + if outerAlpha > 0.001 then + glColor(1, 0.1, 0.09, outerAlpha) + DrawTexturedQuad(atlasSprites.outerRing, outerRadius * outerQuadScale, rotation1) end -- Middle ring - 3 arcs rotating counter-clockwise - glColor(1, 0.22, 0.2, middleAlpha) - for i = 0, 2 do - glPushMatrix() - glRotate(rotation2, 0, 0, 1) - glScale(middleRadius, middleRadius, 1) - glCallList(displayLists.middleArcs[i]) - glPopMatrix() + if middleAlpha > 0.001 then + glColor(1, 0.22, 0.2, middleAlpha) + DrawTexturedQuad(atlasSprites.middleRing, middleRadius * middleQuadScale, rotation2) end -- Inner ring - 2 arcs rotating clockwise - glColor(1, 0.37, 0.33, innerAlpha) - for i = 0, 1 do - glPushMatrix() - glRotate(rotation3, 0, 0, 1) - glScale(innerRadius, innerRadius, 1) - glCallList(displayLists.innerArcs[i]) - glPopMatrix() + if innerAlpha > 0.001 then + glColor(1, 0.37, 0.33, innerAlpha) + DrawTexturedQuad(atlasSprites.innerRing, innerRadius * innerQuadScale, rotation3) end -- Center dot (shrinks from large to small with fade in/out) @@ -298,12 +222,10 @@ local function DrawPing(ping, currentTime, cameraDistance) end local centerAlpha = max(0, centerAlphaMultiplier * 0.6) glColor(1, 0.25, 0.23, centerAlpha) - glPushMatrix() - glScale(centerScale, centerScale, 1) - glCallList(displayLists.centerCircle) - glPopMatrix() + DrawTexturedQuad(atlasSprites.centerDot, centerScale * centerQuadScale) end + glTexture(false) glBlending(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) glPopMatrix() @@ -314,30 +236,46 @@ end -- callins -------------------------------------------------------------------------------- function gadget:Initialize() - CreateDisplayLists() +end + +function gadget:ViewResize(vsx, vsy) end function gadget:Shutdown() - DeleteDisplayLists() end function gadget:UnitSeismicPing(x, y, z, strength, allyTeam, unitID, unitDefID) local spec, fullview = spGetSpectatingState() local myAllyTeam = spGetMyAllyTeamID() - local unitAllyTeam = Spring.GetUnitAllyTeam(unitID) + local unitAllyTeam = spGetUnitAllyTeam(unitID) if (spec or allyTeam == myAllyTeam) and unitAllyTeam ~= allyTeam then if spec and not fullview then if allyTeam ~= myAllyTeam then return end end + if onlyCloakedUnits and not spGetUnitIsCloaked(unitID) then + return + end + + local ping + if pingPoolCount > 0 then + ping = pingPool[pingPoolCount] + pingPool[pingPoolCount] = nil + pingPoolCount = pingPoolCount - 1 + else + ping = {} + end + + ping.x = x + ping.y = spGetGroundHeight(x, z) or y + ping.z = z + ping.startTime = gameTime - table.insert(pings, { - x = x, - y = spGetGroundHeight(x, z) or y, - z = z, - strength = strength, - startTime = gameTime, - }) + pingCount = pingCount + 1 + pings[pingCount] = ping + + -- Inform LuaUI with full payload; widgets that only use first args stay compatible. + Script.LuaUI.UnitSeismicPing(x, y, z, strength, allyTeam, unitID, unitDefID) end end @@ -345,44 +283,57 @@ function gadget:Update(dt) gameTime = gameTime + dt end -function gadget:DrawWorld() - if #pings == 0 or Spring.IsGUIHidden() then +function gadget:DrawWorldPreUnit() + if pingCount == 0 or spIsGUIHidden() then return end glDepthTest(false) - -- Get visible world bounds for culling - local cx, cy, cz = Spring.GetCameraPosition() - local cs = Spring.GetCameraState() - local vsx, vsy = spGetViewGeometry() - - -- Calculate visible world area based on camera state - local viewDistance = cy / math.tan(math.rad(cs.fov or 45)) - local viewWidth = viewDistance * (vsx / vsy) - local margin = maxRadius * thicknessScale * 3 -- Add margin for ping radius - - local minX = cx - viewWidth - margin - local maxX = cx + viewWidth + margin - local minZ = cz - viewDistance - margin - local maxZ = cz + viewDistance + margin + local cx, cy, cz = spGetCameraPosition() + local cullRadius = maxRadius * thicknessScale * 2.5 local currentTime = gameTime local i = 1 - while i <= #pings do + while i <= pingCount do local ping = pings[i] - -- Check if ping is within visible bounds - if ping.x < minX or ping.x > maxX or ping.z < minZ or ping.z > maxZ then - -- Ping is outside view, skip drawing but don't remove yet - if currentTime - ping.startTime > pingLifetime then - table.remove(pings, i) + if spIsSphereInView(ping.x, ping.y + pingHeightOffset, ping.z, cullRadius) then + local isOccluded = false + if hideWhenTerrainOccluded then + isOccluded = IsTerrainOccluded(cx, cy, cz, ping.x, ping.y + pingHeightOffset, ping.z) + end + + if isOccluded then + if currentTime - ping.startTime > pingLifetime then + local dead = ping + pings[i] = pings[pingCount] + pings[pingCount] = nil + pingCount = pingCount - 1 + pingPoolCount = pingPoolCount + 1 + pingPool[pingPoolCount] = dead + else + i = i + 1 + end + -- Ping is visible and not terrain-occluded, try to draw it + elseif not DrawPing(ping, currentTime, cy) then + local dead = ping + pings[i] = pings[pingCount] + pings[pingCount] = nil + pingCount = pingCount - 1 + pingPoolCount = pingPoolCount + 1 + pingPool[pingPoolCount] = dead else i = i + 1 end else - -- Ping is visible, try to draw it - if not DrawPing(ping, currentTime, cy) then - table.remove(pings, i) + -- Ping is outside frustum, skip drawing but only remove when expired + if currentTime - ping.startTime > pingLifetime then + local dead = ping + pings[i] = pings[pingCount] + pings[pingCount] = nil + pingCount = pingCount - 1 + pingPoolCount = pingPoolCount + 1 + pingPool[pingPoolCount] = dead else i = i + 1 end diff --git a/luarules/images/seismic_ping/center_dot.png b/luarules/images/seismic_ping/center_dot.png new file mode 100644 index 0000000000000000000000000000000000000000..20828fb706c37d7c2f6d95e2a0398ffe2d76e309 GIT binary patch literal 1351 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K5893O0R7}x|G!U;i$lZxy-8q?;Kn_c~qpu?a z!^VE@KZ&eBK4*bPWHAE+-(e7DJf6QI1*qnZr;B4qMcmuFx;e7$0t^oh96E65&>;us z!-ozWIOK5HVKYzwC<4S_a?{PMB`alobN7G$vwQpZ+DS__PMxn_J$v`wn>TNY>jN#J z323nFF|3nGo^$`3C{r`6Uk4#vs^higkuDj#>*Uz~!FL&?%oY?cRs^_Eg|L={R zJ%({^M`m~YsPNh#udMX1v%~(F#QtYIi~ip~V0=Qo*kw;|hrexxe6Y(ONkQ?-%P;41 zRxnwb_IK>BF0flVC-X}00c|FmR-YSBg?F$&EK2qesaf;?4FOo|NRaK8dYR@?TwtOV)Obw?+#bd zk2P-@x4K3;Px#9G*P7}2illiwvzEFX`TZ}p@qNmN?*}41r>T8^&lBVOdwTV!^2nVx z_FKP+QvEZn;<^2jyrL{tg{wjLmu>#LeAQX6X&i~GU;p_YTFTVbdSm^Wp6>^4OulpU z#&MQ1U>K}oKjOFdT{83O+pn+eU#)v~oA74&tL^=hY*~Lv9NhG6+E@D}(;Ut+McjXN zwXS?=$c(2~XZ_dXs`CqFb&~4awC>gXeo zP)bHY^28*Oe=_Ra&A)COTo(GR{^#C*{3jfju5o^P|D@h;i9=~uHFwKsYO8{nPXllN zReD}l%agcy-fw~Py+NWUq*NH*KM9q0zcDAL`s(@|zSBoC<-KpriMjMj+}yozmcZ?F z&JX8wSU1%gPiHv4$5L~;-|vV+&z?+P6Mx6}#Dnwa&9l=Vb^N$-T*yql|IjP(?>t$l zkBY9TdYLm!n~=Ny_tCR~PZ)0OyL0~@(Cp$44etvaPtM6m9FqDq^=zUQ^TwV?;oQl^ zRR-BJD)-hV@o^>c-fEnyrnK*?VawTBJEaQTvKS(|rZ!x@!Fi&ngw@I~RHN#Dx^8k$ z`QN1H|CJtnw=3IF`HM-$SDxY861fLurhn^i9_O7lVfp=U-Se~sKd$VT)KLy+HsRPi z^;iA1G_iucI@4Yyvz}fVdEmMnLk9CXKbIq}`+t4+`EOsg)Q{VYd&j9i)~p@1ogL=3 z5@lYm^}l7lzumH0&*`TA?of#Lj%c+aPM)-U+?sR?9*r>mdKI;Vst0KWNAM*si- literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/inner_outline.png b/luarules/images/seismic_ping/inner_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..504e74a86221ceccdc9f04c554d9e8cb236d7eb9 GIT binary patch literal 2625 zcmb7`i96KW8^FI~7{*@6Ho1(QN`|JQVaT2&goy^%Ft^_%OSl6rLb&Jvq(y!g#uf^3(rE>G{{ABXoboX`g-&$1r-JD6tmQTd z>f0Ez#0xK}PWoeiA~<<&n|4q{Hbhtln|_x|X}oMEhfoE9V;+>K_eytJ?_Y->+ufaY zn$OlYIAQ7cj7JF#mU;51tXD#!*hF?a*{K# z8Uq!2;1xwG6X&7ML7O$`G54ReAcT z=Qu{wZW+u$k9--zxRCOf+k8HGC5vUB4pV?StCsAtjSIwO?dt=`o_;sfS`7N_%`_2l z+jqZ$)LN9!q60;#YCT8=1wr3R+#CECBhg0Jz65 z*R%{p{S2Dp!){R|0C%{mlF4;EiE}pWqk`#9TsNCn9khn z%cz|Qn(zj)M*ulA7i$Q3m(MSE1cI{59`0$21EVG%a`triwt>0jd}pbUrtm@fEn3@i zh8D5$K*k|%zJJLT-P8dgHXXPcx53^Uj}gSGFpRInWu0ol3z_L_^dPrmkln`tyCcZo ztl0&XPh-rQAbUAposYsKV^~>ov&-fsSg1ugDq(TWhO?E=VkTmL!n@Ex%~{wXl6^q~ z_fMfQy~Zq+AyT&`VZm^t+Z}gea88Z0<#Rc?Xj=TRSJcxg`A16~?#}*NtF_a|b?N5oQbb_P|+JxK#tiJi^|0r|dilE|9ip_^mq? zzVy$>cR|^CiTk;ZrM3e74IxYASh;nVEVl>{WtE3=Ta^ya-7aiy8uG+j&UUB_`myLJ znI!Riw}^w^A+jSLD5IY1Y)73d#zFqM&}m}yLW&5^a=1$c%0O+7$c2CYpDN51{J z@@DtjjME=oh1GOEkJ(1VW`TSW%%WlBAhlCD?tz83J4Dicv7OBZx zY_~A4H765vg}$E~29ukBv>#Q9op%`z-16o8OgZu6HYUnWq)A(Dq_0Q()ly=5<^uC_ z#6z9dc$z_6`0kQQSgjX}j)#|68x<@PT4lk@1-lI)H#ZAi_*;xn`}?Cyv7jSLN4ZYSLR%fQ&gLrx*D3>5Er?tw_lB<}Rbx0YZ1{8U9T{ z``XzwqvmJcr&@_bZ+QdIg@(mykc=eR+m*XZvJ?mj>l*h+-YtGWpyeHEYXI`jO)J^` z9KsL7gG|8Nio13VS`Ip*-yOcdY1f1u_Hmm&4*VCm%Cp|gI@FXE`b(`Gi5j2&U%w@> z5u^bg?f2Im+awvHwuCfb#}RedVA2pz=-Y>;pw4Q)1kS`!BNg++7P)6VkLS3)t84ke zdp&ra_35pM)ZaNoiEX~Ed>6qqslSa!-B_)cz|B0Tc*LEj3 z6g4d#`Q-B(+qUxsTmImtW(k=Lp?9PDqiLw>kfG9>hap*JxpvrVg{apQGQMoAbiyBm9eWmhOBAEb25%mQt;q44U(J`Z9K{ zmtMX!V@_FJy#x%WR6`Z@hp9oPmt<%r=2=tFR$P zhKiy-c>fIsoALPcds%kf> z+iL+H${|%Mbz5y77!M9=Rj1pc98S@nS*o&t(w-k(3)RPU-%0 z5?!TuJT5r#i$dYz*{9f;2K^PJvxb{STUrGc{;wR>qEPSp>tibQ?>$R@$9AREo{II6WF9Jg#}_e4EVr@`8agi7>&{^p}qYo+i0J1p!VDUCXp9 zPauHK`>$xq-WJ(XQN(@6xN=g#q>Daz=bXv|jh*Tgrf5G#Z^eaVJ~XSYnC=|_i3|9q zs(r8@QsSEQVj^3_TgU63kpkPu4_6ktF2lg38g*u0JXvF#a=rMVj8_L0IYCjhbeDba zp6}n1S`)p>ppZuWR4Zc}TZ8e3nl2d=dC>a_JvuP>(U0#xNzambp|oZWn3#C;D9+y6 L$*RKA_tF0W=cUpD literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/inner_ring.png b/luarules/images/seismic_ping/inner_ring.png new file mode 100644 index 0000000000000000000000000000000000000000..7effc422c7bd6ad48edea13a51194821ad7e3960 GIT binary patch literal 2542 zcmb7``8(U$7so%bMiRBOmbRu*V=1x3Qlcq|y;^D~mJ+2@?aUBsLWTX< zWMv>fMz*yyb0zq0d^wxvVM9#nr3}7$=81%?oyeTh#Q=Wj_#upyC zRlL%!7>Wy;AjYYDOmg!++ZgxSz|ZeiiD0DRceY^C4V8o_?@-=Hi652pseO5QuTY%;!+S(Hl0x{GN>3e< z*eNPc5freibXb79m7YzGSLko%CXj3{zyN{*N)<%BI2k;LS|08Ed z{=P^k!Kg(Err4Vn)2AHbPR#f53;)w&R6w61UjHK>V-%GR?q0ebVQn%uZ*|DUIuVDo zpX!KzSD<++t;U70=3tz<-fC_$=1*;s}CZ(KzWCW@?Oj>ciNom&0}M5$e8 zOuKS=PTRH34-W=Z=e`n0$Jcg+FaO!ksUy8fXRl7RJXc>#=Gt~g;k0B(?H~Vk`*v0Q83=Qhd^w^`C_kas zz?A*@{II|#Hdn!8YS}fg1a?(4`XJn5fga9}4Bra@g*|gL0#OaUWx{Zh}?miZXip(f_T1sgP5>Sw0tGG$y%EfC#V>Vv z;DPxo5J6HifT&aui8&QT%ory@$aP8()YsX!d5Qej6WLLo!y4tkxomboJy*gt*Y{VB zb*8@sx$7oE@1Q|!ca&PW^52&sZfQ4?!fWz`SD&w5hD?$!BnCXww-8N}9rsGQhu)Dk zA>35MyJY+X*kTdtB1zxb5tVzz4Z^LDy5n*!;HRa5cKwXq5`EBV4WZ254EGZi)Rx7c@sv#E41@R z`S8ouFNnECU)NyJy72HzNA~D6uY?c9i`@k$?e|%%PK)RnBcfE@7};L?c=2${Nz&|L zyvD5TgRE#tiNaCr8B*D1hsGJ1!vX>=S-V!~#yW9@^Der*x7tTlujs=HZ)OQOwjzZb z=4GN?C((z6%K65-k4Vvos9_eP^97gd7Pz-0nwWPn250Z1VC;q743Kg)X!%We_k2Kz zha!JOJ3X|{)lqf=ySo@)eKZ-A-upyGf>8EOK~uG9z*r-BG zicXS=6&_6#!7EJ=e~}%euQ!DSOF%uIDuUsuZ3qMhVucc~3Ik5&>VO|mE> zg|a>Vb02gj2Bx2_Tr2X1NVHmzLMa7ALeR r0p0LY5$>MaA^hQk|15p@2`+mWpSuMbt@kiTnhmhEx@uWxj!*wTGUAQ% literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/middle_outline.png b/luarules/images/seismic_ping/middle_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..7980213cf7057a149331acafa685a4a98d179e76 GIT binary patch literal 3318 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

m_{abG*qx z3!r6tbJvw>9e_`#d-HI4aRoxgG#8CdX)Tr`_ zXD$~f!aVNg@tWzKZ!vT}D>o9`3BMqijUUOPK=2FSv?inL-ZM5}~ zGfI+J%s-P@KbXa2j_evv{sHRLvp4#M7n=9O!le70?>9ywHpe+zbt zAKfp~a5xCjj?bn~@^xQH?MGtt7^y_|ic8$VR<+|gX_aoX6eNoO>N$Hqn#%VZwc`W~ z6p}9F6U8OUD;L6LJAJADVY0y-LG4q!j6BsK4dj^;m@%y{ELv;vP-A-6J-4Zw5!R9@ z9Rd;OT^@Gh*UnyJCKUCl4pq*0UMJ#Pnm61U-IkWPge!L?>JDe)8&<_%oDkLj25&Nb zXWcZo^Ibde05h$W^|FzRK?te%sb1H!eaXw;e1VP59)1sBQ6rSD9s5@$J)@|6S(4S;-a#C%t$9~vzRLhIH(=-ri^r4Kgn5y0-)nm&qw?qqqU+VVoB)=`~I zzK8Fxr{@E0jvyK-13h=l)y%|1TjFG=V}gk}(#lVKfGM z5oj-}Wd+X1#M@ua;GQ(u+Hv5zKAkT^{~ctgiK|+@p9+u)yH1hVt4;>ufEijQB_b|)_e!ek=ks7sR_}Z z9m!QQuaTEjV0gq2H?>!^I=EQst3Y_`W5vka1w$fh zoYw_uMSG{wFB~z6g}?Jp#?hXkKV9Ikff@Iy%{7r;+9a$*A4uu;hM$!7se+}O8O)Ba zrc$@;$jDOU4qR=97~2`jdCM%#=*L%w`A+Bzy?O0(KW}wU1L=*M-Ci9-%_3eAd0if!t5>9KbX- z6YEzNQ>w!zhps!JK1? zPQk(N@WW9OhXO}7IUbQ;AE*6G*tOd7+*ZYv%CyT5K<}-`$x%;#%iZcoeV31Wk?R|F zt^?5^2g*aY3nujffcylSH=Ax2C@JRC!3k)e1OvD!-0gv9f^2bMRiGn219^8mkExof zYe+zAYXf$JoQ)jVcDu(TX}mj#|Ji;L0jUDiIFctN#)h}2w{|rm&GNbqN>&S z>kjhi5~$Kg$z6YSww8-boHISZ6(if140deFSPaLInhWX(#-K z8;p`%P=V%?OeyeF*GWGx*opM{jM{QpKV;2JZZ}kHgV>)IcTI22%wzeF*N%(FQq~G2 zN}#VRDr8@ozF)wKz*11NEhKfubAiyX{f@$5a=XG5BbFR9l|Y!Ebn2yXhsI1{arE}8 zsKDe(#qv^#HG|5JP63G|IWN8mW(_#ft?wD>G=pN$Z7Y#@S;?Tfc_M2b5|v`m)^MI# z;1M;v)kSDr-jSk#w5Pz*FL+vqeTfo_+X}ELTQR+Ev>pXBsdu`$IQs>hJ|t{&kWk5i zBJ&mZk5gSI5?5X4N&daB;CB}0k96C=1A~Lbv&vaHQ9;OnhDS`_B516lUf006m(>)| z{8I6nW#70?*^We}_{ThHjLBEgOOz4&?^UjPHOT{EK|HV)CBwY2eLE+Pt+xXWhf`=z%5i?H3m=7=3Gda(2RydAp~t(_4* za1iN4i5m$mc+~s{y+a6%4)V*Pgg<6HO95(7Hlwtq3` za}E=&Em6Tk!u0EhI-euvPvmHd61O%pokKep$sUr*jT_%lNeVZ%C>mc0$&3}QAnLRU zzX+Cyi}xWLiA^E9`7W=tq0~w(IHm!szW^sK@^ShBOol literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/outer_outline.png b/luarules/images/seismic_ping/outer_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..e561d5458db4945ce4551f5e379d4768523bad18 GIT binary patch literal 4274 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 UJgy1s@Bjb+07*qoM6N<$g4E&SCjbBd literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/outer_ring.png b/luarules/images/seismic_ping/outer_ring.png new file mode 100644 index 0000000000000000000000000000000000000000..2ac77f4c81630a6547d387a1bd1dd504044cc41d GIT binary patch literal 4245 zcmV;G5NhvPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D5GqMTK~#8N?cFW3 z9K{xfVI&fXL?W+5B9W1i$Rm-+xX8#zWF#^Y8F^)7Boc|Mx$B#JbJt{_IVaQIySi(4 zzt37REYjyEQ`J?qf9jiW0001RAg9ymyVL3P^HYD~{|}(J)9LhmYweHL+P|j>T5JEb z*8cLJ*Qe9z)#-Hl)sw}K@#_E>Ol$2y133=vEUmTww$}dCUdza0IW=dZt9Z1CvPXTQuv*-2B5Rn+WR;NN21Be6M|ML-^cGCzydw@{XOU` z!K(wd3&0Ym)9F8PfK}33`_x){!(;%=pwsv`sH$qMeegEnyZCbgxS{$A;1-`5D5^l1 z5qQq&bow?9pgr*x;Wz!9@n;9{c^jam^R79xs0>A-2*5R`>mG)-2ip3?-ve-19u$Y- z{09<^0G*t-*51WIas(=1VnqPBCSatr;59kh@Q3(20nYT{a0c2rM}5z44TaEjdagln zRpuN2e2jx=51d%UVIe+q>FUjVd8eiR8voAGT$+TqqEF?%Z#+qSiodf8yb4lJ1Hy+# z>|6NIw#G&#JlvU+f-@-Ok{!H#JlJ7zqIPSWGE@H8#6Pp8uhuNLl_ zXt&}6Qnm$qr`dkVvQ_)$_)Of-+lbdAt`M9|^bo&i?0|dWEe?a}gBzcXd35ONgGhzx zhT+u;)rWiGWoTwiJjo@=+@V+@%Egm(1|Jgs9=}f%;=O*447gYEYax$UYieM+`8L6; z6V3*zQ4GK12Z%SrO8oEmHhBd%iO&XJ#_tV-^1iZXw%g(J0@miTW^ECAP3m9e)g;YG zM&+gB&4ivz#ErM*NKW^E=Ud!=fJP*v@ltVic<)}y42)kJ+i)V+#B|E4`SO!*>y4@l-^-YPL+pEQ#MQkXj-ss4X0oDuc1 z*5LPBgV}sX+yG^*RU-vO*}Sjoe$wx^1&2C2rS04rpCi)66Spz+sLy+Kc06s(N z-k*RlVFGuV>B=ddbo)7wxlLiFB6_qy@4-)z2YfcrHB!JNFvALiUrRt*MB@@E=$c8$ zBrrkmE3LKki(eDyeP;rnyF2te?Wc$XU$a6{8PS2KxM5_k!LNPZZZG;k9&ZV{)fA8? za6``|AMf|c=)>I{C3qV@ONq!Byl`E2ufeaw9YTDV#k~o8tx9Pu z$}hcRR}pq>?L-A1()fHTeU<8$-!W6qJ6372ze2iR=Tts$Jk~a~N35SPAA0k7Rr;3o zP;~=Nzf9dzdGK?ng6>>Q)MFCZe|XTXyHkI841TPw$0XpF;=Zq)>yco=;Kvqvgs+{j zw$Gduoy^A{H~|NK*i4UuK{dOLU#GlI`~Lm!nJ~N_DeSu5E`FV|oK~5AryEH=7)Exo zke{`WB%$n_>O+N0P=;2d@w2*wUz)Y^1Juti{$NWSGHC*3WGA=jl#JN{NT=Di(@E@@hEk7NX&k-!gw# z(tNQ8E*wfZ5AbuwjD%$-LhLdh8i$p6(an&TjCiS&67%16MHCLpcTg_ot?^iG#F8_h z_FWT=LrQ*0j&l~^jK8ddzwwqgK$5J~cx4;LXKyC!?a$I5N1*eq_}QOIH8JtIn^4#< zesgn??S~`CSTPT?QqGvoWT+(#(3u*3#4#|%5ZSTn@)N(YIXoNw5T6h1nKJw0SeRh@ zwBW3_Kezbch~{pehscqc`^`t51n!iD{xcMn|EgT;u~cVj&V+wRpMB-)e+E%57srlE`PK-7$@yFE1hwOgA4%EBv$-w zAI1PM&BEB_8E=1}psd6ns{Me1GD3fx`~wB~@NaQjBMN?ig6>ULcuvU+4gj2&|2LBq zrr6b$$G}l211S#Xshl}Tvk#z}jEg>WPhfm5fC4hE`6BHXC`i>uaWGG%!@u}k00pR7 zM;y>Iyo%2SP(a4%|1f2Y0Pd$U#5j_t8m4_QfN3(4f4JZiC`zHbIGSe|rQd+MD=H00n629tZRcT=)r8lyvPkt-pYZ)V4d0<|(v&h|dI2 z#M1tMT>AwSmXHBR0uWG@Qh4K_o`RcyfTEHj09^YGRFo6}AOjGnNb&nPnx~)w5Gbs* z_7Dg46hr`k!qmS%4(cgL0R{?NS_DK3Frcudmw*rg1PWW413(iHps=NFz_+_wSKTpcaO_%^^0|snFJvbD_Ypwko#^t}QwfDN{C;pu; zEWHVcYrueQD1q40q0H7A#-w~JmcX}MIPnZC1 z&ny7C(B3|d!+SX2=gWv)LN7oj0ANLT?)#teWrSerULs-w0M?VxyW`8&{j30hI+nh$ z9~JH`ol#nAzkTsQ0No@^0AGd#6HCR4`3c}cRe}ofdC8%ZN1YRJ{Wq|oxoN;x?wbGr zj7R-9;)tH;aX}D(8d__A#}PfzkMWrR3P?!Wlkehl0ThtXzK{N$KvfG8_RhG(7bq;D za+mI1KvfRQ&qnD!P}tHez}DL9_)Gvr%&pqJP#f|1OaMhJ>;;(AL_g_)GvrsE8*H>M2@lf5vA5C_qg>o@&y2FAe~KqL%gmFs|r=ST4P?S3O#sNLU?YjU0j4k0RaN!;RprV8ZAnG6RZG0|( z!DZC{>$2bYTmS{=4!<~{XW$+{prWPOf5t69Ks8Hm020D~Kv7GJe}wd3ps1zEKlVd> zE`Y&hIBP<$?c0(5;>9MDs-{09nB>_4IOSAU=GKR`9DwKs7qXHb7v;H#P z{$qsxCJg>FZY#x+Wn}%?_FaGH++M0JLTl}(_$*-WRH_@d$a4&n_dhp`;lH>2@&#B? zYwdj;3v&#S_@lRY{Kh8wJw6lItE{A-Vcz~r)8@r}(BAYIy@Jq@{mG%wLAB_{&Ob6#ue*j9&w&#;5*TvS%5$ z-hXL0@Y<039AKi>+S~Zqo@r;WvIJ3v_x+hG;aZWg?1K*qhEPfJzQ1Oge8vTb*SPHj6=Zezxl*@K zE-UZ(VwmCw;D!z_y}#nuC?$IjGwd2<*FRKO81(N`_<}Gga;MYoGe-*JE5+g3Z ztor*Tl;gpl`cji!H_6TF8*t5&!1jI7a3~5($IoJdx*@-NCD%otc^S69WfO*^J$n2M zE_J9pm)6?5_zPId;YPLt=%(Yc|5%x>{zZO>zlfzc6F@IM?e8)8_Yv=(s#ENg=m667 z2=$5cV}6cb?=Ui3C0+9^#QQx>#cG`4mp=G~)_0$O!D&qsu8}c_rJTi@zDgBU492f_ zSedQFB4%04p_fNyZvQnMg2%6Sc$rN^B8P2`bIOWEvQyWu`5=+c;w_yg0Yx217{Zer zol0xw5xGkcga0`_k}NK~pCdwmM;w((YvUD>tTsXCNkCDD{ij^wZaw$=;v_W&|Gj!; zTwU{Q&z9i8dN28f^b^yxmbj~-e7#nzskyx z7IXty>u~UzmYALhSP=}OXBFQypY-$ZJ`!d0tu^~!bN8XwoDpoxtHJI|e=evUn3sU* z%|`F#Fc49L4P@WU8+q$Thp7s-*b-b*k9Te{URTcvfF*xHP5|jz?C~{zYbcGGRY7Mx zCICWw#d%-Yy_o18t~Gezte_8t|Kjg_35S@Oxc9&fAG#k9-OC>?JsaU{;5n*4&>P4%DRb~N!Q2~sDE>O2waP=6 z(g)fr1ijsK(8Z+ijH#1=&xkmeIU?5+bZ-!m&8O2^DQHWhkf$aFCI|B*@N9~>miuhG zaOUd5%@T;RIsB*zLgmalY>a=dCym`6M0$eP>goKu@E=unvd_n3PA4Are4rPvDBjZ& z4fs3}sOiWsVZ)WZioXxwa1;mjLE+&h0v(LS-v@9=&L(_VtHaDdXmkNwQ-RNv5L0D7 z3n3d0h#WO27e1U}w-EwhF@=+ywYS%E0Q1!fkGXNliR|bkA{INyz(y3f1MHB$&L20+jx^oKdh*gPX1OjXEjW*gY zyxThINQF`H`vb5dogcVbPpbrN8&uiX9XH+p15@(~PYf#Q)#Pv~b6u2+;B5$DKcF%l rHeS{GpC^g0{JHc7peqgl9L4_toFKmNm!0!n00000NkvXXu0mjfYiC1X literal 0 HcmV?d00001 diff --git a/luarules/images/seismic_ping/seismic_atlas.png b/luarules/images/seismic_ping/seismic_atlas.png new file mode 100644 index 0000000000000000000000000000000000000000..c82fd3f638511e58f8a8c4cf544a7ff506cc1065 GIT binary patch literal 24106 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 0.5 then gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) -- Outer outlines - glFunc.Color(0.09, 0, 0, outerAlpha * 0.25) - for j = 0, 3 do - glFunc.PushMatrix() - glFunc.Rotate(rotation1, 0, 0, 1) - glFunc.Scale(outerRadius, outerRadius, 1) - glFunc.CallList(seismicPingDlists.outerOutlines[j]) - glFunc.PopMatrix() + if outerAlpha > 0.001 then + glFunc.Color(0.09, 0, 0, outerAlpha * 0.25) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.outerOutline, outerRadius * seismicOuterQuadScale, rotation1) end -- Middle outlines - glFunc.Color(0.09, 0, 0, middleAlpha * 0.25) - for j = 0, 2 do - glFunc.PushMatrix() - glFunc.Rotate(rotation2, 0, 0, 1) - glFunc.Scale(middleRadius, middleRadius, 1) - glFunc.CallList(seismicPingDlists.middleOutlines[j]) - glFunc.PopMatrix() + if middleAlpha > 0.001 then + glFunc.Color(0.09, 0, 0, middleAlpha * 0.25) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.middleOutline, middleRadius * seismicMiddleQuadScale, rotation2) end -- Inner outlines - glFunc.Color(0.07, 0, 0, innerAlpha * 0.25) - for j = 0, 1 do - glFunc.PushMatrix() - glFunc.Rotate(rotation3, 0, 0, 1) - glFunc.Scale(innerRadius, innerRadius, 1) - glFunc.CallList(seismicPingDlists.innerOutlines[j]) - glFunc.PopMatrix() + if innerAlpha > 0.001 then + glFunc.Color(0.07, 0, 0, innerAlpha * 0.25) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.innerOutline, innerRadius * seismicInnerQuadScale, rotation3) end end @@ -6416,33 +6421,21 @@ local function DrawSeismicPings() gl.Blending(GL.SRC_ALPHA, GL.ONE) -- Outer ring - 4 arcs rotating clockwise - glFunc.Color(1, 0.1, 0.09, outerAlpha) - for j = 0, 3 do - glFunc.PushMatrix() - glFunc.Rotate(rotation1, 0, 0, 1) - glFunc.Scale(outerRadius, outerRadius, 1) - glFunc.CallList(seismicPingDlists.outerArcs[j]) - glFunc.PopMatrix() + if outerAlpha > 0.001 then + glFunc.Color(1, 0.1, 0.09, outerAlpha) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.outerRing, outerRadius * seismicOuterQuadScale, rotation1) end -- Middle ring - 3 arcs rotating counter-clockwise - glFunc.Color(1, 0.22, 0.2, middleAlpha) - for j = 0, 2 do - glFunc.PushMatrix() - glFunc.Rotate(rotation2, 0, 0, 1) - glFunc.Scale(middleRadius, middleRadius, 1) - glFunc.CallList(seismicPingDlists.middleArcs[j]) - glFunc.PopMatrix() + if middleAlpha > 0.001 then + glFunc.Color(1, 0.22, 0.2, middleAlpha) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.middleRing, middleRadius * seismicMiddleQuadScale, rotation2) end -- Inner ring - 2 arcs rotating clockwise - glFunc.Color(1, 0.37, 0.33, innerAlpha) - for j = 0, 1 do - glFunc.PushMatrix() - glFunc.Rotate(rotation3, 0, 0, 1) - glFunc.Scale(innerRadius, innerRadius, 1) - glFunc.CallList(seismicPingDlists.innerArcs[j]) - glFunc.PopMatrix() + if innerAlpha > 0.001 then + glFunc.Color(1, 0.37, 0.33, innerAlpha) + DrawSeismicTexturedQuad(seismicPingAtlasSprites.innerRing, innerRadius * seismicInnerQuadScale, rotation3) end -- Center dot (shrinks from large to small with fade in/out) @@ -6457,12 +6450,10 @@ local function DrawSeismicPings() end local centerAlpha = math.max(0, centerAlphaMultiplier * 0.6) glFunc.Color(1, 0.25, 0.23, centerAlpha) - glFunc.PushMatrix() - glFunc.Scale(centerScale, centerScale, 1) - glFunc.CallList(seismicPingDlists.centerCircle) - glFunc.PopMatrix() + DrawSeismicTexturedQuad(seismicPingAtlasSprites.centerDot, centerScale * seismicCenterQuadScale) end + glFunc.Texture(false) gl.Blending(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA) glFunc.PopMatrix() i = i + 1 @@ -7555,110 +7546,6 @@ end -- Callins ---------------------------------------------------------------------------------------------------- --- Helper: Draw a thick arc as geometry (for display list creation) -local function DrawThickArcVertices(innerRadius, outerRadius, startAngle, endAngle, segments) - local angleStep = (endAngle - startAngle) / segments - local cos, sin = math.cos, math.sin - for i = 0, segments - 1 do - local angle1 = startAngle + i * angleStep - local angle2 = startAngle + (i + 1) * angleStep - local cos1, sin1 = cos(angle1), sin(angle1) - local cos2, sin2 = cos(angle2), sin(angle2) - glFunc.Vertex(cos1 * innerRadius, sin1 * innerRadius, 0) - glFunc.Vertex(cos1 * outerRadius, sin1 * outerRadius, 0) - glFunc.Vertex(cos2 * outerRadius, sin2 * outerRadius, 0) - glFunc.Vertex(cos2 * innerRadius, sin2 * innerRadius, 0) - end -end - --- Create display lists for seismic ping rotating arcs -local function CreateSeismicPingDlists() - local pi = math.pi - local pi2 = pi * 2 - local baseRadius = 16 - local baseThickness = 2.4 - - -- Proportional thicknesses (relative to unit radius 1.0) - local outerThicknessRatio = baseThickness * 1.05 / baseRadius - local middleThicknessRatio = baseThickness * 0.8 / baseRadius - local innerThicknessRatio = baseThickness * 1 / baseRadius - local centerThicknessRatio = baseThickness * 1.8 / baseRadius - local outlineExtra = 0.02 - - -- Outer arcs: 4 arcs, 60 degrees each - local outerInner = 1.08 - outerThicknessRatio / 2 - local outerOuter = 1.08 + outerThicknessRatio / 2 - for i = 0, 3 do - local startAngle = (i * 90) * pi / 180 - local arcLength = 60 * pi / 180 - -- Outline - seismicPingDlists.outerOutlines[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, outerInner - outlineExtra, outerOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 12) - end) - -- Main arc - seismicPingDlists.outerArcs[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, outerInner, outerOuter, startAngle, startAngle + arcLength, 12) - end) - end - - -- Middle arcs: 3 arcs, 80 degrees each, at 0.85 of unit radius - local middleRadiusRatio = 0.85 - local middleInner = middleRadiusRatio - middleThicknessRatio / 2 - local middleOuter = middleRadiusRatio + middleThicknessRatio / 2 - for i = 0, 2 do - local startAngle = (i * 120) * pi / 180 - local arcLength = 80 * pi / 180 - -- Outline - seismicPingDlists.middleOutlines[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, middleInner - outlineExtra, middleOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 12) - end) - -- Main arc - seismicPingDlists.middleArcs[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, middleInner, middleOuter, startAngle, startAngle + arcLength, 12) - end) - end - - -- Inner arcs: 2 arcs, 120 degrees each, at 0.66 of unit radius - local innerRadiusRatio = 0.66 - local innerInner = innerRadiusRatio - innerThicknessRatio / 2 - local innerOuter = innerRadiusRatio + innerThicknessRatio / 2 - for i = 0, 1 do - local startAngle = (i * 180) * pi / 180 - local arcLength = 120 * pi / 180 - -- Outline - seismicPingDlists.innerOutlines[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, innerInner - outlineExtra, innerOuter + outlineExtra, startAngle - 0.02, startAngle + arcLength + 0.02, 16) - end) - -- Main arc - seismicPingDlists.innerArcs[i] = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, innerInner, innerOuter, startAngle, startAngle + arcLength, 16) - end) - end - - -- Center circle: full circle - local centerInner = 1 - centerThicknessRatio / 1.3 - local centerOuter = 1.25 + centerThicknessRatio / 1.3 - seismicPingDlists.centerCircle = gl.CreateList(function() - glFunc.BeginEnd(glConst.QUADS, DrawThickArcVertices, centerInner, centerOuter, 0, pi2, 20) - end) -end - --- Delete seismic ping display lists -local function DeleteSeismicPingDlists() - for i = 0, 3 do - if seismicPingDlists.outerArcs[i] then gl.DeleteList(seismicPingDlists.outerArcs[i]) end - if seismicPingDlists.outerOutlines[i] then gl.DeleteList(seismicPingDlists.outerOutlines[i]) end - end - for i = 0, 2 do - if seismicPingDlists.middleArcs[i] then gl.DeleteList(seismicPingDlists.middleArcs[i]) end - if seismicPingDlists.middleOutlines[i] then gl.DeleteList(seismicPingDlists.middleOutlines[i]) end - end - for i = 0, 1 do - if seismicPingDlists.innerArcs[i] then gl.DeleteList(seismicPingDlists.innerArcs[i]) end - if seismicPingDlists.innerOutlines[i] then gl.DeleteList(seismicPingDlists.innerOutlines[i]) end - end - if seismicPingDlists.centerCircle then gl.DeleteList(seismicPingDlists.centerCircle) end -end -- Register (or re-register) WG['minimap'] API for full compatibility with widgets -- expecting the original minimap API. Called from Initialize and again from DrawScreen @@ -8070,9 +7957,6 @@ end function widget:Initialize() RebuildAllUnitsCache() - -- Create seismic ping display lists - CreateSeismicPingDlists() - drawData.unitOutlineList = gl.CreateList(function() glFunc.BeginEnd(GL.LINE_LOOP, function() glFunc.Vertex( 1, 0, 1) @@ -9247,8 +9131,6 @@ function widget:Shutdown() gl.DeleteList(drawData.unitOutlineList) gl.DeleteList(drawData.radarDotList) - DeleteSeismicPingDlists() - if shaders.los then gl.DeleteShader(shaders.los) shaders.los = nil @@ -18996,9 +18878,13 @@ function widget:UnitSeismicPing(x, y, z, strength, allyTeam, unitID, unitDefID) local myAllyTeam = Spring.GetMyAllyTeamID() local spec, fullview = Spring.GetSpectatingState() - local unitAllyTeam = unitID and Spring.GetUnitAllyTeam(unitID) or allyTeam + local unitAllyTeam = unitID and Spring.GetUnitAllyTeam(unitID) + + if (spec or allyTeam == myAllyTeam) and ((not unitAllyTeam) or unitAllyTeam ~= allyTeam) then + if spec and not fullview then + if allyTeam ~= myAllyTeam then return end + end - if (spec or allyTeam == myAllyTeam) and unitAllyTeam ~= allyTeam then -- Calculate ping radius based on strength (strength is typically 1-10) -- Use larger base radius for visibility local maxRadius = 100 + math.min(strength, 20) * 15