From de8af2af39323e546fd3472b90d53128d927b4ee Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Thu, 31 Oct 2024 15:23:49 -0400
Subject: [PATCH 1/6] Allow addons to add options to SpawnIcon and ContentIcon
 derma menu

---
 .../creationmenu/content/contenticon.lua      | 14 +++++++++++
 garrysmod/lua/derma/derma_menus.lua           | 23 ++++++++++++++++++-
 garrysmod/lua/vgui/spawnicon.lua              | 15 ++++++++++++
 3 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
index b73281181b..ac40220b24 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
@@ -94,8 +94,22 @@ function PANEL:DoRightClick()
 		return hook.Run( "SpawnlistOpenGenericMenu", pCanvas )
 	end
 
+	local openedMenus = GetOpenDermaMenus()
+	local menuCount = #openedMenus
+
 	self:OpenMenu()
 
+	local menu = openedMenus[ menuCount + 1 ]
+
+	--
+	-- Allow addons to easily add their own options to the opened menu
+	--
+	if ( IsValid( menu ) ) then
+
+		hook.Run( "OnContentIconOpenMenu", self, menu )
+
+	end
+
 end
 
 function PANEL:DoClick()
diff --git a/garrysmod/lua/derma/derma_menus.lua b/garrysmod/lua/derma/derma_menus.lua
index 9945b3a651..eea4862918 100644
--- a/garrysmod/lua/derma/derma_menus.lua
+++ b/garrysmod/lua/derma/derma_menus.lua
@@ -1,6 +1,26 @@
 
 local tblOpenMenus = {}
 
+function GetOpenDermaMenus()
+
+	--
+	-- Clear the table of any removed derma menus
+	-- Each option in a derma menu is another derma menu itself
+	--
+	for k, menu in ipairs( tblOpenMenus ) do
+
+		if ( !IsValid( menu ) ) then
+
+			table.remove( tblOpenMenus, k )
+
+		end
+		
+	end
+
+	return tblOpenMenus
+
+end
+
 function RegisterDermaMenuForClose( dmenu )
 
 	table.insert( tblOpenMenus, dmenu )
@@ -30,9 +50,10 @@ function CloseDermaMenus()
 
 		end
 
+		tblOpenMenus[ k ] = nil
+
 	end
 
-	tblOpenMenus = {}
 	hook.Run( "CloseDermaMenus" )
 
 end
diff --git a/garrysmod/lua/vgui/spawnicon.lua b/garrysmod/lua/vgui/spawnicon.lua
index 3d3a672e8c..caa140b448 100644
--- a/garrysmod/lua/vgui/spawnicon.lua
+++ b/garrysmod/lua/vgui/spawnicon.lua
@@ -28,8 +28,23 @@ function PANEL:DoRightClick()
 	if ( IsValid( pCanvas ) && pCanvas:NumSelectedChildren() > 0 && self:IsSelected() ) then
 		return hook.Run( "SpawnlistOpenGenericMenu", pCanvas )
 	end
+	
+	local openedMenus = GetOpenDermaMenus()
+	local menuCount = #openedMenus
 
 	self:OpenMenu()
+
+	local menu = openedMenus[ menuCount + 1 ]
+
+	--
+	-- Allow addons to easily add their own options to the opened menu
+	--
+	if ( IsValid( menu ) ) then
+
+		hook.Run( "OnSpawnIconOpenMenu", self, menu )
+
+	end
+
 end
 
 function PANEL:DoClick()

From dacb2277130f5217d494e8efcf7f949df107a920 Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Thu, 31 Oct 2024 16:55:27 -0400
Subject: [PATCH 2/6] Add sub category headers for NPCs, Entities, and Vehicles

---
 .../content/contenttypes/entities.lua         | 72 ++++++++++++++---
 .../content/contenttypes/npcs.lua             | 72 ++++++++++++++---
 .../content/contenttypes/vehicles.lua         | 78 +++++++++++++++----
 .../content/contenttypes/weapons.lua          | 66 +++++++++++++---
 .../lua/includes/modules/scripted_ents.lua    |  1 +
 garrysmod/lua/includes/modules/weapons.lua    |  1 +
 6 files changed, 244 insertions(+), 46 deletions(-)

diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
index 9804f5bcbd..0db7f7ceba 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
@@ -9,24 +9,33 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 
 	-- Add this list into the tormoil
 	local SpawnableEntities = list.Get( "SpawnableEntities" )
+
 	if ( SpawnableEntities ) then
+		
 		for k, v in pairs( SpawnableEntities ) do
 
 			local Category = v.Category or "Other"
