function widget:GetInfo() return { name = "Decloak Range", desc = "Display decloak range around cloaked units. v2", author = "banana_Ai, dahn, GoogleFrog (rewrite), ashdnazg (effectively), Helwor (implement sphere and rewrite/optimization)", date = "15 Jul 2016", license = "GNU GPL v2", layer = 0, enabled = true, } end VFS.Include("LuaRules/Utilities/glVolumes.lua") local Echo = Spring.Echo local gl = gl local GL = GL local Spring = Spring local spGetUnitDefID = Spring.GetUnitDefID local spGetUnitPosition = Spring.GetUnitPosition local spGetUnitRulesParam = Spring.GetUnitRulesParam local spGetUnitIsCloaked = Spring.GetUnitIsCloaked local spGetSelectedUnits = Spring.GetSelectedUnits local spGetUnitIsCloaked = Spring.GetUnitIsCloaked local spGetSelectedUnitsSorted = Spring.GetSelectedUnitsSorted local spIsSphereInView = Spring.IsSphereInView local glColor = gl.Color local glCallList = gl.CallList local drawAlpha = 0.17 local disabledColor = { 0.9,0.5,0.3, drawAlpha} local cloakedColor = { 0.4, 0.4, 0.9, drawAlpha} -- drawAlpha on purpose! local disabledColor_less = { 0.9,0.5,0.3, drawAlpha/2} local cloakedColor_less = { 0.4, 0.4, 0.9, drawAlpha/2} -- drawAlpha on purpose! local decloakDist = setmetatable({}, {__index = function(self, defID) rawset(self, defID, UnitDefs[defID].decloakDistance or false) end}) local currentSelection = false local selectionMap = false local selectionCanCloak = false local myPlayerID, myTeamID local spec, fullview = Spring.GetSpectatingState() local merged, useSphere = true, false -- defaults options_path = 'Settings/Interface/Defence and Cloak Ranges' options_order = { "label", "drawranges", "mergeCircles", "useSphere", } options = { label = { type = 'label', name = 'Decloak Ranges' }, drawranges = { name = 'Draw decloak ranges', type = 'bool', value = true, OnChange = function (self) if self.value then widgetHandler:UpdateCallIn("DrawWorldPreUnit") else widgetHandler:RemoveCallIn("DrawWorldPreUnit") end end }, mergeCircles = { name = "Draw merged cloak circles", desc = "Merge overlapping grid circle visualisation. Does not work on older hardware and should automatically disable.", type = 'bool', value = merged, OnChange = function(self) merged = self.value end, }, useSphere = { name = "Use Sphere", desc = "Use sphere instead of circle on ground (real decloak range is spherical)", type = 'bool', value = useSphere, path = 'Hel-K/' ..widget.GetInfo().name, OnChange = function(self) useSphere = self.value end, }, } ------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------- local function GetSubjects(wantMerged) local cloakeds, poses = {}, {} local pass1, pass2 if wantMerged then pass1, pass2 = {}, {} else pass1 = {} end for i = 1, #currentSelection do local unitID = currentSelection[i] local unitDefID = spGetUnitDefID(unitID) if unitDefID then local cloaked = spGetUnitIsCloaked(unitID) if cloaked or spGetUnitRulesParam(unitID, "wantcloak") == 1 or spGetUnitRulesParam(unitID, "areacloaked") == 1 then local radius = spGetUnitRulesParam(unitID, "areacloaked_radius") if not radius or radius <= 0 then radius = spGetUnitRulesParam(unitID, "comm_decloak_distance") end if not radius or radius <= 0 then radius = decloakDist[unitDefID] end if radius and radius > 0 then local x, y, z, _, y2 = spGetUnitPosition(unitID, true) if spIsSphereInView(x, y2, z, radius) then poses[unitID] = {x, y, z, y2} if pass2 and not cloaked then pass2[unitID] = radius else pass1[unitID] = radius cloakeds[unitID] = cloaked end end end end end end return pass1, pass2, cloakeds, poses end -- Drawing local function SphereVertex(x, y, z, neg) if neg then gl.Normal(-x, -y, -z) else gl.Normal(x, y, z) end gl.Vertex(x, y, z) end local function Sphere(divs, arcs, neg) local cos, sin = math.cos, math.sin local PI = math.pi local twoPI = PI * 2 local divRads = PI / divs local minRad = sin(divRads) -- top gl.BeginEnd(GL.TRIANGLE_FAN, function() SphereVertex(0, 1, 0, neg) local bot = cos(divRads) local botRad = sin(divRads) for i = 0, arcs do local a = i * twoPI / arcs SphereVertex(sin(a) * botRad, bot, cos(a) * botRad, neg) end end) -- sides for d = 1, divs - 2 do -- -- gl.BeginEnd(GL.QUAD_STRIP, function() gl.BeginEnd(GL.TRIANGLE_STRIP, function() -- much faster than GL.QUAD_STRIP local topRads = divRads * (d + 0) local botRads = divRads * (d + 1) local top = cos(topRads) local bot = cos(botRads) local topRad = sin(topRads) local botRad = sin(botRads) for i = 0, arcs do local a = i * twoPI / arcs SphereVertex(sin(a) * topRad, top, cos(a) * topRad, neg) SphereVertex(sin(a) * botRad, bot, cos(a) * botRad, neg) end end) end -- bottom gl.BeginEnd(GL.TRIANGLE_FAN, function() SphereVertex(0, -1, 0, neg) for i = 0, arcs do local a = -i * twoPI / arcs SphereVertex(sin(a) * minRad, -cos(divRads), cos(a) * minRad, neg) end end) end local list = {} list.sphere = gl.CreateList(Sphere, 16, 32, false) list.mergeVolumes = gl.CreateList( function() -- Draw only where it has not been drawn gl.Clear(GL.STENCIL_BUFFER_BIT, 0) gl.DepthMask(false) gl.StencilTest(true) gl.DepthTest(true) gl.StencilOp(GL.KEEP, GL.KEEP, GL.INCR) gl.StencilMask(1) gl.StencilFunc(GL.EQUAL, 0, 1) end ) list.mergeVolumesEnd = gl.CreateList( function () gl.DepthTest(false) gl.StencilTest(false) gl.StencilMask(0xff) gl.Clear(GL.STENCIL_BUFFER_BIT) end ) function gl.Utilities.DrawSimpleSphere(x, y, z, radius) gl.PushMatrix() gl.Translate(x, y, z) gl.Scale(radius, radius, radius) glCallList(list.sphere) gl.PopMatrix() end -- copied from glVolume (couldn't find a way to do it per batch) -- merge the ground inprint of volume list.stencilMergeInp = gl.CreateList( function() gl.DepthMask(false) if (gl.DepthClamp) then gl.DepthClamp(true) end gl.StencilTest(true) gl.Culling(false) gl.DepthTest(true) gl.ColorMask(false, false, false, false) gl.StencilOp(GL.KEEP, GL.INVERT, GL.KEEP) gl.StencilMask(1) gl.StencilFunc(GL.ALWAYS, 0, 1) end ) list.stencilMergeInpApply = gl.CreateList( function() gl.Culling(GL.FRONT) gl.DepthTest(false) gl.ColorMask(true, true, true, true) gl.StencilOp(GL.KEEP, GL.INCR, GL.INCR) gl.StencilMask(3) gl.StencilFunc(GL.EQUAL, 1, 3) end ) list.stencilMergeInpEnd = gl.CreateList( function() if (gl.DepthClamp) then gl.DepthClamp(false) end gl.StencilTest(false) -- gl.DepthTest(true) gl.Culling(false) end ) function MergeInprint(dlist) glCallList(list.stencilMergeInp) glCallList(dlist) glCallList(list.stencilMergeInpApply) glCallList(dlist) glCallList(list.stencilMergeInpEnd) end function gl.Utilities.DrawMergedSphereInprint(x, y, z, radius) -- Draw ground inprint of spheres gl.PushMatrix() gl.Translate(x, y, z) gl.Scale(radius, radius, radius) MergeInprint(list.sphere) gl.PopMatrix() end local DrawSphereInprint = gl.Utilities.DrawMergedSphereInprint local function DrawDecloakRanges(pass, cloakeds, poses) if not next(pass) then return end local merged = merged and next(pass, next(pass)) local drawGroundCircle, DrawSphere = false, false if useSphere and gl.Utilities.DrawSimpleSphere then DrawSphere = gl.Utilities.DrawSimpleSphere else drawGroundCircle = options.mergeCircles.value and gl.Utilities.DrawMergedGroundCircle or gl.Utilities.DrawGroundCircle end if DrawSphere --[[and merged--]] then for unitID, radius in pairs(pass) do glColor(cloakeds[unitID] and cloakedColor or disabledColor) local pos = poses[unitID] DrawSphereInprint(pos[1], pos[4], pos[3], radius) end end if DrawSphere then gl.BlendFuncSeparate(GL.SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA, GL.ONE_MINUS_DST_ALPHA) if merged then glCallList(list.mergeVolumes) else gl.Culling(GL.BACK) gl.DepthTest(true) end end for unitID, radius in pairs(pass) do if DrawSphere then glColor(cloakeds[unitID] and cloakedColor_less or disabledColor_less) else glColor(cloakeds[unitID] and cloakedColor or disabledColor) end local pos = poses[unitID] if DrawSphere then DrawSphere(pos[1] ,pos[4] , pos[3], radius) else drawGroundCircle(pos[1], pos[3], radius) end end if DrawSphere then gl.BlendFuncSeparate(GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ZERO) if merged then glCallList(list.mergeVolumesEnd) else gl.Clear(GL.STENCIL_BUFFER_BIT, 0) end else gl.Clear(GL.STENCIL_BUFFER_BIT, 0) end gl.DepthTest(false) end function widget:CommandsChanged() currentSelection = spGetSelectedUnits() selectionCanCloak = false selectionMap = {} for i = 1, #currentSelection do local unitID = currentSelection[i] selectionMap[unitID] = true if not selectionCanCloak then if spGetUnitDefID(unitID) then if spGetUnitIsCloaked(unitID) or spGetUnitRulesParam(unitID, "wantcloak") == 1 or spGetUnitRulesParam(unitID, "areacloaked") == 1 then selectionCanCloak = true end end end end end function widget:UnitCloaked(curID, curUnitDefID, teamID) if selectionCanCloak or teamID ~= myTeamID and not (spec or fullview) then return end selectionCanCloak = selectionMap[curID] end local function DrawRanges() local pass1, pass2, cloakeds, poses = GetSubjects(merged) DrawDecloakRanges(pass1, cloakeds, poses) if pass2 then DrawDecloakRanges(pass2, cloakeds, poses) end end function DrawFunc() if Spring.IsGUIHidden() then return end if selectionCanCloak then DrawRanges() glColor(1,1,1,1) end end function widget:PlayerChanged(playerID) if playerID == myPlayerID then myTeamID = Spring.GetMyTeamID() spec, fullview = Spring.GetSpectatingState() end end function widget:DrawWorldPreUnit() DrawFunc() end function widget:Initialize() myPlayerID = Spring.GetMyPlayerID() widget:PlayerChanged(myPlayerID) widget:CommandsChanged() end function widget:Shutdown() for _, l in pairs(list) do gl.DeleteList(l) end end