local IsValid = IsValid local PID = SquadMenu.GetPlayerId local FindByPID = SquadMenu.FindPlayerById local Squad = SquadMenu.Squad or {} SquadMenu.Squad = Squad Squad.__index = Squad --- Set the leader of this squad. `ply` can either be --- a player entity, a ID string that came from `SquadMenu.GetPlayerId`, --- or `nil` if you want to unset the current squad leader. function Squad:SetLeader( ply, name ) if type( ply ) == "string" then self.leaderId = ply -- this is a player ID self.leaderName = name or "?" elseif IsValid( ply ) then self.leaderId = PID( ply ) self.leaderName = ply:Nick() else self.leaderId = nil self.leaderName = nil SquadMenu.PrintF( "Removed leader from squad #%d", self.id ) return end SquadMenu.PrintF( "New leader for squad #%d: %s <%s>", self.id, self.leaderName, self.leaderId ) end --- Get a table containing a list of active squad members. --- Returns an array of player entities that are currently on the server. function Squad:GetActiveMembers() local members, count = {}, 0 local byId = SquadMenu.AllPlayersById() for id, _ in pairs( self.membersById ) do if byId[id] then count = count + 1 members[count] = byId[id] end end return members, count end --- Get a ready-to-be-stringified table containing details from this squad. function Squad:GetBasicInfo() local info = { id = self.id, name = self.name, icon = self.icon, members = {}, enableRings = self.enableRings, friendlyFire = self.friendlyFire, isPublic = self.isPublic, r = self.r, g = self.g, b = self.b } if self.leaderId then info.leaderId = self.leaderId info.leaderName = self.leaderName end local count = 0 for id, name in pairs( self.membersById ) do count = count + 1 info.members[count] = { id, name } end return info end local ValidateString = SquadMenu.ValidateString local ValidateNumber = SquadMenu.ValidateNumber --- Set the details of this squad using a table. function Squad:SetBasicInfo( info ) if SquadMenu.GetForceFriendlyFire() then info.friendlyFire = true end self.name = ValidateString( info.name, "Unnamed", SquadMenu.MAX_NAME_LENGTH ) self.icon = ValidateString( info.icon, "icon16/flag_blue.png", 256 ) self.enableRings = info.enableRings == true self.friendlyFire = info.friendlyFire == true self.isPublic = info.isPublic == true self.r = ValidateNumber( info.r, 255, 0, 255 ) self.g = ValidateNumber( info.g, 255, 0, 255 ) self.b = ValidateNumber( info.b, 255, 0, 255 ) SquadMenu.blockDamage[self.id] = Either( self.friendlyFire, nil, true ) end --- Send details and a list of members to all squad members. --- You should never set `immediate` to `true` unless you know what you're doing. function Squad:SyncWithMembers( immediate ) if not immediate then -- avoid spamming the networking system timer.Remove( "SquadMenu.Sync" .. self.id ) timer.Create( "SquadMenu.Sync" .. self.id, 0.5, 1, function() self:SyncWithMembers( true ) end ) return end local members, count = self:GetActiveMembers() if count == 0 then return end local data = self:GetBasicInfo() SquadMenu.StartCommand( SquadMenu.SETUP_SQUAD ) SquadMenu.WriteTable( data ) net.Send( members ) end --- Send the requests list to the squad leader. function Squad:SyncRequests() local leader = FindByPID( self.leaderId ) if not IsValid( leader ) then return end SquadMenu.StartCommand( SquadMenu.REQUESTS_LIST ) SquadMenu.WriteTable( self.requestsById ) net.Send( leader ) end --- Turns `p` into a id and player entity depending on what `p` is. local function ParsePlayerArg( p ) if type( p ) == "string" then return p, FindByPID( p ) end return PID( p ), p end --- Add a player as a new member. --- `p` can be a player id from `SquadMenu.GetPlayerId` or a player entity. function Squad:AddMember( p ) local id, ply = ParsePlayerArg( p ) if self.membersById[id] then return end local count = table.Count( self.membersById ) if count >= SquadMenu.GetMemberLimit() then return end local name = id if IsValid( ply ) then local oldSquad = SquadMenu:GetSquad( ply:GetSquadID() ) if oldSquad then oldSquad:RemoveMember( ply, SquadMenu.LEAVE_REASON_LEFT ) end ply:SetNWInt( "squad_menu.id", self.id ) name = ply:Nick() end self.membersById[id] = name self:SyncWithMembers() -- We don't send the requests list to the squad leaders (2nd "true" parameter) -- because "player_joined_squad" will already tell them to remove -- this player from their own copies of the join requests list. SquadMenu:CleanupRequests( id, true ) SquadMenu.StartEvent( "player_joined_squad", { squadId = self.id, playerId = id } ) net.Broadcast() hook.Run( "SquadMenu_OnJoinedSquad", self.id, ply, id ) end --- Remove a player from this squad's members list. --- `ply` can be a player id from `SquadMenu.GetPlayerId` or a player entity. --- `reasonId` can be `nil` or one of the values from `SquadMenu.LEAVE_REASON_*`. function Squad:RemoveMember( p, reasonId ) local id, ply = ParsePlayerArg( p ) if not self.membersById[id] then return end if id == self.leaderId then pcall( hook.Run, "SquadMenu_OnLeftSquad", self.id, ply, id ) self:Delete() return end self.membersById[id] = nil self:SyncWithMembers() if not IsValid( ply ) then return end ply:SetNWInt( "squad_menu.id", -1 ) if reasonId ~= nil then SquadMenu.StartCommand( SquadMenu.LEAVE_SQUAD ) net.WriteUInt( reasonId, 3 ) net.Send( ply ) end hook.Run( "SquadMenu_OnLeftSquad", self.id, ply, id ) end --- Add a player to the list of players that --- requested to join and notify the squad leader. function Squad:RequestToJoin( ply ) if self.isPublic then self:AddMember( ply ) return end local plyId = PID( ply ) if self.requestsById[plyId] then return end self.requestsById[plyId] = ply:Nick() self:SyncRequests() end --- Accept all the join requests from a list of player IDs. function Squad:AcceptRequests( ids ) if not table.IsSequential( ids ) then return end local limit = SquadMenu.GetMemberLimit() local count = table.Count( self.membersById ) for _, id in ipairs( ids ) do if count >= limit then break elseif self.requestsById[id] then count = count + 1 self.requestsById[id] = nil self:AddMember( id ) end end end --- Remove all members from this squad and delete it. function Squad:Delete() timer.Remove( "SquadMenu.Sync" .. self.id ) local members, count = self:GetActiveMembers() if count > 0 then for _, ply in ipairs( members ) do ply:SetNWInt( "squad_menu.id", -1 ) end SquadMenu.StartCommand( SquadMenu.LEAVE_SQUAD ) net.WriteUInt( SquadMenu.LEAVE_REASON_DELETED, 3 ) net.Send( members ) end self.membersById = nil self.requestsById = nil local id = self.id SquadMenu.squads[id] = nil SquadMenu.blockDamage[id] = nil SquadMenu.PrintF( "Deleted squad #%d", id ) SquadMenu.StartEvent( "squad_deleted", { id = id } ) net.Broadcast() end