GMADtÕLZWeapon Properties Editor { "description": "Description", "type": "tool", "tags": [ "fun", "roleplay" ] }Author Namelua/autorun/lf_weapon_properties_editor.luaQ}|”¿Blua/lf_shared/von_1_3_4.luaSV²‹Ã-- Weapon Properties Editor -- by LibertyForce http://steamcommunity.com/id/libertyforce if SERVER then AddCSLuaFile() local von = include( "lf_shared/von_1_3_4.lua" ) util.AddNetworkString( "lf_weapon_properties_editor" ) local Weapons_Edited = {} local Weapons_Replaced = {} local Weapons_TempActive = {} local dir = "lf_weapon_properties_editor" local dir_presets = dir.."/presets_v1_0" if not file.Exists( dir, "DATA" ) then file.CreateDir( dir ) end if not file.Exists( dir_presets, "DATA" ) then file.CreateDir( dir_presets ) end local function netmsg( id ) net.Start( "lf_weapon_properties_editor" ) net.WriteUInt( id, 4 ) end local function Notify( ply, id ) netmsg( 0 ) net.WriteUInt( id, 3 ) net.Send( ply ) end local function ApplyChanges( weapon ) local tbl = weapons.GetStored( weapon.Class ) if not istable( tbl ) then return end Weapons_Edited[weapon.Class] = weapon if istable( tbl.Primary ) and istable( weapon.Primary ) then if weapon.Primary.Ammo and tbl.Primary.Ammo then tbl.Primary.Ammo = weapon.Primary.Ammo end if weapon.Primary.Damage and tbl.Primary.Damage then tbl.Primary.Damage = weapon.Primary.Damage end if weapon.Primary.ClipSize and tbl.Primary.ClipSize then tbl.Primary.ClipSize = weapon.Primary.ClipSize end if weapon.Primary.DefaultClip and tbl.Primary.DefaultClip then tbl.Primary.DefaultClip = weapon.Primary.DefaultClip end if weapon.Primary.RPM then if tbl.Primary.RPM then tbl.Primary.RPM = weapon.Primary.RPM elseif ( tbl.Base == "cw_base" or tbl.Base == "fas2_base" ) and tbl.FireDelay then tbl.FireDelay = ( 60 / weapon.Primary.RPM ) end end end if istable( tbl.Secondary ) and istable( weapon.Secondary ) then if weapon.Secondary.Ammo and tbl.Secondary.Ammo then tbl.Secondary.Ammo = weapon.Secondary.Ammo end if weapon.Secondary.Damage and tbl.Secondary.Damage then tbl.Secondary.Damage = weapon.Secondary.Damage end if weapon.Secondary.ClipSize and tbl.Secondary.ClipSize then tbl.Secondary.ClipSize = weapon.Secondary.ClipSize end if weapon.Secondary.DefaultClip and tbl.Secondary.DefaultClip then tbl.Secondary.DefaultClip = weapon.Secondary.DefaultClip end end if weapon.Slot and tbl.Slot then tbl.Slot = weapon.Slot end if weapon.SlotPos and tbl.SlotPos then tbl.SlotPos = weapon.SlotPos end if istable( tbl.FireModes ) and istable( weapon.FireModes ) then if weapon.FireModes[1] and tbl.FireModes[1] then tbl.FireModes[1] = weapon.FireModes[1] end if weapon.FireModes[2] and tbl.FireModes[2] then tbl.FireModes[2] = weapon.FireModes[2] end if weapon.FireModes[3] and tbl.FireModes[3] then tbl.FireModes[3] = weapon.FireModes[3] end end netmsg( 1 ) net.WriteTable( weapon ) net.Broadcast() end local function RespawnWeapon( weapon ) for _, ply in pairs( player.GetAll() ) do if ply:HasWeapon( weapon ) then local ammo1 = ply:GetWeapon( weapon ):Clip1() local ammo2 = ply:GetWeapon( weapon ):Clip2() ply:StripWeapon( weapon ) local wep_ent = ply:Give( weapon, true ) local maxammo1 = wep_ent:GetMaxClip1() local maxammo2 = wep_ent:GetMaxClip2() if ammo1 > maxammo1 then ammo1 = maxammo1 end if ammo2 > maxammo2 then ammo2 = maxammo2 end wep_ent:SetClip1( ammo1 ) wep_ent:SetClip2( ammo2 ) end end end local function GetPresets( ply ) local files = file.Find( dir_presets.."/*.txt", "DATA" ) if not istable( files ) then return end netmsg( 3 ) net.WriteTable( files ) net.Send( ply ) end local function SavePreset( weapon_class, ply ) if istable( Weapons_Edited[weapon_class] ) then file.Write( dir_presets.."/"..tostring( weapon_class )..".txt", von.serialize( Weapons_Edited[weapon_class] ) ) Notify( ply, 1 ) else Notify( ply, 2 ) end end local function DeletePreset( filename, ply ) if file.Exists( dir_presets.."/"..filename..".txt", "DATA" ) then file.Delete( dir_presets.."/"..filename..".txt" ) end GetPresets( ply ) end local function GetReplacements( ply ) if not istable( Weapons_Replaced ) then return end netmsg( 6 ) net.WriteTable( Weapons_Replaced ) net.Send( ply ) end local function SaveReplacement( weapon, value, ply ) if not istable( Weapons_Replaced ) then return end if weapon == value or Weapons_Replaced[value] then -- Prevents loops Weapons_Replaced[weapon] = nil Notify( ply, 3 ) return end Weapons_Replaced[weapon] = value file.Write( dir.."/replacements.txt", von.serialize( Weapons_Replaced ) ) GetReplacements( ply ) end local function DeleteReplacement( weapon, ply ) if not istable( Weapons_Replaced ) then return end Weapons_Replaced[weapon] = nil file.Write( dir.."/replacements.txt", von.serialize( Weapons_Replaced ) ) if ply then GetReplacements( ply ) end end local function LoadFiles() if file.Exists( dir.."/replacements.txt", "DATA" ) then Weapons_Replaced = von.deserialize( file.Read( dir.."/replacements.txt", "DATA" ) ) or {} end local files = file.Find( dir_presets.."/*.txt", "DATA" ) if not istable( files ) then return end for _, filename in pairs( files ) do local weapon_class = string.TrimRight( filename, ".txt" ) local weapon = von.deserialize( file.Read( dir_presets.."/"..tostring( filename ), "DATA" ) ) if not istable( weapon ) then continue end if weapon.Class ~= weapon_class then continue end if not istable( weapon.Primary ) or not istable( weapon.Secondary ) or not istable( weapon.FireModes ) then continue end ApplyChanges( weapon ) end files = nil end hook.Add( "InitPostEntity", "lf_weapon_properties_editor_init", LoadFiles ) if player.GetCount() > 0 then LoadFiles() end -- Debug (for lua refresh) net.Receive("lf_weapon_properties_editor", function( len, ply ) if not ply:IsSuperAdmin() then return end local func = net.ReadUInt( 4 ) if func == 0 then -- Player Notifications -- elseif func == 1 then -- Apply weapon changes to server and clients ApplyChanges( net.ReadTable() ) Notify( ply, 0 ) elseif func == 2 then -- Saving Presets SavePreset( net.ReadString(), ply ) elseif func == 3 then -- Getting Presets List GetPresets( ply ) elseif func == 4 then -- Deleting Presets DeletePreset( net.ReadString(), ply ) elseif func == 5 then -- Saving Replacements SaveReplacement( net.ReadString(), net.ReadString(), ply ) elseif func == 6 then -- Getting Replacements GetReplacements( ply ) elseif func == 7 then -- Deleting Replacements DeleteReplacement( net.ReadString(), ply ) elseif func == 8 then -- Refresh weapon entities RespawnWeapon( net.ReadString() ) end end ) hook.Add( "PlayerAuthed", "lf_weapon_properties_editor_authed", function( ply ) for k, weapon in pairs( Weapons_Edited ) do netmsg( 1 ) net.WriteTable( weapon ) net.Send( ply ) end end ) hook.Add( "PlayerCanPickupWeapon", "lf_weapon_properties_editor_pickup", function( ply, wep_ent ) local wep_ent = wep_ent local wep_class = wep_ent:GetClass() if Weapons_Replaced[wep_class] then if not Weapons_TempActive[wep_ent] then -- Prevents running more then once per entity Weapons_TempActive[wep_ent] = true timer.Simple( 2, function() Weapons_TempActive[wep_ent] = nil end ) wep_ent:Remove() if Weapons_Replaced[Weapons_Replaced[wep_class]] then return false end -- Prevents loops local weapon = ply:GetWeapon( Weapons_Replaced[wep_class] ) if Weapons_Replaced[wep_class] ~= "" and not IsValid( weapon ) then weapon = ply:Give( Weapons_Replaced[wep_class], true ) if IsValid( weapon ) and not weapon:IsWeapon() then weapon:Remove() DeleteReplacement( wep_class ) elseif IsValid( weapon ) then weapon:SetClip1( weapon:GetMaxClip1() ) weapon:SetClip2( 0 ) end else if not IsValid( weapon ) then weapon = wep_ent end local clip = weapon:GetMaxClip1() if clip <= 0 then clip = 1 end local ammo = game.GetAmmoName( weapon:GetPrimaryAmmoType() ) if ammo then ply:GiveAmmo( clip, ammo ) end end end return false end end ) end ----------------------------------------------------------------------------------------------------------------------------------------------------- if CLIENT then local Version = "1.1" local Menu = { Main = {}, Editor = {}, PresetList = {}, Replacements = {} } local default_ammo_types = { "none", "Pistol", "357", "SMG1", "AR2", "Buckshot", "XBowBolt", "Grenade", "RPG_Round", "slam", "SMG1_Grenade", "AR2AltFire", "AlyxGun", "SniperRound", "SniperPenetratedRound", "Thumper", "Gravity", "Battery", "GaussEnergy", "CombineCannon", "AirboatGun", "StriderMinigun", "HelicopterGun", "9mmRound", "MP5_Grenade", "Hornet", "StriderMinigunDirect", "CombineHeavyCannon" } local default_weapon_classes = { "weapon_crowbar", "weapon_physcannon", "weapon_stunstick", "weapon_pistol", "weapon_357", "weapon_smg1", "weapon_ar2", "weapon_shotgun", "weapon_crossbow", "weapon_frag", "weapon_rpg", "weapon_slam", "weapon_bugbait" } local function KeyboardOn( pnl ) if ( IsValid( Menu.Main.Frame ) and IsValid( pnl ) and pnl:HasParent( Menu.Main.Frame ) ) then Menu.Main.Frame:SetKeyboardInputEnabled( true ) end end hook.Add( "OnTextEntryGetFocus", "lf_weapon_properties_editor_keyboard_on", KeyboardOn ) local function KeyboardOff( pnl ) if ( IsValid( Menu.Main.Frame ) and IsValid( pnl ) and pnl:HasParent( Menu.Main.Frame ) ) then Menu.Main.Frame:SetKeyboardInputEnabled( false ) end end hook.Add( "OnTextEntryLoseFocus", "lf_weapon_properties_editor_keyboard_off", KeyboardOff ) local function netmsg( id ) net.Start( "lf_weapon_properties_editor" ) net.WriteUInt( id, 4 ) end local function ApplyChanges( weapon ) local tbl = weapons.GetStored( weapon.Class ) if not istable( tbl ) then return end if istable( tbl.Primary ) and istable( weapon.Primary ) then if weapon.Primary.Ammo and tbl.Primary.Ammo then tbl.Primary.Ammo = weapon.Primary.Ammo end if weapon.Primary.Damage and tbl.Primary.Damage then tbl.Primary.Damage = weapon.Primary.Damage end if weapon.Primary.ClipSize and tbl.Primary.ClipSize then tbl.Primary.ClipSize = weapon.Primary.ClipSize end if weapon.Primary.DefaultClip and tbl.Primary.DefaultClip then tbl.Primary.DefaultClip = weapon.Primary.DefaultClip end if weapon.Primary.RPM then if tbl.Primary.RPM then tbl.Primary.RPM = weapon.Primary.RPM elseif ( tbl.Base == "cw_base" or tbl.Base == "fas2_base" ) and tbl.FireDelay then tbl.FireDelay = ( 60 / weapon.Primary.RPM ) end end end if istable( tbl.Secondary ) and istable( weapon.Secondary ) then if weapon.Secondary.Ammo and tbl.Secondary.Ammo then tbl.Secondary.Ammo = weapon.Secondary.Ammo end if weapon.Secondary.Damage and tbl.Secondary.Damage then tbl.Secondary.Damage = weapon.Secondary.Damage end if weapon.Secondary.ClipSize and tbl.Secondary.ClipSize then tbl.Secondary.ClipSize = weapon.Secondary.ClipSize end if weapon.Secondary.DefaultClip and tbl.Secondary.DefaultClip then tbl.Secondary.DefaultClip = weapon.Secondary.DefaultClip end end if weapon.Slot and tbl.Slot then tbl.Slot = weapon.Slot end if weapon.SlotPos and tbl.SlotPos then tbl.SlotPos = weapon.SlotPos end if istable( tbl.FireModes ) and istable( weapon.FireModes ) then if weapon.FireModes[1] and tbl.FireModes[1] then tbl.FireModes[1] = weapon.FireModes[1] end if weapon.FireModes[2] and tbl.FireModes[2] then tbl.FireModes[2] = weapon.FireModes[2] end if weapon.FireModes[3] and tbl.FireModes[3] then tbl.FireModes[3] = weapon.FireModes[3] end end end local function Notify( msg ) if msg == 0 then notification.AddLegacy( "Weapon modifications applied.", NOTIFY_GENERIC, 5 ) elseif msg == 1 then notification.AddLegacy( "Preset saved successfully.", NOTIFY_GENERIC, 5 ) elseif msg == 2 then notification.AddLegacy( "Weapon is not modified. Apply settings first.", NOTIFY_ERROR, 5 ) elseif msg == 3 then notification.AddLegacy( "Not possible. Remove conflicting replacements first.", NOTIFY_ERROR, 5 ) elseif msg == 4 then notification.AddLegacy( "Weapon class invalid. Please enter the class of a registered SWEP.", NOTIFY_ERROR, 5 ) end end net.Receive("lf_weapon_properties_editor", function() local func = net.ReadUInt( 4 ) if func == 0 then -- Player Notifications Notify( net.ReadUInt( 3 ) ) elseif func == 1 then -- Apply weapon changes to server and clients ApplyChanges( net.ReadTable() ) elseif func == 2 then -- Saving Presets -- elseif func == 3 then -- Getting Presets List if IsValid( Menu.PresetList.List ) then Menu.PresetList.List:Populate( net.ReadTable() ) end elseif func == 4 then -- Deleting Presets -- elseif func == 5 then -- Saving Replacements -- elseif func == 6 then -- Getting Replacements List if IsValid( Menu.Replacements.List ) then Menu.Replacements.List:Populate( net.ReadTable() ) end elseif func == 7 then -- Deleting Replacements -- elseif func == 8 then -- Refresh weapon entities -- end end ) -- Blur Code by: https://facepunch.com/member.php?u=237675 local blur = Material( "pp/blurscreen" ) local function DrawBlur( panel, amount ) local x, y = panel:LocalToScreen( 0, 0 ) local scrW, scrH = ScrW(), ScrH() surface.SetDrawColor( 255, 255, 255 ) surface.SetMaterial( blur ) for i = 1, 3 do blur:SetFloat( "$blur", ( i / 3 ) * ( amount or 6 ) ) blur:Recompute() render.UpdateScreenEffectTexture() surface.DrawTexturedRect( x * -1, y * -1, scrW, scrH ) end end function Menu.Editor:Init( weapon_class ) local tbl = weapons.GetStored( weapon_class ) if not istable( tbl ) then Notify( 4 ) return end local weapon = {} weapon.Primary, weapon.Secondary, weapon.FireModes = {}, {}, {} weapon.Class = weapon_class if istable( tbl.Primary ) then if tbl.Primary.Ammo then weapon.Primary.Ammo = tbl.Primary.Ammo end if tbl.Primary.Damage then weapon.Primary.Damage = tbl.Primary.Damage end if tbl.Primary.ClipSize then weapon.Primary.ClipSize = tbl.Primary.ClipSize end if tbl.Primary.DefaultClip then weapon.Primary.DefaultClip = tbl.Primary.DefaultClip end if tbl.Primary.RPM then weapon.Primary.RPM = tbl.Primary.RPM elseif ( tbl.Base == "cw_base" or tbl.Base == "fas2_base" ) and tbl.FireDelay then weapon.Primary.RPM = math.Round( ( 60 / tbl.FireDelay ) ) end end if istable( tbl.Secondary ) then if tbl.Secondary.Ammo then weapon.Secondary.Ammo = tbl.Secondary.Ammo end if tbl.Secondary.Damage then weapon.Secondary.Damage = tbl.Secondary.Damage end if tbl.Secondary.ClipSize then weapon.Secondary.ClipSize = tbl.Secondary.ClipSize end if tbl.Secondary.DefaultClip then weapon.Secondary.DefaultClip = tbl.Secondary.DefaultClip end end if tbl.Slot then weapon.Slot = tbl.Slot end if tbl.SlotPos then weapon.SlotPos = tbl.SlotPos end if istable( tbl.FireModes ) then if tbl.FireModes[1] then weapon.FireModes[1] = tbl.FireModes[1] end if tbl.FireModes[2] then weapon.FireModes[2] = tbl.FireModes[2] end if tbl.FireModes[3] then weapon.FireModes[3] = tbl.FireModes[3] end end local Frame = vgui.Create( "DFrame", Menu.Main.Frame ) local fw, fh = 600, 600 local pw, ph = fw - 10, fh - 34 Frame:SetPos( ( ScrW() / 2 ) - ( fw / 2 ), ( ScrH() / 2 ) - ( fh / 2 ) ) Frame:SetSize( fw, fh ) Frame:SetTitle( weapon.Class ) Frame:SetVisible( true ) Frame:SetDraggable( true ) Frame:SetScreenLock( false ) Frame:ShowCloseButton( true ) Frame:MakePopup() Frame:SetKeyboardInputEnabled( false ) function Frame:Paint( w, h ) DrawBlur( self, 2 ) draw.RoundedBox( 10, 0, 0, w, h, Color( 0, 99, 177, 200 ) ) return true end function Frame.lblTitle:Paint( w, h ) draw.SimpleTextOutlined( Frame.lblTitle:GetText(), "DermaDefaultBold", 1, 2, Color( 255, 255, 255, 255 ), 0, 0, 1, Color( 0, 0, 0, 255 ) ) return true end local pnl = Frame:Add( "DPanel" ) pnl:Dock( FILL ) pnl:DockPadding( 10, 10, 10, 10 ) local prop = pnl:Add( "DCategoryList" ) prop:Dock( FILL ) local function AddLineText( list, text, val ) local line = list:Add( "DPanel" ) line:DockPadding( 5, 2, 5, 2 ) line:SetDrawBackground( false ) local id if val then local lbl = line:Add( "DLabel" ) lbl:Dock( LEFT ) lbl:SetWide( 239 ) lbl:SetDark( true ) lbl:SetText( text ) id = line:Add( "DTextEntry" ) id:Dock( FILL ) id:SetText( val or "" ) else local lbl = line:Add( "DLabel" ) lbl:Dock( FILL ) lbl:SetText( text ) end return line, id end local function AddLineInt( list, text, val, min, max ) local line = list:Add( "DPanel" ) line:DockPadding( 5, 2, 5, 2 ) line:SetDrawBackground( false ) local id if val and val >= 0 then id = line:Add( "DNumSlider" ) id:Dock( FILL ) id:SetDark( true ) id:SetDecimals( 0 ) id:SetMinMax( min, max ) id:SetText( text ) id:SetValue( val or 0 ) else local lbl = line:Add( "DLabel" ) lbl:Dock( FILL ) lbl:SetText( text ) end return line, id end local cat = prop:Add( "Primary Ammo" ) local list = vgui.Create( "DListLayout" ) cat:SetContents( list ) local line, rPriAmmo = AddLineText( list, "Ammo Type:", weapon.Primary.Ammo ) if rPriAmmo then local c = line:Add( "DComboBox" ) c:Dock( RIGHT ) c:SetWide( 100 ) c:SetSortItems( false ) c:SetValue( "Default Types" ) for _, v in pairs( default_ammo_types ) do c:AddChoice( v ) end function c:OnSelect( index, value ) rPriAmmo:SetText( value ) c:SetValue( "Default Types" ) end end local line, rPriDamage = AddLineInt( list, "Damage:", weapon.Primary.Damage, 0, 1000 ) local line, rPriClipSize = AddLineInt( list, "Magazine Size:", weapon.Primary.ClipSize, 0, 500 ) local line, rPriDefaultClip = AddLineInt( list, "Rounds per pickup:", weapon.Primary.DefaultClip, 0, 500 ) local line, rPriRPM = AddLineInt( list, "Firerate (RPM):", weapon.Primary.RPM, 0, 5000 ) local cat = prop:Add( "Secondary Ammo" ) local list = vgui.Create( "DListLayout" ) cat:SetContents( list ) local line, rSecAmmo = AddLineText( list, "Ammo Type:", weapon.Secondary.Ammo ) if rSecAmmo then local c = line:Add( "DComboBox" ) c:Dock( RIGHT ) c:SetWide( 100 ) c:SetSortItems( false ) c:SetValue( "Default Types" ) for _, v in pairs( default_ammo_types ) do c:AddChoice( v ) end function c:OnSelect( index, value ) rSecAmmo:SetText( value ) c:SetValue( "Default Types" ) end end local line, rSecDamage = AddLineInt( list, "Damage:", weapon.Secondary.Damage, 0, 1000 ) local line, rSecClipSize = AddLineInt( list, "Magazine Size:", weapon.Secondary.ClipSize, 0, 500 ) local line, rSecDefaultClip = AddLineInt( list, "Rounds per pickup:", weapon.Secondary.DefaultClip, 0, 500 ) local cat = prop:Add( "Weapon Slots" ) local list = vgui.Create( "DListLayout" ) cat:SetContents( list ) local niceslot if weapon.Slot then niceslot = weapon.Slot + 1 end local line, rSlot = AddLineInt( list, "Slot (1 - 6 recommended):", niceslot, 1, 10 ) local line, rSlotPos = AddLineInt( list, "Slot Position:", weapon.SlotPos, 0, 127 ) local cat = prop:Add( "FireModes" ) local list = vgui.Create( "DListLayout" ) cat:SetContents( list ) local line, rFireModes1 = AddLineText( list, "Option 1:", weapon.FireModes[1] ) local line, rFireModes2 = AddLineText( list, "Option 2:", weapon.FireModes[2] ) local line, rFireModes3 = AddLineText( list, "Option 3:", weapon.FireModes[3] ) local subpnl = pnl:Add( "DPanel" ) subpnl:Dock( BOTTOM ) subpnl:DockMargin( 0, 10, 0, 0 ) subpnl:SetHeight( 20 ) subpnl:SetDrawBackground( false ) local lw = ( pw - 10 ) / 2 - 10 local b = subpnl:Add( "DButton" ) b:Dock( LEFT ) b:SetWide( lw ) b:SetText( "Save modifications as preset" ) b.DoClick = function() netmsg( 2 ) net.WriteString( weapon.Class ) net.SendToServer() end local b = subpnl:Add( "DButton" ) b:Dock( RIGHT ) b:SetWide( lw ) b:SetText( "Force update of all players with this weapon" ) b.DoClick = function() netmsg( 8 ) net.WriteString( weapon.Class ) net.SendToServer() end local b = pnl:Add( "DButton" ) b:Dock( BOTTOM ) b:DockMargin( 0, 10, 0, 0 ) b:SetHeight( 30 ) b:SetText( "Apply Changes" ) b.DoClick = function() if rPriAmmo then weapon.Primary.Ammo = tostring( rPriAmmo:GetValue() ) end if rPriDamage then weapon.Primary.Damage = math.Round( rPriDamage:GetValue() ) end if rPriClipSize then weapon.Primary.ClipSize = math.Round( rPriClipSize:GetValue() ) end if rPriDefaultClip then weapon.Primary.DefaultClip = math.Round( rPriDefaultClip:GetValue() ) end if rPriRPM then weapon.Primary.RPM = math.Round( rPriRPM:GetValue() ) end if rSecAmmo then weapon.Secondary.Ammo = tostring( rSecAmmo:GetValue() ) end if rSecDamage then weapon.Secondary.Damage = math.Round( rSecDamage:GetValue() ) end if rSecClipSize then weapon.Secondary.ClipSize = math.Round( rSecClipSize:GetValue() ) end if rSecDefaultClip then weapon.Secondary.DefaultClip = math.Round( rSecDefaultClip:GetValue() ) end if rSlot then weapon.Slot = math.Round( rSlot:GetValue() - 1 ) end if rSlotPos then weapon.SlotPos = math.Round( rSlotPos:GetValue() ) end if rFireModes1 then weapon.FireModes[1] = tostring( rFireModes1:GetValue() ) end if rFireModes2 then weapon.FireModes[2] = tostring( rFireModes2:GetValue() ) end if rFireModes3 then weapon.FireModes[3] = tostring( rFireModes3:GetValue() ) end netmsg( 1 ) net.WriteTable( weapon ) net.SendToServer() end end function Menu.PresetList:Init() local Frame = vgui.Create( "DFrame", Menu.Main.Frame ) local fw, fh = 320, 600 local pw, ph = fw - 10, fh - 34 Frame:SetPos( 335, 10 ) Frame:SetSize( fw, fh ) Frame:SetTitle( "Presets" ) Frame:SetVisible( true ) Frame:SetDraggable( true ) Frame:SetScreenLock( false ) Frame:ShowCloseButton( true ) Frame:MakePopup() Frame:SetKeyboardInputEnabled( false ) function Frame:Paint( w, h ) DrawBlur( self, 2 ) draw.RoundedBox( 10, 0, 0, w, h, Color( 0, 99, 177, 200 ) ) return true end function Frame.lblTitle:Paint( w, h ) draw.SimpleTextOutlined( Frame.lblTitle:GetText(), "DermaDefaultBold", 1, 2, Color( 255, 255, 255, 255 ), 0, 0, 1, Color( 0, 0, 0, 255 ) ) return true end local pnl = Frame:Add( "DPanel" ) pnl:Dock( FILL ) pnl:DockPadding( 10, 10, 10, 10 ) Menu.PresetList.List = pnl:Add( "DListView" ) Menu.PresetList.List:Dock( FILL ) Menu.PresetList.List:SetMultiSelect( true ) Menu.PresetList.List:AddColumn( "Presets" ) function Menu.PresetList.List:DoDoubleClick( id, sel ) local weapon = tostring( sel:GetValue( 1 ) ) Menu.Editor:Init( weapon ) end function Menu.PresetList.List:Populate( files ) self:Clear() for _, v in pairs( files ) do self:AddLine( string.TrimRight( v, ".txt" ) ) end self:SortByColumn( 1 ) end netmsg( 3 ) net.SendToServer() local b = pnl:Add( "DButton" ) b:Dock( BOTTOM ) b:DockMargin( 0, 10, 0, 0 ) b:SetHeight( 20 ) b:SetText( "Delete selected files" ) b.DoClick = function() local sel = Menu.PresetList.List:GetSelected() for k, v in pairs( sel ) do local filename = tostring( v:GetValue(1) ) netmsg( 4 ) net.WriteString( filename ) net.SendToServer() end end end function Menu.Replacements:Init() local Frame = vgui.Create( "DFrame", Menu.Main.Frame ) local fw, fh = 600, 600 local pw, ph = fw - 10, fh - 34 Frame:SetPos( ( ScrW() / 2 ) - ( fw / 2 ), ( ScrH() / 2 ) - ( fh / 2 ) ) Frame:SetSize( fw, fh ) Frame:SetTitle( "Weapon Replacements" ) Frame:SetVisible( true ) Frame:SetDraggable( true ) Frame:SetScreenLock( false ) Frame:ShowCloseButton( true ) Frame:MakePopup() Frame:SetKeyboardInputEnabled( false ) function Frame:Paint( w, h ) DrawBlur( self, 2 ) draw.RoundedBox( 10, 0, 0, w, h, Color( 0, 99, 177, 200 ) ) return true end function Frame.lblTitle:Paint( w, h ) draw.SimpleTextOutlined( Frame.lblTitle:GetText(), "DermaDefaultBold", 1, 2, Color( 255, 255, 255, 255 ), 0, 0, 1, Color( 0, 0, 0, 255 ) ) return true end local pnl = Frame:Add( "DPanel" ) pnl:Dock( FILL ) pnl:DockPadding( 10, 10, 10, 10 ) local lw = ( pw - 10 ) / 2 - 10 local subpnl = pnl:Add( "DPanel" ) subpnl:Dock( TOP ) subpnl:DockMargin( 0, 0, 0, 10 ) subpnl:SetHeight( 105 ) subpnl:SetDrawBackground( false ) local left = subpnl:Add( "DPanel" ) left:Dock( LEFT ) left:SetWide( lw ) left:SetDrawBackground( false ) local right = subpnl:Add( "DPanel" ) right:Dock( RIGHT ) right:SetWide( lw ) right:SetDrawBackground( false ) local lbl = left:Add( "DLabel" ) lbl:Dock( TOP ) lbl:DockMargin( 0, 0, 0, 10 ) lbl:SetHeight( 15 ) lbl:SetDark( true ) lbl:SetText( "Picking up this weapon ..." ) local lbl = right:Add( "DLabel" ) lbl:Dock( TOP ) lbl:DockMargin( 0, 0, 0, 10 ) lbl:SetHeight( 15 ) lbl:SetDark( true ) lbl:SetText( "will give players this weapon ..." ) local c = left:Add( "DComboBox" ) c:Dock( TOP ) c:DockMargin( 0, 0, 0, 10 ) c:SetHeight( 20 ) c:SetSortItems( false ) c:SetValue( "Copy default HL2 weapon" ) for _, v in pairs( default_weapon_classes ) do c:AddChoice( v ) end function c:OnSelect( index, value ) Menu.Replacements.LeftEntry:SetText( value ) c:SetValue( "Copy default HL2 weapon" ) end local lbl = right:Add( "DLabel" ) lbl:Dock( TOP ) lbl:DockMargin( 0, 0, 0, 10 ) lbl:SetHeight( 20 ) lbl:SetDark( true ) lbl:SetText( "(Leave the right field empty, to block the weapon.)" ) local b = left:Add( "DButton" ) b:Dock( TOP ) b:DockMargin( 0, 0, 0, 10 ) b:SetHeight( 20 ) b:SetText( "Copy weapon from main menu" ) b.DoClick = function() local text = Menu.Main.WeaponEntry:GetValue() Menu.Replacements.LeftEntry:SetValue( text ) end local b = right:Add( "DButton" ) b:Dock( TOP ) b:DockMargin( 0, 0, 0, 10 ) b:SetHeight( 20 ) b:SetText( "Copy weapon from main menu" ) b.DoClick = function() local text = Menu.Main.WeaponEntry:GetValue() Menu.Replacements.RightEntry:SetValue( text ) end Menu.Replacements.LeftEntry = left:Add( "DTextEntry" ) Menu.Replacements.LeftEntry:Dock( TOP ) Menu.Replacements.LeftEntry:SetHeight( 20 ) Menu.Replacements.RightEntry = right:Add( "DTextEntry" ) Menu.Replacements.RightEntry:Dock( TOP ) Menu.Replacements.RightEntry:SetHeight( 20 ) local b = pnl:Add( "DButton" ) b:Dock( TOP ) b:DockMargin( 0, 0, 0, 20 ) b:SetHeight( 30 ) b:SetText( "Add weapon replacement" ) b.DoClick = function() local key = tostring( Menu.Replacements.LeftEntry:GetValue() ) if key == "" then return end local value = tostring( Menu.Replacements.RightEntry:GetValue() ) netmsg( 5 ) net.WriteString( key ) net.WriteString( value ) net.SendToServer() end Menu.Replacements.List = pnl:Add( "DListView" ) Menu.Replacements.List:Dock( FILL ) Menu.Replacements.List:SetMultiSelect( true ) Menu.Replacements.List:AddColumn( "Replace ..." ) Menu.Replacements.List:AddColumn( "with ..." ) function Menu.Replacements.List:DoDoubleClick( id, sel ) local key = tostring( sel:GetValue( 1 ) ) local value = tostring( sel:GetValue( 2 ) ) Menu.Replacements.LeftEntry:SetText( key ) Menu.Replacements.RightEntry:SetText( value ) end function Menu.Replacements.List:Populate( list ) self:Clear() for k, v in pairs( list ) do self:AddLine( k, v ) end self:SortByColumn( 1 ) end netmsg( 6 ) net.SendToServer() local b = pnl:Add( "DButton" ) b:Dock( BOTTOM ) b:DockMargin( 0, 10, 0, 0 ) b:SetHeight( 20 ) b:SetText( "Delete selected replacements" ) b.DoClick = function() local sel = Menu.Replacements.List:GetSelected() for k, v in pairs( sel ) do local weapon = tostring( v:GetValue(1) ) netmsg( 7 ) net.WriteString( weapon ) net.SendToServer() end end end function Menu.Main:Init() Menu.Main.Frame = vgui.Create( "DFrame" ) local Frame = Menu.Main.Frame local fw, fh = 320, ScrH() - 20 local pw, ph = fw - 10, fh - 34 Frame:SetPos( 10, 10 ) Frame:SetSize( fw, fh ) Frame:SetTitle( "Weapon Properties Editor "..Version ) Frame:SetVisible( true ) Frame:SetDraggable( true ) Frame:SetScreenLock( false ) Frame:ShowCloseButton( true ) Frame:MakePopup() Frame:SetKeyboardInputEnabled( false ) function Frame:Paint( w, h ) DrawBlur( self, 2 ) draw.RoundedBox( 10, 0, 0, w, h, Color( 0, 99, 177, 200 ) ) return true end function Frame.lblTitle:Paint( w, h ) draw.SimpleTextOutlined( Frame.lblTitle:GetText(), "DermaDefaultBold", 1, 2, Color( 255, 255, 255, 255 ), 0, 0, 1, Color( 0, 0, 0, 255 ) ) return true end local pnl = Frame:Add( "DPanel" ) pnl:Dock( FILL ) pnl:DockPadding( 10, 10, 10, 10 ) local b = pnl:Add( "DButton" ) b:Dock( TOP ) b:DockMargin( 0, 0, 0, 10 ) b:SetHeight( 30 ) b:SetText( "Edit Weapon" ) b.DoClick = function() local name = tostring( Menu.Main.WeaponEntry:GetValue() ) if name == "" then return end Menu.Editor:Init( name ) end Menu.Main.WeaponEntry = pnl:Add( "DTextEntry" ) Menu.Main.WeaponEntry:Dock( TOP ) Menu.Main.WeaponEntry:DockMargin( 0, 0, 0, 20 ) Menu.Main.WeaponEntry:SetHeight( 20 ) local b = pnl:Add( "DButton" ) b:Dock( TOP ) b:DockMargin( 0, 0, 0, 10 ) b:SetHeight( 20 ) b:SetText( "Get class of active weapon" ) b.DoClick = function() local weapon = LocalPlayer():GetActiveWeapon() if IsValid( weapon ) then Menu.Main.WeaponEntry:SetValue( weapon:GetClass() ) else Menu.Main.WeaponEntry:SetValue( "" ) end end Menu.Main.WeaponList = pnl:Add( "DListView" ) Menu.Main.WeaponList:Dock( FILL ) Menu.Main.WeaponList:SetMultiSelect( false ) Menu.Main.WeaponList:AddColumn( "Class" ) Menu.Main.WeaponList:AddColumn( "Name" ) function Menu.Main.WeaponList:DoDoubleClick( id, sel ) local weapon = tostring( sel:GetValue( 1 ) ) Menu.Main.WeaponEntry:SetValue( weapon ) end function Menu.Main.WeaponList:Populate() self:Clear() for k, v in pairs( weapons.GetList() ) do if v.Spawnable then self:AddLine( v.ClassName, v.PrintName ) end end self:SortByColumn( 1 ) end Menu.Main.WeaponList:Populate() local b = pnl:Add( "DButton" ) b:Dock( BOTTOM ) b:DockMargin( 0, 20, 0, 0 ) b:SetHeight( 30 ) b:SetText( "Weapon Replacements" ) b.DoClick = function() Menu.Replacements:Init() end local b = pnl:Add( "DButton" ) b:Dock( BOTTOM ) b:DockMargin( 0, 10, 0, 0 ) b:SetHeight( 20 ) b:SetText( "Manage Preset Files" ) b.DoClick = function() Menu.PresetList:Init() end end function Menu.Toggle() if LocalPlayer():IsSuperAdmin() then if IsValid( Menu.Main.Frame ) then Menu.Main.Frame:Close() else Menu.Main:Init() end else if IsValid( Menu.Main.Frame ) then Menu.Main.Frame:Close() end end end concommand.Add( "weapon_properties_editor", Menu.Toggle ) -- Spawn Menu entry. local function SpawnMenu_Entry( panel ) panel:AddControl("Label", {Text = "Administrator Settings:"}) local a = panel:AddControl("Button", {Label = "Open Menu", Command = "weapon_properties_editor"}) a:SetSize(0, 50) a:SetEnabled( LocalPlayer():IsSuperAdmin() ) end hook.Add( "PopulateToolMenu", "lf_weapon_properties_editor_spawnmenu", function() spawnmenu.AddToolMenuOption( "Options", "Player", "lf_weapon_properties_editor_spawnmenu_entry", "Weapon Editor", "", "", SpawnMenu_Entry, {} ) end ) end --[[ vON 1.3.4 Copyright 2012-2014 Alexandru-Mihai Maftei aka Vercas GitHub Repository: https://github.com/vercas/vON You may use this for any purpose as long as: - You don't remove this copyright notice. - You don't claim this to be your own. - You properly credit the author (Vercas) if you publish your work based on (and/or using) this. If you modify the code for any purpose, the above obligations still apply. If you make any interesting modifications, try forking the GitHub repository instead. Instead of copying this code over for sharing, rather use the link: https://github.com/vercas/vON/blob/master/von.lua The author may not be held responsible for any damage or losses directly or indirectly caused by the use of vON. If you disagree with the above, don't use the code. ----------------------------------------------------------------------------------------------------------------------------- Thanks to the following people for their contribution: - Divran Suggested improvements for making the code quicker. Suggested an excellent new way of deserializing strings. Lead me to finding an extreme flaw in string parsing. - pennerlord Provided some performance tests to help me improve the code. - Chessnut Reported bug with handling of nil values when deserializing array components. - People who contributed on the GitHub repository by reporting bugs, posting fixes, etc. ----------------------------------------------------------------------------------------------------------------------------- The vanilla types supported in this release of vON are: - table - number - boolean - string - nil The Garry's Mod-specific types supported in this release are: - Vector - Angle + Entities: - Entity - Vehicle - Weapon - NPC - Player - NextBot These are the types one would normally serialize. ----------------------------------------------------------------------------------------------------------------------------- New in this version: - Fixed addition of extra entity types. I messed up really badly. --]] local von, _deserialize, _serialize, _d_meta, _s_meta, d_findVariable, s_anyVariable local sub, gsub, find, insert, concat, error, tonumber, tostring, type, next = string.sub, string.gsub, string.find, table.insert, table.concat, error, tonumber, tostring, type, next --[[ This section contains localized functions which (de)serialize variables according to the types found. ]] -- This is kept away from the table for speed. function d_findVariable(s, i, len, lastType, jobstate) local i, c, typeRead, val = i or 1 -- Keep looping through the string. while true do -- Stop at the end. Throw an error. This function MUST NOT meet the end! if i > len then error("vON: Reached end of string, cannot form proper variable.") end -- Cache the character. Nobody wants to look for the same character ten times. c = sub(s, i, i) -- If it just read a type definition, then a variable HAS to come after it. if typeRead then -- Attempt to deserialize a variable of the freshly read type. val, i = _deserialize[lastType](s, i, len, false, jobstate) -- Return the value read, the index of the last processed character, and the type of the last read variable. return val, i, lastType -- @ means nil. It should not even appear in the output string of the serializer. Nils are useless to store. elseif c == "@" then return nil, i, lastType -- $ means a table reference will follow - a number basically. elseif c == "$" then lastType = "table_reference" typeRead = true -- n means a number will follow. Base 10... :C elseif c == "n" then lastType = "number" typeRead = true -- b means boolean flags. elseif c == "b" then lastType = "boolean" typeRead = true -- ' means the start of a string. elseif c == "'" then lastType = "string" typeRead = true -- " means the start of a string prior to version 1.2.0. elseif c == "\"" then lastType = "oldstring" typeRead = true -- { means the start of a table! elseif c == "{" then lastType = "table" typeRead = true --[[ Garry's Mod types go here ]] -- e means an entity ID will follow. elseif c == "e" then lastType = "Entity" typeRead = true --[[ -- c means a vehicle ID will follow. elseif c == "c" then lastType = "Vehicle" typeRead = true -- w means a weapon entity ID will follow. elseif c == "w" then lastType = "Weapon" typeRead = true -- x means a NPC ID will follow. elseif c == "x" then lastType = "NPC" typeRead = true --]] -- p means a player ID will follow. -- Kept for backwards compatibility. elseif c == "p" then lastType = "Entity" typeRead = true -- v means a vector will follow. 3 numbers. elseif c == "v" then lastType = "Vector" typeRead = true -- a means an Euler angle will follow. 3 numbers. elseif c == "a" then lastType = "Angle" typeRead = true --[[ Garry's Mod types end here ]] -- If no type has been found, attempt to deserialize the last type read. elseif lastType then val, i = _deserialize[lastType](s, i, len, false, jobstate) return val, i, lastType -- This will occur if the very first character in the vON code is wrong. else error("vON: Malformed data... Can't find a proper type definition. Char#" .. i .. ":" .. c) end -- Move the pointer one step forward. i = i + 1 end end -- This is kept away from the table for speed. -- Yeah, ton of parameters. function s_anyVariable(data, lastType, isNumeric, isKey, isLast, jobstate) local tp = type(data) if jobstate[1] and jobstate[2][data] then tp = "table_reference" end -- Basically, if the type changes. if lastType ~= tp then -- Remember the new type. Caching the type is useless. lastType = tp if _serialize[lastType] then -- Return the serialized data and the (new) last type. -- The second argument, which is true now, means that the data type was just changed. return _serialize[lastType](data, true, isNumeric, isKey, isLast, false, jobstate), lastType else error("vON: No serializer defined for type \"" .. lastType .. "\"!") end end -- Otherwise, simply serialize the data. return _serialize[lastType](data, false, isNumeric, isKey, isLast, false, jobstate), lastType end --[[ This section contains the tables with the functions necessary for decoding basic Lua data types. ]] _deserialize = { -- Well, tables are very loose... -- The first table doesn't have to begin and end with { and }. ["table"] = function(s, i, len, unnecessaryEnd, jobstate) local ret, numeric, i, c, lastType, val, ind, expectValue, key = {}, true, i or 1, nil, nil, nil, 1 -- Locals, locals, locals, locals, locals, locals, locals, locals and locals. if sub(s, i, i) == "#" then local e = find(s, "#", i + 2, true) if e then local id = tonumber(sub(s, i + 1, e - 1)) if id then if jobstate[1][id] and not jobstate[2] then error("vON: There already is a table of reference #" .. id .. "! Missing an option maybe?") end jobstate[1][id] = ret i = e + 1 else error("vON: Malformed table! Reference ID starting at char #" .. i .. " doesn't contain a number!") end else error("vON: Malformed table! Cannot find end of reference ID start at char #" .. i .. "!") end end -- Keep looping. while true do -- Until it meets the end. if i > len then -- Yeah, if the end is unnecessary, it won't spit an error. The main chunk doesn't require an end, for example. if unnecessaryEnd then return ret, i -- Otherwise, the data has to be damaged. else error("vON: Reached end of string, incomplete table definition.") end end -- Cache the character. c = sub(s, i, i) --print(i, "table char:", c, tostring(unnecessaryEnd)) -- If it's the end of a table definition, return. if c == "}" then return ret, i -- If it's the component separator, switch to key:value pairs. elseif c == "~" then numeric = false elseif c == ";" then -- Lol, nothing! -- Remenant from numbers, for faster parsing. -- OK, now, if it's on the numeric component, simply add everything encountered. elseif numeric then -- Find a variable and it's value val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) -- Add it to the table. ret[ind] = val ind = ind + 1 -- Otherwise, if it's the key:value component... else -- If a value is expected... if expectValue then -- Read it. val, i, lastType = d_findVariable(s, i, len, lastType, jobstate) -- Add it? ret[key] = val -- Clean up. expectValue, key = false, nil -- If it's the separator... elseif c == ":" then -- Expect a value next. expectValue = true -- But, if there's a key read already... elseif key then -- Then this is malformed. error("vON: Malformed table... Two keys declared successively? Char#" .. i .. ":" .. c) -- Otherwise the key will be read. else -- I love multi-return and multi-assignement. key, i, lastType = d_findVariable(s, i, len, lastType, jobstate) end end i = i + 1 end return nil, i end, -- Just a number which points to a table. ["table_reference"] = function(s, i, len, unnecessaryEnd, jobstate) local i, a = i or 1 -- Locals, locals, locals, locals a = find(s, "[;:}~]", i) if a then local n = tonumber(sub(s, i, a - 1)) if n then return jobstate[1][n] or error("vON: Table reference does not point to a (yet) known table!"), a - 1 else error("vON: Table reference definition does not contain a valid number!") end end -- Using %D breaks identification of negative numbers. :( error("vON: Number definition started... Found no end.") end, -- Numbers are weakly defined. -- The declaration is not very explicit. It'll do it's best to parse the number. -- Has various endings: \n, }, ~, : and ;, some of which will force the table deserializer to go one char backwards. ["number"] = function(s, i, len, unnecessaryEnd, jobstate) local i, a = i or 1 -- Locals, locals, locals, locals a = find(s, "[;:}~]", i) if a then return tonumber(sub(s, i, a - 1)) or error("vON: Number definition does not contain a valid number!"), a - 1 end -- Using %D breaks identification of negative numbers. :( error("vON: Number definition started... Found no end.") end, -- A boolean is A SINGLE CHARACTER, either 1 for true or 0 for false. -- Any other attempt at boolean declaration will result in a failure. ["boolean"] = function(s, i, len, unnecessaryEnd, jobstate) local c = sub(s,i,i) -- Only one character is needed. -- If it's 1, then it's true if c == "1" then return true, i -- If it's 0, then it's false. elseif c == "0" then return false, i end -- Any other supposely "boolean" is just a sign of malformed data. error("vON: Invalid value on boolean type... Char#" .. i .. ": " .. c) end, -- Strings prior to 1.2.0 ["oldstring"] = function(s, i, len, unnecessaryEnd, jobstate) local res, i, a = "", i or 1 -- Locals, locals, locals, locals while true do a = find(s, "\"", i, true) if a then if sub(s, a - 1, a - 1) == "\\" then res = res .. sub(s, i, a - 2) .. "\"" i = a + 1 else return res .. sub(s, i, a - 2), a end else error("vON: Old string definition started... Found no end.") end end end, -- Strings after 1.2.0 ["string"] = function(s, i, len, unnecessaryEnd, jobstate) local res, i, a = "", i or 1 -- Locals, locals, locals, locals while true do a = find(s, "\"", i, true) if a then if sub(s, a - 1, a - 1) == "\\" then res = res .. sub(s, i, a - 2) .. "\"" i = a + 1 else return res .. sub(s, i, a - 1), a end else error("vON: String definition started... Found no end.") end end end, } _serialize = { -- Uh. Nothing to comment. -- Ton of parameters. -- Makes stuff faster than simply passing it around in locals. -- table.concat works better than normal concatenations WITH LARGE-ISH STRINGS ONLY. ["table"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) --print(string.format("data: %s; mustInitiate: %s; isKey: %s; isLast: %s; nice: %s; indent: %s; first: %s", tostring(data), tostring(mustInitiate), tostring(isKey), tostring(isLast), tostring(nice), tostring(indent), tostring(first))) local result, keyvals, len, keyvalsLen, keyvalsProgress, val, lastType, newIndent, indentString = {}, {}, #data, 0, 0 -- Locals, locals, locals, locals, locals, locals, locals, locals, locals and locals. -- First thing to be done is separate the numeric and key:value components of the given table in two tables. -- pairs(data) is slower than next, data as far as my tests tell me. for k, v in next, data do -- Skip the numeric keyz. if type(k) ~= "number" or k < 1 or k > len or (k % 1 ~= 0) then -- k % 1 == 0 is, as proven by personal benchmarks, keyvals[#keyvals + 1] = k -- the quickest way to check if a number is an integer. end -- k % 1 ~= 0 is the fastest way to check if a number end -- is NOT an integer. > is proven slower. keyvalsLen = #keyvals -- Main chunk - no initial character. if not first then result[#result + 1] = "{" end if jobstate[1] and jobstate[1][data] then if jobstate[2][data] then error("vON: Table #" .. jobstate[1][data] .. " written twice..?") end result[#result + 1] = "#" result[#result + 1] = jobstate[1][data] result[#result + 1] = "#" jobstate[2][data] = true end -- Add numeric values. if len > 0 then for i = 1, len do val, lastType = s_anyVariable(data[i], lastType, true, false, i == len and not first, jobstate) result[#result + 1] = val end end -- If there are key:value pairs. if keyvalsLen > 0 then -- Insert delimiter. result[#result + 1] = "~" -- Insert key:value pairs. for _i = 1, keyvalsLen do keyvalsProgress = keyvalsProgress + 1 val, lastType = s_anyVariable(keyvals[_i], lastType, false, true, false, jobstate) result[#result + 1] = val..":" val, lastType = s_anyVariable(data[keyvals[_i]], lastType, false, false, keyvalsProgress == keyvalsLen and not first, jobstate) result[#result + 1] = val end end -- Main chunk needs no ending character. if not first then result[#result + 1] = "}" end return concat(result) end, -- Number which points to table. ["table_reference"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) data = jobstate[1][data] -- If a number hasn't been written before, add the type prefix. if mustInitiate then if isKey or isLast then return "$"..data else return "$"..data..";" end end if isKey or isLast then return data else return data..";" end end, -- Normal concatenations is a lot faster with small strings than table.concat -- Also, not so branched-ish. ["number"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) -- If a number hasn't been written before, add the type prefix. if mustInitiate then if isKey or isLast then return "n"..data else return "n"..data..";" end end if isKey or isLast then return data else return data..";" end end, -- I hope gsub is fast enough. ["string"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) if sub(data, #data, #data) == "\\" then -- Hah, old strings fix this best. return "\"" .. gsub(data, "\"", "\\\"") .. "v\"" end return "'" .. gsub(data, "\"", "\\\"") .. "\"" end, -- Fastest. ["boolean"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) -- Prefix if we must. if mustInitiate then if data then return "b1" else return "b0" end end if data then return "1" else return "0" end end, -- Fastest. ["nil"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) return "@" end, } --[[ This section handles additions necessary for Garry's Mod. ]] if gmod then -- Luckily, a specific table named after the game is present in Garry's Mod. local Entity = Entity local extra_deserialize = { -- Entities are stored simply by the ID. They're meant to be transfered, not stored anyway. -- Exactly like a number definition, except it begins with "e". ["Entity"] = function(s, i, len, unnecessaryEnd, jobstate) local i, a = i or 1 -- Locals, locals, locals, locals a = find(s, "[;:}~]", i) if a then return Entity(tonumber(sub(s, i, a - 1))), a - 1 end error("vON: Entity ID definition started... Found no end.") end, -- A pair of 3 numbers separated by a comma (,). ["Vector"] = function(s, i, len, unnecessaryEnd, jobstate) local i, a, x, y, z = i or 1 -- Locals, locals, locals, locals a = find(s, ",", i) if a then x = tonumber(sub(s, i, a - 1)) i = a + 1 end a = find(s, ",", i) if a then y = tonumber(sub(s, i, a - 1)) i = a + 1 end a = find(s, "[;:}~]", i) if a then z = tonumber(sub(s, i, a - 1)) end if x and y and z then return Vector(x, y, z), a - 1 end error("vON: Vector definition started... Found no end.") end, -- A pair of 3 numbers separated by a comma (,). ["Angle"] = function(s, i, len, unnecessaryEnd, jobstate) local i, a, p, y, r = i or 1 -- Locals, locals, locals, locals a = find(s, ",", i) if a then p = tonumber(sub(s, i, a - 1)) i = a + 1 end a = find(s, ",", i) if a then y = tonumber(sub(s, i, a - 1)) i = a + 1 end a = find(s, "[;:}~]", i) if a then r = tonumber(sub(s, i, a - 1)) end if p and y and r then return Angle(p, y, r), a - 1 end error("vON: Angle definition started... Found no end.") end, } local extra_serialize = { -- Same as numbers, except they start with "e" instead of "n". ["Entity"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) data = data:EntIndex() if mustInitiate then if isKey or isLast then return "e"..data else return "e"..data..";" end end if isKey or isLast then return data else return data..";" end end, -- 3 numbers separated by a comma. ["Vector"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) if mustInitiate then if isKey or isLast then return "v"..data.x..","..data.y..","..data.z else return "v"..data.x..","..data.y..","..data.z..";" end end if isKey or isLast then return data.x..","..data.y..","..data.z else return data.x..","..data.y..","..data.z..";" end end, -- 3 numbers separated by a comma. ["Angle"] = function(data, mustInitiate, isNumeric, isKey, isLast, first, jobstate) if mustInitiate then if isKey or isLast then return "a"..data.p..","..data.y..","..data.r else return "a"..data.p..","..data.y..","..data.r..";" end end if isKey or isLast then return data.p..","..data.y..","..data.r else return data.p..","..data.y..","..data.r..";" end end, } for k, v in pairs(extra_serialize) do _serialize[k] = v end for k, v in pairs(extra_deserialize) do _deserialize[k] = v end local extraEntityTypes = { "Vehicle", "Weapon", "NPC", "Player", "NextBot" } for i = 1, #extraEntityTypes do _serialize[extraEntityTypes[i]] = _serialize.Entity end end --[[ This section exposes the functions of the library. ]] local function checkTableForRecursion(tab, checked, assoc) local id = checked.ID if not checked[tab] and not assoc[tab] then assoc[tab] = id checked.ID = id + 1 else checked[tab] = true end for k, v in pairs(tab) do if type(k) == "table" and not checked[k] then checkTableForRecursion(k, checked, assoc) end if type(v) == "table" and not checked[v] then checkTableForRecursion(v, checked, assoc) end end end local _s_table = _serialize.table local _d_table = _deserialize.table _d_meta = { __call = function(self, str, allowIdRewriting) if type(str) == "string" then return _d_table(str, nil, #str, true, {{}, allowIdRewriting}) end error("vON: You must deserialize a string, not a "..type(str)) end } _s_meta = { __call = function(self, data, checkRecursion) if type(data) == "table" then if checkRecursion then local assoc, checked = {}, {ID = 1} checkTableForRecursion(data, checked, assoc) return _s_table(data, nil, nil, nil, nil, true, {assoc, {}}) end return _s_table(data, nil, nil, nil, nil, true, {false}) end error("vON: You must serialize a table, not a "..type(data)) end } von = { version = "1.3.4", versionNumber = 1003004, -- Reserving 3 digits per version component. deserialize = setmetatable(_deserialize,_d_meta), serialize = setmetatable(_serialize,_s_meta) } return vonx`ÞÙ