--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] AddCSLuaFile() ParticleListTable = { Games = {}, Addons = {}, } local doubleslash = "//" local function AddToParticleListTable(filename,isaddon,icon) //Converts a list string, with one item per line, into a table, taking comments and empty strings into account. Returns nil if the table is empty. //The format of these tables is item1 = true, item2 = true, etc., not 1 = item1, 2 = item2, etc.; this is so we can use "if table["itemX"] then" to check if a value is inside of it. // //Instead of the arg being the string itself, the args are 1: the table containing the string, and 2: the key the string can be found at. //This is so we can find continuations of the string, which'll be in the same table, but under separate keys. // local function ListStringToTable(listcontainer,listname) //Find the list string we're looking for inside the container, and explode it into its own table. local liststring = listcontainer[listname] if type(liststring) != "string" then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname .. "\" inside of \"" .. filename .. "\" doesn't seem to actually be a string! Check the file and make sure you've formatted everything correctly!") return end if #liststring == 4095 then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname .. "\" inside of \"" .. filename .. "\" is more than 4095 characters long - that's too long for the engine to read all of it! Split it up into multiple lists!") end local listtab = string.Explode("\n", liststring) //Also find any list strings that are continuations of our main list string, and explode them into the same table. for listname2, liststring2 in pairs (listcontainer) do if string.StartWith(listname2, listname .. "_cont") then if type(liststring) != "string" then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname2 .. "\" inside of \"" .. filename .. "\" doesn't seem to actually be a string! Check the file and make sure you've formatted everything correctly!") return end if #liststring2 == 4095 then MsgN("PARTICLE CONTROL ERROR: List string \"" .. listname2 .. "\" inside of \"" .. filename .. "\" is more than 4095 characters long - that's too long for the engine to read all of it! Split it up into multiple lists!") end table.Add(listtab, string.Explode("\n", liststring2) ) end end //Finally, filter out any comments or blank spaces from the table. local listtabfiltered = {} for _, str in pairs (listtab) do //if there's a doubleslash in the string then remove it and everything after it local commentpos, _ = string.find(str, doubleslash) local resultstr = str if commentpos then resultstr = string.sub( str, 1, commentpos - 1 ) end //trim any spaces from the string, and don't add it to the filtered table if it's just an empty string at this point resultstr = string.Trim(resultstr) if resultstr != "" then listtabfiltered[resultstr] = true end end if table.Count(listtabfiltered) > 0 then return listtabfiltered end end local filestr = file.Read(filename,"LUA") if !filestr then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" doesn't exist or isn't a readable file! Something went wrong!") return end local keyvalues = util.KeyValuesToTable( filestr, false, true ) local resulttable = { Info = {}, Particles = {} } if !keyvalues or (table.Count(keyvalues) == 0) then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" isn't a valid keyvalue table and couldn't be read! Check the file and make sure you've formatted everything correctly!") return end if !keyvalues.Info then //On Mac/Linux systems, the keyvalues-to-table function messes up and returns the keyname "Info" as something else, like "inf", so it's possible that the info subtable is //still here, just under a different keyname. The info is the only thing in keyvalues that should be a table value, so we'll check for that: local foundinfo = false for k, v in pairs (keyvalues) do if type(v) == "table" then keyvalues.Info = v MsgN("(" .. filename .. ": Found Info table under keyname " .. tostring(k) .. ")") foundinfo = true end end //If there's no table value in keyvalues at all, then this is a probably a custom file where someone messed up the formatting. if !foundinfo then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\" doesn't have an \"Info\" table that we can find! Check the file and make sure you've formatted everything correctly!") return end end if !keyvalues.Info.CategoryName then MsgN("PARTICLE CONTROL ERROR: Particle list file \"" .. filename .. "\"'s Info table doesn't have a \"CategoryName\" string that we can find! Check the file and make sure you've formatted everything correctly!") return end resulttable.Info.CategoryName = keyvalues.Info.CategoryName resulttable.Info.Icon = icon resulttable.Info.EffectOptions = {} if keyvalues.Info["EffectOptions"] then if keyvalues.Info.EffectOptions["Beams"] then resulttable.Info.EffectOptions["Beams"] = ListStringToTable(keyvalues.Info.EffectOptions, "Beams") end if keyvalues.Info.EffectOptions["Color1"] then resulttable.Info.EffectOptions["Color1"] = ListStringToTable(keyvalues.Info.EffectOptions, "Color1") end if keyvalues.Info.EffectOptions["Color255"] then resulttable.Info.EffectOptions["Color255"] = ListStringToTable(keyvalues.Info.EffectOptions, "Color255") end if keyvalues.Info.EffectOptions["Tracers"] then resulttable.Info.EffectOptions["Tracers"] = ListStringToTable(keyvalues.Info.EffectOptions, "Tracers") end end if keyvalues.Info["UtilEffects"] then resulttable.Info["UtilEffects"] = keyvalues.Info["UtilEffects"] end keyvalues.Info = nil for k, _ in pairs (keyvalues) do if !string.StartWith(k,doubleslash) and string.EndsWith(k, ".pcf") then //ignore .pcf lists that start with a doubleslash; also ignore lists that don't end with .pcf since these are probably continuations local particlelist = ListStringToTable(keyvalues, k) if particlelist then game.AddParticles("particles/" .. k) resulttable.Particles[k] = particlelist end end end local subtablename = string.StripExtension( string.GetFileFromFilename(filename) ) if isaddon then MsgN( "PARTICLE CONTROL: Added list for addon: " .. resulttable.Info.CategoryName ) ParticleListTable.Addons[subtablename] = resulttable else MsgN( "PARTICLE CONTROL: Added list for game: " .. resulttable.Info.CategoryName ) ParticleListTable.Games[subtablename] = resulttable end end //Source Engine / Garry's Mod AddToParticleListTable("particlelists/gmod.lua", false, "games/16/garrysmod.png") //Team Fortress 2 if IsMounted("tf") then AddToParticleListTable("particlelists/tf2.lua", false, "games/16/tf.png") //TODO: currently causes a crash when loading updated pcfs, uncomment once fixed by devs end //Half-Life 2: Episode 1 if IsMounted("episodic") then AddToParticleListTable("particlelists/ep1.lua", false, "games/16/episodic.png") end //Half-Life 2: Episode 2 if IsMounted("ep2") then AddToParticleListTable("particlelists/ep2.lua", false, "games/16/ep2.png") end //Portal if IsMounted("portal") then AddToParticleListTable("particlelists/portal.lua", false, "games/16/portal.png") end //Counter-Strike: Source if IsMounted("cstrike") then AddToParticleListTable("particlelists/cstrike.lua", false, "games/16/cstrike.png") end //Find all list files in the particlelists/addons/ directory, and add them to their own section of the table local alladdons, _ = file.Find( "lua/particlelists/addons/*.lua", "GAME" ) for _, filename in pairs (alladdons) do if !string.StartWith( filename, "_" ) then //don't add files that start with an _, this is how we stop it from reading the example files AddToParticleListTable("particlelists/addons/" .. filename, true, "icon16/bricks.png") end end if CLIENT then //Favorites - this table is global because it's used by every Particle Browser panel on the client ParticleBrowserFavorites = { Effects = {}, //effects in the favorites list Children = {}, //all DTreeNode panels that are using this favorites list } local parctrlfavs_str = file.Read("particlecontrol_favorites.txt","DATA") if parctrlfavs_str then ParticleBrowserFavorites.Effects = util.JSONToTable(parctrlfavs_str) end function ParticleBrowserFavorites_UpdateEffect( effectdisplay, effectoptions ) if effectoptions then //We're adding the effect ParticleBrowserFavorites.Effects[effectdisplay] = effectoptions else //We're removing the effect ParticleBrowserFavorites.Effects[effectdisplay] = nil end for _, panel in pairs (ParticleBrowserFavorites.Children) do if panel then ParticleBrowserFavorites_UpdateChildPanel( panel ) end end file.Write( "particlecontrol_favorites.txt", util.TableToJSON(ParticleBrowserFavorites.Effects, true) ) end function ParticleBrowserFavorites_UpdateChildPanel( panel ) if !panel then return end if panel.FavoriteNodes then //Remove all of the nodes from the panel for k, node in pairs (panel.FavoriteNodes) do node:Remove() panel.FavoriteNodes[k] = nil end else panel.FavoriteNodes = {} end for effectdisplay, effectoptions in SortedPairs (ParticleBrowserFavorites.Effects) do local effect = effectoptions["InternalName"] panel.FavoriteNodes[effect] = panel:AddNode(effectdisplay) panel.FavoriteNodes[effect].EffectOptions = {} if effectoptions["Beam"] then panel.FavoriteNodes[effect].EffectOptions["Beam"] = true end if effectoptions["Colorable"] then panel.FavoriteNodes[effect].EffectOptions["Colorable"] = true end if effectoptions["ColorOutOfOne"] then panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] = true end //Don't use the icons for features we're not using local beamicon = ( (panel.CommandInfo.mode_beam) and (panel.FavoriteNodes[effect].EffectOptions["Beam"] == true) ) local coloricon = ( (panel.CommandInfo.color) and (panel.FavoriteNodes[effect].EffectOptions["Colorable"] == true) ) //Set the effect icon: if beamicon and coloricon then panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line_rainbow.png") elseif beamicon then panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line.png") elseif coloricon then panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_rainbow.png") else panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire.png") end panel.FavoriteNodes[effect].DoClick = function() RunConsoleCommand( panel.CommandInfo.effectname, effect ) if panel.CommandInfo.mode_beam then if panel.FavoriteNodes[effect].EffectOptions["Beam"] then RunConsoleCommand( panel.CommandInfo.mode_beam, "1" ) else RunConsoleCommand( panel.CommandInfo.mode_beam, "0" ) end end if panel.CommandInfo.color then if panel.FavoriteNodes[effect].EffectOptions["Colorable"] then RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "1" ) else RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "0" ) end if panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] then RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "1" ) else RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "0" ) end end end panel.FavoriteNodes[effect].DoRightClick = function() //We only need the remove option here, since it's already in the favorites list local menu = DermaMenu() local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list end ) option:SetImage("icon16/delete.png") menu:Open() end end end function AddParticleBrowser(panel,data) local self = {} //Data table contents: //{ //name = "Effect" (name to show at the top of the panel) //commands = (table of concommands that the panel uses) // { // effectname = "particlecontrol_effectname" // mode_beam = "particlecontrol_mode_beam" (optional) // color = "particlecontrol_color" (optional, assumes there are cvars derived from this name (X_enabled, X_r, X_g, X_b, X_outofone) ) // utileffect = "particlecontrol_utileffect" (optional, assumes there are cvars derived from this name (X_scale, X_magnitude, X_radius) ) // // enabled = "particlecontrol_enabled" (optional - toggles whether or not the panel is open using this cvar) // } //} self.back = vgui.Create("DForm", panel) self.back:SetLabel(data.name) self.back.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.back, self.back:GetWide(), self.back:GetTall() ) surface.SetDrawColor( Color(0,0,0,70) ) surface.DrawRect( 0, 0, self.back:GetWide(), self.back:GetTall() ) end self.back.Header:SetImage("icon16/fire.png") self.back.Header.DoClick = function() end panel:AddPanel(self.back) self.tree = vgui.Create("DTree", self.back) self.tree:SetHeight(400) self.back:AddItem(self.tree) self.tree:GetParent():DockPadding(5,5,5,0) local function PopulateEffectList(parent, effectstab, effectoptions) for k, v in SortedPairs (effectstab) do local effect = k local effectdisplay = k //if v is another string instead of just a filler "true" value, then that means k is the display name and v is the internal name if isstring(v) then effect = v end parent[effect] = parent:AddNode(effectdisplay) parent[effect].EffectOptions = {} if effectoptions["Beams"] and effectoptions["Beams"][effect] then parent[effect].EffectOptions["Beam"] = true end if effectoptions["Color1"] and effectoptions["Color1"][effect] then parent[effect].EffectOptions["Colorable"] = true parent[effect].EffectOptions["ColorOutOfOne"] = true end if effectoptions["Color255"] and effectoptions["Color255"][effect] then parent[effect].EffectOptions["Colorable"] = true parent[effect].EffectOptions["ColorOutOfOne"] = false end //Don't use the icons for features we're not using local beamicon = ( (data.commands.mode_beam) and (parent[effect].EffectOptions["Beam"] == true) ) local coloricon = ( (data.commands.color) and (parent[effect].EffectOptions["Colorable"] == true) ) //Set the effect icon: if beamicon and coloricon then parent[effect].Icon:SetImage("icon16/fire_line_rainbow.png") elseif beamicon then parent[effect].Icon:SetImage("icon16/fire_line.png") elseif coloricon then parent[effect].Icon:SetImage("icon16/fire_rainbow.png") else parent[effect].Icon:SetImage("icon16/fire.png") end //Left Click: Select the effect by setting its concommands parent[effect].DoClick = function() RunConsoleCommand( data.commands.effectname, effect ) if data.commands.mode_beam then if parent[effect].EffectOptions["Beam"] then RunConsoleCommand( data.commands.mode_beam, "1" ) else RunConsoleCommand( data.commands.mode_beam, "0" ) end end if data.commands.color then if parent[effect].EffectOptions["Colorable"] then RunConsoleCommand( data.commands.color .. "_enabled", "1" ) else RunConsoleCommand( data.commands.color .. "_enabled", "0" ) end if parent[effect].EffectOptions["ColorOutOfOne"] then RunConsoleCommand( data.commands.color .. "_outofone", "1" ) else RunConsoleCommand( data.commands.color .. "_outofone", "0" ) end end end //Right Click: Display an option to add/remove the effect from favorites parent[effect].DoRightClick = function() local menu = DermaMenu() if !ParticleBrowserFavorites.Effects[effectdisplay] then local option = menu:AddOption( "Favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserFavorites_UpdateEffect( effectdisplay, { ["InternalName"] = effect, ["Beam"] = tobool(parent[effect].EffectOptions["Beam"]), ["Colorable"] = tobool(parent[effect].EffectOptions["Colorable"]), ["ColorOutOfOne"] = tobool(parent[effect].EffectOptions["ColorOutOfOne"]), } ) end ) option:SetImage("icon16/add.png") else local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list end ) option:SetImage("icon16/delete.png") end menu:Open() end end end //Games self.tree.games = self.tree:AddNode("Games") self.tree.games.Icon:SetImage("icon16/folder_database.png") self.tree.games:SetExpanded(true) // for tabname, tab in SortedPairs (ParticleListTable.Games) do self.tree.games[tabname] = self.tree.games:AddNode(tab.Info.CategoryName) self.tree.games[tabname].Icon:SetImage(tab.Info.Icon) //Create the utilfx list if tab.Info.UtilEffects then self.tree.games[tabname].UtilEffects = self.tree.games[tabname]:AddNode("Scripted Effects") self.tree.games[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") self.tree.games[tabname].UtilEffects.IsPopulated = false self.tree.games[tabname].UtilEffects.DoClick = function() if self.tree.games[tabname].UtilEffects.IsPopulated == true then return end PopulateEffectList(self.tree.games[tabname].UtilEffects, tab.Info.UtilEffects, tab.Info.EffectOptions) self.tree.games[tabname].UtilEffects.IsPopulated = true self.tree.games[tabname].UtilEffects:SetExpanded(true) end end //Create the .pcf lists for pcfname, particles in SortedPairs (tab.Particles) do local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems self.tree.games[tabname][pcftabname] = self.tree.games[tabname]:AddNode(pcfname) self.tree.games[tabname][pcftabname].Icon:SetImage("icon16/page.png") self.tree.games[tabname][pcftabname].IsPopulated = false self.tree.games[tabname][pcftabname].DoClick = function() if self.tree.games[tabname][pcftabname].IsPopulated == true then return end PopulateEffectList(self.tree.games[tabname][pcftabname], particles, tab.Info.EffectOptions) self.tree.games[tabname][pcftabname].IsPopulated = true self.tree.games[tabname][pcftabname]:SetExpanded(true) end end end //Addons self.tree.addons = self.tree:AddNode("Addons") self.tree.addons.Icon:SetImage("icon16/folder_database.png") self.tree.addons:SetExpanded(true) // for tabname, tab in SortedPairs (ParticleListTable.Addons) do self.tree.addons[tabname] = self.tree.addons:AddNode(tab.Info.CategoryName) self.tree.addons[tabname].Icon:SetImage(tab.Info.Icon) //Create the utilfx list if tab.Info.UtilEffects then self.tree.addons[tabname].UtilEffects = self.tree.addons[tabname]:AddNode("Scripted Effects") self.tree.addons[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") self.tree.addons[tabname].UtilEffects.IsPopulated = false self.tree.addons[tabname].UtilEffects.DoClick = function() if self.tree.addons[tabname].UtilEffects.IsPopulated == true then return end PopulateEffectList(self.tree.addons[tabname].UtilEffects, tab.Info.UtilEffects, tab.Info.EffectOptions) self.tree.addons[tabname].UtilEffects.IsPopulated = true self.tree.addons[tabname].UtilEffects:SetExpanded(true) end end //Create the .pcf lists for pcfname, particles in SortedPairs (tab.Particles) do local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems self.tree.addons[tabname][pcftabname] = self.tree.addons[tabname]:AddNode(pcfname) self.tree.addons[tabname][pcftabname].Icon:SetImage("icon16/page.png") self.tree.addons[tabname][pcftabname].IsPopulated = false self.tree.addons[tabname][pcftabname].DoClick = function() if self.tree.addons[tabname][pcftabname].IsPopulated == true then return end PopulateEffectList(self.tree.addons[tabname][pcftabname], particles, tab.Info.EffectOptions) self.tree.addons[tabname][pcftabname].IsPopulated = true self.tree.addons[tabname][pcftabname]:SetExpanded(true) end end end //Favorites self.tree.favorites = self.tree:AddNode("Favorites") self.tree.favorites.Icon:SetImage("icon16/star.png") self.tree.favorites:SetExpanded(true) // table.insert( ParticleBrowserFavorites.Children, self.tree.favorites ) //Add it to the Favorites table's list of child panels and let it do the rest of the work self.tree.favorites.CommandInfo = data.commands //Store the console command info in the panel so the UpdateChildPanel function can access it ParticleBrowserFavorites_UpdateChildPanel( self.tree.favorites ) self.effectnameentry = vgui.Create( "DTextEntry", self.back ) self.effectnameentry:SetConVar( data.commands.effectname ) self.back:AddItem(self.effectnameentry) self.effectnameentry:GetParent():DockPadding(5,5,5,10) if data.commands.mode_beam then self.beam = vgui.Create("DForm", self.back) self.beam.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.beam, self.beam:GetWide(), self.beam:GetTall() ) surface.SetDrawColor( Color(0,0,0,110) ) surface.DrawRect( 0, 0, self.beam:GetWide(), self.beam:GetTall() ) end self.beam:Dock(TOP) self.back:AddItem(self.beam) self.beam:GetParent():DockPadding(5,0,5,0) //Modify the header and tweak things so it doesn't show up when the category is closed. self.beam.Header:SetText( "Beam" ) self.beam.Header:SetImage("icon16/fire_line.png") self.beam.Header.DoClick = function() end self.beam.PerformLayout = function() local us = self.beam //so when we copy this over to another dform we only have to change this one line local Padding = us:GetPadding() or 0 if ( us.Contents ) then if ( us:GetExpanded() ) then us.Contents:InvalidateLayout( true ) us.Contents:SetVisible( true ) else us.Contents:SetVisible( false ) end end if ( us:GetExpanded() ) then us:SizeToChildren( false, true ) else us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() end -- Make sure the color of header text is set us.Header:ApplySchemeSettings() us.animSlide:Run() us:UpdateAltLines(); end self.beam.text = vgui.Create("DLabel", self.beam) self.beam.text:SetColor( Color(60,60,60,255) ) self.beam.text:SetText("This effect attaches to two points.") self.beam.text:SetWrap( true ) self.beam.text:SetAutoStretchVertical( true ) self.beam.text:Dock(TOP) self.beam.text:SizeToContents() self.beam:AddItem(self.beam.text) self.beam.text:GetParent():DockPadding( 15, 10, 15, 10 ) end if data.commands.color then self.color = vgui.Create("DForm", self.back) self.color.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.color, self.color:GetWide(), self.color:GetTall() ) surface.SetDrawColor( Color(0,0,0,110) ) surface.DrawRect( 0, 0, self.color:GetWide(), self.color:GetTall() ) end self.color:Dock(TOP) self.back:AddItem(self.color) self.color:GetParent():DockPadding(5,0,5,0) //Modify the header and tweak things so it doesn't show up when the category is closed. self.color.Header:SetText( "Colorable" ) self.color.Header:SetImage("icon16/fire_rainbow.png") self.color.Header.DoClick = function() end self.color.PerformLayout = function() local us = self.color //so when we copy this over to another dform we only have to change this one line local Padding = us:GetPadding() or 0 if ( us.Contents ) then if ( us:GetExpanded() ) then us.Contents:InvalidateLayout( true ) us.Contents:SetVisible( true ) else us.Contents:SetVisible( false ) end end if ( us:GetExpanded() ) then us:SizeToChildren( false, true ) else us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() end -- Make sure the color of header text is set us.Header:ApplySchemeSettings() us.animSlide:Run() us:UpdateAltLines(); end self.color.text = vgui.Create("DLabel", self.color) self.color.text:SetColor( Color(60,60,60,255) ) self.color.text:SetText("This effect is colorable.") self.color.text:SetWrap( true ) self.color.text:SetAutoStretchVertical( true ) self.color.text:Dock(TOP) self.color.text:SizeToContents() self.color:AddItem(self.color.text) self.color.text:GetParent():DockPadding( 15, 10, 15, 10 ) self.color.selection = vgui.Create( "CtrlColor", self.color ) self.color.selection:SetLabel( "" ) self.color.selection:SetConVarR( data.commands.color .. "_r" ) self.color.selection:SetConVarG( data.commands.color .. "_g" ) self.color.selection:SetConVarB( data.commands.color .. "_b" ) self.color.selection:SetConVarA( nil ) self.color.selection:Dock(TOP) self.color:AddItem(self.color.selection) self.color.selection:GetParent():DockPadding( 10, 0, 10, 10 ) end if data.commands.utileffect then self.utilfx = vgui.Create("DForm", self.back) self.utilfx.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.utilfx, self.utilfx:GetWide(), self.utilfx:GetTall() ) surface.SetDrawColor( Color(0,0,0,110) ) surface.DrawRect( 0, 0, self.utilfx:GetWide(), self.utilfx:GetTall() ) end self.utilfx:Dock(TOP) self.back:AddItem(self.utilfx) self.utilfx:GetParent():DockPadding(5,0,5,0) //Modify the header and tweak things so it doesn't show up when the category is closed. self.utilfx.Header:SetText( "Scripted Effect" ) self.utilfx.Header:SetImage("icon16/cog.png") self.utilfx.Header.DoClick = function() end self.utilfx.PerformLayout = function() local us = self.utilfx //so when we copy this over to another dform we only have to change this one line local Padding = us:GetPadding() or 0 if ( us.Contents ) then if ( us:GetExpanded() ) then us.Contents:InvalidateLayout( true ) us.Contents:SetVisible( true ) else us.Contents:SetVisible( false ) end end if ( us:GetExpanded() ) then us:SizeToChildren( false, true ) else us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() end -- Make sure the color of header text is set us.Header:ApplySchemeSettings() us.animSlide:Run() us:UpdateAltLines(); end self.utilfx.text = vgui.Create("DLabel", self.utilfx) self.utilfx.text:SetColor( Color(60,60,60,255) ) self.utilfx.text:SetText("These options can be used to modify certain scripted effects in different ways depending on the effect being used.") self.utilfx.text:SetWrap( true ) self.utilfx.text:SetAutoStretchVertical( true ) self.utilfx.text:Dock(TOP) self.utilfx.text:SizeToContents() self.utilfx:AddItem(self.utilfx.text) self.utilfx.text:GetParent():DockPadding( 15, 10, 15, 5 ) self.utilfx.slider1 = vgui.Create( "DNumSlider", self.utilfx ) self.utilfx.slider1:SetText( "Util.Effect Scale" ) self.utilfx.slider1:SetMinMax(0.2, 10) self.utilfx.slider1:SetDecimals(2) self.utilfx.slider1:SetConVar( data.commands.utileffect .. "_scale" ) self.utilfx.slider1:SetHeight(15) self.utilfx.slider1:SizeToContents() self.utilfx.slider1:SetDark(true) self.utilfx:AddItem(self.utilfx.slider1) self.utilfx.slider2 = vgui.Create( "DNumSlider", self.utilfx ) self.utilfx.slider2:SetText( "Util.Effect Magnitude" ) self.utilfx.slider2:SetMinMax(1, 10) self.utilfx.slider2:SetDecimals(2) self.utilfx.slider2:SetConVar( data.commands.utileffect .. "_magnitude" ) self.utilfx.slider2:SetHeight(15) self.utilfx.slider2:SizeToContents() self.utilfx.slider2:SetDark(true) self.utilfx:AddItem(self.utilfx.slider2) self.utilfx.slider3 = vgui.Create( "DNumSlider", self.utilfx ) self.utilfx.slider3:SetText( "Util.Effect Radius" ) self.utilfx.slider3:SetMinMax(10, 1000) self.utilfx.slider3:SetDecimals(2) self.utilfx.slider3:SetConVar( data.commands.utileffect .. "_radius" ) self.utilfx.slider3:SetHeight(15) self.utilfx.slider3:SizeToContents() self.utilfx.slider3:SetDark(true) self.utilfx:AddItem(self.utilfx.slider3) end self.back.Think = function() if self.beam then if GetConVarNumber( data.commands.mode_beam ) > 0 then //expand it if self.beam:GetExpanded() == false then self.beam:Toggle() end else //contract it if self.beam:GetExpanded() == true then self.beam:Toggle() end self.beam:GetParent():SetTall(self.beam:GetTall()) end end if self.color then if GetConVarNumber( data.commands.color .. "_enabled" ) > 0 then //expand it if self.color:GetExpanded() == false then self.color:Toggle() end else //contract it if self.color:GetExpanded() == true then self.color:Toggle() end self.color:GetParent():SetTall(self.color:GetTall()) end end if self.utilfx then if string.StartWith( GetConVarString(data.commands.effectname), "!UTILEFFECT!" ) then //expand it if self.utilfx:GetExpanded() == false then self.utilfx:Toggle() end else //contract it if self.utilfx:GetExpanded() == true then self.utilfx:Toggle() end self.utilfx:GetParent():SetTall(self.utilfx:GetTall()) end end if data.commands.enabled then if GetConVarNumber( data.commands.enabled ) > 0 then //expand it if self.back:GetExpanded() == false then self.back:Toggle() end else //contract it if self.back:GetExpanded() == true then self.back:Toggle() end end end end end //Favorites - this table is global too because this is all copied over from the standard Particle Browser, even though I don't suspect we'll ever have more than one of these ParticleBrowserTracerFavorites = { Effects = {}, //effects in the favorites list Children = {}, //all DTreeNode panels that are using this favorites list } local parctrltracerfavs_str = file.Read("particlecontrol_favorites_tracer.txt","DATA") if parctrltracerfavs_str then ParticleBrowserTracerFavorites.Effects = util.JSONToTable(parctrltracerfavs_str) end function ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay, effectoptions ) if effectoptions then //We're adding the effect ParticleBrowserTracerFavorites.Effects[effectdisplay] = effectoptions else //We're removing the effect ParticleBrowserTracerFavorites.Effects[effectdisplay] = nil end for _, panel in pairs (ParticleBrowserTracerFavorites.Children) do if panel then ParticleBrowserTracerFavorites_UpdateChildPanel( panel ) end end file.Write( "particlecontrol_favorites_tracer.txt", util.TableToJSON(ParticleBrowserTracerFavorites.Effects, true) ) end function ParticleBrowserTracerFavorites_UpdateChildPanel( panel ) if !panel then return end if panel.FavoriteNodes then //Remove all of the nodes from the panel for k, node in pairs (panel.FavoriteNodes) do node:Remove() panel.FavoriteNodes[k] = nil end else panel.FavoriteNodes = {} end for effectdisplay, effectoptions in SortedPairs (ParticleBrowserTracerFavorites.Effects) do local effect = effectoptions["InternalName"] panel.FavoriteNodes[effect] = panel:AddNode(effectdisplay) panel.FavoriteNodes[effect].EffectOptions = {} if effectoptions["Colorable"] then panel.FavoriteNodes[effect].EffectOptions["Colorable"] = true end if effectoptions["ColorOutOfOne"] then panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] = true end //Don't use the icons for features we're not using local coloricon = ( (panel.CommandInfo.color) and (panel.FavoriteNodes[effect].EffectOptions["Colorable"] == true) ) //Set the effect icon: if coloricon then panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line_rainbow.png") else panel.FavoriteNodes[effect].Icon:SetImage("icon16/fire_line.png") end panel.FavoriteNodes[effect].DoClick = function() RunConsoleCommand( panel.CommandInfo.effectname, effect ) if panel.CommandInfo.color then if panel.FavoriteNodes[effect].EffectOptions["Colorable"] then RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "1" ) else RunConsoleCommand( panel.CommandInfo.color .. "_enabled", "0" ) end if panel.FavoriteNodes[effect].EffectOptions["ColorOutOfOne"] then RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "1" ) else RunConsoleCommand( panel.CommandInfo.color .. "_outofone", "0" ) end end end panel.FavoriteNodes[effect].DoRightClick = function() //We only need the remove option here, since it's already in the favorites list local menu = DermaMenu() local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list end ) option:SetImage("icon16/delete.png") menu:Open() end end end function AddParticleBrowserTracer(panel,data) local self = {} //Data table contents: //{ //name = "Effect" (name to show at the top of the panel) //commands = (table of concommands that the panel uses) // { // effectname = "particlecontrol_effectname" // color = "particlecontrol_color" (optional, assumes there are cvars derived from this name (X_enabled, X_r, X_g, X_b, X_outofone) ) // } //} self.back = vgui.Create("DForm", panel) self.back:SetLabel(data.name) self.back.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.back, self.back:GetWide(), self.back:GetTall() ) surface.SetDrawColor( Color(0,0,0,70) ) surface.DrawRect( 0, 0, self.back:GetWide(), self.back:GetTall() ) end self.back.Header:SetImage("icon16/fire.png") self.back.Header.DoClick = function() end panel:AddPanel(self.back) self.tree = vgui.Create("DTree", self.back) self.tree:SetHeight(400) self.back:AddItem(self.tree) self.tree:GetParent():DockPadding(5,5,5,0) local function PopulateEffectList(parent, effectstab, effectoptions) for k, v in SortedPairs (effectstab) do local effect = k local effectdisplay = k //if v is another string instead of just a filler "true" value, then that means k is the display name and v is the internal name if isstring(v) then effect = v end parent[effect] = parent:AddNode(effectdisplay) parent[effect].EffectOptions = {} if effectoptions["Color1"] and effectoptions["Color1"][effect] then parent[effect].EffectOptions["Colorable"] = true parent[effect].EffectOptions["ColorOutOfOne"] = true end if effectoptions["Color255"] and effectoptions["Color255"][effect] then parent[effect].EffectOptions["Colorable"] = true parent[effect].EffectOptions["ColorOutOfOne"] = false end //Don't use the icons for features we're not using local coloricon = ( (data.commands.color) and (parent[effect].EffectOptions["Colorable"] == true) ) //Set the effect icon: if coloricon then parent[effect].Icon:SetImage("icon16/fire_line_rainbow.png") else parent[effect].Icon:SetImage("icon16/fire_line.png") end //Left Click: Select the effect by setting its concommands parent[effect].DoClick = function() RunConsoleCommand( data.commands.effectname, effect ) if data.commands.color then if parent[effect].EffectOptions["Colorable"] then RunConsoleCommand( data.commands.color .. "_enabled", "1" ) else RunConsoleCommand( data.commands.color .. "_enabled", "0" ) end if parent[effect].EffectOptions["ColorOutOfOne"] then RunConsoleCommand( data.commands.color .. "_outofone", "1" ) else RunConsoleCommand( data.commands.color .. "_outofone", "0" ) end end end //Right Click: Display an option to add/remove the effect from favorites parent[effect].DoRightClick = function() local menu = DermaMenu() if !ParticleBrowserTracerFavorites.Effects[effectdisplay] then local option = menu:AddOption( "Favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay, { ["InternalName"] = effect, ["Colorable"] = tobool(parent[effect].EffectOptions["Colorable"]), ["ColorOutOfOne"] = tobool(parent[effect].EffectOptions["ColorOutOfOne"]), } ) end ) option:SetImage("icon16/add.png") else local option = menu:AddOption( "Un-favorite \'\'" .. effectdisplay .. "\'\'", function() ParticleBrowserTracerFavorites_UpdateEffect( effectdisplay ) //calling this function with no second arg tells it to remove this effect from the list end ) option:SetImage("icon16/delete.png") end menu:Open() end end end //Games self.tree.games = self.tree:AddNode("Games") self.tree.games.Icon:SetImage("icon16/folder_database.png") self.tree.games:SetExpanded(true) // for tabname, tab in SortedPairs (ParticleListTable.Games) do if tab.Info.EffectOptions.Tracers then self.tree.games[tabname] = self.tree.games:AddNode(tab.Info.CategoryName) self.tree.games[tabname].Icon:SetImage(tab.Info.Icon) //Create the utilfx list if tab.Info.UtilEffects then local tracerlist = {} for k, v in pairs (tab.Info.UtilEffects) do //in a utileffect table, the effect name is stored in the value if tab.Info.EffectOptions.Tracers[v] then tracerlist[k] = v end end if table.Count(tracerlist) > 0 then self.tree.games[tabname].UtilEffects = self.tree.games[tabname]:AddNode("Scripted Effects") self.tree.games[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") self.tree.games[tabname].UtilEffects.IsPopulated = false self.tree.games[tabname].UtilEffects.DoClick = function() if self.tree.games[tabname].UtilEffects.IsPopulated == true then return end PopulateEffectList(self.tree.games[tabname].UtilEffects, tracerlist, tab.Info.EffectOptions) self.tree.games[tabname].UtilEffects.IsPopulated = true self.tree.games[tabname].UtilEffects:SetExpanded(true) end end end //Create the .pcf lists for pcfname, particles in SortedPairs (tab.Particles) do local tracerlist = {} for k, v in pairs (particles) do //in a particle table, the effect name is stored in the key if tab.Info.EffectOptions.Tracers[k] then tracerlist[k] = v end end if table.Count(tracerlist) > 0 then local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems self.tree.games[tabname][pcftabname] = self.tree.games[tabname]:AddNode(pcfname) self.tree.games[tabname][pcftabname].Icon:SetImage("icon16/page.png") self.tree.games[tabname][pcftabname].IsPopulated = false self.tree.games[tabname][pcftabname].DoClick = function() if self.tree.games[tabname][pcftabname].IsPopulated == true then return end PopulateEffectList(self.tree.games[tabname][pcftabname], tracerlist, tab.Info.EffectOptions) self.tree.games[tabname][pcftabname].IsPopulated = true self.tree.games[tabname][pcftabname]:SetExpanded(true) end end end end end //Addons self.tree.addons = self.tree:AddNode("Addons") self.tree.addons.Icon:SetImage("icon16/folder_database.png") self.tree.addons:SetExpanded(true) // for tabname, tab in SortedPairs (ParticleListTable.Addons) do if tab.Info.EffectOptions.Tracers then self.tree.addons[tabname] = self.tree.addons:AddNode(tab.Info.CategoryName) self.tree.addons[tabname].Icon:SetImage(tab.Info.Icon) //Create the utilfx list if tab.Info.UtilEffects then local tracerlist = {} for k, v in pairs (tab.Info.UtilEffects) do //in a utileffect table, the effect name is stored in the value if tab.Info.EffectOptions.Tracers[v] then tracerlist[k] = v end end if table.Count(tracerlist) > 0 then self.tree.addons[tabname].UtilEffects = self.tree.addons[tabname]:AddNode("Scripted Effects") self.tree.addons[tabname].UtilEffects.Icon:SetImage("icon16/page_gear.png") self.tree.addons[tabname].UtilEffects.IsPopulated = false self.tree.addons[tabname].UtilEffects.DoClick = function() if self.tree.addons[tabname].UtilEffects.IsPopulated == true then return end PopulateEffectList(self.tree.addons[tabname].UtilEffects, tracerlist, tab.Info.EffectOptions) self.tree.addons[tabname].UtilEffects.IsPopulated = true self.tree.addons[tabname].UtilEffects:SetExpanded(true) end end end //Create the .pcf lists for pcfname, particles in SortedPairs (tab.Particles) do local tracerlist = {} for k, v in pairs (particles) do //in a particle table, the effect name is stored in the key if tab.Info.EffectOptions.Tracers[k] then tracerlist[k] = v end end if table.Count(tracerlist) > 0 then local pcftabname = string.StripExtension(pcfname) //just in case having a dot in the key name causes problems self.tree.addons[tabname][pcftabname] = self.tree.addons[tabname]:AddNode(pcfname) self.tree.addons[tabname][pcftabname].Icon:SetImage("icon16/page.png") self.tree.addons[tabname][pcftabname].IsPopulated = false self.tree.addons[tabname][pcftabname].DoClick = function() if self.tree.addons[tabname][pcftabname].IsPopulated == true then return end PopulateEffectList(self.tree.addons[tabname][pcftabname], tracerlist, tab.Info.EffectOptions) self.tree.addons[tabname][pcftabname].IsPopulated = true self.tree.addons[tabname][pcftabname]:SetExpanded(true) end end end end end //Favorites self.tree.favorites = self.tree:AddNode("Favorites (Tracers)") self.tree.favorites.Icon:SetImage("icon16/star.png") self.tree.favorites:SetExpanded(true) // table.insert( ParticleBrowserTracerFavorites.Children, self.tree.favorites ) //Add it to the Favorites table's list of child panels and let it do the rest of the work self.tree.favorites.CommandInfo = data.commands //Store the console command info in the panel so the UpdateChildPanel function can access it ParticleBrowserTracerFavorites_UpdateChildPanel( self.tree.favorites ) self.effectnameentry = vgui.Create( "DTextEntry", self.back ) self.effectnameentry:SetConVar( data.commands.effectname ) self.back:AddItem(self.effectnameentry) self.effectnameentry:GetParent():DockPadding(5,5,5,10) if data.commands.color then self.color = vgui.Create("DForm", self.back) self.color.Paint = function() derma.SkinHook( "Paint", "CollapsibleCategory", self.color, self.color:GetWide(), self.color:GetTall() ) surface.SetDrawColor( Color(0,0,0,110) ) surface.DrawRect( 0, 0, self.color:GetWide(), self.color:GetTall() ) end self.color:Dock(TOP) self.back:AddItem(self.color) self.color:GetParent():DockPadding(5,0,5,0) //Modify the header and tweak things so it doesn't show up when the category is closed. self.color.Header:SetText( "Colorable" ) self.color.Header:SetImage("icon16/fire_rainbow.png") self.color.Header.DoClick = function() end self.color.PerformLayout = function() local us = self.color //so when we copy this over to another dform we only have to change this one line local Padding = us:GetPadding() or 0 if ( us.Contents ) then if ( us:GetExpanded() ) then us.Contents:InvalidateLayout( true ) us.Contents:SetVisible( true ) else us.Contents:SetVisible( false ) end end if ( us:GetExpanded() ) then us:SizeToChildren( false, true ) else us:SetTall(0) //this is the only real change from the standard DForm:PerformLayout() end -- Make sure the color of header text is set us.Header:ApplySchemeSettings() us.animSlide:Run() us:UpdateAltLines(); end self.color.text = vgui.Create("DLabel", self.color) self.color.text:SetColor( Color(60,60,60,255) ) self.color.text:SetText("This effect is colorable.") self.color.text:SetWrap( true ) self.color.text:SetAutoStretchVertical( true ) self.color.text:Dock(TOP) self.color.text:SizeToContents() self.color:AddItem(self.color.text) self.color.text:GetParent():DockPadding( 15, 10, 15, 10 ) self.color.selection = vgui.Create( "CtrlColor", self.color ) self.color.selection:SetLabel( "" ) self.color.selection:SetConVarR( data.commands.color .. "_r" ) self.color.selection:SetConVarG( data.commands.color .. "_g" ) self.color.selection:SetConVarB( data.commands.color .. "_b" ) self.color.selection:SetConVarA( nil ) self.color.selection:Dock(TOP) self.color:AddItem(self.color.selection) self.color.selection:GetParent():DockPadding( 10, 0, 10, 10 ) end self.back.Think = function() if self.color then if GetConVarNumber( data.commands.color .. "_enabled" ) > 0 then //expand it if self.color:GetExpanded() == false then self.color:Toggle() end else //contract it if self.color:GetExpanded() == true then self.color:Toggle() end self.color:GetParent():SetTall(self.color:GetTall()) end end end end end