Initial commit

This commit is contained in:
Samuel Williams 2019-12-15 03:14:30 +00:00
parent 6f71e2e1aa
commit d93dbacee2
8 changed files with 520 additions and 1 deletions

View File

@ -1 +0,0 @@
RunConsoleCommand("cl_timeout '240'")

View 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")

View 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")

View 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

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

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

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

Binary file not shown.