--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] include( "background.lua" ) include( "cef_credits.lua" ) include( "crosshair_setup.lua" ) include( "openurl.lua" ) include( "ugcpublish.lua" ) pnlMainMenu = nil local pnlMainMenuFallback = nil local PANEL = {} function PANEL:Init() self:Dock( FILL ) self:SetKeyboardInputEnabled( true ) self:SetMouseInputEnabled( true ) self.HTML = vgui.Create( "DHTML", self ) JS_Language( self.HTML ) JS_Utility( self.HTML ) JS_Workshop( self.HTML ) -- Detect whenther the HTML engine is even there self.menuLoaded = false self.HTML.OnBeginLoadingDocument = function() self.menuLoaded = true if ( IsValid( pnlMainMenuFallback ) ) then pnlMainMenuFallback:Remove() end end self.HTML:Dock( FILL ) self.HTML:OpenURL( "asset://garrysmod/html/menu.html" ) self.HTML:SetKeyboardInputEnabled( true ) self.HTML:SetMouseInputEnabled( true ) self.HTML:SetAllowLua( true ) self.HTML:RequestFocus() ws_dupe.HTML = self.HTML ws_save.HTML = self.HTML addon.HTML = self.HTML demo.HTML = self.HTML self:MakePopup() self:SetPopupStayAtBack( true ) -- If the console is already open, we've got in its way. if ( gui.IsConsoleVisible() ) then gui.ShowConsole() end end function PANEL:ScreenshotScan( folder ) local bReturn = false local Screenshots = file.Find( folder .. "*.*", "GAME" ) for k, v in RandomPairs( Screenshots ) do AddBackgroundImage( folder .. v ) bReturn = true end return bReturn end function PANEL:Paint() DrawBackground() if ( self.IsInGame != IsInGame() ) then self.IsInGame = IsInGame() if ( self.IsInGame ) then if ( IsValid( self.InnerPanel ) ) then self.InnerPanel:Remove() end self:Call( "SetInGame( true )" ) else self:Call( "SetInGame( false )" ) end end if ( !self.IsInGame ) then return end local canAdd = CanAddServerToFavorites() local isFav = serverlist.IsCurrentServerFavorite() if ( self.CanAddServerToFavorites != canAdd or self.IsCurrentServerFavorite != isFav ) then self.CanAddServerToFavorites = canAdd self.IsCurrentServerFavorite = isFav self:Call( "SetShowFavButton( " .. tostring( self.CanAddServerToFavorites ) .. ", " .. tostring( self.IsCurrentServerFavorite ) .. " )" ) end end function PANEL:RefreshContent() self:RefreshGamemodes() self:RefreshAddons() end function PANEL:RefreshGamemodes() local json = util.TableToJSON( engine.GetGamemodes() ) self:Call( "UpdateGamemodes( " .. json .. " )" ) self:UpdateBackgroundImages() self:Call( "UpdateCurrentGamemode( '" .. engine.ActiveGamemode():JavascriptSafe() .. "' )" ) end function PANEL:RefreshAddons() -- TODO end function PANEL:SetProblemCount( problems, severity ) self:Call( "SetProblemCount(" .. problems .. ", " .. severity .. ")" ) end function PANEL:UpdateBackgroundImages() ClearBackgroundImages() -- -- If there's screenshots in gamemodes//backgrounds/*.jpg use them -- if ( !self:ScreenshotScan( "gamemodes/" .. engine.ActiveGamemode() .. "/backgrounds/" ) ) then -- -- If there's no gamemode specific here we'll use the default backgrounds -- self:ScreenshotScan( "backgrounds/" ) end ChangeBackground( engine.ActiveGamemode() ) end function PANEL:Call( js ) self.HTML:QueueJavascript( js ) end vgui.Register( "MainMenuPanel", PANEL, "EditablePanel" ) local PANEL = {} function PANEL:SetText( txt ) self.Text = txt end function PANEL:Paint( w, h ) -- Draw the text local parsed = markup.Parse( self.Text, self:GetParent():GetWide() ) parsed:Draw( 0, 0 ) -- Size to contents. Ew. self:SetSize( parsed:GetWidth(), parsed:GetHeight() ) end -- TODO: Maybe this panel belongs in client realm as well? local markupPanel = vgui.RegisterTable( PANEL, "Panel" ) function OnMenuFailedToLoad() local frame = vgui.Create( "DFrame" ) frame:SetSize( ScrW() / 2, ScrH() / 2 ) frame:Center() frame:SetDraggable( false ) frame:ShowCloseButton( false ) frame:SetTitle( "Menu failed to load" ) frame:MakePopup() pnlMainMenuFallback = frame local lbl = vgui.CreateFromTable( markupPanel, frame ) lbl:Dock( TOP ) lbl:DockMargin( 0, 0, 0, 5 ) lbl:SetText( "Looks like the main menu failed to load.\n\nThis could be due to missing game files (run verification of game file integrity through Steam), or the HTML engine failed to load.\n\nBelow are some simple options to exit the game." ) local btn_srv = frame:Add( "DButton" ) btn_srv:Dock( TOP ) btn_srv:DockMargin( 0, 0, 0, 5 ) btn_srv:SetText( "Open legacy server browser" ) btn_srv:SetConsoleCommand( "gamemenucommand", "OpenServerBrowser" ) local btn_opt = frame:Add( "DButton" ) btn_opt:Dock( TOP ) btn_opt:DockMargin( 0, 0, 0, 5 ) btn_opt:SetText( "Open Settings" ) btn_opt:SetConsoleCommand( "gamemenucommand", "OpenOptionsDialog" ) local btn_exit = frame:Add( "DButton" ) btn_exit:Dock( TOP ) btn_exit:DockMargin( 0, 0, 0, 5 ) btn_exit:SetText( "Exit the game" ) btn_exit.DoClick = function() RunGameUICommand( "quit" ) end end -- -- Called from JS when starting a new game -- function UpdateServerSettings() local array = { hostname = GetConVarString( "hostname" ), sv_lan = GetConVarString( "sv_lan" ), p2p_enabled = GetConVarString( "p2p_enabled" ) } local settings_file = file.Read( "gamemodes/" .. engine.ActiveGamemode() .. "/" .. engine.ActiveGamemode() .. ".txt", true ) if ( settings_file ) then local Settings = util.KeyValuesToTable( settings_file ) if ( istable( Settings.settings ) ) then array.settings = {} for k, v in pairs( Settings.settings ) do local cvar = GetConVar( v.name ) if ( !cvar ) then continue end array.settings[ k ] = v array.settings[ k ].Value = cvar:GetString() array.settings[ k ].Singleplayer = v.singleplayer and true or false end end end local json = util.TableToJSON( array ) pnlMainMenu:Call( "UpdateServerSettings(" .. json .. ")" ) end -- -- Get the player list for this server -- function GetPlayerList( serverip ) serverlist.PlayerList( serverip, function( tbl ) local json = util.TableToJSON( tbl ) pnlMainMenu:Call( "SetPlayerList( '" .. serverip:JavascriptSafe() .. "', " .. json .. ")" ) end ) end local BlackList = { Addresses = {}, Hostnames = {}, Descripts = {}, Gamemodes = {}, Maps = {}, } local NewsList = {} GetAPIManifest( function( result ) result = util.JSONToTable( result ) if ( !result ) then return end NewsList = result.News and result.News.Blogs or {} LoadNewsList() for k, v in pairs( result.Servers and result.Servers.Banned or {} ) do if ( v:StartsWith( "map:" ) ) then table.insert( BlackList.Maps, v:sub( 5 ) ) elseif ( v:StartsWith( "host:" ) or v:StartsWith( "name:" ) ) then table.insert( BlackList.Hostnames, v:sub( 6 ) ) elseif ( v:StartsWith( "desc:" ) ) then table.insert( BlackList.Descripts, v:sub( 6 ) ) elseif ( v:StartsWith( "gm:" ) ) then table.insert( BlackList.Gamemodes, v:sub( 4 ) ) else table.insert( BlackList.Addresses, v ) end end end ) function LoadNewsList() if ( !pnlMainMenu ) then return end local json = util.TableToJSON( NewsList ) local bHide = cookie.GetString( "hide_newslist", "false" ) == "true" pnlMainMenu:Call( "UpdateNewsList(" .. json .. ", " .. tostring( bHide ) .. " )" ) end function SaveHideNews( bHide ) cookie.Set( "hide_newslist", tostring( bHide ) ) end function IsServerBlacklisted( address, hostname, description, gm, map ) local addressNoPort = address:match( "[^:]*" ) for k, v in ipairs( BlackList.Addresses ) do if ( address == v or addressNoPort == v ) then return v end if ( v:EndsWith( "*" ) and address:sub( 1, v:len() - 1 ) == v:sub( 1, v:len() - 1 ) ) then return v end -- IP Ranges if ( string.find( v, "/", 1, false ) ) then local o1, o2, o3, o4, o5 = string.match( v, "(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)/(%d%d?)" ) local blacklistedIP = 2 ^ 24 * o1 + 2 ^ 16 * o2 + 2 ^ 8 * o3 + o4 local mask = bit.lshift( 0xFFFFFFFF, 32-o5 ) o1, o2, o3, o4 = string.match( address, "(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" ) local testIP = 2 ^ 24 * o1 + 2 ^ 16 * o2 + 2 ^ 8 * o3 + o4 if ( bit.band( testIP, mask ) == bit.band( blacklistedIP, mask ) ) then return v end end end for k, v in ipairs( BlackList.Hostnames ) do if ( string.match( hostname, v ) or string.match( hostname:lower(), v ) ) then return "host: " .. v end end for k, v in ipairs( BlackList.Descripts ) do if ( string.match( description, v ) or string.match( description:lower(), v ) ) then return "desc: " .. v end end for k, v in ipairs( BlackList.Gamemodes ) do if ( string.match( gm, v ) or string.match( gm:lower(), v ) ) then return "gm: " .. v end end for k, v in ipairs( BlackList.Maps ) do if ( string.match( map, v ) or string.match( map:lower(), v ) ) then return "map: " .. v end end return nil end local Servers = {} local ShouldStop = {} local function SendServer( pnlMainMenu, category, id, ping, name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gm, workshopid, isAnon, netVersion, luaVersion, loc, gmcat ) name = string.JavascriptSafe( name ) desc = string.JavascriptSafe( desc ) map = string.JavascriptSafe( map ) address = string.JavascriptSafe( address ) gm = string.JavascriptSafe( gm ) workshopid = string.JavascriptSafe( workshopid ) netVersion = string.JavascriptSafe( tostring( netVersion ) ) loc = string.JavascriptSafe( loc ) gmcat = string.JavascriptSafe( gmcat ) pnlMainMenu:Call( string.format( [[AddServer( "%s", "%s", %i, "%s", "%s", "%s", %i, %i, %i, %s, %i, "%s", "%s", "%s", %s, "%s", "%s", "%s" , "%s" );]], category, id, ping, name, desc, map, players, maxplayers, botplayers, tostring( pass ), lastplayed, address, gm, workshopid, tostring( isAnon ), netVersion, tostring( serverlist.IsServerFavorite( address ) ), loc, gmcat ) ) end function GetServers( category, id ) category = string.JavascriptSafe( category ) id = string.JavascriptSafe( id ) ShouldStop[ category ] = false Servers[ category ] = {} local data = { Callback = function( ping, name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gm, workshopid, isAnon, netVersion, luaVersion, loc, gmcat ) if ( Servers[ category ] and Servers[ category ][ address ] ) then print( "Server Browser Error!", address, category ) return end Servers[ category ][ address ] = true local blackListMatch = IsServerBlacklisted( address, name, desc, gm, map ) if ( blackListMatch == nil ) then SendServer( pnlMainMenu, category, id, ping, name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gm, workshopid, isAnon, netVersion, luaVersion, loc, gmcat ) else Msg( "Ignoring server '", name, "' @ ", address, " - ", blackListMatch, " is blacklisted\n" ) end return !ShouldStop[ category ] end, CallbackFailed = function( address ) if ( Servers[ category ] and Servers[ category ][ address ] ) then print( "Server Browser Error!", address, category ) return end Servers[ category ][ address ] = true local version = string.JavascriptSafe( tostring( VERSION ) ) SendServer( pnlMainMenu, category, id, 2000, "The server at address " .. address .. " failed to respond", "Unreachable Servers", "no_map", 0, 2, 0, "false", 0, address, "unkn", "0", "true", version, tostring( serverlist.IsServerFavorite( address ) ), "", "" ) return !ShouldStop[ category ] end, Finished = function() pnlMainMenu:Call( "FinishedServeres( '" .. category:JavascriptSafe() .. "' )" ) Servers[ category ] = {} end, Type = category, GameDir = "garrysmod", AppID = 4000, } serverlist.Query( data ) end function DoStopServers( category ) pnlMainMenu:Call( "FinishedServeres( '" .. category:JavascriptSafe() .. "' )" ) ShouldStop[ category ] = true Servers[ category ] = {} end function PingServer( srvAddress ) serverlist.PingServer( srvAddress, function( ping, name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gm, workshopid, isAnon, netVersion, luaVersion, loc, gmcat ) if ( !name ) then return end name = string.JavascriptSafe( name ) map = string.JavascriptSafe( map ) address = string.JavascriptSafe( address ) pnlMainMenu:Call( string.format( [[UpdateServer( "%s", %i, "%s", "%s", %i, %i, %i, %s );]], address, ping, name, map, players, maxplayers, botplayers, tostring( pass ) ) ) end ) end function FindServersAtAddress( inputStr ) local hasPort = string.find( inputStr, ":", 0, true ) local addresses = {} if ( hasPort ) then table.insert( addresses, inputStr ) else for i = 0, 5 do table.insert( addresses, inputStr .. ":" .. tostring( 27015 + i ) ) table.insert( addresses, inputStr .. ":" .. tostring( 26900 + i ) ) end end local output = {} for i, addr in ipairs( addresses ) do serverlist.PingServer( addr, function( ping, name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gm, ... ) if ( !name ) then table.insert( output, { name = "Server at " .. addr .. " did not respond", address = addr, ping = 2000, favorite = false, players = 0, maxplayers = 0, botplayers = 0, map = "", gamemode = "" } ) else name = string.JavascriptSafe( name ) map = string.JavascriptSafe( map ) address = string.JavascriptSafe( address ) gm = string.JavascriptSafe( gm ) table.insert( output, { address = address, name = name, ping = ping, map = map, gamemode = gm, favorite = serverlist.IsServerFavorite( address ), players = players, botplayers = botplayers, maxplayers = maxplayers, } ) end //if ( #output == #addresses ) then local json = util.TableToJSON( output ) pnlMainMenu:Call( "ReceiveFoundServers(" .. json .. ")" ) //end end ) end end -- -- Called from JS -- function UpdateLanguages() local f = file.Find( "resource/localization/*.png", "MOD" ) local json = util.TableToJSON( f ) pnlMainMenu:Call( "UpdateLanguages(" .. json .. ")" ) end -- -- Called from the engine any time the language changes -- function LanguageChanged( lang ) if ( !IsValid( pnlMainMenu ) ) then return end UpdateLanguages() pnlMainMenu:Call( "UpdateLanguage( \"" .. lang:JavascriptSafe() .. "\" )" ) end function UpdateGames() local games = engine.GetGames() local json = util.TableToJSON( games ) pnlMainMenu:Call( "UpdateGames( " .. json .. ")" ) end function UpdateSubscribedAddons() local subscriptions = engine.GetAddons() local json = util.TableToJSON( subscriptions ) pnlMainMenu:Call( "subscriptions.Update( " .. json .. " )" ) local UGCsubs = engine.GetUserContent() local jsonUGC = util.TableToJSON( UGCsubs ) pnlMainMenu:Call( "subscriptions.UpdateUGC( " .. jsonUGC .. " )" ) end function UpdateAddonDisabledState() local noaddons, noworkshop = GetAddonStatus() pnlMainMenu:Call( "UpdateAddonDisabledState( " .. tostring( noaddons ) .. ", " .. tostring( noworkshop ) .. " )" ) end function MenuGetAddonData( wsid ) steamworks.FileInfo( wsid, function( data ) local json = util.TableToJSON( data ) or "" pnlMainMenu:Call( "ReceivedChildAddonInfo( " .. json .. " )" ) end ) end local presetCache = {} local function EnsurePresetsLoaded() if ( table.IsEmpty( presetCache ) ) then presetCache = util.JSONToTable( LoadAddonPresets() or "", true, true ) or {} end end function CreateNewAddonPreset( json ) EnsurePresetsLoaded() local data = util.JSONToTable( json ) presetCache[ data.name ] = data SaveAddonPresets( util.TableToJSON( presetCache ) ) end function ImportAddonPreset( id, json ) EnsurePresetsLoaded() steamworks.FileInfo( id, function( fileInfo ) if ( !fileInfo.children or #fileInfo.children < 1 ) then pnlMainMenu:Call( "OnImportPresetFailed()" ) return end local data = util.JSONToTable( json ) presetCache[ data.name ] = data presetCache[ data.name ].enabled = fileInfo.children SaveAddonPresets( util.TableToJSON( presetCache ) ) ListAddonPresets() end ) end function DeleteAddonPreset( name ) EnsurePresetsLoaded() presetCache[ name ] = {} presetCache[ name ] = nil SaveAddonPresets( util.TableToJSON( presetCache ) ) ListAddonPresets() end function ListAddonPresets() EnsurePresetsLoaded() pnlMainMenu:Call( "OnReceivePresetList(" .. util.TableToJSON( presetCache ) .. ")" ) end -- Called when UGC subscription status changes hook.Add( "WorkshopSubscriptionsChanged", "WorkshopSubscriptionsChanged", function( msg ) UpdateSubscribedAddons() end ) hook.Add( "GameContentChanged", "RefreshMainMenu", function() if ( !IsValid( pnlMainMenu ) ) then return end pnlMainMenu:RefreshContent() UpdateGames() UpdateServerSettings() UpdateSubscribedAddons() end ) hook.Add( "LoadGModSaveFailed", "LoadGModSaveFailed", function( str, wsid ) local button2 = nil if ( wsid and wsid:len() > 0 and wsid != "0" ) then button2 = "Open map on Steam Workshop" end Derma_Query( str, "Failed to load save!", "OK", nil, button2, function() steamworks.ViewFile( wsid ) end ) gui.ActivateGameUI() end ) -- -- Initialize -- timer.Simple( 0, function() pnlMainMenu = vgui.Create( "MainMenuPanel" ) pnlMainMenu:Call( "UpdateVersion( '" .. VERSIONSTR:JavascriptSafe() .. "', '" .. NETVERSIONSTR:JavascriptSafe() .. "', '" .. BRANCH:JavascriptSafe() .. "' )" ) local lang = GetConVarString( "gmod_language" ) LanguageChanged( lang ) hook.Run( "GameContentChanged" ) if ( !file.Exists( "html/menu.html", "MOD" ) ) then OnMenuFailedToLoad() end timer.Simple( 5, function() if ( !pnlMainMenu.menuLoaded ) then OnMenuFailedToLoad() end end ) end )