Full Code of robotboy655/gmod-lua-menu for AI

master aa5d534a05d3 cached
12 files
103.7 KB
33.4k tokens
1 requests
Download .txt
Repository: robotboy655/gmod-lua-menu
Branch: master
Commit: aa5d534a05d3
Files: 12
Total size: 103.7 KB

Directory structure:
gitextract_c9uuf8d0/

├── CONTRIBUTING.md
├── README.md
└── lua/
    └── menu/
        ├── custom/
        │   ├── _errors.lua
        │   ├── achievements.lua
        │   ├── addons.lua
        │   ├── getmaps.lua
        │   ├── main.lua
        │   ├── mainmenu.lua
        │   ├── new_game.lua
        │   ├── new_game_panels.lua
        │   └── saves.lua
        └── menu.lua

================================================
FILE CONTENTS
================================================

================================================
FILE: CONTRIBUTING.md
================================================
Contributing to Lua Main Menu
=============

Here's what you need to know if you wish to submit Pull Requests to this repository.

Code Formatting
=============

Your code formatting must be consistent with the rest of the code:
* Use tabulation to indent your code - TAB size = 4 spaces
* Use all of the C style Lua in Garry's Mod
* Use UpperCamelCase for function names
* Use lowerCamelCase for variable names
* Do not include variable type in variable names

Examples
=============

These examples are of CODE FORMATTING, not examples of GOOD CODE.

Good:
```
local myTable = {
	meem = "no",
	test = true,
	foo = 1,
	bar = "yes"
}

if ( type( myTable ) != "table" ) then error( "bad" ) end

function Test( myVariable1, myVariable2 )
	if ( !myVariable2 ) then return "hax" end

	if ( myTable[ myVariable1 ] ) then
		return myTable[ myVariable1 ]
	end

	return myVariable2
end
```

Bad:
```
local myTable =
{
 meem =			"no",
  test =true,
   foo= 1,
    bar				= "yes"
}

if type(myTable) ~= "table" then error "bad" end

function Test( myVariable1, myVariable2 )
 if not myVariable2 then return "hax" end

 if myTable[myVariable1] then
  return myTable[myVariable1]
		end

 return myVariable2
end
```

================================================
FILE: README.md
================================================
Garry's Mod Lua Main Menu
=============

A Lua powered ( No HTML ) main menu for Garry's Mod.
It is meant for those who do not have main menu in Garry's Mod by default.
Note that this is a personal project, and it is not going to be included into Garry's Mod.
It does not have some features that I don't use.
Some other features that are not part of the standard menu might be added in the future.

Missing/Broken Features
=============

* Server browser - This menu uses the default Source Engine server browser
* Limited Demos and Saves functionality - No workshop, sorting or filtering
* Good looks
* You can't browse through new/top rated/ect addons in main menu. You should use the Open Workshop button anyway.

New/Fixed Features
=============

* I think it's faster then the default one
* Achievements menu
* More functionality for New Game and Addons menus
* No HTML

Installing
=============

To install this, download the ZIP and extract contents of ```gmod-lua-menu-master``` folder ( folders ```lua``` and ```materials``` ) to your ```SteamApps/common/GarrysMod/garrysmod/``` folder.

Uninstalling
=============

To uninstall this, open ```SteamApps/common/GarrysMod/garrysmod/lua/menu/menu.lua``` and follow instructions inside.
Alternatively, you can simply verify game cache integrity of Garry's Mod and the custom menu will be gone.


================================================
FILE: lua/menu/custom/_errors.lua
================================================

local Errors = {}

local matAlert = Material( "icon16/error.png" )

hook.Add( "DrawOverlay", "MenuErrors", function()

	if ( GetConVarNumber( "mat_dxlevel" ) < 90 ) then
		table.insert( Errors, {
			last	= SysTime(),
			text	= "mat_dxlevel is less than 90!"
		} )
	end

	if ( GetConVarNumber( "lookstrafe" ) >= 1 or GetConVarNumber( "lookstrafe" ) <= -1 ) then
		table.insert( Errors, {
			last	= SysTime(),
			text	= "Console varible \"lookstrafe\" is not 0, expect movement oddities!"
		} )
	end

	if ( table.Count( Errors ) == 0 ) then return end

	local idealy = 32
	local height = 30
	local Recent = SysTime() - 0.5

	for k, v in SortedPairsByMemberValue( Errors, "last" ) do

		surface.SetFont( "DermaDefaultBold" )
		if ( v.y == nil ) then v.y = idealy end
		if ( v.w == nil ) then v.w = surface.GetTextSize( v.text ) + 44 end

		local tw, th = surface.GetTextSize( v.text )
		v.x = ScrW() - tw - 76

		draw.RoundedBox( 2, v.x + 2, v.y + 2, v.w, height, Color( 40, 40, 40, 255 ) )
		draw.RoundedBox( 2, v.x, v.y, v.w, height, Color( 240, 240, 240, 255 ) )

		if ( v.last > Recent ) then

			draw.RoundedBox( 2, v.x, v.y, v.w, height, Color( 255, 200, 0, ( v.last - Recent ) * 510 ) )

		end

		surface.SetTextColor( 90, 90, 90, 255 )
		surface.SetTextPos( v.x + 30, v.y + 8 )
		surface.DrawText( v.text )

		surface.SetDrawColor( 255, 255, 255, 150 + math.sin( v.y + SysTime() * 30 ) * 100 )
		surface.SetMaterial( matAlert )
		surface.DrawTexturedRect( v.x + 6, v.y + 6, 16, 16 )

		v.y = idealy

		idealy = idealy + 40

		Errors[k] = nil

	end

end )


================================================
FILE: lua/menu/custom/achievements.lua
================================================

local PANEL = {}

function PANEL:Init()
	self.AchID = 0

	self:SetTall( 72 )

	self.Icon = vgui.Create( "AchievementIcon", self )
	self.Icon:SetPos( 4, 4 )
	self.Icon:SetSize( 64, 64 )
end

function PANEL:SetAchievementID( num )
	self.AchID = num
	self.Icon:SetAchievement( num )
end

function PANEL:Paint()
	local text_col = Color( 217, 217, 217 )
	if ( achievements.IsAchieved( self.AchID ) ) then
		draw.RoundedBox( 4, 0, 0, self:GetWide(), self:GetTall(), Color( 78, 78, 78 ) )
	else
		draw.RoundedBox( 4, 0, 0, self:GetWide(), self:GetTall(), Color( 52, 52, 52 ) )
		text_col = Color( 131, 131, 131 )
	end

	draw.SimpleText( achievements.GetName( self.AchID ), "Default", self:GetTall(), 4, text_col )
	draw.SimpleText( achievements.GetDesc( self.AchID ), "Default", self:GetTall(), 20, text_col )

	local goal = achievements.GetGoal( self.AchID )
	local count = achievements.GetCount( self.AchID )
	if ( goal > 1 ) then
		local text = count .. "/" .. goal

		surface.SetFont( "Default" )
		local h = 16

		draw.RoundedBox( 0, self:GetTall(), self:GetTall() - h - 4, self:GetWide() - self:GetTall() - 4, h, Color( 64, 64, 64, 255 ) )
		draw.RoundedBox( 0, self:GetTall(), self:GetTall() - h - 4, ( self:GetWide() - self:GetTall() - 4 ) * ( count / goal ), h, Color( 201, 185, 149, 255 ) )
		draw.SimpleText( text, "Default", self:GetWide() - surface.GetTextSize( text ) - 4, self:GetTall() - h * 2 - 4, text_col )
	end
end

vgui.Register( "RAchievement", PANEL, "Panel" )

language.Add( "rb655.achievement_viewer.total", "Total Achievements Earned" )

--------------------------------- --------------------------------- --------------------------------- ---------------------------------

local PANEL = {}

function PANEL:Init()

	self:Dock( FILL )

	--------------------------------- CATEGORIES ---------------------------------

	local frame = vgui.Create( "DPanel", self )
	self.frame = frame

	local achieved = 0
	local count = achievements.Count() - 1

	for achid = 1, count do
		if ( achievements.IsAchieved( achid ) ) then
			achieved = achieved + 1
		end
	end

	local ach_total = vgui.Create( "DPanel", frame )
	ach_total:Dock( TOP )
	ach_total:SetTall( 40 )
	ach_total:DockMargin( 5, 5, 5, 5 )
	function ach_total:Paint()
		draw.RoundedBox( 4, 0, 0, self:GetWide(), self:GetTall(), Color( 26, 26, 26, 255 ) )

		local text = achieved .. " / " .. count .. " ( " .. math.floor( ( achieved / count ) * 100 ) .. "% )"
		surface.SetFont( "Default" )
		local w = surface.GetTextSize( text ) + 4

		draw.SimpleText( "#rb655.achievement_viewer.total", "Default", 4, 4, Color( 217, 217, 217 ) )
		draw.SimpleText( text, "Default", self:GetWide() - w, 4, Color( 217, 217, 217 ) )

		draw.RoundedBox( 0, 4, 20, self:GetWide() - 8, 16, Color( 78, 78, 78 ) )
		draw.RoundedBox( 0, 4, 20, math.floor( ( achieved / count ) * self:GetWide() ) - 8, 16, Color( 158, 195, 79, 255 ) )
	end

	local ach_list = vgui.Create( "DPanelList", frame )
	ach_list:Dock( FILL )
	ach_list:DockMargin( 5, 0, 5, 5 )
	ach_list:SetSpacing( 5 )
	ach_list:SetPadding( 5 )
	ach_list:EnableHorizontal( false )
	ach_list:EnableVerticalScrollbar()
	function ach_list:Paint()
		draw.RoundedBox( 4, 0, 0, self:GetWide(), self:GetTall(), Color( 16, 16, 16, 255 ) )
	end

	for achid = 1, count do
		local ach = vgui.Create( "RAchievement", ach_list )
		ach:SetAchievementID( achid )
		ach_list:AddItem( ach )
	end

end

function PANEL:Paint( w, h )
	self.frame:SetSize( self:GetWide() / 2, self:GetTall() / 1.5 )
	self.frame:SetPos( self:GetWide() / 2 - self.frame:GetWide() / 2, self:GetTall() / 2 - self.frame:GetTall() / 2 )

	draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 150 ) )
end

vgui.Register( "AchievementsPanel", PANEL, "EditablePanel" )



================================================
FILE: lua/menu/custom/addons.lua
================================================

surface.CreateFont( "rb655_AddonName", {
	size = ScreenScale( 12 ),
	font = "Tahoma"
} )

surface.CreateFont( "rb655_AddonDesc", {
	size = ScreenScale( 8 ),
	font = "Tahoma"
} )

local PANEL = {}

function PANEL:Init()
	self:SetTall( 200 )
	self:SetWide( 200 )

	self.Selected = false

	local DermaCheckbox = vgui.Create( "DCheckBox", self )
	DermaCheckbox:SetPos( 10, 10 )
	DermaCheckbox:SetValue( 0 )
	self.DermaCheckbox = DermaCheckbox

end

function PANEL:OnMouseReleased( mousecode )

	if ( mousecode == MOUSE_RIGHT ) then

		local m = DermaMenu()

		if ( !self.panel.ToggleMounted:GetDisabled() ) then
			m:AddOption( "Invert Selection", function() self.panel:InvertSelection() end )

			m:AddSpacer()
		end

		m:AddOption( "Open Workshop Page", function() if ( !self.Addon ) then return end steamworks.ViewFile( self.Addon.wsid ) end )
		m:AddSpacer()
		if ( steamworks.ShouldMountAddon( self.Addon.wsid ) ) then
			m:AddOption( "Disable", function() if ( !self.Addon ) then return end steamworks.SetShouldMountAddon( self.Addon.wsid, false ) steamworks.ApplyAddons() end )
		else
			m:AddOption( "Enable", function() if ( !self.Addon ) then return end steamworks.SetShouldMountAddon( self.Addon.wsid, true ) steamworks.ApplyAddons() end )
		end
		m:AddOption( "Uninstall", function() if ( !self.Addon ) then return end steamworks.Unsubscribe( self.Addon.wsid ) steamworks.ApplyAddons() end ) -- Do we need ApplyAddons here?
		m:AddSpacer()
		m:AddOption( "Cancel", function() end )
		m:Open()
	end

end

function PANEL:Toggle()
end

function PANEL:SetSelected( b )
	self.DermaCheckbox:SetChecked( b )
end

function PANEL:GetSelected()
	return self.DermaCheckbox:GetChecked()
end

gDataTable = gDataTable or {}
function PANEL:SetAddon( data )
	self.Addon = data
	if ( gDataTable[ data.wsid ] ) then self.AdditionalData = gDataTable[ data.wsid ] return end

	steamworks.FileInfo( data.wsid, function( result )

		gDataTable[ data.wsid ] = result

		if ( !file.Exists( "cache/workshop/" .. result.previewid .. ".cache", "MOD" ) ) then
			steamworks.Download( result.previewid, false, function( name ) end )
		end

		if ( !IsValid( self ) ) then return end

		self.panel:RefreshAddons()
		self.AdditionalData = result

	end )
end

local missingMat = Material( "../html/img/addonpreview.png", "nocull smooth" )
local lastBuild = 0
local imageCache = {}
function PANEL:Paint( w, h )

	if ( IsValid( self.DermaCheckbox ) ) then
		self.DermaCheckbox:SetVisible( self.Hovered or self.DermaCheckbox.Hovered or self:GetSelected() )
	end

	if ( self.AdditionalData and imageCache[ self.AdditionalData.previewid ] ) then
		self.Image = imageCache[ self.AdditionalData.previewid ]
	end

	if ( !self.Image and self.AdditionalData and file.Exists( "cache/workshop/" .. self.AdditionalData.previewid .. ".cache", "MOD" ) and CurTime() - lastBuild > 0.1 ) then
		self.Image = AddonMaterial( "cache/workshop/" .. self.AdditionalData.previewid .. ".cache" )
		imageCache[ self.AdditionalData.previewid ] = self.Image
		lastBuild = CurTime()
	end

	if ( self:GetSelected() ) then
		draw.RoundedBox( 4, 0, 0, w, h, Color( 0, 150, 255, 200 ) )
	elseif ( self.Addon and steamworks.ShouldMountAddon( self.Addon.wsid ) ) then
		draw.RoundedBox( 4, 0, 0, w, h, Color( 255, 255, 255, 200 ) )
	else
		draw.RoundedBox( 4, 0, 0, w, h, Color( 0, 0, 0, 255 ) )
	end

	if ( self.Image ) then
		surface.SetMaterial( self.Image )
	else
		surface.SetMaterial( missingMat )
	end
	local imageSize = self:GetTall() - 10
	surface.SetDrawColor( color_white )
	surface.DrawTexturedRect( 5, 5, imageSize, imageSize )

	if ( gDataTable[ self.Addon.wsid ] ) then
		local ratio = gDataTable[ self.Addon.wsid ].score
		local x = math.floor( ( self:GetWide() - 10 ) * ratio )

		for i = -5, -1 do
			surface.SetDrawColor( Color( 255, 0, 0, 128 ) )
			surface.DrawLine( 5 + x, self:GetTall() - 5 + i, 4 + ( self:GetWide() - 10 ), self:GetTall() - 5 + i )

			surface.SetDrawColor( Color( 0, 255, 0, 128 ) )
			surface.DrawLine( 5, self:GetTall() - 5 + i, 5 + x, self:GetTall() - 5 + i )
		end
	end

	--[[if ( self.Addon and !steamworks.ShouldMountAddon( self.Addon.wsid ) ) then
		draw.RoundedBox( 4, 0, 0, w, h, Color( 0, 0, 0, 180 ) )
	end]]

	if ( self.Addon and self.Hovered ) then
		draw.RoundedBox( 0, 5, h - 25, w - 10, 15, Color( 0, 0, 0, 180 ) )
		draw.SimpleText( self.Addon.title, "Default", 8, h - 24, Color( 255, 255, 255 ) )
	end

end

vgui.Register( "MenuAddon", PANEL, "Panel" )

--------------------------------------------------------------------------------------------------------------------------------

local AddonFilters = {
	none = {
		label = "None",
		func = function( mod )
			return true
		end
	},
	enabled = {
		label = "Enabled",
		func = function( mod )
			return mod.mounted
		end
	},
	disabled = {
		label = "Disabled",
		func = function( mod )
			return !mod.mounted
		end
	},
}