-			if ( !isstring( Category ) ) then Category = tostring( Category ) end
-			Categorised[ Category ] = Categorised[ Category ] or {}
+			Category = tostring( Category )
+	
+			local SubCategory = v.SubCategory or "Other"
+			SubCategory = tostring( SubCategory )
 
 			v.SpawnName = k
-			table.insert( Categorised[ Category ], v )
+	
+			Categorised[ Category ] = Categorised[ Category ] or {}
+			Categorised[ Category ][ SubCategory ] = Categorised[ Category ][ SubCategory ] or {}
+	
+			table.insert( Categorised[ Category ][ SubCategory ], v )
 
 		end
+
 	end
 
 	--
 	-- Add a tree node for each category
 	--
 	local CustomIcons = list.Get( "ContentCategoryIcons" )
-	for CategoryName, v in SortedPairs( Categorised ) do
+	for CategoryName, subCategories in SortedPairs( Categorised ) do
 
 		-- Add a node to the tree
 		local node = tree:AddNode( CategoryName, CustomIcons[ CategoryName ] or "icon16/bricks.png" )
@@ -42,14 +51,55 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 			self.PropPanel:SetVisible( false )
 			self.PropPanel:SetTriggerSpawnlistChange( false )
 
-			for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do
+			local createOtherHeader = false
+
+			for subCategoryName, tab in SortedPairs( subCategories ) do
+
+				if ( subCategoryName == "Other" ) then continue end
+
+				local label = vgui.Create( "ContentHeader" )
+
+				label:SetText( subCategoryName )
+
+				self.PropPanel:Add( label )
+
+				for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
+
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
+						nicename	= ent.PrintName or ent.ClassName,
+						spawnname	= ent.SpawnName,
+						material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
+						admin		= ent.AdminOnly
+					} )
+	
+				end
+
+				createOtherHeader = true
+
+			end
+
+			if ( subCategories.Other ) then
+
+				if ( createOtherHeader ) then
+
+					local label = vgui.Create( "ContentHeader" )
+
+					label:SetText( "Other" )
+
+					self.PropPanel:Add( label )
+
+				end
+
+				for k, ent in SortedPairsByMemberValue( subCategories.Other, "PrintName" ) do
 
-				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-					nicename	= ent.PrintName or ent.ClassName,
-					spawnname	= ent.SpawnName,
-					material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
-					admin		= ent.AdminOnly
-				} )
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
+						nicename	= ent.PrintName or ent.ClassName,
+						spawnname	= ent.SpawnName,
+						material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
+						admin		= ent.AdminOnly
+					} )
+	
+				end
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
index a8a37fffcb..281dfb613a 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
@@ -6,20 +6,28 @@ hook.Add( "PopulateNPCs", "AddNPCContent", function( pnlContent, tree, browseNod
 
 	-- Categorize them
 	local Categories = {}
+
 	for k, v in pairs( NPCList ) do
 
 		local Category = v.Category or "Other"
-		if ( !isstring( Category ) ) then Category = tostring( Category ) end
+		Category = tostring( Category )
+
+		local SubCategory = v.SubCategory or "Other"
+		SubCategory = tostring( SubCategory )
 
 		local Tab = Categories[ Category ] or {}
-		Tab[ k ] = v
+		local subCategoryTab = Tab[ SubCategory ] or {}
+
+		subCategoryTab[ k ] = v
+
+		Tab[ SubCategory ] = subCategoryTab
 		Categories[ Category ] = Tab
 
 	end
 
 	-- Create an icon for each one and put them on the panel
 	local CustomIcons = list.Get( "ContentCategoryIcons" )
-	for CategoryName, v in SortedPairs( Categories ) do
+	for CategoryName, subCategories in SortedPairs( Categories ) do
 
 		-- Add a node to the tree
 		local node = tree:AddNode( CategoryName, CustomIcons[ CategoryName ] or "icon16/monkey.png" )
@@ -35,15 +43,57 @@ hook.Add( "PopulateNPCs", "AddNPCContent", function( pnlContent, tree, browseNod
 			self.PropPanel:SetVisible( false )
 			self.PropPanel:SetTriggerSpawnlistChange( false )
 
-			for name, ent in SortedPairsByMemberValue( v, "Name" ) do
+			local createOtherHeader = false
+
+			for subCategoryName, tab in SortedPairs( subCategories ) do
+
+				if ( subCategoryName == "Other" ) then continue end
+
+				local label = vgui.Create( "ContentHeader" )
+
+				label:SetText( subCategoryName )
+
+				self.PropPanel:Add( label )
+
+				for name, ent in SortedPairsByMemberValue( tab, "Name" ) do
+
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", self.PropPanel, {
+						nicename	= ent.Name or name,
+						spawnname	= name,
+						material	= ent.IconOverride or "entities/" .. name .. ".png",
+						weapon		= ent.Weapons,
+						admin		= ent.AdminOnly
+					} )
+
+				end
+
+				createOtherHeader = true
+
+			end
+
+			if ( subCategories.Other ) then
+
+				if ( createOtherHeader ) then
+
+					local label = vgui.Create( "ContentHeader" )
+
+					label:SetText( "Other" )
+
+					self.PropPanel:Add( label )
+
+				end
+
+				for name, ent in SortedPairsByMemberValue( subCategories.Other, "Name" ) do
+
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", self.PropPanel, {
+						nicename	= ent.Name or name,
+						spawnname	= name,
+						material	= ent.IconOverride or "entities/" .. name .. ".png",
+						weapon		= ent.Weapons,
+						admin		= ent.AdminOnly
+					} )
 
