if SERVER then -- Send file to clients AddCSLuaF...
Creado el: 8 de febrero de 2025
Creado el: 8 de febrero de 2025
if SERVER then
-- Send file to clients
AddCSLuaFile()
text-------------------------------- -- Global (local) variables for the orders system local commandMarkers = {} local UpdateClients -------------------------------- -- GROUP SYSTEM (SERVER SIDE) -------------------------------- -- Table of groups: key – leader, value – group data local groups = {} -- Function to get the group a player belongs to local function GetPlayerGroup(ply) for leader, group in pairs(groups) do if group.members[ply] then return group, leader end end return nil, nil end -- Network strings for groups util.AddNetworkString("Group_Create") util.AddNetworkString("Group_Invite") util.AddNetworkString("Group_InviteNotify") util.AddNetworkString("Group_AcceptInvite") util.AddNetworkString("Group_DeclineInvite") util.AddNetworkString("Group_Update") util.AddNetworkString("Group_Kick") util.AddNetworkString("Group_Leave") util.AddNetworkString("DeleteCommand") util.AddNetworkString("OpenCommandMenu") util.AddNetworkString("ExecuteCommand") util.AddNetworkString("UpdateMarkers") util.AddNetworkString("Group_RequestInviteList") util.AddNetworkString("Group_SendInviteList") -- Client requests to create a group net.Receive("Group_Create", function(len, ply) local existingGroup, _ = GetPlayerGroup(ply) if existingGroup then return end local groupData = { leader = ply, members = {} } groupData.members[ply] = true groups[ply] = groupData net.Start("Group_Update") net.WriteTable({ leader = ply:SteamID(), members = { ply:SteamID() } }) net.Send(ply) end) -- Client invites targetPlayer to his group net.Receive("Group_Invite", function(len, ply) local targetSteamID = net.ReadString() local target = player.GetBySteamID(targetSteamID) if not IsValid(target) then return end local group, _ = GetPlayerGroup(ply) if not group then return end -- Only the leader can invite if group.leader ~= ply then return end -- If target is already in a group, ignore if GetPlayerGroup(target) then return end net.Start("Group_InviteNotify") net.WriteEntity(ply) net.Send(target) end) -- Client accepted an invitation net.Receive("Group_AcceptInvite", function(len, ply) local inviter = net.ReadEntity() if not IsValid(inviter) then return end local group, _ = GetPlayerGroup(inviter) if not group then return end -- If the player is already in a group, do nothing if GetPlayerGroup(ply) then return end group.members[ply] = true local memberSteamIDs = {} for member in pairs(group.members) do table.insert(memberSteamIDs, member:SteamID()) end net.Start("Group_Update") net.WriteTable({ leader = group.leader:SteamID(), members = memberSteamIDs }) net.Send(table.GetKeys(group.members)) end) -- Client declined the invitation net.Receive("Group_DeclineInvite", function(len, ply) -- Read the inviter entity local inviter = net.ReadEntity() if IsValid(inviter) then inviter:ChatPrint(ply:Nick() .. " has declined your invitation") end -- Additional logic if needed end) -- Group leader kicks a member net.Receive("Group_Kick", function(len, ply) local targetSteamID = net.ReadString() local target = player.GetBySteamID(targetSteamID) if not IsValid(target) then return end local group, _ = GetPlayerGroup(ply) if not group then return end if group.leader ~= ply then return end if group.members[target] then group.members[target] = nil -- Notify the kicked player – his group is now empty net.Start("Group_Update") net.WriteTable({ leader = nil, members = {} }) net.Send(target) -- Update the remaining group members local memberSteamIDs = {} for member in pairs(group.members) do table.insert(memberSteamIDs, member:SteamID()) end net.Start("Group_Update") net.WriteTable({ leader = group.leader:SteamID(), members = memberSteamIDs }) net.Send(table.GetKeys(group.members)) end end) -- Leaving a group (if leader – disband) net.Receive("Group_Leave", function(len, ply) local group, _ = GetPlayerGroup(ply) if not group then return end if ply == group.leader then local allMembers = table.GetKeys(group.members) groups[group.leader] = nil net.Start("Group_Update") net.WriteTable({ leader = nil, members = {} }) net.Send(allMembers) else group.members[ply] = nil local memberSteamIDs = {} for member in pairs(group.members) do table.insert(memberSteamIDs, member:SteamID()) end net.Start("Group_Update") net.WriteTable({ leader = group.leader:SteamID(), members = memberSteamIDs }) net.Send(table.GetKeys(group.members)) net.Start("Group_Update") net.WriteTable({ leader = nil, members = {} }) net.Send(ply) end end) -- Delete an order (command for deleting orders) net.Receive("DeleteCommand", function(len, ply) local created = net.ReadFloat() -- use the creation time as a unique identifier if not created or created == 0 then return end local removed = false for i = #commandMarkers, 1, -1 do local m = commandMarkers[i] if m.owner == ply:SteamID() and math.abs(m.created - created) < 0.001 then table.remove(commandMarkers, i) removed = true end end if removed then UpdateClients() end end) -- When a player disconnects, update or disband the group hook.Add("PlayerDisconnected", "GroupPlayerDisconnect", function(ply) local group, _ = GetPlayerGroup(ply) if not group then return end if ply == group.leader then local allMembers = table.GetKeys(group.members) groups[ply] = nil net.Start("Group_Update") net.WriteTable({ leader = nil, members = {} }) net.Send(allMembers) else group.members[ply] = nil local memberSteamIDs = {} for member in pairs(group.members) do table.insert(memberSteamIDs, member:SteamID()) end net.Start("Group_Update") net.WriteTable({ leader = group.leader:SteamID(), members = memberSteamIDs }) net.Send(table.GetKeys(group.members)) end end) -- Handler for the request of a list of players available for invitation net.Receive("Group_RequestInviteList", function(len, ply) local availablePlayers = {} -- Check each player for _, target in ipairs(player.GetAll()) do if target ~= ply and not GetPlayerGroup(target) then table.insert(availablePlayers, { nick = target:Nick(), steamid = target:SteamID() }) end end -- Send the list to the client net.Start("Group_SendInviteList") net.WriteTable(availablePlayers) net.Send(ply) end) -------------------------------- -- ORDERS SYSTEM (SERVER SIDE) -------------------------------- -- Function to update markers for clients UpdateClients = function() for _, ply in ipairs(player.GetAll()) do if not IsValid(ply) then continue end local relevantMarkers = {} local plySteamID = ply:SteamID() for _, marker in ipairs(commandMarkers) do if marker.owner == plySteamID or marker.receiver == plySteamID then table.insert(relevantMarkers, marker) end end net.Start("UpdateMarkers") net.WriteTable(relevantMarkers) net.Send(ply) end end -- Table of command handlers local commandHandlers = { [1] = function(ply, pos, ent) return { type = "move", pos = pos, text = "Moving" } end, [2] = function(ply, pos, ent) if IsValid(ent) then return { type = "take", pos = ent:GetPos(), text = "Object", entIndex = ent:EntIndex() } end end, [3] = function(ply, pos, ent) return { type = "defend", pos = ply:GetPos(), text = "Protection", target = ply:EntIndex() } end, [4] = function(ply, pos, ent) if IsValid(ent) and ent:IsVehicle() then return { type = "sit", pos = ent:GetPos(), text = "Vehicle", entIndex = ent:EntIndex() } end end, [5] = function(ply, pos, ent) if IsValid(ent) and (ent:IsNPC() or ent:IsPlayer()) then return { type = "attack", pos = ent:GetPos(), text = "Attack Target", target = ent:EntIndex() } end end, } local executedCommands = {} -- Receive a command from the client and create a marker
net.Receive("ExecuteCommand", function(len, ply)
local cmdId = net.ReadUInt(4)
local pos = net.ReadVector()
local ent = net.ReadEntity()
local receiver = net.ReadEntity()
textif not IsValid(receiver) then local group, _ = GetPlayerGroup(ply) if not group then return end for member in pairs(group.members) do if IsValid(member) then local markerData = commandHandlers[cmdId](ply, pos, ent) if markerData then if executedCommands[markerData.created] then return end markerData.owner = ply:SteamID() markerData.receiver = member:SteamID() markerData.timeout = CurTime() + 600 markerData.created = CurTime() table.insert(commandMarkers, markerData) executedCommands[markerData.created] = true end end end UpdateClients() else local markerData = commandHandlers[cmdId](ply, pos, ent) if markerData then if executedCommands[markerData.created] then return end markerData.owner = ply:SteamID() markerData.receiver = receiver:SteamID() markerData.timeout = CurTime() + 600 markerData.created = CurTime() table.insert(commandMarkers, markerData) executedCommands[markerData.created] = true UpdateClients() end end
end)
text-- Timer to clean up expired markers and update positions timer.Create("CleanupMarkers", 1, 0, function() local changed = false for i = #commandMarkers, 1, -1 do local m = commandMarkers[i] -- (1) For "defend" and "attack" markers bound to a target if (m.type == "defend" or m.type == "attack") and m.target then local ent = Entity(m.target) local shouldRemove = false if IsValid(ent) then if ent:IsPlayer() then shouldRemove = not ent:Alive() elseif ent:IsNPC() then shouldRemove = ent:Health() <= 0 end if not shouldRemove then local newPos = ent:GetPos() if m.pos ~= newPos then m.pos = newPos changed = true end end else shouldRemove = true end if shouldRemove then table.remove(commandMarkers, i) changed = true continue end end -- (2) If the marker is bound to an object (take, sit) – update its position if m.entIndex then local ent = Entity(m.entIndex) if IsValid(ent) then local newPos = ent:GetPos() if m.pos ~= newPos then m.pos = newPos changed = true end else table.remove(commandMarkers, i) changed = true continue end end -- (3) Remove expired markers if m.timeout < CurTime() then table.remove(commandMarkers, i) changed = true continue end -- (4) Additional conditions: -- For "move": if the player is already close to the target local receiverPly = player.GetBySteamID(m.receiver or "") if m.type == "move" and IsValid(receiverPly) then local dist = receiverPly:GetPos():Distance(m.pos) if dist <= 250 then table.remove(commandMarkers, i) changed = true continue end end -- For "take": if the object (for example, a weapon) has already been picked up if m.type == "take" and m.entIndex then local ent = Entity(m.entIndex) if IsValid(ent) and ent:IsWeapon() then if IsValid(ent:GetOwner()) then table.remove(commandMarkers, i) changed = true continue end end end -- For "sit": if the player has entered a vehicle if m.type == "sit" and m.entIndex then local veh = Entity(m.entIndex) local receiverPly = player.GetBySteamID(m.receiver or "") if IsValid(receiverPly) and IsValid(veh) then if receiverPly:InVehicle() then local currentVehicle = receiverPly:GetVehicle() if currentVehicle == veh then table.remove(commandMarkers, i) changed = true continue end end if veh:IsVehicle() and veh:GetDriver() == receiverPly then table.remove(commandMarkers, i) changed = true continue end end end end if changed then UpdateClients() end end) -- Additional hooks to remove "take" markers local function RemoveTakeMarkerForEntity(ent, ply) if not IsValid(ent) or not IsValid(ply) then return end local steamID = ply:SteamID() local removedSomething = false for i = #commandMarkers, 1, -1 do local m = commandMarkers[i] if m.type == "take" and m.entIndex and m.receiver == steamID and ent:EntIndex() == m.entIndex then table.remove(commandMarkers, i) removedSomething = true end end if removedSomething then UpdateClients() end end hook.Add("PlayerUse", "TakeCommand_RemoveOnUse", function(ply, ent) RemoveTakeMarkerForEntity(ent, ply) end) hook.Add("PhysgunPickup", "TakeCommand_RemoveOnPickup", function(ply, ent) RemoveTakeMarkerForEntity(ent, ply) end)
else
--------------------------------
-- CLIENT SIDE (GROUPS + ORDERS)
--------------------------------
-- ConVars for group UI position and keys for orders
local groupUIPosition = CreateClientConVar("group_pos", "topright", true, false, "Group UI position: bottomright, topright, topleft, bottomleft")
local commandKey = CreateClientConVar("command_key", "g", true, false, "Key to call orders")
local deleteOrderKey = CreateClientConVar("delete_order_key", "h", true, false, "Key to delete orders")
local toggleGroupUIKey = CreateClientConVar("toggle_group_ui_key", "j", true, false, "Key to toggle group UI")
local groupUIEnabled = CreateClientConVar("group_ui_enabled", "1", true, true, "Is the group UI enabled?")
-- Variables for group management
local isInGroup = false
local groupLeaderSteamID = nil
local groupMembers = {} -- table of SteamIDs
local groupInviteFrom = nil
local showInviteNotification = false
local inviteNotificationEndTime = 0
local avatarsCache = {}
text-- Receiving an invitation net.Receive("Group_InviteNotify", function() local inviter = net.ReadEntity() if not IsValid(inviter) then return end groupInviteFrom = inviter showInviteNotification = true inviteNotificationEndTime = CurTime() + 30 end) -- Update group composition net.Receive("Group_Update", function() local data = net.ReadTable() groupLeaderSteamID = data.leader groupMembers = data.members or {} if groupLeaderSteamID == nil then isInGroup = false groupLeaderSteamID = nil groupMembers = {} else isInGroup = true end groupInviteFrom = nil showInviteNotification = false end) -------------------------------- -- GROUP INTERFACE -------------------------------- local mouseDown = false -- Check if a player is in the same group as the local player local function IsInMyGroup(ply) if not isInGroup then return false end if not IsValid(ply) then return false end local plySteamID = ply:SteamID() local localSteamID = LocalPlayer():SteamID() local meInGroup = false for _, sid in ipairs(groupMembers) do if sid == localSteamID then meInGroup = true break end end if not meInGroup then return false end for _, sid in ipairs(groupMembers) do if sid == plySteamID then return true end end return false end -- Function to draw a player avatar with caching local function DrawPlayerAvatar(ply, x, y, size) if not IsValid(ply) then return end local sid64 = ply:SteamID64() if not sid64 then return end local cacheKey = sid64 .. "_" .. size if not avatarsCache[cacheKey] then local avPanel = vgui.Create("AvatarImage") avPanel:SetSize(size, size) avPanel:SetPlayer(ply, size) avPanel:SetPaintedManually(true) avatarsCache[cacheKey] = avPanel end avatarsCache[cacheKey]:SetPos(x, y) avatarsCache[cacheKey]:PaintManual() end -- Kick menu function OpenKickMenu() local frame = vgui.Create("DFrame") frame:SetTitle("Kick a player from the group") frame:SetSize(300, 400) frame:Center() frame:MakePopup() local playerList = vgui.Create("DListView", frame) playerList:Dock(FILL) playerList:AddColumn("Player") playerList:AddColumn("SteamID") for _, steamID in ipairs(groupMembers) do if steamID ~= LocalPlayer():SteamID() then local ply = player.GetBySteamID(steamID) if IsValid(ply) then playerList:AddLine(ply:Nick(), steamID) end end end playerList.OnRowSelected = function(lst, index, pnl) local selectedSteamID = pnl:GetValue(2) if IsValid(frame.kickButton) then frame.kickButton:Remove() end frame.kickButton = vgui.Create("DButton", frame) frame.kickButton:SetText("Kick selected player") frame.kickButton:Dock(BOTTOM) frame.kickButton.DoClick = function() net.Start("Group_Kick") net.WriteString(selectedSteamID) net.SendToServer() frame:Close() end end end -- Invitation menu (only for the leader) local function OpenInviteMenu() net.Start("Group_RequestInviteList") net.SendToServer() end -- Handler for receiving the list of players available for invitation net.Receive("Group_SendInviteList", function() local availablePlayers = net.ReadTable() -- Create the UI local frame = vgui.Create("DFrame") frame:SetTitle("Select a player to invite") frame:SetSize(300, 400) frame:Center() frame:MakePopup() local playerList = vgui.Create("DListView", frame) playerList:Dock(FILL) playerList:AddColumn("Player") -- Fill the list for _, plData in ipairs(availablePlayers) do local line = playerList:AddLine(plData.nick) line.SteamID = plData.steamid end -- Selection handler playerList.OnRowSelected = function(_, index, line) net.Start("Group_Invite") net.WriteString(line.SteamID) net.SendToServer() frame:Close() end end) -- Clear avatar cache when a player disconnects hook.Add("PlayerDisconnected", "ClearAvatarCache", function(ply) local sid64 = ply:SteamID64() if sid64 then for k in pairs(avatarsCache) do if string.StartWith(k, sid64 .. "_") then avatarsCache[k]:Remove() avatarsCache[k] = nil end end end end) local lastToggleKeyState = false hook.Add("Think", "GroupUIToggle", function() -- Если открыта консоль или виден VGUI‑курсор (обычно это означает, что открыт чат или другой текстовый ввод), то не обрабатываем переключение UI. if gui.IsConsoleVisible() or vgui.CursorVisible() then return end local desiredKey = _G["KEY_" .. string.upper(toggleGroupUIKey:GetString())] or KEY_J local isDown = input.IsKeyDown(desiredKey) if isDown and not lastToggleKeyState then groupUIEnabled:SetBool(not groupUIEnabled:GetBool()) end lastToggleKeyState = isDown end) -- HUDPaint for the group panel and notifications hook.Add("HUDPaint", "Groups_HUDPaint", function() --------------------------- -- Invitation notification --------------------------- if showInviteNotification and IsValid(groupInviteFrom) then local notificationAvatarSize = 32 local w, h = 350, 120 local x, y = (ScrW() - w) / 2, 50 surface.SetDrawColor(50, 50, 50, 200) surface.DrawRect(x, y, w, h) surface.SetDrawColor(255, 255, 255, 255) surface.DrawOutlinedRect(x, y, w, h) DrawPlayerAvatar(groupInviteFrom, x + 10, y + 10, notificationAvatarSize) local textX = x + 10 + notificationAvatarSize + 10 local textY = y + 10 + (notificationAvatarSize / 2) - 6 draw.SimpleText(groupInviteFrom:Nick() .. " invites you to join the group", "DermaDefaultBold", textX, textY, Color(255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) local buttonW, buttonH = 80, 25 local acceptX, acceptY = x + 10, y + h - 35 local denyX, denyY = acceptX + buttonW + 10, acceptY local mx, my = gui.MousePos() local hoverAccept = mx >= acceptX and mx <= acceptX + buttonW and my >= acceptY and my <= acceptY + buttonH surface.SetDrawColor(hoverAccept and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(acceptX, acceptY, buttonW, buttonH) draw.SimpleText("Accept", "DermaDefault", acceptX + buttonW/2, acceptY + buttonH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if hoverAccept and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true net.Start("Group_AcceptInvite") net.WriteEntity(groupInviteFrom) net.SendToServer() showInviteNotification = false groupInviteFrom = nil elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end local hoverDeny = mx >= denyX and mx <= denyX + buttonW and my >= denyY and my <= denyY + buttonH surface.SetDrawColor(hoverDeny and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(denyX, denyY, buttonW, buttonH) draw.SimpleText("Decline", "DermaDefault", denyX + buttonW/2, denyY + buttonH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) -- Send the inviter entity along with the decline to the server if hoverDeny and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true net.Start("Group_DeclineInvite") net.WriteEntity(groupInviteFrom) net.SendToServer() showInviteNotification = false groupInviteFrom = nil elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end if CurTime() > inviteNotificationEndTime then showInviteNotification = false groupInviteFrom = nil end end if not groupUIEnabled:GetBool() then return end --------------------------- -- Group panel --------------------------- -- Base panel width; we will adjust if needed local panelW = 220 local panelH if not isInGroup then panelH = 200 else local baseHeight = 60 local lineHeight = 28 local buttonAreaHeight = 35 if groupLeaderSteamID == LocalPlayer():SteamID() then buttonAreaHeight = buttonAreaHeight + 30 end panelH = baseHeight + (#groupMembers * lineHeight) + buttonAreaHeight end -- Determine panel position based on ConVar setting local uiPos = string.lower(groupUIPosition:GetString()) local x, y if uiPos == "bottomright" then x = ScrW() - panelW - 10 y = ScrH() - panelH - 10 elseif uiPos == "topright" then x = ScrW() - panelW - 10 y = 10 elseif uiPos == "topleft" then x = 10 y = 10 elseif uiPos == "bottomleft" then x = 10 y = ScrH() - panelH - 10 else x = ScrW() - panelW - 10 y = ScrH() - panelH - 10 end -- Before drawing, recalc required width based on the longest row. local requiredWidth = panelW for _, steamID in ipairs(groupMembers) do local memberPly = player.GetBySteamID(steamID) if IsValid(memberPly) then local leaderPrefix = (steamID == groupLeaderSteamID) and "[Leader] " or "" local nameText = leaderPrefix .. memberPly:Nick() surface.SetFont("DermaDefault") local nameWidth = surface.GetTextSize(nameText) local dist = math.Round(LocalPlayer():GetPos():Distance(memberPly:GetPos()) / 60) local distanceText = string.format("%d m", dist) local distanceWidth = surface.GetTextSize(distanceText) -- Calculate the right edge for this row: -- Starting at x+15 (left margin) + avatarSize + 6 (gap) + nameWidth + 10 (gap) + distanceWidth + 10 (gap) + 16 (health icon width) + 10 (right padding) local rowRight = (15 + 24 + 6) + nameWidth + 10 + distanceWidth + 10 + 16 + 10 if rowRight > requiredWidth then requiredWidth = rowRight end end end -- If required width is greater than the base panel width, expand the panel to the left by adjusting x and panelW. if requiredWidth > panelW then panelW = requiredWidth x = ScrW() - panelW - 10 end -- Draw the panel background surface.SetDrawColor(40, 40, 40, 200) surface.DrawRect(x, y, panelW, panelH) surface.SetDrawColor(255, 255, 255, 255) surface.DrawOutlinedRect(x, y, panelW, panelH) local textY = y + 10 local mx, my = gui.MousePos() if not isInGroup then draw.SimpleText("You are not in a group", "DermaDefault", x + 10, textY, Color(255,255,255)) textY = textY + 20 local buttonW, buttonH = panelW - 20, 30 local bx, by = x + 10, textY local hoverCreate = mx >= bx and mx <= bx + buttonW and my >= by and my <= by + buttonH surface.SetDrawColor(hoverCreate and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(bx, by, buttonW, buttonH) draw.SimpleText("+ Create Group", "DermaDefault", bx + buttonW/2, by + buttonH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if hoverCreate and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true net.Start("Group_Create") net.SendToServer() elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end else draw.SimpleText("Group:", "DermaDefault", x + 10, textY, Color(255,255,255)) textY = textY + 15 local lineH = 28 local avatarSize = 24 -- For each member, compute dynamic positions to avoid overlapping for _, steamID in ipairs(groupMembers) do local memberPly = player.GetBySteamID(steamID) if IsValid(memberPly) then DrawPlayerAvatar(memberPly, x + 15, textY, avatarSize) local leaderPrefix = (steamID == groupLeaderSteamID) and "[Leader] " or "" local nameText = leaderPrefix .. memberPly:Nick() local nameX = x + 15 + avatarSize + 6 draw.SimpleText(nameText, "DermaDefault", nameX, textY + (avatarSize / 2) - 6, Color(255,255,255)) local dist = math.Round(LocalPlayer():GetPos():Distance(memberPly:GetPos()) / 60) local distanceText = string.format("%d m", dist) local nameWidth = surface.GetTextSize(nameText) local distanceX = nameX + nameWidth + 10 -- 10 pixel gap draw.SimpleText(distanceText, "DermaDefault", distanceX, textY + (avatarSize / 2) - 6, Color(255,255,255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP) local distanceWidth = surface.GetTextSize(distanceText) local healthIconX = distanceX + distanceWidth + 10 local healthIconY = textY + (avatarSize / 2) - 6 local health = memberPly:Health() local healthColor = Color(0, 255, 0) if health < 1 then healthColor = Color(0, 0, 0) elseif health <= 25 then healthColor = Color(255, 0, 0) elseif health <= 50 then healthColor = Color(255, 140, 0) elseif health <= 75 then healthColor = Color(255, 255, 0) end local healthCrossMaterial = Material("vgui/gradient-u") surface.SetMaterial(healthCrossMaterial) surface.SetDrawColor(healthColor) surface.DrawTexturedRect(healthIconX, healthIconY, 16, 16) textY = textY + lineH else draw.SimpleText("Player: " .. steamID, "DermaDefault", x + 15, textY, Color(255,255,255)) textY = textY + lineH end end local buttonW, buttonH = panelW - 20, 25 local bx, by = x + 10, (y + panelH) - 35 local hoverLeave = mx >= bx and mx <= bx + buttonW and my >= by and my <= by + buttonH surface.SetDrawColor(hoverLeave and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(bx, by, buttonW, buttonH) draw.SimpleText("Leave", "DermaDefault", bx + buttonW/2, by + buttonH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if hoverLeave and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true net.Start("Group_Leave") net.SendToServer() elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end if groupLeaderSteamID == LocalPlayer():SteamID() then local kickW, kickH = panelW - 20, 25 local kbx, kby = x + 10, by - 30 local hoverKick = mx >= kbx and mx <= kbx + kickW and my >= kby and my <= kby + kickH surface.SetDrawColor(hoverKick and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(kbx, kby, kickW, kickH) draw.SimpleText("- Kick", "DermaDefault", kbx + kickW/2, kby + kickH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if hoverKick and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true OpenKickMenu() elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end end if groupLeaderSteamID == LocalPlayer():SteamID() then local inviteW, inviteH = panelW - 20, 25 local ibx, iby = x + 10, by - 60 local hoverInvite = mx >= ibx and mx <= ibx + inviteW and my >= iby and my <= iby + inviteH surface.SetDrawColor(hoverInvite and Color(70,70,70,200) or Color(60,60,60,200)) surface.DrawRect(ibx, iby, inviteW, inviteH) draw.SimpleText("+ Invite", "DermaDefault", ibx + inviteW/2, iby + inviteH/2, Color(255,255,255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) if hoverInvite and input.IsMouseDown(MOUSE_LEFT) and not mouseDown then mouseDown = true OpenInviteMenu() elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDown = false end end end end) -------------------------------- -- RENDERING ORDERS (3D markers) -------------------------------- surface.CreateFont("CommandMarkerFont", { font = "Arial", size = 48, weight = 1000, antialias = true }) local COMMAND_COLORS = { ["move"] = Color(50, 200, 50), ["take"] = Color(255, 255, 0), ["defend"] = Color(50, 100, 255), ["sit"] = Color(170, 50, 255), ["attack"] = Color(255, 50, 50) } local COMMAND_RADIUS = 120 local commandsList = { { id = 1, name = "Move here", type = "move", angle = 0 }, { id = 2, name = "Take object", type = "take", angle = 72 }, { id = 3, name = "Defend me", type = "defend", angle = 144 }, { id = 4, name = "Enter vehicle", type = "sit", angle = 216 }, { id = 5, name = "Attack", type = "attack", angle = 288 } } local COMMAND_ICONS = { ["move"] = Material("icon16/arrow_down.png"), ["take"] = Material("icon16/package.png"), ["defend"] = Material("icon16/shield.png"), ["sit"] = Material("icon16/car.png"), ["attack"] = Material("icon16/status_online.png") } local MARKER_OFFSET = Vector(0, 0, 250) local TEXT_SCALE_FACTOR = 0.003 local commandMenuOpen = false local commandMarkers = {} -- local variable on the client (separate from the server one) net.Receive("UpdateMarkers", function() commandMarkers = net.ReadTable() end) local mouseDownDelete = false local function OpenDeleteOrderMenu() local frame = vgui.Create("DFrame") frame:SetTitle("Delete Orders") frame:SetSize(400, 300) frame:Center() frame:MakePopup() local orderList = vgui.Create("DListView", frame) orderList:Dock(FILL) orderList:AddColumn("Order") orderList:AddColumn("Receiver") orderList:AddColumn("Time Left (sec)") local localSteam = LocalPlayer():SteamID() local orders = {} -- Gather and process orders for _, m in ipairs(commandMarkers or {}) do if m.owner == localSteam then local key = m.created if not orders[key] then -- Convert the receiver's SteamID to a name local receiverName = "Unknown" if m.receiver then if m.receiver == "Group" then receiverName = "Group" else local receiverPly = player.GetBySteamID(m.receiver) receiverName = IsValid(receiverPly) and receiverPly:Nick() or m.receiver end end orders[key] = { text = m.text or "Marker", receiver = receiverName, timeout = m.timeout, created = m.created, count = 1 } else orders[key].count = orders[key].count + 1 orders[key].receiver = "Group" -- For group orders end end end -- Populate the list for k, order in pairs(orders) do local timeLeft = math.max(0, math.Round(order.timeout - CurTime())) local displayText = order.text if order.count > 1 then displayText = displayText .. " (Group x" .. order.count .. ")" end local line = orderList:AddLine(displayText, order.receiver, timeLeft) line.CreatedTime = order.created end -- The rest of the function remains unchanged orderList.OnRowSelected = function(lst, index, pnl) if IsValid(frame.deleteButton) then frame.deleteButton:Remove() end frame.deleteButton = vgui.Create("DButton", frame) frame.deleteButton:Dock(BOTTOM) frame.deleteButton:SetText("Delete selected order") frame.deleteButton.DoClick = function() local created = pnl.CreatedTime if created then net.Start("DeleteCommand") net.WriteFloat(created) net.SendToServer() frame:Close() end end end end hook.Add("PlayerButtonDown", "OpenDeleteOrderMenu_Key", function(ply, btn) if gui.IsConsoleVisible() or vgui.CursorVisible() then return end if not IsFirstTimePredicted() then return end local desiredKey = _G["KEY_" .. string.upper(deleteOrderKey:GetString())] or KEY_H if btn == desiredKey then if not mouseDownDelete then mouseDownDelete = true OpenDeleteOrderMenu() end elseif not input.IsMouseDown(MOUSE_LEFT) then mouseDownDelete = false end end) -- Rendering 3D command markers hook.Add("PostDrawTranslucentRenderables", "DrawCommandMarkers", function() local eyePos = EyePos() for _, marker in ipairs(commandMarkers) do if not marker.pos then continue end local basePosition = marker.pos local finalPosition = basePosition + MARKER_OFFSET local distance = eyePos:Distance(finalPosition) local scale = math.Clamp(distance / 6000, 0.8 / 3.5, 1) * 3.5 local textScale = math.Clamp(distance / 6000, 0.3 / 4.5, 1) * 4.5 local cmdColor = COMMAND_COLORS[marker.type] or Color(0, 255, 0) cmdColor.a = 255 local tr = util.TraceLine({ start = eyePos, endpos = finalPosition, filter = LocalPlayer() }) local ignoreZ = false if tr.Fraction < 1.0 then cmdColor.a = 100 ignoreZ = true end local verticalOffset = math.Clamp(distance / 6000 * 60, 10, 100) finalPosition = finalPosition + Vector(0, 0, verticalOffset) if ignoreZ then cam.IgnoreZ(true) end local arrowPos = finalPosition local playerPos = LocalPlayer():GetPos() -- Check for nil before calculating direction if playerPos and arrowPos then local direction = (playerPos - arrowPos):GetNormalized() direction.z = 0 direction:Normalize() local rotation = direction:Angle() rotation:RotateAroundAxis(rotation:Up(), 90) local rotationMatrix = Matrix() rotationMatrix:SetAngles(rotation) rotationMatrix:SetTranslation(arrowPos) local coneHeight = 80 * scale local radius = 30 * scale cam.PushModelMatrix(rotationMatrix) render.SetColorMaterial() mesh.Begin(MATERIAL_TRIANGLES, 3) local point1 = Vector(0, 0, -coneHeight) local point2 = Vector(-radius, 0, 0) local point3 = Vector(radius, 0, 0) mesh.Position(point1) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() mesh.Position(point2) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() mesh.Position(point3) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() mesh.End() mesh.Begin(MATERIAL_TRIANGLES, 32 * 2) for i = 0, 31 do local theta1 = (i / 32) * math.pi * 2 local theta2 = ((i + 1) / 32) * math.pi * 2 local c1x = math.cos(theta1) * radius local c1y = math.sin(theta1) * radius local c2x = math.cos(theta2) * radius local c2y = math.sin(theta2) * radius mesh.Position(Vector(0, 0, 0)) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() mesh.Position(Vector(c1x, c1y, 0)) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() mesh.Position(Vector(c2x, c2y, 0)) mesh.Color(cmdColor.r, cmdColor.g, cmdColor.b, cmdColor.a) mesh.AdvanceVertex() end mesh.End() cam.PopModelMatrix() else -- Handle case when playerPos or arrowPos are nil print("Error: playerPos or arrowPos are not vectors!") end if ignoreZ then cam.IgnoreZ(false) end local distanceMeters = math.Round(distance / 60) local displayText = (marker.text or "Marker") .. " " .. distanceMeters .. "M" if ignoreZ then cam.IgnoreZ(true) end cam.Start3D2D(finalPosition + Vector(0, 0, 30 * scale), Angle(0, EyeAngles().y - 90, 90), textScale) draw.SimpleText(displayText, "CommandMarkerFont", 0, 0, Color(255, 255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) cam.End3D2D() if ignoreZ then cam.IgnoreZ(false) end end end) -------------------------------- -- RADIAL MENU HANDLING -------------------------------- local function CloseMenu() commandMenuOpen = false gui.EnableScreenClicker(false) hook.Remove("HUDPaint", "DrawCommandWheel") hook.Remove("GUIMousePressed", "CommandSelect") end local function SelectCommand() local mouseX, mouseY = gui.MousePos() local centerX, centerY = ScrW() / 2, ScrH() / 2 local angle = math.deg(math.atan2(mouseY - centerY, mouseX - centerX)) angle = (angle + 360) % 360 for _, cmd in ipairs(commandsList) do local sectorStart = (cmd.angle - 36 + 360) % 360 local sectorEnd = (cmd.angle + 36) % 360 if sectorStart > sectorEnd then if angle >= sectorStart or angle <= sectorEnd then return cmd.id, cmd.type end else if angle >= sectorStart and angle <= sectorEnd then return cmd.id, cmd.type end end end end local function OpenPlayerSelectionPanel(cmdId, hitPos, hitEnt) local frame = vgui.Create("DFrame") frame:SetTitle("Select a player for the order") frame:SetSize(300, 150) frame:Center() frame:MakePopup() local combo = vgui.Create("DComboBox", frame) combo:SetPos(10, 35) combo:SetSize(280, 20) combo:SetValue("Players...") combo:SetEnabled(true) for _, steamID in ipairs(groupMembers) do local ply = player.GetBySteamID(steamID) if IsValid(ply) and ply ~= LocalPlayer() then combo:AddChoice(ply:Nick(), ply) end end local checkBox = vgui.Create("DCheckBoxLabel", frame) checkBox:SetPos(10, 60) checkBox:SetText("Order to all players") checkBox:SetValue(0) checkBox:SizeToContents() local btn = vgui.Create("DButton", frame) btn:SetPos(10, 90) btn:SetSize(280, 25) btn:SetText("Confirm") btn.DoClick = function() local isSendToAll = checkBox:GetChecked() if isSendToAll then for _, steamID in ipairs(groupMembers) do local playerInGroup = player.GetBySteamID(steamID) if IsValid(playerInGroup) and playerInGroup ~= LocalPlayer() then net.Start("ExecuteCommand") net.WriteUInt(cmdId, 4) net.WriteVector(hitPos) net.WriteEntity(hitEnt or NULL) net.WriteEntity(playerInGroup) net.SendToServer() end end else local _, selectedPlayer = combo:GetSelected() if not selectedPlayer or not IsValid(selectedPlayer) then frame:Close() return end net.Start("ExecuteCommand") net.WriteUInt(cmdId, 4) net.WriteVector(hitPos) net.WriteEntity(hitEnt or NULL) net.WriteEntity(selectedPlayer) net.SendToServer() end frame:Close() end checkBox.OnChange = function(self, value) if value then combo:SetEnabled(false) combo:SetValue("Order to all players") else combo:SetEnabled(true) combo:SetValue("Players...") end end end local function OnCommandSelect(mouseCode) if mouseCode == MOUSE_LEFT then local cmdId, cmdType = SelectCommand() if cmdId then local tr = LocalPlayer():GetEyeTrace() local hitPos = tr.HitPos local hitEnt = tr.Entity CloseMenu() OpenPlayerSelectionPanel(cmdId, hitPos, hitEnt) else CloseMenu() end end end -------------------------------- -- DRAWING THE RADIAL MENU -------------------------------- local GLOW_MATERIAL = Material("sprites/glow04_noz") local SECTOR_PADDING = 20 local SECTOR_BORDER = -10 local function DrawCommandWheel() if not commandMenuOpen then return end local w, h = ScrW(), ScrH() local centerX, centerY = w / 2, h / 2 local mouseX, mouseY = gui.MousePos() surface.SetDrawColor(30, 30, 40, 220) draw.Circle(centerX, centerY, COMMAND_RADIUS + SECTOR_PADDING, 64) surface.SetDrawColor(255, 255, 255, 220) for _, cmd in ipairs(commandsList) do local angle = math.rad(cmd.angle + 36) local innerStartX = centerX + math.cos(angle) * (COMMAND_RADIUS - SECTOR_PADDING) local innerStartY = centerY + math.sin(angle) * (COMMAND_RADIUS - SECTOR_PADDING) local outerEndX = centerX + math.cos(angle) * (COMMAND_RADIUS + SECTOR_PADDING) local outerEndY = centerY + math.sin(angle) * (COMMAND_RADIUS + SECTOR_PADDING) for i = -SECTOR_BORDER, SECTOR_BORDER do local offsetAngle = angle + math.rad(90) local dx = math.cos(offsetAngle) * i * 0.5 local dy = math.sin(offsetAngle) * i * 0.5 surface.DrawLine(innerStartX + dx, innerStartY + dy, outerEndX + dx, outerEndY + dy) end end local activeSector = nil local mouseAngle = math.atan2(gui.MouseY() - centerY, gui.MouseX() - centerX) if mouseAngle < 0 then mouseAngle = mouseAngle + 2 * math.pi end for _, cmd in ipairs(commandsList) do local sectorStart = math.rad(cmd.angle - 36) local sectorEnd = math.rad(cmd.angle + 36) if sectorStart > sectorEnd then if mouseAngle >= sectorStart or mouseAngle <= sectorEnd then activeSector = cmd break end elseif mouseAngle >= sectorStart and mouseAngle <= sectorEnd then activeSector = cmd break end end if activeSector then local iconRad = math.rad(activeSector.angle) local iconX = centerX + math.cos(iconRad) * COMMAND_RADIUS local iconY = centerY + math.sin(iconRad) * COMMAND_RADIUS surface.SetMaterial(GLOW_MATERIAL) for i = 1, 3 do local glowSize = 64 * (activeSector.lastScale or 1) + i * 10 local rotation = math.sin(RealTime() * 3) * 5 surface.SetDrawColor(255, 255, 255, 50) surface.DrawTexturedRectRotated(iconX, iconY, glowSize, glowSize, rotation) end end for _, cmd in ipairs(commandsList) do local rad = math.rad(cmd.angle) local x = centerX + math.cos(rad) * COMMAND_RADIUS local y = centerY + math.sin(rad) * COMMAND_RADIUS local isActive = (cmd == activeSector) local targetScale = isActive and 1.3 or 1 local scale = Lerp(FrameTime() * 10, cmd.lastScale or 1, targetScale) local rotation = isActive and math.sin(RealTime() * 3) * 5 or 0 cmd.lastScale = scale surface.SetMaterial(COMMAND_ICONS[cmd.type]) surface.SetDrawColor(255, 255, 255, 200) surface.DrawTexturedRectRotated(x, y, 32 * scale, 32 * scale, rotation) end end function draw.Circle(x, y, radius, segments) local circle = {} for i = 0, segments do local angle = math.rad((i / segments) * 360) circle[#circle + 1] = { x = x + math.cos(angle) * radius, y = y + math.sin(angle) * radius, u = 0, v = 0 } end surface.DrawPoly(circle) end -- New hook to open/close the order menu by the command_key local wasCommandKeyDown = false hook.Add("Think", "CommandKeyListener", function() local chatWasOpen = false if gui.IsConsoleVisible() or (LocalPlayer():ConCommand("say \"\"") and chatWasOpen) or vgui.GetKeyboardFocus() ~= nil then return end local desiredKey = _G["KEY_" .. string.upper(commandKey:GetString())] or KEY_G local isDown = input.IsKeyDown(desiredKey) if isDown and not wasCommandKeyDown then if not isInGroup then LocalPlayer():ChatPrint("You are not in a group!") else commandMenuOpen = true gui.EnableScreenClicker(true) hook.Add("HUDPaint", "DrawCommandWheel", DrawCommandWheel) hook.Add("GUIMousePressed", "CommandSelect", OnCommandSelect) end elseif not isDown and wasCommandKeyDown then if commandMenuOpen then CloseMenu() end end wasCommandKeyDown = isDown end)
end
Having an issue in our web console where this command repeats and floods our console ServerLog: "IC-1309 Niner<2><STEAM_0:0:52105625><Stormtrooper Cadet>" say ""
Что это вообще может быть? Какая строка может выдавать такое вообще?
Это garry's mod GLUA
Это сообщение не генерируется вашим orders‑кодом, а исходит из стандартного механизма Garry’s Mod для вывода чата. Формат
ИмяИгрока<UserID><SteamID><Group> say "сообщение"
— стандартный способ, которым сервер логирует чат-сообщения игроков. Если в кавычках ничего нет (то есть пустая строка), это означает, что игрок (в данном случае «IC-1309 Niner») каким‑либо образом отправляет пустое сообщение через команду say.
В вашем коде нет вызовов вроде RunConsoleCommand("say", …) или других инструкций, которые напрямую отправляют пустую строку в чат, поэтому эта нагрузка, скорее всего, исходит от другого аддона или из-за настроенного keybind’а, который по ошибке вызывает пустое сообщение.
Таким образом, если в веб‑консоли постоянно появляется
IC-1309 Niner<2><STEAM_0:0:52105625><Stormtrooper Cadet> say ""
— значит, кто‑то (или какой‑то скрипт/бинд) вызывает команду say с пустой строкой, а не ваш orders‑код. Проверьте другие аддоны или настройки клиента, которые могут инициировать пустой вызов чата.