From d93dbacee20cde9ab70807be2a7126a785994223 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sun, 15 Dec 2019 03:14:30 +0000 Subject: [PATCH] Initial commit --- .../client/cl_disconnect_interface.lua | 1 - lua/autorun/client/cl_init.lua | 3 + lua/autorun/server/sv_init.lua | 9 + .../client/cl_api.lua | 86 +++++ .../client/cl_interface.lua | 327 ++++++++++++++++++ .../client/cl_ponger.lua | 65 ++++ .../server/sv_pinger.lua | 30 ++ resource/fonts/coolvetica.ttf | Bin 0 -> 42264 bytes 8 files changed, 520 insertions(+), 1 deletion(-) delete mode 100644 lua/autorun/client/cl_disconnect_interface.lua create mode 100644 lua/autorun/client/cl_init.lua create mode 100644 lua/autorun/server/sv_init.lua create mode 100644 lua/cfc_disconnect_interface/client/cl_api.lua create mode 100644 lua/cfc_disconnect_interface/client/cl_interface.lua create mode 100644 lua/cfc_disconnect_interface/client/cl_ponger.lua create mode 100644 lua/cfc_disconnect_interface/server/sv_pinger.lua create mode 100644 resource/fonts/coolvetica.ttf diff --git a/lua/autorun/client/cl_disconnect_interface.lua b/lua/autorun/client/cl_disconnect_interface.lua deleted file mode 100644 index 27d2345..0000000 --- a/lua/autorun/client/cl_disconnect_interface.lua +++ /dev/null @@ -1 +0,0 @@ -RunConsoleCommand("cl_timeout '240'") diff --git a/lua/autorun/client/cl_init.lua b/lua/autorun/client/cl_init.lua new file mode 100644 index 0000000..9f9fdec --- /dev/null +++ b/lua/autorun/client/cl_init.lua @@ -0,0 +1,3 @@ +RunConsoleCommand("cl_timeout '240'") +include("cfc_disconnect_interface/client/cl_ponger.lua") +include("cfc_disconnect_interface/client/cl_interface.lua") \ No newline at end of file diff --git a/lua/autorun/server/sv_init.lua b/lua/autorun/server/sv_init.lua new file mode 100644 index 0000000..69fa389 --- /dev/null +++ b/lua/autorun/server/sv_init.lua @@ -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") \ No newline at end of file diff --git a/lua/cfc_disconnect_interface/client/cl_api.lua b/lua/cfc_disconnect_interface/client/cl_api.lua new file mode 100644 index 0000000..12828f7 --- /dev/null +++ b/lua/cfc_disconnect_interface/client/cl_api.lua @@ -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 diff --git a/lua/cfc_disconnect_interface/client/cl_interface.lua b/lua/cfc_disconnect_interface/client/cl_interface.lua new file mode 100644 index 0000000..07c639e --- /dev/null +++ b/lua/cfc_disconnect_interface/client/cl_interface.lua @@ -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) + diff --git a/lua/cfc_disconnect_interface/client/cl_ponger.lua b/lua/cfc_disconnect_interface/client/cl_ponger.lua new file mode 100644 index 0000000..6ff1702 --- /dev/null +++ b/lua/cfc_disconnect_interface/client/cl_ponger.lua @@ -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) diff --git a/lua/cfc_disconnect_interface/server/sv_pinger.lua b/lua/cfc_disconnect_interface/server/sv_pinger.lua new file mode 100644 index 0000000..8eb8547 --- /dev/null +++ b/lua/cfc_disconnect_interface/server/sv_pinger.lua @@ -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) \ No newline at end of file diff --git a/resource/fonts/coolvetica.ttf b/resource/fonts/coolvetica.ttf new file mode 100644 index 0000000000000000000000000000000000000000..efe72cc19a2b97a719d5cb1f92c44d669a10152f GIT binary patch literal 42264 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