-				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", self.PropPanel, {
-					nicename	= ent.Name or name,
-					spawnname	= name,
-					material	= ent.IconOverride or "entities/" .. name .. ".png",
-					weapon		= ent.Weapons,
-					admin		= ent.AdminOnly
-				} )
+				end
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
index 4d5b157564..5f33b616ee 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
@@ -1,35 +1,44 @@
 
 hook.Add( "PopulateVehicles", "AddEntityContent", function( pnlContent, tree, browseNode )
 
-	local Categorised = {}
-
 	-- Add this list into the tormoil
 	local Vehicles = list.Get( "Vehicles" )
+	local Categorised = {}
+
 	if ( Vehicles ) then
+
 		for k, v in pairs( Vehicles ) do
 
 			local Category = v.Category or "Other"
-			if ( !isstring( Category ) ) then Category = tostring( Category ) end
-			Categorised[ Category ] = Categorised[ Category ] or {}
+			Category = tostring( Category )
+	
+			local SubCategory = v.SubCategory or "Other"
+			SubCategory = tostring( SubCategory )
 
 			v.ClassName = k
 			v.PrintName = v.Name
 			v.ScriptedEntityType = "vehicle"
-			table.insert( Categorised[ Category ], v )
+
+			Categorised[ Category ] = Categorised[ Category ] or {}
+			Categorised[ Category ][ SubCategory ] = Categorised[ Category ][ SubCategory ] or {}
+
+			table.insert( Categorised[ Category ][ SubCategory ], v )
 
 		end
+
 	end
 
 	--
 	-- Add a tree node for each category
 	--
 	local CustomIcons = list.Get( "ContentCategoryIcons" )
-	for CategoryName, v in SortedPairs( Categorised ) do
+
+	for CategoryName, subCategories in SortedPairs( Categorised ) do
 
 		-- Add a node to the tree
 		local node = tree:AddNode( CategoryName, CustomIcons[ CategoryName ] or "icon16/bricks.png" )
 
-			-- When we click on the node - populate it using this function
+		-- When we click on the node - populate it using this function
 		node.DoPopulate = function( self )
 
 			-- If we've already populated it - forget it.
@@ -40,14 +49,55 @@ hook.Add( "PopulateVehicles", "AddEntityContent", function( pnlContent, tree, br
 			self.PropPanel:SetVisible( false )
 			self.PropPanel:SetTriggerSpawnlistChange( false )
 
-			for k, ent in SortedPairsByMemberValue( v, "PrintName" ) do
+			local createOtherHeader = false
+
+			for subCategoryName, tab in SortedPairs( subCategories ) do
+
+				if ( subCategoryName == "Other" ) then continue end
+
+				local label = vgui.Create( "ContentHeader" )
+
+				label:SetText( subCategoryName )
+
+				self.PropPanel:Add( label )
+
+				for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
+
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
+						nicename	= ent.PrintName or ent.ClassName,
+						spawnname	= ent.ClassName,
+						material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
+						admin		= ent.AdminOnly
+					} )
+	
+				end
+
+				createOtherHeader = true
+
+			end
+
+			if ( subCategories.Other ) then
+
+				if ( createOtherHeader ) then
+
+					local label = vgui.Create( "ContentHeader" )
+
+					label:SetText( "Other" )
+
+					self.PropPanel:Add( label )
+
+				end
+
+				for k, ent in SortedPairsByMemberValue( subCategories.Other, "PrintName" ) do
 
-				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-					nicename	= ent.PrintName or ent.ClassName,
-					spawnname	= ent.ClassName,
-					material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
-					admin		= ent.AdminOnly
-				} )
+					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
+						nicename	= ent.PrintName or ent.ClassName,
+						spawnname	= ent.ClassName,
+						material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
+						admin		= ent.AdminOnly
+					} )
+					
+				end
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
index 853614ea0e..5133611767 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
@@ -9,10 +9,15 @@ local function BuildWeaponCategories()
 		if ( !weapon.Spawnable ) then continue end
 
 		local Category = weapon.Category or "Other"