local Grouping = {
	none = {
		label = "None",
		func = function( addons )
			return { { addons = addons } }
		end
	},
	enabled = {
		label = "Enabled",
		func = function( addons )
			local t = {
				enabled = {
					title = "Enabled",
					addons = {}
				},
				disabled = {
					title = "Disabled",
					addons = {}
				}
			}

			for _, addon in pairs( engine.GetAddons() ) do
				if ( addon.mounted ) then
					table.insert( t.enabled.addons, addon )
				else
					table.insert( t.disabled.addons, addon )
				end
			end

			return t
		end
	},
	ptags = {
		label = "Primary Tags",
		func = function( addons )
			local t = {
				noinfo = {
					title = "Information not loaded yet!",
					addons = {}
				}
			}

			for _, addon in pairs( engine.GetAddons() ) do
				if ( !gDataTable[ addon.wsid ] ) then
					table.insert( t.noinfo.addons, addon )
				else
					local Ptags = { servercontent = "Server Content", effects = "Effects", model = "Model", gamemode = "Gamemode", npc = "NPC", tool = "Tool", vehicle = "Vehicle", weapon = "Weapon", map = "Map" }
					local tags = string.Explode( ",", gDataTable[ addon.wsid ].tags )
					for _, tag in pairs( tags ) do
						if ( tag == "Addon" ) then continue end -- Don't duplicate ALL addons
						if ( !Ptags[ tag:lower() ] ) then tag = "Other" end
						tag = Ptags[ tag:lower() ] or tag
						if ( !t[ tag ] ) then t[ tag ] = { title = tag, addons = {} } end

						table.insert( t[ tag ].addons, addon )
						break
					end
				end
			end

			return t
		end
	},
	--[[models = {
		label = "Models",
		func = function( addons )
			local t = {
				models = {
					title = "Has Models",
					addons = {}
				},
				nomodels = {
					title = "Doesn't Have Models",
					addons = {}
				}
			}

			for _, addon in pairs( engine.GetAddons() ) do
				if ( addon.models > 0 ) then
					table.insert( t.models.addons, addon )
				else
					table.insert( t.nomodels.addons, addon )
				end
			end

			return t
		end
	}]] -- Disabled models are reported as "no models" :(
}

local BackgroundColor = Color( 200, 200, 200, 128 )
local BackgroundColor2 = Color( 200, 200, 200, 255 ) --Color( 0, 0, 0, 100 )

local PANEL = {}

function PANEL:Init()

	self:Dock( FILL )

	local Categories = vgui.Create( "DListLayout", self )
	Categories:DockPadding( 5, 200, 5, 5 )
	Categories:Dock( LEFT )
	Categories:SetWide( 200 )

	--[[ ------------------------------------------------------------------------- ]]

	local Groups = vgui.Create( "DComboBox", Categories )
	Groups:Dock( TOP )
	Groups:SetTall( 30 )
	Groups:DockMargin( 0, 0, 0, 5 )
	for id, group in pairs( Grouping ) do Groups:AddChoice( "Group by: " .. group.label, id, !Groups:GetSelectedID() ) end
	Groups.OnSelect = function( index, value, data ) self:RefreshAddons() end
	self.Groups = Groups

	local Filters = vgui.Create( "DComboBox", Categories )
	Filters:Dock( TOP )
	Filters:SetTall( 30 )
	Filters:DockMargin( 0, 0, 0, 40 )
	for id, f in pairs( AddonFilters ) do Filters:AddChoice( "Filter by: " .. f.label, id, !Filters:GetSelectedID() ) end
	Filters.OnSelect = function( index, value, data ) self:RefreshAddons() end
	self.Filters = Filters

	--[[ ------------------------------------------------------------------------- ]]

	local ToggleMounted = vgui.Create( "DButton", Categories )
	ToggleMounted:Dock( TOP )
	ToggleMounted:SetText( "#Toggle Selected" )
	ToggleMounted:SetTall( 30 )
	ToggleMounted:DockMargin( 0, 0, 0, 5 )
	ToggleMounted.DoClick = function() self:ToggleSelected() end
	self.ToggleMounted = ToggleMounted

	local EnableSelection = vgui.Create( "DButton", Categories )
	EnableSelection:Dock( TOP )
	EnableSelection:SetText( "#Enable Selected" )
	EnableSelection:SetTall( 30 )
	EnableSelection:DockMargin( 0, 0, 0, 5 )
	EnableSelection.DoClick = function() self:EnableSelected() end
	self.EnableSelection = EnableSelection

	local DisableSelection = vgui.Create( "DButton", Categories )
	DisableSelection:Dock( TOP )
	DisableSelection:SetText( "#Disable Selected" )
	DisableSelection:SetTall( 30 )
	DisableSelection:DockMargin( 0, 0, 0, 5 )
	DisableSelection.DoClick = function() self:DisableSelected() end
	self.DisableSelection = DisableSelection

	--[[ ------------------------------------------------------------------------- ]]

	local SelectAll = vgui.Create( "DButton", Categories )
	SelectAll:Dock( TOP )
	SelectAll:SetText( "#Select All" )
	SelectAll:SetTall( 16 )
	SelectAll:DockMargin( 0, 0, 0, 5 )
	SelectAll.DoClick = function() self:SelectAll() end
	self.SelectAllButton = SelectAll

	local DeselectAll = vgui.Create( "DButton", Categories )
	DeselectAll:Dock( TOP )
	DeselectAll:SetText( "#Deselect All" )
	DeselectAll:SetTall( 16 )
	DeselectAll:DockMargin( 0, 0, 0, 5 )
	DeselectAll.DoClick = function() self:DeselectAll() end
	self.DeselectAllButton = DeselectAll

	local InvertAll = vgui.Create( "DButton", Categories )
	InvertAll:Dock( TOP )
	InvertAll:SetText( "#Invert Selection" )
	InvertAll:SetTall( 16 )
	InvertAll:DockMargin( 0, 0, 0, 40 )
	InvertAll.DoClick = function() self:InvertSelection() end

	--[[ ------------------------------------------------------------------------- ]]

	local OpenWorkshop = vgui.Create( "DButton", Categories )
	OpenWorkshop:Dock( TOP )
	OpenWorkshop:SetText( "#Open Workshop" )
	OpenWorkshop:SetTall( 30 )
	OpenWorkshop:DockMargin( 0, 40, 0, 0 )
	OpenWorkshop.DoClick = function() steamworks.OpenWorkshop() end

	------------------- Addon List

	local Scroll = vgui.Create( "DScrollPanel", self )
	Scroll:Dock( FILL )
	Scroll:DockMargin( 0, 5, 5, 5 )

	local AddonList = vgui.Create( "DIconLayout", Scroll )
	AddonList:SetSpaceX( 5 )
	AddonList:SetSpaceY( 5 )
	AddonList:Dock( FILL )
	AddonList:DockMargin( 5, 5, 5, 5 )
	AddonList:DockPadding( 5, 5, 5, 10 )

	function Scroll:Paint( w, h )
		draw.RoundedBoxEx( 4, 0, 0, w, h, BackgroundColor, false, true, false, true )
		draw.RoundedBoxEx( 4, 0, 0, w, h, BackgroundColor2, false, true, false, true )
	end

	self.AddonList = AddonList
	self:RefreshAddons()

end

function PANEL:Think()
	local anySelected = false
	local allSelected = true
	local onlyEnabled = true
	local onlyDisabled = true
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( pnl.GetSelected and pnl:GetSelected() ) then anySelected = true end
		if ( pnl.GetSelected and !pnl:GetSelected() ) then allSelected = false end
		if ( pnl.Addon and !steamworks.ShouldMountAddon( pnl.Addon.wsid ) ) then onlyEnabled = false end
		if ( pnl.Addon and steamworks.ShouldMountAddon( pnl.Addon.wsid ) ) then onlyDisabled = false end
	end
	self.ToggleMounted:SetDisabled( !anySelected )
	self.EnableSelection:SetDisabled( !anySelected or onlyEnabled )
	self.DisableSelection:SetDisabled( !anySelected or onlyDisabled )

	self.SelectAllButton:SetDisabled( allSelected )
	self.DeselectAllButton:SetDisabled( !anySelected )
end

function PANEL:ToggleSelected()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected or !pnl:GetSelected() ) then continue end
		steamworks.SetShouldMountAddon( pnl.Addon.wsid, !steamworks.ShouldMountAddon( pnl.Addon.wsid ) )
	end
	steamworks.ApplyAddons()
end

function PANEL:DisableSelected()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected or !pnl:GetSelected() ) then continue end
		steamworks.SetShouldMountAddon( pnl.Addon.wsid, false )
	end
	steamworks.ApplyAddons()
end

function PANEL:EnableSelected()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected or !pnl:GetSelected() ) then continue end
		steamworks.SetShouldMountAddon( pnl.Addon.wsid, true )
	end
	steamworks.ApplyAddons()
end

function PANEL:InvertSelection()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected ) then continue end
		pnl:SetSelected( !pnl:GetSelected() )
	end
end

function PANEL:DeselectAll()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected ) then continue end
		pnl:SetSelected( false )
	end
end

function PANEL:SelectAll()
	for id, pnl in pairs( self.AddonList:GetChildren() ) do
		if ( !pnl.GetSelected ) then continue end
		pnl:SetSelected( true )
	end
end

function PANEL:Update()
	self:RefreshAddons()
end

