if SERVER then -- Send file to clients AddCSLuaF...

Creado el: 8 de febrero de 2025

Respondido usando O3 Mini por Chat01

Pregunta

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()

text
if 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

Pensamiento

Investigating code issue

The user is having trouble with a command that floods their console, showing an output like "IC-1309 Niner<2><STEAM_0:0:52105625><Stormtrooper Cadet>" with a "say" command. This is from Garry's Mod, using GLua. It seems like a command or event is being triggered repeatedly, causing this output.

I wonder if the repeating command is coming from a loop or an event handler that's not being properly stopped. Hmm, maybe a specific function in the GLua code is retriggering when it shouldn't. I'll look into the source of the command.

Investigating empty message issue

The code doesn't explicitly call the "say" command, but the server console log suggests it's a chat message output, showing the player name, team, and empty message. This typical log comes up when a player sends a chat with no content, often caused by issuing a "say" command with an empty string. It seems like this might be slipping through when the orders system's code doesn't handle chat commands properly, leading to those blank messages. It's likely tied to an "if SERVER" block causing it.

Considering the log flood

This repetitive chat log might come from a default "say" command, which is logged when a player sends an empty chat message. The format typically shows the player's nickname, Steam ID, and empty message if no content is provided.

It could be triggered by a game function, plugin, or misbehaving mod executing the "say" command with an empty string. The code doesn't show any direct "say" calls, so it's likely not coming from the custom code. It might be due to continuous empty message triggers or chat command misfires.

Reasoned for 18 seconds

Respuesta

Это сообщение не генерируется вашим 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‑код. Проверьте другие аддоны или настройки клиента, которые могут инициировать пустой вызов чата.

Compartir este Q&A