-		if ( !isstring( Category ) ) then Category = tostring( Category ) end
+		Category = tostring( Category )
+
+		local SubCategory = weapon.SubCategory or "Other"
+		SubCategory = tostring( SubCategory )
 
 		Categorised[ Category ] = Categorised[ Category ] or {}
-		table.insert( Categorised[ Category ], weapon )
+		Categorised[ Category ][ SubCategory ] = Categorised[ Category ][ SubCategory ] or {}
+
+		table.insert( Categorised[ Category ][ SubCategory ], weapon )
 
 	end
 
@@ -37,15 +42,56 @@ local function AddCategory( tree, cat )
 		self.PropPanel:SetVisible( false )
 		self.PropPanel:SetTriggerSpawnlistChange( false )
 
-		local weps = BuildWeaponCategories()[ cat ]
-		for k, ent in SortedPairsByMemberValue( weps, "PrintName" ) do
+		local subCategories = BuildWeaponCategories()[ cat ]
+		local createOtherHeader = false
+
+		for subCategoryName, weps in SortedPairs( subCategories ) do
+
+			if ( subCategoryName == "Other" ) then continue end
+
+			local label = vgui.Create( "ContentHeader" )
+
+			label:SetText( subCategoryName )
 
-			spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", self.PropPanel, {
-				nicename	= ent.PrintName or ent.ClassName,
-				spawnname	= ent.ClassName,
-				material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
-				admin		= ent.AdminOnly
-			} )
+			self.PropPanel:Add( label )
+
+			for k, ent in SortedPairsByMemberValue( weps, "PrintName" ) do
+
+				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", self.PropPanel, {
+					nicename	= ent.PrintName or ent.ClassName,
+					spawnname	= ent.ClassName,
+					material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
+					admin		= ent.AdminOnly
+				} )
+	
+			end
+
+			createOtherHeader = true
+
+		end
+
+		if ( subCategories.Other ) then
+
+			if ( createOtherHeader ) then
+
+				local label = vgui.Create( "ContentHeader" )
+
+				label:SetText( "Other" )
+
+				self.PropPanel:Add( label )
+
+			end
+
+			for name, ent in SortedPairsByMemberValue( subCategories.Other, "Name" ) do
+
+				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", self.PropPanel, {
+					nicename	= ent.PrintName or ent.ClassName,
+					spawnname	= ent.ClassName,
+					material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
+					admin		= ent.AdminOnly
+				} )
+
+			end
 
 		end
 
diff --git a/garrysmod/lua/includes/modules/scripted_ents.lua b/garrysmod/lua/includes/modules/scripted_ents.lua
index 23903b9860..1664690019 100644
--- a/garrysmod/lua/includes/modules/scripted_ents.lua
+++ b/garrysmod/lua/includes/modules/scripted_ents.lua
@@ -127,6 +127,7 @@ function Register( t, name )
 		Category		= t.Category,
 
 		-- Optional information
+		SubCategory 	= t.SubCategory,
 		NormalOffset	= t.NormalOffset,
 		DropToFloor		= t.DropToFloor,
 		Author			= t.Author,
diff --git a/garrysmod/lua/includes/modules/weapons.lua b/garrysmod/lua/includes/modules/weapons.lua
index a3624703d2..85dd1a9eaa 100644
--- a/garrysmod/lua/includes/modules/weapons.lua
+++ b/garrysmod/lua/includes/modules/weapons.lua
@@ -59,6 +59,7 @@ function Register( t, name )
 		ClassName = name,
 		PrintName = t.PrintName or name,
 		Category = t.Category or "Other",
+		SubCategory = t.SubCategory,
 		Spawnable = t.Spawnable,
 		AdminOnly = t.AdminOnly,
 		ScriptedEntityType = t.ScriptedEntityType,

From 3dee6f736da740e97ea0221ed6e4fa688e978c80 Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Thu, 31 Oct 2024 18:10:01 -0400
Subject: [PATCH 3/6] Allow spawn list custom ordering for NPCs, Entities, and
 Vehicles

---
 .../content/contenttypes/entities.lua         | 57 ++++++++++-------
 .../content/contenttypes/npcs.lua             | 62 ++++++++++++-------
 .../content/contenttypes/vehicles.lua         | 55 ++++++++++------
 .../content/contenttypes/weapons.lua          | 55 ++++++++++------
 4 files changed, 145 insertions(+), 84 deletions(-)

diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
index 0db7f7ceba..76dfd16626 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/entities.lua
@@ -3,6 +3,39 @@ list.Set( "ContentCategoryIcons", "Half-Life: Source", "games/16/hl1.png" )
 list.Set( "ContentCategoryIcons", "Half-Life 2", "games/16/hl2.png" )
 list.Set( "ContentCategoryIcons", "Portal", "games/16/portal.png" )
 
