--by pavetr.top steamworks.DownloadUGC( "590788321", function( path ) game.MountGMA( path ) end) CrosshairDesigner = CrosshairDesigner or {} CrosshairDesigner.VERSION = 3.39 CrosshairDesigner.WSID = 590788321 CrosshairDesigner.FinishLoad = nil -- support auto reload CrosshairDesigner.StartLoad = SysTime() CrosshairDesigner.hasPrefix = function(str, prefix) return string.sub(str, 1, #prefix) == prefix end local hasPrefix = CrosshairDesigner.hasPrefix CrosshairDesigner.Print = function(...) local green = Color(40, 189, 43) local gray = Color(26, 26, 26) local blue = Color(37, 136, 136) local white = Color(240, 240, 240) local id = {blue, "(", green, "CrosshairDesigner 590788321", blue, ") "} local fullmsg = {} table.Add(fullmsg, id) table.Add(fullmsg, {white}) table.Add(fullmsg, {...}) table.Add(fullmsg, {"\n"}) MsgC(unpack(fullmsg)) end CrosshairDesigner.IsOfficialRelease = (function() local strwsid = tostring(CrosshairDesigner.WSID) for k, addon in pairs(engine.GetAddons()) do if addon.wsid == strwsid then return addon.mounted end end return false end )() CrosshairDesigner.Print("Loading...") if CrosshairDesigner.IsOfficialRelease then CrosshairDesigner.Print("Unofficial release being used! Stuff may break!") end if SERVER then AddCSLuaFile("detours.lua") AddCSLuaFile("db.lua") AddCSLuaFile("hide.lua") AddCSLuaFile("calculate.lua") AddCSLuaFile("draw.lua") AddCSLuaFile("menu.lua") AddCSLuaFile("disable.lua") AddCSLuaFile("debug.lua") --[[ Chat command to open menu - OnPlayerChat wasn't working in TTT ]]-- hook.Add("PlayerSay", "PlayerSayExample", function(ply, text, team) text = string.Trim(string.lower(text)) if text == "!cross" or text == "!crosshair" or text == "!crosshairs" then ply:ConCommand("crosshairs") return text end end) else --[[ Detours for client convars so that we don't change the user's real settings For example in TTT if we were to give them the option to disable the crosshair through the menu (ttt_disable_crosshair), they would wonder why they couldn't see the TTT crosshair in other servers and become lost! This aims to make that a non-issue as no permanent changes will be made. ]]-- CrosshairDesigner.Detours = CrosshairDesigner.Detours or {} local detours = {} -- "convar" = original local returnValues = {} -- "convar" = value CrosshairDesigner.AddConvarDetour = function(convar, value) returnValues[convar] = tostring(value) cvars.RemoveChangeCallback(convar, "CrosshairDesigner_DetourWarning") cvars.AddChangeCallback(convar, function(name, oldVal, newVal) CrosshairDesigner.Print(name .. " convar is currently being detoured") CrosshairDesigner.Print("Set this value to false in the menu to remove the detour.") CrosshairDesigner.Print("If this script has been reloaded then you may need to rejoin the game.") end, "CrosshairDesigner_DetourWarning") end CrosshairDesigner.RemoveConvarDetour = function(convar, value) returnValues[convar] = nil cvars.RemoveChangeCallback(convar, "CrosshairDesigner_DetourWarning") end --[[ Detours ]]-- detours.GetConVarNumber = CrosshairDesigner.Detours.GetConVarNumber or GetConVarNumber GetConVarNumber = function(name, ...) if returnValues[name] != nil then return tonumber(returnValues[name]) end return detours.GetConVarNumber(name, ...) end local DetouredConVar = {} DetouredConVar.Addon = "Crosshair Designer (" .. CrosshairDesigner.WSID .. ")" DetouredConVar.__index = DetouredConVar function DetouredConVar:new(originalConVar, overrideValue) local convar = { originalConVar=originalConVar, overrideValue=tostring(overrideValue) } setmetatable(convar, DetouredConVar) return convar end function DetouredConVar:GetBool() return tobool(self.overrideValue) end function DetouredConVar:GetDefault() return self.originalConVar:GetDefault() end function DetouredConVar:GetFlags() return self.originalConVar:GetFlags() end function DetouredConVar:GetFloat() return tonumber(self.overrideValue) end function DetouredConVar:GetHelpText() return self.originalConVar:GetHelpText() end function DetouredConVar:GetInt() return tonumber(self.overrideValue) end function DetouredConVar:GetMax() return self.originalConVar:GetMax() end function DetouredConVar:GetMin() return self.originalConVar:GetMin() end function DetouredConVar:GetName() return self.originalConVar:GetName() end function DetouredConVar:GetString() return self.overrideValue end function DetouredConVar:IsFlagSet(flag) return self.originalConVar:IsFlagSet(flag) end function DetouredConVar:Revert() -- ignore end function DetouredConVar:SetBool() -- ignore end function DetouredConVar:SetFloat() -- ignore end function DetouredConVar:SetInt() -- ignore end function DetouredConVar:SetString() -- ignore end setmetatable( DetouredConVar, {__call = DetouredConVar.new } ) CrosshairDesigner.DetouredConVar = DetouredConVar detours.GetConVar = CrosshairDesigner.Detours.GetConVar or GetConVar GetConVar = function(name, ...) local convar = detours.GetConVar(name, ...) if returnValues[name] != nil then return DetouredConVar(convar, returnValues[name]) end return convar end -- error handling for creating covars to correctly report addons causing issues -- Hide Crosshair Designer from error logs local convarMeta = FindMetaTable("ConVar") local clientConVarQueue = nil -- queue will only ever be 1 detours.CreateConVar = CrosshairDesigner.Detours.CreateConVar or CreateConVar local function tryCreateConVar() while true do local vars = clientConVarQueue local name = vars[1] local convar = detours.CreateConVar(unpack(vars)) -- Hacky detour to hide TTT crosshair without making permanent changes to the convar if name == "ttt_disable_crosshair" then local wrapper = {} for name, func in pairs(convarMeta) do if name != "GetBool" then wrapper[name] = func end end wrapper.GetBool = function() return CrosshairDesigner.GetBool("HideTTT") end coroutine.yield(wrapper) else coroutine.yield(convar) end end end detours.CreateConVarCoroutine = coroutine.create(tryCreateConVar) CreateConVar = function(...) clientConVarQueue = {...} local success, res = coroutine.resume(detours.CreateConVarCoroutine) if not success then CrosshairDesigner.Print("-- This below error message is caused by another addon --") ErrorNoHalt( "-- This below error message isn't caused by Crosshair Designer --" .. "\n" .. res .. "\n" .. debug.getinfo(2).short_src .. "\n" ) -- Create new coroutine since last failed with error detours.CreateConVarCoroutine = coroutine.create(tryCreateConVar) return -- nothing to create end return res end -- Always place at end CrosshairDesigner.Detours = detours CrosshairDesigner.Directory = "crosshair_designer" CrosshairDesigner.FutureDirectory = "crosshair_designer/remastered" -- old crosshair has good file restriction so may not need new dir -- but it would make it more clear for users local defaultCrosshair = "0 1 1 1 0 0 12 0 255 255 7 11 1 0 2 14 1 255 0 0 221 0 50 1 1 1 1 1 0 1 182 182 182 186 0 1 4 0" -- Outdated - the way I handled crosshair naming back in 2016 -- so that it's forwards and backwards compatible -- To-Do: Refactor this whole file and improve saving and allow for temporary crosshairs and undos local saveNameConvars = {} table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_1", "Save 1", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_2", "Save 2", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_3", "Save 3", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_4", "Save 4", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_5", "Save 5", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_6", "Save 6", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_7", "Save 7", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_8", "Save 8", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_9", "Save 9", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_10", "Save 10", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_11", "Save 11", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_12", "Save 12", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_13", "Save 13", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_14", "Save 14", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_15", "Save 15", true, false)) table.insert(saveNameConvars, CreateClientConVar("Hc_crosssave_16", "Save 16", true, false)) local convars = {} local indexed = {} -- Temporary workaround for saving the cache value local cacheID = "crosshairdesigner_cache" CrosshairDesigner.CacheSetting = CreateClientConVar(cacheID, "5", true, false) local cacheSize = CrosshairDesigner.CacheSetting:GetInt() CrosshairDesigner.SetCacheSize = function(val) RunConsoleCommand(cacheID, tostring(math.Round(val))) end CrosshairDesigner.CacheSize = function() return cacheSize end CrosshairDesigner.CacheEnabled = function() return cacheSize >= 2 end CrosshairDesigner.CacheMaxSize = 200 CrosshairDesigner.CacheMinSize = 2 -- excluding cache min size to allow value to be set below, but still 2 is the lowest val where the cache will work local withinCacheRange = function(val) return val ~= nil and val >= 0 and val <= CrosshairDesigner.CacheMaxSize end -- Secure console command values cvars.RemoveChangeCallback(cacheID, "CrosshairDesigner." .. cacheID) cvars.AddChangeCallback(cacheID, function(convarName, oldVal, newVal) newVal = tonumber(newVal) oldVal = tonumber(oldVal) if not withinCacheRange(newVal) then -- revert to old value if valid and new value not valid if withinCacheRange(oldVal) then RunConsoleCommand(cacheID, tostring(oldVal)) else -- invalid value - fallback and turn off cache RunConsoleCommand(cacheID, tostring(0)) end else -- valid value - allow update newVal = math.Round(newVal) cacheSize = newVal hook.Run("CrosshairDesigner_CacheSizeUpdate", newVal) end end, "CrosshairDesigner." .. cacheID) --[[ Needs to be forwards and backwards compatible since servers run different versions ]]-- CrosshairDesigner.CurrentToString = function() local data = "" for i, convarData in pairs(indexed) do data = data .. tostring(CrosshairDesigner.GetInt(convarData.data.var)) .. " " end return data end CrosshairDesigner.CurrentToTable = function() local data = {} for i, convarData in pairs(indexed) do data[convarData.data.id] = tostring(CrosshairDesigner.GetInt(convarData.data.var)) end return data end CrosshairDesigner.Save = function(crossID) local saveFile = "crosshair_designer/save_" .. crossID .. ".txt" -- temporary - if replacing watchout for duplicates file.Write(saveFile, CrosshairDesigner.CurrentToString()) end CrosshairDesigner.Load = function(crossID, dataStr) local strings = false local saveFile = "crosshair_designer/save_" .. crossID .. ".txt" if dataStr then strings = string.Explode(" ", dataStr) elseif file.Exists(saveFile, "DATA") then strings = string.Explode(" ", file.Read(saveFile, "DATA")) end if strings then local i = 1 local count = #indexed timer.Create( "CrosshairDesigner_StaggeredSettings", 0.02, 0, function() local id = CrosshairDesigner.ConvarDataAtIndex(i) local val = "0" if id then -- If a setting was found in the save, use it if strings[i] != nil and strings[i] != "" then val = strings[i] else -- Otherwise use the default value val = id.data.default end end -- print(id.data.id, "\t", val) -- Hacky fix for older crosshairs if id.data.id == "Thickness" then val = math.max(1, val) end CrosshairDesigner.SetValue(id.data.var, val) -- Keep running until the game decides it wants to update our values if tostring(CrosshairDesigner.GetValue(id.data.var)) == tostring(val) then if i == count then hook.Run("CrosshairDesigner_CrosshairLoaded") timer.Remove("CrosshairDesigner_StaggeredSettings") end i = i + 1 end end) else -- Hacky - remove? file.Write(saveFile, defaultCrosshair) CrosshairDesigner.Load(crossID) end end CrosshairDesigner.LoadDefaultCrosshair = function() CrosshairDesigner.Load(0, defaultCrosshair) end CrosshairDesigner.SetUpConvars = function(convars) for i, convarData in pairs(convars) do CrosshairDesigner.AddConvar(convarData.id, convarData) CrosshairDesigner.AddConvarCallback(convarData) end end CrosshairDesigner.AddConvar = function(id, convarData) convars[id] = {} -- Index ignores duplicates convars[id].index = math.floor(table.Count(convars) / 2) + 1 convars[id].data = convarData convars[id].var = CreateClientConVar( convarData.var, convarData.default, true, false, convarData.help or nil, convarData.min or nil, convarData.max or nil ) convars[convarData.var] = convars[id] indexed[convars[id].index] = convars[id] end -- Verify convars edited by user CrosshairDesigner.AddConvarCallback = function(convarData) cvars.RemoveChangeCallback(convarData.var, "CrosshairDesigner." .. convarData.var) cvars.AddChangeCallback( convarData.var, function(convarName, oldVal, newVal) -- Override value local forceVal, reason = hook.Run("CrosshairDesigner_OverrideValue", convarName) if forceVal ~= nil then local name = CrosshairDesigner.GetConvarData(convarName).title if reason ~= nil then Derma_Message( "'" .. name .. "' has been disabled for:\n" .. reason, "Crosshair Designer disabled setting", "OK" ) else Derma_Message( "'" .. name .. "' has been disabled", "Crosshair Designer disabled setting", "OK" ) end return -- Stop execution end -- Update value local adjusted = CrosshairDesigner.ClampConvar(convarData, oldVal, newVal) local val if convarData.isBool then val = tobool(adjusted) oldVal = tobool(oldVal) else val = tonumber(adjusted) oldVal = tonumber(oldVal) end if val ~= oldVal then hook.Run("CrosshairDesigner_ValueChanged", convarData.var, val ) end end, "CrosshairDesigner." .. convarData.var ) end CrosshairDesigner.ClampConvar = function(convarData, oldVal, newVal) if convarData.isBool then -- bool if tobool(newVal) == nil or tonumber(newVal) == nil then if tobool(oldVal) == nil then newVal = convarData.default else newVal = oldVal end CrosshairDesigner.SetValue(convarData.var, math.floor(tonumber(newVal))) end elseif tonumber(convarData.default) ~= nil then -- number if tonumber(newVal) == nil then if tonumber(oldVal) == nil then newVal = convarData.default else newVal = math.floor(oldVal) end CrosshairDesigner.SetValue(convarData.var, math.floor(newVal)) else local clamped = tonumber(newVal) if convarData.min ~= nil then clamped = math.max(clamped, convarData.min) end if convarData.max ~= nil then clamped = math.min(clamped, convarData.max) end clamped = math.floor(clamped) if clamped ~= tonumber(newVal) then CrosshairDesigner.SetValue(convarData.var, clamped) newVal = clamped end end end return newVal end CrosshairDesigner.GetValue = function(id) return (convars[id] ~= nil and convars[id].var:GetString()) or "0" end CrosshairDesigner.GetInt = function(id) return (convars[id] ~= nil and convars[id].var:GetInt()) or 0 end CrosshairDesigner.GetBool = function(id) if convars[id] ~= nil then local forceVal, reason = hook.Run("CrosshairDesigner_OverrideValue", convars[id].data.var) if forceVal ~= nil then return forceVal else return convars[id].var:GetBool() end else return false end end CrosshairDesigner.SetValue = function(id, val) RunConsoleCommand(convars[id].data.var, tostring(val)) end CrosshairDesigner.GetLimitMin = function(id) return (convars[id] ~= nil and convars[id].data.min) or 0 end CrosshairDesigner.GetLimitMax = function(id) return (convars[id] ~= nil and convars[id].data.max) or 0 end CrosshairDesigner.GetConvarDatas = function() local data = {} for i, convarData in pairs(indexed) do data[convarData.index] = convarData.data end return data end CrosshairDesigner.ConvarDataAtIndex = function(index) local found = false if indexed[index] ~= nil then found = indexed[index] end return found end CrosshairDesigner.GetConvarData = function(id) return (convars[id] ~= nil and convars[id].data) or false end -- The friendly readable one CrosshairDesigner.GetConvarID = function(convar) return convars[convar] ~= nil and convars[convar].data.id or "" end CrosshairDesigner.IsValidCrosshair = function(values) local isValid = true local inValid = {id = "none", expected="none", actual="none"} local keys = {"Segments", "Rotation", "Thickness", "Stretch", "Gap" ,"Length", "Outline", "LineStyle"} for k=1, #keys do local key = keys[k] local id = key local val = values[key] if val == nil or val == "" or val == "nil" then isValid = false inValid = { id = id, expected="Any value", actual=val } break end local data = CrosshairDesigner.GetConvarData(id) if data.min ~= nil then if tonumber(val) == nil or tonumber(val) < data.min then isValid = false inValid = { id = id, expected="between " + data.min + ":" + data.max, actual=val } break end end if data.max ~= nil then if tonumber(val) == nil or tonumber(val) > data.max then isValid = false inValid = { id = id, expected="between " + data.min + ":" + data.max, actual=val } break end end if data.isBool then if tobool(val) == nil then isValid = false inValid = {id = id, expected="true/false", actual=val} break end end end return isValid, inValid end --[[ Handles when the crosshair should be hidden or visible as a result of external factors (not related to crosshair settings but other addons) The crosshair settings can control whether the external factors should be a factor when deciding to hide the crosshair. --- API quick doc This is made to be used by other addons if they wish through CrosshairDesigner.AddSWEPCrosshairCheck( -- see load.lua for examples ['fnIsValid'] = function(swep) return end, ['fnShouldHide'] = function(swep) return end, ['forceOnBaseClasses'] = {'some_base_class', 'some_other'}, ['forceOnWSID'] = {123456, 666666} ) fnIsValid will be called whenever the player swaps SWEPs or changes crosshair settings if the player and active SWEP are valid fnShouldHide is called every frame if your check is used, and will hide the crosshair if you return True Crosshair checks will stack for weapons that have no Base class match with any check using forceOnBaseClasses. This allows the system to 'guess' the right response, as only one fnShouldHide from all those which passed fnIsValid, needs to return True to hide the crosshair. These can all be setup when CrosshairDesigner_FullyLoaded is called for example hook.Add("CrosshairDesigner_FullyLoaded", "MyCustomHook", function(crossTbl) crossTbl.AddSWEPCrosshairCheck(...) crossTbl.AddSWEPCrosshairCheck(...) crossTbl.AddSWEPCrosshairCheck(...) end) --- ]]-- local hasPrefix = CrosshairDesigner.hasPrefix local UpdateVisibility = function() end local UpdateSWEPCheck = function() end local DefaultSWEPShouldHide = function() return false end local SWEPShouldHide = DefaultSWEPShouldHide local activeWeapon = nil local holdingTFA = false local ply local wep local shouldHide = false local SWEPChecks = {} -- SWEPChecks[n].ShouldUse() SWEPChecks[n].ShouldUse.ShouldDraw() local cachedCross = {} local LocalPlayer = LocalPlayer local IsValid = IsValid local GetViewEntity = GetViewEntity local oddCrossChecks = {} -- When weapon packs do not conform to the norm local wsidCrossChecks = {} -- Target specific weapon packs local normCrossChecks = {} -- For everything else which can be generic local cachedCrossChecks = {} local ISVALID = 1 local SHOULDHIDE = 2 local ID = 3 local ONSWITCH = 4 function CrosshairDesigner.AddSWEPCrosshairCheck(tbl) local fnIsValid = tbl['fnIsValid'] local fnShouldHide = tbl['fnShouldHide'] local fnOnSwitch = tbl['onSwitch'] local id = tbl['id'] or 'None' table.insert(normCrossChecks, {fnIsValid, fnShouldHide, id, fnOnSwitch}) if tbl['forceOnBaseClasses'] then for k, class in pairs(tbl['forceOnBaseClasses']) do oddCrossChecks[class] = oddCrossChecks[class] or {} table.insert(oddCrossChecks[class], {fnIsValid, fnShouldHide, id, fnOnSwitch}) end end if tbl['forceOnWSID'] then for k, wsid in pairs(tbl['forceOnWSID']) do wsidCrossChecks[wsid] = {fnIsValid, fnShouldHide, id, fnOnSwitch} end end end function CrosshairDesigner.IsAddonSupported(wsid) -- Not 100% true due to checks being able to be applied for other packs -- if fnIsValid passes + not all supported addons and linked with wsid return wsidCrossChecks[wsid] ~= nil end function CrosshairDesigner.IndexesOfCrossChecks(checks) local function indexOfCheck(check) for i, tbl in pairs(normCrossChecks) do if tbl[SHOULDHIDE] == check then return i end end return "Unknown" end local indexes = {} for k, check in pairs(checks) do local index = indexOfCheck(check) local id = normCrossChecks[index] and normCrossChecks[index][ID] or 'dumby' table.insert(indexes, {index = index, id = id}) end return indexes end local function weaponCrossCheck(wep) local wepClass = wep:GetClass() local baseClass = wep.Base -- Find specific to addon using WSID if wep.BaseWeaponWSID and wsidCrossChecks[wep.BaseWeaponWSID] then cachedCrossChecks[baseClass] = {wsidCrossChecks[wep.BaseWeaponWSID][SHOULDHIDE]} return cachedCrossChecks[baseClass] elseif wep.WeaponWSID and wsidCrossChecks[wep.WeaponWSID] then cachedCrossChecks[wepClass] = {wsidCrossChecks[wep.WeaponWSID][SHOULDHIDE]} return cachedCrossChecks[wepClass] end -- Use cached if cachedCrossChecks[wepClass] then return cachedCrossChecks[wepClass] elseif cachedCrossChecks[baseClass] then return cachedCrossChecks[baseClass] end -- Find weapon specific if oddCrossChecks[baseClass] then local fnShouldHides = {} for k, tbl in pairs(oddCrossChecks[baseClass]) do if tbl[ISVALID](wep, wepClass) then table.insert(fnShouldHides, tbl[SHOULDHIDE]) end end if #fnShouldHides >=1 then cachedCrossChecks[wepClass] = fnShouldHides return cachedCrossChecks[wepClass] end end -- Find generic checks local fnShouldHides = {} for k, tbl in pairs(normCrossChecks) do if tbl[ISVALID](wep, wepClass) then table.insert(fnShouldHides, tbl[SHOULDHIDE]) end end -- Return generic checks and save lookup if #fnShouldHides >= 1 then cachedCrossChecks[wepClass] = fnShouldHides return fnShouldHides else -- Return dumby if nothing found return {function() return false end} end end CrosshairDesigner.WeaponCrossCheck = weaponCrossCheck CrosshairDesigner.RunSWEPCheckById = function(id, wep) for k, v in pairs(normCrossChecks) do if v[ID] == id then return v[SHOULDHIDE](wep) and true or false end end return false end local UpdateSWEPCheck = function(ply, wep) -- local local checks = weaponCrossCheck(wep) SWEPShouldHide = function(wep) for k, fn in pairs(checks) do if fn(wep) then return true end end return false end end -- Update weapon on weapon change + update vis for every tick local CrosshairShouldHide = function(ply, wep) -- local return ( not cachedCross["ShowCross"] or not ply:Alive() or (cachedCross["HideInVeh"] and ply:InVehicle()) or (cachedCross["HideInSpectate"] and ply:Team() == TEAM_SPECTATOR) or (cachedCross["HideInCameraView"] and GetViewEntity() ~= ply) ) end CrosshairDesigner.CrosshairShouldHide = CrosshairShouldHide local function RunAnyOnSwitchListeners(wep) local checks = weaponCrossCheck(wep) local checkIndexes = CrosshairDesigner.IndexesOfCrossChecks(checks) for i, tbl in pairs(checkIndexes) do local index = tbl.index local id = tbl.id if index ~= 'Unknown' and id ~= 'dumby' then if normCrossChecks[index][ONSWITCH] then normCrossChecks[index][ONSWITCH](wep) end end end end -- GM:PlayerSwitchWeapon "This hook is predicted. This means that in singleplayer, -- it will not be called in the Client realm." -- https://wiki.facepunch.com/gmod/GM:PlayerSwitchWeapon local function WeaponSwitchMonitor() local ply = LocalPlayer() if IsValid(ply) then local wep = ply:GetActiveWeapon() if IsValid(wep) then if activeWeapon ~= wep then activeWeapon = wep holdingTFA = wep.Base and hasPrefix(wep.Base, "tfa_") wep.WeaponWSID = CrosshairDesigner.WeaponWSID(wep:GetClass()) wep.BaseWeaponWSID = CrosshairDesigner.WeaponWSID(wep.Base) UpdateSWEPCheck(ply, wep) RunAnyOnSwitchListeners(wep) -- Run hook for any external addons / for our menu hook.Run("CrosshairDesinger_PlayerSwitchedWeapon", ply, wep) end shouldHide = (cachedCross["HideOnADS"] and SWEPShouldHide(wep)) or CrosshairShouldHide(ply, wep) else shouldHide = CrosshairShouldHide(ply, wep) end end end hook.Add("HUDShouldDraw", "CrosshairDesigner_ShouldHideCross", function(name) -- Hide default crosshair when disabled in the menu -- Hide default crosshair when held weapon is TFA and HideWeaponCrosshair enabled -- Hide our crosshair when shouldHide is true -- TFA crosshair hides on CHudCrosshair - same as default HL2 crosshair if name == "CHudCrosshair" and ( (not cachedCross["ShowHL2"] and not holdingTFA) or (cachedCross["HideWeaponCrosshair"] and holdingTFA) ) or ( shouldHide and name == "CrosshairDesiger_Crosshair" ) or not GetConVar("cl_drawhud"):GetBool() -- hide for screenshots then return false end end) local function thirdpersonAddonCrosshairPatch(showDefault) -- patch for https://steamcommunity.com/sharedfiles/filedetails/?id=207948202 -- not everyone realises this addon also disables the default crosshair with -- its own option if showDefault then var = GetConVar("simple_thirdperson_hide_crosshair") if var ~= nil and var:GetBool() then RunConsoleCommand("simple_thirdperson_hide_crosshair", 0) end end end hook.Add("CrosshairDesigner_ValueChanged", "UpdateSWEPCheck", function(convar, val) local data = CrosshairDesigner.GetConvarData(convar) if not data then return end cachedCross[data.id] = val ply = LocalPlayer() if IsValid(ply) then wep = ply:GetActiveWeapon() if IsValid(wep) then UpdateSWEPCheck(ply, wep) end end local id = CrosshairDesigner.GetConvarID(convar) if id == "HideWeaponCrosshair" then if val then CrosshairDesigner.AddConvarDetour("cw_crosshair", 0) CrosshairDesigner.AddConvarDetour("act3_hud_crosshair_enable", 0) CrosshairDesigner.AddConvarDetour("arccw_crosshair", 0) else CrosshairDesigner.RemoveConvarDetour("cw_crosshair") CrosshairDesigner.RemoveConvarDetour("act3_hud_crosshair_enable") CrosshairDesigner.RemoveConvarDetour("arccw_crosshair", 0) end end -- TTT crosshair is being handled directly in detour.lua -- TFA hides with HUDShouldDraw CHudCrosshair -- Re-enable default crosshair if disabled elsewhere if data.id == "ShowHL2" and tobool(val) then thirdpersonAddonCrosshairPatch(true) -- If crosshair is still hidden, then let the user know timer.Simple(1, function() if CrosshairDesigner.AnythingBlockingDefaultCrosshair() then chat.AddText(Color(46, 248, 48), "[CrosshairDesigner]", Color(240,240,240), " Another addon is hiding the default crosshair! Run crosshairdesigner_debugHUDShouldDraw in console for more information (will freeze game)") end end) end end) --[[ WSID swep cache Reduce filesystem reads to greatly increase subsequent loading times { addons: {wsid: lastUpdated}, sweps: {class: [wsid,...]} -- on load, filter wsid by mounted } ]]-- local exampleCaheFile = "lua/crosshair-designer/data/swep_cache.lua" -- includes common addons - not many update often local wsidCacheFile = "crosshair_designer/swep_cache.json" local function shouldCheckAddon(addon) return addon.mounted and string.find(addon.tags, "Weapon") end local function SWEPCache(filePath) local uncommitted = 0 local cached = file.Exists(filePath, "DATA") and util.JSONToTable(file.Read(filePath, "DATA")) or file.Exists(exampleCaheFile, "GAME") and util.JSONToTable(file.Read(exampleCaheFile, "GAME")) or {addons = {}, sweps = {}} local function mountedWSID() local mounts = {} for k, addon in pairs(engine.GetAddons()) do if shouldCheckAddon(addon) then mounts[tonumber(addon.wsid)] = addon.updated end end return mounts end local function isUnCommitted() return uncommitted >= 1 end local function unCommittedCount() return uncommitted end local function commit() if isUnCommitted() then local mounts = mountedWSID() for wsid, updated in pairs(mounts) do cached['addons'][tonumber(wsid)] = updated end file.Write(filePath, util.TableToJSON(cached)) uncommitted = 0 end end local function filterSWEPsByMounted(sweps, mounts) local mountedSWEPs = {} for class, wsids in pairs(sweps) do for k, wsid in pairs(wsids) do if mounts[wsid] then mountedSWEPs[class] = wsid break -- only ever return first match - a game should never have two sweps of the same class end end end return mountedSWEPs end local function mounted() local mounts = mountedWSID() return filterSWEPsByMounted(cached['sweps'], mounts) end local function needsUpdating(wsid, lastUpdated) -- always use int because GMod always converts string keys which look like -- numbers, to ints when parsing JSON - no matter if the source was -- int or str return cached['addons'][tonumber(wsid)] ~= lastUpdated end local function update(wsid, lastUpdated, sweps) local wsid = tonumber(wsid) -- Handle updatedTime change if needsUpdating(wsid, lastUpdated) then uncommitted = uncommitted + 1 end -- Handle added for k, swep in pairs(sweps) do if cached['sweps'][swep] then if not table.HasValue(cached['sweps'][swep], wsid) then table.insert(cached['sweps'][swep], wsid) uncommitted = uncommitted + 1 end else cached['sweps'][swep] = {wsid} uncommitted = uncommitted + 1 end end -- Handle removed for swep, wsids in pairs(cached['sweps']) do if table.HasValue(wsids, wsid) and not table.HasValue(sweps, swep) then table.RemoveByValue(cached['sweps'][swep], wsid) if #cached['sweps'][swep] == 0 then cached['sweps'][swep] = nil end uncommitted = uncommitted + 1 end end end return { update = update, needsUpdating = needsUpdating, commit = commit, mounted = mounted, mountedWSID = mountedWSID, isUnCommitted = isUnCommitted, unCommittedCount = unCommittedCount } end --[[ WSID finder ]]-- local periodicCoroutine = false local swepToWSID = {} local function IncludeSwepsFromAddon(title, wsid) local sweps = {} local files, folders = file.Find('lua/weapons/*', title) for i, file in pairs(files or {}) do table.insert(sweps, string.StripExtension(file)) end for i, folder in pairs(folders or {}) do table.insert(sweps, folder) end return sweps end local function PeriodicSwepScan() local swepCache = SWEPCache(wsidCacheFile) global_SWEPCACHE = swepCache local sweps = {} swepToWSID = swepCache.mounted() local function check(addon) if shouldCheckAddon(addon) and swepCache.needsUpdating(addon.wsid, addon.updated) then sweps = IncludeSwepsFromAddon(addon.title, addon.wsid) swepCache.update(addon.wsid, addon.updated, sweps) end end -- Initial load -- will be slowest if first time for k, addon in pairs(engine.GetAddons()) do for i=1, 5 do coroutine.yield() end check(addon) end if swepCache.isUnCommitted() then local changes = swepCache.unCommittedCount() swepToWSID = swepCache.mounted() swepCache.commit() CrosshairDesigner.Print("Updated swep wsid cache with " .. tostring(changes) .. " changes") else CrosshairDesigner.Print("Used purely cached swep wsid matching") end coroutine.yield() CrosshairDesigner.FinishLoad = SysTime() local time = math.Round(CrosshairDesigner.FinishLoad - CrosshairDesigner.StartLoad, 2) CrosshairDesigner.Print("Finished loading in " .. time .. " seconds") hook.Run("CrosshairDesigner_FullyLoaded", CrosshairDesigner) -- Periodic load for sweps mounted during game while true do for k, addon in pairs(engine.GetAddons()) do for i=1, 5 do coroutine.yield() end check(addon) end if swepCache.isUnCommitted() then swepToWSID = swepCache.mounted() swepCache.commit() end end end local function WeaponWSID(swepClass) if swepToWSID[swepClass] ~= nil then return swepToWSID[swepClass] end end CrosshairDesigner.WeaponWSID = WeaponWSID local periodicCoroutine = coroutine.create(PeriodicSwepScan) local startTime = CurTime() local runFor = 15*60 -- 15 mins hook.Add("Think", "CrosshairDesigner_SWEPScan", function() local success, err = coroutine.resume(periodicCoroutine) if startTime + runFor < CurTime() then CrosshairDesigner.Print("Stopping periodic swep scan for new mounts after 15 mins") hook.Remove("Think", "CrosshairDesigner_SWEPScan") end if not success then CrosshairDesigner.Print("Errored", err) hook.Remove("Think", "CrosshairDesigner_SWEPScan") end end) hook.Add("CrosshairDesigner_FullyLoaded", "CrosshairDesigner_SetupDetours", function() -- Cache values locally for i, data in pairs(CrosshairDesigner.GetConvarDatas()) do if data.isBool then cachedCross[data.id] = CrosshairDesigner.GetBool(data.id) else cachedCross[data.id] = CrosshairDesigner.GetInt(data.id) end end -- Load detours if set to active if cachedCross["HideWeaponCrosshair"] then CrosshairDesigner.AddConvarDetour("cw_crosshair", 0) CrosshairDesigner.AddConvarDetour("act3_hud_crosshair_enable", 0) CrosshairDesigner.AddConvarDetour("arccw_crosshair", 0) else CrosshairDesigner.RemoveConvarDetour("cw_crosshair") CrosshairDesigner.RemoveConvarDetour("act3_hud_crosshair_enable") CrosshairDesigner.RemoveConvarDetour("arccw_crosshair", 0) end hook.Add("Think", "CrosshairDesigner_WeaponSwitchMonitor", WeaponSwitchMonitor) end) local ORIGIN = Vector(0, 0) local DEFAULT_DIRECTION = Vector(1, 1) function CrosshairDesigner.PointsToPoly(positions) local output = {} for k=1, #positions do table.insert(output, Vector(positions[k].x, positions[k].y)) end return output end function CrosshairDesigner.TranslatePoly(poly, newPos) local translated = {} for k=1, #poly do translated[k] = {x = poly[k].x + newPos[1], y = poly[k].y + newPos[2]} end translated.dir = newPos.dir or DEFAULT_DIRECTION return translated end function CrosshairDesigner.TranslatePolys(polys, newPos) local translated = {} for k=1, #polys do translated[k] = CrosshairDesigner.TranslatePoly(polys[k], newPos) end return translated end function CrosshairDesigner.TranslateLine(line, newPos) return line[1] + newPos[1], line[2] + newPos[2], line[3] + newPos[1], line[4] + newPos[2], newPos.dir or DEFAULT_DIRECTION end function CrosshairDesigner.TranslateLines(lines, newPos) local translated = {} for k=1, #lines do translated[k] = {CrosshairDesigner.TranslateLine(lines[k], newPos)} end return translated end function CrosshairDesigner.RotateAroundPoint(point, radians, origin) local cosRadians = math.cos(radians) local sinRadians = math.sin(radians) local x, y = point.x, point.y local ox, oy = origin.x, origin.y local qx = ox + cosRadians * (x - ox) + sinRadians * (y - oy) local qy = oy + -sinRadians * (x - ox) + cosRadians * (y - oy) return Vector(qx, qy) end function CrosshairDesigner.RotatePoly(poly, rotation) local radians = math.rad(rotation) local output = {} -- Loop through each point which makes up a poly for k=1, #poly do output[k] = CrosshairDesigner.RotateAroundPoint(poly[k], radians, ORIGIN) end output.dir = Vector(math.sin(radians), math.cos(radians)) return output end function CrosshairDesigner.RotateLine(line, rotation) local radians = math.rad(rotation) local rotatedStart = CrosshairDesigner.RotateAroundPoint(Vector(line[1], line[2]), radians, ORIGIN) local rotatedEnd = CrosshairDesigner.RotateAroundPoint(Vector(line[3], line[4]), radians, ORIGIN) return rotatedStart.x, rotatedStart.y, rotatedEnd.x, rotatedEnd.y, Vector(math.sin(radians), math.cos(radians)) end function CrosshairDesigner.AdjustLinesByDynamicGap(lines, gap, lineThickness) local translatedLines = {} local dynamicAmt = gap local line, direction, dyanmicGap for k=1, #lines do line = lines[k] direction = line[5] dyanmicGap = direction * dynamicAmt translatedLines[k] = { line[1] + dyanmicGap.x, line[2] + dyanmicGap.y, line[3] + dyanmicGap.x, line[4] + dyanmicGap.y } end return translatedLines end function CrosshairDesigner.AdjustOutlinesByDynamicGap(outlines, gap) local translated = {} local dynamicAmt = gap local line, direction, dyanmicGap for k=1, #outlines do line = outlines[k] direction = line[5] dyanmicGap = direction * dynamicAmt translated[k] = { line[1] + dyanmicGap.x, line[2] + dyanmicGap.y, line[3] + dyanmicGap.x, line[4] + dyanmicGap.y } end return translated end function CrosshairDesigner.AdjustPolysByDynamicGap(polys, gap) local translatedPolys = {} local dynamicAmt = gap local poly, direction, dyanmicGap for k=1, #polys do poly = polys[k] direction = poly.dir dyanmicGap = direction * dynamicAmt translatedPolys[k] = CrosshairDesigner.TranslatePoly(poly, dyanmicGap) end return translatedPolys end function CrosshairDesigner.CalculateLinePolys(config) -- Parameter check if config.lineCount == nil then Error("No lineCount supplied to CalculateLinePolys!") end if config.thickness == nil then Error("No thickness supplied to CalculateLinePolys!") end if config.gap == nil then Error("No gap supplied to CalculateLinePolys!") end if config.length == nil then Error("No length supplied to CalculateLinePolys!") end -- Parameter mapping local lineCount = config.lineCount local rotation = config.rotation != nil and config.rotation or 0 local thickness = config.thickness local stretch = config.stretch != nil and config.stretch or 0 local gap = config.gap local length = config.length local addOutline = config.addOutline != nil and config.addOutline or false local outlineWidth = config.outlineWidth or 1 local pointInwards = config.pointInwards != nil and config.pointInwards or false local pointOutwards = config.pointOutwards != nil and config.pointOutwards or false local polys = {} local outlinePolys = {} -- Based on clockwise with start at the top, so right then left local leftThickness = math.floor(thickness/2) local rightThickness = math.ceil(thickness/2) -- top left, top right, bottom right, bottom left local topLeft, topRight, bottomRight, bottomLeft if pointInwards then topLeft = Vector(0, 0) topRight = Vector(0, 0) bottomRight = Vector(rightThickness-stretch, length) bottomLeft = Vector(-leftThickness-stretch, length) elseif pointOutwards then topLeft = Vector(-leftThickness, 0) topRight = Vector(rightThickness, 0) bottomRight = Vector(0-stretch, length) bottomLeft = Vector(0-stretch, length) else topLeft = Vector(-leftThickness, 0) topRight = Vector(rightThickness, 0) bottomRight = Vector(rightThickness-stretch, length) bottomLeft = Vector(-leftThickness-stretch, length) end -- For each line, calculate line and outline if requested for i=1, lineCount do local rot = (((360 / lineCount) * i) - rotation) % 360 local middleOffset = Vector(0, 0) local poly = {} -- 45 is the rotation offset needed to separate top and left from bottom and right -- This split was chosen because the HL2 crosshair uses the top right pixel -- The rotation goes anti-clockwise, hence using -cachedCross["Rotation"]) if rot >= 0+45 and rot <= (180+45)%360 then local gapOffset = math.ceil(gap/2) if (rot >=0+45 and rot <= 90+45) then -- Right side (with 0 rotation) middleOffset = Vector(-1, gapOffset) -- x = y, y = x else -- Top side (with 0 rotation) middleOffset = Vector(-1, -1 + gapOffset) end else local gapOffset = math.floor((gap/2)) + 1 if (rot > 180+45 and rot <= 270+45) then -- Left side (with 0 rotation) middleOffset = Vector(0, -1 + gapOffset) else -- Bottom side (with 0 rotation) middleOffset = Vector(0, gapOffset) end end if addOutline then poly = CrosshairDesigner.RotatePoly( CrosshairDesigner.TranslatePoly( -- Apply middle gap offset CrosshairDesigner.PointsToPoly({ topLeft+Vector(-outlineWidth, -outlineWidth), topRight+Vector(outlineWidth, -outlineWidth), bottomRight+Vector(outlineWidth, outlineWidth), bottomLeft+Vector(-outlineWidth, outlineWidth) }), middleOffset ), rot ) table.insert(outlinePolys, poly) end -- Normal line poly = CrosshairDesigner.RotatePoly( CrosshairDesigner.TranslatePoly( -- Apply middle gap offset CrosshairDesigner.PointsToPoly({topLeft, topRight, bottomRight, bottomLeft}), middleOffset ), rot ) table.insert(polys, poly) end return polys, outlinePolys end function CrosshairDesigner.CalculateLines(config) -- Parameter check if config.lineCount == nil then Error("No lineCount supplied to CalculateLines!") end if config.thickness == nil then Error("No thickness supplied to CalculateLines!") end if config.gap == nil then Error("No gap supplied to CalculateLines!") end if config.length == nil then Error("No length supplied to CalculateLines!") end -- Parameter mapping local lineCount = config.lineCount local rotation = config.rotation != nil and config.rotation or 0 local thickness = config.thickness local stretch = config.stretch != nil and config.stretch or 0 local gap = config.gap local length = config.length local addOutline = config.addOutline != nil and config.addOutline or false local outlineWidth = config.outlineWidth != nil and config.outlineWidth or 1 local pointInwards = config.pointInwards != nil and config.pointInwards or false local pointOutwards = config.pointOutwards != nil and config.pointOutwards or false local lines = {} local outlineLines = {} -- Based on clockwise with start at the top, so right then left local leftThickness = math.floor(thickness/2) local rightThickness = math.ceil(thickness/2) local lineStart = Vector(0, 0) local lineEnd = Vector(0, length) -- For each line, calculate line and outline if requested for i=1, lineCount do local rot = (((360 / lineCount) * i) - rotation) % 360 local middleOffset = Vector(0, 0) local line = {} -- 45 is the rotation offset needed to separate top and left from bottom and right -- This split was chosen because the HL2 crosshair uses the top right pixel -- The rotation goes anti-clockwise, hence using -cachedCross["Rotation"]) -- Differs from DrawPoly version if rot >= 0+45 and rot <= (180+45)%360 then local gapOffset = math.ceil(gap/2) if (rot >=0+45 and rot <= 90+45) then -- Right side (with 0 rotation) middleOffset = Vector(0, gapOffset) -- x = y, y = x else -- Top side (with 0 rotation) middleOffset = Vector(0, gapOffset) end else local gapOffset = math.floor((gap/2)) + 1 if (rot > 180+45 and rot <= 270+45) then -- Left side (with 0 rotation) middleOffset = Vector(0, gapOffset) else -- Bottom side (with 0 rotation) middleOffset = Vector(0, gapOffset) end end if addOutline then -- based on | being the line -- right for w=1, outlineWidth do line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ not pointInwards and lineStart.x-leftThickness - w or lineStart.x, lineStart.y, not pointOutwards and lineEnd.x-leftThickness-stretch - w or lineEnd.x, lineEnd.y-stretch }, middleOffset )}, rot )} table.insert(outlineLines, line) -- left line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ not pointInwards and lineStart.x+rightThickness + w -1 or lineStart.x, lineStart.y, not pointOutwards and lineEnd.x+rightThickness-stretch + w -1 or lineEnd.x, lineEnd.y-stretch }, middleOffset )}, rot )} table.insert(outlineLines, line) -- inner if not pointInwards then line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ lineStart.x-leftThickness-outlineWidth, lineStart.y-1 + w, lineStart.x+rightThickness+outlineWidth, lineStart.y-1 +w }, middleOffset )}, rot )} table.insert(outlineLines, line) end -- outer if not pointOutwards then line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ lineEnd.x-leftThickness-stretch-outlineWidth, lineEnd.y-stretch + w -1, lineEnd.x+rightThickness-stretch+outlineWidth, lineEnd.y-stretch + w -1 }, middleOffset )}, rot )} table.insert(outlineLines, line) end end end -- Middle lines line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine( {lineStart.x, lineStart.y, lineEnd.x - stretch, lineEnd.y - stretch}, middleOffset )}, rot )} table.insert(lines, line) -- Thickness lines for t=2, thickness do local offset = math.floor(t/2) if t % 2 == 0 then -- Draw clockwise on other side of the line line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ not pointInwards and lineStart.x - offset or lineStart.x, lineStart.y, not pointOutwards and lineEnd.x - offset - stretch or lineEnd.x, lineEnd.y - stretch }, middleOffset )}, rot )} table.insert(lines, line) else -- Draw anti-clockwise on other side of the line line = {CrosshairDesigner.RotateLine( {CrosshairDesigner.TranslateLine({ not pointInwards and lineStart.x + offset or lineStart.x, lineStart.y, not pointOutwards and lineEnd.x + offset - stretch or lineEnd.x, lineEnd.y - stretch }, middleOffset )}, rot )} table.insert(lines, line) end end end return lines, outlineLines end local cachedCross = {} local cacheHits = 0 local cacheMisses = 0 local newCacheDepth = CrosshairDesigner.CacheSize() local cacheDepth = CrosshairDesigner.CacheSize() -- Pause cache stats on menu open local menuOpenState = 0 hook.Add("CrosshairDesigner_MenuOpened", "StartCacheStats", function() menuOpenState = 1 end) hook.Add("CrosshairDesigner_MenuClosed", "StopCacheStats", function() menuOpenState = 0 end) CrosshairDesigner.CacheHitPercent = function() return (cacheHits / (cacheHits + cacheMisses)) * 100 end -- Watch for cache size updates hook.Add("CrosshairDesigner_CacheSizeUpdate", "CrosshairDesigner.draw", function(newVal) cacheHits = 0 cacheMisses = 0 newCacheDepth = newVal end) -- Centre circle generation local generateCircle = function(x, y, radius, seg) local cir = {} table.insert(cir, {x=x, y=y, u=0.5, v=0.5}) for i = 0, seg do local a = math.rad((i/seg) * -360) table.insert(cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 }) end local a = math.rad(0) table.insert(cir, { x = x + math.sin( a ) * radius, y = y + math.cos( a ) * radius, u = math.sin( a ) / 2 + 0.5, v = math.cos( a ) / 2 + 0.5 }) return cir end local function generateCrossCircle(x, y, radius, segments, rotation) local circle = generateCircle(0, 0, radius, segments) if rotation ~= 0 then circle = CrosshairDesigner.RotatePoly( circle, rotation ) end return CrosshairDesigner.TranslatePoly( circle, {x, y} ) end local function updateStaticCircles() cachedCross.circleOutline = generateCrossCircle( (ScrW()/2) + cachedCross["CircleXOffset"], (ScrH()/2) + 1 - cachedCross["CircleYOffset"], cachedCross["CircleRadius"] + cachedCross["CircleOutlineThickness"], cachedCross["CircleSegments"], cachedCross["CircleRotation"] ) cachedCross.circle = generateCrossCircle( (ScrW()/2) + cachedCross["CircleXOffset"], (ScrH()/2) + 1 - cachedCross["CircleYOffset"], cachedCross["CircleRadius"], cachedCross["CircleSegments"], cachedCross["CircleRotation"] ) end --[[ Smooth dynamic crosshair (copied from old version) todo - update ]]-- local IsValid = IsValid local dynamic = 0 local hc_shootingvalue = 0 local ply local hc_dynamiccorsshair = function() if not cachedCross["Dynamic"] or timer.Exists("HC_SmoothDynamics") then timer.Destroy ("HC_SmoothDynamics") end if cachedCross["Dynamic"] then timer.Create( "HC_SmoothDynamics", 0.03, 0, function() if not IsValid(ply) then return end local hc_dynamicamount = cachedCross["DynamicSize"] local speedzzz = ply:GetVelocity():Length() if ply:Health() > 0 and ply:GetActiveWeapon():IsValid() then if ply:GetActiveWeapon():Clip1() > 0 then local spedzLength = string.len(speedzzz) local inAttack = ply:KeyDown(IN_ATTACK) if speedzzz / spedzLength < hc_dynamicamount and speedzzz / spedzLength > 3 then dynamic = speedzzz / spedzLength elseif speedzzz / spedzLength < 3 and inAttack and hc_shootingvalue < hc_dynamicamount / 3 then hc_shootingvalue = hc_shootingvalue + 0.5 dynamic = hc_shootingvalue elseif speedzzz / spedzLength < 3 and !inAttack and hc_shootingvalue > 0 then hc_shootingvalue = hc_shootingvalue - 0.5 dynamic = hc_shootingvalue elseif speedzzz / spedzLength < 4 and !inAttack and hc_shootingvalue < 1 then ---- IN_ATTACK1 instead dynamic = speedzzz / spedzLength end else dynamic = 0 end end end) end end local trace = {} local traceResult = nil local target local alreadyTraced = false local LocalPlayer = LocalPlayer local surface = surface local ScrH = ScrH local ScrW = ScrW local IsValid = IsValid local unpack = unpack local math_Round = math.Round local mx, my local shouldDraw = true local crossCentre = Vector(0, 0) local crossCircleCentre = Vector(0, 0) local defaultColour = Color(0,0,0,255) local drawCol = defaultColour local outlineCol = defaultColour -- Cache prototype -- TODO: Look at removing code duplication though it seems tricky due to -- all the minor differences local cache2 = {} cache2Ordered = {} local key local function cacheAdjustPolysByDynamicGap(polys, isOutline, dynamic, rotation) if cacheDepth < 2 then return CrosshairDesigner.AdjustPolysByDynamicGap(polys, dynamic, rotation) end -- Do not need to hash polys or rotation key = tostring(isOutline) .. '-' .. tostring(dynamic) if cache2[key] then -- move the cache hit to the front table.RemoveByValue(cache2Ordered, key) table.insert(cache2Ordered, 1, key) -- remove a miss from the end if #cache2Ordered > cacheDepth then miss_key = table.remove(cache2Ordered, #cache2Ordered) cache2[miss_key] = nil end cacheHits = cacheHits + 1 - menuOpenState return cache2[key] else -- calc new result for cache result = CrosshairDesigner.AdjustPolysByDynamicGap(polys, dynamic, rotation) cache2[key] = result table.insert(cache2Ordered, 1, key) -- remove a miss from the end if #cache2Ordered > cacheDepth then miss_key = table.remove(cache2Ordered, #cache2Ordered) cache2[miss_key] = nil end cacheMisses = cacheMisses + 1 - menuOpenState return result end end local cache3 = {} cache3Ordered = {} local function cacheTranslatePolys(polys, isOutline, screenCentre, dynamic) if cacheDepth < 2 then return CrosshairDesigner.TranslatePolys(polys, screenCentre) end key = tostring(isOutline) .. '-' .. tostring(screenCentre) .. '-' .. tostring(dynamic) if cache3[key] then -- move the cache hit to the front table.RemoveByValue(cache3Ordered, key) table.insert(cache3Ordered, 1, key) -- remove a miss from the end if #cache3Ordered > cacheDepth then miss_key = table.remove(cache3Ordered, #cache3Ordered) cache3[miss_key] = nil end cacheHits = cacheHits + 1 - menuOpenState return cache3[key] else -- calc new result for cache result = CrosshairDesigner.TranslatePolys(polys, screenCentre) cache3[key] = result table.insert(cache3Ordered, 1, key) -- remove a miss from the end if #cache3Ordered > cacheDepth then miss_key = table.remove(cache3Ordered, #cache3Ordered) cache3[miss_key] = nil end cacheMisses = cacheMisses + 1 - menuOpenState return result end end local cache4 = {} cache4Ordered = {} local function cacheAdjustLinesByDynamicGap(lines, dynamic) if cacheDepth < 2 then return CrosshairDesigner.AdjustLinesByDynamicGap(lines, dynamic) end key = tostring(dynamic) if cache4[key] then -- move the cache hit to the front table.RemoveByValue(cache4Ordered, key) table.insert(cache4Ordered, 1, key) -- remove a miss from the end if #cache4Ordered > cacheDepth then miss_key = table.remove(cache4Ordered, #cache4Ordered) cache4[miss_key] = nil end cacheHits = cacheHits + 1 - menuOpenState return cache4[key] else -- calc new result for cache result = CrosshairDesigner.AdjustLinesByDynamicGap(lines, dynamic) cache4[key] = CrosshairDesigner.AdjustLinesByDynamicGap(lines, dynamic) table.insert(cache4Ordered, 1, key) -- remove a miss from the end if #cache4Ordered > cacheDepth then miss_key = table.remove(cache4Ordered, #cache4Ordered) cache4[miss_key] = nil end cacheMisses = cacheMisses + 1 - menuOpenState return result end end local cache5 = {} cache5Ordered = {} local function cacheAdjustOutlinesByDynamicGap(outlines, dynamic) if cacheDepth < 2 then return CrosshairDesigner.AdjustOutlinesByDynamicGap(outlines, dynamic) end key = tostring(dynamic) if cache5[key] then -- move the cache hit to the front table.RemoveByValue(cache5Ordered, key) table.insert(cache5Ordered, 1, key) -- remove a miss from the end if #cache5Ordered > (cacheDepth / 2) then miss_key = table.remove(cache5Ordered, #cache5Ordered) cache5[miss_key] = nil end cacheHits = cacheHits + 1 - menuOpenState return cache5[key] else -- calc new result for cache result = CrosshairDesigner.AdjustOutlinesByDynamicGap(outlines, dynamic) cache5[key] = result table.insert(cache5Ordered, 1, key) -- remove a miss from the end if #cache5Ordered > (cacheDepth / 2) then miss_key = table.remove(cache5Ordered, #cache5Ordered) cache5[miss_key] = nil end cacheMisses = cacheMisses + 1 - menuOpenState return result end end local cache6 = {} cache6Ordered = {} local function cacheTranslateLines(lines, isOutline, screenCentre, dynamic) if cacheDepth < 2 then return CrosshairDesigner.TranslateLines(lines, screenCentre) end key = tostring(isOutline) .. '-' .. tostring(screenCentre) .. '-' .. tostring(dynamic) if cache6[key] then -- move the cache hit to the front table.RemoveByValue(cache6Ordered, key) table.insert(cache6Ordered, 0, key) -- remove a miss from the end if #cache6Ordered > cacheDepth then miss_key = table.remove(cache6Ordered, #cache6Ordered) cache6[miss_key] = nil end cacheHits = cacheHits + 1 - menuOpenState return cache6[key] else -- calc new result for cache result = CrosshairDesigner.TranslateLines(lines, screenCentre) cache6[key] = result table.insert(cache6Ordered, 0, key) -- remove a miss from the end if #cache6Ordered > cacheDepth then miss_key = table.remove(cache6Ordered, #cache6Ordered) cache6[miss_key] = nil end cacheMisses = cacheMisses + 1 - menuOpenState return result end end local previousDrawCol = Color(0,0,0,255) local nextDrawCol = Color(0,0,0,255) local Lerp = Lerp local function LerpColor(t, from, to) return Color( Lerp(t, from.r, to.r), Lerp(t, from.g, to.g), Lerp(t, from.b, to.b), Lerp(t, from.a, to.a) ) end local cumulativeDrawFrameTime = 0 local function calcInvertedColours(previousCol) local updateRate = 0.2 cumulativeDrawFrameTime = cumulativeDrawFrameTime + RealFrameTime() if cumulativeDrawFrameTime > updateRate then previousDrawCol = previousCol render.CapturePixels() local a, b, c = render.ReadPixel(mx, my) local r, g, b = 255 - a, 255 - b, 255 - c if cachedCross['HighContrastInvertedCol'] then local constrast = r * 0.299 + g * 0.587 + b * 0.114 if constrast > 186 then nextDrawCol = Color(255, 255, 255, 255) else nextDrawCol = Color(0, 0, 0, 255) end else nextDrawCol = Color(r, g, b, 255) end cumulativeDrawFrameTime = 0 end return LerpColor(cumulativeDrawFrameTime / (updateRate / 2), previousDrawCol, nextDrawCol) end local Crosshair = function() -- Conditions for crosshair to be drawn shouldDraw = hook.Run("HUDShouldDraw", "CrosshairDesiger_Crosshair") -- drawCol = defaultColour alreadyTraced = false if not shouldDraw or not IsValid(ply) then return end -- Thanks Simple ThirdPerson - https://github.com/Metastruct/simplethirdperson/blob/master/lua/autorun/thirdperson.lua#L933 if cachedCross["TraceDraw"] then if not alreadyTraced then trace.start = ply:GetShootPos() trace.endpos = trace.start + ply:GetAimVector() * 9000 trace.filter = ply traceResult = util.TraceLine(trace) end local pos = traceResult.HitPos:ToScreen() -- smooth out jitter caused by tracing from eyes mx, my = math.Round(pos.x - 1), math.Round(pos.y) else -- Align with HL2 crosshair mx = (ScrW() / 2) - 1 my = ScrH() / 2 end crossCentre.x = mx + cachedCross["CrossXOffset"] crossCentre.y = my - cachedCross["CrossYOffset"] crossCircleCentre.x = mx + cachedCross["CircleXOffset"] crossCircleCentre.y = my - cachedCross["CircleYOffset"] + 1 -- Ignore texture set by other addons draw.NoTexture() -- Change col on target if cachedCross["ColOnTarget"] then trace.start = ply:GetShootPos() trace.endpos = trace.start + ply:GetAimVector() * 9000 trace.filter = ply traceResult = util.TraceLine(trace) alreadyTraced = true target = traceResult.Entity if IsValid(target) and (target:IsPlayer() or target:IsNPC()) then drawCol = cachedCross["TargetColour"] else if cachedCross["InvertCol"] then drawCol = calcInvertedColours(drawCol) if cachedCross["InvertOutlineCol"] then outlineCol = drawCol else outlineCol = cachedCross["OutlineColour"] end elseif cachedCross["InvertOutlineCol"] then outlineCol = calcInvertedColours(outlineCol) drawCol = cachedCross["Colour"] else drawCol = cachedCross["Colour"] outlineCol = cachedCross["OutlineColour"] end end else if cachedCross["InvertCol"] then drawCol = calcInvertedColours(drawCol) if cachedCross["InvertOutlineCol"] then outlineCol = drawCol else outlineCol = cachedCross["OutlineColour"] end elseif cachedCross["InvertOutlineCol"] then outlineCol = calcInvertedColours(outlineCol) drawCol = cachedCross["Colour"] else drawCol = cachedCross["Colour"] outlineCol = cachedCross["OutlineColour"] end end dynamicGap = math.Round(dynamic) if cachedCross["UseLine"] then if cachedCross["FillDraw"] then -- -- Draw poly renderer -- local polys = cachedCross["LinePolys"] or {} local outlinePolys = cachedCross["OutlinePolys"] or {} -- Apply dynamic offset if cachedCross["Dynamic"] then polys = cacheAdjustPolysByDynamicGap(polys, false, dynamicGap, cachedCross["Rotation"]) outlinePolys = cacheAdjustPolysByDynamicGap(outlinePolys, true, dynamicGap, cachedCross["Rotation"]) end -- Translate to middle of screen polys = cacheTranslatePolys(polys, false, crossCentre, dynamicGap) outlinePolys = cacheTranslatePolys(outlinePolys, true, crossCentre, dynamicGap) -- Draw outline surface.SetDrawColor(outlineCol) for k=1, #outlinePolys do surface.DrawPoly(outlinePolys[k]) end -- Draw crosshair inner surface.SetDrawColor(drawCol) for k=1, #polys do surface.DrawPoly(polys[k]) end else -- -- Draw line renderer -- local lines = cachedCross["Lines"] or {} local outlines = cachedCross["Outlines"] or {} local offset = cachedCross["LineStyle"] > 0 and 1 or 0 -- pointInwards and 1 or pointOutwards and 1 or 0 -- Apply dynamic offset if cachedCross["Dynamic"] then lines = cacheAdjustLinesByDynamicGap(lines, dynamicGap) outlines = cacheAdjustOutlinesByDynamicGap(outlines, dynamicGap) end -- Translate to middle of screen lines = cacheTranslateLines(lines, false, crossCentre, dynamicGap) outlines = cacheTranslateLines(outlines, true, crossCentre, dynamicGap) -- Draw lines surface.SetDrawColor(drawCol) for k=1, #lines do surface.DrawLine(unpack(lines[k])) end -- Draw crosshair outline surface.SetDrawColor(outlineCol) for k=1, #outlines do surface.DrawLine(unpack(outlines[k])) end end end if cachedCross["UseCircle"] then surface.SetDrawColor(drawCol) if cachedCross["CircleRadius"] == 1 then -- Pixel perfect under the HL2 crosshair surface.DrawRect(crossCircleCentre.x, crossCircleCentre.y, 1, 1) else -- If the circle pos is based off of tracing, -- then it needs updating every frame if cachedCross["TraceDraw"] then cachedCross.circleOutline = generateCrossCircle( crossCircleCentre.x, crossCircleCentre.y, cachedCross["CircleRadius"] + cachedCross["CircleOutlineThickness"], cachedCross["CircleSegments"], cachedCross["CircleRotation"] ) cachedCross.circle = generateCrossCircle( crossCircleCentre.x, crossCircleCentre.y, cachedCross["CircleRadius"], cachedCross["CircleSegments"], cachedCross["CircleRotation"] ) end -- Draw outline if cachedCross["CircleOutlineThickness"] > 0 then surface.SetDrawColor(outlineCol) surface.DrawPoly(cachedCross.circleOutline) end -- Draw inner surface.SetDrawColor(drawCol) surface.DrawPoly(cachedCross.circle) end end -- Update cache size after drawing instead of in hook.Add. -- Otherwise GMod updates the value in the middle of drawing -- and we get script errors with null indexes if newCacheDepth ~= cacheDepth then cacheDepth = newCacheDepth end end CrosshairDesigner.FormatBytes = function(bytes, precision) precision = precision or 2 -- local units = {'B', 'KiB', 'MiB', 'GiB', 'TiB'} -- local bytes = math.max(bytes, 0) -- local pow = math.floor((bytes and math.log(bytes) or 0) / math.log(1024)) -- pow = math.min(pow, #units) -- return math.Round(bytes, precision) .. ' ' .. units[pow] if (bytes > math.pow(1024,3)) then return math.Round(bytes / math.pow(1024,3), precision) .."GB" elseif (bytes > math.pow(1024,2)) then return math.Round(bytes / math.pow(1024,2), precision) .."MB" elseif (bytes > 1024) then return math.Round(bytes / 1024, precision) .."KB" else return tostring(bytes).."B" end end CrosshairDesigner.CalcMemoryUsage = function() local INT_MEM = 4 local CHAR_MEM = 1 local ints = 0 local chars = 0 local intsPerLine = 5 local charsPerLine = 1 local intsPerPoly = 9 local charsPerPoly = 9 local info = CrosshairDesigner.CalcInfo() -- keys (chars) representing one cache entry local keyLenPerPoly = string.len("true") + (string.len("100")*2) + string.len("960.000000 540.000000 0.000000") local keyLenPerLine = (string.len("true")*3) + string.len("100") + string.len("960.000000 540.000000 0.000000") local keyLength = cachedCross["FillDraw"] and keyLenPerPoly or keyLenPerLine -- multiplier represents the extra data stored due to everything using seaparte cache counts local multiplier = cachedCross["FillDraw"] and 2 or 3 ints = (info.lines * intsPerLine * multiplier) + (info.polys * intsPerPoly * multiplier) chars = (info.lines * charsPerLine * multiplier) + (info.polys * charsPerPoly * multiplier) + keyLength usedBytes = (ints * INT_MEM) + (chars * CHAR_MEM) return usedBytes end CrosshairDesigner.CalcInfo = function() -- If menu is opened before crosshair finished loading if CrosshairDesigner.FinishLoad == nil then return { lines=0, polys=0 } end info = {} if cachedCross["FillDraw"] then info.lines = 0 info.polys = cachedCross["Segments"] * math.max(cachedCross["Outline"]+1, 1) else info.lines = (cachedCross["Segments"] * math.max(cachedCross["Thickness"], 1)) + (cachedCross["Segments"] * cachedCross["Outline"] * 4) info.polys = 0 end if cachedCross["UseCircle"] then info.polys = info.polys + 1 if cachedCross["CircleOutlineThickness"] > 0 then info.polys = info.polys + 1 end end return info end local LINE_STYLE = { RECTANLE = 0, INWARDS = 1, OUTWARDS = 2 } local function updateCalculated() -- clear cache cache2 = {} cache2Ordered = {} cache3 = {} cache3Ordered = {} cache4 = {} cache4Ordered = {} cache5 = {} cache5Ordered = {} cache6 = {} cache6Ordered = {} cacheHits = 0 cacheMisses = 0 -- Only update if all values are valid local isValid, inValid = CrosshairDesigner.IsValidCrosshair({ ["Segments"] = cachedCross["Segments"], ["Rotation"] = cachedCross["Rotation"], ["Thickness"] = cachedCross["Thickness"], ["Stretch"] = cachedCross["Stretch"], ["Gap"] = cachedCross["Gap"], ["Length"] = cachedCross["Length"], ["Outline"] = cachedCross["Outline"], ["LineStyle"] = cachedCross["LineStyle"] }) if not isValid then PrintTable(inValid) return end -- Poly based if cachedCross["FillDraw"] then local polys, outlinePolys = CrosshairDesigner.CalculateLinePolys({ lineCount = cachedCross["Segments"], rotation = cachedCross["Rotation"], thickness = cachedCross["Thickness"], stretch = cachedCross["Stretch"], gap = cachedCross["Gap"], length = cachedCross["Length"], addOutline = cachedCross["Outline"] > 0, outlineWidth = cachedCross["Outline"], pointInwards = cachedCross["LineStyle"] == LINE_STYLE.INWARDS, pointOutwards = cachedCross["LineStyle"] == LINE_STYLE.OUTWARDS, }) cachedCross["LinePolys"] = polys cachedCross["OutlinePolys"] = outlinePolys else -- Line based local lines, lineOutlines = CrosshairDesigner.CalculateLines({ lineCount = cachedCross["Segments"], rotation = cachedCross["Rotation"], thickness = cachedCross["Thickness"], stretch = cachedCross["Stretch"], gap = cachedCross["Gap"], length = cachedCross["Length"], addOutline = cachedCross["Outline"] > 0, outlineWidth = cachedCross["Outline"], pointInwards = cachedCross["LineStyle"] == LINE_STYLE.INWARDS, pointOutwards = cachedCross["LineStyle"] == LINE_STYLE.OUTWARDS, }) cachedCross["Lines"] = lines cachedCross["Outlines"] = lineOutlines end end local function updateColours() cachedCross["Colour"] = Color( cachedCross["Red"], cachedCross["Green"], cachedCross["Blue"], cachedCross["Alpha"] ) cachedCross["TargetColour"] = Color( cachedCross["TargetRed"], cachedCross["TargetGreen"], cachedCross["TargetBlue"], cachedCross["TargetAlpha"] ) cachedCross["OutlineColour"] = Color( cachedCross["OutlineRed"], cachedCross["OutlineGreen"], cachedCross["OutlineBlue"], cachedCross["OutlineAlpha"] ) end -- Update cached values hook.Add("CrosshairDesigner_ValueChanged", "UpdateCrosshair", function(convar, val) local data = CrosshairDesigner.GetConvarData(convar) if not data then return end cachedCross[data.id] = val if data.menuGroup == "circle" and CrosshairDesigner.FinishLoad ~= nil then updateStaticCircles() end if data.id == "Dynamic" then if not val then if timer.Exists("HC_SmoothDynamics") then timer.Destroy("HC_SmoothDynamics") dynamic = 0 end else if CrosshairDesigner.FinishLoad ~= nil then hc_dynamiccorsshair() end end end if CrosshairDesigner.FinishLoad ~= nil then updateColours() updateCalculated() end end) -- Load cached values hook.Add("CrosshairDesigner_FullyLoaded", "CrosshairDesigner_SetupDrawing", function(tbl) for i, data in pairs(CrosshairDesigner.GetConvarDatas()) do -- Do not update if the value has already been set if cachedCross[data.id] == nil then if data.isBool then cachedCross[data.id] = CrosshairDesigner.GetBool(data.id) else cachedCross[data.id] = CrosshairDesigner.GetInt(data.id) end end end updateStaticCircles() updateColours() updateCalculated() timer.Create("CrosshairDesigner_WaitForValidPly", 0.2, 0, function() ply = LocalPlayer() if IsValid(ply) then timer.Remove("CrosshairDesigner_WaitForValidPly") end end) hook.Add("HUDPaint", "CrosshairDesigner_DrawCrosshair", Crosshair) hc_dynamiccorsshair() end) hook.Add("OnScreenSizeChanged", "CrosshairDesignerCenterCircle", function() updateStaticCircles() end) local buttonSizeYOffset = 10 -- fontSize+buttonSizeOffset = buttonSize local fontSize = 14 -- Later add option in settings, maybe also allow weight adjustment etc and type? local fontTitleSize = 20 local screenW, screenH = ScrW(), ScrH() local cols = { title = Color(35,35,35) } -- Return a frame size which takes into account the aspect ratio local function CalculateMenuSize(screenWidth, screenHeight) local baseW, baseH = 1920, 1080 -- Stops the menu from becoming wider if only the screen width increases local aspect = math.min(screenWidth/screenHeight, baseW/baseH) local diff = math.min((baseW/baseH) / (screenWidth/screenHeight), 1) local frameW = math.Round(screenWidth * (0.2 * ((1920/1080)-aspect +1)) * diff) local frameH = screenHeight return frameW, frameH end -- Return position for menu to be at top right local function CalculateMenuPos(calcMenuSize, screenWidth, screenHeight) local frameW, frameH = calcMenuSize(screenWidth, screenHeight) local x = screenWidth - frameW + 1 local y = 0 return x, y end -- Returns the factor to scale everything by to better support higher resolutions local function CalculateScaleFactor(calcMenuSize, screenWidth, screenHeight) local baseW, baseH = calcMenuSize(1920, 1080) local frameW, frameH = calcMenuSize(screenWidth, screenHeight) return math.max(frameW/baseW, 1) end -- Check for resolution changes hook.Add("OnScreenSizeChanged", "CrosshairDesignerMenuResize", function() if IsValid(CrosshairDesigner.Menu) then CrosshairDesigner.OpenMenu(true) -- Updates menu size if already open end end) CrosshairDesigner.IsMenuOpen = function() return CrosshairDesigner.Menu ~= nil and IsValid(CrosshairDesigner.Menu) and CrosshairDesigner.Menu:IsVisible() end hook.Add("CrosshairDesigner_ValueChanged", "UpdateStats", function(convar, val) if IsValid(CrosshairDesigner.Menu) then CrosshairDesigner.Sheet.Advanced.Mem.Recalculate() CrosshairDesigner.Sheet.Advanced.Info.Recalculate() end end) local function sortedConvarData(tbl, typeCheck, groupOrder) local sorted = {} -- group for i, group in ipairs(groupOrder) do for ii, row in ipairs(tbl) do if typeCheck(row) and row.menuGroup and row.menuGroup == group then table.insert(sorted, row) end end end -- add remainder for ii, row in ipairs(tbl) do if typeCheck(row) and not table.HasValue(sorted, row) then table.insert(sorted, row) end end return sorted end local function createSpacer() local spacer = vgui.Create("DPanel") spacer:Dock(TOP) spacer:DockMargin(0, 5, 0, 0) spacer:SetHeight(1) function spacer:Paint( w, h ) draw.RoundedBox( 8, 0, 0, w, h, Color( 90, 90, 90 ) ) end return spacer end CrosshairDesigner.OpenMenu = function(resolutionChanged) -- Make the frame seem the same size regardless of aspect ratio screenW, screenH = ScrW(), ScrH() local frameW, frameH = CalculateMenuSize(screenW, screenH) local frameX, frameY = CalculateMenuPos(CalculateMenuSize, screenW, screenH) --local scaleFactor = CalculateScaleFactor(CalculateMenuSize, screenW, screenH) -- To later use to change font size? -- Only open one copy if IsValid(CrosshairDesigner.Menu) then -- Update size or open menu if resolutionChanged then CrosshairDesigner.Menu:SetSize(frameW, frameH) CrosshairDesigner.Menu:SetPos(frameX, frameY) else CrosshairDesigner.Menu:SetVisible(true) end -- update the cache stat local stat = tostring(math.Round(CrosshairDesigner.CacheHitPercent(), 2)) or "0" CrosshairDesigner.Sheet.Advanced.Stat:SetText("Current cache hit percent: " .. stat .. "%") CrosshairDesigner.Sheet.Advanced.Mem.Recalculate() CrosshairDesigner.Sheet.Advanced.Info.Recalculate() hook.Run("CrosshairDesigner_MenuOpened", CrosshairDesigner.Menu) return end local markIfUnofficial = not CrosshairDesigner.IsOfficialRelease and " (unofficial release)" or "" CrosshairDesigner.Menu = vgui.Create( "DFrame" ) CrosshairDesigner.Menu:SetSize(frameW, frameH) CrosshairDesigner.Menu:SetPos(frameX, frameY) CrosshairDesigner.Menu:MakePopup(false) CrosshairDesigner.Menu:SetTitle( "Crosshair Designer " .. CrosshairDesigner.VERSION .. markIfUnofficial ) CrosshairDesigner.Menu.btnClose.DoClick = function(button) CrosshairDesigner.Menu:SetVisible(false) hook.Run("CrosshairDesigner_MenuClosed", CrosshairDesigner.Menu) end CrosshairDesigner.Sheet = vgui.Create("DPropertySheet", CrosshairDesigner.Menu) CrosshairDesigner.Sheet:Dock(FILL) CrosshairDesigner.Sheet.Settings = vgui.Create("DPanel", CrosshairDesigner.Sheet) CrosshairDesigner.Sheet.Settings.Paint = function(self, w, h) draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 0)) end CrosshairDesigner.Sheet:AddSheet("Settings", CrosshairDesigner.Sheet.Settings) CrosshairDesigner.Sheet.Saving = vgui.Create("DPanel", CrosshairDesigner.Sheet) CrosshairDesigner.Sheet.Saving.Paint = function(self, w, h) draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 0)) end CrosshairDesigner.Sheet:AddSheet("Saving", CrosshairDesigner.Sheet.Saving) CrosshairDesigner.Sheet.Advanced = vgui.Create("DPanel", CrosshairDesigner.Sheet) CrosshairDesigner.Sheet.Advanced.Paint = function(self, w, h) draw.RoundedBox(4, 0, 0, w, h, Color(0, 0, 0, 0)) end CrosshairDesigner.Sheet:AddSheet("Advanced", CrosshairDesigner.Sheet.Advanced) local convarDatas = CrosshairDesigner.GetConvarDatas() CrosshairDesigner.ScrollPanel = vgui.Create("DScrollPanel", CrosshairDesigner.Sheet.Settings) CrosshairDesigner.ScrollPanel:Dock(FILL) -- Create toggles local prevGroup = 'cross' local toggles = sortedConvarData( convarDatas, function(data) return data.isBool end, {'cross', 'hide', 'cross-circle', 'cirlce'} ) local excludeItems = {['HideFAS']=true, ['HideCW']=true} -- temporarily hide these whilst I decide how best to remove them for i, data in pairs(toggles) do if excludeItems[data.id] ~= true then if prevGroup ~= data.menuGroup then CrosshairDesigner.ScrollPanel:AddItem(createSpacer()) prevGroup = data.menuGroup end local checkBox = vgui.Create("DCheckBoxLabel", CrosshairDesigner.ScrollPanel) checkBox:SetText(data.title) checkBox:SetTextColor(Color(255, 255, 255, 255)) checkBox:SetFont("DermaDefaultBold") checkBox:SetConVar(data.var) checkBox:Dock( TOP ) checkBox:DockMargin( 0, 5, 0, 0 ) checkBox:SetTooltip(data.help) end end CrosshairDesigner.ScrollPanel:AddItem(createSpacer()) CrosshairDesigner.Sliders = {} -- Create sliders local prevGroup = 'cross' local slides = sortedConvarData( convarDatas, function(data) return not data.isBool and not data.isColour end, {'cross', 'hide', 'cross-circle', 'cirlce'} ) for i, data in pairs(slides) do if prevGroup ~= data.menuGroup then CrosshairDesigner.ScrollPanel:AddItem(createSpacer()) prevGroup = data.menuGroup end local label = vgui.Create("DLabel", CrosshairDesigner.ScrollPanel) label:SetTextColor(Color(255, 255, 255, 255)) label:SetText(data.title) label:SetFont("DermaDefaultBold") label:Dock(TOP) label:DockMargin(0, 5, 0, 0) local slider = vgui.Create("Slider", CrosshairDesigner.ScrollPanel) slider:SetMin(data.min) slider:SetMax(data.max) slider:SetDecimals(0) slider:SetConVar(data.var) slider:SetValue(CrosshairDesigner.GetInt(data.var)) slider:Dock(TOP) slider:DockMargin(0, 0, 0, 0) slider.var = data.var table.insert(CrosshairDesigner.Sliders, slider) end CrosshairDesigner.ScrollPanel:AddItem(createSpacer()) -- Colour picker for normal local label = vgui.Create("DLabel", CrosshairDesigner.ScrollPanel) label:SetTextColor(Color(255, 255, 255, 255)) label:SetFont("DermaDefaultBold") label:SetText("Normal crosshair colour") label:SetDark( 1 ) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) local colourPicker = vgui.Create("DColorMixer", CrosshairDesigner.ScrollPanel) colourPicker:SetPalette(true) colourPicker:SetAlphaBar(true) colourPicker:SetWangs(true) colourPicker:Dock(TOP) colourPicker:DockMargin(0, 5, 0, 0) colourPicker:SetColor(Color( CrosshairDesigner.GetInt("Red"), CrosshairDesigner.GetInt("Green"), CrosshairDesigner.GetInt("Blue"), CrosshairDesigner.GetInt("Alpha") )) CrosshairDesigner.colourPicker = colourPicker local confirm = vgui.Create("DButton", CrosshairDesigner.ScrollPanel) confirm:SetText("Normal colour") confirm:Dock(TOP) confirm:DockMargin(0, 5, 0, 0) confirm.DoClick = function() local colour = colourPicker:GetColor() CrosshairDesigner.SetValue("Red", colour.r) CrosshairDesigner.SetValue("Green", colour.g) CrosshairDesigner.SetValue("Blue", colour.b) CrosshairDesigner.SetValue("Alpha", colour.a) end -- Colour picker for target local label = vgui.Create("DLabel", CrosshairDesigner.ScrollPanel) label:SetTextColor(Color(255, 255, 255, 255)) label:SetFont("DermaDefaultBold") label:SetText("On target crosshair colour") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) local targetColourPicker = vgui.Create("DColorMixer", CrosshairDesigner.ScrollPanel) targetColourPicker:SetPalette(true) targetColourPicker:SetAlphaBar(true) targetColourPicker:SetWangs(true) targetColourPicker:Dock(TOP) targetColourPicker:DockMargin(0, 5, 0, 0) targetColourPicker:SetColor(Color( CrosshairDesigner.GetInt("TargetRed"), CrosshairDesigner.GetInt("TargetGreen"), CrosshairDesigner.GetInt("TargetBlue"), CrosshairDesigner.GetInt("TargetAlpha") )) CrosshairDesigner.targetColourPicker = targetColourPicker local targetConfirm = vgui.Create("DButton", CrosshairDesigner.ScrollPanel) targetConfirm:SetText("Target colour") targetConfirm:Dock(TOP) targetConfirm:DockMargin(0, 5, 0, 0) targetConfirm.DoClick = function() local colour = targetColourPicker:GetColor() CrosshairDesigner.SetValue("TargetRed", colour.r) CrosshairDesigner.SetValue("TargetGreen", colour.g) CrosshairDesigner.SetValue("TargetBlue", colour.b) CrosshairDesigner.SetValue("TargetAlpha", colour.a) end -- Colour picker for outline local label = vgui.Create("DLabel", CrosshairDesigner.ScrollPanel) label:SetTextColor(Color(255, 255, 255, 255)) label:SetFont("DermaDefaultBold") label:SetText("Outline colour (requires outline enabled)") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) local outlineColourPicker = vgui.Create("DColorMixer", CrosshairDesigner.ScrollPanel) outlineColourPicker:SetPalette(true) outlineColourPicker:SetAlphaBar(true) outlineColourPicker:SetWangs(true) outlineColourPicker:Dock(TOP) outlineColourPicker:DockMargin(0, 5, 0, 0) outlineColourPicker:SetColor(Color( CrosshairDesigner.GetInt("OutlineRed"), CrosshairDesigner.GetInt("OutlineGreen"), CrosshairDesigner.GetInt("OutlineBlue"), CrosshairDesigner.GetInt("OutlineAlpha") )) CrosshairDesigner.outlineColourPicker = outlineColourPicker local outlineConfirm = vgui.Create("DButton", CrosshairDesigner.ScrollPanel) outlineConfirm:SetText("Outline colour") outlineConfirm:Dock(TOP) outlineConfirm:DockMargin(0, 5, 0, 0) outlineConfirm.DoClick = function() local colour = outlineColourPicker:GetColor() CrosshairDesigner.SetValue("OutlineRed", colour.r) CrosshairDesigner.SetValue("OutlineGreen", colour.g) CrosshairDesigner.SetValue("OutlineBlue", colour.b) CrosshairDesigner.SetValue("OutlineAlpha", colour.a) end -- Saving menu now for i=1, 16 do local dPanel = vgui.Create("DPanel", CrosshairDesigner.Sheet.Saving) dPanel:Dock(TOP) dPanel:DockMargin(0, 5, 0, 0) local whichtextsave = GetConVar("Hc_crosssave_" .. i) local textEntry = vgui.Create("DTextEntry", dPanel) textEntry:Dock(FILL) textEntry:DockMargin(0, 0, 0, 0) textEntry:SetText(whichtextsave:GetString()) textEntry.OnChange = function( self ) RunConsoleCommand("Hc_crosssave_" .. i, (self:GetValue())) end local loadButton = vgui.Create("DButton", dPanel) loadButton:SetText("Load") loadButton:Dock(RIGHT) loadButton:DockMargin(0, 0, 0, 0) loadButton.DoClick = function() CrosshairDesigner.Load(i) end local saveButton = vgui.Create("DButton", dPanel) saveButton:SetText("Save") saveButton:Dock(RIGHT) saveButton:DockMargin(0, 0, 0, 0) saveButton.DoClick = function() if file.Exists("crosshair_designer/save_" .. i .. ".txt", "DATA") then CrosshairDesigner.OpenSavePrompt(i) else CrosshairDesigner.Save(i) end end end local toClipboard = vgui.Create("DButton", CrosshairDesigner.Sheet.Saving) toClipboard:SetText("Copy current crosshair to clipboard") toClipboard:Dock(TOP) toClipboard:DockMargin(0, 5, 0, 0) toClipboard.DoClick = function() SetClipboardText(CrosshairDesigner.CurrentToString()) end local toClipboard = vgui.Create("DButton", CrosshairDesigner.Sheet.Saving) toClipboard:SetText("Load crosshair from string (control+v to paste)") toClipboard:Dock(TOP) toClipboard:DockMargin(0, 5, 0, 0) toClipboard.DoClick = function() Derma_StringRequest( "Crosshair Designer", "Paste the crosshair settings string (control+v)", "", function(text) CrosshairDesigner.Load(0, text) end, function(text) end ) end local resetCrosshair = vgui.Create("DButton", CrosshairDesigner.Sheet.Saving) resetCrosshair:SetText("Reset crosshair to default") resetCrosshair:Dock(BOTTOM) resetCrosshair:DockMargin(0, 5, 0, 0) resetCrosshair.DoClick = function() prompt = { title="Crosshair Designer - Reset Crosshair", text="Are you sure? Any unsaved changes will be lost!", btn1text="Yes", btn2text="Cancel", btn1func=function(text) CrosshairDesigner.LoadDefaultCrosshair() end, btn2func=function(text) end } Derma_Query( prompt.text, prompt.title, prompt.btn1text, prompt.btn1func, prompt.btn2text, prompt.btn2func ) end -- Advanced TAB local label = vgui.Create("DLabel", CrosshairDesigner.Sheet.Advanced) label:SetTextColor(Color(255, 255, 255, 255)) label:SetAutoStretchVertical(true) label:SetWrap(true) label:SetText("Cache the calculations used to generate the crosshair at each position on the screen. The bigger the cache, the more memory used but the less you need to re-calculate and the higher the cache hit percent. - Can be left at 1 for static crosshairs (dynamic off + centre to player angles off)") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) local label = vgui.Create("DLabel", CrosshairDesigner.Sheet.Advanced) label:SetTextColor(Color(255, 255, 255, 255)) label:SetAutoStretchVertical(true) label:SetWrap(true) label:SetText("Placeholder") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) label.Recalculate = function() local stat = tostring(math.Round(CrosshairDesigner.CacheHitPercent(), 2)) or "0" label:SetText("Current cache hit percent: " .. stat .. "%") end CrosshairDesigner.Sheet.Advanced.Stat = label CrosshairDesigner.Sheet.Advanced.Stat.Recalculate() local cacheTickbox = vgui.Create("DCheckBoxLabel", CrosshairDesigner.Sheet.Advanced) cacheTickbox:SetText("Enable cache") cacheTickbox:SetValue(CrosshairDesigner.CacheEnabled()) cacheTickbox:Dock( TOP ) cacheTickbox:DockMargin( 0, 5, 0, 0 ) cacheTickbox:SetTooltip("Enable for more performance") hook.Add("CrosshairDesigner_CacheSizeUpdate", "MenuUpdate", function(newVal) CrosshairDesigner.Sheet.Advanced.cacheSlider:SetVisible(newVal >= 2) if newVal >= 2 then CrosshairDesigner.Sheet.Advanced.cacheSlider:SetValue(newVal) end CrosshairDesigner.Sheet.Advanced.Mem.Recalculate() CrosshairDesigner.Sheet.Advanced.Stat.Recalculate() end) cacheTickbox.OnChange = function(self, newVal) if newVal then CrosshairDesigner.SetCacheSize(5) else CrosshairDesigner.SetCacheSize(0) end end local cacheSlider = vgui.Create("DNumSlider", CrosshairDesigner.Sheet.Advanced) cacheSlider:SetMin(CrosshairDesigner.CacheMinSize) cacheSlider:SetMax(CrosshairDesigner.CacheMaxSize) cacheSlider:SetDecimals(0) cacheSlider:SetValue(CrosshairDesigner.CacheSize()) cacheSlider:Dock(TOP) cacheSlider:DockMargin(0, 0, 0, 0) cacheSlider:SetVisible(CrosshairDesigner.CacheEnabled()) cacheSlider.OnValueChanged = function(self, val) CrosshairDesigner.SetCacheSize(val) CrosshairDesigner.Sheet.Advanced.Stat.Recalculate() end CrosshairDesigner.Sheet.Advanced.cacheSlider = cacheSlider local label = vgui.Create("DLabel", CrosshairDesigner.Sheet.Advanced) label:SetTextColor(Color(255, 255, 255, 255)) label:SetAutoStretchVertical(true) label:SetWrap(true) label:SetText("Current estimated memory usage: Unknown") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) label.Recalculate = function() local crossMem = CrosshairDesigner.CalcMemoryUsage() local cacheSize = math.max(CrosshairDesigner.CacheSize(), 1) local mem = crossMem * cacheSize label:SetText("Current estimated memory usage: " .. CrosshairDesigner.FormatBytes(mem)) end label.Recalculate() CrosshairDesigner.Sheet.Advanced.Mem = label local label = vgui.Create("DLabel", CrosshairDesigner.Sheet.Advanced) label:SetTextColor(Color(255, 255, 255, 255)) label:SetAutoStretchVertical(true) label:SetWrap(true) label:SetText("Placeholder") label:SetDark(1) label:Dock(TOP) label:DockMargin(0, 5, 0, 0) label.Recalculate = function() local info = CrosshairDesigner.CalcInfo() local txt = "Crosshair stats:\n" txt = txt .. "Lines: " .. info.lines .. "\n" txt = txt .. "Polys: " .. info.polys .. "\n" label:SetText(txt) end CrosshairDesigner.Sheet.Advanced.Info = label CrosshairDesigner.Sheet.Advanced.Info.Recalculate() CrosshairDesigner.Sheet.Advanced:Add(createSpacer()) --[[ Held SWEP info ]]-- local getWep = function() return LocalPlayer():GetActiveWeapon() end local validWep = IsValid(LocalPlayer()) and IsValid(getWep()) local getWSIDorOther = function() return (validWep and getWep().WeaponWSID ~= nil) and getWep().WeaponWSID or "Unknown" end local getBaseWSIDorOther = function() return (validWep and getWep().BaseWeaponWSID ~= nil) and getWep().BaseWeaponWSID or "Unknown" end local dPanel = vgui.Create("DPanel", CrosshairDesigner.Sheet.Advanced) dPanel:Dock(TOP) dPanel:DockMargin(0, 5, 0, 0) dPanel.Paint = function() end local wepText = vgui.Create("DLabel", dPanel) wepText:Dock(FILL) wepText:SetTextColor(Color(255, 255, 255, 255)) wepText:SetText("Held SWEP WSID: " .. getWSIDorOther()) local loadButton = vgui.Create("DButton", dPanel) loadButton:SetText("Open Workshop") loadButton:Dock(FILL) loadButton:DockMargin(250, 0, 0, 0) loadButton.DoClick = function() if getWSIDorOther() == "Unknown" then Derma_Message("No SWEP found on Workshop (could be locally installed addon)", "Crosshair Designer", "OK") else gui.OpenURL("https://steamcommunity.com/sharedfiles/filedetails/?id=" .. getWSIDorOther()) end end -- local dPanel = vgui.Create("DPanel", CrosshairDesigner.Sheet.Advanced) dPanel:Dock(TOP) dPanel:DockMargin(0, 5, 0, 0) dPanel.Paint = function() end local wepBaseText = vgui.Create("DLabel", dPanel) wepBaseText:Dock(FILL) wepBaseText:SetTextColor(Color(255, 255, 255, 255)) wepBaseText:SetText("Held SWEP Base WSID: " .. getBaseWSIDorOther()) local loadButton = vgui.Create("DButton", dPanel) loadButton:SetText("Open Workshop") loadButton:Dock(FILL) loadButton:DockMargin(250, 0, 0, 0) loadButton.DoClick = function() if getBaseWSIDorOther() == "Unknown" then Derma_Message("No base SWEP found on Workshop (could be locally installed addon)", "Crosshair Designer", "OK") else gui.OpenURL("https://steamcommunity.com/sharedfiles/filedetails/?id=" .. getBaseWSIDorOther()) end end hook.Add("CrosshairDesinger_PlayerSwitchedWeapon", "UpdateMenu", function(ply, wep) if wepText and wepBaseText then wepText:SetText("Held SWEP WSID: " .. getWSIDorOther()) wepBaseText:SetText("Held SWEP Base WSID: " .. getBaseWSIDorOther()) end end) local dPanel = vgui.Create("DPanel", CrosshairDesigner.Sheet.Advanced) dPanel:Dock(TOP) dPanel:DockMargin(0, 5, 0, 0) dPanel.Paint = function() end local loadButton = vgui.Create("DButton", dPanel) loadButton:SetText("Request weapon pack support/compatibility") loadButton:Dock(FILL) loadButton:DockMargin(0, 0, 0, 0) loadButton.DoClick = function() problemWSID = (getWSIDorOther() ~= "Unknown") and getWSIDorOther() or getBaseWSIDorOther() problemURL = "https://steamcommunity.com/sharedfiles/filedetails/?id=" SetClipboardText(problemURL .. problemWSID) LocalPlayer():ChatPrint("Set clipboard text to " .. problemURL .. problemWSID) gui.OpenURL("https://steamcommunity.com/workshop/filedetails/discussion/590788321/3780246026239811661/") end -- Always at bottom hook.Run("CrosshairDesigner_MenuOpened", CrosshairDesigner.Menu) end concommand.Add("crosshairs", function() if CrosshairDesigner.IsMenuOpen() then CrosshairDesigner.Menu:SetVisible(false) hook.Run("CrosshairDesigner_MenuClosed", CrosshairDesigner.Menu) else CrosshairDesigner.OpenMenu() end end) concommand.Add("+crosshairs", function() CrosshairDesigner.OpenMenu() end) concommand.Add("-crosshairs", function() CrosshairDesigner.Menu:SetVisible(false) hook.Run("CrosshairDesigner_MenuClosed", CrosshairDesigner.Menu) end) hook.Add("CrosshairDesigner_CrosshairLoaded", "UpdateMenu", function() if CrosshairDesigner.Menu == nil or not IsValid(CrosshairDesigner.Menu) then return end for i, slider in pairs(CrosshairDesigner.Sliders) do local val = CrosshairDesigner.GetInt(slider.var) slider:SetValue(val) end CrosshairDesigner.colourPicker:SetColor(Color( CrosshairDesigner.GetInt("Red"), CrosshairDesigner.GetInt("Green"), CrosshairDesigner.GetInt("Blue"), CrosshairDesigner.GetInt("Alpha") )) CrosshairDesigner.targetColourPicker:SetColor(Color( CrosshairDesigner.GetInt("TargetRed"), CrosshairDesigner.GetInt("TargetGreen"), CrosshairDesigner.GetInt("TargetBlue"), CrosshairDesigner.GetInt("TargetAlpha") )) CrosshairDesigner.outlineColourPicker:SetColor(Color( CrosshairDesigner.GetInt("OutlineRed"), CrosshairDesigner.GetInt("OutlineGreen"), CrosshairDesigner.GetInt("OutlineBlue"), CrosshairDesigner.GetInt("OutlineAlpha") )) end) CrosshairDesigner.OpenSavePrompt = function(crossID) local Frame = vgui.Create("DFrame") Frame:SetPos(ScrW() / 2 - 150, ScrH() / 2 - 75) Frame:SetSize(300, 130) Frame:SetTitle("Confirm") Frame:SetVisible(true) Frame:SetBackgroundBlur(true) Frame:SetDraggable(false) Frame:ShowCloseButton(true) Frame:MakePopup() function Frame:Paint( w, h ) draw.RoundedBox(0, 0, 0, w, h, Color( 255, 93, 0 )) draw.RoundedBox(0, 1, 1, w-2, h-2, Color( 36, 36, 36 )) end local DLabel = vgui.Create("DLabel", Frame) DLabel:SetPos(20, 30 ) DLabel:SetText("Are you sure you want to override") DLabel:SizeToContents() local DLabel = vgui.Create("DLabel", Frame) DLabel:SetPos(20 , 50) DLabel:SetText(GetConVar("Hc_crosssave_" .. crossID):GetString() .. "?") DLabel:SizeToContents() local yesButton = vgui.Create("DButton", Frame) yesButton:SetText("Yes") yesButton:SetSize(40, 20) yesButton:SetPos(70, 90) yesButton.DoClick = function() CrosshairDesigner.Save(crossID) Frame:Close() end local noButton = vgui.Create("DButton", Frame) noButton:SetText("No") noButton:SetSize(40, 20) noButton:SetPos(190, 90) noButton.DoClick = function() Frame:Close() end end list.Set("DesktopWindows", "CrosshairDesigner", { title = "Crosshair", icon = "crosshair_designer/ui/logo.png", init = function(icon, window) CrosshairDesigner.OpenMenu() end }) local function conVarOrZero(conVar) var = GetConVar(conVar) if var ~= nil then return var:GetInt() end return 0 end local function HideWeaponCrosshairHUD(fn_name, fn_forceshow) fn_forceshow = fn_forceshow or function(wep) return false end return function(wep) -- sometimes a weapon will re-create the draw function (at least Modern Warefare weapons does) local original = wep[fn_name] if wep.CrosshairDesignerDetour == nil or wep.CrosshairDesignerDetour ~= wep[fn_name] then wep[fn_name] = function(...) if not CrosshairDesigner.GetBool('HideWeaponCrosshair') or fn_forceshow(wep) then return original(...) end end wep.CrosshairDesignerDetour = wep[fn_name] wep.CrosshairDesignerDetoured = true end end end --[[ Setup the client convars and callbacks to verify values ]]-- CrosshairDesigner.SetUpConvars({ -- Must be in this order as it's the order the values are read from file { id="ShowHL2", var="toggle_crosshair_hide", default="0", help="Show the half life crosshair", title="Show HL2/default crosshair", isBool=true, menuGroup="cross" }, { id="ShowCross", var="toggle_crosshair", default="1", help="Hide the custom crosshair", title="Show custom crosshair", isBool=true, menuGroup="cross" }, { id="HideOnADS", var="cross_ads", default="1", help="Hide the custom crosshair when aiming down sights", title="Hide when aiming down sights", isBool=true, menuGroup="hide" }, { id="UseLine", var="cross_line", default="1", help="Draw crosshair lines", title="Show crosshair lines", isBool=true, menuGroup="cross" }, { id="LineStyle", var="cross_arrow", default="0", help="Change the lines on the crosshair to be pointed:\n0 = Rectangle\n1 = Inwards\n2 = Outwards", title="Line style", min=0, max=2, menuGroup="cross" }, { id="UseCircle", var="cross_circle", default="0", help="Add a circle to the middle of the crosshair", title="Add circle crosshair", isBool=true, menuGroup="cross" -- Only in this group because it makes sense in the menu }, { id="Red", var="cross_hud_color_r", default="50", help="Change the amount of red in the crosshair", min=0, max=255, isColour=true, }, { id="Green", var="cross_hud_color_g", default="250", help="Change the amount of green in the crosshair", min=0, max=255, isColour=true, }, { id="Blue", var="cross_hud_color_b", default="50", help="Change the amount of blue in the crosshair", min=0, max=255, isColour=true, }, { id="Alpha", var="cross_hud_color_a", default="255", help="Change the transparency of the crosshair", min=0, max=255, isColour=true, }, { id="Gap", var="cross_gap", default="8", help="Change size of the gap in the middle of the crosshair", title="Gap in middle", min=0, max=100, menuGroup="cross" }, { id="Length", var="cross_length", default="7", help="Change the length of the lines in the crosshair", title="Length of lines", min=2, max=100, menuGroup="cross" }, { id="Thickness", var="cross_thickness", default="3", help="Change the thickness of the lines in the crosshair", title="Thickness of lines", min=1, max=100, menuGroup="cross" }, { id="Stretch", var="cross_stretch", default="0", help="The amount to stretch the crosshair by (disabled when using fill draw)", title="Stretch of lines", min=-180, max=180, menuGroup="cross" }, { id="CircleRadius", var="cross_radius", default="0", help="The radius for the circle if enabled with cross_circle", title="Circle radius", min=0, max=100, menuGroup="circle" }, { id="CircleSegments", var="cross_segments", default="0", help="The number of segments for the circle if enabled with cross_circle", title="Circle segments", min=0, max=100, menuGroup="circle" }, { id="ColOnTarget", var="hc_target_colour", default="1", help="Change the colour of the crosshair when aiming at a target", title="Change colour on target", isBool=true, menuGroup="cross-circle" }, { id="TargetRed", var="target_cross_hud_color_r", default="250", help="Change the amount of red in the crosshair when aiming at a target", min=0, max=255, isColour=true, }, { id="TargetGreen", var="target_cross_hud_color_g", default="46", help="Change the amount of green in the crosshair when aiming at a target", min=0, max=255, isColour=true, }, { id="TargetBlue", var="target_cross_hud_color_b", default="46", help="Change the amount of blue in the crosshair when aiming at a target", min=0, max=255, isColour=true, }, { id="TargetAlpha", var="target_cross_hud_color_a", default="255", help="Change the transparency of the crosshair when aiming at a target", min=0, max=255, isColour=true, }, { id="Dynamic", var="hc_dynamic_cross", default="0", help="Make the crosshair dynamic", title="Make dynamic", isBool=true, menuGroup="cross" }, { id="DynamicSize", var="hc_dynamic_amount", default="50", help="The amount the dynamic crosshair will move when enabled with hc_dynamic_cross", title="Dynamic effect size", min=0, max=100, menuGroup="cross" }, { id="HideInVeh", var="hc_vehicle_cross", default="1", help="Hide the crosshair when in a vehicle", title="Hide in vehicle", isBool=true, menuGroup="hide" }, -- NEW { id="HideInSpectate", var="crosshairdesigner_hideinspectate", default="1", help="Hide the crosshair when spectating", title="Hide when spectating", isBool=true, menuGroup="hide" }, { id="HideTTT", var="crosshairdesigner_hidettt", default="1", help="Hide the TTT crosshair", title="Hide TTT crosshair", isBool=true, menuGroup="hide" }, -- -- Do not show these two anymore in the menu -- replace with HideWeaponCrosshair -- { id="HideFAS", -- unused var="crosshairdesigner_hidefas", default="1", help="Hide the FA:S crosshair", title="Hide FA:S crosshair", isBool=true, menuGroup="hide" }, -- { -- hide one of these to keep the same number of args for numbered saves -- id="HideCW", -- unused -- var="crosshairdesigner_hidecw", -- default="1", -- help="Hide the CW 2.0 crosshair", -- title="Hide CW crosshair", -- isBool=true, -- menuGroup="hide" -- }, -- -- /end -- { id="HideWeaponCrosshair", -- replacement var="crosshairdesigner_hideweaponcross", -- default always 1 due to being set in crosshairdesigner_hidefas default=math.min(math.max(conVarOrZero("crosshairdesigner_hidecw"), conVarOrZero("crosshairdesigner_hidefas")), 1), help="Hide weapon crosshair", title="Hide FA:S, CW, MW, PWB2 crosshairs", isBool=true, menuGroup="hide" }, { id="TraceDraw", var="crosshairdesigner_tracedraw", default="0", help="Draw based on player angles", title="Centre to player angles", isBool=true, menuGroup="cross-circle" }, -- Outline { id="Outline", var="crosshair_designer_outline", default="0", help="Outlines the crosshair", title="Outline thickness", min=0, max=100, menuGroup="cross" }, { id="OutlineRed", var="crosshair_designer_outline_r", default="0", help="Change the amount of red in the outline of the crosshair", min=0, max=255, isColour=true, }, { id="OutlineGreen", var="crosshair_designer_outline_g", default="0", help="Change the amount of green in the outline of the crosshair", min=0, max=255, isColour=true, }, { id="OutlineBlue", var="crosshair_designer_outline_b", default="0", help="Change the amount of blue in the outline of the crosshair", min=0, max=255, isColour=true, }, { id="OutlineAlpha", var="crosshair_designer_outline_a", default="255", help="Change the transparency of the outline of the crosshair", min=0, max=255, isColour=true, }, -- Rotation { id="Rotation", var="crosshairdesigner_rotation", default="0", help="How much to rotate the crosshair by", title="Rotation", min=0, max=360, menuGroup="cross" }, -- Draw with polys instead of lines { id="FillDraw", var="crosshairdesigner_filldraw", default="0", help="Draw a single shape instead of multiple lines", title="Fill draw", isBool=true, menuGroup="cross" }, { id="Segments", var="crosshairdesigner_segments", default="4", help="Change the number of segemnts which make up the crosshair", title="Segments", min=1, max=100, menuGroup="cross" }, { id="HideInCameraView", var="crosshairdesigner_hideincameraview", default="0", help="Hides the crosshair when looking through a camera", title="Hide in camera view", isBool=true, menuGroup="hide" }, { id="InvertCol", var="crosshairdesigner_invertcol", default="0", help="Inverts the colour of the crosshair", title="Invert crosshair colour", isBool=true, menuGroup="cross-circle" }, { id="InvertOutlineCol", var="crosshairdesigner_invertoutlinecol", default="0", help="Inverts the colour of the outline", title="Invert outline colour", isBool=true, menuGroup="cross-circle" }, { id="HighContrastInvertedCol", var="crosshairdesigner_highconstrastinvertedcol", default="0", help="Forces the inverted colour to be either black or white", title="High contrast inverted colour", isBool=true, menuGroup="cross-circle" }, { id="CrossXOffset", var="crosshairdesigner_crossxoffset", default="0", help="Offset the crosshair on the horizontal axis", title="Cross X Offset", min=-25, max=25, menuGroup="cross" }, { id="CrossYOffset", var="crosshairdesigner_crossyoffset", default="0", help="Offset the crosshair on the vertical axis", title="Cross Y Offset", min=-25, max=25, menuGroup="cross" }, { id="CircleXOffset", var="crosshairdesigner_circlexoffset", default="0", help="Offset the circle on the horizontal axis", title="Circle X Offset", min=-25, max=25, menuGroup="circle" }, { id="CircleYOffset", var="crosshairdesigner_circleyoffset", default="0", help="Offset the circle on the vertical axis", title="Circle Y Offset", min=-25, max=25, menuGroup="circle" }, { id="CircleRotation", var="crosshairdesigner_circlerotation", default="0", help="How much to rotate the circle by", title="Circle Rotation", min=0, max=360, menuGroup="circle" }, { id="CircleOutlineThickness", var="crosshairdesigner_circleoutline", default="0", help="Outlines the circle crosshair", title="Circle Outline Thickness", min=0, max=100, menuGroup="circle" }, { id="HideWithHands", var="crosshairdesigner_hidewithhands", default="0", help="Hides the crosshair when using a hand swep", title="Hide with hands", isBool=true, menuGroup="hide" }, }) --[[ Crosshair checks Checks the currently held weapon and tries to read information from the weapon to workout if our custom crosshair should be hidden. Such as if the player is using the aiming down sights. ]]-- -- The more odd they are, the further down the -- list they should be placed, so they don't -- interfer with anything. -- -- If they're specific then add the param -- forceOnBaseClasses, and they will be -- used before anything else -- Also use forceOnBaseClasses to speedup lookup -- for known combinations -- TFA -- thank you TFA for being simple! -- + Scifi weapons CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'GetIronSights', ['fnIsValid'] = function(wep, cls) return wep.GetIronSights ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetIronSights() end, ['forceOnBaseClasses'] = { 'tfa_gun_base' } }) -- DarkRP special case for ls_sniper CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'ls_sniper', ['fnIsValid'] = function(wep, cls) return wep.GetIronsights ~= nil and cls == "ls_sniper" end, ['fnShouldHide'] = function(wep) return wep:GetIronsights() and wep:GetScopeLevel() > 1 end, ['forceOnBaseClasses'] = { 'weapon_cs_base2' } }) -- DarkRP uses lower case sights CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'GetIron[s]ights', ['fnIsValid'] = function(wep, cls) return wep.GetIronsights ~= nil and cls ~= "ls_sniper" end, ['fnShouldHide'] = function(wep) return wep:GetIronsights() end, }) -- If DarkRP has lower case sights, maybe some other addon has -- something similar with iron, or the whole thing CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Get[i]ron[s]ights', ['fnIsValid'] = function(wep, cls) return wep.Getironsights ~= nil end, ['fnShouldHide'] = function(wep) return wep:Getironsights() end, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = '[g]et[i]ron[s]ights', ['fnIsValid'] = function(wep, cls) return wep.getironsights ~= nil end, ['fnShouldHide'] = function(wep) return wep:getironsights() end, }) -- found variant in Crysis 2 weapons (1257784914) -- though getIronsights doesn't actually work for that addon CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = '[g]etIron[s]ights', ['fnIsValid'] = function(wep, cls) return wep.getIronsights ~= nil end, ['fnShouldHide'] = function(wep) return wep:getIronsights() end, }) -- Varient found in Sanctum 2 Weapons 391683214 CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'GetIronSight', ['fnIsValid'] = function(wep, cls) return wep.GetIronSight ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetIronSight() end, ['forceOnWSID'] = {391683214} }) -- Varient found in Titanfall Heavy Weapons 840091742 CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Iron', ['fnIsValid'] = function(wep, cls) return wep:GetNWInt("Iron", nil) ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetNWInt("Iron", nil) == 1 end, ['forceOnWSID'] = {840091742} }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Modern Warfare 2459720887', ['fnIsValid'] = function(wep, cls) return wep.GetAimDelta ~= nil and hasPrefix(cls, "mg_") end, ['fnShouldHide'] = function(wep) return wep:GetAimDelta() > 0.5 end, ['forceOnBaseClasses'] = { -- Triggers a bunch of others otherwise 'mg_base' }, ['onSwitch'] = function(wep) local fn_name = 'DrawCrosshairSticks' if wep.CrosshairDesignerDetour == nil or wep.CrosshairDesignerDetour ~= wep[fn_name] then fn_hide = HideWeaponCrosshairHUD(fn_name) -- Modern Warefare reloads a bunch of stuff when an attachment -- is added, so we need to re-apply the patch each time, including -- the trigger for the patch (SWEP:Attach) local applyAttachPatch = function(reapply_fn) local originalAttach = wep.Attach wep.Attach = function(...) local val = originalAttach(...) fn_hide(wep) reapply_fn(reapply_fn) return val end end applyAttachPatch(applyAttachPatch) fn_hide(wep) wep.CrosshairDesignerDetoured = true end end }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'ArcCW', ['fnIsValid'] = function(wep, cls) return wep.Sighted ~= nil and ArcCW ~= nil end, ['fnShouldHide'] = function(wep) return wep.Sighted or wep:GetState() == ArcCW.STATE_SIGHTS end, ['forceOnBaseClasses'] = { 'arccw_base' } -- detour manually handles in hide.lua }) -- DayOfDefeat weapons CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'GetNetworkedBool Iron[s]ights', ['fnIsValid'] = function(wep, cls) return wep.Weapon ~= nil and wep.Weapon:GetNetworkedBool("Ironsights", nil) ~= nil end, ['fnShouldHide'] = function(wep) return wep.Weapon:GetNetworkedBool("Ironsights", false) end }) -- Alternative IronSights GetNWBool vs GetNetworkedBool CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'GetNetworkedBool IronSights', ['fnIsValid'] = function(wep, cls) return wep.Weapon ~= nil and wep.Weapon:GetNWBool("IronSights", nil) ~= nil end, ['fnShouldHide'] = function(wep) return wep.Weapon:GetNWBool("IronSights", false) end }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'FA:S', ['fnIsValid'] = function(wep, cls) return wep.dt ~= nil and wep.dt.Status ~= nil end, ['fnShouldHide'] = function(wep) return wep.dt.Status == FAS_STAT_ADS end, ['forceOnBaseClasses'] = { 'fas2_base', }, ['onSwitch'] = function(wep) -- Hide FA:S 2 crosshair by setting alpha to 0 if not wep.CrosshairDesignerDetoured then local original = wep.DrawHUD wep.CrosshairDesignerDetoured = true wep.DrawHUD = function(...) if CrosshairDesigner.GetBool('HideWeaponCrosshair') then wep.CrossAlpha = 0 -- Temp set firemode to safe to force cross alpha to 0 -- Also temp hide grenade crosshair local originalVehicle = wep.Vehicle wep.Vehicle = true -- Call original draw hud local drawHUDResult = original(...) -- Revert back overrides wep.Vehicle = originalVehicle return drawHUDResult else return original(...) end end end end }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'CW 2.0', ['fnIsValid'] = function(wep, cls) return wep.dt ~= nil and wep.dt.State ~= nil end, ['fnShouldHide'] = function(wep) return wep.dt.State == CW_AIMING end, ['forceOnBaseClasses'] = { 'cw_base', } }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'M9K Legacy', ['fnIsValid'] = function(wep, cls) return wep.GetIronSights ~= nil and wep.IronSightsPos ~= nil and wep.RunSightsPos ~= nil and not MMM_M9k_IsBaseInstalled end, ['fnShouldHide'] = function(wep) return wep:GetIronSights() and wep.IronSightsPos ~= wep.RunSightsPos end, ['forceOnBaseClasses'] = { 'bobs_gun_base', 'bobs_scoped_base', 'bobs_shotty_base' } }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'M9K Remastered scoped', ['fnIsValid'] = function(wep, cls) return wep:GetNWInt("ScopeState", nil) ~= nil and MMM_M9k_IsBaseInstalled end, ['fnShouldHide'] = function(wep) return wep.IronSightState or wep:GetNWInt("ScopeState") > 0 end, ['forceOnBaseClasses'] = { 'bobs_scoped_base', } }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'M9K Remastered un scoped', ['fnIsValid'] = function(wep, cls) return wep.IronSightState ~= nil and MMM_M9k_IsBaseInstalled end, ['fnShouldHide'] = function(wep) return wep.IronSightState ~= nil and wep.IronSightState end, ['forceOnBaseClasses'] = { 'bobs_gun_base', 'bobs_shotty_base' } }) -- Crysis 2 weapons (1257784914) -- Hackery as getIronSights doesn't work as expected CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'base_autorif', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "tsp_") end, ['fnShouldHide'] = function(wep) return wep:GetDTBool(1) end, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'ARC9 Weapon Base 2910505837', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "arc9_") or table.HasValue({'arc9_base', 'arc9_base_nade', 'arc9_go_base'}, wep.Base) end, ['fnShouldHide'] = function(wep) return wep:GetInSights() and not wep:GetSight().CrosshairInSights -- akimbo support end, ['forceOnBaseClasses'] = { -- Triggers GetIron[S|s]igns in addition otherwise 'arc9_base', 'arc9_base_nade', 'arc9_go_base' } }) -- GetIronSights checks are added, but never trigger CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'PWB 2 1470662323', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_pwb2") and wep:GetNWBool("Iron", nil) ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetNWBool("Iron", false) end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), ['forceOnWSID'] = {916201112, 921842698, 922433152, 913618147, 917779957, 914808995, 918900571, 923515273, 1498694632, 1492190276, 1470662323, 1469422668, 1468151297}, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Unreal Tournament SWEPs 189453748', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_ut99") end, ['fnShouldHide'] = function(wep) return false end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), ['forceOnBaseClasses'] = {'weapon_ut99_base'}, ['forceOnWSID'] = {189453748} }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'DOOM 2016/Eternal Weapons 2296325632', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_dredux") end, ['fnShouldHide'] = function(wep) return false -- no ADS in addon end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawCrosshairElementRotated'), ['forceOnBaseClasses'] = {'weapon_dredux_base2', 'weapon_dredux_base3'}, ['forceOnWSID'] = {2296325632} }) -- GetIronSights checks are added, but never trigger CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Dr>Breens Private Reserve Weaponry 2506186936', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "azbr_pr") and wep.GetIsZoomedIn ~= nil end, ['fnShouldHide'] = function(wep) return wep.GetIsZoomedIn and wep:GetIsZoomedIn() end, ['forceOnWSID'] = {2506186936} }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Classic CS:S weapons for GMod 13 124725938', ['fnIsValid'] = function(wep, cls) return wep.Base ~= nil and wep.Base == "weapon_cs_base" end, ['fnShouldHide'] = function(wep) return wep:GetNWBool("IronSights", false) -- duplciates others end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), ['forceOnBaseClasses'] = {'weapon_cs_base'}, ['forceOnWSID'] = {124725938} }) -- Hide their hud only when not scoped / otherwise swap in our crosshair CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Team Fortress 2 Weapon Pack 949733637', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "tf_weapon") end, ['fnShouldHide'] = function(wep) return wep:GetNWInt("CrosshairAlpha", 255) == 0 end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD', function(wep) return (wep:GetNWInt("ScopeLaserAlpha", 0) == 255 or wep:GetNWInt("ScopeAlpha", 0) == 255) -- Allow HUD for scoped weapons end), ['forceOnWSID'] = {949733637} }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'CS:GO Weapons 163660', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_csgo") end, ['fnShouldHide'] = function(wep) return ( (wep.GetZoomLevel and wep:GetZoomLevel() ~= 1) or (wep.GetIronsights and wep:GetIronsights()) -- specific to weapon_csgo_rif_sg553 ) end, ['forceOnWSID'] = {163660}, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Counter-Strike: Global Offensive Operation Breakout Weapon Pack 1257243225', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_csgo_breakout") end, ['fnShouldHide'] = function(wep) return wep:GetNWInt("ScopeAlpha", 0) == 255 or wep:GetNWInt("ScopeAlpha", 0) == 1 end, ['forceOnWSID'] = {1257243225}, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD', function(wep) return wep:GetNWInt("ScopeAlpha", 0) == 255 end) }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Counter-Strike: Global Offensive Assault Rifle Pack 1239501421', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_csgo") end, ['fnShouldHide'] = function(wep) return tonumber(wep:GetNWInt("ScopeAlpha", 0)) == 1 end, ['forceOnWSID'] = {1239501421}, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD', function(wep) return tonumber(wep:GetNWInt("ScopeAlpha", 0)) == 1 end) }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Counter-Strike: Global Offensive Sniper Rifle Pack 1244760503', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_csgo") end, ['fnShouldHide'] = function(wep) return wep:GetNWInt("ScopeAlpha", 0) == 255 end, ['forceOnWSID'] = {1244760503}, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'DOOM 3 SWEPs 210267782', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_doom3") end, ['fnShouldHide'] = function(wep) return wep.GetIronSights and wep:GetIronSights() end, ['forceOnWSID'] = {210267782}, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'DOOM 3 SWEPs 1218893879', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_l4d") end, ['fnShouldHide'] = function(wep) return wep.GetIronSights and wep:GetIronSights() -- actually no ADS in this pack end, ['forceOnWSID'] = {1218893879}, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Quake 4 SWEPs 1341861055', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_quake4") end, ['fnShouldHide'] = function(wep) return false --wep:GetNWInt("Ironsights", 0) == 1 -- needs crosshair always visible end, ['forceOnWSID'] = {1341861055}, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD'), }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'ACT3: Arctic\'s Customizable Thirdperson (Weapons) 3.0 1631362949', ['fnIsValid'] = function(wep, cls) return wep.ACT3Weapon ~= nil and wep.State != nil and ACT3_STATE_INSIGHTS != nil end, ['fnShouldHide'] = function(wep) return wep.State == ACT3_STATE_INSIGHTS end, ['forceOnWSID'] = {1631362949}, -- detour manually handles in hide.lua }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Call of Duty 4: Modern Warfare 1510230490', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "weapon_cod4") and wep:GetNWBool("Crosshair", nil) ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetNWBool("Crosshair", true) == false end, ['onSwitch'] = HideWeaponCrosshairHUD('DrawHUD', function(wep) return wep:GetNWBool("Scope", false) == true end), ['forceOnWSID'] = {1606353301, 1506867125, 1601712255, 1598900546, 1537777119, 1510224023}, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = 'Hide with hands', ['fnIsValid'] = function(wep, cls) return table.HasValue({"weapon_hand", "climb_swep", "ohandsswep"}, cls) end, ['fnShouldHide'] = function(wep) return CrosshairDesigner.GetBool('HideWithHands') end, ['forceOnWSID'] = { 852703807, -- hands 113495466, -- climb swep 105225353, -- slappers 2778482614 -- outlast hands swep }, }) CrosshairDesigner.AddSWEPCrosshairCheck({ ['id'] = '[TacRP] Tactical RP Weapons 2588031232', ['fnIsValid'] = function(wep, cls) return hasPrefix(cls, "tacrp_") and wep.GetSightAmount ~= nil end, ['fnShouldHide'] = function(wep) return wep:GetSightAmount() > 0.5 end, ['forceOnWSID'] = {2588031232}, }) -- Disable Target Cross for Prop Hunt and Guess Who to stop cheating local gm = engine.ActiveGamemode() if gm == "prop_hunt" then CrosshairDesigner.DisableFeature("ColOnTarget", false, "Giving away positions in Prop Hunt!") elseif gm == "guesswho" then CrosshairDesigner.DisableFeature("ColOnTarget", false, "Giving away positions in Guess Who!") end -- Directory where everything is saved if not file.IsDir( "crosshair_designer", "DATA" ) then file.CreateDir( "crosshair_designer", "DATA" ) end if not file.IsDir( "crosshair_designer/debug", "DATA" ) then file.CreateDir( "crosshair_designer/debug", "DATA" ) end end