From ad93c4ce01b99e0fd971c53a89c1fdd25834fcd7 Mon Sep 17 00:00:00 2001 From: Deyan Dobromirov Date: Sat, 30 Mar 2024 14:07:53 +0200 Subject: [PATCH] Extract transfrom from a DB model only if is selected by the user (#56) Fixed: Model transform points not being initialized Added: Rearranged the think hook for models validation Added: Comment classifications about luapad and how to close its panel Added: Populate origin/angle transform when spawning ghosts and snapping pieces Added: Selection search point slot being taken with priority Updated: Spawn margin sends message to the user --- lua/autorun/trackassembly_init.lua | 14 +- lua/trackassembly/trackasmlib.lua | 167 +++++++++++------- .../gmod_tool/stools/trackassembly.lua | 34 ++-- 3 files changed, 129 insertions(+), 86 deletions(-) diff --git a/lua/autorun/trackassembly_init.lua b/lua/autorun/trackassembly_init.lua index 1223644d..cc6b5f22 100644 --- a/lua/autorun/trackassembly_init.lua +++ b/lua/autorun/trackassembly_init.lua @@ -84,7 +84,7 @@ local asmlib = trackasmlib; if(not asmlib) then -- Module present ------------ CONFIGURE ASMLIB ------------ asmlib.InitBase("track","assembly") -asmlib.SetOpVar("TOOL_VERSION","8.741") +asmlib.SetOpVar("TOOL_VERSION","8.749") asmlib.SetIndexes("V" ,1,2,3) asmlib.SetIndexes("A" ,1,2,3) asmlib.SetIndexes("WV",1,2,3) @@ -945,11 +945,11 @@ if(CLIENT) then if(luapad.Frame) then luapad.Frame:SetVisible(true) else asmlib.SetAsmConvar(oPly, "*luapad", gsToolNameL) end luapad.AddTab("["..defTab.Nick.."]"..pnSelf:GetText(), fileRead(sFile, "DATA"), sDsv); - if(defTab.Nick == "PIECES") then local sCat = fDSV:format(sPref, "CATEGORY") - if(fileExists(sCat,"DATA")) then + if(defTab.Nick == "PIECES") then -- Load the categoty provider for this DSV + local sCat = fDSV:format(sPref, "CATEGORY"); if(fileExists(sCat,"DATA")) then luapad.AddTab("[CATEGORY]"..pnSelf:GetText(), fileRead(sCat, "DATA"), sDsv); - end - end + end -- This is done so we can distinguish between luapad and other panels + end -- Luapad is designed not to be closed so we need to make it invisible luapad.Frame:SetVisible(true); luapad.Frame:Center() luapad.Frame:MakePopup(); conElements:Push({luapad.Frame}) end @@ -957,7 +957,7 @@ if(CLIENT) then function() fileDelete(sFile) asmlib.LogInstance("Delete "..asmlib.GetReport1(sFile), sLog..".Button") if(defTab.Nick == "PIECES") then local sCat = fDSV:format(sPref, "CATEGORY") - if(fileExists(sCat,"DATA")) then fileDelete(sCat) + if(fileExists(sCat,"DATA")) then fileDelete(sCat) -- Delete category when present asmlib.LogInstance("Deleted "..asmlib.GetReport1(sCat), sLog..".Button") end end; pnManage:Remove() end @@ -965,7 +965,7 @@ if(CLIENT) then while(tOptions[iO]) do local sO = tostring(iO) local sDescr = languageGetPhrase("tool."..gsToolNameL..".pn_externdb_bt"..sO) pnMenu:AddOption(sDescr, tOptions[iO]):SetIcon(asmlib.ToIcon("pn_externdb_bt"..sO)) - iO = iO + 1 -- Loop trough the functions list and add to the menu + iO = iO + 1 -- Loop trough the functions list and add them to the menu end; pnMenu:Open() end else asmlib.LogInstance("File missing ["..tostring(iP).."]",sLog..".Button") end diff --git a/lua/trackassembly/trackasmlib.lua b/lua/trackassembly/trackasmlib.lua index 6f173b02..6e015421 100644 --- a/lua/trackassembly/trackasmlib.lua +++ b/lua/trackassembly/trackasmlib.lua @@ -2146,23 +2146,6 @@ function MakeEntityNone(sModel, vPos, aAng) local eNone LogInstance("Create "..GetReport2(eNone:EntIndex(),sModel)); return eNone end ---[[ - * Locates an active point on the piece offset record. - * This function is used to check the correct offset and return it. - * It also returns the normalized active point ID if needed - * oRec > Record structure of a track piece - * ivPoID > The POA offset ID to check and locate -]]-- -function LocatePOA(oRec, ivPoID) - if(not oRec) then LogInstance("Missing record"); return nil end - if(not oRec.Offs) then LogInstance("Missing offsets for <"..tostring(oRec.Slot)..">"); return nil end - local iPoID = tonumber(ivPoID); if(iPoID) then iPoID = mathFloor(iPoID) - else LogInstance("ID mismatch "..GetReport(ivPoID)); return nil end - local stPOA = oRec.Offs[iPoID]; if(not IsHere(stPOA)) then - LogInstance("Missing ID #"..tostring(iPoID).." for <"..tostring(oRec.Slot)..">"); return nil end - return stPOA, iPoID -end - function ReloadPOA(nXP,nYY,nZR) local arPOA = GetOpVar("ARRAY_DECODEPOA") arPOA[1] = (tonumber(nXP) or 0) @@ -2211,7 +2194,7 @@ function TransferPOA(tData,sMode) LogInstance("Mode mismatch "..GetReport(sMode)); return nil end local arPOA = GetOpVar("ARRAY_DECODEPOA") local ctA, ctB, ctC = GetIndexes(sMode); if(not (ctA and ctB and ctC)) then - LogInstance("Missed offset mode <"..sMode..">"); return nil end + LogInstance("Missed offset mode "..GetReport(sMode)); return nil end tData[ctA], tData[ctB], tData[ctC] = arPOA[1], arPOA[2], arPOA[3]; return arPOA end @@ -2224,7 +2207,7 @@ function DecodePOA(sStr) for iD = 1, arPOA.Size do -- Apply on all components local nCom = tonumber(atPOA[iD]) -- Is the data really a number if(not IsHere(nCom)) then nCom = 0 -- If not write zero and report it - LogInstance("Mismatch <"..sStr..">") end; arPOA[iD] = nCom + LogInstance("Mismatch "..GetReport(sStr)) end; arPOA[iD] = nCom end; return arPOA -- Return the converted string to POA end @@ -2250,6 +2233,67 @@ function GetTransformOA(sModel, sKey) return mTOA.Pos, mTOA.Ang -- The function must return transform origin and angle end +--[[ + * Locates an active point on the piece offset cache record. + * This function is used to check the correct offset and return it. + * It also returns the normalized active point ID if needed + * Updates current record origin and angle when they use attachments + * oRec > Record structure of a track piece stored in the cache + * ivPoID > The POA offset ID to be checked and located + * Returns a cache record and the converted to number offset ID +]]-- +function LocatePOA(oRec, ivPoID) + if(not oRec) then LogInstance("Missing record"); return nil end + if(not oRec.Offs) then LogInstance("Missing offsets for "..GetReport(oRec.Slot)); return nil end + local iPoID = tonumber(ivPoID); if(iPoID) then iPoID = mathFloor(iPoID) + else LogInstance("ID mismatch "..GetReport(ivPoID)); return nil end + local stPOA = oRec.Offs[iPoID]; if(not IsHere(stPOA)) then + LogInstance("Missing ID "..GetReport2(iPoID, oRec.Slot)); return nil end + if(oRec.Tran) then oRec.Tran = nil -- Transforming has started + local sE = GetOpVar("OPSYM_ENTPOSANG") -- Extract transform from model + local sD = GetOpVar("OPSYM_DISABLE") -- Use for searched hit point disabled + for ID = 1, oRec.Size do local tOA = oRec.Offs[ID] -- Index current offset + local sP, sO, sA = tOA.P.Slot, tOA.O.Slot, tOA.A.Slot -- Localize transform index + ---------- Origin ---------- + if(sO and sO:sub(1,1) == sE) then -- POA origin must extracted from the model + local sO = sO:sub(2, -1) -- Read origin transform ID and try to index + local vO, aA = GetTransformOA(oRec.Slot, sO) -- Read transform position/angle + if(IsHere(vO)) then ReloadPOA(vO[cvX], vO[cvY], vO[cvZ]) -- Load origin into POA + else -- Try decoding the transform origin when not applicable + if(IsNull(sO) or IsBlank(sO)) then ReloadPOA() else + if(not DecodePOA(sO)) then LogInstance("Origin mismatch "..GetReport2(ID, oRec.Slot)) end + end end -- Decode the transformation when is not null or empty string + if(not IsHere(TransferPOA(tOA.O, "V"))) then + LogInstance("Origin transfer fail "..GetReport(ID, oRec.Slot)) end + LogInstance("Origin transform from model "..GetReport3(ID, sO, StringPOA(tOA.O, "V"))) + end -- Transform origin is decoded from the model and stored in the cache + ---------- Angle ---------- + if(sA and sA:sub(1,1) == sE) then -- POA angle must extracted from the model + local sA = sA:sub(2, -1) -- Read angle transform ID and try to index + local vO, aA = GetTransformOA(oRec.Slot, sA) -- Read transform position/angle + if(IsHere(aA)) then ReloadPOA(aA[caP], aA[caY], aA[caR]) -- Load angle into POA + else -- Try decoding the transform angle when not applicable + if(IsNull(sA) or IsBlank(sA)) then ReloadPOA() else + if(not DecodePOA(sA)) then LogInstance("Angle mismatch "..GetReport2(ID, oRec.Slot)) end + end end -- Decode the transformation when is not null or empty string + if(not IsHere(TransferPOA(tOA.A, "A"))) then + LogInstance("Angle mismatch fail "..GetReport2(ID, oRec.Slot)) end + LogInstance("Angle transform from model "..GetReport3(ID, sA, StringPOA(tOA.A, "A"))) + end -- Transform angle is decoded from the model and stored in the cache + ---------- Point ---------- + if(sP:sub(1,1) == sD) then -- Check whenever point is disabled + ReloadPOA(tOA.O[cvX], tOA.O[cvY], tOA.O[cvZ]) -- Override with the origin + else -- When the point is disabled take the origin otherwise try to process it + if(IsNull(sP) or IsBlank(sP)) then -- In case of empty value or null use the origin + ReloadPOA(tOA.O[cvX], tOA.O[cvY], tOA.O[cvZ]) -- Override with the origin + else -- When the point is empty use the origin otherwise decode the value + if(not DecodePOA(sP)) then LogInstance("Point mismatch "..GetReport2(ID, oRec.Slot)) end + end -- The point is already decoded and ready to be populated in the cache + end; if(not IsHere(TransferPOA(tOA.P, "V"))) then LogInstance("Point transfer fail"); return nil end + end -- Loop and transform all the POA configuration at once. Game model slot will be taken + end; return stPOA, iPoID +end + function RegisterPOA(stData, ivID, sP, sO, sA) local sNull = GetOpVar("MISS_NOSQL"); if(not stData) then LogInstance("Cache record invalid"); return nil end @@ -2274,40 +2318,34 @@ function RegisterPOA(stData, ivID, sP, sO, sA) end; local sE, sD = GetOpVar("OPSYM_ENTPOSANG"), GetOpVar("OPSYM_DISABLE") ---------- Origin ---------- if(sO:sub(1,1) == sD) then ReloadPOA() else - if(sO:sub(1,1) == sE) then tOffs.O.Slot = sO; sO = sO:sub(2,-1) - local vO, aA = GetTransformOA(stData.Slot, sO) - if(IsHere(vO)) then ReloadPOA(vO[cvX], vO[cvY], vO[cvZ]) - else -- Decode the transform origin and angle when not applicable - if(IsNull(sO) or IsBlank(sO)) then ReloadPOA() else - if(not DecodePOA(sO)) then LogInstance("Origin mismatch ["..iID.."]"..stData.Slot) end - end end -- Decode the transformation when is not null or empty string + if(sO:sub(1,1) == sE) then -- To be decoded on spawn via locating + stData.Tran = true; ReloadPOA(); tOffs.O.Slot = sO -- Store transform + LogInstance("Origin transform "..GetReport2(sO, stData.Slot)) elseif(IsNull(sO) or IsBlank(sO)) then ReloadPOA() else - if(not DecodePOA(sO)) then LogInstance("Origin mismatch ["..iID.."]"..stData.Slot) end + if(not DecodePOA(sO)) then LogInstance("Origin mismatch "..GetReport2(iID, stData.Slot)) end end - end; if(not IsHere(TransferPOA(tOffs.O, "V"))) then LogInstance("Origin mismatch"); return nil end + end; if(not IsHere(TransferPOA(tOffs.O, "V"))) then LogInstance("Origin transfer fail"); return nil end ---------- Angle ---------- if(sA:sub(1,1) == sD) then ReloadPOA() else - if(sA:sub(1,1) == sE) then tOffs.A.Slot = sA; sA = sA:sub(2,-1) - local vO, aA = GetTransformOA(stData.Slot, sA) - if(IsHere(aA)) then ReloadPOA(aA[caP], aA[caY], aA[caR]) - else -- Decode the transform origin and angle when not applicable - if(IsNull(sA) or IsBlank(sA)) then ReloadPOA() else - if(not DecodePOA(sA)) then LogInstance("Angle mismatch ["..iID.."]"..stData.Slot) end - end end -- Decode the transformation when is not null or empty string + if(sA:sub(1,1) == sE) then -- To be decoded on spawn via locating + stData.Tran = true; ReloadPOA(); tOffs.A.Slot = sA -- Store transform + LogInstance("Angle transform "..GetReport2(sA, stData.Slot)) elseif(IsNull(sA) or IsBlank(sA)) then ReloadPOA() else - if(not DecodePOA(sA)) then LogInstance("Angle mismatch ["..iID.."]"..stData.Slot) end + if(not DecodePOA(sA)) then LogInstance("Angle mismatch "..GetReport2(iID, stData.Slot)) end end - end; if(not IsHere(TransferPOA(tOffs.A, "A"))) then LogInstance("Angle mismatch"); return nil end + end; if(not IsHere(TransferPOA(tOffs.A, "A"))) then LogInstance("Angle transfer fail"); return nil end ---------- Point ---------- - if(sP:sub(1,1) == sD) then + if(tOffs.O.Slot) then -- Origin transform point trigger + stData.Tran = true; ReloadPOA(); tOffs.P.Slot = sP + elseif(sP:sub(1,1) == sD) then -- Point is disabled ReloadPOA(tOffs.O[cvX], tOffs.O[cvY], tOffs.O[cvZ]) else -- when the point is disabled take the origin if(IsNull(sP) or IsBlank(sP)) then ReloadPOA(tOffs.O[cvX], tOffs.O[cvY], tOffs.O[cvZ]) else -- When the point is empty use the origin otherwise decode the value - if(not DecodePOA(sP)) then LogInstance("Point mismatch ["..iID.."]"..stData.Slot) end + if(not DecodePOA(sP)) then LogInstance("Point mismatch "..GetReport2(iID, stData.Slot)) end end - end; if(not IsHere(TransferPOA(tOffs.P, "V"))) then LogInstance("Point mismatch"); return nil end + end; if(not IsHere(TransferPOA(tOffs.P, "V"))) then LogInstance("Point transfer fail"); return nil end return tOffs end @@ -2647,7 +2685,7 @@ function CreateTable(sTable,defTab,bDelete,bReload) local qtCol = qtDef[iD]; if(qtCol) then return qtCol[1] end LogInstance("Mismatch "..GetReport(vD), tabDef.Nick); return nil end - -- Returns the colomn information by the given ID > 0 + -- Returns the column information by the given ID > 0 function self:GetColumnInfo(vD, vI) local iD = (tonumber(vD) or 0) local qtDef = self:GetDefinition() @@ -2857,7 +2895,7 @@ function CreateTable(sTable,defTab,bDelete,bReload) local qtCmd = self:GetCommand() qtCmd.Commit = "COMMIT;"; return self end - -- Build create/drop/delete statement table of statemenrts + -- Build create/drop/delete statement table of statements function self:Create() local qtDef = self:GetDefinition() local qtCmd, iInd = self:GetCommand(), 1; qtCmd.STMT = "Create" @@ -3215,7 +3253,7 @@ function ExportPanelDB(stPanel, bExp, makTab, sFunc) if(not cT or cT ~= sT) then -- Category has been changed F:Write("# Categorize [ "..sMoDB.." ]("..sT.."): "..tostring(WorkshopID(sT) or sMiss)) F:Write("\n"); cT = sT -- Cache category name - end -- Otherwise just wite down the piece active point + end -- Otherwise just write down the piece active point F:Write("\""..sM.."\""..symSep.."\""..sT.."\""..symSep.."\""..sN.."\"") F:Write("\n"); iCnt = iCnt + 1 end; F:Flush(); F:Close() @@ -3360,7 +3398,8 @@ end function ExportPOA(stPOA,sOut) local sE = tostring(sOut or GetOpVar("MISS_NOSQL")) - local sP = (IsEqualPOA(stPOA.P, stPOA.O) and sE or StringPOA(stPOA.P, "V")) + local sP = (IsZeroPOA(stPOA.P, "V") and sE or StringPOA(stPOA.P, "V")) + sP = (stPOA.P.Slot and stPOA.P.Slot or sP) local sO = (IsZeroPOA(stPOA.O, "V") and sE or StringPOA(stPOA.O, "V")) sO = (stPOA.O.Slot and stPOA.O.Slot or sO) local sA = (IsZeroPOA(stPOA.A, "A") and sE or StringPOA(stPOA.A, "A")) @@ -3606,7 +3645,7 @@ function SynchronizeDSV(sTable, tData, bRepl, sPref, sDelim) vID = tRow[iD-1]; nID, sID = tonumber(vID), tostring(vID) nID = (nID or (sID:sub(1,1) == symOff and iCnt or 0)) -- Where the line ID must be read from. Skip the key itself and convert the disabled value - if(iCnt ~= nID) then -- Validate the line ID being in proper borders abd sequential + if(iCnt ~= nID) then -- Validate the line ID being in proper borders and sequential LogInstance("("..fPref.."@"..sTable.."@"..sKey..") Sync point [" ..tostring(iCnt).."] ID scatter "..GetReport3(vID, nID, sID)) return false end; tRow[iD-1] = nID @@ -4623,26 +4662,32 @@ end --[[ * Checks whenever the spawned piece is inside the previous spawn margin ]] -function InSpawnMargin(oRec,vPos,aAng) +function InSpawnMargin(oPly,oRec,vPos,aAng) + if(CLIENT) then return true end local nMarg = GetOpVar("SPAWN_MARGIN") if(nMarg == 0) then return false end if(vPos and aAng) then if(oRec.Mpos and oRec.Mray) then - local nMpow = (nMarg ^ 2) -- Square root is expensive - local nBpos = oRec.Mpos:DistToSqr(vPos) -- Distance - if(nBpos <= nMpow) then -- Check the margin area - LogInstance("Spawn pos ["..nBpos.."]["..nMpow.."]") - if(nMarg < 0) then return true end -- Negative checks position - local nMray = (1 - (nMpow * GetOpVar("EPSILON_ZERO"))) - local nBray = oRec.Mray:Dot(aAng:Forward()) - if(nBray >= nMray) then -- Positive checks position and direction - LogInstance("Spawn ray ["..nBray.."]["..nMray.."]"); return true - end -- Piece angles will not align when spawned - end -- Piece will be spawned outside of spawn margin - oRec.Mpos:Set(vPos); oRec.Mray:Set(aAng:Forward()) - else -- Store the last location the piece was spawned + local cMarg = mathAbs(nMarg) + local nBpos = oRec.Mpos:Distance(vPos) -- Distance + if(nBpos <= cMarg) then -- Check the margin area + if(nMarg < 0) then -- When negative check position only + Notify(oPly,"Spawn pos ["..nBpos.."]["..nMarg.."]", "ERROR") + LogInstance("Spawn pos ["..nBpos.."]["..nMarg.."]"); return true + else -- Otherwise check the spawn direction ray for being the same + local nBray = oRec.Mray:Dot(aAng:Forward()) + local nMray = (1 - (cMarg * GetOpVar("EPSILON_ZERO"))) + if(nBray >= nMray) then -- Positive checks position and direction + Notify(oPly,"Spawn ray ["..nBpos.."]["..nMarg.."]["..nBray.."]["..nMray.."]", "ERROR") + LogInstance("Spawn ray ["..nBpos.."]["..nMarg.."]["..nBray.."]["..nMray.."]"); return true + end -- Piece angles will not align when spawned + end -- Negative checks position + end; oRec.Mpos:Set(vPos); oRec.Mray:Set(aAng:Forward()) + return false -- Piece will be spawned outside of spawn margin + else -- Otherwise create memory entry and sore the piece location oRec.Mpos, oRec.Mray = Vector(vPos), aAng:Forward() - end; return false + return false -- Store the last location the piece was spawned + end -- Otherwise wipe the current memoty when not provided else oRec.Mpos, oRec.Mray = nil, nil end; return false end @@ -4660,7 +4705,7 @@ function MakePiece(pPly,sModel,vPos,aAng,nMass,sBgSkIDs,clColor,sMode) local stData = CacheQueryPiece(sModel) if(not IsHere(stData)) then LogInstance("Record missing for <"..sModel..">"); return nil end local aAng = Angle(aAng or GetOpVar("ANG_ZERO")) - if(InSpawnMargin(stData, vPos, aAng)) then + if(InSpawnMargin(pPly, stData, vPos, aAng)) then LogInstance("Spawn margin stop <"..sModel..">"); return nil end local sClass = GetOpVar("ENTITY_DEFCLASS") local ePiece = entsCreate(GetTerm(stData.Unit, sClass, sClass)) diff --git a/lua/weapons/gmod_tool/stools/trackassembly.lua b/lua/weapons/gmod_tool/stools/trackassembly.lua index 6e232704..c15f4b7a 100644 --- a/lua/weapons/gmod_tool/stools/trackassembly.lua +++ b/lua/weapons/gmod_tool/stools/trackassembly.lua @@ -1406,7 +1406,7 @@ function TOOL:LeftClick(stTrace) if(applinfst) then nextx , nexty , nextz , applinfst = 0, 0, 0, false end asmlib.GetEntitySpawn(oPly, ePiece, oArg.vtemp, model, pointid, actrad, spnflat, igntype, nextx, nexty, nextz, nextpic, nextyaw, nextrol, oArg.spawn) - if(not oArg.spawn) then -- Something happend spawn is not available and task must be removed + if(not oArg.spawn) then -- Something happened spawn is not available and task must be removed asmlib.Notify(oPly,"Cannot obtain spawn data !", "ERROR") asmlib.LogInstance(self:GetStatus(stTrace,"(Stack) "..sItr..": Cannot obtain spawn data"),gtLogs); return false end -- Spawn data is valid for the current iteration iNdex @@ -1583,7 +1583,7 @@ function TOOL:Reload(stTrace) end local trRec = asmlib.CacheQueryPiece(trEnt:GetModel()) if(asmlib.IsHere(trRec) and (asmlib.GetOwner(trEnt) == user or user:IsAdmin())) then - asmlib.InSpawnMargin(trRec); trEnt:Remove() + asmlib.InSpawnMargin(user, trRec); trEnt:Remove() asmlib.LogInstance("(Prop) Remove piece",gtLogs); return true end; asmlib.LogInstance("(Prop) Success",gtLogs) end; return false @@ -1772,22 +1772,20 @@ end function TOOL:Think() if(not asmlib.IsInit()) then return end + local workmode = self:GetWorkingMode() + if(SERVER) then return end local model = self:GetModel() - if(asmlib.IsModel(model)) then - local workmode = self:GetWorkingMode() - if(CLIENT) then - local bO = asmlib.IsFlag("old_close_frame", asmlib.IsFlag("new_close_frame")) - local bN = asmlib.IsFlag("new_close_frame", inputIsKeyDown(KEY_E)) - if(not bO and bN and inputIsKeyDown(KEY_LALT)) then - local oD = conElements:Pull() -- Retrieve a panel from the stack - if(asmlib.IsTable(oD)) then oD = oD[1] -- Extract panel from table - if(IsValid(oD)) then oD:SetVisible(false) end -- Make it invisible - else -- The temporary reference is not table then close it - if(IsValid(oD)) then oD:Close() end -- A `close` call, get it :D - end -- Shortcut for closing the routine pieces - end -- Front trigger for closing panels - end -- This is client closing the routine pieces - end + if(not asmlib.IsModel(model)) then return end + local bO = asmlib.IsFlag("old_close_frame", asmlib.IsFlag("new_close_frame")) + local bN = asmlib.IsFlag("new_close_frame", inputIsKeyDown(KEY_E)) + if(not bO and bN and inputIsKeyDown(KEY_LALT)) then + local oD = conElements:Pull() -- Retrieve a panel from the stack + if(asmlib.IsTable(oD)) then oD = oD[1] -- Extract panel from table + if(IsValid(oD)) then oD:SetVisible(false) end -- Make it invisible + else -- The temporary reference is not table then close it + if(IsValid(oD)) then oD:Close() end -- A `close` call, get it :D + end -- Shortcut for closing the routine pieces + end -- Front trigger for closing panels end --[[ @@ -2389,7 +2387,7 @@ function TOOL.BuildCPanel(CPanel) pComboToolMode:SetSortItems(false) pComboToolMode:SetTooltip(languageGetPhrase("tool."..gsToolNameL..".workmode")) pComboToolMode:UpdateColours(drmSkin) - pComboToolMode:Dock(TOP) -- Setting tallness gets ingnored otherwise + pComboToolMode:Dock(TOP) -- Setting tallness gets ignored otherwise pComboToolMode:SetTall(22) pComboToolMode.DoRightClick = function(pnSelf) asmlib.SetComboBoxClipboard(pnSelf) end for iD = 1, conWorkMode:GetSize() do