function PANEL:RefreshAddons()

	self.AddonList:Clear()

	local grp = self.Groups:GetOptionData( self.Groups:GetSelectedID() )
	local filter = self.Filters:GetOptionData( self.Filters:GetSelectedID() )

	local addons = Grouping[ grp ].func( engine.GetAddons() )

	for id, group in SortedPairsByMemberValue( addons, "title" ) do
		if ( #group.addons < 1 ) then continue end

		local addns = {}
		for k, mod in pairs( group.addons ) do
			if ( !AddonFilters[ filter ].func( mod ) ) then continue end
			table.insert( addns, mod )
		end

		if ( #addns < 1 ) then continue end

		if ( group.title ) then
			local pnl = self.AddonList:Add( "DLabel" )
			pnl.OwnLine = true
			pnl:SetFont( "rb655_AddonName" )
			pnl:SetText( group.title )
			pnl:SetDark( true )
			pnl:SizeToContents()
		end

		for k, mod in SortedPairsByMemberValue( addns, "title" ) do

			local pnl = self.AddonList:Add( "MenuAddon" )
			pnl.panel = self
			pnl:SetAddon( mod )
			pnl:DockMargin( 0, 0, 5, 5 )

		end

	end

end

vgui.Register( "AddonsPanel", PANEL, "EditablePanel" )


================================================
FILE: lua/menu/custom/getmaps.lua
================================================

local RefreshMaps

--
-- Favourites
--

local MapFavourites

local function LoadFavourites()

	local cookiestr = cookie.GetString( "favmaps" )
	MapFavourites = MapFavourites or ( cookiestr and string.Explode( ";", cookiestr ) or {} )

end

function IsMapFavourite( map )

	LoadFavourites()

	return table.HasValue( MapFavourites, map )

end

function ToggleFavourite( map )

	LoadFavourites()

	if ( table.HasValue( MapFavourites, map ) ) then -- is favourite, remove it
		table.remove( MapFavourites, table.KeysFromValue( MapFavourites, map )[ 1 ] )
	else -- not favourite, add it
		table.insert( MapFavourites, map )
	end

	cookie.Set( "favmaps", table.concat( MapFavourites, ";" ) )

	RefreshMaps( true )

	UpdateMapList()

end

--
-- Map Gamemodes
--

local MapPatterns = {}

local MapNames = {}
MapNames[ "bhop_" ] = "Bunny Hop"
MapNames[ "cinema_" ] = "Cinema"
MapNames[ "theater_" ] = "Cinema"
MapNames[ "xc_" ] = "Climb"
MapNames[ "deathrun_" ] = "Deathrun"
MapNames[ "dr_" ] = "Deathrun"
MapNames[ "fm_" ] = "Flood"
MapNames[ "gmt_" ] = "GMod Tower"
MapNames[ "gg_" ] = "Gun Game"
MapNames[ "scoutzknivez" ] = "Gun Game"
MapNames[ "ba_" ] = "Jailbreak"
MapNames[ "jail_" ] = "Jailbreak"
MapNames[ "jb_" ] = "Jailbreak"
MapNames[ "mg_" ] = "Minigames"
MapNames[ "pw_" ] = "Pirate Ship Wars"
MapNames[ "ph_" ] = "Prop Hunt"
MapNames[ "rp_" ] = "Roleplay"
MapNames[ "slb_" ] = "Sled Build"
MapNames[ "sb_" ] = "Spacebuild"
MapNames[ "slender_" ] = "Stop it Slender"
MapNames[ "gms_" ] = "Stranded"
MapNames[ "surf_" ] = "Surf"
MapNames[ "ts_" ] = "The Stalker"
MapNames[ "zm_" ] = "Zombie Master"
MapNames[ "zombiesurvival_" ] = "Zombie Survival"
MapNames[ "zs_" ] = "Zombie Survival"
MapNames[ "ze_" ] = "Zombie Escape"
MapNames[ "gd_" ] = "Guardian"
MapNames[ "dz_" ] = "Danger Zone"
MapNames[ "cm_" ] = "Custom"
MapNames[ "gt_" ] = "Ghost Town"
MapNames[ "tp_" ] = "Team Play"
MapNames[ "vs_" ] = "Versus"
MapNames[ "coop_" ] = "Cooperative"
MapNames[ "vsh_" ] = "Versus Saxton Hale"
MapNames[ "zi_" ] = "Zombie Infection"

MapNames[ "am_" ] = "Aim Multi (1v1)"
MapNames[ "de_" ] = "Bomb Defuse"
MapNames[ "cs_" ] = "Hostage Rescue"
MapNames[ "dm_" ] = "Deathmatch"
MapNames[ "ar_" ] = "Arms Race"
MapNames[ "aim_" ] = "Aim Arena"
MapNames[ "awp_" ] = "AWP Arena"
MapNames[ "arena_" ] = "Arena"
MapNames[ "ctf_" ] = "Capture The Flag"
MapNames[ "cp_" ] = "Control Point"
MapNames[ "koth_" ] = "King Of The Hill"
MapNames[ "mvm_" ] = "Mann Versus Machine"
MapNames[ "pass_" ] = "PASS time"
MapNames[ "pl_" ] = "Payload"
MapNames[ "plr_" ] = "Payload Race"
MapNames[ "tow_" ] = "Tug of War"
MapNames[ "pd_" ] = "Player Destruction"
MapNames[ "rd_" ] = "Robot Destruction"
MapNames[ "kz_" ] = "Kreedz Climbing"
MapNames[ "sd_" ] = "Special Delivery"
MapNames[ "tc_" ] = "Territorial Control"
MapNames[ "tr_" ] = "Training"
MapNames[ "dod_" ] = "Control Point"
MapNames[ "fof_" ] = "Fistful of Frags"
MapNames[ "bm_" ] = "Black Mesa"
-- MapNames[ "phys_" ] = "Physics Sandbox" -- Defined by Sandbox gamemode

MapNames[ "halls3" ] = "Deathmatch"

-- HL1: DM
MapNames[ "boot_camp" ] = "Deathmatch"
MapNames[ "bounce" ] = "Deathmatch"
MapNames[ "crossfire" ] = "Deathmatch"
MapNames[ "datacore" ] = "Deathmatch"
MapNames[ "frenzy" ] = "Deathmatch"
MapNames[ "lambda_bunker" ] = "Deathmatch"
MapNames[ "rapidcore" ] = "Deathmatch"
MapNames[ "snarkpit" ] = "Deathmatch"
MapNames[ "stalkyard" ] = "Deathmatch"
MapNames[ "subtransit" ] = "Deathmatch"
MapNames[ "undertow" ] = "Deathmatch"

local MapGamemodes = {}

local function UpdateGamemodeMaps()

	local GamemodeList = engine.GetGamemodes()

	for id, gm in ipairs( GamemodeList ) do

		local name = gm.title or "Unnammed Gamemode"
		local maps = string.Split( gm.maps, "|" )

		if ( maps and gm.maps != "" ) then

			for k, pattern in ipairs( maps ) do
				-- When in doubt, just try to match it with string.find
				MapGamemodes[ string.lower( pattern ) ] = name
			end

		end

	end

end

--
-- Sub Categories ( For single player games )
--

local MapSubCategories = {

	-- HL1:S
	[ "c1a0c" ] = "c. Unforeseen Consequences",
	[ "c2a4d" ] = "k. Questionable Ethics",
	[ "c2a4e" ] = "k. Questionable Ethics",
	[ "c2a4f" ] = "k. Questionable Ethics",
	[ "c2a4g" ] = "k. Questionable Ethics",
	[ "c4a1" ] = "o. Xen",
	[ "c4a1z" ] = "z. Unreleased Content", -- Not part of the campagn
	[ "c4a1y" ] = "z. Unreleased Content",

	-- HL2: LC
	[ "d2_lostcoast" ] = "h. Lost Coast",

	-- L4D
	[ "l4d_sv_lighthouse" ] = "f. The Last Stand",

}

local MapPatternSubCategories = {

	-- Random, TODO: Move to Gamemode list?
	[ "^hns_" ] = "Hide and Seek",
	[ "^pf_" ] = "Parkour Fortress",
	[ "^fy_" ] = "Fight Yard",
	[ "^hg_" ] = "Hunger Games",
	[ "^trade_" ] = "Trade",
	[ "^35hp" ] = "35HP Knife Only",

	-- Alien Swarm
	[ "^asi[-]jac" ] = "Jacob's Rest",

	-- Left 4 Dead 1
	[ "^l4d_hospital0" ] = "No Mercy",
	[ "^l4d_garage0" ] = "Crash Course",
	[ "^l4d_smalltown0" ] = "Death Toll",
	[ "^l4d_airport0" ] = "Dead Air",
	[ "^l4d_farm0" ] = "Blood Harvest",
	[ "^l4d_river0" ] = "The Sacrifice",
	[ "^l4d_vs_hospital0" ] = "No Mercy (Versus)",
	[ "^l4d_vs_garage0" ] = "Crash Course (Versus)",
	[ "^l4d_vs_smalltown0" ] = "Death Toll (Versus)",
	[ "^l4d_vs_airport0" ] = "Dead Air (Versus)",
	[ "^l4d_vs_farm0" ] = "Blood Harvest (Versus)",
	[ "^l4d_vs_river0" ] = "The Sacrifice (Versus)",

	-- Left 4 Dead 2
	[ "^c1m" ] = "Dead Center",
	[ "^c2m" ] = "Dark Carnival",
	[ "^c3m" ] = "Swamp Fever",
	[ "^c4m" ] = "Hard Rain",
	[ "^c5m" ] = "The Parish",
	[ "^c6m" ] = "The Passing",
	[ "^c7m" ] = "The Sacrifice (L4D1)",
	[ "^c8m" ] = "No Mercy (L4D1)",
	[ "^c9m" ] = "Crash Course (L4D1)",
	[ "^c10m" ] = "Death Toll (L4D1)",
	[ "^c11m" ] = "Dead Air (L4D1)",
	[ "^c12m" ] = "Blood Harvest (L4D1)",
	[ "^c13m" ] = "Cold Stream",
	[ "^c14m" ] = "The Last Stand",

	-- Portal
	[ "^testchmb_a_(%d+)$" ] = "a. Test Chambers",
	[ "^testchmb_(.*)_advanced$" ] = "c. Advanced Test Chambers",
	[ "^escape_" ] = "b. GLaDOS Escape",

	-- Portal 2, TODO: Most of these should be moved up
	[ "sp_a1_intro" ] = "a. The Courtesy Call",
	[ "sp_a1_wakeup" ] = "a. The Courtesy Call",
	[ "sp_a2_intro" ] = "a. The Courtesy Call",

	[ "sp_a2_laser_intro" ] = "b. The Cold Boot",
	[ "sp_a2_laser_stairs" ] = "b. The Cold Boot",
	[ "sp_a2_dual_lasers" ] = "b. The Cold Boot",
	[ "sp_a2_laser_over_goo" ] = "b. The Cold Boot",
	[ "sp_a2_catapult_intro" ] = "b. The Cold Boot",
	[ "sp_a2_trust_fling" ] = "b. The Cold Boot",
	[ "sp_a2_pit_flings" ] = "b. The Cold Boot",
	[ "sp_a2_fizzler_intro" ] = "b. The Cold Boot",

	[ "sp_a2_sphere_peek" ] = "c. The Return",
	[ "sp_a2_ricochet" ] = "c. The Return",
	[ "sp_a2_bridge_intro" ] = "c. The Return",
	[ "sp_a2_bridge_the_gap" ] = "c. The Return",
	[ "sp_a2_turret_intro" ] = "c. The Return",
	[ "sp_a2_laser_relays" ] = "c. The Return",
	[ "sp_a2_turret_blocker" ] = "c. The Return",
	[ "sp_a2_laser_vs_turret" ] = "c. The Return",
	[ "sp_a2_pull_the_rug" ] = "c. The Return",

	[ "sp_a2_column_blocker" ] = "d. The Surprise",
	[ "sp_a2_laser_chaining" ] = "d. The Surprise",
	[ "sp_a2_triple_laser" ] = "d. The Surprise",
	[ "sp_a2_bts1" ] = "d. The Surprise",
	[ "sp_a2_bts2" ] = "d. The Surprise",

	[ "sp_a2_bts3" ] = "e. The Escape",
	[ "sp_a2_bts4" ] = "e. The Escape",
	[ "sp_a2_bts5" ] = "e. The Escape",
	[ "sp_a2_bts6" ] = "e. The Escape",
	[ "sp_a2_core" ] = "e. The Escape",

	[ "sp_a3_0" ] = "f. The Fall",
	[ "sp_a3_jump_intro" ] = "f. The Fall",
	[ "sp_a3_bomb_flings" ] = "f. The Fall",
	[ "sp_a3_crazy_box" ] = "f. The Fall",
	[ "sp_a3_transition01" ] = "f. The Fall",

	[ "sp_a3_speed_ramp" ] = "g. The Reunion",
	[ "sp_a3_speed_flings" ] = "g. The Reunion",
	[ "sp_a3_portal_intro" ] = "g. The Reunion",
	[ "sp_a3_end" ] = "g. The Reunion",

	[ "sp_a4_intro" ] = "h. The Itch",
	[ "sp_a4_tb_intro" ] = "h. The Itch",
	[ "sp_a4_tb_trust_drop" ] = "h. The Itch",
	[ "sp_a4_tb_wall_button" ] = "h. The Itch",
	[ "sp_a4_tb_polarity" ] = "h. The Itch",
	[ "sp_a4_tb_catch" ] = "h. The Itch",
	[ "sp_a4_stop_the_box" ] = "h. The Itch",
	[ "sp_a4_laser_catapult" ] = "h. The Itch",
	[ "sp_a4_laser_platform" ] = "h. The Itch",
	[ "sp_a4_speed_tb_catch" ] = "h. The Itch",
	[ "sp_a4_jump_polarity" ] = "h. The Itch",

	[ "sp_a4_finale" ] = "i. The Part Where...",
	[ "sp_a5_credits" ] = "i. The Part Where...",

	[ "e1912" ] = "j. Promotional",

	[ "mp_coop_start" ] = "k. Coop Calibration & Hubs",
	[ "mp_coop_lobby_" ] = "k. Coop Calibration & Hubs",

	[ "mp_coop_doors" ] = "l. Coop Course 1: Team Building",
	[ "mp_coop_race_2" ] = "l. Coop Course 1: Team Building",
	[ "mp_coop_laser_2" ] = "l. Coop Course 1: Team Building",
	[ "mp_coop_rat_maze" ] = "l. Coop Course 1: Team Building",
	[ "mp_coop_laser_crusher" ] = "l. Coop Course 1: Team Building",
	[ "mp_coop_teambts" ] = "l. Coop Course 1: Team Building",

	[ "mp_coop_fling_3" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_infinifling_train" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_come_along" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_fling_1" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_catapult_1" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_multifling_1" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_fling_crushers" ] = "m. Coop Course 2: Mass and Velocity",
	[ "mp_coop_fan" ] = "m. Coop Course 2: Mass and Velocity",

	[ "^mp_coop_wall" ] = "n. Coop Course 3: Hard-Light Surfaces",
	[ "mp_coop_catapult_wall_intro" ] = "n. Coop Course 3: Hard-Light Surfaces",
	[ "mp_coop_catapult_2" ] = "n. Coop Course 3: Hard-Light Surfaces",
	[ "^mp_coop_turret_" ] = "n. Coop Course 3: Hard-Light Surfaces",

	[ "^mp_coop_tbeam_" ] = "o. Coop Course 4: Excursion Funnels",

	[ "mp_coop_paint_come_along" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_redirect" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_bridge" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_walljumps" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_speed_fling" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_red_racer" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_speed_catch" ] = "p. Coop Course 5: Mobility Gels",
	[ "mp_coop_paint_longjump_intro" ] = "p. Coop Course 5: Mobility Gels",

	[ "^mp_coop_separation_1" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_tripleaxis" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_catapult_catch" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_2paints_1bridge" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_paint_conversion" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_bridge_catch" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_laser_tbeam" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_paint_rat_maze" ] = "q. Additional Coop Course: Art Therapy",
	[ "^mp_coop_paint_crazy_box" ] = "q. Additional Coop Course: Art Therapy",

	-- Half-Life: Source
	[ "^t0a0" ] = "_. Hazard Course",
	[ "^c0a0" ] = "a. Black Mesa Inbound",
	[ "^c1a0" ] = "b. Anomalous Materials",
	[ "^c1a1" ] = "c. Unforeseen Consequences",
	[ "^c1a2" ] = "d. Office Complex",
	[ "^c1a3" ] = "e. \"We've Got Hostiles\"",
	[ "^c1a4" ] = "f. Blast Pit",
	[ "^c2a1" ] = "g. Power Up",
	[ "^c2a2" ] = "h. On A Rail",
	[ "^c2a3" ] = "i. Apprehension",
	[ "^c2a4" ] = "j. Residue Processing",

	[ "^c2a5" ] = "l. Surface Tension",
	[ "^c3a1" ] = "m. \"Forget About Freeman!\"",
	[ "^c3a2" ] = "n. Lambda Core",
	[ "^c4a2" ] = "p. Gonarch's Lair",
	[ "^c4a1(.+)$" ] = "r. Interloper",
	[ "^c4a3" ] = "s. Nihilanth",
	[ "^c5a1" ] = "t. Endgame",

	-- Half-Life 2
	[ "^d1_trainstation_0[1-4]" ] = "a. Point Insertion",
	[ "^d1_trainstation_0[5-6]" ] = "b. \"A Red Letter Day\"",

	[ "^d1_canals_0[1-5]" ] = "c. Route Kanal",

	[ "^d1_canals_0[6-9]" ] = "d. Water Hazard",
	[ "^d1_canals_1[0-3]" ] = "d. Water Hazard",

	[ "^d1_eli" ] = "e. Black Mesa East",
	[ "^d1_town" ] = "f. \"We Don't Go To Ravenholm...\"",
	[ "^d2_coast_0[1-8]" ] = "g. Highway 17",

	[ "^d2_coast_09" ] = "h. Sandtraps",
	[ "^d2_coast_1" ] = "h. Sandtraps",
	[ "^d2_prison_01" ] = "h. Sandtraps",

	[ "^d2_prison_0[2-5]" ] = "i. Nova Prospekt",
	[ "^d2_prison_0[6-8]" ] = "j. Entaglement",

	[ "^d3_c17_01" ] = "j. Entaglement",

	[ "^d3_c17_0[2-8]" ] = "k. Anticitizen One",

	[ "^d3_c17_09" ] = "l. \"Follow Freeman!\"",
	[ "^d3_c17_1" ] = "l. \"Follow Freeman!\"",

	[ "^d3_citadel" ] = "m. Our Benefactors",
	[ "^d3_breen" ] = "n. Dark Energy",

	-- Half-Life 2: Episode 1
	[ "^ep1_citadel_0[0-2]" ] = "o. Undue Alarm",
	[ "^ep1_citadel_0[3-4]" ] = "p. Direct Intervention",
	[ "^ep1_c17_00" ] = "q. Lowlife",
	[ "^ep1_c17_0[1-2]" ] = "r. Urban Flight",
	[ "^ep1_c17_0[5-6]" ] = "s. Exit 17",

	-- Half-Life 2: Episode 2
	[ "^ep2_outland_01" ] = "t. To the White Forest",
	[ "^ep2_outland_0[2-4]" ] = "u. This Vortal Coil",
	[ "^ep2_outland_0[5-6]$" ] = "v. Freeman Pontifex",
	[ "^ep2_outland_06a" ] = "w. Riding Shotgun",
	[ "^ep2_outland_0[7-8]" ] = "w. Riding Shotgun",

	[ "^ep2_outland_09" ] = "x. Under the Radar",
	[ "^ep2_outland_10" ] = "x. Under the Radar",
	[ "^ep2_outland_1[1-2]$" ] = "y. Our Mutual Fiend",
	[ "^ep2_outland_11a" ] = "y. Our Mutual Fiend",
	[ "^ep2_outland_11b" ] = "y. Our Mutual Fiend",
	[ "^ep2_outland_12a" ] = "z. T-Minus One",

	-- Half-Life 2 backgrounds
	[ "^background" ] = "z. Backgrounds",
	[ "^ep1_background" ] = "z. Backgrounds",
	[ "^ep2_background" ] = "z. Backgrounds",
}

--
-- Hidden maps
--

local IgnorePatterns = {
	"^background",
	"^ep1_background",
	"^ep2_background",
	"^devtest",
	"^test_",
	"^styleguide",
	"^sdk_",
	"^vst_",
}

local IgnoreMaps = {
	c4a1y = true, -- Doesn't load
	credits = true,
	d2_coast_02 = true, -- Doesn't load
	d3_c17_02_camera = true,
	ep1_citadel_00_demo = true,
	c5m1_waterfront_sndscape = true,
	intro = true,
	test = true,
}

-- Hide single player games from gamemode map list, their maps have their own game category
local IgnoreGames = {
	[ 220 ] = true, -- HL2
	[ 280 ] = true, -- HL:S
	[ 340 ] = true, -- HL2:LC
	[ 380 ] = true, -- HL2:EP1
	[ 400 ] = true, -- P
	[ 420 ] = true, -- HL2:EP2
	[ 500 ] = true, -- L4D
	[ 550 ] = true, -- L4D2
	[ 620 ] = true, -- P2
	[ 630 ] = true, -- Alien Swarm
	[ 251110 ] = true, -- INFRA
	[ 221910 ] = true, -- Stanley Parable
	[ 362890 ] = true, -- Black Mesa
}

-- Maps from these games cannot be loaded in Garry's Mod
local IncompatibleGames = {
	--[ 550 ] = true, -- L4D2
	--[ 620 ] = true, -- P2
	--[ 730 ] = true, -- CSGO
}

--
-- Map lists
--

local MapList = {}
local GameMapList = {}

-- TODO: ConVar for hiding bad maps

local function IsUselessMap( map_name )
	local Ignore = IgnoreMaps[ map_name ]
	if ( Ignore ) then return true end

	for _, ignore in ipairs( IgnorePatterns ) do
		if ( string.find( map_name, ignore ) ) then
			return true
		end
	end

	return false
end

local function AddMapInfo( map_info, cat, cat_name, cat_table )
	if ( !cat_table[ cat ] ) then cat_table[ cat ] = { name = cat_name, maps = {} } end

	-- I hate this, I hate that CS:GO and CS:S have same map names!
	for id, t in pairs( cat_table[ cat ].maps ) do
		if ( t.name == map_info.name ) then return end
	end

	table.insert( cat_table[ cat ].maps, map_info ) -- TODO: Perhaps make the key the map name?
end

RefreshMaps = function( skip )

	if ( !skip ) then UpdateGamemodeMaps() end

	MapList = {}
	GameMapList = {}
	local ExistingMaps = {}

	local games = engine.GetGames()
	table.insert( games, { title = "Garry's Mod", depot = 4000, folder = "MOD", mounted = true } )
	table.insert( games, { title = "Addons", depot = 0, folder = "thirdparty", mounted = true } )
	table.insert( games, { title = "Downloaded Maps", depot = -1, folder = "DOWNLOAD", mounted = true } )
	table.insert( games, { title = "mount.cfg", depot = -2, folder = "GAME", mounted = true } ) -- Must be last!
	-- Note: "Games" map categories are bundled by depotID, not folder/title!

	-- Can't do this unfortunately
	--[[for pathid, path in pairs( util.KeyValuesToTable( file.Read( "cfg/mount.cfg", "MOD" ) ) ) do
		print( pathid, path )
		PrintTable( file.Find( "maps/*.bsp", pathid ) )
		table.insert( games, { title = pathid .. " (mount.cfg)", depot = 0, folder = pathid, mounted = true } )
	end]]

	for id, tab in pairs( games ) do
		if ( !tab.mounted ) then continue end

		local maps = file.Find( "maps/*.bsp", tab.folder )

		for k, v in ipairs( maps ) do
			local map_name = string.gsub( v, "%.bsp$", "" ):lower()
			local prefix = string.match( map_name, "^(.-_)" )

			if ( tab.folder == "GAME" ) then
				if ( ExistingMaps[ map_name ] ) then continue end
			else
				ExistingMaps[ map_name ] = true
			end

			-- Don't add useless maps
			if ( IsUselessMap( map_name ) ) then continue end

			-- Map info
			local map_info = {
				name = map_name,
				incompatible = IncompatibleGames[ tab.depot ], -- Ideally this should be replaced with BSP version numbers
				--useless = IsUselessMap( map_name ) -- Maybe it should be done like this instead?
			}

			-- Add map to the game list
			AddMapInfo( map_info, tab.depot, tab.title, GameMapList )

			-- Ignore maps from certain games
			if ( IgnoreGames[ tab.depot ] ) then continue end

			-- For a full list of maps we don't want to process already processed maps
			--[[if ( tab.folder == "GAME" ) then
				if ( ExistingMaps[ map_name ] ) then continue end
			end]]

			-- Give the map a category
			local Category = MapNames[ map_name ] or MapNames[ prefix ]
			if ( !Category ) then
				local patterns = table.Merge( table.Copy( MapGamemodes ), MapPatterns )
				for pattern, category in pairs( patterns ) do
					if ( string.find( map_name, pattern ) ) then
						Category = category
					end
				end
			end

			-- Throw all uncategorised maps into "Other"
			Category = Category or "Other"

			-- Favourite maps
			if ( IsMapFavourite( map_name ) ) then
				AddMapInfo( map_info, "Favourites", "Favourites", MapList )
			end

			AddMapInfo( map_info, Category, Category, MapList )
		end
	end

	UpdateMapList()

end

hook.Add( "MenuStart", "FindMaps", RefreshMaps )
hook.Add( "GameContentChanged", "RefreshMaps", RefreshMaps )

function GetMapCategories( catType )
	local output = {}

	-- This could be done better, but I will leave it like this for now
	if ( catType == "game" ) then
		for cat, tab in pairs( GameMapList ) do
			if ( !tab or !tab.maps or #tab.maps < 1 ) then continue end
			output[ cat ] = tab.name
		end
	else
		for cat, tab in pairs( MapList ) do
			if ( !tab or !tab.maps or #tab.maps < 1 ) then continue end
			output[ cat ] = tab.name
		end
	end

	return output
end

local function map_cat_helper( map, search_t )
	for cat, tab in pairs( search_t ) do
		if ( !tab or !tab.maps or #tab.maps < 1 ) then continue end

		for _, map_t in pairs( tab.maps ) do
			if ( map_t.name == map:lower() ) then
				return cat
			end
		end
	end
end

function GetMapCategory( map )
	local r = map_cat_helper( map, MapList )
	if ( !r ) then r = map_cat_helper( map, GameMapList ) end
	return r
end

function GetMapsFromCategory( cat )
	if ( !DoesCategoryExist( cat ) ) then return {} end

	local maps = MapList[ cat ] and MapList[ cat ].maps or {}
	if ( #maps < 1 ) then maps = GameMapList[ tonumber( cat ) ] and GameMapList[ tonumber( cat ) ].maps or {} end
	return maps
end

function DoesCategoryExist( cat )
	if ( !MapList[ cat ] and !GameMapList[ tonumber( cat ) ] ) then return false end
	return true
end

function DoesMapExist( map )
	return file.Exists( "maps/" .. map .. ".bsp", "GAME" )
end

function GetMapSubCategories()
	local subCats = table.Copy( MapSubCategories )
	local subCatPatterns = table.Copy( MapPatternSubCategories )

	for pattern, catName in pairs( MapPatterns ) do
		subCatPatterns[ pattern ] = catName
	end

	for pattern, catName in pairs( MapGamemodes ) do
		subCatPatterns[ pattern ] = catName
	end

	for prefix, catName in pairs( MapNames ) do
		if ( prefix:EndsWith( "_" ) ) then
			if ( subCatPatterns[ "^" .. prefix ] ) then print( "Prefix " .. prefix .. " has 2 categories '" .. subCatPatterns[ "^" .. prefix ] .. "' and '" .. catName .. "'!" ) end
			subCatPatterns[ "^" .. prefix ] = catName
		else
			if ( subCatPatterns[ prefix ] ) then print( "Map " .. prefix .. " has 2 categories '" .. subCatPatterns[ prefix ] .. "' and '" .. catName .. "'!" ) end
			subCats[ prefix ] = catName
		end
	end

	return subCats, subCatPatterns
end

--
-- Last Map
--

function SaveLastMap( map, cat )

	local t = string.Explode( ";", cookie.GetString( "lastmap", "" ) )
	if ( !map ) then map = t[ 1 ] or "gm_flatgrass" end
	if ( !cat ) then cat = t[ 2 ] or "Sandbox" end

	cookie.Set( "lastmap", map .. ";" .. cat )

end

function LoadLastMap()

	local t = string.Explode( ";", cookie.GetString( "lastmap", "" ) )

	local map = t[ 1 ] or "gm_flatgrass"
	local cat = t[ 2 ] or "Sandbox"

	-- Game categories are stored as numbers!
	cat = tonumber( cat ) or cat

	if ( !DoesMapExist( map ) ) then
		map = "gm_flatgrass"
		cat = "Sandbox"
	end

	return map, cat

end


================================================
FILE: lua/menu/custom/main.lua
================================================

-- Developer stuff
concommand.Add( "lua", function( ply, cmd, args, str )
	if ( IsInGame() ) then return end
	RunString( str )
end )

local PANEL = {}

language.Add( "achievements", "Achievements" )

surface.CreateFont( "MenuButton", {
	font	= "Helvetica",
	size	= 24,
	weight	= 600
} )

local DLabel = baseclass.Get( "DLabel" )

function PANEL:Init()
	self:SetFont( "MenuButton" )
	self:SetCursor( "hand" )
	self:SetMouseInputEnabled( true )
	self:SetTextColor( Color( 255, 255, 255 ) )
end

function PANEL:SetText( ... )
	DLabel.SetText( self, ... )
	self:SizeToContents()
end

function PANEL:SetDisabled( b )
	self.Disabled = b
	self:SetCursor( b and "none" or "hand" )
end

function PANEL:Paint()
	if ( self.Disabled == true ) then self:SetFGColor( Color( 120, 120, 120 ) ) return end
	self:SetFGColor( self.Hovered and Color( 255, 255, 128 ) or Color( 255, 255, 255 ) )
end

function PANEL:OnCursorEntered()
	self.Hovered = true
	if ( !self.Disabled ) then surface.PlaySound( "garrysmod/ui_hover.wav" ) end
end
function PANEL:OnCursorExited()
	self.Hovered = false
end

function PANEL:OnMousePressed()
	if self.Disabled then return end
	DLabel.OnMousePressed( self )
	surface.PlaySound( "garrysmod/ui_click.wav" )
end

vgui.Register( "MenuButton", PANEL, "DLabel" )

local PANEL = {}

function PANEL:Init()

	self:Dock( FILL )

	local mainButtons = vgui.Create( "DPanel", self )
	function mainButtons:Paint( w, h )
		---draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 200 ) )
		self:SetPos( ScrW() / 20, math.max( ScrH() / 2 - self:GetTall() / 2, 150 ) )
	end
	mainButtons:SetSize( 250, 350 )
	self.MenuButtons = mainButtons

	local Resume = vgui.Create( "MenuButton", mainButtons )
	Resume:Dock( TOP )
	Resume:DockMargin( 5, 5, 5, 20 )
	Resume:SetText( "#resume_game" )
	Resume.DoClick = function()
		gui.HideGameUI()
	end
	self.Resume = Resume

	local NewGame = vgui.Create( "MenuButton", mainButtons )
	NewGame:Dock( TOP )
	NewGame:DockMargin( 5, 5, 5, 0 )
	NewGame:SetText( "#new_game" )
	NewGame.DoClick = function()
		self:GetParent():OpenNewGameMenu()
	end

	local PlayMP = vgui.Create( "MenuButton", mainButtons )
	PlayMP:Dock( TOP )
	PlayMP:DockMargin( 5, 0, 5, 0 )
	PlayMP:SetText( "#find_mp_game" )
	PlayMP.DoClick = function()
		RunGameUICommand( "OpenServerBrowser" )
	end

	local Addons = vgui.Create( "MenuButton", mainButtons )
	Addons:Dock( TOP )
	Addons:DockMargin( 5, 20, 5, 0 )
	Addons:SetText( "#addons" )
	Addons.DoClick = function()
		self:GetParent():OpenAddonsMenu()
	end

	local Saves = vgui.Create( "MenuButton", mainButtons )
	Saves:Dock( TOP )
	Saves:DockMargin( 5, 0, 5, 0 )
	Saves:SetText( "#saves" )
	Saves.DoClick = function()
		self:GetParent():OpenCreationMenu( false, "saves" )
	end

	local Demos = vgui.Create( "MenuButton", mainButtons )
	Demos:Dock( TOP )
	Demos:DockMargin( 5, 0, 5, 0 )
	Demos:SetText( "#demos" )
	Demos.DoClick = function()
		self:GetParent():OpenCreationMenu( false, "demos" )
	end

	local Achievements = vgui.Create( "MenuButton", mainButtons )
	Achievements:Dock( TOP )
	Achievements:DockMargin( 5, 0, 5, 0 )
	Achievements:SetText( "#achievements" )
	Achievements.DoClick = function()
		self:GetParent():OpenAchievementsMenu()
	end

	local Options = vgui.Create( "MenuButton", mainButtons )
	Options:Dock( TOP )
	Options:SetText( "#options" )
	Options:DockMargin( 5, 20, 5, 20 )
	Options.DoClick = function()
		RunGameUICommand( "OpenOptionsDialog" )
	end

	local Disconnect = vgui.Create( "MenuButton", mainButtons )
	Disconnect:Dock( TOP )
	Disconnect:SetText( "#disconnect" )
	Disconnect:DockMargin( 5, 5, 5, 0 )
	Disconnect.DoClick = function()
		RunGameUICommand( "Disconnect" )
	end
	self.Disconnect = Disconnect

	local Quit = vgui.Create( "MenuButton", mainButtons )
	Quit:Dock( TOP )
	Quit:SetText( "#quit" )
	Quit:DockMargin( 5, 0, 5, 0 )
	Quit.DoClick = function()
		RunGameUICommand( "quit" )
	end

end

local old = 0
function PANEL:Paint()

	if ScrH() != old then
		old = ScrH()
	end

	if ( !self.Image or self.Image:GetName() != "../gamemodes/" .. engine.ActiveGamemode() .. "/logo" ) then
		self.Image = Material( "../gamemodes/" .. engine.ActiveGamemode() .. "/logo.png", "nocull smooth" )
	end

	if ( self.Image and !self.Image:IsError() ) then
		surface.SetMaterial( self.Image )
		local x, y = self.MenuButtons:GetPos()
		local w, h = self.Image:GetInt( "$realwidth" ), self.Image:GetInt( "$realheight" )
		surface.DrawTexturedRect( x, y - h, w, h )
	end

	if ( self.IsInGame != IsInGame() ) then

		self.IsInGame = IsInGame()

		if ( self.IsInGame ) then
			self.Disconnect:SetVisible( true )
			self.Resume:SetVisible( true )
		else
			self.Disconnect:SetVisible( false )
			self.Resume:SetVisible( false )
		end

	end

end

vgui.Register( "MainMenuScreenPanel", PANEL, "EditablePanel" )


================================================
FILE: lua/menu/custom/mainmenu.lua
================================================

ScreenScale = function( size ) return size * ( ScrW() / 640.0 ) end

include( "getmaps.lua" )
include( "addons.lua" )
include( "new_game.lua" )
include( "saves.lua" )
include( "achievements.lua" )
include( "main.lua" )
include( "_errors.lua" )
include( "../background.lua" )
include( "../crosshair_setup.lua" )

pnlMainMenu = nil

local PANEL = {}

function PANEL:SetSpecial( b )
	self.Special = b
end

local matGradientUp = Material( "gui/gradient_up" )
function PANEL:Paint( w, h )
	if ( !self.Special ) then
		self:SetFGColor( color_black )
		local clr = color_white
		if ( self.Hovered ) then clr = Color( 255, 255, 220 ) end
		if ( self.Depressed ) then self:SetFGColor( color_white ) clr = Color( 35, 150, 255 ) end
		draw.RoundedBox( 4, 0, 0, w, h, clr )
	else
		self:SetFGColor( color_white )
		local clr = Color( 0, 134, 204 )
		if ( self.Hovered ) then clr = Color( 34, 168, 238 ) end
		if ( self.Depressed ) then clr = Color( 0, 134, 204 ) end
		--draw.RoundedBox( 4, 0, 0, w, h, clr )

		surface.SetDrawColor( clr )
		surface.DrawRect( 1, 1, w - 2, h - 2 )

		surface.SetDrawColor( Color( 0, 85, 204 ) )
		if ( self.Hovered ) then clr = surface.SetDrawColor( Color( 34, 119, 238 ) ) end
		surface.SetMaterial( matGradientUp )
		surface.DrawTexturedRect( 1, 1, w - 2, h - 2 )

		surface.SetDrawColor( Color( 0, 85, 204 ) )
		--surface.DrawOutlinedRect( 0, 0, w, h )

		surface.DrawLine( 1, 0, w-1, 0 ) -- top
		surface.DrawLine( 0, 1, 0, h - 1 ) -- left
		surface.DrawLine( w - 1, 1, w - 1, h - 1 ) -- right

		surface.SetDrawColor( Color( 0, 53, 128 ) )
		surface.DrawLine( 1, h - 1, w-1, h - 1 ) -- bottom

		local clr2 = Color( 52, 160, 214 )
		if ( self.Hovered ) then clr2 = Color( 79, 187, 241 ) end
		if ( self.Depressed ) then clr2 = Color( 52, 160, 214 ) end
		surface.SetDrawColor( clr2 )
		surface.DrawLine( 1, 1, w - 1, 1 )
	end
end

function PANEL:PerformLayout( w, h )
	if ( IsValid( self.m_Image ) ) then
		self.m_Image:SetPos( 5, ( self:GetTall() - self.m_Image:GetTall() ) * 0.5 )
		self:SetTextInset( 10, 0 )
	end
	DLabel.PerformLayout( self )
end


vgui.Register( "DMenuButton", PANEL, "DButton" )

local PANEL = {}

function PANEL:Init()

	self:Dock( FILL )
	self:SetKeyboardInputEnabled( true )
	self:SetMouseInputEnabled( true )

	local lowerPanel = vgui.Create( "DPanel", self )
	function lowerPanel:Paint( w, h )
		draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 220 ) )
	end
	lowerPanel:SetTall( 50 )
	lowerPanel:Dock( BOTTOM )

	local BackButton = vgui.Create( "DMenuButton", lowerPanel )
	BackButton:Dock( LEFT )
	BackButton:SetText( "#back_to_main_menu" )
	BackButton:SetIcon( "icon16/arrow_left.png" )
	BackButton:SetTextInset( BackButton.m_Image:GetWide() + 20, 0 )
	BackButton:DockMargin( 5, 5, 5, 5 )
	BackButton:SetContentAlignment( 6 )
	BackButton:SizeToContents()
	BackButton:SetVisible( false )
	BackButton.DoClick = function() self:Back() end
	self.BackButton = BackButton



	local Gamemodes = vgui.Create( "DMenuButton", lowerPanel )
	Gamemodes:Dock( RIGHT )
	Gamemodes:DockMargin( 5, 5, 5, 5 )
	Gamemodes:SetContentAlignment( 6 )
	Gamemodes.DoClick = function() self:OpenGamemodesList( Gamemodes ) end
	self.GamemodeList = Gamemodes
	self:RefreshGamemodes()

	local MountedGames = vgui.Create( "DMenuButton", lowerPanel )
	MountedGames:Dock( RIGHT )
	MountedGames:DockMargin( 5, 5, 0, 5 )
	MountedGames:SetContentAlignment( 6 )
	MountedGames:SetText( "#games" )
	MountedGames:SetWide( 88 )
	MountedGames:SetIcon( "../html/img/back_to_game.png" )
	MountedGames.DoClick = function() self:OpenMountedGamesList( MountedGames ) end
	self.MountedGames = MountedGames

	local Languages = vgui.Create( "DMenuButton", lowerPanel )
	Languages:Dock( RIGHT )
	Languages:DockMargin( 5, 5, 0, 5 )
	Languages:SetContentAlignment( 6 )
	Languages:SetText( "" )
	Languages:SetWide( 40 )
	Languages:SetIcon( "../resource/localization/" .. GetConVarString( "gmod_language" ) .. ".png" )
	Languages.DoClick = function() self:OpenLanguages( Languages ) end
	function Languages:PerformLayout()
		if ( IsValid( self.m_Image ) ) then
			self.m_Image:SetPos( ( self:GetWide() - self.m_Image:GetWide() ) * 0.5, ( self:GetTall() - self.m_Image:GetTall() ) * 0.5 )
			self.m_Image:SetSize( 16, 11 )
		end
		DLabel.PerformLayout( self )
	end
	self.Languages = Languages

	local Problems = vgui.Create( "DMenuButton", lowerPanel )
	Problems:Dock( RIGHT )
	Problems:DockMargin( 5, 5, 0, 5 )
	Problems:SetContentAlignment( 6 )
	Problems:SetText( "#problems" )
	Problems:SetWide( 88 )
	Problems:SetIcon( "../html/img/error.png" )
	Problems.DoClick = function() OpenProblemsPanel() end
	self.ProblemsBtn = Problems



	self:MakePopup()
	self:SetPopupStayAtBack( true )

	self:OpenMainMenu()

end

function PANEL:Paint()

	if ( !IsValid( self.NewGameFrame ) and !IsValid( self.AddonsFrame ) and !IsValid( self.AchievementsFrame ) and !IsValid( self.SavesFrame ) ) then
		self.BackButton:SetVisible( false )
	else
		self.BackButton:SetVisible( true )
	end

	if ( self.IsInGame != IsInGame() ) then

		self.IsInGame = IsInGame()

		self:OpenMainMenu() -- To update the buttons

	end

	DrawBackground()

end

function PANEL:ClosePopups( b )
	if ( IsValid( self.LanguageList ) ) then self.LanguageList:Remove() end
	if ( !b and IsValid( self.MountedGamesList ) ) then self.MountedGamesList:Remove() end -- The ugly 'b' hack
	if ( IsValid( self.GamemodesList ) ) then self.GamemodesList:Remove() end
end

function PANEL:CloseAllMenus()
	if ( IsValid( self.MainMenuPanel ) ) then self.MainMenuPanel:Remove() end
	if ( IsValid( self.NewGameFrame ) ) then self.NewGameFrame:Remove() end
	if ( IsValid( self.AddonsFrame ) ) then self.AddonsFrame:Remove() end
	if ( IsValid( self.AchievementsFrame ) ) then self.AchievementsFrame:Remove() end
	if ( IsValid( self.SavesFrame ) ) then self.SavesFrame:Remove() end
end

function PANEL:Back()
	self:CloseAllMenus()
	self:OpenMainMenu()
end

function PANEL:OpenMainMenu( b )
	self:CloseAllMenus()
	self:ClosePopups( b )

	local frame = vgui.Create( "MainMenuScreenPanel", self )
	self.MainMenuPanel = frame
end

function PANEL:OpenAddonsMenu( b )
	self:CloseAllMenus()
	self:ClosePopups( b )

	local frame = vgui.Create( "AddonsPanel", self )
	self.AddonsFrame = frame
end

function PANEL:OpenCreationMenu( b, typ )
	self:CloseAllMenus()
	self:ClosePopups( b )

	local frame = vgui.Create( "SavesPanel", self )
	self.SavesFrame = frame
	self.SavesFrame:SetType( typ )
end

function PANEL:OpenAchievementsMenu( b )
	self:CloseAllMenus()
	self:ClosePopups( b )

	local frame = vgui.Create( "AchievementsPanel", self )
	self.AchievementsFrame = frame
end

function PANEL:OpenNewGameMenu( b )
	self:CloseAllMenus()
	self:ClosePopups( b )

	local frame = vgui.Create( "NewGamePanel", self )
	self.NewGameFrame = frame

	hook.Run( "MenuStart" )
end

function PANEL:OpenLanguages( pnl )
	if ( IsValid( self.LanguageList ) ) then self.LanguageList:Remove() return end
	self:ClosePopups()

	local perRow = 8

	local panel = vgui.Create( "DScrollPanel", self )
	panel:SetSize( perRow * 16 + ( perRow + 1 ) * 10, 90 )
	panel:SetPos( pnl:GetPos() - panel:GetWide() / 2 + pnl:GetWide() / 2, ScrH() - 55 - panel:GetTall() )
	self.LanguageList = panel

	function panel:Paint( w, h )
		draw.RoundedBox( 0, 0, 0, w - 5, h, Color( 0, 0, 0, 220 ) )
	end

	local p = vgui.Create( "DIconLayout", panel )
	p:Dock( FILL )
	p:SetBorder( 10 )
	p:SetSpaceY( 10 )
	p:SetSpaceX( 10 )

	for id, flag in pairs( file.Find( "resource/localization/*.png", "GAME" ) ) do
		local f = p:Add( "DImageButton" )
		f:SetImage( "../resource/localization/" .. flag )
		f:SetSize( 16, 12 )
		f.DoClick = function()
			RunConsoleCommand( "gmod_language", string.StripExtension( flag ) )
			--LanguageChanged( string.StripExtension( flag ) )
		end
	end

end


function PANEL:OpenMountedGamesList( pnl )
	if ( IsValid( self.MountedGamesList ) ) then self.MountedGamesList:Remove() return end
	self:ClosePopups()

	local p = vgui.Create( "DPanelList", self )
	p:EnableVerticalScrollbar()
	p:SetSize( 276, 256 )
	p:SetPos( math.min( pnl:GetPos() - p:GetWide() / 2 + pnl:GetWide() / 2, ScrW() - p:GetWide() - 5 ), ScrH() - 55 - p:GetTall() )
	p:SetSpacing( 5 )
	p:SetPadding( 5 )
	self.MountedGamesList = p

	function p:Paint( w, h )
		draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 220 ) )
	end

	local function add( t )
		local a = p:Add( "DCheckBoxLabel" )
		a:SetText( t.title )
		if ( !t.installed ) then a:SetText( t.title .. " ( not installed )" ) end
		if ( !t.owned ) then a:SetText( t.title .. " ( not owned )" ) end

		p:AddItem( a )
		a:SetChecked( t.mounted )
		a.OnChange = function( panel ) engine.SetMounted( t.depot, a:GetChecked() ) end
		if ( !t.owned or !t.installed ) then
			a:SetDisabled( true )
		end
	end

	for id, t in SortedPairsByMemberValue( engine.GetGames(), "title" ) do
		add( t )
	end
	--[[for id, t in SortedPairsByMemberValue( engine.GetGames(), "title" ) do
		if ( t.installed and t.owned ) then add( t ) end
	end

	for id, t in SortedPairsByMemberValue( engine.GetGames(), "title" ) do
		if ( !t.installed and t.owned ) then add( t ) end
	end

	for id, t in SortedPairsByMemberValue( engine.GetGames(), "title" ) do
		if ( !t.installed and !t.owned ) then add( t ) end
	end]]

end

function PANEL:OpenGamemodesList( pnl )
	if ( IsValid( self.GamemodesList ) ) then self.GamemodesList:Remove() return end
	self:ClosePopups()

	local p = vgui.Create( "DPanelList", self )
	p:EnableVerticalScrollbar()
	p:SetSpacing( 5 )
	p:SetPadding( 5 )
	self.GamemodesList = p

	function p:Paint( w, h )
		draw.RoundedBox( 0, 0, 0, w, h, Color( 0, 0, 0, 220 ) )
	end

	local w = 100
	local h = 5

	for id, t in SortedPairsByMemberValue( engine.GetGamemodes(), "title" ) do
		if ( !t.menusystem ) then continue end
		local Gamemode = p:Add( "DMenuButton" )
		Gamemode:SetContentAlignment( 6 )
		Gamemode:SetText( t.title )
		if ( Material( "../gamemodes/" .. t.name .. "/icon24.png" ):IsError() ) then
			Gamemode:SetIcon( "../gamemodes/base/icon24.png" )
		else
			Gamemode:SetIcon( "../gamemodes/" .. t.name .. "/icon24.png" )
		end
		Gamemode:SetTextInset( Gamemode.m_Image:GetWide() + 25, 0 )
		Gamemode:SizeToContents()
		Gamemode:SetTall( 40 )
		Gamemode.DoClick = function()
			RunConsoleCommand( "gamemode", t.name )
			self:ClosePopups()
		end

		p:AddItem( Gamemode )

		w = math.max( w, Gamemode:GetWide() + 20 )
		h = h + 45
	end

	--p:SetWide( w, h )

	p:SetSize( w, math.min( h, ScrH() / 1.5 ) )
	p:SetPos( math.min( pnl:GetPos() - p:GetWide() / 2 + pnl:GetWide() / 2, ScrW() - p:GetWide() - 5 ), ScrH() - 55 - p:GetTall() )
end

function PANEL:RefreshGamemodes( b )

	for id, gm in pairs( engine.GetGamemodes() ) do
		if ( gm.name == engine.ActiveGamemode() ) then self.GamemodeList:SetText( gm.title ) end
	end

	if ( Material( "../gamemodes/" .. engine.ActiveGamemode() .. "/icon24.png" ):IsError() ) then
		self.GamemodeList:SetIcon( "../gamemodes/base/icon24.png" )
	else
		self.GamemodeList:SetIcon( "../gamemodes/" .. engine.ActiveGamemode() .. "/icon24.png" )
	end

	self.GamemodeList:SetTextInset( self.GamemodeList.m_Image:GetWide() + 25, 0 )
	self.GamemodeList:SizeToContents()

	self:UpdateBackgroundImages()

	if ( IsValid( self.NewGameFrame ) ) then self.NewGameFrame:Update() end
	if ( IsValid( self.AddonsFrame ) ) then self.AddonsFrame:Update() end
	--if ( IsValid( self.NewGameFrame ) ) then self:OpenNewGameMenu( b ) end
	--if ( IsValid( self.AddonsFrame ) ) then self:OpenAddonsMenu( b ) end
	--if ( IsValid( self.MainMenuPanel ) ) then self:OpenMainMenu( b ) end
	--if ( IsValid( self.AchievementsFrame ) ) then self:OpenAchievementsMenu( b ) end

	if ( IsValid( self.MountedGamesList ) ) then self.MountedGamesList:MoveToFront() end

end

function PANEL:RefreshAddons()
	if ( !IsValid( self.AddonsFrame ) ) then return end

	self.AddonsFrame:RefreshAddons()

end

function PANEL:RefreshContent()

	self:RefreshGamemodes( true )
	self:RefreshAddons()

end

function PANEL:SetProblemCount( problems, severity )
	
	-- The lua menu doesn't have a problems menu. This function is only here to shut any errors up.
end

function PANEL:ScreenshotScan( folder )

	local bReturn = false

	local Screenshots = file.Find( folder .. "*.jpg", "GAME" )
	for k, v in RandomPairs( Screenshots ) do

		AddBackgroundImage( folder .. v )
		bReturn = true

	end

	return bReturn

end


function PANEL:UpdateBackgroundImages()

	ClearBackgroundImages()

	--
	-- If there's screenshots in gamemodes/<gamemode>/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( str )
	print( "Not Implemented: ", str )
end

vgui.Register( "MainMenuPanel", PANEL, "EditablePanel" )

--
-- Called from the engine any time the language changes
--
function LanguageChanged( lang )
	if ( !IsValid( pnlMainMenu ) ) then return end

	local pnl = pnlMainMenu
	if ( IsValid( pnl.NewGameFrame ) ) then pnl.NewGameFrame:UpdateLanguage() end
	if ( IsValid( pnl.AddonsFrame ) ) then pnl:OpenAddonsMenu() end
	if ( IsValid( pnl.MainMenuPanel ) ) then pnl:OpenMainMenu() end
	if ( IsValid( pnl.AchievementsFrame ) ) then pnl:OpenAchievementsMenu() end
	if ( IsValid( pnl.SavesFrame ) ) then pnl:OpenCreationMenu() end

	pnl.Languages:SetIcon( "../resource/localization/" .. lang .. ".png" )

	pnl.ProblemsBtn:SetText( "#problems" )
	pnl.MountedGames:SetText( "#games" )

	pnl.BackButton:SetTextInset( pnl.BackButton.m_Image:GetWide() + 20, 0 )
	pnl.BackButton:SetText( "#back_to_main_menu" )
	pnl.BackButton:SizeToContents()
end

function UpdateMapList()
	if ( !IsValid( pnlMainMenu ) ) then return end

	local pnl = pnlMainMenu

	if ( IsValid( pnl.NewGameFrame ) ) then pnl.NewGameFrame:Update() end
	--[[if ( IsValid( pnl.NewGameFrame ) ) then pnl:OpenNewGameMenu() end
	if ( IsValid( pnl.AddonsFrame ) ) then pnl:OpenAddonsMenu() end
	if ( IsValid( pnl.MainMenuPanel ) ) then pnl:OpenMainMenu() end
	if ( IsValid( pnl.AchievementsFrame ) ) then pnl:OpenAchievementsMenu() end]]
end

hook.Add( "GameContentChanged", "RefreshMainMenu", function()
	if ( !IsValid( pnlMainMenu ) ) then return end

	pnlMainMenu:RefreshContent()
end )

timer.Simple( 0, function()
	if ( IsValid( pnlMainMenu ) ) then pnlMainMenu:Remove() end

	pnlMainMenu = vgui.Create( "MainMenuPanel" )

	hook.Run( "GameContentChanged" )
end )

-- A hack to bring the console to front when menu_reload is ran
timer.Simple( 1, function()
	if ( gui.IsConsoleVisible() ) then gui.ShowConsole() end
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.."', "..json..")" )

	end )

