init commit
This commit is contained in:
63
lua/squad_menu/client/config.lua
Normal file
63
lua/squad_menu/client/config.lua
Normal file
@@ -0,0 +1,63 @@
|
||||
local Config = SquadMenu.Config or {}
|
||||
|
||||
SquadMenu.Config = Config
|
||||
|
||||
function Config:Reset()
|
||||
self.showMembers = true
|
||||
self.showRings = true
|
||||
self.showHalos = false
|
||||
self.enableSounds = true
|
||||
|
||||
self.nameDistance = 3000
|
||||
self.haloDistance = 8000
|
||||
self.pingKey = KEY_B
|
||||
end
|
||||
|
||||
function Config:Load()
|
||||
self:Reset()
|
||||
|
||||
local data = file.Read( SquadMenu.DATA_FILE, "DATA" )
|
||||
if not data then return end
|
||||
|
||||
data = SquadMenu.JSONToTable( data )
|
||||
|
||||
self.showMembers = data.showMembers == true
|
||||
self.showRings = data.showRings == true
|
||||
self.showHalos = data.showHalos == true
|
||||
self.enableSounds = data.enableSounds == true
|
||||
|
||||
self.nameDistance = SquadMenu.ValidateNumber( data.nameDistance, 3000, 500, 50000 )
|
||||
self.haloDistance = SquadMenu.ValidateNumber( data.haloDistance, 8000, 500, 50000 )
|
||||
self.pingKey = math.floor( SquadMenu.ValidateNumber( data.pingKey, KEY_B, 1, 159 ) )
|
||||
end
|
||||
|
||||
function Config:Save( immediate )
|
||||
if not immediate then
|
||||
-- avoid spamming the file system
|
||||
timer.Remove( "SquadMenu.SaveConfigDelay" )
|
||||
timer.Create( "SquadMenu.SaveConfigDelay", 0.5, 1, function()
|
||||
self:Save( true )
|
||||
end )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local path = SquadMenu.DATA_FILE
|
||||
|
||||
local data = SquadMenu.TableToJSON( {
|
||||
showMembers = self.showMembers,
|
||||
showRings = self.showRings,
|
||||
showHalos = self.showHalos,
|
||||
enableSounds = self.enableSounds,
|
||||
pingKey = self.pingKey
|
||||
} )
|
||||
|
||||
SquadMenu.PrintF( "%s: writing %s", path, string.NiceSize( string.len( data ) ) )
|
||||
file.Write( path, data )
|
||||
|
||||
if SquadMenu.mySquad then
|
||||
SquadMenu:UpdateMembersHUD()
|
||||
end
|
||||
end
|
||||
|
||||
Config:Load()
|
||||
344
lua/squad_menu/client/hud.lua
Normal file
344
lua/squad_menu/client/hud.lua
Normal file
@@ -0,0 +1,344 @@
|
||||
local squad
|
||||
local nameDistance, haloDistance
|
||||
|
||||
function SquadMenu:RemoveMembersHUD()
|
||||
if self.membersPanel then
|
||||
self.membersPanel:Remove()
|
||||
end
|
||||
|
||||
self.membersPanel = nil
|
||||
|
||||
hook.Remove( "PrePlayerDraw", "SquadMenu.DrawRing" )
|
||||
hook.Remove( "PreDrawHalos", "SquadMenu.DrawHalos" )
|
||||
hook.Remove( "HUDPaint", "SquadMenu.DrawHUD" )
|
||||
hook.Remove( "HUDDrawTargetID", "SquadMenu.HideTargetInfo" )
|
||||
hook.Remove( "PlayerButtonDown", "SquadMenu.PingKey" )
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateMembersHUD()
|
||||
local config = self.Config
|
||||
|
||||
squad = self.mySquad
|
||||
nameDistance = config.nameDistance * config.nameDistance
|
||||
haloDistance = config.haloDistance * config.haloDistance
|
||||
|
||||
if config.showRings and self.mySquad.enableRings then
|
||||
hook.Add( "PrePlayerDraw", "SquadMenu.DrawRing", self.DrawRing )
|
||||
else
|
||||
hook.Remove( "PrePlayerDraw", "SquadMenu.DrawRing" )
|
||||
end
|
||||
|
||||
if config.showHalos then
|
||||
hook.Add( "PreDrawHalos", "SquadMenu.DrawHalos", self.DrawHalos )
|
||||
else
|
||||
hook.Remove( "PreDrawHalos", "SquadMenu.DrawHalos" )
|
||||
end
|
||||
|
||||
hook.Add( "HUDPaint", "SquadMenu.DrawHUD", self.DrawHUD )
|
||||
hook.Add( "HUDDrawTargetID", "SquadMenu.HideTargetInfo", self.HideTargetInfo )
|
||||
hook.Add( "PlayerButtonDown", "SquadMenu.PingKey", self.PingKey )
|
||||
|
||||
if self.membersPanel then
|
||||
self.membersPanel:SetVisible( config.showMembers )
|
||||
return
|
||||
end
|
||||
|
||||
local panel = vgui.Create( "DPanel" )
|
||||
panel:SetVisible( config.showMembers )
|
||||
panel:SetPaintBackground( false )
|
||||
panel:ParentToHUD()
|
||||
panel._OriginalInvalidateLayout = panel.InvalidateLayout
|
||||
|
||||
panel.InvalidateLayout = function( s, layoutNow )
|
||||
local screenW, screenH = ScrW(), ScrH()
|
||||
|
||||
local childCount = #s:GetChildren()
|
||||
local childH = math.Round( screenH * 0.04 )
|
||||
local childOffset = math.max( 1, math.Round( screenH * 0.002 ) )
|
||||
|
||||
local w = screenH * 0.22
|
||||
local h = ( childH + childOffset ) * childCount
|
||||
|
||||
s.childH = childH
|
||||
s.childOffset = childOffset
|
||||
s:SetSize( w, h )
|
||||
|
||||
local position = math.Clamp( SquadMenu.GetMembersPosition(), 1, 9 )
|
||||
local offset = screenH * 0.02
|
||||
local x, y
|
||||
|
||||
if position == 1 or position == 4 or position == 7 then
|
||||
-- left
|
||||
x = offset
|
||||
|
||||
elseif position == 2 or position == 5 or position == 8 then
|
||||
-- center
|
||||
x = ( screenW * 0.5 ) - ( w * 0.5 )
|
||||
else
|
||||
-- right
|
||||
x = screenW - w - offset
|
||||
end
|
||||
|
||||
if position == 7 or position == 8 or position == 9 then
|
||||
-- top
|
||||
y = offset
|
||||
|
||||
elseif position == 4 or position == 5 or position == 6 then
|
||||
-- center
|
||||
y = ( screenH * 0.5 ) - ( h * 0.5 )
|
||||
else
|
||||
-- bottom
|
||||
y = screenH - h - offset
|
||||
end
|
||||
|
||||
s:SetPos( x, y )
|
||||
s:_OriginalInvalidateLayout( layoutNow )
|
||||
end
|
||||
|
||||
panel.PerformLayout = function( s, w )
|
||||
local children = s:GetChildren()
|
||||
if #children == 0 then return end
|
||||
|
||||
local offset = s.childOffset or 1
|
||||
local height = s.childH or 24
|
||||
local y = 0
|
||||
|
||||
for _, p in ipairs( children ) do
|
||||
p:SetSize( w, height )
|
||||
p:SetPos( 0, y )
|
||||
|
||||
y = y + height + offset
|
||||
end
|
||||
end
|
||||
|
||||
self.membersPanel = panel
|
||||
end
|
||||
|
||||
function SquadMenu:AddMemberToHUD( member )
|
||||
member.panel = vgui.Create( "Squad_MemberInfo", self.membersPanel )
|
||||
member.panel:SetPlayer( member.id, member.name, squad )
|
||||
|
||||
self.membersPanel:InvalidateLayout()
|
||||
end
|
||||
|
||||
function SquadMenu:RemoveMemberFromHUD( member )
|
||||
if member.panel then
|
||||
member.panel:Remove()
|
||||
member.panel = nil
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
|
||||
local COLORS = {
|
||||
WHITE = Color( 255, 255, 255, 255 ),
|
||||
HEALTH = Color( 94, 253, 255, 255 ),
|
||||
LOW_HEALTH = Color( 250, 20, 20, 255 ),
|
||||
BOX_BG = Color( 0, 0, 0, 200 )
|
||||
}
|
||||
|
||||
local SetColor = surface.SetDrawColor
|
||||
local DrawRect = surface.DrawRect
|
||||
local DrawOutlinedRect = surface.DrawOutlinedRect
|
||||
|
||||
local DrawHealthBar = function( x, y, w, h, health, armor )
|
||||
if armor > 0 then
|
||||
SetColor( 255, 255, 255, 255 )
|
||||
DrawOutlinedRect( x - 1, y - 1, ( w + 2 ) * armor, h + 2, 1 )
|
||||
end
|
||||
|
||||
SetColor( 20, 20, 20, 255 )
|
||||
DrawRect( x, y, w, h )
|
||||
|
||||
x, y = x + 1, y + 1
|
||||
w, h = w - 2, h - 2
|
||||
|
||||
local color = health < 0.3 and COLORS.LOW_HEALTH or COLORS.HEALTH
|
||||
|
||||
SetColor( color:Unpack() )
|
||||
DrawRect( x, y, w * health, h )
|
||||
end
|
||||
|
||||
SquadMenu.DrawHealthBar = DrawHealthBar
|
||||
|
||||
----------
|
||||
|
||||
local EyePos = EyePos
|
||||
local Clamp = math.Clamp
|
||||
local SetMaterial = surface.SetMaterial
|
||||
local DrawTexturedRect = surface.DrawTexturedRect
|
||||
local LocalPlayer = LocalPlayer
|
||||
local PID = SquadMenu.GetPlayerId
|
||||
|
||||
do
|
||||
local Start3D2D = cam.Start3D2D
|
||||
local End3D2D = cam.End3D2D
|
||||
|
||||
local ringMaxDist = 3000 * 3000
|
||||
local ringAngle = Angle( 0, 0, 0 )
|
||||
local ringOffset = Vector( 0, 0, 5 )
|
||||
local ringMat = Material( "squad_menu/ring.png" )
|
||||
|
||||
SquadMenu.DrawRing = function( ply, flags )
|
||||
if flags == 1 then return end
|
||||
if ply == LocalPlayer() then return end
|
||||
if not squad.membersById[PID( ply )] then return end
|
||||
|
||||
local pos = ply:GetPos()
|
||||
local mult = Clamp( pos:DistToSqr( EyePos() ) / ringMaxDist, 0, 1 )
|
||||
local size = 300 + 1000 * mult
|
||||
|
||||
Start3D2D( pos + ringOffset * mult, ringAngle, 0.08 )
|
||||
|
||||
SetMaterial( ringMat )
|
||||
SetColor( squad.color:Unpack() )
|
||||
DrawTexturedRect( -size * 0.5, -size * 0.5, size, size )
|
||||
|
||||
End3D2D()
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
|
||||
local AllPlayersById = SquadMenu.AllPlayersById
|
||||
|
||||
SquadMenu.DrawHalos = function()
|
||||
local origin = EyePos()
|
||||
local me = LocalPlayer()
|
||||
local byId = AllPlayersById()
|
||||
local i, t, dist = 0, {}
|
||||
|
||||
for _, member in ipairs( squad.members ) do
|
||||
local ply = byId[member.id]
|
||||
|
||||
if ply and ply ~= me and ply:Alive() and not ply:IsDormant() then
|
||||
dist = origin:DistToSqr( ply:EyePos() )
|
||||
|
||||
if dist < haloDistance then
|
||||
i = i + 1
|
||||
t[i] = ply
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if i > 0 then
|
||||
halo.Add( t, squad.color, 2, 2, 1, true, true )
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
|
||||
SquadMenu.HideTargetInfo = function()
|
||||
local trace = util.TraceLine( util.GetPlayerTrace( LocalPlayer() ) )
|
||||
if not trace.Hit or not trace.HitNonWorld then return end
|
||||
|
||||
local ply = trace.Entity
|
||||
if not ply:IsPlayer() then return end
|
||||
|
||||
if squad.membersById[PID( ply )] and EyePos():DistToSqr( ply:EyePos() ) < nameDistance then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
|
||||
local DrawSimpleText = draw.SimpleText
|
||||
local GetTextSize = surface.GetTextSize
|
||||
local SetAlphaMultiplier = surface.SetAlphaMultiplier
|
||||
|
||||
local function DrawTag( ply )
|
||||
local nick = ply:Nick()
|
||||
local isAlive = ply:Alive()
|
||||
|
||||
local pos = ply:EyePos():ToScreen()
|
||||
local boxW, boxH = GetTextSize( nick )
|
||||
|
||||
local x = pos.x - boxW * 0.5
|
||||
local y = pos.y - boxH
|
||||
|
||||
SetColor( COLORS.BOX_BG:Unpack() )
|
||||
DrawRect( x - 2, y - 2, boxW + 4, boxH + 6 )
|
||||
|
||||
if isAlive then
|
||||
DrawSimpleText( nick, "SquadMenuInfo", pos.x, y, COLORS.WHITE, 1, 0 )
|
||||
DrawHealthBar( x - 2, y + boxH + 4, boxW + 4, 4, Clamp( ply:Health() / 100, 0, 1 ), ply:Armor() / 100 )
|
||||
else
|
||||
DrawSimpleText( nick, "SquadMenuInfo", pos.x, y, COLORS.LOW_HEALTH, 1, 0 )
|
||||
end
|
||||
end
|
||||
|
||||
local DrawOutlinedText = draw.SimpleTextOutlined
|
||||
local pingMat = Material( "squad_menu/ping.png", "smooth ignorez nocull" )
|
||||
local pings = SquadMenu.pings or {}
|
||||
|
||||
SquadMenu.pings = pings
|
||||
|
||||
SquadMenu.DrawHUD = function()
|
||||
surface.SetFont( "SquadMenuInfo" )
|
||||
|
||||
-- Draw player tags
|
||||
local origin = EyePos()
|
||||
local me = LocalPlayer()
|
||||
local byId = AllPlayersById()
|
||||
local dist
|
||||
|
||||
for _, member in ipairs( squad.members ) do
|
||||
local ply = byId[member.id]
|
||||
|
||||
if ply and ply ~= me and not ply:IsDormant() then
|
||||
dist = origin:DistToSqr( ply:EyePos() )
|
||||
|
||||
if dist < nameDistance then
|
||||
SetAlphaMultiplier( 1 - dist / nameDistance )
|
||||
DrawTag( ply )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Draw pings
|
||||
local t = RealTime()
|
||||
local sw, sh = ScrW(), ScrH()
|
||||
local r, g, b = squad.color:Unpack()
|
||||
local size = sh * 0.025
|
||||
local border = sh * 0.01
|
||||
|
||||
SetMaterial( pingMat )
|
||||
|
||||
for id, p in pairs( pings ) do
|
||||
if t > p.start + p.lifetime then
|
||||
pings[id] = nil
|
||||
else
|
||||
local showAnim = Clamp( ( t - p.start ) * 6, 0, 1 )
|
||||
local hideAnim = Clamp( p.start + p.lifetime - t, 0, 1 )
|
||||
|
||||
SetAlphaMultiplier( showAnim * hideAnim )
|
||||
|
||||
local pos = p.pos:ToScreen()
|
||||
local x = Clamp( pos.x, border, sw - border )
|
||||
local y = Clamp( pos.y, sh * 0.05, sh - border )
|
||||
local iconY = y - size - ( size * ( 1 - showAnim ) )
|
||||
|
||||
SetColor( 0, 0, 0 )
|
||||
DrawTexturedRect( 1 + x - size * 0.5, 1 + iconY, size, size )
|
||||
|
||||
SetColor( r, g, b )
|
||||
DrawTexturedRect( x - size * 0.5, iconY, size, size )
|
||||
DrawOutlinedText( p.label, "SquadMenuInfo", x, y - size - sh * 0.006, squad.color, 1, 4, 1, color_black )
|
||||
end
|
||||
end
|
||||
|
||||
SetAlphaMultiplier( 1 )
|
||||
end
|
||||
|
||||
SquadMenu.PingKey = function( ply, button )
|
||||
if ply ~= LocalPlayer() then return end
|
||||
if not IsFirstTimePredicted() then return end
|
||||
if button ~= SquadMenu.Config.pingKey then return end
|
||||
|
||||
local tr = ply:GetEyeTrace()
|
||||
if not tr.Hit then return end
|
||||
|
||||
SquadMenu.StartCommand( SquadMenu.PING )
|
||||
net.WriteVector( tr.HitPos )
|
||||
net.SendToServer()
|
||||
end
|
||||
380
lua/squad_menu/client/main.lua
Normal file
380
lua/squad_menu/client/main.lua
Normal file
@@ -0,0 +1,380 @@
|
||||
function SquadMenu.GetLanguageText( id )
|
||||
return language.GetPhrase( "squad_menu." .. id ):Trim()
|
||||
end
|
||||
|
||||
function SquadMenu:PlayUISound( path )
|
||||
if self.Config.enableSounds then
|
||||
sound.Play( path, Vector(), 0, 120, 0.75 )
|
||||
end
|
||||
end
|
||||
|
||||
local L = SquadMenu.GetLanguageText
|
||||
|
||||
function SquadMenu.GlobalMessage( ... )
|
||||
chat.AddText( SquadMenu.THEME_COLOR, "[" .. L( "title" ) .. "] ", Color( 255, 255, 255 ), ... )
|
||||
end
|
||||
|
||||
function SquadMenu.SquadMessage( ... )
|
||||
local squad = SquadMenu.mySquad
|
||||
if not squad then return end
|
||||
|
||||
local contents = { color_white, "[", squad.color, squad.name, color_white, "] ", ... }
|
||||
|
||||
if CustomChat then
|
||||
CustomChat:AddMessage( contents, "squad" )
|
||||
else
|
||||
chat.AddText( unpack( contents ) )
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu.LeaveMySquad( buttonToBlank, leaveNow )
|
||||
local squad = SquadMenu.mySquad
|
||||
if not squad then return end
|
||||
|
||||
if not leaveNow and squad.leaderId == SquadMenu.GetPlayerId( LocalPlayer() ) then
|
||||
Derma_Query( L"leave_leader", L"leave_squad", L"yes", function()
|
||||
SquadMenu.LeaveMySquad( buttonToBlank, true )
|
||||
end, L"no" )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
if IsValid( buttonToBlank ) then
|
||||
buttonToBlank:SetEnabled( false )
|
||||
buttonToBlank:SetText( "..." )
|
||||
end
|
||||
|
||||
SquadMenu.StartCommand( SquadMenu.LEAVE_SQUAD )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
--- If GMinimap is installed, update squad members' blips.
|
||||
function SquadMenu:UpdatePlayerBlips( icon, color )
|
||||
if not self.mySquad then return end
|
||||
|
||||
local me = LocalPlayer()
|
||||
local byId = self.AllPlayersById()
|
||||
|
||||
for _, member in ipairs( self.mySquad.members ) do
|
||||
local ply = byId[member.id]
|
||||
|
||||
if ply and ply ~= me then
|
||||
ply:SetBlipIcon( icon )
|
||||
ply:SetBlipColor( color )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the current members of the local player's squad.
|
||||
--- Updates the HUD and shows join/leave messages (if `printMessages` is `true`).
|
||||
---
|
||||
--- `newMembers` is an array where items are also arrays
|
||||
--- with a number (player id) and a string (player name).
|
||||
function SquadMenu:SetMembers( newMembers, printMessages )
|
||||
local members = self.mySquad.members
|
||||
local membersById = self.mySquad.membersById
|
||||
local keep = {}
|
||||
|
||||
-- Add new members that we do not have on our end
|
||||
for _, m in ipairs( newMembers ) do
|
||||
local id = m[1]
|
||||
local member = { id = id, name = m[2] }
|
||||
|
||||
keep[id] = true
|
||||
|
||||
if not membersById[id] then
|
||||
membersById[id] = member
|
||||
members[#members + 1] = member
|
||||
|
||||
self:AddMemberToHUD( member )
|
||||
|
||||
if printMessages then
|
||||
self.SquadMessage( string.format( L"member_joined", member.name ) )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local byId = self.AllPlayersById()
|
||||
|
||||
-- Remove members that we have locally but do not exist on `newMembers`.
|
||||
-- Backwards loop because we use `table.remove`
|
||||
for i = #members, 1, -1 do
|
||||
local member = members[i]
|
||||
local id = member.id
|
||||
|
||||
if not keep[id] then
|
||||
membersById[id] = nil
|
||||
table.remove( members, i )
|
||||
|
||||
self:RemoveMemberFromHUD( member )
|
||||
|
||||
if printMessages then
|
||||
self.SquadMessage( string.format( L"member_left", member.name ) )
|
||||
end
|
||||
|
||||
local ply = byId[id]
|
||||
|
||||
if IsValid( ply ) and GMinimap then
|
||||
ply:SetBlipIcon( nil )
|
||||
ply:SetBlipColor( nil )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the local player's squad.
|
||||
--- `data` is a table that comes from `squad:GetBasicInfo`.
|
||||
function SquadMenu:SetupSquad( data )
|
||||
local squad = self.mySquad or { id = -1 }
|
||||
local isUpdate = data.id == squad.id
|
||||
|
||||
self.mySquad = squad
|
||||
|
||||
squad.id = data.id
|
||||
squad.name = data.name
|
||||
squad.icon = data.icon
|
||||
|
||||
squad.leaderId = data.leaderId
|
||||
squad.leaderName = data.leaderName or ""
|
||||
|
||||
squad.enableRings = data.enableRings
|
||||
squad.friendlyFire = data.friendlyFire
|
||||
squad.isPublic = data.isPublic
|
||||
|
||||
squad.color = Color( data.r, data.g, data.b )
|
||||
|
||||
if CustomChat and squad.name then
|
||||
CustomChat:CreateCustomChannel( "squad", squad.name, squad.icon )
|
||||
end
|
||||
|
||||
if not isUpdate then
|
||||
squad.requests = {}
|
||||
squad.members = {}
|
||||
squad.membersById = {}
|
||||
|
||||
self:PlayUISound( "buttons/combine_button3.wav" )
|
||||
self.SquadMessage( L"squad_welcome", squad.color, " " .. squad.name )
|
||||
self.SquadMessage( L"chat_tip", " " .. table.concat( self.CHAT_PREFIXES, ", " ) )
|
||||
end
|
||||
|
||||
self:UpdateMembersHUD()
|
||||
self:SetMembers( data.members, isUpdate )
|
||||
|
||||
if IsValid( self.frame ) then
|
||||
self:RequestSquadListUpdate()
|
||||
self:UpdateSquadStatePanel()
|
||||
self:UpdateRequestsPanel()
|
||||
self:UpdateSquadMembersPanel()
|
||||
self:UpdateSquadPropertiesPanel()
|
||||
|
||||
self.frame:SetActiveTabByIndex( 3 ) -- squad members
|
||||
end
|
||||
|
||||
if GMinimap then
|
||||
self:UpdatePlayerBlips( "gminimap/blips/npc_default.png", squad.color )
|
||||
|
||||
hook.Add( "CanSeePlayerBlip", "ShowSquadBlips", function( ply )
|
||||
if ply:GetSquadID() == squad.id then return true, 50000 end
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:OnLeaveSquad( reason )
|
||||
if GMinimap then
|
||||
self:UpdatePlayerBlips( nil, nil )
|
||||
hook.Remove( "CanSeePlayerBlip", "ShowSquadBlips" )
|
||||
end
|
||||
|
||||
local reasonText = {
|
||||
[self.LEAVE_REASON_DELETED] = "deleted_squad",
|
||||
[self.LEAVE_REASON_KICKED] = "kicked_from_squad"
|
||||
}
|
||||
|
||||
if self.mySquad then
|
||||
self.GlobalMessage( L( reasonText[reason] or "left_squad" ) )
|
||||
self:PlayUISound( "buttons/combine_button2.wav" )
|
||||
end
|
||||
|
||||
self.mySquad = nil
|
||||
self:RemoveMembersHUD()
|
||||
|
||||
if IsValid( self.frame ) then
|
||||
self:RequestSquadListUpdate()
|
||||
self:UpdateSquadStatePanel()
|
||||
self:UpdateRequestsPanel()
|
||||
self:UpdateSquadMembersPanel()
|
||||
self:UpdateSquadPropertiesPanel()
|
||||
|
||||
if self.frame.lastTabIndex ~= 5 then -- not in settings
|
||||
self.frame:SetActiveTabByIndex( 1 ) -- squad list
|
||||
end
|
||||
end
|
||||
|
||||
if CustomChat then
|
||||
CustomChat:RemoveCustomChannel( "squad" )
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
|
||||
local commands = {}
|
||||
|
||||
commands[SquadMenu.SQUAD_LIST] = function()
|
||||
SquadMenu:UpdateSquadList( SquadMenu.ReadTable() )
|
||||
end
|
||||
|
||||
commands[SquadMenu.SETUP_SQUAD] = function()
|
||||
local data = SquadMenu.ReadTable()
|
||||
SquadMenu:SetupSquad( data )
|
||||
end
|
||||
|
||||
commands[SquadMenu.LEAVE_SQUAD] = function()
|
||||
local reason = net.ReadUInt( 3 )
|
||||
SquadMenu:OnLeaveSquad( reason )
|
||||
end
|
||||
|
||||
commands[SquadMenu.REQUESTS_LIST] = function()
|
||||
local squad = SquadMenu.mySquad
|
||||
if not squad then return end
|
||||
|
||||
local requests = squad.requests
|
||||
|
||||
-- Remember which players have requested before
|
||||
local alreadyRequested = {}
|
||||
|
||||
for _, member in ipairs( requests ) do
|
||||
alreadyRequested[member.id] = true
|
||||
end
|
||||
|
||||
-- Compare the new requests against what we already got
|
||||
local requestsById = SquadMenu.ReadTable()
|
||||
local newCount = 0
|
||||
|
||||
for id, name in pairs( requestsById ) do
|
||||
if not alreadyRequested[id] then
|
||||
-- This is a new request for us
|
||||
requests[#requests + 1] = { id = id, name = name }
|
||||
newCount = newCount + 1
|
||||
|
||||
SquadMenu.SquadMessage( string.format( L"request_message", name ) )
|
||||
end
|
||||
end
|
||||
|
||||
if newCount > 0 then
|
||||
SquadMenu:PlayUISound( "buttons/combine_button1.wav" )
|
||||
end
|
||||
|
||||
-- Remove requests we already got if they aren't on the new requests list
|
||||
for i = #requests, 1, -1 do
|
||||
local member = requests[i]
|
||||
|
||||
if not requestsById[member.id] then
|
||||
table.remove( requests, i )
|
||||
end
|
||||
end
|
||||
|
||||
SquadMenu:UpdateRequestsPanel()
|
||||
end
|
||||
|
||||
commands[SquadMenu.PING] = function()
|
||||
local pos = net.ReadVector()
|
||||
local label = net.ReadString()
|
||||
local id = net.ReadString()
|
||||
|
||||
local ping = SquadMenu.pings[id]
|
||||
|
||||
if not ping then
|
||||
ping = {}
|
||||
end
|
||||
|
||||
ping.pos = pos
|
||||
ping.label = label
|
||||
ping.start = RealTime()
|
||||
ping.lifetime = 5
|
||||
|
||||
SquadMenu.pings[id] = ping
|
||||
|
||||
if not SquadMenu.Config.enableSounds then return end
|
||||
|
||||
local eyePos = EyePos()
|
||||
local soundDir = pos - eyePos
|
||||
soundDir:Normalize()
|
||||
|
||||
sound.Play( "friends/friend_join.wav", eyePos + soundDir * 500, 100, 120, 1 )
|
||||
end
|
||||
|
||||
commands[SquadMenu.BROADCAST_EVENT] = function()
|
||||
local data = SquadMenu.ReadTable()
|
||||
local event = data.event
|
||||
|
||||
SquadMenu.PrintF( "Event received: %s", event )
|
||||
|
||||
if event == "open_menu" then
|
||||
SquadMenu:OpenFrame()
|
||||
|
||||
elseif event == "squad_position_changed" then
|
||||
if SquadMenu.membersPanel then
|
||||
SquadMenu.membersPanel:InvalidateLayout()
|
||||
end
|
||||
|
||||
elseif event == "player_joined_squad" then
|
||||
local squad = SquadMenu.mySquad
|
||||
if not squad then return end
|
||||
|
||||
-- Remove this player from my requests list
|
||||
local requests = squad.requests
|
||||
|
||||
for i, member in ipairs( requests ) do
|
||||
if data.playerId == member.id then
|
||||
table.remove( requests, i )
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
SquadMenu:UpdateRequestsPanel()
|
||||
|
||||
elseif event == "squad_created" or event == "squad_deleted" then
|
||||
SquadMenu:RequestSquadListUpdate()
|
||||
|
||||
if event == "squad_created" and data.name and SquadMenu.GetShowCreationMessage() then
|
||||
local color = Color( data.r, data.g, data.b )
|
||||
SquadMenu.GlobalMessage( string.format( L"squad_created", data.leaderName ), color, " " .. data.name )
|
||||
end
|
||||
|
||||
elseif event == "members_chat" then
|
||||
local squad = SquadMenu.mySquad
|
||||
if not squad then return end
|
||||
|
||||
SquadMenu.SquadMessage( squad.color, data.senderName, color_white, ": ", data.text )
|
||||
end
|
||||
end
|
||||
|
||||
net.Receive( "squad_menu.command", function()
|
||||
local cmd = net.ReadUInt( SquadMenu.COMMAND_SIZE )
|
||||
|
||||
if not commands[cmd] then
|
||||
SquadMenu.PrintF( "Received a unknown network command! (%d)", cmd )
|
||||
return
|
||||
end
|
||||
|
||||
commands[cmd]( ply, ent )
|
||||
end )
|
||||
|
||||
concommand.Add(
|
||||
"squad_menu",
|
||||
function() SquadMenu:OpenFrame() end,
|
||||
nil,
|
||||
"Opens the squad menu."
|
||||
)
|
||||
|
||||
if engine.ActiveGamemode() == "sandbox" then
|
||||
list.Set(
|
||||
"DesktopWindows",
|
||||
"SquadMenuDesktopIcon",
|
||||
{
|
||||
title = SquadMenu.GetLanguageText( "title" ),
|
||||
icon = "materials/squad_menu/squad_menu.png",
|
||||
init = function() SquadMenu:OpenFrame() end
|
||||
}
|
||||
)
|
||||
end
|
||||
598
lua/squad_menu/client/menu.lua
Normal file
598
lua/squad_menu/client/menu.lua
Normal file
@@ -0,0 +1,598 @@
|
||||
local PID = SquadMenu.GetPlayerId
|
||||
local L = SquadMenu.GetLanguageText
|
||||
local ScaleSize = StyledTheme.ScaleSize
|
||||
|
||||
function SquadMenu:CloseFrame()
|
||||
if IsValid( self.frame ) then
|
||||
self.frame:Close()
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:OpenFrame()
|
||||
if IsValid( self.frame ) then
|
||||
self:CloseFrame()
|
||||
return
|
||||
end
|
||||
|
||||
local frame = vgui.Create( "Styled_TabbedFrame" )
|
||||
frame:SetTitle( L"title" )
|
||||
frame:Center()
|
||||
frame:MakePopup()
|
||||
|
||||
frame.OnClose = function()
|
||||
self.frame = nil
|
||||
end
|
||||
|
||||
local h = ScaleSize( 550 )
|
||||
frame:SetTall( h )
|
||||
frame:SetMinHeight( h )
|
||||
|
||||
self.frame = frame
|
||||
|
||||
local panels = {}
|
||||
frame._panels = panels
|
||||
|
||||
-- Squad state
|
||||
local separation = ScaleSize( 4 )
|
||||
|
||||
panels.squadState = vgui.Create( "DPanel", frame )
|
||||
panels.squadState:SetTall( ScaleSize( 40 ) )
|
||||
panels.squadState:Dock( BOTTOM )
|
||||
panels.squadState:DockMargin( separation, separation, 0, 0 )
|
||||
panels.squadState:DockPadding( separation, separation, separation, separation )
|
||||
|
||||
-- Tabs
|
||||
panels.squadList = frame:AddTab( "styledstrike/icons/bullet_list.png", L"tab.squad_list" )
|
||||
panels.squadProperties = frame:AddTab( "styledstrike/icons/flag_two_tone.png", L"tab.squad_properties", "DPanel" )
|
||||
panels.squadMembers = frame:AddTab( "styledstrike/icons/users.png", L"tab.squad_members", "DPanel" )
|
||||
panels.joinRequests = frame:AddTab( "styledstrike/icons/user_add.png", L"tab.join_requests", "DPanel" )
|
||||
panels.settings = frame:AddTab( "styledstrike/icons/cog.png", L"tab.settings" )
|
||||
|
||||
self:RequestSquadListUpdate()
|
||||
self:UpdateSquadStatePanel()
|
||||
self:UpdateRequestsPanel()
|
||||
self:UpdateSquadMembersPanel()
|
||||
self:UpdateSquadPropertiesPanel()
|
||||
|
||||
local squad = self.mySquad
|
||||
|
||||
if squad then
|
||||
if #squad.members < 2 then
|
||||
frame:SetActiveTabByIndex( 4 ) -- Join requests
|
||||
else
|
||||
frame:SetActiveTabByIndex( 3 ) -- Squad members
|
||||
end
|
||||
end
|
||||
|
||||
-- Settings
|
||||
StyledTheme.CreateFormHeader( panels.settings, L"tab.settings", 0 )
|
||||
|
||||
StyledTheme.CreateFormSlider( panels.settings, L"settings.name_draw_distance", self.Config.nameDistance, 500, 50000, 0, function( value )
|
||||
self.Config.nameDistance = self.ValidateNumber( value, 2000, 500, 50000 )
|
||||
self.Config:Save()
|
||||
end )
|
||||
|
||||
StyledTheme.CreateFormSlider( panels.settings, L"settings.halo_draw_distance", self.Config.haloDistance, 500, 50000, 0, function( value )
|
||||
self.Config.haloDistance = self.ValidateNumber( value, 8000, 500, 50000 )
|
||||
self.Config:Save()
|
||||
end )
|
||||
|
||||
local binderPing = StyledTheme.CreateFormBinder( panels.settings, L"settings.ping_key", self.Config.pingKey )
|
||||
|
||||
binderPing.OnChange = function( _, key )
|
||||
self.Config.pingKey = key
|
||||
self.Config:Save()
|
||||
end
|
||||
|
||||
StyledTheme.CreateFormToggle( panels.settings, L"settings.show_members", self.Config.showMembers, function( checked )
|
||||
self.Config.showMembers = checked
|
||||
self.Config:Save()
|
||||
end )
|
||||
|
||||
StyledTheme.CreateFormToggle( panels.settings, L"settings.show_rings", self.Config.showRings, function( checked )
|
||||
self.Config.showRings = checked
|
||||
self.Config:Save()
|
||||
end )
|
||||
|
||||
StyledTheme.CreateFormToggle( panels.settings, L"settings.show_halos", self.Config.showHalos, function( checked )
|
||||
self.Config.showHalos = checked
|
||||
self.Config:Save()
|
||||
end )
|
||||
|
||||
StyledTheme.CreateFormToggle( panels.settings, L"settings.enable_sounds", self.Config.enableSounds, function( checked )
|
||||
self.Config.enableSounds = checked
|
||||
self.Config:Save()
|
||||
end )
|
||||
end
|
||||
|
||||
function SquadMenu:GetPanel( id )
|
||||
if IsValid( self.frame ) then
|
||||
return self.frame._panels[id]
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateSquadStatePanel()
|
||||
local statePanel = self:GetPanel( "squadState" )
|
||||
if not statePanel then return end
|
||||
|
||||
statePanel:Clear()
|
||||
|
||||
local squad = self.mySquad
|
||||
local squadColor = squad and squad.color or Color( 0, 0, 0 )
|
||||
|
||||
statePanel.Paint = function( _, w, h )
|
||||
surface.SetDrawColor( 20, 20, 20 )
|
||||
surface.DrawRect( 0, 0, w, h )
|
||||
|
||||
surface.SetDrawColor( squadColor:Unpack() )
|
||||
surface.DrawOutlinedRect( 0, 0, w, h, 1 )
|
||||
end
|
||||
|
||||
local imageIcon = vgui.Create( "DImage", statePanel )
|
||||
imageIcon:Dock( LEFT )
|
||||
imageIcon:SetWide( ScaleSize( 32 ) )
|
||||
imageIcon:SetImage( squad and squad.icon or "vgui/avatar_default" )
|
||||
|
||||
local labelName = vgui.Create( "DLabel", statePanel )
|
||||
labelName:Dock( FILL )
|
||||
labelName:DockMargin( ScaleSize( 8 ), 0, 0, 0 )
|
||||
labelName:SetText( squad and squad.name or L"not_in_a_squad" )
|
||||
|
||||
StyledTheme.Apply( labelName )
|
||||
|
||||
if not squad then return end
|
||||
|
||||
local buttonLeave = vgui.Create( "DButton", statePanel )
|
||||
buttonLeave:SetText( L"leave_squad" )
|
||||
buttonLeave:SetWide( ScaleSize( 180 ) )
|
||||
buttonLeave:Dock( RIGHT )
|
||||
|
||||
StyledTheme.Apply( buttonLeave )
|
||||
|
||||
buttonLeave.DoClick = function()
|
||||
SquadMenu.LeaveMySquad( buttonLeave )
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:RequestSquadListUpdate( immediate )
|
||||
timer.Remove( "SquadMenu.RequestListUpdate" )
|
||||
|
||||
local listPanel = self:GetPanel( "squadList" )
|
||||
if not listPanel then return end
|
||||
|
||||
listPanel:Clear()
|
||||
|
||||
StyledTheme.CreateFormHeader( listPanel, L"fetching_data", 0 )
|
||||
|
||||
if not immediate then
|
||||
-- Don't spam when this function gets called in quick succession
|
||||
timer.Create( "SquadMenu.RequestListUpdate", 1, 1, function()
|
||||
SquadMenu:RequestSquadListUpdate( true )
|
||||
end )
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
self.StartCommand( self.SQUAD_LIST )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateSquadList( squads )
|
||||
local listPanel = self:GetPanel( "squadList" )
|
||||
if not listPanel then return end
|
||||
|
||||
listPanel:Clear()
|
||||
|
||||
if #squads == 0 then
|
||||
StyledTheme.CreateFormHeader( listPanel, L"no_available_squads", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
StyledTheme.CreateFormHeader( listPanel, L"tab.squad_list", 0 )
|
||||
|
||||
local separation = ScaleSize( 6 )
|
||||
|
||||
for _, squad in ipairs( squads ) do
|
||||
local line = vgui.Create( "Squad_ListRow", listPanel )
|
||||
line:SetSquad( squad )
|
||||
line:Dock( TOP )
|
||||
line:DockMargin( 0, 0, 0, separation )
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateRequestsPanel()
|
||||
local requestsPanel = self:GetPanel( "joinRequests" )
|
||||
if not requestsPanel then return end
|
||||
|
||||
requestsPanel:Clear()
|
||||
|
||||
local padding = StyledTheme.dimensions.scrollPadding
|
||||
requestsPanel:DockPadding( padding, 0, padding, padding )
|
||||
requestsPanel:SetPaintBackground( true )
|
||||
requestsPanel:SetBackgroundColor( StyledTheme.colors.scrollBackground )
|
||||
|
||||
self.frame:SetTabNotificationCountByIndex( 4, 0 ) -- Join requests tab
|
||||
|
||||
local squad = self.mySquad
|
||||
|
||||
if not squad then
|
||||
StyledTheme.CreateFormHeader( requestsPanel, L"not_in_a_squad", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
if squad.leaderId ~= PID( LocalPlayer() ) then
|
||||
StyledTheme.CreateFormHeader( requestsPanel, L"not_squad_leader", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
local memberLimit = self.GetMemberLimit() - #squad.members
|
||||
|
||||
if memberLimit < 1 then
|
||||
StyledTheme.CreateFormHeader( requestsPanel, L"member_limit_reached", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
local requestsHeaderLabel = StyledTheme.CreateFormHeader( requestsPanel, L"requests_list", 0 ):GetChildren()[1]
|
||||
|
||||
local function UpdateMemberCount( current )
|
||||
requestsHeaderLabel:SetText( L( "slots" ) .. ": " .. current .. "/" .. self.GetMemberLimit() )
|
||||
end
|
||||
|
||||
UpdateMemberCount( #squad.members )
|
||||
|
||||
if squad.isPublic then
|
||||
StyledTheme.CreateFormHeader( requestsPanel, L"no_requests_needed", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
if #squad.requests == 0 then
|
||||
StyledTheme.CreateFormHeader( requestsPanel, L"no_requests_yet", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
self.frame:SetTabNotificationCountByIndex( 4, #squad.requests ) -- Join requests tab
|
||||
|
||||
local scrollRequests = vgui.Create( "DScrollPanel", requestsPanel )
|
||||
scrollRequests:Dock( FILL )
|
||||
scrollRequests:SetPaintBackground( false )
|
||||
|
||||
local buttonAccept
|
||||
local acceptedPlayers = {}
|
||||
|
||||
local function OnClickAccept()
|
||||
local ids = table.GetKeys( acceptedPlayers )
|
||||
|
||||
self.StartCommand( self.ACCEPT_REQUESTS )
|
||||
self.WriteTable( ids )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local function UpdateAcceptedCount( count )
|
||||
UpdateMemberCount( #squad.members + count )
|
||||
|
||||
if buttonAccept then
|
||||
buttonAccept:Remove()
|
||||
buttonAccept = nil
|
||||
end
|
||||
|
||||
if count == 0 then return end
|
||||
|
||||
buttonAccept = vgui.Create( "DButton", requestsPanel )
|
||||
buttonAccept:SetText( L"accept" )
|
||||
buttonAccept:Dock( BOTTOM )
|
||||
buttonAccept.DoClick = OnClickAccept
|
||||
buttonAccept._themeHighlight = true
|
||||
|
||||
StyledTheme.Apply( buttonAccept )
|
||||
end
|
||||
|
||||
UpdateAcceptedCount( 0 )
|
||||
|
||||
local function OnClickRow( row )
|
||||
local id = row._id
|
||||
local count = #table.GetKeys( acceptedPlayers )
|
||||
|
||||
if acceptedPlayers[id] then
|
||||
acceptedPlayers[id] = nil
|
||||
count = count - 1
|
||||
else
|
||||
if count < memberLimit then
|
||||
acceptedPlayers[id] = true
|
||||
count = count + 1
|
||||
else
|
||||
Derma_Message( L"cannot_accept_more", L"title", L"ok" )
|
||||
end
|
||||
end
|
||||
|
||||
row.isChecked = acceptedPlayers[id] ~= nil
|
||||
UpdateAcceptedCount( count )
|
||||
end
|
||||
|
||||
local rowHeight = ScaleSize( 48 )
|
||||
local rowPadding = ScaleSize( 6 )
|
||||
local nameColor = Color( 255, 255, 255 )
|
||||
|
||||
local function OnPaintRow( row, w, h )
|
||||
row._OriginalPaint( row, w, h )
|
||||
draw.SimpleText( row._name, "StyledTheme_Small", rowHeight + rowPadding, h * 0.5, nameColor, 0, 1 )
|
||||
end
|
||||
|
||||
local byId = SquadMenu.AllPlayersById()
|
||||
local dimensions = StyledTheme.dimensions
|
||||
|
||||
for _, member in ipairs( squad.requests ) do
|
||||
local row = vgui.Create( "DButton", scrollRequests )
|
||||
row:SetText( "" )
|
||||
row:Dock( TOP )
|
||||
row:DockMargin( dimensions.formPadding, 0, dimensions.formPadding, dimensions.formSeparator )
|
||||
row:DockPadding( rowPadding, rowPadding, rowPadding, rowPadding )
|
||||
|
||||
StyledTheme.Apply( row )
|
||||
|
||||
row._id = member.id
|
||||
row._name = member.name
|
||||
row.isToggle = true
|
||||
row.isChecked = false
|
||||
row.DoClick = OnClickRow
|
||||
row:SetTall( rowHeight )
|
||||
|
||||
row._OriginalPaint = row.Paint
|
||||
row.Paint = OnPaintRow
|
||||
|
||||
local avatar = vgui.Create( "AvatarImage", row )
|
||||
avatar:Dock( LEFT )
|
||||
avatar:SetWide( rowHeight - rowPadding * 2 )
|
||||
|
||||
if byId[member.id] then
|
||||
avatar:SetPlayer( byId[member.id], 64 )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateSquadMembersPanel()
|
||||
local membersPanel = self:GetPanel( "squadMembers" )
|
||||
if not membersPanel then return end
|
||||
|
||||
membersPanel:Clear()
|
||||
|
||||
local padding = StyledTheme.dimensions.scrollPadding
|
||||
membersPanel:DockPadding( padding, 0, padding, padding )
|
||||
membersPanel:SetPaintBackground( true )
|
||||
membersPanel:SetBackgroundColor( StyledTheme.colors.scrollBackground )
|
||||
|
||||
local squad = self.mySquad
|
||||
|
||||
if not squad then
|
||||
StyledTheme.CreateFormHeader( membersPanel, L"not_in_a_squad", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
local memberCount = #squad.members
|
||||
|
||||
StyledTheme.CreateFormHeader( membersPanel, L( "slots" ) .. ": " .. memberCount .. "/" .. self.GetMemberLimit(), 0 )
|
||||
|
||||
if memberCount < 2 then
|
||||
StyledTheme.CreateFormHeader( membersPanel, L"no_members", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
local localId = PID( LocalPlayer() )
|
||||
local isLocalPlayerLeader = squad.leaderId == localId
|
||||
|
||||
local membersScroll = vgui.Create( "DScrollPanel", membersPanel )
|
||||
membersScroll:Dock( FILL )
|
||||
membersScroll:DockMargin( 0, padding, 0, padding )
|
||||
|
||||
local OnClickKick = function( s )
|
||||
s:SetEnabled( false )
|
||||
s:SetText( "..." )
|
||||
|
||||
self.StartCommand( self.KICK )
|
||||
net.WriteString( s._id )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local rowHeight = ScaleSize( 48 )
|
||||
local rowPadding = ScaleSize( 6 )
|
||||
|
||||
local colors = StyledTheme.colors
|
||||
local DrawRect = StyledTheme.DrawRect
|
||||
|
||||
local function OnPaintRow( row, w, h )
|
||||
DrawRect( 0, 0, w, h, colors.buttonBorder )
|
||||
DrawRect( 1, 1, w - 2, h - 2, colors.panelBackground )
|
||||
|
||||
draw.SimpleText( row._name, "StyledTheme_Small", rowHeight + rowPadding, h * 0.5, colors.labelText, 0, 1 )
|
||||
end
|
||||
|
||||
local byId = SquadMenu.AllPlayersById()
|
||||
local dimensions = StyledTheme.dimensions
|
||||
|
||||
for _, member in ipairs( squad.members ) do
|
||||
local row = vgui.Create( "Panel", membersScroll )
|
||||
row:Dock( TOP )
|
||||
row:DockMargin( dimensions.formPadding, 0, dimensions.formPadding, dimensions.formSeparator )
|
||||
row:DockPadding( rowPadding, rowPadding, rowPadding, rowPadding )
|
||||
|
||||
row._name = member.name
|
||||
row.Paint = OnPaintRow
|
||||
row:SetTall( rowHeight )
|
||||
|
||||
local avatar = vgui.Create( "AvatarImage", row )
|
||||
avatar:Dock( LEFT )
|
||||
avatar:SetWide( rowHeight - rowPadding * 2 )
|
||||
|
||||
if byId[member.id] then
|
||||
avatar:SetPlayer( byId[member.id], 64 )
|
||||
end
|
||||
|
||||
if isLocalPlayerLeader and member.id ~= localId then
|
||||
local kick = vgui.Create( "DButton", row )
|
||||
kick:SetText( L"kick" )
|
||||
kick:SetWide( ScaleSize( 100 ) )
|
||||
kick:Dock( RIGHT )
|
||||
|
||||
kick._id = member.id
|
||||
kick.DoClick = OnClickKick
|
||||
|
||||
StyledTheme.Apply( kick )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function SquadMenu:UpdateSquadPropertiesPanel()
|
||||
local propertiesPanel = self:GetPanel( "squadProperties" )
|
||||
if not propertiesPanel then return end
|
||||
|
||||
propertiesPanel:Clear()
|
||||
|
||||
local padding = StyledTheme.dimensions.scrollPadding
|
||||
propertiesPanel:DockPadding( padding, 0, padding, padding )
|
||||
propertiesPanel:SetPaintBackground( true )
|
||||
propertiesPanel:SetBackgroundColor( StyledTheme.colors.scrollBackground )
|
||||
|
||||
local squad = self.mySquad
|
||||
|
||||
if squad and squad.leaderId ~= PID( LocalPlayer() ) then
|
||||
StyledTheme.CreateFormHeader( propertiesPanel, L"leave_first_create", 0 )
|
||||
return
|
||||
end
|
||||
|
||||
local isNew = squad == nil
|
||||
local oldName = squad and squad.name or nil
|
||||
local oldColor = squad and squad.color or nil
|
||||
|
||||
if not oldColor then
|
||||
local c = HSVToColor( math.random( 0, 360 ), 1, 1 )
|
||||
oldColor = Color( c.r, c.g, c.b ) -- Reconstruct color instance to avoid a bug
|
||||
end
|
||||
|
||||
squad = squad or {
|
||||
enableRings = true
|
||||
}
|
||||
|
||||
StyledTheme.CreateFormHeader( propertiesPanel, L( isNew and "create_squad" or "edit_squad" ), 0 )
|
||||
|
||||
local data = {
|
||||
name = squad.name or string.format( L"default_squad_name", LocalPlayer():Nick() ),
|
||||
icon = squad.icon or "icon16/flag_blue.png",
|
||||
|
||||
enableRings = squad.enableRings == true,
|
||||
friendlyFire = squad.friendlyFire == true,
|
||||
isPublic = squad.isPublic == true,
|
||||
|
||||
r = oldColor.r,
|
||||
g = oldColor.g,
|
||||
b = oldColor.b
|
||||
}
|
||||
|
||||
local buttonCreate = vgui.Create( "DButton", propertiesPanel )
|
||||
buttonCreate:SetTall( 36 )
|
||||
buttonCreate:SetText( L( isNew and "create_squad" or "edit_squad" ) )
|
||||
buttonCreate:Dock( BOTTOM )
|
||||
buttonCreate:DockMargin( 0, ScaleSize( 8 ), 0, 0 )
|
||||
|
||||
StyledTheme.Apply( buttonCreate )
|
||||
|
||||
buttonCreate.DoClick = function( s )
|
||||
s:SetEnabled( false )
|
||||
s:SetText( "..." )
|
||||
|
||||
self.StartCommand( self.SETUP_SQUAD )
|
||||
self.WriteTable( data )
|
||||
net.SendToServer()
|
||||
end
|
||||
|
||||
local leftPanel = vgui.Create( "DPanel", propertiesPanel )
|
||||
leftPanel:Dock( FILL )
|
||||
|
||||
StyledTheme.Apply( leftPanel )
|
||||
StyledTheme.CreateFormHeader( leftPanel, L"squad_name", 0, 0 )
|
||||
|
||||
local separator = ScaleSize( 6 )
|
||||
local rowHeight = StyledTheme.dimensions.buttonHeight
|
||||
|
||||
local entryName = vgui.Create( "DTextEntry", leftPanel )
|
||||
entryName:SetTall( rowHeight )
|
||||
entryName:Dock( TOP )
|
||||
entryName:DockMargin( separator, separator, separator, separator )
|
||||
entryName:SetMaximumCharCount( self.MAX_NAME_LENGTH )
|
||||
entryName:SetValue( data.name )
|
||||
|
||||
entryName.OnChange = function()
|
||||
local value = entryName:GetValue()
|
||||
data.name = value:Trim() == "" and oldName or value
|
||||
end
|
||||
|
||||
StyledTheme.Apply( entryName )
|
||||
StyledTheme.CreateFormHeader( leftPanel, L"tab.squad_properties", 0, 0 )
|
||||
|
||||
local buttonIcon = vgui.Create( "DButton", leftPanel )
|
||||
buttonIcon:SetTall( rowHeight )
|
||||
buttonIcon:SetIcon( data.icon )
|
||||
buttonIcon:SetText( L"choose_icon" )
|
||||
buttonIcon:Dock( TOP )
|
||||
buttonIcon:DockMargin( separator, separator, separator, 0 )
|
||||
|
||||
StyledTheme.Apply( buttonIcon )
|
||||
|
||||
buttonIcon.DoClick = function()
|
||||
local iconBrowser = vgui.Create( "DIconBrowser" )
|
||||
iconBrowser:SetSize( 300, 200 )
|
||||
|
||||
local m = DermaMenu()
|
||||
m:AddPanel( iconBrowser )
|
||||
m:SetPaintBackground( false )
|
||||
m:Open( gui.MouseX() + 8, gui.MouseY() + 10 )
|
||||
|
||||
iconBrowser.OnChange = function( s )
|
||||
local iconPath = s:GetSelectedIcon()
|
||||
|
||||
buttonIcon:SetIcon( iconPath )
|
||||
data.icon = iconPath
|
||||
|
||||
CloseDermaMenus()
|
||||
end
|
||||
end
|
||||
|
||||
StyledTheme.CreateFormToggle( leftPanel, L"squad_is_public", data.isPublic, function( checked )
|
||||
data.isPublic = checked
|
||||
end ):DockMargin( separator, separator, separator, 0 )
|
||||
|
||||
local ffButton = StyledTheme.CreateFormToggle( leftPanel, L"squad_friendly_fire", data.friendlyFire, function( checked )
|
||||
data.friendlyFire = checked
|
||||
end )
|
||||
|
||||
ffButton:DockMargin( separator, separator, separator, 0 )
|
||||
|
||||
if SquadMenu.GetForceFriendlyFire() then
|
||||
ffButton:SetEnabled( false )
|
||||
ffButton:SetIcon( "icon16/accept.png" )
|
||||
ffButton:SetText( L"squad_force_friendly_fire" )
|
||||
end
|
||||
|
||||
StyledTheme.CreateFormToggle( leftPanel, L"squad_rings", data.enableRings, function( checked )
|
||||
data.enableRings = checked
|
||||
end ):DockMargin( separator, separator, separator, 0 )
|
||||
|
||||
local rightPanel = vgui.Create( "DPanel", propertiesPanel )
|
||||
rightPanel:SetWide( ScaleSize( 260 ) )
|
||||
rightPanel:Dock( RIGHT )
|
||||
rightPanel:DockMargin( separator, 0, 0, 0 )
|
||||
|
||||
StyledTheme.Apply( rightPanel )
|
||||
StyledTheme.CreateFormHeader( rightPanel, L"squad_color", 0, 0 )
|
||||
|
||||
local colorPicker = vgui.Create( "DColorMixer", rightPanel )
|
||||
colorPicker:Dock( FILL )
|
||||
colorPicker:SetPalette( true )
|
||||
colorPicker:SetAlphaBar( false )
|
||||
colorPicker:SetWangs( true )
|
||||
colorPicker:SetColor( oldColor )
|
||||
|
||||
colorPicker.ValueChanged = function( _, color )
|
||||
data.r = color.r
|
||||
data.g = color.g
|
||||
data.b = color.b
|
||||
end
|
||||
end
|
||||
100
lua/squad_menu/client/vgui/member_status.lua
Normal file
100
lua/squad_menu/client/vgui/member_status.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
local IsValid = IsValid
|
||||
local Clamp = math.Clamp
|
||||
local RealTime = RealTime
|
||||
local FrameTime = FrameTime
|
||||
local Approach = math.Approach
|
||||
|
||||
local SetColor = surface.SetDrawColor
|
||||
local SetMaterial = surface.SetMaterial
|
||||
local DrawOutlinedText = draw.SimpleTextOutlined
|
||||
local DrawTexturedRect = surface.DrawTexturedRect
|
||||
local DrawRect = surface.DrawRect
|
||||
|
||||
local DrawHealthBar = SquadMenu.DrawHealthBar
|
||||
local matGradient = Material( "vgui/gradient-r" )
|
||||
|
||||
local COLORS = {
|
||||
WHITE = Color( 255, 255, 255, 255 ),
|
||||
LOW_HEALTH = Color( 250, 20, 20, 255 ),
|
||||
OUTLINE = Color( 0, 0, 0, 255 )
|
||||
}
|
||||
|
||||
local PANEL = {}
|
||||
|
||||
function PANEL:Init()
|
||||
self.avatar = vgui.Create( "AvatarImage", self )
|
||||
self:InvalidateLayout()
|
||||
self:SetPlayer()
|
||||
end
|
||||
|
||||
function PANEL:SetPlayer( id, name, squad )
|
||||
self.squad = squad
|
||||
self.playerId = id
|
||||
self.validateTimer = 0
|
||||
|
||||
self.name = SquadMenu.ValidateString( name, "", 20 )
|
||||
self.health = 1
|
||||
self.armor = 0
|
||||
self.alive = true
|
||||
|
||||
self.healthAnim = 0
|
||||
self.armorAnim = 0
|
||||
end
|
||||
|
||||
function PANEL:Think()
|
||||
if IsValid( self.ply ) then
|
||||
self.health = Clamp( self.ply:Health() / 100, 0, 1 )
|
||||
self.armor = Clamp( self.ply:Armor() / 100, 0, 1 )
|
||||
self.alive = self.ply:Alive()
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
-- Keep trying to get the player entity periodically
|
||||
if RealTime() < self.validateTimer then return end
|
||||
|
||||
self.validateTimer = RealTime() + 1
|
||||
|
||||
local ply = SquadMenu.FindPlayerById( self.playerId )
|
||||
|
||||
if ply then
|
||||
self.ply = ply
|
||||
self.name = SquadMenu.ValidateString( ply:Nick(), "", 20 )
|
||||
self.avatar:SetPlayer( ply, 64 )
|
||||
end
|
||||
end
|
||||
|
||||
function PANEL:Paint( w, h )
|
||||
local split = h
|
||||
|
||||
if self.squad then
|
||||
SetColor( self.squad.color:Unpack() )
|
||||
DrawRect( w - split, 0, split, h )
|
||||
end
|
||||
|
||||
SetColor( 0, 0, 0, 240 )
|
||||
SetMaterial( matGradient )
|
||||
DrawTexturedRect( 0, 0, w - split, h )
|
||||
|
||||
local dt = FrameTime()
|
||||
|
||||
self.healthAnim = Approach( self.healthAnim, self.health, dt * 2 )
|
||||
self.armorAnim = Approach( self.armorAnim, self.armor, dt )
|
||||
|
||||
if self.alive then
|
||||
local barH = h * 0.2
|
||||
DrawHealthBar( 2, h - barH - 5, w - split - 6, barH, self.healthAnim, self.armorAnim )
|
||||
end
|
||||
|
||||
DrawOutlinedText( self.name, "SquadMenuInfo", 2, 2 + h * 0.5,
|
||||
self.alive and COLORS.WHITE or COLORS.LOW_HEALTH, 0, self.alive and 4 or 1, 1, COLORS.OUTLINE )
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout( w, h )
|
||||
local size = h - 4
|
||||
|
||||
self.avatar:SetSize( size, size )
|
||||
self.avatar:SetPos( w - size - 2, 2 )
|
||||
end
|
||||
|
||||
vgui.Register( "Squad_MemberInfo", PANEL, "DPanel" )
|
||||
199
lua/squad_menu/client/vgui/squad_list_row.lua
Normal file
199
lua/squad_menu/client/vgui/squad_list_row.lua
Normal file
@@ -0,0 +1,199 @@
|
||||
local L = SquadMenu.GetLanguageText
|
||||
local ScaleSize = StyledTheme.ScaleSize
|
||||
|
||||
local UpdateButton = function( button, text, enabled )
|
||||
button:SetEnabled( enabled )
|
||||
button:SetText( L( text ) )
|
||||
button:SizeToContentsX( ScaleSize( 12 ) )
|
||||
button:GetParent():InvalidateLayout()
|
||||
end
|
||||
|
||||
local PANEL = {}
|
||||
local COLOR_BLACK = Color( 0, 0, 0, 255 )
|
||||
|
||||
function PANEL:Init()
|
||||
self.squad = {
|
||||
id = 0,
|
||||
name = "-",
|
||||
leaderName = "-",
|
||||
color = COLOR_BLACK
|
||||
}
|
||||
|
||||
self:SetCursor( "hand" )
|
||||
self:SetExpanded( false )
|
||||
self.animHover = 0
|
||||
|
||||
self.collapsedHeight = ScaleSize( 52 )
|
||||
self.padding = ScaleSize( 6 )
|
||||
self.iconSize = self.collapsedHeight - self.padding * 2
|
||||
|
||||
self.icon = vgui.Create( "DImage", self )
|
||||
self.icon:SetSize( self.iconSize, self.iconSize )
|
||||
|
||||
self.buttonJoin = vgui.Create( "DButton", self )
|
||||
|
||||
StyledTheme.Apply( self.buttonJoin )
|
||||
self.buttonJoin:SetTall( self.collapsedHeight - self.padding * 2 )
|
||||
|
||||
self.buttonJoin.DoClick = function()
|
||||
if self.leaveOnClick then
|
||||
SquadMenu.LeaveMySquad( self.buttonJoin )
|
||||
else
|
||||
UpdateButton( self.buttonJoin, "waiting_response", false )
|
||||
|
||||
SquadMenu.StartCommand( SquadMenu.JOIN_SQUAD )
|
||||
net.WriteUInt( self.squad.id, 16 )
|
||||
net.SendToServer()
|
||||
end
|
||||
end
|
||||
|
||||
self.memberCount = vgui.Create( "DPanel", self )
|
||||
self.memberCount:SetTall( self.collapsedHeight - self.padding * 2 )
|
||||
self.memberCount:SetPaintBackground( false )
|
||||
|
||||
self:SetTall( self.collapsedHeight )
|
||||
end
|
||||
|
||||
function PANEL:PerformLayout( w )
|
||||
self.icon:SetPos( self.padding, self.padding )
|
||||
|
||||
local joinWidth = self.buttonJoin:GetWide()
|
||||
|
||||
self.buttonJoin:SetPos( w - joinWidth - self.padding, self.padding )
|
||||
self.memberCount:SetPos( w - joinWidth - self.memberCount:GetWide() - self.padding * 2, self.padding )
|
||||
end
|
||||
|
||||
local colors = StyledTheme.colors
|
||||
local DrawRect = StyledTheme.DrawRect
|
||||
|
||||
function PANEL:Paint( w, h )
|
||||
self.animHover = Lerp( FrameTime() * 10, self.animHover, self:IsHovered() and 1 or 0 )
|
||||
|
||||
DrawRect( 0, 0, w, h, self.squad.color )
|
||||
DrawRect( 1, 1, w - 2, h - 2, COLOR_BLACK )
|
||||
DrawRect( 1, 1, w - 2, h - 2, colors.buttonHover, self.animHover )
|
||||
|
||||
local x = self.iconSize + self.padding * 2
|
||||
local y = self.collapsedHeight * 0.5
|
||||
|
||||
draw.SimpleText( self.squad.name, "StyledTheme_Small", x, y, colors.labelText, 0, 4 )
|
||||
draw.SimpleText( self.squad.leaderName or "<Server>", "StyledTheme_Tiny", x, y, colors.buttonTextDisabled, 0, 3 )
|
||||
end
|
||||
|
||||
function PANEL:OnMousePressed( keyCode )
|
||||
if keyCode == MOUSE_LEFT then
|
||||
self:SetExpanded( not self.isExpanded, true )
|
||||
end
|
||||
end
|
||||
|
||||
--- Set the squad data.
|
||||
--- `squad` is a table that comes from `squad:GetBasicInfo`.
|
||||
function PANEL:SetSquad( squad )
|
||||
squad.color = Color( squad.r, squad.g, squad.b )
|
||||
|
||||
self.squad = squad
|
||||
self.icon:SetImage( squad.icon )
|
||||
|
||||
local maxMembers = SquadMenu.GetMemberLimit()
|
||||
local count = #squad.members
|
||||
|
||||
self.leaveOnClick = squad.id == ( SquadMenu.mySquad and SquadMenu.mySquad.id or -1 )
|
||||
|
||||
if self.leaveOnClick then
|
||||
UpdateButton( self.buttonJoin, "leave_squad", true )
|
||||
|
||||
elseif count < maxMembers then
|
||||
UpdateButton( self.buttonJoin, squad.isPublic and "join" or "request_to_join", true )
|
||||
|
||||
else
|
||||
UpdateButton( self.buttonJoin, "full_squad", false )
|
||||
end
|
||||
|
||||
self.memberCount:Clear()
|
||||
|
||||
local labelCount = vgui.Create( "DLabel", self.memberCount )
|
||||
labelCount:SetText( count .. "/" .. maxMembers )
|
||||
labelCount:SizeToContents()
|
||||
labelCount:Dock( FILL )
|
||||
|
||||
local labelWide = labelCount:GetWide()
|
||||
|
||||
local iconCount = vgui.Create( "DImage", self.memberCount )
|
||||
iconCount:SetImage( "styledstrike/icons/users.png" )
|
||||
iconCount:SetWide( self.collapsedHeight - self.padding * 4 )
|
||||
iconCount:Dock( LEFT )
|
||||
iconCount:DockMargin( 0, self.padding, 0, self.padding )
|
||||
|
||||
self.memberCount:SetWide( labelWide + iconCount:GetWide() )
|
||||
end
|
||||
|
||||
function PANEL:SetExpanded( expanded, scroll )
|
||||
self.isExpanded = expanded
|
||||
|
||||
local height = self.collapsedHeight
|
||||
local memberHeight = ScaleSize( 32 )
|
||||
|
||||
if expanded then
|
||||
height = height + self.padding + memberHeight * math.min( #self.squad.members, 5 )
|
||||
end
|
||||
|
||||
self:SetTall( height )
|
||||
self:InvalidateLayout()
|
||||
|
||||
if expanded and scroll then
|
||||
self:GetParent():GetParent():ScrollToChild( self )
|
||||
end
|
||||
|
||||
if self.membersScroll then
|
||||
self.membersScroll:Remove()
|
||||
self.membersScroll = nil
|
||||
end
|
||||
|
||||
if not expanded then return end
|
||||
|
||||
local membersScroll = vgui.Create( "DScrollPanel", self )
|
||||
membersScroll:Dock( FILL )
|
||||
membersScroll:DockMargin( 0, self.collapsedHeight, 0, 0 )
|
||||
membersScroll.pnlCanvas:DockPadding( 0, 0, 0, 0 )
|
||||
|
||||
self.membersScroll = membersScroll
|
||||
|
||||
local byId = SquadMenu.AllPlayersById()
|
||||
local separation = ScaleSize( 2 )
|
||||
local padding = ScaleSize( 4 )
|
||||
|
||||
for _, m in ipairs( self.squad.members ) do
|
||||
local id = m[1]
|
||||
|
||||
local row = vgui.Create( "DPanel", membersScroll )
|
||||
row:SetBackgroundColor( colors.panelBackground )
|
||||
row:SetTall( memberHeight - separation )
|
||||
row:Dock( TOP )
|
||||
row:DockMargin( self.padding, 0, self.padding, separation )
|
||||
|
||||
local name = vgui.Create( "DLabel", row )
|
||||
name:SetText( m[2] )
|
||||
name:Dock( FILL )
|
||||
|
||||
local avatar = vgui.Create( "AvatarImage", row )
|
||||
avatar:SetWide( memberHeight - padding * 2 )
|
||||
avatar:Dock( LEFT )
|
||||
avatar:DockMargin( padding, padding, padding, padding )
|
||||
|
||||
if byId[id] then
|
||||
avatar:SetPlayer( byId[id], 64 )
|
||||
end
|
||||
|
||||
if id == self.squad.leaderId then
|
||||
row:SetZPos( -1 )
|
||||
|
||||
local leaderIcon = vgui.Create( "DImage", row )
|
||||
leaderIcon:SetWide( memberHeight - padding * 2 )
|
||||
leaderIcon:SetImage( "icon16/award_star_gold_3.png" )
|
||||
leaderIcon:Dock( RIGHT )
|
||||
leaderIcon:DockMargin( 0, padding, padding, padding )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vgui.Register( "Squad_ListRow", PANEL, "DPanel" )
|
||||
Reference in New Issue
Block a user