Initial commit
This commit is contained in:
parent
6f71e2e1aa
commit
d93dbacee2
@ -1 +0,0 @@
|
||||
RunConsoleCommand("cl_timeout '240'")
|
3
lua/autorun/client/cl_init.lua
Normal file
3
lua/autorun/client/cl_init.lua
Normal file
@ -0,0 +1,3 @@
|
||||
RunConsoleCommand("cl_timeout '240'")
|
||||
include("cfc_disconnect_interface/client/cl_ponger.lua")
|
||||
include("cfc_disconnect_interface/client/cl_interface.lua")
|
9
lua/autorun/server/sv_init.lua
Normal file
9
lua/autorun/server/sv_init.lua
Normal file
@ -0,0 +1,9 @@
|
||||
util.AddNetworkString("cfc_di_ping")
|
||||
util.AddNetworkString("cfc_di_loaded")
|
||||
util.AddNetworkString("cfc_di_shutdown")
|
||||
|
||||
AddCSLuaFile("cfc_disconnect_interface/client/cl_ponger.lua")
|
||||
AddCSLuaFile("cfc_disconnect_interface/client/cl_api.lua")
|
||||
AddCSLuaFile("cfc_disconnect_interface/client/cl_interface.lua")
|
||||
|
||||
include("cfc_disconnect_interface/server/sv_pinger.lua")
|
86
lua/cfc_disconnect_interface/client/cl_api.lua
Normal file
86
lua/cfc_disconnect_interface/client/cl_api.lua
Normal file
@ -0,0 +1,86 @@
|
||||
crashApi = {}
|
||||
|
||||
local cfc_endpoint = "https://scripting.cfcservers.org/cfc3-ping"
|
||||
local global_endpoint = "https://www.google.com"
|
||||
|
||||
local api = crashApi
|
||||
|
||||
api.INACTIVE = 0
|
||||
api.PINGING_API = 1
|
||||
api.NO_INTERNET = 2
|
||||
api.SERVER_DOWN = 3
|
||||
api.SERVER_UP = 4
|
||||
|
||||
local responses = {cfc = nil, global = nil} -- Does nothing but helps with clarity
|
||||
|
||||
local state = api.INACTIVE
|
||||
|
||||
local pingCancelled = false
|
||||
|
||||
local function getState()
|
||||
return state
|
||||
end
|
||||
|
||||
local function handleResponses()
|
||||
if pingCancelled then -- Ignore responses if ping was cancelled
|
||||
return
|
||||
end
|
||||
if responses.cfc == nil or responses.global == nil then -- Not all responses arrived yet
|
||||
return
|
||||
end
|
||||
if responses.cfc then
|
||||
-- Server is up
|
||||
state = api.SERVER_UP
|
||||
elseif not responses.cfc and responses.global then
|
||||
-- Server is down
|
||||
state = api.SERVER_DOWN
|
||||
else
|
||||
-- Internet is down
|
||||
state = api.NO_INTERNET
|
||||
end
|
||||
end
|
||||
|
||||
local function triggerPing()
|
||||
pingCancelled = false
|
||||
state = api.PINGING_API
|
||||
responses = {cfc = nil, global = nil}
|
||||
|
||||
http.Fetch(cfc_endpoint,
|
||||
function(body, size, headers, code)
|
||||
local data = util.JSONToTable( body )
|
||||
if not data or data["server-is-up"] == nil then -- Can't use dot notation cuz api field has dashes >:(
|
||||
responses.cfc = false
|
||||
handleResponses()
|
||||
else
|
||||
responses.cfc = data["server-is-up"]
|
||||
handleResponses()
|
||||
end
|
||||
end,
|
||||
function(err)
|
||||
responses.cfc = false
|
||||
handleResponses()
|
||||
end
|
||||
)
|
||||
|
||||
http.Fetch(global_endpoint,
|
||||
function(body, size, headers, code)
|
||||
responses.global = true
|
||||
handleResponses()
|
||||
end,
|
||||
function(err)
|
||||
responses.global = false
|
||||
handleResponses()
|
||||
end
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
local function cancelPing()
|
||||
state = api.INACTIVE
|
||||
pingCancelled = true
|
||||
end
|
||||
|
||||
|
||||
api.getState = getState
|
||||
api.triggerPing = triggerPing
|
||||
api.cancelPing = cancelPing
|
327
lua/cfc_disconnect_interface/client/cl_interface.lua
Normal file
327
lua/cfc_disconnect_interface/client/cl_interface.lua
Normal file
@ -0,0 +1,327 @@
|
||||
include("cfc_disconnect_interface/client/cl_api.lua")
|
||||
|
||||
surface.CreateFont( "CFC_Normal",
|
||||
{
|
||||
font = "arial",
|
||||
size = 18,
|
||||
weight = 500
|
||||
}
|
||||
)
|
||||
|
||||
surface.CreateFont( "CFC_Special",
|
||||
{
|
||||
font = "coolvetica",
|
||||
size = 26,
|
||||
weight = 500
|
||||
}
|
||||
)
|
||||
|
||||
surface.CreateFont( "CFC_Button",
|
||||
{
|
||||
font = "arial",
|
||||
size = 18,
|
||||
weight = 1500
|
||||
}
|
||||
)
|
||||
|
||||
local interfaceDerma = false
|
||||
|
||||
local TIME_TO_RESTART = 10
|
||||
local timeDown
|
||||
local apiState
|
||||
local previouslyShown = false
|
||||
|
||||
-- Colors
|
||||
primaryCol = Color( 36, 41, 67 )
|
||||
secondaryCol = Color( 42, 47, 74 )
|
||||
accentCol = Color( 84, 84, 150 )
|
||||
|
||||
local function lerpColor(fraction, from, to)
|
||||
return Color(from.r + (to.r - from.r) * fraction,
|
||||
from.g + (to.g - from.g) * fraction,
|
||||
from.b + (to.b - from.b) * fraction)
|
||||
end
|
||||
|
||||
local function secondsAsTime(s)
|
||||
return string.FormattedTime( s, "%02i:%02i" )
|
||||
end
|
||||
|
||||
-- Delay Function
|
||||
local delayId = 0
|
||||
local function delaycall(time, callback)
|
||||
local wait = RealTime() + time
|
||||
delayId = delayId + 1
|
||||
local hookName = "cfc_di_delay_" .. delayId
|
||||
hook.Add("Tick", hookName, function()
|
||||
if RealTime() > wait then
|
||||
hook.Remove("Tick", hookName)
|
||||
callback()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local function rejoin()
|
||||
delaycall(1, function() -- gm_crashsys does this, I don't feel like going through finding out why, so I'm just gonna do the same :)
|
||||
RunConsoleCommand( "snd_restart" )
|
||||
RunConsoleCommand( "retry" )
|
||||
end)
|
||||
end
|
||||
|
||||
local function leave()
|
||||
delaycall(1, function()
|
||||
RunConsoleCommand( "disconnect" )
|
||||
end)
|
||||
end
|
||||
|
||||
local function addTitleBar(frame)
|
||||
local frameW, frameH = frame:GetSize()
|
||||
local titleBarHeight = 32
|
||||
local titleBar = vgui.Create( "DPanel", frame )
|
||||
titleBar:SetSize( frameW, titleBarHeight )
|
||||
titleBar:SetPos( 0, 0 )
|
||||
function titleBar:Paint(w, h)
|
||||
surface.SetDrawColor( secondaryCol )
|
||||
surface.DrawRect( 0, 0, w, h )
|
||||
end
|
||||
|
||||
local closeBtnPadding = (titleBarHeight - 16) / 2
|
||||
|
||||
local closeBtn = vgui.Create( "DImageButton", titleBar )
|
||||
closeBtn:SetSize( 16, 16 )
|
||||
closeBtn:SetPos( frameW - 16 - closeBtnPadding, closeBtnPadding)
|
||||
closeBtn:SetImage( "icon16/cross.png" )
|
||||
function closeBtn:DoClick()
|
||||
frame:Close()
|
||||
end
|
||||
|
||||
local titleLabelPadding = (titleBarHeight - 26) / 2
|
||||
|
||||
local titleLabel = vgui.Create( "DLabel", titleBar )
|
||||
titleLabel:SetFont( "CFC_Special" )
|
||||
titleLabel:SetText( "Oops! Looks like the server crashed..." )
|
||||
titleLabel:SizeToContents()
|
||||
titleLabel:SetPos( 0, titleLabelPadding + 2 )
|
||||
titleLabel:CenterHorizontal()
|
||||
|
||||
return titleBar
|
||||
end
|
||||
|
||||
local function makeButton(frame, text, xFraction, doClick, outlineCol, fillCol, hoverOutlineCol, hoverFillCol)
|
||||
outlineCol = outlineCol or Color( 255, 255, 255 )
|
||||
fillCol = fillCol or primaryCol
|
||||
hoverOutlineCol = hoverOutlineCol or Color(155,241,255)
|
||||
hoverFillCol = hoverFillCol or primaryCol
|
||||
|
||||
local frameW, frameH = frame:GetSize()
|
||||
local btn = vgui.Create( "DButton", frame )
|
||||
btn:SetText( text )
|
||||
btn:SetTextColor( Color( 255, 255, 255 ) )
|
||||
btn:SetFont( "CFC_Button" )
|
||||
btn:SetSize( frameW * 0.4, frameH * 0.6 )
|
||||
btn:CenterHorizontal( xFraction )
|
||||
btn:CenterVertical()
|
||||
btn.DoClick = doClick
|
||||
|
||||
btn.fadeState = 0
|
||||
btn.prevTime = CurTime()
|
||||
|
||||
local btnAnimSpeed = 0.05 * 60
|
||||
|
||||
function btn:Think()
|
||||
-- Make anim same speed for all framerates
|
||||
local dt = CurTime() - self.prevTime
|
||||
self.prevTime = CurTime()
|
||||
if dt > 1 then dt = 0 end
|
||||
|
||||
if self:IsHovered() and self.fadeState < 1 then
|
||||
self.fadeState = math.Clamp(self.fadeState + btnAnimSpeed * dt, 0, 1)
|
||||
elseif not self:IsHovered() and self.fadeState > 0 then
|
||||
self.fadeState = math.Clamp(self.fadeState - btnAnimSpeed * dt, 0, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local btnBorderWeight = 2
|
||||
function btn:Paint(w, h)
|
||||
local lineCol
|
||||
local bgCol
|
||||
if self:GetDisabled() then
|
||||
lineCol = Color( 74, 74, 74 )
|
||||
bgCol = fillCol
|
||||
self:SetCursor( "no" )
|
||||
else
|
||||
lineCol = lerpColor(self.fadeState, outlineCol, hoverOutlineCol)
|
||||
bgCol = lerpColor(self.fadeState, fillCol, hoverFillCol)
|
||||
self:SetCursor( "hand" )
|
||||
end
|
||||
|
||||
self:SetTextColor( lineCol )
|
||||
|
||||
surface.SetDrawColor( lineCol )
|
||||
surface.DrawRect( 0, 0, w, h )
|
||||
surface.SetDrawColor( bgCol )
|
||||
surface.DrawRect( btnBorderWeight, btnBorderWeight,
|
||||
w - (btnBorderWeight*2), h - (btnBorderWeight*2) )
|
||||
end
|
||||
|
||||
return btn
|
||||
end
|
||||
|
||||
local function addButtonsBar(frame)
|
||||
local frameW, frameH = frame:GetSize()
|
||||
|
||||
local buttonBarHeight = 64
|
||||
|
||||
local barPanel = vgui.Create( "DPanel", frame )
|
||||
barPanel:SetSize( frameW, buttonBarHeight )
|
||||
barPanel:SetPos( 0, frameH - buttonBarHeight )
|
||||
function barPanel:Paint(w, h)
|
||||
surface.SetDrawColor( accentCol )
|
||||
surface.DrawLine( 16, 0, w - 16, 0 )
|
||||
end
|
||||
|
||||
barPanel.reconBtn = makeButton(barPanel, "RECONNECT", 0.25, rejoin,
|
||||
Color( 74, 251, 191 ), nil, Color( 74, 251, 191 ), Color( 64, 141, 131 ))
|
||||
barPanel.reconBtn:SetDisabled( true )
|
||||
barPanel.disconBtn = makeButton(barPanel, "DISCONNECT", 0.75, leave)
|
||||
|
||||
return barPanel
|
||||
end
|
||||
|
||||
local function makeLabel(frame, text, top, col, xFraction)
|
||||
col = col or Color( 255, 255, 255 )
|
||||
local label = vgui.Create( "DLabel", frame )
|
||||
label:SetText( text )
|
||||
label:SetFont( "CFC_Special" )
|
||||
label:SizeToContents()
|
||||
label:SetPos( 0, top )
|
||||
label:SetTextColor( col )
|
||||
label:CenterHorizontal( xFraction )
|
||||
return label
|
||||
end
|
||||
|
||||
local function populateBodyInternetDown(body)
|
||||
local label1 = makeLabel(body, "Looks like your internet has gone down!", 20)
|
||||
local label2 = makeLabel(body, "Stick around for when it comes back", 64)
|
||||
end
|
||||
|
||||
local function populateBodyServerDown(body)
|
||||
|
||||
local frameW, frameH = body:GetSize()
|
||||
local restartTimeStr = "The server normally takes about " .. secondsAsTime(TIME_TO_RESTART) .. " to restart!"
|
||||
local restartTimeLabel = makeLabel(body, restartTimeStr, 0)
|
||||
local curTimePreLabel = makeLabel(body, "It has been down for", 32)
|
||||
function curTimePreLabel:Think()
|
||||
if apiState == crashApi.SERVER_UP and not self.backUp then
|
||||
self:SetText( "It was down for" )
|
||||
self:SizeToContents()
|
||||
self:CenterHorizontal()
|
||||
self.backUp = true
|
||||
end
|
||||
end
|
||||
|
||||
local tooLongLabel = makeLabel(body, "Uh oh, seems it's taking a little longer than usual!", 70, Color( 251, 191, 83 ), 0.8)
|
||||
tooLongLabel:SetAlpha(0)
|
||||
tooLongLabel:Hide()
|
||||
|
||||
local curTimeLabel = makeLabel(body, secondsAsTime(math.floor(timeDown)), 70, Color( 251, 191, 83 ))
|
||||
function curTimeLabel:Think()
|
||||
if apiState ~= crashApi.SERVER_UP then
|
||||
self:SetText(secondsAsTime(math.floor(timeDown)))
|
||||
if timeDown > TIME_TO_RESTART then
|
||||
self:SetTextColor(Color(255, 0, 0))
|
||||
if not tooLongLabel:IsVisible() then
|
||||
tooLongLabel:Show()
|
||||
tooLongLabel:AlphaTo(255, 1)
|
||||
end
|
||||
end
|
||||
else
|
||||
self:SetTextColor(Color(0, 255, 0))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function populateBody(body)
|
||||
body.Paint = nil
|
||||
if apiState == crashApi.NO_INTERNET then
|
||||
populateBodyInternetDown(body)
|
||||
else -- Server down or up via api, and down via net
|
||||
populateBodyServerDown(body)
|
||||
end
|
||||
|
||||
local frameW, frameH = 0.8 * ScrW(), 0.8 * ScrH()
|
||||
|
||||
local playGameLabel = makeLabel(body, "Why not play a game while you wait? (Press space)", 108)
|
||||
|
||||
local gamePanel = vgui.Create( "DPanel", body )
|
||||
gamePanel:SetSize( frameW - 20, frameH - 134 - 15 )
|
||||
gamePanel:SetPos( -6, 134 + 10 )
|
||||
gamePanel.Paint = nil
|
||||
|
||||
local gameHtml = vgui.Create( "DHTML", gamePanel )
|
||||
gameHtml:Dock( FILL )
|
||||
gameHtml:OpenURL("https://cdn.cfcservers.org/media/dinosaur/index.html")
|
||||
function gameHtml:Think()
|
||||
if not gameHtml:HasFocus() then gameHtml:RequestFocus() end
|
||||
end
|
||||
end
|
||||
|
||||
local function createInterface()
|
||||
|
||||
local frameW, frameH = 0.8 * ScrW(), 0.8 * ScrH()
|
||||
|
||||
local startTime = SysTime()
|
||||
|
||||
local frame = vgui.Create( "DFrame" )
|
||||
interfaceDerma = frame
|
||||
frame:SetSize( frameW, frameH )
|
||||
frame:Center()
|
||||
frame:SetTitle("")
|
||||
frame:SetDraggable( false )
|
||||
frame:MakePopup()
|
||||
frame:ShowCloseButton( false )
|
||||
|
||||
function frame:Paint(w, h)
|
||||
Derma_DrawBackgroundBlur(self, startTime)
|
||||
surface.SetDrawColor( primaryCol )
|
||||
surface.DrawRect( 0, 0, w, h )
|
||||
end
|
||||
|
||||
local titlePanel = addTitleBar(frame)
|
||||
local btnsPanel = addButtonsBar(frame)
|
||||
|
||||
local body = vgui.Create( "DPanel", frame )
|
||||
body:SetSize(frameW - 32, frameH - 32 - titlePanel:GetTall() - btnsPanel:GetTall())
|
||||
body:SetPos(16, titlePanel:GetTall() + 16)
|
||||
populateBody(body)
|
||||
|
||||
function frame:Think()
|
||||
if apiState == crashApi.INACTIVE then
|
||||
frame:Close() -- Server recovered without ever closing
|
||||
elseif apiState == crashApi.SERVER_UP then
|
||||
if btnsPanel.reconBtn:GetDisabled() == true then
|
||||
btnsPanel.reconBtn:SetDisabled( false ) -- Server back up
|
||||
-- Maybe show a "The server is back up, click here to reconnect?"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function frame:OnClose()
|
||||
interfaceDerma = nil
|
||||
end
|
||||
end
|
||||
|
||||
hook.Add("cfc_di_crashTick", "cfc_di_interfaceUpdate", function(isCrashing, _timeDown, _apiState)
|
||||
timeDown = _timeDown or 0
|
||||
apiState = _apiState
|
||||
if isCrashing and apiState ~= crashApi.PINGING_API and not interfaceDerma and not previouslyShown then
|
||||
createInterface()
|
||||
previouslyShown = true
|
||||
end
|
||||
if not isCrashing then
|
||||
previouslyShown = false
|
||||
if interfaceDerma then
|
||||
interfaceDerma:Close()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
65
lua/cfc_disconnect_interface/client/cl_ponger.lua
Normal file
65
lua/cfc_disconnect_interface/client/cl_ponger.lua
Normal file
@ -0,0 +1,65 @@
|
||||
include("cfc_disconnect_interface/client/cl_api.lua")
|
||||
|
||||
local GRACE_TIME = 3.5 -- How many seconds of lag should we have before showing the panel?
|
||||
local PING_MISS = 2 -- How many pings can we miss on join?
|
||||
|
||||
local API_TIMEOUT = 15 -- How often to call the api
|
||||
|
||||
local lastPing
|
||||
local lastApiCall
|
||||
|
||||
net.Receive("cfc_di_ping", function()
|
||||
if PING_MISS > 0 then -- Allow some pings before actually starting crash systems. (Avoid bugs on join stutter.)
|
||||
PING_MISS = PING_MISS - 1
|
||||
else
|
||||
lastPong = RealTime()
|
||||
end
|
||||
end)
|
||||
|
||||
local function shutdown()
|
||||
timer.Remove("cfc_di_startup")
|
||||
hook.Remove("Tick", "cfc_di_tick")
|
||||
end
|
||||
|
||||
net.Receive("cfc_di_shutdown", shutdown)
|
||||
hook.Add("ShutDown", "crashsys", shutdown)
|
||||
|
||||
local function crashTick(timedown)
|
||||
local apiState = crashApi.getState();
|
||||
if (apiState == crashApi.INACTIVE) or -- No ping sent
|
||||
(apiState ~= crashApi.SERVER_UP and RealTime() - lastApiCall > API_TIMEOUT) then -- Previous ping failed, and API_TIMEOUT has passed
|
||||
crashApi.triggerPing();
|
||||
lastApiCall = RealTime();
|
||||
|
||||
apiState = crashApi.getState();
|
||||
end
|
||||
hook.Run("cfc_di_crashTick", true, timedown, apiState);
|
||||
end
|
||||
|
||||
local function checkCrashTick()
|
||||
if not lastPong then return end
|
||||
if not LocalPlayer():IsValid() then return end -- disconnected or connecting
|
||||
|
||||
local timeout = RealTime() - lastPong
|
||||
|
||||
if timeout > GRACE_TIME then
|
||||
crashTick(timeout)
|
||||
else
|
||||
if crashApi.getState() ~= crashApi.INACTIVE then
|
||||
crashApi.cancelPing();
|
||||
end
|
||||
hook.Run("cfc_di_crashTick", false);
|
||||
end
|
||||
end
|
||||
|
||||
-- Ping the server when the client is ready.
|
||||
timer.Create("cfc_di_startup", 0.01, 0, function()
|
||||
local ply = LocalPlayer()
|
||||
if ply:IsValid() then
|
||||
net.Start("cfc_di_loaded")
|
||||
net.SendToServer()
|
||||
timer.Remove("cfc_di_startup")
|
||||
print("cfc_disconnect_interface loaded.")
|
||||
hook.Add("Tick", "cfc_di_tick", checkCrashTick)
|
||||
end
|
||||
end)
|
30
lua/cfc_disconnect_interface/server/sv_pinger.lua
Normal file
30
lua/cfc_disconnect_interface/server/sv_pinger.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local PING_TIME = 3
|
||||
|
||||
local players = {}
|
||||
local table = table
|
||||
|
||||
local function ping(ply)
|
||||
net.Start("cfc_di_ping")
|
||||
net.Send(ply or players)
|
||||
end
|
||||
|
||||
net.Receive("cfc_di_loaded", function(len, ply)
|
||||
if not IsValid(ply) then return end
|
||||
if not table.HasValue(players, ply) then
|
||||
table.insert(players, ply)
|
||||
end
|
||||
end)
|
||||
|
||||
hook.Add("PlayerDisconnected", "crashsys", function(ply)
|
||||
ping(ply)
|
||||
table.RemoveByValue(players, ply)
|
||||
end)
|
||||
|
||||
timer.Create("cfc_di_pingTimer", PING_TIME, 0, function()
|
||||
ping()
|
||||
end)
|
||||
|
||||
hook.Add("ShutDown", "cfc_di", function()
|
||||
net.Start("cfc_di_shutdown")
|
||||
net.Send(players)
|
||||
end)
|
BIN
resource/fonts/coolvetica.ttf
Normal file
BIN
resource/fonts/coolvetica.ttf
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user