end

local Servers = {}

function GetServers( type, id )


	local data =
	{
		Finished = function()

		end,

		Callback = function( ping , name, desc, map, players, maxplayers, botplayers, pass, lastplayed, address, gamemode, workshopid )

			name	= string.JavascriptSafe( name )
			desc	= string.JavascriptSafe( desc )
			map		= string.JavascriptSafe( map )
			address = string.JavascriptSafe( address )
			gamemode = string.JavascriptSafe( gamemode )
			workshopid = string.JavascriptSafe( workshopid )

			if ( pass ) then pass = "true" else pass = "false" end

			pnlMainMenu:Call( "AddServer( '"..type.."', '"..id.."', "..ping..", \""..name.."\", \""..desc.."\", \""..map.."\", "..players..", "..maxplayers..", "..botplayers..", "..pass..", "..lastplayed..", \""..address.."\", \""..gamemode.."\", \""..workshopid.."\" )" )

		end,

		Type = type,
		GameDir = 'garrysmod',
		AppID = 4000,
	}

	serverlist.Query( data )

end

]]


================================================
FILE: lua/menu/custom/new_game.lua
================================================

include( "new_game_panels.lua" )

CreateConVar( "cl_maxplayers", "1", FCVAR_ARCHIVE )

--[[
TODO:
make sure the text of categories / multiplayer settings does not overflow, especially on lower resolutions
Fix reloading the panels resetting the scrolling for categories and server settings?

Auto scroll to select map on first open or something?

Better map icon visuals?
Allow people to create their own categories?
Figure out fonts? The current ones look neat but blurry
]]