+local function BuildContentList( tab, propPanel )
+
+	local orderedList = {}
+
+	for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
+
+		local order = isnumber( ent.SpawnListOrder ) and ent.SpawnListOrder
+
+		if ( order ) then
+
+			table.insert( orderedList, order, ent )
+
+		else
+			
+			table.insert( orderedList, ent )
+
+		end
+
+	end
+
+	for k, ent in SortedPairs( orderedList ) do
+
+		spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", propPanel, {
+			nicename	= ent.PrintName or ent.ClassName,
+			spawnname	= ent.SpawnName,
+			material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
+			admin		= ent.AdminOnly
+		} )
+		
+	end
+
+end
+
 hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, browseNode )
 
 	local Categorised = {}
@@ -11,7 +44,6 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 	local SpawnableEntities = list.Get( "SpawnableEntities" )
 
 	if ( SpawnableEntities ) then
-		
 		for k, v in pairs( SpawnableEntities ) do
 
 			local Category = v.Category or "Other"
@@ -28,7 +60,6 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 			table.insert( Categorised[ Category ][ SubCategory ], v )
 
 		end
-
 	end
 
 	--
@@ -63,16 +94,7 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 
 				self.PropPanel:Add( label )
 
-				for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-						nicename	= ent.PrintName or ent.ClassName,
-						spawnname	= ent.SpawnName,
-						material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
-						admin		= ent.AdminOnly
-					} )
-	
-				end
+				BuildContentList( tab, self.PropPanel )
 
 				createOtherHeader = true
 
