add sborka
This commit is contained in:
18
garrysmod/lua/autorun/binder_init.lua
Normal file
18
garrysmod/lua/autorun/binder_init.lua
Normal file
@@ -0,0 +1,18 @@
|
||||
-- Garry's Mod Binder - Entry Point
|
||||
AddCSLuaFile("binder/sh_lang.lua")
|
||||
AddCSLuaFile("binder/core/sh_config.lua")
|
||||
AddCSLuaFile("binder/cl_init.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_frame.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_profiles_tab.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_keybind_tab.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_radial_tab.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_radial_hud.lua")
|
||||
AddCSLuaFile("binder/vgui/cl_settings_tab.lua")
|
||||
|
||||
if SERVER then
|
||||
include("binder/sv_init.lua")
|
||||
print("[Binder] Server loaded")
|
||||
else
|
||||
include("binder/cl_init.lua")
|
||||
print("[Binder] Client loaded")
|
||||
end
|
||||
135
garrysmod/lua/autorun/realistic_police_helix_sync.lua
Normal file
135
garrysmod/lua/autorun/realistic_police_helix_sync.lua
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
-- Realistic Police Helix Sync
|
||||
-- Created to restrict computer to FSB and sync wanted status with Military ID
|
||||
|
||||
local FSB_PODR_ID = 6
|
||||
|
||||
Realistic_Police = Realistic_Police or {}
|
||||
|
||||
-- New helper to get the job name, respecting Helix units (podrazdeneniya)
|
||||
function Realistic_Police.GetPlayerJob(ply)
|
||||
if not IsValid(ply) then return "" end
|
||||
local char = ply.GetCharacter and ply:GetCharacter()
|
||||
if not char then return team.GetName(ply:Team()) end
|
||||
|
||||
-- In this server, if the faction is FACTION_RUSSIAN, we check the unit ID
|
||||
if char:GetFaction() == (FACTION_RUSSIAN or -1) then
|
||||
local podrId = char:GetPodr()
|
||||
if podrId == FSB_PODR_ID then
|
||||
return "ФСБ «Вымпел»" -- Note: must match config exactly (2 spaces)
|
||||
end
|
||||
end
|
||||
|
||||
return team.GetName(ply:Team())
|
||||
end
|
||||
|
||||
if SERVER then
|
||||
local plyMeta = FindMetaTable("Player")
|
||||
|
||||
-- Define wanted status for players in Helix
|
||||
function plyMeta:isWanted()
|
||||
local char = self:GetCharacter()
|
||||
if not char then return false end
|
||||
return char:GetData("wanted", false)
|
||||
end
|
||||
|
||||
function plyMeta:wanted(actor, reason, duration)
|
||||
local char = self:GetCharacter()
|
||||
if not char then return end
|
||||
char:SetData("wanted", true)
|
||||
char:SetData("wanted_reason", reason or "Розыск ФСБ")
|
||||
|
||||
-- Custom long-term wanted logic: we ignore duration and keep it until manual removal
|
||||
net.Start("RealisticPolice:Open")
|
||||
net.WriteString("Notify")
|
||||
net.WriteString(self:Name() .. " теперь находится в розыске!")
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
function plyMeta:unWanted(actor)
|
||||
local char = self:GetCharacter()
|
||||
if not char then return end
|
||||
char:SetData("wanted", false)
|
||||
char:SetData("wanted_reason", nil)
|
||||
|
||||
net.Start("RealisticPolice:Open")
|
||||
net.WriteString("Notify")
|
||||
net.WriteString(self:Name() .. " снят с розыска.")
|
||||
net.Broadcast()
|
||||
end
|
||||
|
||||
-- Hook into say commands if the police addon uses them
|
||||
hook.Add("PlayerSay", "RealisticPolice_WantedCommands", function(ply, text)
|
||||
local lowText = string.lower(text)
|
||||
local isWanted = string.StartWith(lowText, "/wanted") or string.StartWith(lowText, "!wanted")
|
||||
local isUnwanted = string.StartWith(lowText, "/unwanted") or string.StartWith(lowText, "!unwanted")
|
||||
|
||||
if isWanted or isUnwanted then
|
||||
-- Permission check
|
||||
local job = Realistic_Police.GetPlayerJob(ply)
|
||||
if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[ply:GetUserGroup()] then
|
||||
return
|
||||
end
|
||||
|
||||
-- Parse Name and Reason
|
||||
local cmd = isWanted and (string.StartWith(lowText, "/wanted") and "/wanted" or "!wanted") or (string.StartWith(lowText, "/unwanted") and "/unwanted" or "!unwanted")
|
||||
local fullParams = string.Trim(string.sub(text, #cmd + 1))
|
||||
|
||||
local target = nil
|
||||
local reason = ""
|
||||
|
||||
-- Find the player whose name matches the start of fullParams
|
||||
for _, v in pairs(player.GetAll()) do
|
||||
local name = v:Name()
|
||||
if string.StartWith(fullParams, name) then
|
||||
-- If multiple players match (e.g. "Ivan" and "Ivan Ivanov"), we want the longest match
|
||||
if not target or #name > #target:Name() then
|
||||
target = v
|
||||
reason = string.Trim(string.sub(fullParams, #name + 1))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if target then
|
||||
if isWanted then
|
||||
target:wanted(ply, reason)
|
||||
else
|
||||
target:unWanted(ply)
|
||||
end
|
||||
return "" -- Hide from chat
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Robust server-side check for opening the computer
|
||||
hook.Add("PlayerUse", "RealisticPolice_FSBRestrict", function(ply, ent)
|
||||
if IsValid(ent) and ent:GetClass() == "realistic_police_computer" then
|
||||
local job = Realistic_Police.GetPlayerJob(ply)
|
||||
if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[ply:GetUserGroup()] then
|
||||
if not (Realistic_Police.HackerJob and Realistic_Police.HackerJob[job]) then
|
||||
Realistic_Police.SendNotify(ply, "Доступ к компьютеру разрешен только подразделению ФСБ!")
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
-- Override the computer open check to restrict to FSB
|
||||
timer.Simple(1, function()
|
||||
if not Realistic_Police then return end
|
||||
|
||||
local oldOpenMenu = Realistic_Police.OpenMenu
|
||||
Realistic_Police.OpenMenu = function(ent, bool)
|
||||
local job = Realistic_Police.GetPlayerJob(LocalPlayer())
|
||||
if not Realistic_Police.OpenComputer[job] and not Realistic_Police.AdminRank[LocalPlayer():GetUserGroup()] then
|
||||
if not (Realistic_Police.HackerJob and Realistic_Police.HackerJob[job]) then
|
||||
RealisticPoliceNotify("Доступ запрещен. Только для подразделения ФСБ.")
|
||||
return
|
||||
end
|
||||
end
|
||||
oldOpenMenu(ent, bool)
|
||||
end
|
||||
end)
|
||||
end
|
||||
77
garrysmod/lua/autorun/server/sv_admin_physics_fix.lua
Normal file
77
garrysmod/lua/autorun/server/sv_admin_physics_fix.lua
Normal file
@@ -0,0 +1,77 @@
|
||||
if not SERVER then return end
|
||||
|
||||
-- Таблица групп, которым разрешён административный доступ к Physgun
|
||||
local AdminGroups = {
|
||||
["superadmin"] = true,
|
||||
["super admin"] = true,
|
||||
["owner"] = true,
|
||||
["curator"] = true,
|
||||
["sudo-curator"] = true,
|
||||
["asist-sudo"] = true,
|
||||
["admin"] = true,
|
||||
["st.admin"] = true,
|
||||
["projectteam"] = true,
|
||||
["teh.admin"] = true,
|
||||
["ivent"] = true,
|
||||
["st.event"] = true,
|
||||
["event"] = true,
|
||||
["specadmin"] = true,
|
||||
["assistant"] = true,
|
||||
["disp"] = true,
|
||||
}
|
||||
|
||||
-- Хук для Physgun: позволяем администраторам поднимать энтити и игроков
|
||||
hook.Add("PhysgunPickup", "!!!!Admin_PhysgunPickup_Fix", function(ply, ent)
|
||||
if not IsValid(ply) then return end
|
||||
|
||||
local group = ply:GetUserGroup()
|
||||
if AdminGroups[group] then
|
||||
-- Если это игрок, SAM уже имеет свою логику, но мы подтверждаем наше разрешение
|
||||
if ent:IsPlayer() then
|
||||
-- Запрещаем поднимать суперадминов если мы просто "админ" (опционально)
|
||||
-- if group == "admin" and ent:IsSuperAdmin() then return false end
|
||||
return true
|
||||
end
|
||||
|
||||
-- Для всех остальных энтитей — разрешаем
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Хук для ToolGun: позволяем администраторам использовать инструменты на защищённых объектах
|
||||
hook.Add("CanTool", "!!!!Admin_CanTool_Fix", function(ply, tr, tool)
|
||||
if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Хук для Properties (ПКМ меню): позволяем администраторам видеть всё
|
||||
hook.Add("CanProperty", "!!!!Admin_CanProperty_Fix", function(ply, property, ent)
|
||||
if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Хук для Unfreeze: позволяем администраторам размораживать объекты
|
||||
hook.Add("CanPlayerUnfreeze", "!!!!Admin_CanPlayerUnfreeze_Fix", function(ply, ent, phys)
|
||||
if IsValid(ply) and AdminGroups[ply:GetUserGroup()] then
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
-- Принудительная регистрация привилегий в CAMI для Helix и SAM
|
||||
hook.Add("InitPostEntity", "!!!!Admin_CAMI_Permissions_Fix", function()
|
||||
if CAMI then
|
||||
CAMI.RegisterPrivilege({
|
||||
Name = "Helix - Bypass Prop Protection",
|
||||
MinAccess = "admin"
|
||||
})
|
||||
|
||||
-- Для SAM
|
||||
if SAM_LOADED and sam then
|
||||
sam.permissions.add("can_physgun_players", nil, "admin")
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
print("[ADMIN FIX] Physics and Prop Protection bypass for 'admin' and 'curator' groups loaded.")
|
||||
5
garrysmod/lua/autorun/server/sv_disable_sprays.lua
Normal file
5
garrysmod/lua/autorun/server/sv_disable_sprays.lua
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Script to globally disable player sprays
|
||||
|
||||
hook.Add("PlayerSpray", "DisablePlayerSprays", function(ply)
|
||||
return true -- Returning true prevents the spray
|
||||
end)
|
||||
17
garrysmod/lua/autorun/server/sv_hostname_force.lua
Normal file
17
garrysmod/lua/autorun/server/sv_hostname_force.lua
Normal file
@@ -0,0 +1,17 @@
|
||||
local ForceHostname = "FT 4.0 | Война на Украине | ВАЙП"
|
||||
|
||||
local function EnforceHostname()
|
||||
if GetConVar("hostname"):GetString() ~= ForceHostname then
|
||||
RunConsoleCommand("hostname", ForceHostname)
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("Think", "ForceHostnameAggressive", function()
|
||||
EnforceHostname()
|
||||
end)
|
||||
|
||||
timer.Create("ForceHostnameTimer", 0.1, 0, function()
|
||||
EnforceHostname()
|
||||
end)
|
||||
|
||||
EnforceHostname()
|
||||
35
garrysmod/lua/autorun/server/sv_lvs_health_entry_fix.lua
Normal file
35
garrysmod/lua/autorun/server/sv_lvs_health_entry_fix.lua
Normal file
@@ -0,0 +1,35 @@
|
||||
if not SERVER then return end
|
||||
|
||||
-- MRP Fix: Disable entering LVS vehicles if health is below 0 or destroyed
|
||||
hook.Add( "LVS.CanPlayerDrive", "MRP_LVS_HealthFix", function( ply, vehicle )
|
||||
if not IsValid( vehicle ) then return end
|
||||
|
||||
-- Check if vehicle is destroyed or has negative health
|
||||
if vehicle.IsDestroyed and vehicle:IsDestroyed() then
|
||||
if not ply:IsAdmin() then
|
||||
ply:Notify("Техника уничтожена и не может управляться!")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
if vehicle.GetHP and vehicle:GetHP() <= 0 then
|
||||
if not ply:IsAdmin() then
|
||||
ply:Notify("Техника слишком сильно повреждена для эксплуатации!")
|
||||
return false
|
||||
end
|
||||
end
|
||||
end )
|
||||
|
||||
-- Prevents sitting in passenger seats of destroyed LVS vehicles
|
||||
hook.Add( "CanPlayerEnterVehicle", "MRP_LVS_SeatSafety", function( ply, vehicle )
|
||||
local lvsVehicle = vehicle.lvsGetVehicle and vehicle:lvsGetVehicle() or vehicle:GetParent()
|
||||
|
||||
if IsValid( lvsVehicle ) and lvsVehicle.LVS then
|
||||
if (lvsVehicle.IsDestroyed and lvsVehicle:IsDestroyed()) or (lvsVehicle.GetHP and lvsVehicle:GetHP() <= 0) then
|
||||
if not ply:IsAdmin() then
|
||||
ply:Notify("Техника уничтожена!")
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
end )
|
||||
65
garrysmod/lua/autorun/server/sv_lvs_missile_fix.lua
Normal file
65
garrysmod/lua/autorun/server/sv_lvs_missile_fix.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
-- Фикс краша IVP от lvs_missile вылетающих за пределы карты
|
||||
-- IVP Failed at ivu_vhash.cxx 157 — физдвижок падает когда ракета улетает за map boundary
|
||||
-- Решение: отслеживаем lvs_missile и удаляем их до краша при выходе за пределы
|
||||
|
||||
if not SERVER then return end
|
||||
|
||||
-- Получаем границы карты при старте
|
||||
local mapMin, mapMax
|
||||
|
||||
hook.Add("InitPostEntity", "MRP_LVSMissileSafetyInit", function()
|
||||
local wMin, wMax = game.GetWorld():GetModelBounds()
|
||||
|
||||
-- Fallback for weird maps
|
||||
if not wMin or wMin == vector_origin then
|
||||
wMin = Vector(-16384, -16384, -16384)
|
||||
wMax = Vector(16383, 16383, 16383)
|
||||
end
|
||||
|
||||
-- margin 1000 instead of 2000 to be safer on small maps
|
||||
local margin = Vector(1000, 1000, 1000)
|
||||
mapMin = wMin + margin
|
||||
mapMax = wMax - margin
|
||||
|
||||
print("[MRP] LVS Missile Safety initialized. Bounds: " .. tostring(mapMin) .. " -> " .. tostring(mapMax))
|
||||
end)
|
||||
|
||||
-- Таймер мониторинга, не хук Think (чтобы не перегружать каждый тик)
|
||||
timer.Create("MRP_LVSMissileWatchdog", 0.1, 0, function()
|
||||
if not mapMin then return end
|
||||
|
||||
for _, ent in ipairs(ents.FindByClass("lvs_missile")) do
|
||||
if not IsValid(ent) then continue end
|
||||
|
||||
-- ЗАЩИТА 1: Не удаляем ракеты которые еще не инициализированы через Enable()
|
||||
if not ent.IsEnabled or not ent.SpawnTime then
|
||||
continue
|
||||
end
|
||||
|
||||
local pos = ent:GetPos()
|
||||
local age = CurTime() - ent.SpawnTime
|
||||
|
||||
-- ЗАЩИТА 2: Не удаляем ракеты моложе 1 сек (время на полную инициализацию)
|
||||
if age < 1 then
|
||||
continue
|
||||
end
|
||||
|
||||
-- Проверка 1: вышла за границы карты
|
||||
local outOfBounds = pos.x < mapMin.x or pos.x > mapMax.x
|
||||
or pos.y < mapMin.y or pos.y > mapMax.y
|
||||
or pos.z < mapMin.z or pos.z > mapMax.z
|
||||
|
||||
-- Проверка 2: координаты явно безумные (> 15000 = за пределами любой карты GMod)
|
||||
local crazyOrigin = pos:Length() > 15000
|
||||
|
||||
if outOfBounds or crazyOrigin then
|
||||
-- Безопасное удаление через SafeRemoveEntityDelayed
|
||||
-- НЕ вызываем Detonate() — это создаст взрыв в рандомной точке
|
||||
if ent.IsDetonated then continue end
|
||||
ent.IsDetonated = true
|
||||
|
||||
print("[Debug] Watchdog: Removing OOB missile age=" .. string.format("%.2f", age) .. "s at " .. tostring(pos))
|
||||
SafeRemoveEntityDelayed(ent, 0)
|
||||
end
|
||||
end
|
||||
end)
|
||||
80
garrysmod/lua/autorun/server/sv_spawn_protection.lua
Normal file
80
garrysmod/lua/autorun/server/sv_spawn_protection.lua
Normal file
@@ -0,0 +1,80 @@
|
||||
-- Script to prevent players from spawning inside each other
|
||||
-- Especially useful for MilitaryRP/Helix where many players spawn simultaneously
|
||||
|
||||
local UNSTUCK_TIMEOUT = 5 -- How many seconds we consider for unstuck logic
|
||||
local UNSTUCK_SEARCH_RADIUS = 300 -- Max search radius
|
||||
|
||||
local function IsPosEmpty(pos, filter)
|
||||
local trace = util.TraceHull({
|
||||
start = pos + Vector(0,0,10),
|
||||
endpos = pos + Vector(0,0,10),
|
||||
mins = Vector(-16, -16, 0),
|
||||
maxs = Vector(16, 16, 71),
|
||||
filter = filter
|
||||
})
|
||||
|
||||
if trace.Hit then return false end
|
||||
|
||||
-- Also check for other players explicitly (TraceHull filter might skip them if they are within each other)
|
||||
local entsNear = ents.FindInBox(pos + Vector(-16, -16, 0), pos + Vector(16, 16, 72))
|
||||
for _, v in pairs(entsNear) do
|
||||
if v:IsPlayer() and v:Alive() and v ~= filter then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function FindSafePos(startPos, ply)
|
||||
if IsPosEmpty(startPos, ply) then return startPos end
|
||||
|
||||
-- Spiral search
|
||||
for r = 32, UNSTUCK_SEARCH_RADIUS, 32 do
|
||||
local steps = math.floor(r / 4)
|
||||
for i = 0, steps do
|
||||
local ang = (i / steps) * 360
|
||||
local offset = Angle(0, ang, 0):Forward() * r
|
||||
local tryPos = startPos + offset
|
||||
|
||||
if IsPosEmpty(tryPos, ply) then
|
||||
-- Ensure there is ground below
|
||||
local groundTrace = util.TraceLine({
|
||||
start = tryPos + Vector(0,0,50),
|
||||
endpos = tryPos - Vector(0,0,100),
|
||||
filter = ply
|
||||
})
|
||||
|
||||
if groundTrace.Hit then
|
||||
return groundTrace.HitPos
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return startPos
|
||||
end
|
||||
|
||||
hook.Add("PlayerSpawn", "Helix_AntiStuckSpawn", function(ply)
|
||||
-- Wait a frame for Helix to finish its set-spawn-point logic
|
||||
timer.Simple(0, function()
|
||||
if not IsValid(ply) or not ply:Alive() then return end
|
||||
|
||||
local currentPos = ply:GetPos()
|
||||
local safePos = FindSafePos(currentPos, ply)
|
||||
|
||||
if safePos ~= currentPos then
|
||||
ply:SetPos(safePos)
|
||||
end
|
||||
|
||||
-- Temporary NO COLLISION with other players for 3 seconds to avoid "jittery" physics if slightly overlapping
|
||||
ply:SetCollisionGroup(COLLISION_GROUP_DEBRIS_TRIGGER)
|
||||
timer.Create("Unstuck_CD_" .. ply:SteamID64(), 3, 1, function()
|
||||
if IsValid(ply) then
|
||||
ply:SetCollisionGroup(COLLISION_GROUP_PLAYER)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
print("[Anti-Stuck] Player spawn protection loaded")
|
||||
78
garrysmod/lua/autorun/server/sv_tacrp_att_fix.lua
Normal file
78
garrysmod/lua/autorun/server/sv_tacrp_att_fix.lua
Normal file
@@ -0,0 +1,78 @@
|
||||
-- Патч для tacrp_att: корректный подбор и возможность удаления суперадминами
|
||||
-- Проблема: если free_atts = true, GiveAtt блокирует подбор с сообщением
|
||||
-- "All attachments are free! This is not necessary!" и обвес не удаляется.
|
||||
|
||||
if not SERVER then return end
|
||||
|
||||
-- Таблица групп, имеющих админские права для поднятия сущностей
|
||||
local AdminPrivs = {
|
||||
["superadmin"] = true,
|
||||
["curator"] = true,
|
||||
["admin"] = true,
|
||||
["owner"] = true,
|
||||
["projectteam"] = true,
|
||||
["teh.admin"] = true,
|
||||
}
|
||||
|
||||
|
||||
-- Ждём загрузки всех энтитей перед патчем
|
||||
hook.Add("InitPostEntity", "MRP_PatchTacRPAtt", function()
|
||||
-- Идём по всем зарегистрированным энтитям и патчим те, что начинаются на tacrp_att
|
||||
for classname, meta in pairs(scripted_ents.GetList()) do
|
||||
if string.StartWith(classname, "tacrp_att") and meta.t then
|
||||
local ENT = meta.t
|
||||
|
||||
ENT.GiveAtt = function(self, ply)
|
||||
if not self.AttToGive then return end
|
||||
|
||||
-- Проверка lock_atts: если включён и обвес уже есть — не выдаём
|
||||
if TacRP and TacRP.ConVars and TacRP.ConVars["lock_atts"] and
|
||||
TacRP.ConVars["lock_atts"]:GetBool() and
|
||||
TacRP:PlayerGetAtts(ply, self.AttToGive) > 0 then
|
||||
ply:PrintMessage(HUD_PRINTTALK, "У вас уже есть этот обвес!")
|
||||
return
|
||||
end
|
||||
|
||||
-- Выдаём обвес
|
||||
if TacRP then
|
||||
local given = TacRP:PlayerGiveAtt(ply, self.AttToGive, 1)
|
||||
if given then
|
||||
TacRP:PlayerSendAttInv(ply)
|
||||
self:EmitSound("TacRP/weapons/flashlight_on.wav")
|
||||
self:Remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Патчим Use чтобы не падал если TacRP не загружен
|
||||
ENT.Use = function(self, ply)
|
||||
if not ply:IsPlayer() then return end
|
||||
self:GiveAtt(ply)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print("[MRP] tacrp_att patched: pickup fix applied to all derived attachments")
|
||||
end)
|
||||
|
||||
-- Хук для удаления tacrp_att через physgun для суперадминов
|
||||
hook.Add("PhysgunPickup", "MRP_AllowAttPickup", function(ply, ent)
|
||||
if string.StartWith(ent:GetClass(), "tacrp_att") then
|
||||
-- Разрешаем суперадминам поднимать и удалять обвесы
|
||||
if ply:IsSuperAdmin() or AdminPrivs and AdminPrivs[ply:GetUserGroup()] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Хук для удаления через ToolGun (суперадмин right-click)
|
||||
hook.Add("CanTool", "MRP_AllowAttRemove", function(ply, tr, tool)
|
||||
if tool == "remover" then
|
||||
local ent = tr.Entity
|
||||
if IsValid(ent) and string.StartWith(ent:GetClass(), "tacrp_att") then
|
||||
if ply:IsSuperAdmin() or AdminPrivs and AdminPrivs[ply:GetUserGroup()] then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
BIN
garrysmod/lua/bin/gmsv_chttp_linux64.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_chttp_linux64.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_chttp_win64.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_chttp_win64.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_dumptimers_fix_linux.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_dumptimers_fix_linux.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_enginespew_linux.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_enginespew_linux.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_enginespew_win32.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_enginespew_win32.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_fileio_linux.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_fileio_linux.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_fileio_win32.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_fileio_win32.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_mysqloo_linux.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_mysqloo_linux.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_mysqloo_linux64.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_mysqloo_linux64.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_mysqloo_win32.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_mysqloo_win32.dll
Normal file
Binary file not shown.
BIN
garrysmod/lua/bin/gmsv_mysqloo_win64.dll
Normal file
BIN
garrysmod/lua/bin/gmsv_mysqloo_win64.dll
Normal file
Binary file not shown.
107
garrysmod/lua/binder/cl_init.lua
Normal file
107
garrysmod/lua/binder/cl_init.lua
Normal file
@@ -0,0 +1,107 @@
|
||||
-- Garry's Mod Binder - Main Client Logic
|
||||
include("binder/sh_lang.lua")
|
||||
include("binder/core/sh_config.lua")
|
||||
include("binder/vgui/cl_frame.lua")
|
||||
include("binder/vgui/cl_profiles_tab.lua")
|
||||
include("binder/vgui/cl_keybind_tab.lua")
|
||||
include("binder/vgui/cl_radial_tab.lua")
|
||||
include("binder/vgui/cl_radial_hud.lua")
|
||||
include("binder/vgui/cl_settings_tab.lua")
|
||||
|
||||
Binder = Binder or {}
|
||||
Binder.Profiles = Binder.Profiles or {}
|
||||
Binder.F6Pressed = false
|
||||
|
||||
surface.CreateFont("Binder_Key", {
|
||||
font = "Roboto",
|
||||
size = 12,
|
||||
weight = 700,
|
||||
extended = true,
|
||||
})
|
||||
|
||||
CreateClientConVar("binder_show_feedback", "1", true, false)
|
||||
CreateClientConVar("binder_dark_theme", "1", true, false)
|
||||
CreateClientConVar("binder_radial_key", tostring(Binder.Config.DefaultRadialKey), true, false)
|
||||
|
||||
-- Networking
|
||||
net.Receive("Binder_SyncProfiles", function()
|
||||
local length = net.ReadUInt(32)
|
||||
local compressed = net.ReadData(length)
|
||||
local data = util.Decompress(compressed)
|
||||
|
||||
if data then
|
||||
Binder.Profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
|
||||
if IsValid(Binder.Frame) then
|
||||
Binder.Frame:RefreshActiveTab()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function Binder.SaveToServer()
|
||||
local data = util.TableToJSON(Binder.Profiles)
|
||||
local compressed = util.Compress(data)
|
||||
|
||||
net.Start("Binder_UpdateProfile")
|
||||
net.WriteUInt(#compressed, 32)
|
||||
net.WriteData(compressed, #compressed)
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
-- Input Hook for Binds (Client-side Think for Singleplayer support)
|
||||
local PrevBindState = {}
|
||||
|
||||
hook.Add("Think", "Binder_CheckBindsThink", function()
|
||||
local activeProfile
|
||||
for _, p in ipairs(Binder.Profiles or {}) do
|
||||
if p.Active then activeProfile = p break end
|
||||
end
|
||||
|
||||
if not activeProfile or not activeProfile.Binds then return end
|
||||
|
||||
-- Don't trigger new presses if in console, ESC menu, typing in chat, or focused on a VGUI text entry
|
||||
local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or LocalPlayer():IsTyping()
|
||||
|
||||
for key, commandStr in pairs(activeProfile.Binds) do
|
||||
local isDown = input.IsButtonDown(key)
|
||||
local wasDown = PrevBindState[key] or false
|
||||
|
||||
if isDown and not wasDown then
|
||||
if not blockNewInput then
|
||||
PrevBindState[key] = true
|
||||
if string.sub(commandStr, 1, 1) == "/" then
|
||||
LocalPlayer():ConCommand("say " .. commandStr)
|
||||
else
|
||||
LocalPlayer():ConCommand(commandStr)
|
||||
end
|
||||
end
|
||||
elseif not isDown and wasDown then
|
||||
PrevBindState[key] = false
|
||||
|
||||
-- Automatically release + commands
|
||||
local cmd = string.Explode(" ", commandStr)[1]
|
||||
if cmd and string.sub(cmd, 1, 1) == "+" then
|
||||
local minusCmd = "-" .. string.sub(cmd, 2)
|
||||
LocalPlayer():ConCommand(minusCmd)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
concommand.Add("binder_menu", function()
|
||||
if Binder.Frame and IsValid(Binder.Frame) then
|
||||
Binder.Frame:Remove()
|
||||
end
|
||||
|
||||
Binder.Frame = vgui.Create("Binder_Frame")
|
||||
end)
|
||||
|
||||
-- Open menu on F6
|
||||
hook.Add("Think", "Binder_F6Check", function()
|
||||
if input.IsKeyDown(KEY_F6) and not Binder.F6Pressed then
|
||||
Binder.F6Pressed = true
|
||||
RunConsoleCommand("binder_menu")
|
||||
elseif not input.IsKeyDown(KEY_F6) then
|
||||
Binder.F6Pressed = false
|
||||
end
|
||||
end)
|
||||
65
garrysmod/lua/binder/core/sh_config.lua
Normal file
65
garrysmod/lua/binder/core/sh_config.lua
Normal file
@@ -0,0 +1,65 @@
|
||||
-- Garry's Mod Binder - Shared Configuration
|
||||
Binder = Binder or {}
|
||||
Binder.Config = {
|
||||
AccentColor = Color(0, 67, 28), -- Dark green (#00431c)
|
||||
BgColor = Color(30, 30, 30, 240),
|
||||
HeaderColor = Color(0, 67, 28),
|
||||
TabActiveColor = Color(255, 255, 255, 20),
|
||||
TabHoverColor = Color(255, 255, 255, 10),
|
||||
Font = "Inter", -- Assuming Inter is available or we'll define it
|
||||
DefaultRadialKey = KEY_N,
|
||||
}
|
||||
|
||||
function Binder.SanitizeProfiles(profiles)
|
||||
for _, p in ipairs(profiles or {}) do
|
||||
if p.Binds then
|
||||
local newBinds = {}
|
||||
for k, v in pairs(p.Binds) do
|
||||
newBinds[tonumber(k) or k] = v
|
||||
end
|
||||
p.Binds = newBinds
|
||||
end
|
||||
if p.Radial then
|
||||
local newRadial = {}
|
||||
for k, v in pairs(p.Radial) do
|
||||
newRadial[tonumber(k) or k] = v
|
||||
end
|
||||
p.Radial = newRadial
|
||||
end
|
||||
end
|
||||
return profiles
|
||||
end
|
||||
|
||||
if CLIENT then
|
||||
surface.CreateFont("Binder_Title", {
|
||||
font = "Roboto",
|
||||
size = 24,
|
||||
weight = 800,
|
||||
extended = true,
|
||||
antialias = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Binder_Tab", {
|
||||
font = "Roboto",
|
||||
size = 18,
|
||||
weight = 600,
|
||||
extended = true,
|
||||
antialias = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Binder_Main", {
|
||||
font = "Roboto",
|
||||
size = 18,
|
||||
weight = 500,
|
||||
extended = true,
|
||||
antialias = true,
|
||||
})
|
||||
|
||||
surface.CreateFont("Binder_Small", {
|
||||
font = "Roboto",
|
||||
size = 14,
|
||||
weight = 500,
|
||||
extended = true,
|
||||
antialias = true,
|
||||
})
|
||||
end
|
||||
37
garrysmod/lua/binder/sh_lang.lua
Normal file
37
garrysmod/lua/binder/sh_lang.lua
Normal file
@@ -0,0 +1,37 @@
|
||||
-- Garry's Mod Binder - Shared Localization (Russian)
|
||||
Binder = Binder or {}
|
||||
Binder.Lang = {
|
||||
title = "[BindMenu]",
|
||||
profiles = "Профили",
|
||||
keybinds = "Клавиши",
|
||||
radial = "Радиальное меню",
|
||||
settings = "Настройки",
|
||||
profile_management = "Управление профилями",
|
||||
profile_desc = "Выберите профиль для редактирования или создайте новый. Ранги Premium и Loyalty открывают дополнительные слоты.",
|
||||
create_profile = "Создать профиль",
|
||||
raiding_profile = "Рейд Профиль",
|
||||
banker_profile = "Банкир Профиль",
|
||||
binds_count = "%s биндов",
|
||||
general_settings = "Общие настройки",
|
||||
show_feedback = "Показывать сообщения обратной связи",
|
||||
auto_scale = "Автоматическое масштабирование",
|
||||
dark_theme = "Темная тема",
|
||||
import_defaults = "Импорт стандартных биндов",
|
||||
import_btn = "Импортировать бинды",
|
||||
appearance_settings = "Настройки внешнего вида",
|
||||
accent_color = "Акцентный цвет:",
|
||||
change_color = "Изменить цвет",
|
||||
preset_colors = "Предустановленные цвета:",
|
||||
radial_config = "Настройка радиального меню",
|
||||
radial_desc = "Нажмите на сегмент ниже для настройки бинда",
|
||||
radial_center = "Радиальное",
|
||||
slot = "Слот %s",
|
||||
save = "Сохранить",
|
||||
delete = "Удалить",
|
||||
cancel = "Отмена",
|
||||
}
|
||||
|
||||
function Binder.GetPhrase(key, ...)
|
||||
local phrase = Binder.Lang[key] or key
|
||||
return string.format(phrase, ...)
|
||||
end
|
||||
71
garrysmod/lua/binder/sv_init.lua
Normal file
71
garrysmod/lua/binder/sv_init.lua
Normal file
@@ -0,0 +1,71 @@
|
||||
-- Garry's Mod Binder - Main Server Logic
|
||||
include("binder/sh_lang.lua")
|
||||
include("binder/core/sh_config.lua")
|
||||
|
||||
util.AddNetworkString("Binder_SyncProfiles")
|
||||
util.AddNetworkString("Binder_UpdateProfile")
|
||||
util.AddNetworkString("Binder_DeleteProfile")
|
||||
util.AddNetworkString("Binder_SetActiveProfile")
|
||||
|
||||
Binder = Binder or {}
|
||||
Binder.Profiles = Binder.Profiles or {}
|
||||
|
||||
-- Default structure
|
||||
-- Profile = { Name = "Profile1", Binds = { [KEY_X] = "cmd" }, Radial = { [1] = { Label = "L", Cmd = "cmd" } }, Active = true }
|
||||
|
||||
function Binder.SaveProfiles(ply)
|
||||
local data = util.TableToJSON(Binder.Profiles[ply:SteamID64()] or {})
|
||||
ply:SetPData("Binder_Profiles", data)
|
||||
end
|
||||
|
||||
function Binder.LoadProfiles(ply)
|
||||
local data = ply:GetPData("Binder_Profiles", "[]")
|
||||
local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
|
||||
|
||||
-- Ensure at least one default profile if empty
|
||||
if table.Count(profiles) == 0 then
|
||||
table.insert(profiles, {
|
||||
Name = Binder.GetPhrase("raiding_profile"),
|
||||
Binds = {},
|
||||
Radial = {},
|
||||
Active = true
|
||||
})
|
||||
end
|
||||
|
||||
Binder.Profiles[ply:SteamID64()] = profiles
|
||||
|
||||
-- Sync to client
|
||||
net.Start("Binder_SyncProfiles")
|
||||
local compressed = util.Compress(util.TableToJSON(profiles))
|
||||
net.WriteUInt(#compressed, 32)
|
||||
net.WriteData(compressed, #compressed)
|
||||
net.Send(ply)
|
||||
end
|
||||
|
||||
hook.Add("PlayerInitialSpawn", "Binder_LoadOnSpawn", function(ply)
|
||||
Binder.LoadProfiles(ply)
|
||||
end)
|
||||
|
||||
-- Receive entire profile updates from client (e.g., binds changed)
|
||||
net.Receive("Binder_UpdateProfile", function(len, ply)
|
||||
local length = net.ReadUInt(32)
|
||||
local compressed = net.ReadData(length)
|
||||
local data = util.Decompress(compressed)
|
||||
|
||||
if data then
|
||||
local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
|
||||
if profiles then
|
||||
Binder.Profiles[ply:SteamID64()] = profiles
|
||||
Binder.SaveProfiles(ply)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
-- Chat Command to open the Binder
|
||||
hook.Add("PlayerSay", "Binder_ChatCommand", function(ply, text, teamTalk)
|
||||
local lowerText = string.lower(text)
|
||||
if string.sub(lowerText, 1, 5) == "/bind" or string.sub(lowerText, 1, 5) == "!bind" then
|
||||
ply:ConCommand("binder_menu")
|
||||
return "" -- Hide the command from chat
|
||||
end
|
||||
end)
|
||||
138
garrysmod/lua/binder/vgui/cl_frame.lua
Normal file
138
garrysmod/lua/binder/vgui/cl_frame.lua
Normal file
@@ -0,0 +1,138 @@
|
||||
-- Garry's Mod Binder - Custom Frame
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:SetSize(ScrW() * 0.8, ScrH() * 0.8)
|
||||
self:Center()
|
||||
self:MakePopup()
|
||||
|
||||
self.AccentColor = Binder.Config.AccentColor
|
||||
|
||||
self.CurrentTab = "Profiles"
|
||||
|
||||
self.HeaderHeight = 60
|
||||
self.TabHeight = 40
|
||||
|
||||
-- Tabs definitions
|
||||
self.Tabs = {
|
||||
{id = "Profiles", name = Binder.GetPhrase("profiles")},
|
||||
{id = "Keybinds", name = Binder.GetPhrase("keybinds")},
|
||||
{id = "Radial", name = Binder.GetPhrase("radial")},
|
||||
{id = "Settings", name = Binder.GetPhrase("settings")},
|
||||
}
|
||||
|
||||
-- Close Button
|
||||
self.CloseBtn = vgui.Create("DButton", self)
|
||||
self.CloseBtn:SetText("✕")
|
||||
self.CloseBtn:SetFont("Binder_Main")
|
||||
self.CloseBtn:SetTextColor(Color(255, 255, 255, 150))
|
||||
self.CloseBtn.DoClick = function() self:Remove() end
|
||||
self.CloseBtn.Paint = function(s, w, h)
|
||||
if s:IsHovered() then
|
||||
s:SetTextColor(Color(255, 255, 255, 255))
|
||||
else
|
||||
s:SetTextColor(Color(255, 255, 255, 150))
|
||||
end
|
||||
end
|
||||
|
||||
-- Tab Content Container
|
||||
self.Content = vgui.Create("DPanel", self)
|
||||
self.Content:Dock(FILL)
|
||||
self.Content:DockMargin(0, self.HeaderHeight + self.TabHeight, 0, 0)
|
||||
self.Content.Paint = function(s, w, h) end
|
||||
|
||||
self:SwitchTab("Profiles")
|
||||
end
|
||||
|
||||
include("binder/vgui/cl_profiles_tab.lua")
|
||||
include("binder/vgui/cl_keybind_tab.lua")
|
||||
include("binder/vgui/cl_radial_tab.lua")
|
||||
include("binder/vgui/cl_settings_tab.lua")
|
||||
|
||||
function PANEL:SwitchTab(id)
|
||||
self.CurrentTab = id
|
||||
self.Content:Clear()
|
||||
|
||||
if id == "Profiles" then
|
||||
self.ActiveTab = vgui.Create("Binder_ProfilesTab", self.Content)
|
||||
elseif id == "Keybinds" then
|
||||
self.ActiveTab = vgui.Create("Binder_KeybindsTab", self.Content)
|
||||
elseif id == "Radial" then
|
||||
self.ActiveTab = vgui.Create("Binder_RadialTab", self.Content)
|
||||
elseif id == "Settings" then
|
||||
self.ActiveTab = vgui.Create("Binder_SettingsTab", self.Content)
|
||||
else
|
||||
local label = vgui.Create("DLabel", self.Content)
|
||||
|
||||
|
||||
label:SetText("Content for " .. id)
|
||||
label:SetFont("Binder_Title")
|
||||
label:SizeToContents()
|
||||
label:Center()
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:RefreshActiveTab()
|
||||
if IsValid(self.ActiveTab) and self.ActiveTab.Refresh then
|
||||
self.ActiveTab:Refresh()
|
||||
elseif self.CurrentTab == "Profiles" and IsValid(self.ActiveTab) then
|
||||
-- Fallback if the tab logic doesn't have a specific refresh method,
|
||||
-- though we should add :Refresh to all of them.
|
||||
self:SwitchTab(self.CurrentTab)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(w, h)
|
||||
if IsValid(self.CloseBtn) then
|
||||
self.CloseBtn:SetSize(30, 30)
|
||||
self.CloseBtn:SetPos(w - 40, 15)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint(w, h)
|
||||
-- Background with blur
|
||||
draw.RoundedBox(8, 0, 0, w, h, Color(30, 30, 30, 200))
|
||||
|
||||
-- Header
|
||||
draw.RoundedBoxEx(8, 0, 0, w, self.HeaderHeight, self.AccentColor, true, true, false, false)
|
||||
draw.SimpleText(Binder.GetPhrase("title"), "Binder_Title", 20, self.HeaderHeight / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Tabs Bar
|
||||
draw.RoundedBox(0, 0, self.HeaderHeight, w, self.TabHeight, Color(45, 45, 45))
|
||||
|
||||
local tabWidth = w / #self.Tabs
|
||||
for i, tab in ipairs(self.Tabs) do
|
||||
local x = (i - 1) * tabWidth
|
||||
local isActive = self.CurrentTab == tab.id
|
||||
|
||||
if isActive then
|
||||
draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 10))
|
||||
draw.RoundedBox(0, x + 20, self.HeaderHeight + self.TabHeight - 3, tabWidth - 40, 3, Color(255, 255, 255))
|
||||
end
|
||||
|
||||
local isHovered = gui.MouseX() >= self:GetPos() + x and gui.MouseX() < self:GetPos() + x + tabWidth and
|
||||
gui.MouseY() >= self:GetY() + self.HeaderHeight and gui.MouseY() < self:GetY() + self.HeaderHeight + self.TabHeight
|
||||
|
||||
if isHovered and not isActive then
|
||||
draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 5))
|
||||
end
|
||||
|
||||
draw.SimpleText(tab.name, "Binder_Tab", x + tabWidth / 2, self.HeaderHeight + self.TabHeight / 2,
|
||||
isActive and Color(255, 255, 255) or Color(150, 150, 150),
|
||||
TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
-- Hidden button for tab click
|
||||
if not self.TabButtons then self.TabButtons = {} end
|
||||
if not self.TabButtons[i] then
|
||||
local btn = vgui.Create("DButton", self)
|
||||
btn:SetText("")
|
||||
btn.Paint = function() end
|
||||
btn.DoClick = function() self:SwitchTab(tab.id) end
|
||||
self.TabButtons[i] = btn
|
||||
end
|
||||
self.TabButtons[i]:SetSize(tabWidth, self.TabHeight)
|
||||
self.TabButtons[i]:SetPos(x, self.HeaderHeight)
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("Binder_Frame", PANEL, "EditablePanel")
|
||||
204
garrysmod/lua/binder/vgui/cl_keybind_tab.lua
Normal file
204
garrysmod/lua/binder/vgui/cl_keybind_tab.lua
Normal file
@@ -0,0 +1,204 @@
|
||||
-- Garry's Mod Binder - Keybinds Tab (Virtual Keyboard)
|
||||
local PANEL = {}
|
||||
|
||||
local KEY_LAYOUT = {
|
||||
{"ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"},
|
||||
{"~", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "BKSP"},
|
||||
{"TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"},
|
||||
{"CAPS", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "ENTER"},
|
||||
{"SHIFT", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "SHIFT"},
|
||||
{"CTRL", "WIN", "ALT", "SPACE", "ALT", "WIN", "MENU"}
|
||||
}
|
||||
|
||||
local KEY_SPECIAL = {
|
||||
{"PRSC", "SCRLK", "PAUSE"},
|
||||
{"INS", "HOME", "PGUP"},
|
||||
{"DEL", "END", "PGDN"},
|
||||
{"↑"},
|
||||
{"←", "↓", "→"}
|
||||
}
|
||||
|
||||
local NUM_PAD = {
|
||||
{"NUM", "/", "*", "-"},
|
||||
{"7", "8", "9", "+"},
|
||||
{"4", "5", "6"},
|
||||
{"1", "2", "3", "ENT"},
|
||||
{"0", "."}
|
||||
}
|
||||
|
||||
local STRING_TO_KEY = {
|
||||
["ESC"] = KEY_ESCAPE, ["~"] = KEY_TILDE, ["-"] = KEY_MINUS, ["="] = KEY_EQUAL, ["BKSP"] = KEY_BACKSPACE, ["TAB"] = KEY_TAB,
|
||||
["["] = KEY_LBRACKET, ["]"] = KEY_RBRACKET, ["\\"] = KEY_BACKSLASH, ["CAPS"] = KEY_CAPSLOCK, [";"] = KEY_SEMICOLON,
|
||||
["'"] = KEY_APOSTROPHE, ["ENTER"] = KEY_ENTER, ["SHIFT"] = KEY_LSHIFT, [","] = KEY_COMMA, ["."] = KEY_PERIOD,
|
||||
["/"] = KEY_SLASH, ["CTRL"] = KEY_LCONTROL, ["WIN"] = KEY_LWIN, ["ALT"] = KEY_LALT, ["SPACE"] = KEY_SPACE,
|
||||
["MENU"] = KEY_APP,
|
||||
["PRSC"] = KEY_SYSRQ, ["SCRLK"] = KEY_SCROLLLOCK, ["PAUSE"] = KEY_BREAK, ["INS"] = KEY_INSERT, ["HOME"] = KEY_HOME,
|
||||
["PGUP"] = KEY_PAGEUP, ["DEL"] = KEY_DELETE, ["END"] = KEY_END, ["PGDN"] = KEY_PAGEDOWN,
|
||||
["↑"] = KEY_UP, ["←"] = KEY_LEFT, ["↓"] = KEY_DOWN, ["→"] = KEY_RIGHT,
|
||||
["NUM"] = KEY_NUMLOCK, ["/"] = KEY_PAD_DIVIDE, ["*"] = KEY_PAD_MULTIPLY, ["-"] = KEY_PAD_MINUS, ["+"] = KEY_PAD_PLUS,
|
||||
["ENT"] = KEY_PAD_ENTER, ["."] = KEY_PAD_DECIMAL,
|
||||
["1"] = KEY_1, ["2"] = KEY_2, ["3"] = KEY_3, ["4"] = KEY_4, ["5"] = KEY_5, ["6"] = KEY_6, ["7"] = KEY_7, ["8"] = KEY_8, ["9"] = KEY_9, ["0"] = KEY_0,
|
||||
}
|
||||
|
||||
-- Add letters and F-keys
|
||||
for i = 1, 26 do STRING_TO_KEY[string.char(64 + i)] = _G["KEY_" .. string.char(64 + i)] end
|
||||
for i = 1, 12 do STRING_TO_KEY["F" .. i] = _G["KEY_F" .. i] end
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:DockMargin(20, 20, 20, 20)
|
||||
self.Paint = function() end
|
||||
|
||||
self.InfoBar = vgui.Create("DPanel", self)
|
||||
self.InfoBar:Dock(TOP)
|
||||
self.InfoBar:SetHeight(40)
|
||||
self.InfoBar.Paint = function(s, w, h)
|
||||
draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255, 5))
|
||||
|
||||
local activeProf = self:GetActiveProfile()
|
||||
local name = activeProf and activeProf.Name or "No Active Profile"
|
||||
local bindCount = activeProf and table.Count(activeProf.Binds or {}) or 0
|
||||
|
||||
draw.SimpleText(name, "Binder_Main", 50, h / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w - 20, h / 2, Color(255, 255, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
self.KeyboardContainer = vgui.Create("DPanel", self)
|
||||
self.KeyboardContainer:Dock(FILL)
|
||||
self.KeyboardContainer:DockMargin(0, 40, 0, 0)
|
||||
self.KeyboardContainer.Paint = function() end
|
||||
|
||||
self.KeyButtons = {}
|
||||
self:BuildKeyboard()
|
||||
self:Refresh()
|
||||
end
|
||||
|
||||
function PANEL:GetActiveProfile()
|
||||
for _, p in ipairs(Binder.Profiles or {}) do
|
||||
if p.Active then return p end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function PANEL:Refresh()
|
||||
local profile = self:GetActiveProfile()
|
||||
local binds = profile and profile.Binds or {}
|
||||
|
||||
for _, btnData in ipairs(self.KeyButtons) do
|
||||
local isBound = binds[btnData.enum] != nil
|
||||
btnData.btn.IsActiveNode = isBound
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PromptBind(keyStr, keyEnum)
|
||||
local profile = self:GetActiveProfile()
|
||||
if not profile then return end
|
||||
|
||||
local currentCmd = profile.Binds[keyEnum] or ""
|
||||
|
||||
Derma_StringRequest(
|
||||
"Bind " .. keyStr,
|
||||
"Enter console command to execute on press (e.g., 'say /raid'):\nLeave blank to unbind.",
|
||||
currentCmd,
|
||||
function(text)
|
||||
if text == "" then
|
||||
profile.Binds[keyEnum] = nil
|
||||
else
|
||||
profile.Binds[keyEnum] = text
|
||||
end
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function PANEL:CreateKey(parent, text, x, y, w, h)
|
||||
local btn = vgui.Create("DButton", parent)
|
||||
btn:SetSize(w or 35, h or 35)
|
||||
btn:SetPos(x, y)
|
||||
btn:SetText("")
|
||||
btn.IsActiveNode = false
|
||||
|
||||
local keyEnum = STRING_TO_KEY[text]
|
||||
|
||||
btn.Paint = function(s, bw, bh)
|
||||
local bgColor = s.IsActiveNode and Binder.Config.AccentColor or Color(40, 40, 40)
|
||||
if s:IsHovered() then
|
||||
bgColor = Color(bgColor.r + 20, bgColor.g + 20, bgColor.b + 20)
|
||||
end
|
||||
draw.RoundedBox(4, 0, 0, bw, bh, bgColor)
|
||||
draw.SimpleText(text, "Binder_Key", bw / 2, bh / 2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function()
|
||||
if keyEnum then
|
||||
self:PromptBind(text, keyEnum)
|
||||
else
|
||||
print("Binder: Unmapped key - " .. text)
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(self.KeyButtons, {btn = btn, sysName = text, enum = keyEnum})
|
||||
end
|
||||
|
||||
function PANEL:BuildKeyboard()
|
||||
local startX, startY = 10, 10
|
||||
local keySize = 38
|
||||
local spacing = 4
|
||||
|
||||
-- Main Block
|
||||
for r, row in ipairs(KEY_LAYOUT) do
|
||||
local rx = startX
|
||||
local ry = startY + (r - 1) * (keySize + spacing)
|
||||
|
||||
-- Adjustments
|
||||
if r == 1 then ry = ry - 5 end
|
||||
|
||||
for k, key in ipairs(row) do
|
||||
local kw = keySize
|
||||
if key == "BKSP" then kw = keySize * 2 + spacing
|
||||
elseif key == "TAB" then kw = keySize * 1.5
|
||||
elseif key == "CAPS" then kw = keySize * 1.8
|
||||
elseif key == "ENTER" then kw = keySize * 1.8
|
||||
elseif key == "SHIFT" then kw = keySize * 2.3
|
||||
elseif key == "SPACE" then kw = keySize * 5 + spacing * 4
|
||||
elseif key == "CTRL" or key == "ALT" or key == "WIN" then kw = keySize * 1.2
|
||||
end
|
||||
|
||||
self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, keySize)
|
||||
rx = rx + kw + spacing
|
||||
end
|
||||
end
|
||||
|
||||
-- Special Keys Block
|
||||
local specX = startX + 15 * (keySize + spacing)
|
||||
for r, row in ipairs(KEY_SPECIAL) do
|
||||
local rx = specX
|
||||
local ry = startY + (r - 1) * (keySize + spacing)
|
||||
if r == 4 then rx = rx + keySize + spacing end
|
||||
|
||||
for k, key in ipairs(row) do
|
||||
self:CreateKey(self.KeyboardContainer, key, rx, ry, keySize, keySize)
|
||||
rx = rx + keySize + spacing
|
||||
end
|
||||
end
|
||||
|
||||
-- Num Pad
|
||||
local numX = specX + 4 * (keySize + spacing)
|
||||
for r, row in ipairs(NUM_PAD) do
|
||||
local rx = numX
|
||||
local ry = startY + (r - 1) * (keySize + spacing)
|
||||
for k, key in ipairs(row) do
|
||||
local kh = keySize
|
||||
if key == "+" or key == "ENT" then kh = keySize * 2 + spacing end
|
||||
|
||||
local kw = keySize
|
||||
if key == "0" then kw = keySize * 2 + spacing end
|
||||
|
||||
self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, kh)
|
||||
rx = rx + kw + spacing
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("Binder_KeybindsTab", PANEL, "EditablePanel")
|
||||
104
garrysmod/lua/binder/vgui/cl_profiles_tab.lua
Normal file
104
garrysmod/lua/binder/vgui/cl_profiles_tab.lua
Normal file
@@ -0,0 +1,104 @@
|
||||
-- Garry's Mod Binder - Profiles Tab
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:DockMargin(40, 20, 40, 20)
|
||||
self.Paint = function() end
|
||||
|
||||
-- Title and Description
|
||||
self.Header = vgui.Create("DPanel", self)
|
||||
self.Header:Dock(TOP)
|
||||
self.Header:SetHeight(50)
|
||||
self.Header.Paint = function(s, w, h)
|
||||
draw.SimpleText(Binder.GetPhrase("profile_management"), "Binder_Title", 0, 0, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
end
|
||||
|
||||
-- Profile Grid
|
||||
self.Grid = vgui.Create("DIconLayout", self)
|
||||
self.Grid:Dock(FILL)
|
||||
self.Grid:SetSpaceX(20)
|
||||
self.Grid:SetSpaceY(20)
|
||||
|
||||
self:Refresh()
|
||||
end
|
||||
|
||||
function PANEL:Refresh()
|
||||
self.Grid:Clear()
|
||||
|
||||
local profiles = Binder.Profiles or {}
|
||||
|
||||
for i, profile in ipairs(profiles) do
|
||||
local card = self.Grid:Add("DButton")
|
||||
card:SetSize(200, 150)
|
||||
card:SetText("")
|
||||
card.Paint = function(s, w, h)
|
||||
local bgColor = profile.Active and Binder.Config.AccentColor or Color(40, 40, 40)
|
||||
draw.RoundedBox(8, 0, 0, w, h, bgColor)
|
||||
|
||||
draw.SimpleText(profile.Name, "Binder_Main", w / 2, h / 2 - 10, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
local bindCount = table.Count(profile.Binds or {})
|
||||
draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w / 2, h / 2 + 15, Color(255, 255, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
if profile.Active then
|
||||
draw.RoundedBox(4, w - 15, 10, 8, 8, Color(0, 255, 0)) -- Active dot
|
||||
end
|
||||
end
|
||||
card.DoClick = function()
|
||||
-- Set active
|
||||
for _, p in ipairs(Binder.Profiles) do p.Active = false end
|
||||
profile.Active = true
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
end
|
||||
card.DoRightClick = function()
|
||||
-- Delete profile
|
||||
if #Binder.Profiles > 1 then
|
||||
local menu = DermaMenu()
|
||||
menu:AddOption(Binder.GetPhrase("delete"), function()
|
||||
table.remove(Binder.Profiles, i)
|
||||
-- ensure one is active
|
||||
local hasActive = false
|
||||
for _, p in ipairs(Binder.Profiles) do if p.Active then hasActive = true break end end
|
||||
if not hasActive and #Binder.Profiles > 0 then Binder.Profiles[1].Active = true end
|
||||
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
end):SetIcon("icon16/delete.png")
|
||||
menu:Open()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create New Profile Buttons (to fill up to 6 as in screenshot)
|
||||
if #profiles < 6 then
|
||||
local card = self.Grid:Add("DButton")
|
||||
card:SetSize(200, 150)
|
||||
card:SetText("")
|
||||
card.Paint = function(s, w, h)
|
||||
draw.RoundedBox(8, 0, 0, w, h, Color(40, 40, 40))
|
||||
draw.SimpleText("+", "Binder_Title", w / 2, h / 2 - 15, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
draw.SimpleText(Binder.GetPhrase("create_profile"), "Binder_Main", w / 2, h / 2 + 20, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
card.DoClick = function()
|
||||
Derma_StringRequest(
|
||||
Binder.GetPhrase("create_profile"),
|
||||
"Enter profile name:",
|
||||
"New Profile",
|
||||
function(text)
|
||||
table.insert(Binder.Profiles, {
|
||||
Name = text,
|
||||
Binds = {},
|
||||
Radial = {},
|
||||
Active = false
|
||||
})
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("Binder_ProfilesTab", PANEL, "EditablePanel")
|
||||
94
garrysmod/lua/binder/vgui/cl_radial_hud.lua
Normal file
94
garrysmod/lua/binder/vgui/cl_radial_hud.lua
Normal file
@@ -0,0 +1,94 @@
|
||||
-- Garry's Mod Binder - Radial HUD
|
||||
local IsRadialOpen = false
|
||||
local SelectedSlot = 0
|
||||
|
||||
local function GetActiveProfile()
|
||||
for _, p in ipairs(Binder.Profiles or {}) do
|
||||
if p.Active then return p end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
hook.Add("HUDPaint", "Binder_DrawRadial", function()
|
||||
if not IsRadialOpen then return end
|
||||
|
||||
local profile = GetActiveProfile()
|
||||
if not profile or not profile.Radial then return end
|
||||
|
||||
local cx, cy = ScrW() / 2, ScrH() / 2
|
||||
local radius = 150
|
||||
|
||||
-- Draw Center
|
||||
draw.NoTexture()
|
||||
surface.SetDrawColor(Color(30, 30, 30, 240))
|
||||
surface.DrawCircle(cx, cy, 50, 255, 255, 255, 255)
|
||||
draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Main", cx, cy, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
|
||||
local mouseX, mouseY = gui.MousePos()
|
||||
if mouseX == 0 and mouseY == 0 then
|
||||
mouseX, mouseY = cx, cy -- fallback if not capturing properly
|
||||
end
|
||||
|
||||
local dist = math.Dist(cx, cy, mouseX, mouseY)
|
||||
local angle = math.deg(math.atan2(mouseY - cy, mouseX - cx)) + 90
|
||||
if angle < 0 then angle = angle + 360 end
|
||||
|
||||
-- Determine selected segment based on angle (8 segments, 45 deg each)
|
||||
if dist > 50 then
|
||||
SelectedSlot = math.floor((angle + 22.5) / 45) + 1
|
||||
if SelectedSlot > 8 then SelectedSlot = 1 end
|
||||
else
|
||||
SelectedSlot = 0
|
||||
end
|
||||
|
||||
-- Draw segments
|
||||
for i = 1, 8 do
|
||||
local slotData = profile.Radial[i]
|
||||
if slotData then
|
||||
local segAngle = (i - 1) * 45 - 90
|
||||
local rad = math.rad(segAngle)
|
||||
local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius
|
||||
|
||||
local boxColor = (SelectedSlot == i) and Binder.Config.AccentColor or Color(40, 40, 45, 240)
|
||||
|
||||
draw.RoundedBox(8, lx - 40, ly - 20, 80, 40, boxColor)
|
||||
draw.SimpleText(slotData.Label, "Binder_Main", lx, ly, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw cursor line
|
||||
surface.SetDrawColor(255, 255, 255, 50)
|
||||
surface.DrawLine(cx, cy, mouseX, mouseY)
|
||||
end)
|
||||
|
||||
local PrevRadialState = false
|
||||
|
||||
hook.Add("Think", "Binder_RadialThink", function()
|
||||
local cvar = GetConVar("binder_radial_key")
|
||||
local radialKey = cvar and cvar:GetInt() or Binder.Config.DefaultRadialKey
|
||||
|
||||
local isDown = input.IsButtonDown(radialKey)
|
||||
local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or (LocalPlayer() and LocalPlayer():IsValid() and LocalPlayer():IsTyping())
|
||||
|
||||
if isDown and not PrevRadialState then
|
||||
if not blockNewInput then
|
||||
PrevRadialState = true
|
||||
IsRadialOpen = true
|
||||
gui.EnableScreenClicker(true)
|
||||
SelectedSlot = 0
|
||||
end
|
||||
elseif not isDown and PrevRadialState then
|
||||
PrevRadialState = false
|
||||
if IsRadialOpen then
|
||||
IsRadialOpen = false
|
||||
gui.EnableScreenClicker(false)
|
||||
|
||||
if SelectedSlot > 0 then
|
||||
local profile = GetActiveProfile()
|
||||
if profile and profile.Radial and profile.Radial[SelectedSlot] then
|
||||
LocalPlayer():ConCommand(profile.Radial[SelectedSlot].Command)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
150
garrysmod/lua/binder/vgui/cl_radial_tab.lua
Normal file
150
garrysmod/lua/binder/vgui/cl_radial_tab.lua
Normal file
@@ -0,0 +1,150 @@
|
||||
-- Garry's Mod Binder - Radial Menu Tab
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:DockMargin(40, 20, 40, 20)
|
||||
self.Paint = function() end
|
||||
|
||||
self.Header = vgui.Create("DPanel", self)
|
||||
self.Header:Dock(TOP)
|
||||
self.Header:SetHeight(80)
|
||||
self.Header.Paint = function(s, w, h)
|
||||
draw.SimpleText(Binder.GetPhrase("radial_config"), "Binder_Title", 0, 10, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
draw.SimpleText(Binder.GetPhrase("radial_desc"), "Binder_Small", 0, 40, Color(255, 255, 255, 100), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
|
||||
end
|
||||
|
||||
self.RadialContainer = vgui.Create("DPanel", self)
|
||||
self.RadialContainer:Dock(FILL)
|
||||
self.RadialContainer.Paint = function(s, w, h)
|
||||
local cx, cy = w / 2, h / 2
|
||||
|
||||
draw.NoTexture()
|
||||
surface.SetDrawColor(Color(255, 255, 255, 10))
|
||||
surface.DrawCircle(cx, cy, 40, 255, 255, 255, 255)
|
||||
draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Small", cx, cy, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
self.Segments = {}
|
||||
self:BuildSegments()
|
||||
self:Refresh()
|
||||
end
|
||||
|
||||
function PANEL:GetActiveProfile()
|
||||
for _, p in ipairs(Binder.Profiles or {}) do
|
||||
if p.Active then return p end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function PANEL:BuildSegments()
|
||||
local cx, cy = 0, 0 -- Will be updated in PerformLayout
|
||||
local radius = 120
|
||||
|
||||
for i = 1, 8 do
|
||||
local btn = vgui.Create("DButton", self.RadialContainer)
|
||||
btn:SetSize(80, 80)
|
||||
btn:SetText("")
|
||||
btn.Slot = i
|
||||
btn.Paint = function(s, w, h)
|
||||
local bgColor = s:IsHovered() and Binder.Config.AccentColor or Color(40, 40, 45)
|
||||
draw.RoundedBox(20, 0, 0, w, h, bgColor)
|
||||
|
||||
local activeProf = self:GetActiveProfile()
|
||||
local text = Binder.GetPhrase("slot", i)
|
||||
if activeProf and activeProf.Radial and activeProf.Radial[i] then
|
||||
text = activeProf.Radial[i].Label
|
||||
end
|
||||
|
||||
draw.SimpleText(text, "Binder_Small", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
|
||||
end
|
||||
|
||||
btn.DoClick = function(s)
|
||||
self:PromptSegment(s.Slot)
|
||||
end
|
||||
|
||||
table.insert(self.Segments, btn)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Refresh()
|
||||
-- Nothing inherently needed here unless we drastically change profile
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout(w, h)
|
||||
local cx, cy = w / 2, (h - 80) / 2
|
||||
local radius = 120
|
||||
|
||||
for i, btn in ipairs(self.Segments) do
|
||||
local angle = (i - 1) * (360 / 8) - 90 -- Start at top
|
||||
local rad = math.rad(angle)
|
||||
local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius
|
||||
|
||||
btn:SetPos(lx - 40, ly - 40)
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:PromptSegment(slot)
|
||||
local profile = self:GetActiveProfile()
|
||||
if not profile then return end
|
||||
|
||||
profile.Radial = profile.Radial or {}
|
||||
local currentLabel = profile.Radial[slot] and profile.Radial[slot].Label or ""
|
||||
local currentCmd = profile.Radial[slot] and profile.Radial[slot].Command or ""
|
||||
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetTitle("Configure Radial Slot " .. slot)
|
||||
frame:SetSize(300, 150)
|
||||
frame:Center()
|
||||
frame:MakePopup()
|
||||
|
||||
local lbl1 = vgui.Create("DLabel", frame)
|
||||
lbl1:SetPos(10, 30)
|
||||
lbl1:SetText("Label (Text):")
|
||||
lbl1:SizeToContents()
|
||||
|
||||
local txtLabel = vgui.Create("DTextEntry", frame)
|
||||
txtLabel:SetPos(10, 50)
|
||||
txtLabel:SetSize(280, 20)
|
||||
txtLabel:SetText(currentLabel)
|
||||
|
||||
local lbl2 = vgui.Create("DLabel", frame)
|
||||
lbl2:SetPos(10, 80)
|
||||
lbl2:SetText("Console Command:")
|
||||
lbl2:SizeToContents()
|
||||
|
||||
local txtCmd = vgui.Create("DTextEntry", frame)
|
||||
txtCmd:SetPos(10, 100)
|
||||
txtCmd:SetSize(280, 20)
|
||||
txtCmd:SetText(currentCmd)
|
||||
|
||||
local saveBtn = vgui.Create("DButton", frame)
|
||||
saveBtn:SetPos(10, 125)
|
||||
saveBtn:SetSize(135, 20)
|
||||
saveBtn:SetText("Save")
|
||||
saveBtn.DoClick = function()
|
||||
local l = txtLabel:GetValue()
|
||||
local c = txtCmd:GetValue()
|
||||
if l == "" or c == "" then
|
||||
profile.Radial[slot] = nil
|
||||
else
|
||||
profile.Radial[slot] = { Label = l, Command = c }
|
||||
end
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
frame:Remove()
|
||||
end
|
||||
|
||||
local clearBtn = vgui.Create("DButton", frame)
|
||||
clearBtn:SetPos(155, 125)
|
||||
clearBtn:SetSize(135, 20)
|
||||
clearBtn:SetText("Clear Slot")
|
||||
clearBtn.DoClick = function()
|
||||
profile.Radial[slot] = nil
|
||||
Binder.SaveToServer()
|
||||
self:Refresh()
|
||||
frame:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("Binder_RadialTab", PANEL, "EditablePanel")
|
||||
182
garrysmod/lua/binder/vgui/cl_settings_tab.lua
Normal file
182
garrysmod/lua/binder/vgui/cl_settings_tab.lua
Normal file
@@ -0,0 +1,182 @@
|
||||
-- Garry's Mod Binder - Settings Tab
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self:Dock(FILL)
|
||||
self:DockMargin(40, 20, 40, 20)
|
||||
self.Paint = function() end
|
||||
|
||||
self.Scroll = vgui.Create("DScrollPanel", self)
|
||||
self.Scroll:Dock(FILL)
|
||||
|
||||
local sbar = self.Scroll:GetVBar()
|
||||
sbar:SetWide(6)
|
||||
sbar:SetHideButtons(true)
|
||||
sbar.Paint = function(s, w, h) end
|
||||
sbar.btnGrip.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30))
|
||||
end
|
||||
|
||||
self:AddCategory(Binder.GetPhrase("general_settings"), {
|
||||
{type = "check", label = Binder.GetPhrase("show_feedback"), convar = "binder_show_feedback"},
|
||||
{type = "check", label = Binder.GetPhrase("dark_theme"), convar = "binder_dark_theme"},
|
||||
})
|
||||
|
||||
self:AddCategory("Radial Menu Key", {
|
||||
{type = "keybind", label = "Activation Key", convar = "binder_radial_key"},
|
||||
})
|
||||
|
||||
self:AddCategory(Binder.GetPhrase("import_defaults"), {
|
||||
{type = "button", label = Binder.GetPhrase("import_btn"), btnText = Binder.GetPhrase("import_btn"),
|
||||
onClick = function()
|
||||
Binder.Profiles = {}
|
||||
table.insert(Binder.Profiles, {Name = Binder.GetPhrase("raiding_profile"), Binds = {}, Radial = {}, Active = true})
|
||||
Binder.SaveToServer()
|
||||
if Binder.Frame and IsValid(Binder.Frame) then Binder.Frame:RefreshActiveTab() end
|
||||
end},
|
||||
})
|
||||
|
||||
self:AddCategory(Binder.GetPhrase("appearance_settings"), {
|
||||
{type = "color", label = Binder.GetPhrase("accent_color")},
|
||||
{type = "presets", label = Binder.GetPhrase("preset_colors")},
|
||||
})
|
||||
end
|
||||
|
||||
function PANEL:AddCategory(title, items)
|
||||
local cat = self.Scroll:Add("DPanel")
|
||||
cat:Dock(TOP)
|
||||
cat:DockMargin(0, 0, 0, 20)
|
||||
cat:SetHeight(40 + #items * 40)
|
||||
cat.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, 30, Binder.Config.AccentColor)
|
||||
draw.SimpleText(title, "Binder_Main", 10, 15, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
|
||||
draw.RoundedBoxEx(4, 0, 30, w, h - 30, Color(40, 40, 40), false, false, true, true)
|
||||
end
|
||||
|
||||
local itemContainer = vgui.Create("DPanel", cat)
|
||||
itemContainer:Dock(FILL)
|
||||
itemContainer:DockMargin(10, 35, 10, 5)
|
||||
itemContainer.Paint = function() end
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local row = vgui.Create("DPanel", itemContainer)
|
||||
row:Dock(TOP)
|
||||
row:SetHeight(35)
|
||||
row.Paint = function() end
|
||||
|
||||
if item.type == "check" and item.convar then
|
||||
local chk = vgui.Create("DCheckBox", row)
|
||||
chk:SetPos(0, 10)
|
||||
chk:SetConVar(item.convar)
|
||||
|
||||
local lbl = vgui.Create("DLabel", row)
|
||||
lbl:SetText(item.label)
|
||||
lbl:SetFont("Binder_Small")
|
||||
lbl:SetPos(25, 8)
|
||||
lbl:SizeToContents()
|
||||
|
||||
elseif item.type == "keybind" and item.convar then
|
||||
local lbl = vgui.Create("DLabel", row)
|
||||
lbl:SetText(item.label)
|
||||
lbl:SetFont("Binder_Small")
|
||||
lbl:SetPos(0, 8)
|
||||
lbl:SizeToContents()
|
||||
|
||||
local binderBtn = vgui.Create("DBinder", row)
|
||||
binderBtn:SetSize(120, 20)
|
||||
binderBtn:SetPos(150, 8)
|
||||
binderBtn:SetConVar(item.convar)
|
||||
binderBtn.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60))
|
||||
draw.SimpleText(input.GetKeyName(s:GetValue()) or "NONE", "Binder_Small", w/2, h/2, Color(255,255,255), 1, 1)
|
||||
end
|
||||
|
||||
elseif item.type == "button" then
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetText(item.btnText)
|
||||
btn:SetFont("Binder_Small")
|
||||
btn:SetSize(150, 25)
|
||||
btn:SetPos(0, 5)
|
||||
btn:SetTextColor(Color(255, 255, 255))
|
||||
btn.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor)
|
||||
end
|
||||
if item.onClick then btn.DoClick = item.onClick end
|
||||
|
||||
elseif item.type == "color" then
|
||||
local lbl = vgui.Create("DLabel", row)
|
||||
lbl:SetText(item.label)
|
||||
lbl:SetFont("Binder_Small")
|
||||
lbl:SetPos(0, 8)
|
||||
lbl:SizeToContents()
|
||||
|
||||
local colPreview = vgui.Create("DPanel", row)
|
||||
colPreview:SetSize(30, 20)
|
||||
colPreview:SetPos(150, 8)
|
||||
colPreview.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor)
|
||||
end
|
||||
|
||||
local btn = vgui.Create("DButton", row)
|
||||
btn:SetText(Binder.GetPhrase("change_color"))
|
||||
btn:SetSize(100, 20)
|
||||
btn:SetPos(190, 8)
|
||||
btn:SetFont("Binder_Small")
|
||||
btn:SetTextColor(Color(255, 255, 255))
|
||||
btn.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60))
|
||||
end
|
||||
btn.DoClick = function()
|
||||
local frame = vgui.Create("DFrame")
|
||||
frame:SetTitle("Select Theme Color")
|
||||
frame:SetSize(300, 250)
|
||||
frame:Center()
|
||||
frame:MakePopup()
|
||||
|
||||
local mixer = vgui.Create("DColorMixer", frame)
|
||||
mixer:Dock(FILL)
|
||||
mixer:SetPalette(true)
|
||||
mixer:SetAlphaBar(false)
|
||||
mixer:SetWangs(true)
|
||||
mixer:SetColor(Binder.Config.AccentColor)
|
||||
|
||||
local apply = vgui.Create("DButton", frame)
|
||||
apply:Dock(BOTTOM)
|
||||
apply:SetText("Apply")
|
||||
apply.DoClick = function()
|
||||
Binder.Config.AccentColor = mixer:GetColor()
|
||||
if IsValid(Binder.Frame) then Binder.Frame.AccentColor = Binder.Config.AccentColor end
|
||||
frame:Remove()
|
||||
end
|
||||
end
|
||||
|
||||
elseif item.type == "presets" then
|
||||
local lbl = vgui.Create("DLabel", row)
|
||||
lbl:SetText(item.label)
|
||||
lbl:SetFont("Binder_Small")
|
||||
lbl:SetPos(0, 8)
|
||||
lbl:SizeToContents()
|
||||
|
||||
local presets = {Color(0, 67, 28), Color(52, 152, 219), Color(46, 204, 113), Color(231, 76, 60), Color(155, 89, 182), Color(230, 126, 34)}
|
||||
for i, col in ipairs(presets) do
|
||||
local p = vgui.Create("DButton", row)
|
||||
p:SetSize(20, 20)
|
||||
p:SetPos(150 + (i-1)*25, 8)
|
||||
p:SetText("")
|
||||
p.Paint = function(s, w, h)
|
||||
draw.RoundedBox(4, 0, 0, w, h, col)
|
||||
if Binder.Config.AccentColor == col then
|
||||
surface.SetDrawColor(255, 255, 255)
|
||||
surface.DrawOutlinedRect(0, 0, w, h, 2)
|
||||
end
|
||||
end
|
||||
p.DoClick = function()
|
||||
Binder.Config.AccentColor = col
|
||||
if IsValid(Binder.Frame) then Binder.Frame.AccentColor = col end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register("Binder_SettingsTab", PANEL, "EditablePanel")
|
||||
Reference in New Issue
Block a user