surface.CreateFont( "StartNewGameFont", {
	font = "Roboto Lt",
	size = 18,
} )

surface.CreateFont( "SingleMultiPlayer", {
	font = "Roboto Lt",
	size = 17,
} )

surface.CreateFont( "DermaRobotoDefault", {
	font = "Roboto Lt",
	size = 13
} )

surface.CreateFont( "StartNewGame", {
	font = "Roboto",
	size = 30,
} )

surface.CreateFont( "rb655_MapList", {
	size = 12,
	font = "Tahoma"
} )

surface.CreateFont( "rb655_MapSubCat", {
	size = 30,
	--weight = 900,
	font = "Roboto Lt"
} )

local noise = Material( "gui/noise.png", "nocull noclamp smooth" )
local function DrawHUDBox( x, y, w, h, mat, clr )
	surface.SetDrawColor( clr or Color( 255, 255, 255, 255 ) )

	surface.SetMaterial( mat or noise )
	surface.DrawTexturedRectUV( x, y, w, h, 0, 0, w / 128, h / 128 )
end

local function EnableMouseScroll( s )
	local mousePressed = input.IsMouseDown( MOUSE_RIGHT ) or input.IsMouseDown( MOUSE_LEFT )
	if ( !mousePressed ) then s.start = nil s.EnableMouseScrollEnabled = false return end

	if ( !s.start and s:IsChildHovered() and !s:GetVBar():IsChildHovered() and !s.EnableMouseScrollEnabled ) then
		s.start = s:GetVBar():GetScroll()
		local x, y = input.GetCursorPos()
		s.startY = y
		s.EnableMouseScrollEnabled = true
	end
	s.EnableMouseScrollEnabled = true

	if ( s.start ) then
		local x, y = input.GetCursorPos()
		s:GetVBar():SetScroll( s.start + ( s.startY - y ) )
	end
end

local matGradientUp = Material( "gui/gradient_up" )
local function DrawScrollDarkGradients( self, w, h )
	if ( self.VBar:GetScroll() + self:GetTall() < self.pnlCanvas:GetTall() ) then
		local height = math.min( ( self.pnlCanvas:GetTall() - ( self.VBar:GetScroll() + self:GetTall() ) ) / 3, 30 )
		height = math.floor( height )

		surface.SetMaterial( matGradientUp )
		surface.SetDrawColor( Color( 0, 0, 0, 200 ) )
		surface.DrawTexturedRect( 0, h - height, w, height )
	end

	if ( self.VBar:GetScroll() > 0 ) then
		local height = math.min( self.VBar:GetScroll() / 3, 30 )
		height = math.floor( height )

		surface.SetMaterial( matGradientUp )
		surface.SetDrawColor( Color( 0, 0, 0, 200 ) )
		surface.DrawTexturedRectUV( 0, 0, w, height, 0, 1, 1, 0 )
	end
end

local LocalizedShit = {}
local function SetLocalizedString( self, txt )
	self:SetText( language.GetPhrase( txt ) )
	table.insert( LocalizedShit, { panel = self, text = txt } )
end

local HeaderColor = color_black
local HeaderColor_mid = HeaderColor

local g_CurrentScroll