@@ -90,16 +112,7 @@ hook.Add( "PopulateEntities", "AddEntityContent", function( pnlContent, tree, br
 
 				end
 
-				for k, ent in SortedPairsByMemberValue( subCategories.Other, "PrintName" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-						nicename	= ent.PrintName or ent.ClassName,
-						spawnname	= ent.SpawnName,
-						material	= ent.IconOverride or ( "entities/" .. ent.SpawnName .. ".png" ),
-						admin		= ent.AdminOnly
-					} )
-	
-				end
+				BuildContentList( subCategories.Other, self.PropPanel )
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
index 281dfb613a..44ba4da93f 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/npcs.lua
@@ -1,4 +1,42 @@
 
+local function BuildContentList( tab, propPanel )
+
+	local orderedList = {}
+
+	for name, ent in SortedPairsByMemberValue( tab, "Name" ) do
+
+		local order = isnumber( ent.SpawnListOrder ) and ent.SpawnListOrder
+		local data = { name = name, ent = ent }
+
+		if ( order ) then
+
+			table.insert( orderedList, order, data )
+
+		else
+			
+			table.insert( orderedList, data )
+
+		end
+
+	end
+
+	for k, data in SortedPairs( orderedList ) do
+
+		local ent = data.ent
+		local name = data.name
+
+		spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", propPanel, {
+			nicename	= ent.Name or name,
+			spawnname	= name,
+			material	= ent.IconOverride or "entities/" .. name .. ".png",
+			weapon		= ent.Weapons,
+			admin		= ent.AdminOnly
+		} )
+
+	end
+
+end
+
 hook.Add( "PopulateNPCs", "AddNPCContent", function( pnlContent, tree, browseNode )
 
 	-- Get a list of available NPCs
@@ -55,17 +93,7 @@ hook.Add( "PopulateNPCs", "AddNPCContent", function( pnlContent, tree, browseNod
 
 				self.PropPanel:Add( label )
 
-				for name, ent in SortedPairsByMemberValue( tab, "Name" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", self.PropPanel, {
-						nicename	= ent.Name or name,
-						spawnname	= name,
-						material	= ent.IconOverride or "entities/" .. name .. ".png",
-						weapon		= ent.Weapons,
-						admin		= ent.AdminOnly
-					} )
-
-				end
+				BuildContentList( tab, self.PropPanel )
 
 				createOtherHeader = true
 
@@ -83,17 +111,7 @@ hook.Add( "PopulateNPCs", "AddNPCContent", function( pnlContent, tree, browseNod
 
 				end
 
-				for name, ent in SortedPairsByMemberValue( subCategories.Other, "Name" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "npc", self.PropPanel, {
-						nicename	= ent.Name or name,
-						spawnname	= name,
-						material	= ent.IconOverride or "entities/" .. name .. ".png",
-						weapon		= ent.Weapons,
-						admin		= ent.AdminOnly
-					} )
-
-				end
+				BuildContentList( subCategories.Other, self.PropPanel )
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
index 5f33b616ee..1fbfc2c750 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/vehicles.lua
@@ -1,4 +1,37 @@
 
+local function BuildContentList( tab, propPanel )
+
+	local orderedList = {}
+
+	for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
+
+		local order = isnumber( ent.SpawnListOrder ) and ent.SpawnListOrder
+
+		if ( order ) then
+
+			table.insert( orderedList, order, ent )
+
+		else
+			
+			table.insert( orderedList, ent )
+
+		end
+
+	end
+
+	for k, ent in SortedPairs( orderedList ) do
+
+		spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", propPanel, {
+			nicename	= ent.PrintName or ent.ClassName,
+			spawnname	= ent.ClassName,
+			material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
+			admin		= ent.AdminOnly
+		} )
+
+	end
+
+end
+
 hook.Add( "PopulateVehicles", "AddEntityContent", function( pnlContent, tree, browseNode )
 
 	-- Add this list into the tormoil
@@ -61,16 +94,7 @@ hook.Add( "PopulateVehicles", "AddEntityContent", function( pnlContent, tree, br
 
 				self.PropPanel:Add( label )
 
-				for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-						nicename	= ent.PrintName or ent.ClassName,
-						spawnname	= ent.ClassName,
-						material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
-						admin		= ent.AdminOnly
-					} )
-	
-				end
+				BuildContentList( tab, self.PropPanel )
 
 				createOtherHeader = true
 
@@ -88,16 +112,7 @@ hook.Add( "PopulateVehicles", "AddEntityContent", function( pnlContent, tree, br
 
 				end
 
-				for k, ent in SortedPairsByMemberValue( subCategories.Other, "PrintName" ) do
-
-					spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "entity", self.PropPanel, {
-						nicename	= ent.PrintName or ent.ClassName,
-						spawnname	= ent.ClassName,
-						material	= ent.IconOverride or "entities/" .. ent.ClassName .. ".png",
-						admin		= ent.AdminOnly
-					} )
-					
-				end
+				BuildContentList( subCategories.Other, self.PropPanel )
 
 			end
 
diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
index 5133611767..75183518f9 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenttypes/weapons.lua
@@ -24,6 +24,39 @@ local function BuildWeaponCategories()
 	return Categorised
 end
 
+local function BuildContentList( tab, propPanel )
+
+	local orderedList = {}
+
+	for k, ent in SortedPairsByMemberValue( tab, "PrintName" ) do
+
+		local order = isnumber( ent.SpawnListOrder ) and ent.SpawnListOrder
+
+		if ( order ) then
+
+			table.insert( orderedList, order, ent )
+
+		else
+			
+			table.insert( orderedList, ent )
+
+		end
+
+	end
+
+	for k, ent in SortedPairs( orderedList ) do
+
+		spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", propPanel, {
+			nicename	= ent.PrintName or ent.ClassName,
+			spawnname	= ent.ClassName,
+			material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
+			admin		= ent.AdminOnly
+		} )
+
+	end
+
+end
+
 local function AddCategory( tree, cat )
 	local CustomIcons = list.Get( "ContentCategoryIcons" )
 
@@ -55,16 +88,7 @@ local function AddCategory( tree, cat )
 
 			self.PropPanel:Add( label )
 
-			for k, ent in SortedPairsByMemberValue( weps, "PrintName" ) do
-
-				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", self.PropPanel, {
-					nicename	= ent.PrintName or ent.ClassName,
-					spawnname	= ent.ClassName,
-					material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
-					admin		= ent.AdminOnly
-				} )
-	
-			end
+			BuildContentList( weps, self.PropPanel )
 
 			createOtherHeader = true
 
@@ -82,16 +106,7 @@ local function AddCategory( tree, cat )
 
 			end
 
-			for name, ent in SortedPairsByMemberValue( subCategories.Other, "Name" ) do
-
-				spawnmenu.CreateContentIcon( ent.ScriptedEntityType or "weapon", self.PropPanel, {
-					nicename	= ent.PrintName or ent.ClassName,
-					spawnname	= ent.ClassName,
-					material	= ent.IconOverride or ( "entities/" .. ent.ClassName .. ".png" ),
-					admin		= ent.AdminOnly
-				} )
-
-			end
+			BuildContentList( subCategories.Other, self.PropPanel )
 
 		end
 

From dbdaec15f7eaa3b079cbc19b5c63323f21ce85c1 Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Thu, 31 Oct 2024 18:11:11 -0400
Subject: [PATCH 4/6] Almost forgot these

---
 garrysmod/lua/includes/modules/scripted_ents.lua | 1 +
 garrysmod/lua/includes/modules/weapons.lua       | 1 +
 2 files changed, 2 insertions(+)

