local PLUGIN = PLUGIN -- Таблица активных отрядов PLUGIN.squads = PLUGIN.squads or {} PLUGIN.playerSquads = PLUGIN.playerSquads or {} -- [SteamID] = squadID PLUGIN.squadInvites = PLUGIN.squadInvites or {} -- [SteamID] = {squadID, inviterSteamID} util.AddNetworkString("ixSquadCreate") util.AddNetworkString("ixSquadDisband") util.AddNetworkString("ixSquadInvite") util.AddNetworkString("ixSquadAcceptInvite") util.AddNetworkString("ixSquadDeclineInvite") util.AddNetworkString("ixSquadKick") util.AddNetworkString("ixSquadLeave") util.AddNetworkString("ixSquadPromote") util.AddNetworkString("ixSquadSync") util.AddNetworkString("ixSquadNotify") util.AddNetworkString("ixSquadUpdateMarkers") util.AddNetworkString("ixSquadPlaceMarker") util.AddNetworkString("ixSquadWorldMarker") local function IsAdminMode(ply) return ply.GetNetVar and ply:GetNetVar("AdminMode", false) end -- Генерация уникального ID отряда function PLUGIN:GenerateSquadID() local id repeat id = "squad_" .. os.time() .. "_" .. math.random(1000, 9999) until not self.squads[id] return id end -- Получить отряд игрока function PLUGIN:GetPlayerSquad(client) local steamID = client:SteamID() local squadID = self.playerSquads[steamID] if squadID then return self.squads[squadID], squadID end return nil, nil end -- Получить фракцию игрока function PLUGIN:GetPlayerFaction(client) local character = client:GetCharacter() if not character then return nil end return character:GetFaction() end -- Создание отряда function PLUGIN:CreateSquad(leader) if IsAdminMode(leader) then return false, "Администратор не может создавать отряд" end if not IsValid(leader) then return false, "Недействительный игрок" end local steamID = leader:SteamID() -- Проверяем, не состоит ли уже в отряде if self.playerSquads[steamID] then return false, "Вы уже состоите в отряде" end local faction = self:GetPlayerFaction(leader) if not faction then return false, "У вас нет активного персонажа" end local squadID = self:GenerateSquadID() self.squads[squadID] = { id = squadID, leader = steamID, faction = faction, members = {steamID}, memberData = { [steamID] = { name = leader:Name(), isLeader = true, joinTime = os.time() } }, createdAt = os.time() } self.playerSquads[steamID] = squadID self:SyncSquadToMembers(squadID) self:StartSquadMarkers(squadID) -- Логирование создания отряда local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local factionName = ix.faction.Get(faction).name or tostring(faction) local message = string.format("%s создал отряд (ID: %s, фракция: %s)", leader:Nick(), squadID, factionName) serverlogsPlugin:AddLog("SQUAD_CREATE", message, leader, { squadID = squadID, faction = faction, factionName = factionName }) end return true, "Отряд успешно создан" end -- Расформирование отряда function PLUGIN:DisbandSquad(squadID) local squad = self.squads[squadID] if not squad then return false, "Отряд не найден" end -- Логируем расформирование перед удалением local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local leaderName = squad.memberData[squad.leader] and squad.memberData[squad.leader].name or "Unknown" local factionName = ix.faction.Get(squad.faction).name or tostring(squad.faction) local message = string.format("Отряд %s расформирован (лидер: %s, членов: %d, фракция: %s)", squadID, leaderName, #squad.members, factionName) serverlogsPlugin:AddLog("SQUAD_DELETE", message, nil, { squadID = squadID, leader = squad.leader, leaderName = leaderName, memberCount = #squad.members, faction = squad.faction, factionName = factionName }) end -- Останавливаем маркеры self:StopSquadMarkers(squadID) -- Очищаем данные на клиенте и уведомляем всех членов for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then -- Отправляем пустую таблицу для очистки данных на клиенте net.Start("ixSquadSync") net.WriteString("{}") net.Send(member) net.Start("ixSquadNotify") net.WriteString("Отряд был расформирован") net.Send(member) end self.playerSquads[memberSteamID] = nil end self.squads[squadID] = nil return true, "Отряд расформирован" end -- Приглашение в отряд function PLUGIN:InviteToSquad(inviter, target) print("[SQUADS] InviteToSquad вызван: inviter=" .. (IsValid(inviter) and inviter:Name() or "NIL") .. ", target=" .. (IsValid(target) and target:Name() or "NIL")) if IsAdminMode(inviter) then return false, "Администратор не может приглашать в отряд" end if not IsValid(inviter) or not IsValid(target) then print("[SQUADS] Ошибка: недействительный игрок") return false, "Недействительный игрок" end local squad, squadID = self:GetPlayerSquad(inviter) if not squad then print("[SQUADS] Ошибка: инвайтер не в отряде") return false, "Вы не состоите в отряде" end print("[SQUADS] Отряд найден: " .. squadID) local inviterSteamID = inviter:SteamID() if squad.leader ~= inviterSteamID then print("[SQUADS] Ошибка: не лидер (leader=" .. squad.leader .. ", inviter=" .. inviterSteamID .. ")") return false, "Только лидер может приглашать игроков" end local targetSteamID = target:SteamID() -- Проверяем, не состоит ли уже в отряде if self.playerSquads[targetSteamID] then print("[SQUADS] Ошибка: target уже в отряде") return false, target:Name() .. " уже состоит в отряде" end -- Проверяем лимит if #squad.members >= ix.config.Get("squadMaxMembers", 16) then print("[SQUADS] Ошибка: отряд заполнен") return false, "Отряд заполнен (макс. " .. ix.config.Get("squadMaxMembers", 16) .. " человек)" end -- Проверяем фракцию local targetFaction = self:GetPlayerFaction(target) if targetFaction ~= squad.faction then print("[SQUADS] Ошибка: разные фракции (squad=" .. squad.faction .. ", target=" .. (targetFaction or "NIL") .. ")") return false, target:Name() .. " из другой фракции" end -- Проверяем, нет ли уже приглашения if self.squadInvites[targetSteamID] then print("[SQUADS] Ошибка: у target уже есть приглашение") return false, target:Name() .. " уже имеет активное приглашение" end -- Сохраняем приглашение self.squadInvites[targetSteamID] = { squadID = squadID, inviterSteamID = inviterSteamID, inviterName = inviter:Name(), time = CurTime() } print("[SQUADS] Приглашение сохранено для " .. target:Name()) -- Отправляем приглашение net.Start("ixSquadInvite") net.WriteString(squadID) net.WriteString(inviter:Name()) net.Send(target) print("[SQUADS] Приглашение отправлено клиенту " .. target:Name()) -- Уведомляем инвайтера net.Start("ixSquadNotify") net.WriteString("Приглашение отправлено " .. target:Name()) net.Send(inviter) -- Логирование приглашения в отряд local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local message = string.format("%s пригласил %s в отряд %s", inviter:Nick(), target:Nick(), squadID) serverlogsPlugin:AddLog("SQUAD_INVITE", message, inviter, { squadID = squadID, targetSteamID = targetSteamID, targetName = target:Nick() }) end -- Автоматическое удаление через 30 секунд timer.Simple(30, function() if self.squadInvites[targetSteamID] and self.squadInvites[targetSteamID].squadID == squadID then self.squadInvites[targetSteamID] = nil print("[SQUADS] Приглашение для " .. targetSteamID .. " истекло") end end) return true, "Приглашение отправлено" end -- Принятие приглашения function PLUGIN:AcceptSquadInvite(client) local steamID = client:SteamID() local invite = self.squadInvites[steamID] if IsAdminMode(client) then return false, "Администратор не может вступать в отряд" end if not invite then return false, "У вас нет активных приглашений" end local squad = self.squads[invite.squadID] if not squad then self.squadInvites[steamID] = nil return false, "Отряд больше не существует" end -- Проверяем лимит еще раз if #squad.members >= ix.config.Get("squadMaxMembers", 16) then self.squadInvites[steamID] = nil return false, "Отряд уже заполнен" end -- Проверяем фракцию еще раз (на случай смены персонажа) local currentFaction = self:GetPlayerFaction(client) if currentFaction ~= squad.faction then self.squadInvites[steamID] = nil return false, "Вы больше не в той же фракции" end -- Добавляем в отряд table.insert(squad.members, steamID) squad.memberData[steamID] = { name = client:Name(), isLeader = false, joinTime = os.time() } self.playerSquads[steamID] = invite.squadID self.squadInvites[steamID] = nil -- Уведомляем всех членов отряда for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadNotify") net.WriteString(client:Name() .. " присоединился к отряду") net.Send(member) end end self:SyncSquadToMembers(invite.squadID) -- Логирование вступления в отряд local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local factionName = ix.faction.Get(squad.faction).name or tostring(squad.faction) local message = string.format("%s присоединился к отряду %s (лидер: %s)", client:Nick(), invite.squadID, squad.memberData[squad.leader].name) serverlogsPlugin:AddLog("SQUAD_JOIN", message, client, { squadID = invite.squadID, inviterSteamID = invite.inviterSteamID, inviterName = invite.inviterName, faction = squad.faction, factionName = factionName }) end return true, "Вы присоединились к отряду" end -- Исключение из отряда function PLUGIN:KickFromSquad(kicker, targetSteamID) if not IsValid(kicker) then return false, "Недействительный игрок" end local squad, squadID = self:GetPlayerSquad(kicker) if not squad then return false, "Вы не состоите в отряде" end if squad.leader ~= kicker:SteamID() then return false, "Только лидер может исключать игроков" end if targetSteamID == kicker:SteamID() then return false, "Используйте расформирование отряда" end -- Удаляем из отряда for i, memberSteamID in ipairs(squad.members) do if memberSteamID == targetSteamID then table.remove(squad.members, i) break end end local targetName = squad.memberData[targetSteamID].name squad.memberData[targetSteamID] = nil self.playerSquads[targetSteamID] = nil -- Уведомляем исключенного игрока и очищаем его данные local target = player.GetBySteamID(targetSteamID) if IsValid(target) then -- Очищаем данные отряда на клиенте net.Start("ixSquadSync") net.WriteString("{}") net.Send(target) net.Start("ixSquadNotify") net.WriteString("Вы были исключены из отряда") net.Send(target) end -- Уведомляем остальных for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadNotify") net.WriteString(targetName .. " был исключен из отряда") net.Send(member) end end self:SyncSquadToMembers(squadID) -- Логирование исключения из отряда local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local message = string.format("%s исключил %s из отряда %s", kicker:Nick(), targetName, squadID) serverlogsPlugin:AddLog("SQUAD_KICK", message, kicker, { squadID = squadID, targetSteamID = targetSteamID, targetName = targetName }) end return true, targetName .. " исключен из отряда" end -- Выход из отряда function PLUGIN:LeaveSquad(client) local squad, squadID = self:GetPlayerSquad(client) if not squad then return false, "Вы не состоите в отряде" end local steamID = client:SteamID() -- Если лидер - расформировываем отряд if squad.leader == steamID then return self:DisbandSquad(squadID) end -- Удаляем из отряда for i, memberSteamID in ipairs(squad.members) do if memberSteamID == steamID then table.remove(squad.members, i) break end end squad.memberData[steamID] = nil self.playerSquads[steamID] = nil -- Уведомляем остальных for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadNotify") net.WriteString(client:Name() .. " покинул отряд") net.Send(member) end end self:SyncSquadToMembers(squadID) -- Логирование выхода из отряда local serverlogsPlugin = ix.plugin.list["serverlogs"] if (serverlogsPlugin) then local message = string.format("%s покинул отряд %s", client:Nick(), squadID) serverlogsPlugin:AddLog("SQUAD_LEAVE", message, client, { squadID = squadID }) end return true, "Вы покинули отряд" end -- Система маркеров на компасе function PLUGIN:StartSquadMarkers(squadID) local timerName = "ixSquadMarkers_" .. squadID timer.Create(timerName, ix.config.Get("squadMarkerUpdateRate", 0.5), 0, function() local squad = self.squads[squadID] if not squad then timer.Remove(timerName) return end -- Обновляем маркеры для каждого члена отряда for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then -- Получаем других членов отряда local teammates = {} for _, otherSteamID in ipairs(squad.members) do if otherSteamID ~= memberSteamID then local teammate = player.GetBySteamID(otherSteamID) if IsValid(teammate) and teammate:Alive() then table.insert(teammates, teammate) end end end -- Отправляем позиции союзников этому игроку if #teammates > 0 then net.Start("ixSquadUpdateMarkers") net.WriteUInt(#teammates, 8) for _, teammate in ipairs(teammates) do net.WriteVector(teammate:GetPos()) net.WriteString(teammate:Name()) net.WriteEntity(teammate) end net.Send(member) end end end end) end function PLUGIN:StopSquadMarkers(squadID) timer.Remove("ixSquadMarkers_" .. squadID) end -- Синхронизация данных отряда function PLUGIN:SyncSquadToMembers(squadID) local squad = self.squads[squadID] if not squad then return end for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadSync") net.WriteString(util.TableToJSON(squad)) net.Send(member) end end end function PLUGIN:SyncAllSquads() for _, client in ipairs(player.GetAll()) do local squad, squadID = self:GetPlayerSquad(client) if squad then net.Start("ixSquadSync") net.WriteString(util.TableToJSON(squad)) net.Send(client) end end end -- Обработка отключения игрока function PLUGIN:PlayerDisconnected(client) if not IsValid(client) then return end local steamID = client:SteamID() local squad, squadID = self:GetPlayerSquad(client) if squad then -- Если это лидер - расформировываем отряд if squad.leader == steamID then print("[SQUADS] Лидер " .. client:Name() .. " отключился, расформирование отряда " .. squadID) self:DisbandSquad(squadID) else -- Обычный участник - просто покидает отряд print("[SQUADS] Игрок " .. client:Name() .. " отключился, удаление из отряда " .. squadID) -- Удаляем из списка участников for i, memberSteamID in ipairs(squad.members) do if memberSteamID == steamID then table.remove(squad.members, i) break end end squad.memberData[steamID] = nil self.playerSquads[steamID] = nil -- Уведомляем остальных участников for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadNotify") net.WriteString(client:Name() .. " покинул отряд (отключение)") net.Send(member) end end -- Синхронизируем данные с оставшимися участниками self:SyncSquadToMembers(squadID) end end -- Удаляем приглашения self.squadInvites[steamID] = nil end -- Сетевые обработчики net.Receive("ixSquadCreate", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local success, message = plugin:CreateSquad(client) net.Start("ixSquadNotify") net.WriteString(message) net.Send(client) end) net.Receive("ixSquadDisband", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local squad, squadID = plugin:GetPlayerSquad(client) if squad and squad.leader == client:SteamID() then plugin:DisbandSquad(squadID) end end) net.Receive("ixSquadInvite", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local targetSteamID = net.ReadString() print("[SQUADS SERVER] Получен запрос на приглашение от " .. client:Name() .. " для SteamID: " .. targetSteamID) local target = player.GetBySteamID(targetSteamID) print("[SQUADS SERVER] Найден игрок: " .. (IsValid(target) and target:Name() or "NIL")) if IsValid(target) then local success, message = plugin:InviteToSquad(client, target) print("[SQUADS SERVER] Результат приглашения: " .. tostring(success) .. " - " .. message) net.Start("ixSquadNotify") net.WriteString(message) net.Send(client) else net.Start("ixSquadNotify") net.WriteString("Игрок не найден") net.Send(client) end end) net.Receive("ixSquadAcceptInvite", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local success, message = plugin:AcceptSquadInvite(client) net.Start("ixSquadNotify") net.WriteString(message) net.Send(client) end) net.Receive("ixSquadDeclineInvite", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local invite = plugin.squadInvites[client:SteamID()] -- Уведомляем лидера об отклонении if invite then local inviter = player.GetBySteamID(invite.inviterSteamID) if IsValid(inviter) then net.Start("ixSquadNotify") net.WriteString(client:Name() .. " отклонил приглашение") net.Send(inviter) end end plugin.squadInvites[client:SteamID()] = nil net.Start("ixSquadNotify") net.WriteString("Приглашение отклонено") net.Send(client) end) net.Receive("ixSquadKick", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local targetSteamID = net.ReadString() local success, message = plugin:KickFromSquad(client, targetSteamID) net.Start("ixSquadNotify") net.WriteString(message) net.Send(client) end) net.Receive("ixSquadLeave", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if IsAdminMode(client) then return end local success, message = plugin:LeaveSquad(client) net.Start("ixSquadNotify") net.WriteString(message) net.Send(client) end) net.Receive("ixSquadPlaceMarker", function(len, client) local plugin = ix.plugin.Get("squads") if not plugin then return end if not IsValid(client) or not client:Alive() then return end local squad, squadID = plugin:GetPlayerSquad(client) if not squad then return end local pos = net.ReadVector() if not pos then return end for _, memberSteamID in ipairs(squad.members) do local member = player.GetBySteamID(memberSteamID) if IsValid(member) then net.Start("ixSquadWorldMarker") net.WriteVector(pos) net.WriteString(client:Name()) net.Send(member) end end end)