function GetMapsFromCategorySearch( cat, searchText )
	local maps = GetMapsFromCategory( cat )
	if ( !maps or #maps < 1 ) then return {} end

	local output = {}
	for _, map in SortedPairs( maps ) do
		if ( searchText and !map.name:find( searchText:lower() ) ) then continue end

		table.insert( output, map )
	end

	return output
end

local PANEL = {}

gMapIcons = gMapIcons or {}

local matIncompat = Material( "html/img/incompatible.png" )
local matNoIcon = Material( "gui/noicon.png", "nocull smooth" )

function PANEL:Init()

	g_CurrentScroll = nil
	self.SearchText = nil

	self:Dock( FILL )

	--------------------------------- CATEGORIES ---------------------------------

	local MapCategories = vgui.Create( "DPanel", self )
	MapCategories:Dock( LEFT )
	MapCategories:SetWide( math.Clamp( ScrW() / 6, 150, 200 ) )
	MapCategories:DockMargin( 5, 5, 0, 5 )
	MapCategories:DockPadding( 5, 5, 0, 5 )
	function MapCategories:Paint( w, h )
		DrawHUDBox( 0, 0, w, h )
	end

	local searchBar = vgui.Create( "DFancyTextEntry", MapCategories )
	searchBar:Dock( TOP )
	searchBar:SetFont( "DermaRobotoDefault" )
	searchBar:SetPlaceholderText( "searchbar_placeholer" )
	searchBar:DockMargin( 0, 0, 0, 0 )
	searchBar:SetZPos( -1 )
	searchBar:SetHeight( 24 )
	searchBar:SetUpdateOnType( true )
	searchBar.OnValueChange = function() self:DoSearch( searchBar:GetText() ) end

	self.Categories = {}
	local cat_pnl = self:AddCategoryButton( MapCategories, "Favourites", "Favourites" )
	cat_pnl:SetZPos( 0 )

	local CategoriesScroll = vgui.Create( "DScrollPanel", MapCategories )
	CategoriesScroll:Dock( FILL )
	CategoriesScroll:DockMargin( 0, 5, 0, 0 )
	CategoriesScroll:GetVBar():SetWide( 0 )
	CategoriesScroll.Think = EnableMouseScroll
	CategoriesScroll.PaintOver = DrawScrollDarkGradients

	self.CategoriesPanel = CategoriesScroll

	---------------------------- CONTAINER FOR MAPS ----------------------------

	local Scroll = vgui.Create( "DScrollPanel", self )
	Scroll:Dock( FILL )
	Scroll:DockMargin( 0, 5, 5, 5 )
	function Scroll:Paint( w, h )
		DrawHUDBox( 0, 0, w, h )
	end
	Scroll.Think = EnableMouseScroll

	local sbar = Scroll:GetVBar()
	sbar:SetWide( 8 )
	if ( sbar.SetHideButtons ) then sbar:SetHideButtons( true ) end

	-- HACK!!!!
	sbar.OldSetScroll = sbar.SetScroll
	function sbar:SetScroll( scroll )
		g_CurrentScroll = scroll
		self:OldSetScroll( scroll )
	end

	function sbar:Paint( w, h )
		surface.SetDrawColor( 100, 100, 100, 100 )
		surface.DrawRect( 0, 0, w, h )
	end
	function sbar.btnGrip:Paint( w, h )
		surface.SetDrawColor( 0, 0, 0, 128 )
		surface.DrawRect( 0, 0, w, h )
	end

	local CategoryMaps = vgui.Create( "DIconLayout", Scroll )
	CategoryMaps:SetSpaceX( 5 )
	CategoryMaps:SetSpaceY( 5 )
	CategoryMaps:Dock( TOP )
	CategoryMaps:DockMargin( 5, 5, 5, 5 )
	CategoryMaps:DockPadding( 5, 5, 5, 5 )
	self.CategoryMaps = CategoryMaps

	--------------------------------- SETTINGS ---------------------------------

	local Settings = vgui.Create( "DListLayout", self )
	Settings:Dock( RIGHT )
	Settings:SetWide( math.max( ScrW() / 6, 180 ) )
	Settings:DockMargin( 0, 5, 5, 5 )
	Settings:DockPadding( 5, 5, 5, 5 )
	function Settings:Paint( w, h )
		DrawHUDBox( 0, 0, w, h )
	end
	self.Settings = Settings

	--------------------------------- SINGLE / MULTIPLAYER ---------------------------------

	local SingleMultiPlayer = vgui.Create( "DButton", Settings )
	SingleMultiPlayer:SetFont( "SingleMultiPlayer" )
	SingleMultiPlayer:SetTall( 32 )
	SingleMultiPlayer:SetZPos( -3 )
	SingleMultiPlayer:Dock( TOP )
	SingleMultiPlayer:DockMargin( 0, 0, 0, 5 )
	SingleMultiPlayer:SetColor( color_white )

	SingleMultiPlayer.SetTextGenerated = function( s, n ) s:SetText( language.GetPhrase( "maxplayers_" .. n ) ) end

	SingleMultiPlayer.SetValue = function( s, n ) s.PlayerCount = tonumber( n ) RunConsoleCommand( "cl_maxplayers", n ) s:DoUpdateText() end
	SingleMultiPlayer.GetValue = function( s ) return s.PlayerCount or 1 end

	SingleMultiPlayer.DoUpdateText = function( s )
		s:SetTextGenerated( s:GetValue() )
		s:DoUpdatePanels( Settings )
		if ( self.GamemodeSettings ) then
			s:DoUpdatePanels( self.GamemodeSettings:GetCanvas() )

			-- Hack
			local hasVisibleChildren = false
			for id, pnl in pairs( self.GamemodeSettings:GetCanvas():GetChildren() ) do
				if ( pnl:IsVisible() ) then hasVisibleChildren = true break end
			end
			self.GamemodeSettingsLabel:SetVisible( hasVisibleChildren )
		end
	end
	SingleMultiPlayer.DoUpdatePanels = function( s, parent )
		if ( !IsValid( parent ) ) then return end
		for id, pnl in pairs( parent:GetChildren() ) do
			if ( pnl.Singleplayer == nil ) then continue end

			pnl:SetVisible( ( s:GetValue() < 2 ) == pnl.Singleplayer or pnl.Singleplayer )
			if ( !pnl.OldHeight ) then pnl.OldHeight = pnl:GetTall() end
			pnl:SetHeight( pnl:IsVisible() and pnl.OldHeight or 0 )
		end
		parent:InvalidateLayout( true )
	end
	SingleMultiPlayer.CloseDropDown = function( s )
		if ( s.Butts and #s.Butts > 0 ) then
			for id, p in pairs( s.Butts ) do p:Remove() end
			s.Butts = {}
			return true
		end
	end

	hook.Add( "VGUIMousePressed", "NewGameMenu_FuyckingHack", function( pnl, mc )
		if ( !IsValid( SingleMultiPlayer ) or !SingleMultiPlayer.Butts or vgui.GetHoveredPanel() == SingleMultiPlayer ) then return end
		for id, p in pairs( SingleMultiPlayer.Butts ) do if ( vgui.GetHoveredPanel() == p ) then return end end

		SingleMultiPlayer:CloseDropDown()
	end )

	SingleMultiPlayer.DoClick = function( s )
		if ( s:CloseDropDown() ) then return end

		s.Butts = {}
		local alt = false
		local x, y = s:LocalToScreen( 0, 0 )
		for id, v in pairs( { 1, 2, 4, 8, 16, 32, 64 } ) do
			alt = !alt
			y = y + s:GetTall()
			local but = vgui.Create( "DButton", self )
			but:SetPos( x, y )
			but:SetSize( s:GetSize() )
			s.SetTextGenerated( but, v )
			but.Bottom = (v == 64)
			but.Value = v
			but.Alt = alt
			but:SetFont( "SingleMultiPlayer" )
			but:SetColor( color_white )
			but.DoClick = function( buttt ) s:SetValue( buttt.Value ) s:CloseDropDown() end
			but.Paint = function( buttt, w, h )
				local clr = Color( 30, 190, 30 )
				if ( but.Alt ) then clr = Color( 32, 196, 32 ) end
				if ( but.Hovered ) then clr = Color( 48, 210, 48 ) end
				if ( but.Depressed ) then clr = Color( 24, 180, 24 ) end
				surface.SetDrawColor( clr )
				if ( !buttt.Bottom ) then
					surface.DrawRect( 0, 0, w, h )
				else
					surface.DrawRect( 0, 0, w, h - 1 )
				end

				surface.SetDrawColor( Color( 34, 170, 34 ) )
				if ( !buttt.Bottom ) then
					--surface.DrawLine( 0, 0, 0, h ) -- left
					--surface.DrawLine( w - 1, 0, w - 1, h ) -- right -- Doesn't show bottom pixel??
					surface.DrawRect( 0, 0, 1, h )
					surface.DrawRect( w - 1, 0, 1, h )
				else
					surface.DrawLine( 0, 0, 0, h - 1 ) -- left
					surface.DrawLine( w - 1, 0, w - 1, h - 1 ) -- right

					surface.SetDrawColor( Color( 17, 136, 17 ) )
					surface.DrawLine( 1, h - 1, w - 1, h - 1 ) -- bottom
				end
			end
			table.insert( s.Butts, but )
		end
	end
	SingleMultiPlayer.Paint = function( s, w, h )
		local menuOpen = s.Butts and #s.Butts > 0

		local clr = Color( 82, 204, 82 )
		if ( s.Hovered ) then clr = Color( 86, 210, 86 ) end
		if ( s.Depressed ) then clr = Color( 82, 204, 82 ) end

		surface.SetDrawColor( clr )
		surface.DrawRect( 1, 1, w-2, h-2 )

		surface.SetDrawColor( Color( 30, 190, 30 ) )
		if ( s.Hovered ) then surface.SetDrawColor( Color( 36, 200, 36 ) ) end
		surface.SetMaterial( matGradientUp )
		surface.DrawTexturedRect( 1, 1, w-2, h-2 )

		surface.SetDrawColor( Color( 34, 170, 34 ) )
		surface.DrawLine( 1, 0, w-1, 0 ) -- top
		if ( menuOpen ) then
			surface.DrawRect( 0, 1, 1, h ) -- left
			surface.DrawRect( w - 1, 1, 1, h ) -- right

			surface.SetDrawColor( Color( 30, 190, 30 ) )
			if ( s.Hovered ) then surface.SetDrawColor( Color( 36, 200, 36 ) ) end
			surface.DrawRect( 1, h-2, w-2, 2 )
		else
			surface.DrawLine( 0, 1, 0, h - 1 ) -- left
			surface.DrawLine( w - 1, 1, w - 1, h - 1 ) -- right

		end

		surface.SetDrawColor( Color( 17, 136, 17 ) )
		surface.DrawLine( 1, h - 1, w - 1, h - 1 ) -- bottom

		local clr2 = Color( 118, 214, 118 )
		if ( s.Hovered ) then clr2 = Color( 128, 220, 128 ) end
		--if ( s.Depressed ) then clr2 = Color( 118, 214, 118 ) end
		surface.SetDrawColor( clr2 )
		surface.DrawLine( 1, 1, w - 1, 1 )

		local size = 9
		surface.SetDrawColor( Color( 255, 255, 255 ) )
		draw.NoTexture()
		surface.DrawPoly( {
			{ x = w - ( h / 2 + size / 2 ), y = h / 2 - size / 4 },
			{ x = w - ( h / 2 - size / 2 ), y = h / 2 - size / 4 },
			{ x = w - ( h / 2 ), y = h / 2 + size / 4 }
		} )
	end
	self.SingleMultiPlayer = SingleMultiPlayer

	--------------------------------- TOP CONTENT ---------------------------------

	local ServerName = self:ServerSettings_AddTextEntry( {
		name = "hostname",
		text = "server_name",
		zOrder = -2,
		help = "The name of your server that will appear in the server browser"
	}, Settings )
	Settings.ServerName = ServerName

	self:ServerSettings_AddTextEntry( {
		name = "sv_password",
		text = "server_password",
		zOrder = -1,
		help = "The password for your server that other people have to enter before they can join your server"
	}, Settings )

	local sv_lan = self:ServerSettings_AddCheckbox( {
		text = "lan_server",
		name = "sv_lan",
		help = "Only people on your Local Area Network can connect to the server",
	}, Settings )

	local p2p_enabled = self:ServerSettings_AddCheckbox( {
		text = "p2p_server",
		name = "p2p_enabled",
		help = "Allow people to connect to your Listen Server using Steam P2P networking",
	}, Settings )

	local p2p_friendsonly = self:ServerSettings_AddCheckbox( {
		text = "p2p_server_friendsonly",
		name = "p2p_friendsonly",
		help = "Only allow people on your friends list to join your P2P server",
	}, Settings )

	sv_lan.OnValueChanged = function( pnl, checked )
		if ( checked ) then
			p2p_enabled:SetChecked( false )
			p2p_friendsonly:SetChecked( false )
		end
	end
	p2p_enabled.OnValueChanged = function( pnl, checked )
		if ( checked ) then
			sv_lan:SetChecked( false )
		end
	end
	p2p_friendsonly.OnValueChanged = function( pnl, checked )
		if ( checked ) then
			sv_lan:SetChecked( false )
			p2p_enabled:SetChecked( true )
		end
	end

	--------------------------------- MIDDLE CONTENT - LABEL ---------------------------------

	local GamemodeSettingsLabel = Settings:Add( "DLabel" )
	GamemodeSettingsLabel:Dock( TOP )
	GamemodeSettingsLabel:SetText( "Gamemode Settings" )
	GamemodeSettingsLabel:SetFont( "StartNewGameFont" )
	GamemodeSettingsLabel:SetContentAlignment( 5 )
	GamemodeSettingsLabel:DockMargin( 0, 4, 0, 4 )
	GamemodeSettingsLabel:SetTextColor( HeaderColor )
	self.GamemodeSettingsLabel = GamemodeSettingsLabel

	--------------------------------- MIDDLE CONTENT ---------------------------------

	local GamemodeSettings = vgui.Create( "DScrollPanel", Settings )
	GamemodeSettings:Dock( FILL )
	GamemodeSettings:DockMargin( -5, 0, -5, 5 )
	GamemodeSettings:GetCanvas():DockPadding( 5, 0, 5, 0 )
	GamemodeSettings:GetVBar():SetWide( 0 )
	self.GamemodeSettings = GamemodeSettings
	function GamemodeSettings:Paint( w, h )
		--surface.SetDrawColor( 0, 0, 0, 32 )
		--surface.DrawRect( 0, 0, w, h )
	end
	GamemodeSettings.Think = EnableMouseScroll
	GamemodeSettings.PaintOver = DrawScrollDarkGradients

	--------------------------------- END CONTENT ---------------------------------

	local StartGame = Settings:Add( "DMenuButton" )
	StartGame:Dock( BOTTOM )
	StartGame:SetFont( "StartNewGame" )
	SetLocalizedString( StartGame, "start_game" )
	StartGame:SetTall( 48 )
	StartGame.DoClick = function()
		self:LoadMap()
	end
	StartGame:SetSpecial( true )

	--------------------------------- Update Content ---------------------------------

	self:Update()

end

function PANEL:Paint( w, h )
	surface.SetDrawColor( 0, 0, 0, 150 )
	surface.DrawRect( 0, 0, w, h )

	if ( self.CurrentCategory and IsValid( self.Categories[ self.CurrentCategory ] ) ) then
		--if ( !self.Categories[ self.CurrentCategory ] ) then self:SelectCat( "Sandbox" ) return end
		self.Categories[ self.CurrentCategory ].Depressed = true
	end
end

function PANEL:SelectMap( map )
	self.CurrentMap = map
end

function PANEL:AddCategoryButton( parent, catClass, name )
	local button = parent:Add( "MenuCategoryButton" )
	button:Dock( TOP )
	button:DockMargin( 0, 1, 0, 0 )
	button:SetText( name )
	button.DoClick = function()
		g_CurrentScroll = nil
		self:SelectCat( catClass )
	end
	button:SetContentAlignment( 4 )
	button:SetTextInset( 5, 0 )
	button:SetCategory( catClass )
	--button:SetAlt( alt )
	button.NewGameMenu = self

	self.Categories[ catClass ] = button
	return button
end

function PANEL:DoSearch( txt )
	self.SearchText = txt
	if ( self.SearchText:Trim() == "" ) then self.SearchText = nil end

	self:SelectCat( self.CurrentCategory ) -- Refreshes the map list
end

function PANEL:ServerSettings_AddTextEntry( v, parent )

	local DTextEntry = vgui.Create( "MenuSettingsTextEntry", parent )
	DTextEntry:Dock( TOP )
	DTextEntry:SetFont( "DermaRobotoDefault" )
	DTextEntry:SetTall( 22 )
	DTextEntry:DockMargin( 0, 0, 0, 1 )
	if ( v.text ) then DTextEntry:SetText( v.text ) end
	if ( v.name ) then DTextEntry:SetConVar( v.name ) end
	if ( v.zOrder ) then DTextEntry:SetZPos( v.zOrder + 1 ) end
	if ( v.help ) then DTextEntry:SetTooltip( v.help ) end
	if ( v.singleplayer ) then DTextEntry.Singleplayer = true else DTextEntry.Singleplayer = false end
	return DTextEntry

end

function PANEL:ServerSettings_AddCheckbox( v, parent )

	local CheckBox = vgui.Create( "MenuSettingsCheckbox", parent )
	CheckBox:Dock( TOP )
	CheckBox:SetFont( "DermaRobotoDefault" )
	CheckBox:SetTall( 22 )
	CheckBox:DockMargin( 0, 0, 0, 1 )
	if ( v.name ) then CheckBox:SetConVar( v.name ) end
	if ( v.text ) then CheckBox:SetText( v.text ) end
	if ( v.zOrder ) then CheckBox:SetZPos( v.zOrder ) end
	if ( v.help ) then CheckBox:SetTooltip( v.help ) end
	if ( v.singleplayer ) then CheckBox.Singleplayer = true else CheckBox.Singleplayer = false end

	return CheckBox

end

function PANEL:ServerSettings_AddSlider( v, parent )
	local Slider = vgui.Create( "MenuSettingsSlider", parent )
	Slider:Dock( TOP )
	if ( v.name ) then Slider:SetConVar( v.name ) end
	if ( v.text ) then Slider:SetText( v.text ) end
	Slider:SetFont( "DermaRobotoDefault" )
	Slider:SetTall( 22 )
	Slider:DockMargin( 0, 0, 0, 1 )
	if ( v.zOrder ) then Slider:SetZPos( v.zOrder ) end
	if ( v.help ) then Slider:SetTooltip( v.help ) end
	if ( v.singleplayer ) then Slider.Singleplayer = true else Slider.Singleplayer = false end

	return Slider
end

function PANEL:UpdateLanguage()
	self.SingleMultiPlayer:SetTextGenerated( self.SingleMultiPlayer:GetValue() )

	for id, t in pairs( LocalizedShit ) do
		if ( !IsValid( t.panel ) ) then table.remove( LocalizedShit, id ) continue end -- Not too sure about the removal of the element inside the loop. Is Lua OK with this?
		t.panel:SetText( language.GetPhrase( t.text ) )
	end

	-- Update Favourites?
	-- Update whatever
end

function PANEL:Update()

	---------------------------------- Build Categories ----------------------------------

	local Categories = self.CategoriesPanel
	Categories:Clear()

	--self.Categories = {}

	local pergamemode = Categories:Add( "DLabel" )
	pergamemode:Dock( TOP )
	pergamemode:SetText( "Gamemodes" )
	pergamemode:SetFont( "StartNewGameFont" )
	pergamemode:SetContentAlignment( 5 )
	pergamemode:SetTextColor( HeaderColor )
	pergamemode:DockMargin( 0, 0, 0, 3 )

	for cat, name in SortedPairsByValue( GetMapCategories() ) do
		if ( cat == "Favourites" ) then continue end -- We have custom handling of this category
		self:AddCategoryButton( Categories, cat, name )
	end

	local games = Categories:Add( "DLabel" )
	games:Dock( TOP )
	games:SetText( "Games" )
	games:SetFont( "StartNewGameFont" )
	games:SetContentAlignment( 5 )
	games:SetTextColor( HeaderColor )
	games:DockMargin( 0, 5, 0, 3 )

	for cat, name in SortedPairsByValue( GetMapCategories( "game" ) ) do
		self:AddCategoryButton( Categories, cat, name )
	end

	--for i = 0, 10 do self:AddCategoryButton( Categories, "Filler " .. i, "Filler " .. i, i % 2 ) end

	---------------------------------- Build server settigns ----------------------------------

	local GamemodeSettings = self.GamemodeSettings
	GamemodeSettings:Clear()

	local settings_file = file.Read( "gamemodes/" .. engine.ActiveGamemode() .. "/" .. engine.ActiveGamemode() .. ".txt", true )

	if ( settings_file ) then

		local SettingsFile = util.KeyValuesToTable( settings_file )

		if ( SettingsFile.settings ) then
			local zOrder = 0

			for k, v in pairs( SettingsFile.settings ) do
				if ( v.type == "CheckBox" ) then
					v.zOrder = zOrder
					self:ServerSettings_AddCheckbox( v, GamemodeSettings )
				elseif ( v.type == "Text" ) then
					v.zOrder = zOrder
					self:ServerSettings_AddTextEntry( v, GamemodeSettings )
					zOrder = zOrder + 1 -- Account for the label
				elseif ( v.type == "Numeric" ) then
					v.zOrder = zOrder
					self:ServerSettings_AddSlider( v, GamemodeSettings )
				end

				zOrder = zOrder + 1
			end
		end
	end

	--------------------------------- Update Singleplayer / Multiplayer selector ---------------------------------

	self.SingleMultiPlayer:SetValue( GetConVarNumber( "cl_maxplayers" ) )

	--------------------------------- LOAD LAST MAP ---------------------------------

	local map, cat = LoadLastMap()

	if ( self.CurrentMap and self.CurrentCategory ) then -- We had some map selected, switch back to it!
		map = self.CurrentMap
		cat = self.CurrentCategory
	end

	if ( !DoesCategoryExist( cat ) ) then
		-- Try to find the category the map is in
		cat = GetMapCategory( map )
	end

	if ( !cat or !map ) then
		map = "gm_flatgrass"
		cat = "Sandbox"
	end

	self:SelectCat( cat )
	self:SelectMap( map )

end

function PANEL:BuildIconList()
	self.IconListCache = {}

	local files = file.Find( "maps/thumb/*.png", "GAME" )
	for id, filename in pairs( files ) do
		self.IconListCache[ filename:sub( 0, filename:len() - 4 ) ] = "maps/thumb/" .. filename
	end

	-- Stupid ass addons that didn't update yet
	local files2 = file.Find( "maps/*.png", "GAME" )
	for id, filename in pairs( files2 ) do
		self.IconListCache[ filename:sub( 0, filename:len() - 4 ) ] = "maps/" .. filename
	end
end

function PANEL:CacheIcon( map )
	if ( !self.IconListCache ) then self:BuildIconList() end

	map = map:Trim() -- Duplicate CS:GO maps have spaces on the end! ( When loaded from the HTML menu )
	--[[local mat = Material( "maps/thumb/" .. map .. ".png" )
	if ( mat:IsError() ) then mat = Material( "maps/" .. map .. ".png" ) end -- Stupid ass addons that didn't update yet
	if ( mat:IsError() ) then mat = matNoIcon end]]
	if ( !self.IconListCache[ map ] ) then return matNoIcon end
	return Material( self.IconListCache[ map ] )
end

local border = 4
local border_w = 5
local matHover = Material( "gui/sm_hover.png", "nocull" )
local boxHover = GWEN.CreateTextureBorder( border, border, 64 - border * 2, 64 - border * 2, border_w, border_w, border_w, border_w, matHover )

function PANEL:SelectCat( cat )

	if ( self.CurrentCategory and self.Categories[ self.CurrentCategory ] ) then self.Categories[ self.CurrentCategory ].Depressed = false end
	self.CurrentCategory = cat

	self.CategoryMaps:Clear()

	local maps = GetMapsFromCategorySearch( cat, self.SearchText )
	if ( !maps or #maps < 1 ) then return end

	local subCategories, subCategorieyPatterns = GetMapSubCategories()

	local categories = {}
	for _, map in SortedPairs( maps ) do
		local c = subCategories[ map.name ] or "Other"

		if ( !subCategories[ map.name ] ) then
			for pattern, cate in SortedPairs( subCategorieyPatterns ) do
				if ( string.find( map.name, pattern ) ) then
					if ( c != "Other" ) then print( "Multiple categories for 1 map!!!", map.name, c, cate ) end
					c = cate
				end
			end
		end

		categories[ c ] = categories[ c ] or {}
		table.insert( categories[ c ], map )
	end

	for cat_name, cat_maps in SortedPairs( categories ) do

		local catText = cat_name
		if ( catText:sub( 2, 3 ) == ". " ) then catText = catText:sub( 4 ) end

		local label = self.CategoryMaps:Add( "DLabel" )
		label.OwnLine = true
		label:SetTextColor( HeaderColor_mid )
		label:SetText( catText )
		label:SetFont( "rb655_MapSubCat" )
		label:SizeToContents()
		label:SetBright( true )

		for _, map in SortedPairsByMemberValue( cat_maps, "name" ) do

			local button = self.CategoryMaps:Add( "DImageButton" )
			button:SetText( map.name )

			-- Get rid of the shitty "clicled on" animation
			--button.OnMousePressed = function( s, mc ) DButton.OnMousePressed( s, mc ) end

			-- Handles above stuff too
			Menu_InstallDButtonScrollProtection( button, 2, true )

			if ( map.incompatible ) then
				button.m_Image:SetMaterial( matIncompat )
			else
				if ( !gMapIcons[ map.name ] ) then gMapIcons[ map.name ] = self:CacheIcon( map.name ) end
				button.m_Image:SetMaterial( gMapIcons[ map.name ] )
			end

			button:SetSize( 128, 128 )
			button.DoClick = function()
				self:SelectMap( map.name )
			end
			button.DoDoubleClick = function()
				self:SelectMap( map.name )
				self:LoadMap()
			end
			button.PaintOver = function( pnl, w, h )

				if ( pnl:GetText() == self.CurrentMap ) then
					boxHover( 0, 0, w, h, color_white )
				end

				if ( pnl.Hovered ) then return end

				surface.SetDrawColor( Color( 0, 0, 0, 150 ) )
				surface.DrawRect( 0, h - 20, w, 20 )

				surface.SetFont( "rb655_MapList" )

				local tw = surface.GetTextSize( pnl:GetText() )
				if ( tw > w ) then
					draw.SimpleText( pnl:GetText(), "rb655_MapList", w / 2 - tw / 2 + ( ( w - tw ) * math.sin( CurTime() ) ), h - 16, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
				else
					draw.SimpleText( pnl:GetText(), "rb655_MapList", w / 2 - tw / 2, h - 16, color_white, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER )
				end

			end

			button.DoRightClick = function()
				local m = DermaMenu()
				m:AddOption( "Toggle Favourite", function() ToggleFavourite( map.name ) end ):SetIcon( IsMapFavourite( map.name ) and "icon16/heart_delete.png" or "icon16/heart_add.png" )
				m:AddOption( "Copy to Clipboard", function() SetClipboardText( map.name ) end ):SetIcon( "icon16/page_copy.png" )
				m:AddOption( "Cancel", function() end )
				m:Open()
			end

		end
	end

	-- Scroll back to the top of the map list
	self.CategoryMaps:GetParent():GetParent():GetVBar():SetScroll( g_CurrentScroll or 0 )

end

function PANEL:LoadMap()

	local maxplayers = GetConVarNumber( "cl_maxplayers" ) or 1

	SaveLastMap( self.CurrentMap:Trim(), self.CurrentCategory )

	hook.Run( "StartGame" )
	RunConsoleCommand( "progress_enable" )

	RunConsoleCommand( "disconnect" )

	if ( maxplayers > 0 ) then

		RunConsoleCommand( "sv_cheats", "0" )
		--RunConsoleCommand( "commentary", "0" )

	end

	RunConsoleCommand( "maxplayers", maxplayers )
	RunConsoleCommand( "map", self.CurrentMap:Trim() )
	--RunConsoleCommand( "hostname", self.Settings.ServerName.TextEntry:GetText() )

	pnlMainMenu:Back()

end

vgui.Register( "NewGamePanel", PANEL, "EditablePanel" )


================================================
FILE: lua/menu/custom/new_game_panels.lua
================================================

function Menu_InstallDButtonScrollProtection( pnl, depth, allowM2 )
	depth = depth or 1
	allowM2 = allowM2 or false

	function pnl:OnMousePressed( mc )
		DLabel.OnMousePressed( self, mc ) -- Should use baseclass here tbh

		-- Only left mouse button can cause the animations and shit
		if ( mc != MOUSE_LEFT and !allowM2 ) then
			self.Depressed = nil
		end

		-- This is a shitty hack, but I dont see another way
		local parent = self
		for i = 1, depth do parent = parent:GetParent() end
		self.ClickStartX, self.ClickStartY = parent:GetPos()
	end

	function pnl:Think()
		local parent = self
		for i = 1, depth do parent = parent:GetParent() end
		local x, y = parent:GetPos()
		if ( self.ClickStartY and ( self.Hovered or self.Depressed ) and math.abs( self.ClickStartY - y ) > self:GetTall() / 2 ) then
			self.Depressed = nil -- Disable animations, stop DoClick from working
			self.ClickStartX, self.ClickStartY = nil, nil
		end
	end

	function pnl:OnMouseReleased( mc )
		self.ClickStartX, self.ClickStartY = nil, nil

		DLabel.OnMouseReleased( self, mc ) -- Should use baseclass here tbh
	end
end

-- TODO: Localisation
local function DrawToolTip( self, w, h )
	if ( !self:GetTooltip() ) then return end
	if ( !( self.Hovered or self.IsHovered and self:IsHovered() ) ) then return end

	local font = self.GetFont and self:GetFont() or "DermaRobotoDefault"
	surface.SetFont( font )

	local texts = { tostring( self:GetTooltip() ) }

	local tW, tH = surface.GetTextSize( texts[ #texts ] )
	local x = -tW - 25
	local screenX = self:LocalToScreen( x, 0 )
	local minX = ScrW() / 4
	while ( screenX < minX ) do -- Probably could do with caching
		local LastText = texts[ #texts ]

		local LastSpace = 0
		for i = 1, #LastText do
			if ( LastText:sub( i, i ) == " " ) then LastSpace = i end

			local txtW, txtH = surface.GetTextSize( LastText:sub( 0, i ) )
			local tempX = self:LocalToScreen( -txtW - 25, 0 )
			if ( tempX < minX ) then -- This is probably wrong. But it works in my tests
				if ( LastSpace == 0 ) then LastSpace = i end

				texts[ #texts ] = LastText:sub( 0, LastSpace )
				table.insert( texts, LastText:sub( LastSpace + 1 ) )

				local txtW2, txtH2 = surface.GetTextSize( texts[ #texts ] )
				screenX = self:LocalToScreen( -txtW2 - 25, 0 )
				break
			end
		end
	end

	DisableClipping( true )
	draw.NoTexture()
	surface.SetDrawColor( Color( 0, 128, 255 ) )
	surface.DrawPoly( {
		{ x = -15, y = 0 },
		{ x = -5, y = h / 2 },
		{ x = -15, y = h }
	} )

	local targetW = 0
	for id, txt in pairs( texts ) do
		local txtW, txtH = surface.GetTextSize( txt )
		targetW = math.max( targetW, txtW )
	end
	local targetX = -targetW - 25

	local boxH = math.max( h, ( h - tH ) + tH * #texts )
	surface.DrawRect( targetX, 0, -15 - targetX, boxH )
	for id, txt in pairs( texts ) do
		draw.SimpleText( txt, font, targetX + 5, h / 2 + ( id - 1 ) * tH, color_white, 0, 1 )
	end
	DisableClipping( false )
end

---------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------- MenuCategoryButton ------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------

local PANEL = {}

function PANEL:GetMapCount()
	if ( !self.Category ) then return -1 end

	if ( !self.LastCache or self.LastCache < CurTime() or self.NewGameMenu.SearchText != self.SearchText ) then
		self.CachedMapCount = #GetMapsFromCategorySearch( self.Category, self.NewGameMenu.SearchText )
		self.LastCache = CurTime() + 1
		self.SearchText = self.NewGameMenu.SearchText
	end

	return self.CachedMapCount
end

--[[function PANEL:SetAlt( b )
	self.Alt = b
end]]

Menu_InstallDButtonScrollProtection( PANEL )

function PANEL:SetCategory( cat )
	self.Category = cat
	self:SetFont( "DermaRobotoDefault" )
	self:SetTextInset( 5, 1 )
end

function PANEL:Paint( w, h )
	local count = self:GetMapCount()

	local clr = Color( 255, 255, 255 )
	local clr2 = Color( 245, 245, 245 )
	local clr_t = Color( 85, 85, 85 )
	--[[if ( self.Alt ) then
		clr = Color( 253, 253, 253 )
	end]]
	if ( self.Hovered ) then
		clr2 = Color( 230, 230, 230 )
		clr = Color( 240, 240, 240 )
	end
	if ( self.Depressed ) then
		clr_t = color_white
		clr = Color( 35, 150, 255 )
		clr2 = Color( 26, 112, 191 )
	end
	self:SetFGColor( clr_t )

	if ( count and count > 0 ) then
		surface.SetFont( self:GetFont() )
		local tW, tH = surface.GetTextSize( tostring( count ) )
		local bW = math.max( tW, 30 ) + 6
		local tX = w - bW + bW / 2

		surface.SetDrawColor( clr )
		surface.DrawRect( 0, 0, w - bW, h )

		surface.SetDrawColor( clr2 )
		surface.DrawRect( w - bW, 0, bW, h )

		draw.SimpleText( count, self:GetFont(), tX, h / 2, clr_t, 1, 1 )
	else
		surface.SetDrawColor( clr )
		surface.DrawRect( 0, 0, w, h )
	end
end

vgui.Register( "MenuCategoryButton", PANEL, "DButton" )

---------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------- MenuSettingsCheckbox ----------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------

local PANEL = {}

AccessorFunc( PANEL, "m_bChecked", "Checked", FORCE_BOOL )
AccessorFunc( PANEL, "m_sConVar", "ConVar", FORCE_STRING )
AccessorFunc( PANEL, "m_sText", "Text", FORCE_STRING )
AccessorFunc( PANEL, "m_sFont", "Font", FORCE_STRING )
AccessorFunc( PANEL, "m_sToolTip", "Tooltip", FORCE_STRING )

function PANEL:Init()
	self:SetMouseInputEnabled( true )
	self:SetChecked( false )

	self.ThinkBlock = 0 -- HACK
end

local width = 40
function PANEL:OnMousePressed( mcode )
	if ( mcode != MOUSE_LEFT ) then return end

	local x, y = self:LocalCursorPos()
	if ( self:GetWide() - width > x ) then return end

	self.Depressed = true
end

function PANEL:OnMouseReleased( mcode )
	if ( mcode != MOUSE_LEFT ) then return end

	local x, y = self:LocalCursorPos()
	if ( self:GetWide() - width > x ) then return end

	self.Depressed = false

	self:SetChecked( !self:GetChecked() )
end

function PANEL:Think()
	if ( self:GetConVar() and self.ThinkBlock < CurTime() ) then
		if ( GetConVarNumber( self:GetConVar() ) > 0 ) then self:SetChecked( true ) else self:SetChecked( false ) end
	end
end

function PANEL:SetChecked( b )
	if ( self.m_bChecked == b ) then return end

	self.m_bChecked = b
	if ( self:GetConVar() ) then
		RunConsoleCommand( self:GetConVar(), self:GetChecked() and "1" or "0" )
		self.ThinkBlock = CurTime() + 1 -- HACK: we gotta let the command buffer to go through..
	end

	if ( self.OnValueChanged ) then self:OnValueChanged( b ) end
end

local checkboxMat = Material( "gui/check.png" )
function PANEL:Paint( w, h )
	local x, y = self:LocalCursorPos()

	surface.SetDrawColor( Color( 255, 255, 255, 255 ) )
	if ( self.Hovered and self:GetWide() - width > x ) then surface.SetDrawColor( Color( 253, 253, 253, 255 ) ) end
	surface.DrawRect( 0, 0, w - width - 1, h )

	surface.SetDrawColor( Color( 245, 245, 245, 255 ) )
	if ( self:GetChecked() ) then surface.SetDrawColor( Color( 240, 255, 240, 200 ) ) end

	if ( self.Hovered and self:GetWide() - width < x ) then surface.SetDrawColor( Color( 230, 230, 230, 255 ) ) end
	if ( self.Depressed and self:GetWide() - width < x ) then surface.SetDrawColor( Color( 210, 210, 210, 255 ) ) end

	surface.DrawRect( w - width, 0, width, h )

	surface.SetDrawColor( Color( 0, 0, 0, 10 ) )
	if ( self:GetChecked() ) then surface.SetDrawColor( Color( 0, 200, 0, 255 ) ) end
	surface.SetMaterial( checkboxMat )
	surface.DrawTexturedRect( w - width / 2- h / 2, 0, h, h )

	if ( self:GetText() ) then
		draw.SimpleText( language.GetPhrase( self:GetText() ), self:GetFont(), 5, h / 2, color_black, 0, 1 )
	end

	DrawToolTip( self, w, h )
end

vgui.Register( "MenuSettingsCheckbox", PANEL, "Panel" )

---------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------- MenuSettingsSlider -----------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------

local PANEL = {}

AccessorFunc( PANEL, "m_sConVar", "ConVar", FORCE_STRING )
AccessorFunc( PANEL, "m_sText", "Text", FORCE_STRING )
AccessorFunc( PANEL, "m_sFont", "Font", FORCE_STRING )
AccessorFunc( PANEL, "m_sToolTip", "Tooltip", FORCE_STRING )
AccessorFunc( PANEL, "m_nMin", "Min", FORCE_NUMBER )
AccessorFunc( PANEL, "m_nMax", "Max", FORCE_NUMBER )

function PANEL:Init()
	self:SetMouseInputEnabled( true )
	self:SetKeyboardInputEnabled( true )

	self.TextEntry = vgui.Create( "DTextEntry", self )
	self.TextEntry:Dock( RIGHT )
	self.TextEntry:SetUpdateOnType( true )
	self.TextEntry:SetNumeric( true )
	self.TextEntry:SetWide( 40 )
	self.TextEntry.OnValueChange = function( s )
		if ( self:GetConVar() ) then RunConsoleCommand( self:GetConVar(), s:GetText() ) end
	end
	function self.TextEntry:Paint( w, h )

		surface.SetDrawColor( Color( 245, 245, 245, 255 ) )
		if ( self.Hovered ) then surface.SetDrawColor( Color( 230, 230, 230, 255 ) ) end
		surface.DrawRect( 0, 0, w, h )

		self:DrawTextEntryText( self:GetTextColor(), self:GetHighlightColor(), self:GetCursorColor() )
	end
end

function PANEL:IsHovered()
	return self.Hovered or self.TextEntry:IsHovered()
end

function PANEL:SetFont( font )
	self.m_sFont = font
	self.TextEntry:SetFont( font )
end
function PANEL:SetMinMax( min, max ) self:SetMin( min ) self:SetMax( max ) end
function PANEL:Think()
	if ( !self.TextEntry:IsEditing() and self:GetConVar() and GetConVarNumber( self:GetConVar() ) and GetConVarNumber( self:GetConVar() ) != tonumber( self.TextEntry:GetText() ) ) then
		self.TextEntry:SetText( GetConVarNumber( self:GetConVar() ) )
	end
end

function PANEL:Paint( w, h )
	local x, y = self:LocalCursorPos()
	surface.SetDrawColor( Color( 255, 255, 255, 255 ) )
	if ( self.Hovered and self:GetWide() - width > x ) then surface.SetDrawColor( Color( 253, 253, 253, 255 ) ) end
	surface.DrawRect( 0, 0, w - self.TextEntry:GetWide() - 1, h )

	if ( self:GetText() ) then
		draw.SimpleText( language.GetPhrase( self:GetText() ), self:GetFont(), 5, h / 2, color_black, 0, 1 )
	end

	DrawToolTip( self, w, h )
end

vgui.Register( "MenuSettingsSlider", PANEL, "Panel" )

---------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------ MenuSettingsTextEntry ----------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------

local PANEL = {}

AccessorFunc( PANEL, "m_sConVar", "ConVar", FORCE_STRING )
AccessorFunc( PANEL, "m_sText", "Text", FORCE_STRING )
AccessorFunc( PANEL, "m_sFont", "Font", FORCE_STRING )
AccessorFunc( PANEL, "m_sToolTip", "Tooltip", FORCE_STRING )

function PANEL:Init()
	self:SetMouseInputEnabled( true )

	self.TextEntry = vgui.Create( "DTextEntry", self )
	self.TextEntry:Dock( RIGHT )
	self.TextEntry:SetUpdateOnType( true )
	self.TextEntry.OnValueChange = function( s )
		if ( self:GetConVar() ) then RunConsoleCommand( self:GetConVar(), s:GetText() ) end
	end
	function self.TextEntry:Paint( w, h )
		surface.SetDrawColor( Color( 245, 245, 245, 255 ) )
		if ( self.Hovered ) then surface.SetDrawColor( Color( 230, 230, 230, 255 ) ) end
		surface.DrawRect( 0, 0, w, h )

		self:DrawTextEntryText( self:GetTextColor(), self:GetHighlightColor(), self:GetCursorColor() )
	end
end

function PANEL:IsHovered()
	return self.Hovered or self.TextEntry:IsHovered()
end

function PANEL:SetFont( font )
	self.m_sFont = font
	self.TextEntry:SetFont( font )
end

function PANEL:Think()
	if ( !self.TextEntry:IsEditing() and self:GetConVar() and GetConVarString( self:GetConVar() ) and GetConVarString( self:GetConVar() ) != self.TextEntry:GetText() ) then
		self.TextEntry:SetText( GetConVarString( self:GetConVar() ) )
	end
end

function PANEL:Paint( w, h )
	local x, y = self:LocalCursorPos()
	surface.SetDrawColor( Color( 255, 255, 255, 255 ) )
	if ( self.Hovered and self:GetWide() - width > x ) then surface.SetDrawColor( Color( 253, 253, 253, 255 ) ) end
	surface.DrawRect( 0, 0, w - self.TextEntry:GetWide() - 1, h )

	if ( self:GetText() ) then
		local tW = draw.SimpleText( language.GetPhrase( self:GetText() ), self:GetFont(), 5, h / 2, color_black, 0, 1 )
		self.TextEntry:SetWide( self:GetWide() - tW - 12 )
	end

	DrawToolTip( self, w, h )
end

vgui.Register( "MenuSettingsTextEntry", PANEL, "Panel" )

---------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------- DFancyTextEntry --------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------------------------

local PANEL = {}

AccessorFunc( PANEL, "m_sPlaceholder", "PlaceholderText", FORCE_STRING )
AccessorFunc( PANEL, "m_sToolTip", "Tooltip", FORCE_STRING )

function PANEL:Init()
	self:SetHighlightColor( Color( 35, 150, 255 ) )
end

function PANEL:Paint( w, h )
	surface.SetDrawColor( Color( 245, 245, 245, 255 ) )
	if ( self.Hovered ) then surface.SetDrawColor( Color( 230, 230, 230, 255 ) ) end
	surface.DrawRect( 0, 0, w, h )

	self:DrawTextEntryText( self:GetTextColor(), self:GetHighlightColor(), self:GetCursorColor() )

	if ( !self:GetText() or self:GetText():Trim():len() < 1 ) then
		draw.SimpleText( language.GetPhrase( self:GetPlaceholderText() ), self:GetFont(), 5, h / 2, Vector( 1, 1, 1 ) * 150, nil, 1 )
	end

	DrawToolTip( self, w, h )
end

vgui.Register( "DFancyTextEntry", PANEL, "DTextEntry" )


================================================
FILE: lua/menu/custom/saves.lua
================================================

local PANEL = {}

function PANEL:Init()
	self:Dock( FILL )
end

function PANEL:SetType( typ )
	self.Type = typ

	self:UpdateList()
end

function PANEL:UpdateList()
	self:Clear()

	local Scroll = vgui.Create( "DScrollPanel", self )
	Scroll:Dock( FILL )
	Scroll:DockMargin( 5, 5, 5, 5 )

	local List = vgui.Create( "DIconLayout", Scroll )
	List:Dock( FILL )
	List:SetSpaceY( 5 )
	List:SetSpaceX( 5 )

	local f = nil
	if ( self.Type == "saves" ) then
		f = file.Find( "saves/*.gms", "MOD", "datedesc" )
	elseif ( self.Type == "demos" ) then
		f = file.Find( "demos/*.dem", "MOD", "datedesc" )
	elseif ( self.Type == "dupes" ) then
		f = file.Find( "dupes/*.dupe", "MOD", "datedesc" )
	end

	for k, v in pairs( f ) do
		local ListItem = List:Add( "DImageButton" )
		ListItem:SetSize( 128, 128 )
		ListItem:SetImage( self.Type .. "/" .. v:StripExtension() .. ".jpg" )
		ListItem.DoDoubleClick = function()
			if ( self.Type == "saves" ) then
				RunConsoleCommand( "gm_load", "saves/" .. v )
			elseif ( self.Type == "demos" ) then
				RunConsoleCommand( "playdemo", "demos/" .. v )
			end
		end
		ListItem.DoRightClick = function()
			local m = DermaMenu()

			if ( self.Type == "saves" ) then
				m:AddOption( "Load", function() RunConsoleCommand( "gm_load", "saves/" .. v ) end )
			elseif ( self.Type == "demos" ) then
				m:AddOption( "Play", function() RunConsoleCommand( "playdemo", "demos/" .. v ) end )
			end

			if ( self.Type == "demos" ) then
				m:AddOption( "Demo To Video", function() RunConsoleCommand( "gm_demo_to_video", "demos/" .. v ) end )
			end
			m:AddOption( "Delete", function()
				file.Delete( self.Type .. "/" .. v, "MOD" )
				file.Delete( self.Type .. "/" .. v:StripExtension() .. ".jpg", "MOD" )
				self:UpdateList()
			end )
			m:AddOption( "Cancel" )
			m:Open()
		end
	end
end

function PANEL:Paint( w, h )
	surface.SetDrawColor( 0, 0, 0, 150 )
	surface.DrawRect( 0, 0, w, h )
end

vgui.Register( "SavesPanel", PANEL, "EditablePanel" )


================================================
FILE: lua/menu/menu.lua
================================================

include( "mount/mount.lua" )
include( "getmaps.lua" )
include( "loading.lua" )

-- To uninstall, change the 1 to 0 below
local useNewMenu = 1

-- Do not touch anything below
if ( useNewMenu == 1 ) then
	include( "custom/mainmenu.lua" )
else
	include( "mainmenu.lua" )
end

include( "video.lua" )
include( "demo_to_video.lua" )

include( "menu_save.lua" )
include( "menu_demo.lua" )
include( "menu_addon.lua" )
include( "menu_dupe.lua" )
include( "errors.lua" )
include( "problems/problems.lua" )

include( "motionsensor.lua" )
include( "util.lua" )
Download .txt
gitextract_c9uuf8d0/

├── CONTRIBUTING.md
├── README.md
└── lua/
    └── menu/
        ├── custom/
        │   ├── _errors.lua
        │   ├── achievements.lua
        │   ├── addons.lua
        │   ├── getmaps.lua
        │   ├── main.lua
        │   ├── mainmenu.lua
        │   ├── new_game.lua
        │   ├── new_game_panels.lua
        │   └── saves.lua
        └── menu.lua
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (116K chars).
[
  {
    "path": "CONTRIBUTING.md",
    "chars": 1202,
    "preview": "Contributing to Lua Main Menu\n=============\n\nHere's what you need to know if you wish to submit Pull Requests to this re"
  },
  {
    "path": "README.md",
    "chars": 1349,
    "preview": "Garry's Mod Lua Main Menu\n=============\n\nA Lua powered ( No HTML ) main menu for Garry's Mod.\nIt is meant for those who "
  },
  {
    "path": "lua/menu/custom/_errors.lua",
    "chars": 1561,
    "preview": "\nlocal Errors = {}\n\nlocal matAlert = Material( \"icon16/error.png\" )\n\nhook.Add( \"DrawOverlay\", \"MenuErrors\", function()\n\n"
  },
  {
    "path": "lua/menu/custom/achievements.lua",
    "chars": 3722,
    "preview": "\nlocal PANEL = {}\n\nfunction PANEL:Init()\n\tself.AchID = 0\n\n\tself:SetTall( 72 )\n\n\tself.Icon = vgui.Create( \"AchievementIco"
  },
  {
    "path": "lua/menu/custom/addons.lua",
    "chars": 14097,
    "preview": "\nsurface.CreateFont( \"rb655_AddonName\", {\n\tsize = ScreenScale( 12 ),\n\tfont = \"Tahoma\"\n} )\n\nsurface.CreateFont( \"rb655_Ad"
  },
  {
    "path": "lua/menu/custom/getmaps.lua",
    "chars": 20968,
    "preview": "\nlocal RefreshMaps\n\n--\n-- Favourites\n--\n\nlocal MapFavourites\n\nlocal function LoadFavourites()\n\n\tlocal cookiestr = cookie"
  },
  {
    "path": "lua/menu/custom/main.lua",
    "chars": 4776,
    "preview": "\n-- Developer stuff\nconcommand.Add( \"lua\", function( ply, cmd, args, str )\n\tif ( IsInGame() ) then return end\n\tRunString"
  },
  {
    "path": "lua/menu/custom/mainmenu.lua",
    "chars": 15844,
    "preview": "\nScreenScale = function( size ) return size * ( ScrW() / 640.0 ) end\n\ninclude( \"getmaps.lua\" )\ninclude( \"addons.lua\" )\ni"
  },
  {
    "path": "lua/menu/custom/new_game.lua",
    "chars": 25936,
    "preview": "\ninclude( \"new_game_panels.lua\" )\n\nCreateConVar( \"cl_maxplayers\", \"1\", FCVAR_ARCHIVE )\n\n--[[\nTODO:\nmake sure the text of"
  },
  {
    "path": "lua/menu/custom/new_game_panels.lua",
    "chars": 14212,
    "preview": "\nfunction Menu_InstallDButtonScrollProtection( pnl, depth, allowM2 )\n\tdepth = depth or 1\n\tallowM2 = allowM2 or false\n\n\tf"
  },
  {
    "path": "lua/menu/custom/saves.lua",
    "chars": 1969,
    "preview": "\nlocal PANEL = {}\n\nfunction PANEL:Init()\n\tself:Dock( FILL )\nend\n\nfunction PANEL:SetType( typ )\n\tself.Type = typ\n\n\tself:U"
  },
  {
    "path": "lua/menu/menu.lua",
    "chars": 550,
    "preview": "\ninclude( \"mount/mount.lua\" )\ninclude( \"getmaps.lua\" )\ninclude( \"loading.lua\" )\n\n-- To uninstall, change the 1 to 0 belo"
  }
]

About this extraction

This page contains the full source code of the robotboy655/gmod-lua-menu GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (103.7 KB), approximately 33.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!