diff --git a/garrysmod/lua/includes/modules/scripted_ents.lua b/garrysmod/lua/includes/modules/scripted_ents.lua
index 1664690019..f6c38f937c 100644
--- a/garrysmod/lua/includes/modules/scripted_ents.lua
+++ b/garrysmod/lua/includes/modules/scripted_ents.lua
@@ -127,6 +127,7 @@ function Register( t, name )
 		Category		= t.Category,
 
 		-- Optional information
+		SpawnListOrder = t.SpawnListOrder,
 		SubCategory 	= t.SubCategory,
 		NormalOffset	= t.NormalOffset,
 		DropToFloor		= t.DropToFloor,
diff --git a/garrysmod/lua/includes/modules/weapons.lua b/garrysmod/lua/includes/modules/weapons.lua
index 85dd1a9eaa..7b43a78ce3 100644
--- a/garrysmod/lua/includes/modules/weapons.lua
+++ b/garrysmod/lua/includes/modules/weapons.lua
@@ -59,6 +59,7 @@ function Register( t, name )
 		ClassName = name,
 		PrintName = t.PrintName or name,
 		Category = t.Category or "Other",
+		SpawnListOrder = t.SpawnListOrder,
 		SubCategory = t.SubCategory,
 		Spawnable = t.Spawnable,
 		AdminOnly = t.AdminOnly,

From 8b8094c8eda5c39cf7f4628a63cdcc6ca40229c4 Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Fri, 1 Nov 2024 15:37:01 -0400
Subject: [PATCH 5/6] Get the correct derma menu properly

---
 .../creationmenu/content/contenticon.lua      | 22 ++++++++++++++----
 garrysmod/lua/derma/derma_menus.lua           | 12 ++++------
 garrysmod/lua/vgui/spawnicon.lua              | 23 +++++++++++++++----
 3 files changed, 41 insertions(+), 16 deletions(-)

diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
index ac40220b24..f2e8b61e24 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
@@ -90,20 +90,34 @@ end
 function PANEL:DoRightClick()
 
 	local pCanvas = self:GetSelectionCanvas()
+
 	if ( IsValid( pCanvas ) && pCanvas:NumSelectedChildren() > 0 && self:IsSelected() ) then
 		return hook.Run( "SpawnlistOpenGenericMenu", pCanvas )
 	end
 
-	local openedMenus = GetOpenDermaMenus()
-	local menuCount = #openedMenus
+	local oldMenuCount = #GetOpenDermaMenus()
 
 	self:OpenMenu()
 
-	local menu = openedMenus[ menuCount + 1 ]
+	local openedMenus = GetOpenDermaMenus()
+	local newMenuCount = #openedMenus
+
+	local menuIndex = newMenuCount == oldMenuCount and oldMenuCount or oldMenuCount + 1
+	local menu = openedMenus[ menuIndex ]
+
+	if ( !IsValid( menu ) ) then return end
 
 	--
-	-- Allow addons to easily add their own options to the opened menu
+	-- Get the correct derma menu
+	-- Each option in a derma menu is a derma menu itself
 	--
+	while ( IsValid( menu.ParentMenu ) ) do
+		
+		menuIndex = menuIndex - 1
+		menu = openedMenus[ menuIndex ]
+
+	end
+
 	if ( IsValid( menu ) ) then
 
 		hook.Run( "OnContentIconOpenMenu", self, menu )
diff --git a/garrysmod/lua/derma/derma_menus.lua b/garrysmod/lua/derma/derma_menus.lua
index eea4862918..bbcb35e95c 100644
--- a/garrysmod/lua/derma/derma_menus.lua
+++ b/garrysmod/lua/derma/derma_menus.lua
@@ -3,21 +3,19 @@ local tblOpenMenus = {}
 
 function GetOpenDermaMenus()
 
-	--
-	-- Clear the table of any removed derma menus
-	-- Each option in a derma menu is another derma menu itself
-	--
+	local fixedCopy = {}
+
 	for k, menu in ipairs( tblOpenMenus ) do
 
-		if ( !IsValid( menu ) ) then
+		if ( IsValid( menu ) ) then
 
-			table.remove( tblOpenMenus, k )
+			table.insert( fixedCopy, menu )
 
 		end
 		
 	end
 
-	return tblOpenMenus
+	return fixedCopy
 
 end
 
diff --git a/garrysmod/lua/vgui/spawnicon.lua b/garrysmod/lua/vgui/spawnicon.lua
index caa140b448..48d1f9a628 100644
--- a/garrysmod/lua/vgui/spawnicon.lua
+++ b/garrysmod/lua/vgui/spawnicon.lua
@@ -28,17 +28,30 @@ function PANEL:DoRightClick()
 	if ( IsValid( pCanvas ) && pCanvas:NumSelectedChildren() > 0 && self:IsSelected() ) then
 		return hook.Run( "SpawnlistOpenGenericMenu", pCanvas )
 	end
-	
-	local openedMenus = GetOpenDermaMenus()
-	local menuCount = #openedMenus
+
+	local oldMenuCount = #GetOpenDermaMenus()
 
 	self:OpenMenu()
 
-	local menu = openedMenus[ menuCount + 1 ]
+	local openedMenus = GetOpenDermaMenus()
+	local newMenuCount = #openedMenus
+
+	local menuIndex = newMenuCount == oldMenuCount and oldMenuCount or oldMenuCount + 1
+	local menu = openedMenus[ menuIndex ]
+
+	if ( !IsValid( menu ) ) then return end
 
 	--
-	-- Allow addons to easily add their own options to the opened menu
+	-- Get the correct derma menu
+	-- Each option in a derma menu is a derma menu itself
 	--
+	while ( IsValid( menu.ParentMenu ) ) do
+		
+		menuIndex = menuIndex - 1
+		menu = openedMenus[ menuIndex ]
+
+	end
+
 	if ( IsValid( menu ) ) then
 
 		hook.Run( "OnSpawnIconOpenMenu", self, menu )

From 4087c7dadf95f07a17c5c17b8568dc7cc8aed0f3 Mon Sep 17 00:00:00 2001
From: Zaurzo <wafflesfordaysyt@gmail.com>
Date: Fri, 1 Nov 2024 17:06:34 -0400
Subject: [PATCH 6/6] Better way to get the correct derma menu

---
 .../creationmenu/content/contenticon.lua      | 20 ++++++-------------
 garrysmod/lua/vgui/spawnicon.lua              | 20 ++++++-------------
 2 files changed, 12 insertions(+), 28 deletions(-)

diff --git a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
index f2e8b61e24..561553ed6e 100644
--- a/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
+++ b/garrysmod/gamemodes/sandbox/gamemode/spawnmenu/creationmenu/content/contenticon.lua
@@ -100,30 +100,22 @@ function PANEL:DoRightClick()
 	self:OpenMenu()
 
 	local openedMenus = GetOpenDermaMenus()
-	local newMenuCount = #openedMenus
 
-	local menuIndex = newMenuCount == oldMenuCount and oldMenuCount or oldMenuCount + 1
+	local menuIndex = #openedMenus == oldMenuCount and oldMenuCount or oldMenuCount + 1
 	local menu = openedMenus[ menuIndex ]
 
 	if ( !IsValid( menu ) ) then return end
 
-	--
-	-- Get the correct derma menu
-	-- Each option in a derma menu is a derma menu itself
-	--
-	while ( IsValid( menu.ParentMenu ) ) do
-		
-		menuIndex = menuIndex - 1
-		menu = openedMenus[ menuIndex ]
+	local parent = menu.ParentMenu
 
-	end
-
-	if ( IsValid( menu ) ) then
+	if ( IsValid( parent ) ) then
 
-		hook.Run( "OnContentIconOpenMenu", self, menu )
+		menu = parent
 
 	end
 
+	hook.Run( "OnContentIconOpenMenu", self, menu )
+
 end
 
 function PANEL:DoClick()
diff --git a/garrysmod/lua/vgui/spawnicon.lua b/garrysmod/lua/vgui/spawnicon.lua
index 48d1f9a628..5f0782a396 100644
--- a/garrysmod/lua/vgui/spawnicon.lua
+++ b/garrysmod/lua/vgui/spawnicon.lua
@@ -34,30 +34,22 @@ function PANEL:DoRightClick()
 	self:OpenMenu()
 
 	local openedMenus = GetOpenDermaMenus()
-	local newMenuCount = #openedMenus
 
-	local menuIndex = newMenuCount == oldMenuCount and oldMenuCount or oldMenuCount + 1
+	local menuIndex = #openedMenus == oldMenuCount and oldMenuCount or oldMenuCount + 1
 	local menu = openedMenus[ menuIndex ]
 
 	if ( !IsValid( menu ) ) then return end
 
-	--
-	-- Get the correct derma menu
-	-- Each option in a derma menu is a derma menu itself
-	--
-	while ( IsValid( menu.ParentMenu ) ) do
-		
-		menuIndex = menuIndex - 1
-		menu = openedMenus[ menuIndex ]
+	local parent = menu.ParentMenu
 
-	end
-
-	if ( IsValid( menu ) ) then
+	if ( IsValid( parent ) ) then
 
-		hook.Run( "OnSpawnIconOpenMenu", self, menu )
+		menu = parent
 
 	end
 
+	hook.Run( "OnSpawnIconOpenMenu", self, menu )
+	
 end
 
 function PANEL:DoClick()