local z,a,h,n,P,f,M,B,s,U=table,ipairs,string,next,pcall,game,workspace,tostring,tonumber,getmetatable local S=h.format local F=h.char local k=h.gsub local r=h.match local i=h.rep local c=h.sub local u=h.gmatch local y=z.find local j=z.concat local O=z.insert local T=typeof or type local b do local z={"\097\110\100";"\098\114\101\097\107";"\100\111";"\101\108\115\101";"\101\108\115\101\105\102";"\101\110\100";"\102\097\108\115\101";"\102\111\114","\102\117\110\099\116\105\111\110";"\105\102";"\105\110";"\108\111\099\097\108";"\110\105\108","\110\111\116","\111\114","\114\101\112\101\097\116";"\114\101\116\117\114\110";"\116\104\101\110";"\116\114\117\101","\117\110\116\105\108";"\119\104\105\108\101";"\099\111\110\116\105\110\117\101"}b=setmetatable({},{__mode="\107"})for z,a in n,z do b[a]=true end end y=y or function(z,h)for z,a in a(z)do if a==h then return z end end return end local function w(z,a,...)local h={...}local n=T(z)if not y(h,n)then error(S("\076\117\097\069\110\099\111\100\101\058 \073\110\099\111\114\114\101\099\116 \116\121\112\101 \102\111\114 \096\037\115\096\058 \096\037\115\096 \101\120\112\101\099\116\101\100\044 \103\111\116 \096\037\115\096",a,j(h,"\044 "),n),0)end return z end local Z do local z={["\""]="\092\"";["\092"]="\092\092";["\a"]="\092\097";["\b"]="\092\098",["\t"]="\092\116";["\n"]="\092\110";["\v"]="\092\118";["\012"]="\092\102",["\r"]="\092\114"}for a=0,255,1 do local h=F(a)if not z[h]and(a<32 or a>126)then z[h]="\092"..a end end function Z(a)return"\""..(k(a,"\091\037\122\092\"\001-\031\127-\255\093",z).."\"")end end local function e(z)local a=""for z in u(z,"\037\093\040\061\042\041\037\093")do if#z>=#a then a=z.."\061"end end return"--\091"..(a..("\091"..(z..("\093"..(a.."\093")))))end local q do local function z(z)local a,h=P(f.FindService,f,z.ClassName)if a and h then return true end return false end function q(a)local h=a if not h then return end local n=""while h do local a=h.Name local P=h.ClassName local B=h.Parent if B==f and z(h)then n="\058\071\101\116\083\101\114\118\105\099\101\040"..(Z(P)..("\041"..n))elseif not b[a]and r(a,"\094\091\065-\090\097-\122\095\093\091\065-\090\097-\122\048-\057\095\093\042\036")then n="\046"..(a..n)else n="\091"..(Z(a)..("\093"..n))end if B==f then n="\103\097\109\101"..n return n elseif B==M then n="\119\111\114\107\115\112\097\099\101"..n return n end h=B end return end end local function Q(z,a)w(z,"\105\110\112\117\116\084\097\098\108\101","\116\097\098\108\101")w(a,"\111\112\116\105\111\110\115","\116\097\098\108\101","\110\105\108")if a then w(a.Prettify,"\111\112\116\105\111\110\115\046\080\114\101\116\116\105\102\121","\098\111\111\108\101\097\110","\110\105\108")w(a.PrettyPrinting,"\111\112\116\105\111\110\115\046\080\114\101\116\116\121\080\114\105\110\116\105\110\103","\098\111\111\108\101\097\110","\110\105\108")w(a.IndentCount,"\111\112\116\105\111\110\115\046\073\110\100\101\110\116\067\111\117\110\116","\110\117\109\098\101\114","\110\105\108")w(a.OutputWarnings,"\111\112\116\105\111\110\115\046\079\117\116\112\117\116\087\097\114\110\105\110\103\115","\098\111\111\108\101\097\110","\110\105\108")w(a.StackLimit,"\111\112\116\105\111\110\115\046\083\116\097\099\107\076\105\109\105\116","\110\117\109\098\101\114","\110\105\108")w(a.FunctionsReturnRaw,"\111\112\116\105\111\110\115\046\070\117\110\099\116\105\111\110\115\082\101\116\117\114\110\082\097\119","\098\111\111\108\101\097\110","\110\105\108")w(a.UseInstancePaths,"\111\112\116\105\111\110\115\046\085\115\101\073\110\115\116\097\110\099\101\080\097\116\104\115","\098\111\111\108\101\097\110","\110\105\108")w(a.SerializeMathHuge,"\111\112\116\105\111\110\115\046\083\101\114\105\097\108\105\122\101\077\097\116\104\072\117\103\101","\098\111\111\108\101\097\110","\110\105\108")w(a._StackLevel,"\111\112\116\105\111\110\115\046\095\083\116\097\099\107\076\101\118\101\108","\110\117\109\098\101\114","\110\105\108")w(a._VisitedTables,"\111\112\116\105\111\110\115\046\095\083\116\097\099\107\076\101\118\101\108","\116\097\098\108\101","\110\105\108")end a=a or{}local h=a.Prettify==nil and(a.PrettyPrinting==nil and false)or a.Prettify~=nil and a.Prettify or a.PrettyPrinting and a.PrettyPrinting local f=a.IndentCount or h and 4 or 0 local M=a.OutputWarnings==nil and true or a.OutputWarnings local F=a.StackLimit or 500 local k=a.FunctionsReturnRaw==nil and false or a.FunctionsReturnRaw local u=a.UseInstancePaths==nil and true or a.UseInstancePaths local y=a.SerializeMathHuge==nil and true or a.SerializeMathHuge local L=a._StackLevel or 1 local p=a._VisitedTables or{[z]=true}if L>=F then return"\123--\091\091\076\117\097\069\110\099\111\100\101\058 \083\116\097\099\107 \108\101\118\101\108 \108\105\109\105\116 \111\102 \096"..(F.."\096 \114\101\097\099\104\101\100\093\093\125")end local G=y and"\109\097\116\104\046\104\117\103\101"or"\049\047\048"local I=y and"-\109\097\116\104\046\104\117\103\101"or"-\049\047\048"local d=h and"\n"or""local A=h and"\044 "or"\044"local K=h and" "or""local g=i(" ",f)g=h and i(g,L)or g local E=#g>0 and c(g,1,-f-1)or""local x=1 local H={}do local function P(z,a)local h=H[z](a,false)return h end local function M(...)local z={}for a,h in n,{...}do O(z,P(T(h),h))end return j(z,A)end local function F(z,a)return"\040\102\117\110\099\116\105\111\110\040\118\044 \112\041 \102\111\114 \112\110\044 \112\118 \105\110 \110\101\120\116\044 \112 \100\111 \118\091\112\110\093 \061 \112\118 \101\110\100 \114\101\116\117\114\110 \118 \101\110\100\041\040"..(j({z,P("\116\097\098\108\101",a)},A).."\041")end H.number=function(z,a)if a and z==x then x=x+1 return nil,true end if z==1/0 then return G elseif z==-1/0 then return I end return S("\037\046\049\052\103",z)end H.string=function(z,a)if a and(not b[z]and r(z,"\094\091\065-\090\097-\122\095\093\091\065-\090\097-\122\048-\057\095\093\042\036"))then return z,true end return Z(z)end H.table=function(n,P)do local a=p[n]if a then return S("\123--\091\091\076\117\097\069\110\099\111\100\101\058 \068\117\112\108\105\099\097\116\101 \114\101\102\101\114\101\110\099\101\037\115\093\093\125",n==z and" \040\111\102 \112\097\114\101\110\116\041"or"")end p[n]=true end local M=setmetatable({},{__index=a})do M.Prettify=P and false or h M.IndentCount=P and(not h and f or 1)or f M._StackLevel=P and 1 or L+1 M._VisitedTables=p end return Q(n,M)end H.boolean=function(z)return z and"\116\114\117\101"or"\102\097\108\115\101"end H["\110\105\108"]=function(z)return"\110\105\108"end H["\102\117\110\099\116\105\111\110"]=function(z)if k then return z()end return"\102\117\110\099\116\105\111\110\040\041 --\091\091\076\117\097\069\110\099\111\100\101\058 \096\111\112\116\105\111\110\115\046\070\117\110\099\116\105\111\110\115\082\101\116\117\114\110\082\097\119\096 \102\097\108\115\101\059 \099\097\110\'\116 \115\101\114\105\097\108\105\122\101 \102\117\110\099\116\105\111\110\115\093\093 \114\101\116\117\114\110 \101\110\100"end H.Axes=function(z)local a={}local h={["\069\110\117\109\046\065\120\105\115\046\088"]=z.X,["\069\110\117\109\046\065\120\105\115\046\089"]=z.Y;["\069\110\117\109\046\065\120\105\115\046\090"]=z.Z}for z,h in n,h do if h then O(a,z)end end return"\065\120\101\115\046\110\101\119\040"..(j(a,A).."\041")end H.BrickColor=function(z)return"\066\114\105\099\107\067\111\108\111\114\046\110\101\119\040"..(h and P("\115\116\114\105\110\103",z.Name))or z.Number.."\041"end H.CFrame=function(z)return"\067\070\114\097\109\101\046\110\101\119\040"..(M(z:components()).."\041")end H.CatalogSearchParams=function(z)return F("\067\097\116\097\108\111\103\083\101\097\114\099\104\080\097\114\097\109\115\046\110\101\119\040\041",{SearchKeyword=z.SearchKeyword,MinPrice=z.MinPrice,MaxPrice=z.MaxPrice;SortType=z.SortType,CategoryFilter=z.CategoryFilter;BundleTypes=z.BundleTypes;AssetTypes=z.AssetTypes})end H.Color3=function(z)return"\067\111\108\111\114\051\046\110\101\119\040"..M(z.R,z.G,z.B)end H.ColorSequence=function(z)return"\067\111\108\111\114\083\101\113\117\101\110\099\101\046\110\101\119\040"..(P("\116\097\098\108\101",z.Keypoints).."\041")end H.ColorSequenceKeypoint=function(z)return"\067\111\108\111\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116\046\110\101\119\040"..(M(z.Time,z.Value).."\041")end H.DateTime=function(z)return"\068\097\116\101\084\105\109\101\046\102\114\111\109\085\110\105\120\084\105\109\101\115\116\097\109\112\040"..(z.UnixTimestamp.."\041")end H.DockWidgetPluginGuiInfo=function(z)local a=B(z)return"\068\111\099\107\087\105\100\103\101\116\080\108\117\103\105\110\071\117\105\073\110\102\111\046\110\101\119\040"..(M(Enum.InitialDockState[r(a,"\073\110\105\116\105\097\108\068\111\099\107\083\116\097\116\101\058\040\037\119\043\041")],r(a,"\073\110\105\116\105\097\108\069\110\097\098\108\101\100\058\040\037\119\043\041")=="\049",r(a,"\073\110\105\116\105\097\108\069\110\097\098\108\101\100\083\104\111\117\108\100\079\118\101\114\114\105\100\101\082\101\115\116\111\114\101\058\040\037\119\043\041")=="\049",s(r(a,"\070\108\111\097\116\105\110\103\088\083\105\122\101\058\040\037\119\043\041")),s(r(a,"\070\108\111\097\116\105\110\103\089\083\105\122\101\058\040\037\119\043\041")),s(r(a,"\077\105\110\087\105\100\116\104\058\040\037\119\043\041")),s(r(a,"\077\105\110\072\101\105\103\104\116\058\040\037\119\043\041"))).."\041")end H.Enum=function(z)return"\069\110\117\109\046"..B(z)end H.EnumItem=function(z)return B(z)end H.Enums=function(z)return"\069\110\117\109"end H.Faces=function(z)local a={}local h={["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\084\111\112"]=z.Top;["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\066\111\116\116\111\109"]=z.Bottom;["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\076\101\102\116"]=z.Left;["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\082\105\103\104\116"]=z.Right,["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\066\097\099\107"]=z.Back,["\069\110\117\109\046\078\111\114\109\097\108\073\100\046\070\114\111\110\116"]=z.Front}for z,h in n,h do if h then O(a,z)end end return"\070\097\099\101\115\046\110\101\119\040"..(j(a,A).."\041")end H.FloatCurveKey=function(z)return"\070\108\111\097\116\067\117\114\118\101\075\101\121\046\110\101\119\040"..(M(z.Time,z.Value,z.Interpolation).."\041")end H.Font=function(z)return"\070\111\110\116\046\110\101\119\040"..(M(z.Family,z.Weight,z.Style).."\041")end H.Instance=function(z)if u then local a=q(z)if a then return a end end return"\110\105\108"..(K..e("\073\110\115\116\097\110\099\101\046\110\101\119\040"..(P("\115\116\114\105\110\103",z.ClassName).."\041")))end H.NumberRange=function(z)return"\078\117\109\098\101\114\082\097\110\103\101\046\110\101\119\040"..(M(z.Min,z.Max).."\041")end H.NumberSequence=function(z)return"\078\117\109\098\101\114\083\101\113\117\101\110\099\101\046\110\101\119\040"..(P("\116\097\098\108\101",z.Keypoints).."\041")end H.NumberSequenceKeypoint=function(z)return"\078\117\109\098\101\114\083\101\113\117\101\110\099\101\075\101\121\112\111\105\110\116\046\110\101\119\040"..(M(z.Time,z.Value,z.Envelope).."\041")end H.OverlapParams=function(z)return F("\079\118\101\114\108\097\112\080\097\114\097\109\115\046\110\101\119\040\041",{FilterDescendantsInstances=z.FilterDescendantsInstances;FilterType=z.FilterType,MaxParts=z.MaxParts;CollisionGroup=z.CollisionGroup,RespectCanCollide=z.RespectCanCollide})end H.PathWaypoint=function(z)return"\080\097\116\104\087\097\121\112\111\105\110\116\046\110\101\119\040"..(M(z.Position,z.Action,z.Label).."\041")end H.PhysicalProperties=function(z)return"\080\104\121\115\105\099\097\108\080\114\111\112\101\114\116\105\101\115\046\110\101\119\040"..(M(z.Density,z.Friction,z.Elasticity,z.FrictionWeight,z.ElasticityWeight).."\041")end H.Random=function()return"\082\097\110\100\111\109\046\110\101\119\040\041"end H.Ray=function(z)return"\082\097\121\046\110\101\119\040"..(M(z.Origin,z.Direction).."\041")end H.RaycastParams=function(z)return F("\082\097\121\099\097\115\116\080\097\114\097\109\115\046\110\101\119\040\041",{FilterDescendantsInstances=z.FilterDescendantsInstances,FilterType=z.FilterType,IgnoreWater=z.IgnoreWater;CollisionGroup=z.CollisionGroup,RespectCanCollide=z.RespectCanCollide})end H.Rect=function(z)return"\082\101\099\116\046\110\101\119\040"..(M(z.Min,z.Max).."\041")end H.Region3=function(z)local a=z.CFrame local h=z.Size return"\082\101\103\105\111\110\051\046\110\101\119\040"..(M(a*CFrame.new(-h/2),a*CFrame.new(h/2)).."\041")end H.Region3int16=function(z)return"\082\101\103\105\111\110\051\105\110\116\049\054\046\110\101\119\040"..(M(z.Min,z.Max).."\041")end H.TweenInfo=function(z)return"\084\119\101\101\110\073\110\102\111\046\110\101\119\040"..(M(z.Time,z.EasingStyle,z.EasingDirection,z.RepeatCount,z.Reverses,z.DelayTime).."\041")end H.RotationCurveKey=function(z)return"\082\111\116\097\116\105\111\110\067\117\114\118\101\075\101\121\046\110\101\119\040"..(M(z.Time,z.Value,z.Interpolation).."\041")end H.UDim=function(z)return"\085\068\105\109\046\110\101\119\040"..(M(z.Scale,z.Offset).."\041")end H.UDim2=function(z)return"\085\068\105\109\050\046\110\101\119\040"..(M(z.X.Scale,z.X.Offset,z.Y.Scale,z.Y.Offset).."\041")end H.Vector2=function(z)return"\086\101\099\116\111\114\050\046\110\101\119\040"..(M(z.X,z.Y).."\041")end H.Vector2int16=function(z)return"\086\101\099\116\111\114\050\105\110\116\049\054\046\110\101\119\040"..(M(z.X,z.Y).."\041")end H.Vector3=function(z)return"\086\101\099\116\111\114\051\046\110\101\119\040"..(M(z.X,z.Y,z.Z).."\041")end H.Vector3int16=function(z)return"\086\101\099\116\111\114\051\105\110\116\049\054\046\110\101\119\040"..(M(z.X,z.Y,z.Z).."\041")end H.userdata=function(z)if U(z)then return"\110\101\119\112\114\111\120\121\040\116\114\117\101\041"else return"\110\101\119\112\114\111\120\121\040\041"end end end local m=""for a,f in n,z do local B=T(a)local s=T(f)if H[B]and H[s]then local U=h and d..g or""local F=false local k,r,i=P(H[B],a,true)local c,u=P(H[s],f,false)if k and(c and u)then local z=r and(i and r or S("\091\037\115\093",r))..(h and" \061 "or"\061")or""U=U..(z..u)F=true elseif M then local z=S("\076\117\097\069\110\099\111\100\101\058 \070\097\105\108\101\100 \116\111 \101\110\099\111\100\101 \037\115 \111\102 \068\097\116\097\084\121\112\101 \096\037\115\096\058 \037\115",not k and"\107\101\121"or not c and"\118\097\108\117\101"or"\107\101\121\047\118\097\108\117\101",s,not k and Z(r)or not c and Z(u)or"\040\070\097\105\108\101\100 \116\111 \103\101\116 \101\114\114\111\114 \109\101\115\115\097\103\101\041")U=U..e(z)end if n(z,a)then if F then U=U.."\044"end else U=U..(d..E)end m=m..U end end m="\123"..(m.."\125")return m end return Q
-- LuaEncode - Optimal Table Serialization for Native Luau/Lua 5.1+
-- Copyright (c) 2022-2023 Reggie <reggie@latte.to> | MIT License
-- https://github.com/regginator/LuaEncode

--!nocheck
--!optimize 2

-- Localizing certain libraries/variables used throughout for runtime efficiency (not specific to Luau)
local table, ipairs, string, next, pcall, game, workspace, tostring, tonumber, getmetatable = table, ipairs, string, next, pcall, game, workspace, tostring, tonumber, getmetatable

local string_format = string.format
local string_char = string.char
local string_gsub = string.gsub
local string_match = string.match
local string_rep = string.rep
local string_sub = string.sub
local string_gmatch = string.gmatch

local table_find = table.find
local table_concat = table.concat
local table_insert = table.insert

-- For custom Roblox engine data-type support via `typeof`, if it exists
local Type = typeof or type

-- Used for checking direct getfield syntax; Lua keywords can't be used as keys without being a str
-- FYI; `continue` is Luau only (in Lua it's actually a global function), but we're including it
-- here anyway to be safe
local LuaKeywords do
    local LuaKeywordsArray = {
        "and", "break", "do", "else",
        "elseif", "end", "false", "for",
        "function", "if", "in", "local",
        "nil", "not", "or", "repeat",
        "return", "then", "true", "until",
        "while", "continue"
    }

    -- We're now setting each keyword str to a weak key, so it's faster at runtime for `SerializeString()`
    LuaKeywords = setmetatable({}, {__mode = "k"})

    for _, Keyword in next, LuaKeywordsArray do
        LuaKeywords[Keyword] = true
    end
end

-- Lua 5.1 doesn't have table.find
table_find = table_find or function(inputTable, valueToFind) -- Ignoring the `init` arg, unneeded for us
    for Key, Value in ipairs(inputTable) do
        if Value == valueToFind then
            return Key -- Return the key idx
        end
    end

    return
end

-- Simple function for directly checking the type on values, with their input, variable name,
-- and desired type name(s) to check
local function CheckType(inputData, dataName, ...)
    local DesiredTypes = {...}
    local InputDataType = Type(inputData)

    if not table_find(DesiredTypes, InputDataType) then
        error(string_format(
            "LuaEncode: Incorrect type for `%s`: `%s` expected, got `%s`",
            dataName,
            table_concat(DesiredTypes, ", "), -- For if multiple types are accepted
            InputDataType
        ), 0)
    end

    return inputData -- Return back input directly
end

-- This re-serializes a string back into Lua, for the interpreter AND humans to read. This fixes
-- `string_format("%q")` only outputting in system encoding, instead of explicit Lua byte escapes
local SerializeString do
    -- These are control characters to be encoded in a certain way in Lua rather than just a byte
    -- escape (e.g. "\n" -> "\10")
    local SpecialCharacters = {
        ["\""] = "\\\"", -- Double-Quote
        ["\\"] = "\\\\", -- (Literal) Backslash
        -- Special ASCII control char codes
        ["\a"] = "\\a", -- Bell; ASCII #7
        ["\b"] = "\\b", -- Backspace; ASCII #8
        ["\t"] = "\\t", -- Horizontal-Tab; ASCII #9
        ["\n"] = "\\n", -- Newline; ASCII #10
        ["\v"] = "\\v", -- Vertical-Tab; ASCII #11
        ["\f"] = "\\f", -- Form-Feed; ASCII #12
        ["\r"] = "\\r", -- Carriage-Return; ASCII #13
    }

    -- We need to assign all extra normal byte escapes for runtime optimization
    for Index = 0, 255 do
        local Character = string_char(Index)

        if not SpecialCharacters[Character] and (Index < 32 or Index > 126) then
            SpecialCharacters[Character] = "\\" .. Index
        end
    end

    function SerializeString(inputString)
        -- FYI; We can't do "\0-\31" in Lua 5.1 (Only Luau/Lua 5.2+) due to an embedded zeros in pattern
        -- issue. See: https://stackoverflow.com/a/22962409
        return "\"" .. string_gsub(inputString, "[%z\\\"\1-\31\127-\255]", SpecialCharacters) .. "\""
    end
end

-- We need to occasionally construct valid comment blocks from external input, with proper escapes etc
local function CommentBlock(inputString)
    local Padding = ""
    for Match in string_gmatch(inputString, "%](=*)%]") do
        if #Match >= #Padding then
            Padding = Match .. "="
        end
    end

    return "--[" .. Padding .. "[" .. inputString .. "]" .. Padding .. "]"
end

local EvaluateInstancePath do
    -- VERY simple function to get if an object is a service, used in instance path eval
    local function IsService(object)
        -- Logically, if an object is actually under a service, that service *has* to already exist, as we've
        -- presumably evaluated to said path
        local FindServiceSuccess, ServiceObject = pcall(game.FindService, game, object.ClassName)

        if FindServiceSuccess and ServiceObject then
            return true
        end

        return false
    end

    -- Evaluating an instances' accessable "path" with just it's ref, and if the root parent is nil/isn't
    -- under `game` or `workspace`, returns nil.
    function EvaluateInstancePath(object)
        -- "Recursive" eval
        local ObjectPointer = object

        -- Input itself doesn't exist?
        if not ObjectPointer then
            return
        end

        local Path = ""

        while ObjectPointer do
            local ObjectName = ObjectPointer.Name
            local ObjectClassName = ObjectPointer.ClassName
            local ObjectParent = ObjectPointer.Parent

            if ObjectParent == game and IsService(ObjectPointer) then
                -- ^^ Then we'll use GetService directly, since it's actually a service under the DataModel

                Path = ":GetService(" .. SerializeString(ObjectClassName) .. ")" .. Path
            elseif not LuaKeywords[ObjectName] and string_match(ObjectName, "^[A-Za-z_][A-Za-z0-9_]*$") then
                -- ^^ Like the `string` DataType, this means means we can index the name directly in Lua
                -- without an explicit string
                Path = "." .. ObjectName .. Path
            else
                Path = "[" .. SerializeString(ObjectName) .. "]" .. Path
            end

            if ObjectParent == game then
                Path = "game" .. Path
                return Path
            elseif ObjectParent == workspace then
                Path = "workspace" .. Path
                return Path
            end

            -- Advance ObjectPointer, whether it exists or not (JUMPBACK)
            ObjectPointer = ObjectParent
        end

        -- Fall back to no ret.. Only objects parented under game/workspace will be serialized
        return
    end
end

--[[
LuaEncode(inputTable: {[any]: any}, options: {[string]:any}): string

    ---------- OPTIONS: ----------

    Prettify <boolean?:false> | Whether or not the output should use pretty printing.

    IndentCount <number?:0> | The amount of "spaces" that should be indented per entry. (*Note:
    If `Prettify` is set to true and this is unspecified, it'll be set to `4` automatically.*)

    OutputWarnings <boolean?:true> | If "warnings" should be placed to the output (as
    comments); It's recommended to keep this enabled, however this can be disabled at ease.

    StackLimit <number?:500> | The limit to the stack level before recursive encoding cuts
    off, and stops execution. This is used to prevent stack overflow errors and such. You
    could use `math.huge` here if you really wanted.

    FunctionsReturnRaw <boolean?:false> | If functions in said table return back a "raw"
    value to place in the output as the key/value.

    UseInstancePaths <boolean?:true> | If `Instance` reference objects should return their
    Lua-accessable path for encoding. If the instance is parented under `nil` or isn't under
    `game`/`workspace`, it'll always fall back to `Instance.new(ClassName)` as before.

    SerializeMathHuge <boolean?:true> | If numbers calculated as "infinite" (or negative-inf)
    numbers should be serialized with "math.huge". (uses the `math` import, as opposed to just
    a direct data type) If false, "`1/0`" or "`-1/0`" will be serialized, which is supported
    on all target versions.

]]

local function LuaEncode(inputTable, options)
    -- Check all arg and option types
    CheckType(inputTable, "inputTable", "table") -- Required*, nil not allowed
    CheckType(options, "options", "table", "nil") -- `options` is an optional arg

    -- Options
    if options then
        CheckType(options.Prettify, "options.Prettify", "boolean", "nil")
        CheckType(options.PrettyPrinting, "options.PrettyPrinting", "boolean", "nil") -- Alias for `Options.Prettify`
        CheckType(options.IndentCount, "options.IndentCount", "number", "nil")
        CheckType(options.OutputWarnings, "options.OutputWarnings", "boolean", "nil")
        CheckType(options.StackLimit, "options.StackLimit", "number", "nil")
        CheckType(options.FunctionsReturnRaw, "options.FunctionsReturnRaw", "boolean", "nil")
        CheckType(options.UseInstancePaths, "options.UseInstancePaths", "boolean", "nil")
        CheckType(options.SerializeMathHuge, "options.SerializeMathHuge", "boolean", "nil")
        
        -- Internal options:
        CheckType(options._StackLevel, "options._StackLevel", "number", "nil")
        CheckType(options._VisitedTables, "options._StackLevel", "table", "nil")
    end

    options = options or {}

    -- Because no if-else-then exp. in Lua 5.1+ (only Luau), for optional boolean values we need to check
    -- if it's nil first, THEN fall back to whatever it's actually set to if it's not nil
    local Prettify = (options.Prettify == nil and options.PrettyPrinting == nil and false) or (options.Prettify ~= nil and options.Prettify) or (options.PrettyPrinting and options.PrettyPrinting)
    local IndentCount = options.IndentCount or (Prettify and 4) or 0
    local OutputWarnings = (options.OutputWarnings == nil and true) or options.OutputWarnings
    local StackLimit = options.StackLimit or 500
    local FunctionsReturnRaw = (options.FunctionsReturnRaw == nil and false) or options.FunctionsReturnRaw
    local UseInstancePaths = (options.UseInstancePaths == nil and true) or options.UseInstancePaths
    local SerializeMathHuge = (options.SerializeMathHuge == nil and true) or options.SerializeMathHuge

    -- Internal options:

    -- Internal stack level for depth checks and indenting
    local StackLevel = options._StackLevel or 1
    -- Set root as visited; cyclic detection
    local VisitedTables = options._VisitedTables or {[inputTable] = true} -- [`visitedTable <table>`] = `isVisited <boolean>`

    -- Stack overflow/output abuse etc; default StackLimit is 500
    if StackLevel >= StackLimit then
        return "{--[[LuaEncode: Stack level limit of `" .. StackLimit .. "` reached]]}"
    end

    -- For +/- inf num serialization
    local PositiveInf = (SerializeMathHuge and "math.huge") or "1/0"
    local NegativeInf = (SerializeMathHuge and "-math.huge") or "-1/0"

    -- Easy-to-reference values for specific args
    local NewEntryString = (Prettify and "\n") or ""
    local ValueSeperator = (Prettify and ", ") or ","
    local BlankSeperator = (Prettify and " ") or ""

    -- For pretty printing (which is optional, and false by default) we need to keep track
    -- of the current stack, then repeat IndentString by that count
    local IndentString = string_rep(" ", IndentCount) -- If 0 this will just be ""
    IndentString = (Prettify and string_rep(IndentString, StackLevel)) or IndentString

    local EndingIndentString = (#IndentString > 0 and string_sub(IndentString, 1, -IndentCount - 1)) or ""

    -- For number key values, incrementing the current internal index
    local KeyIndex = 1

    -- Cases (C-Like) for encoding values, then end setup. Using cases so no elseif bs!
    -- Functions are all expected to return a (<string> EncodedKey, <boolean?> EncloseInBrackets)
    local TypeCases = {} do
        -- Basic func for getting the direct value of an encoded type without weird table.pack()[1] syntax
        local function TypeCase(typeName, value)
            -- Each of these funcs return a tuple, so it'd be annoying to do case-by-case
            local EncodedValue = TypeCases[typeName](value, false) -- False to label as NOT `isKey`
            return EncodedValue
        end

        -- For "tuple" args specifically, so there isn't a bunch of re-used code
        local function Args(...)
            local EncodedValues = {}

            for _, Arg in next, {...} do
                table_insert(EncodedValues, TypeCase(
                    Type(Arg),
                    Arg
                ))
            end

            return table_concat(EncodedValues, ValueSeperator)
        end

        -- For certain Roblox DataTypes, we use a custom serialization method for filling out params etc
        local function Params(newData, params)
            return "(function(v, p) for pn, pv in next, p do v[pn] = pv end return v end)(" ..
                table_concat({newData, TypeCase("table", params)}, ValueSeperator) ..
                ")"
        end

        TypeCases["number"] = function(value, isKey)
            -- If the number isn't the current real index of the table, we DO want to
            -- explicitly define it in the serialization no matter what for accuracy
            if isKey and value == KeyIndex then
                -- ^^ What's EXPECTED unless otherwise explicitly defined, if so, return no encoded num
                KeyIndex = KeyIndex + 1
                return nil, true
            end

            -- Lua's internal `tostring` handling will denote positive/negativie-infinite number TValues as "inf", which
            -- makes certain numbers not encode properly. We also just want to make the output precise
            if value == 1/0 then
                return PositiveInf
            elseif value == -1/0 then
                return NegativeInf
            end

            -- Return fixed-formatted precision num
            return string_format("%.14g", value)
        end

        TypeCases["string"] = function(value, isKey)
            if isKey and not LuaKeywords[value] and string_match(value, "^[A-Za-z_][A-Za-z0-9_]*$") then
                -- ^^ Then it's a syntaxically-correct variable, doesn't need explicit string def
                return value, true
            end

            return SerializeString(value)
        end

        TypeCases["table"] = function(value, isKey)
            -- Check duplicate/cyclic references
            do
                local VisitedTable = VisitedTables[value]
                if VisitedTable then
                    return string_format(
                        "{--[[LuaEncode: Duplicate reference%s]]}",
                        (value == inputTable and " (of parent)") or ""
                    )
                end

                VisitedTables[value] = true
            end

            -- *Point index not set by NewOptions to original
            local NewOptions = setmetatable({}, {__index = options}) do
                -- Overriding if key because it'd look worse pretty printed in a key
                NewOptions.Prettify = (isKey and false) or Prettify

                -- If Prettify is already false in the real args, set the indent to whatever
                -- the REAL IndentCount is set to
                NewOptions.IndentCount = (isKey and ((not Prettify and IndentCount) or 1)) or IndentCount

                -- Internal options
                NewOptions._StackLevel = (isKey and 1) or StackLevel + 1 -- If isKey, stack lvl is set to the **LOWEST** because it's the key to a value
                NewOptions._VisitedTables = VisitedTables
            end

            return LuaEncode(value, NewOptions)
        end

        TypeCases["boolean"] = function(value)
            return value and "true" or "false"
        end

        TypeCases["nil"] = function(value)
            return "nil"
        end

        TypeCases["function"] = function(value)
            -- If `FunctionsReturnRaw` is set as true, we'll call the function here itself, expecting
            -- a raw value for FunctionsReturnRaw to add as the key/value, you may want to do this for custom userdata or
            -- function closures. Thank's for listening to my Ted Talk!
            if FunctionsReturnRaw then
                return value()
            end

            -- If all else, force key func to return nil; can't handle a func val..
            return "function() --[[LuaEncode: `options.FunctionsReturnRaw` false; can't serialize functions]] return end"
        end

        ---------- ROBLOX CUSTOM DATATYPES BELOW ----------

        TypeCases["Axes"] = function(value)
            local EncodedArgs = {}
            local EnumValues = {
                ["Enum.Axis.X"] = value.X,
                ["Enum.Axis.Y"] = value.Y,
                ["Enum.Axis.Z"] = value.Z,
            }

            for EnumValue, IsEnabled in next, EnumValues do
                if IsEnabled then
                    table_insert(EncodedArgs, EnumValue)
                end
            end

            return "Axes.new(" .. table_concat(EncodedArgs, ValueSeperator) .. ")"
        end

        TypeCases["BrickColor"] = function(value)
            -- BrickColor.Number (Its enum ID) will be slightly more efficient in all cases in deser,
            -- so we'll use it if Options.Prettify is false
            return "BrickColor.new(" ..
                (Prettify and TypeCase("string", value.Name)) or value.Number ..
                ")"
        end

        TypeCases["CFrame"] = function(value)
            return "CFrame.new(" .. Args(value:components()) .. ")"
        end

        TypeCases["CatalogSearchParams"] = function(value)
            return Params("CatalogSearchParams.new()", {
                SearchKeyword = value.SearchKeyword,
                MinPrice = value.MinPrice,
                MaxPrice = value.MaxPrice,
                SortType = value.SortType, -- EnumItem
                CategoryFilter = value.CategoryFilter, -- EnumItem
                BundleTypes = value.BundleTypes, -- table
                AssetTypes = value.AssetTypes -- table
            })
        end

        TypeCases["Color3"] = function(value)
            -- Using floats for RGB values, most accurate for direct serialization
            return "Color3.new(" .. Args(value.R, value.G, value.B)
        end

        TypeCases["ColorSequence"] = function(value)
            return "ColorSequence.new(" .. TypeCase("table", value.Keypoints) .. ")"
        end

        TypeCases["ColorSequenceKeypoint"] = function(value)
            return "ColorSequenceKeypoint.new(" .. Args(value.Time, value.Value) .. ")"
        end

        -- We're using fromUnixTimestamp to serialize the object
        TypeCases["DateTime"] = function(value)
            -- Always an int, we don't need to do anything special
            return "DateTime.fromUnixTimestamp(" .. value.UnixTimestamp .. ")"
        end

        -- Properties seem to throw an error on index if the scope isn't a Studio plugin, so we're
        -- directly getting values! (so fun!!!!)
        TypeCases["DockWidgetPluginGuiInfo"] = function(value)
            -- e.g.: "InitialDockState:Right InitialEnabled:0 InitialEnabledShouldOverrideRestore:0 FloatingXSize:0 FloatingYSize:0 MinWidth:0 MinHeight:0"
            local ValueString = tostring(value)

            return "DockWidgetPluginGuiInfo.new(" ..
                Args(
                    -- InitialDockState (Enum.InitialDockState)
                    Enum.InitialDockState[string_match(ValueString, "InitialDockState:(%w+)")], -- Enum.InitialDockState.Right
                    -- InitialEnabled and InitialEnabledShouldOverrideRestore (boolean as number; `0` or `1`)
                    string_match(ValueString, "InitialEnabled:(%w+)") == "1", -- false
                    string_match(ValueString, "InitialEnabledShouldOverrideRestore:(%w+)") == "1", -- false
                    -- FloatingXSize/FloatingYSize (numbers)
                    tonumber(string_match(ValueString, "FloatingXSize:(%w+)")), -- 0
                    tonumber(string_match(ValueString, "FloatingYSize:(%w+)")), -- 0
                    -- MinWidth/MinHeight (numbers)
                    tonumber(string_match(ValueString, "MinWidth:(%w+)")), -- 0
                    tonumber(string_match(ValueString, "MinHeight:(%w+)")) -- 0
                ) ..
                ")"
        end

        -- e.g. `Enum.UserInputType`
        TypeCases["Enum"] = function(value)
            return "Enum." .. tostring(value) -- For now, this is the behavior of enums in tostring.. I have no other choice atm
        end

        -- e.g. `Enum.UserInputType.Gyro`
        TypeCases["EnumItem"] = function(value)
            return tostring(value) -- Returns the full enum index for now (e.g. "Enum.UserInputType.Gyro")
        end

        -- i.e. the `Enum` global return
        TypeCases["Enums"] = function(value)
            return "Enum"
        end

        TypeCases["Faces"] = function(value)
            local EncodedArgs = {}
            local EnumValues = {
                ["Enum.NormalId.Top"] = value.Top, -- These return bools
                ["Enum.NormalId.Bottom"] = value.Bottom,
                ["Enum.NormalId.Left"] = value.Left,
                ["Enum.NormalId.Right"] = value.Right,
                ["Enum.NormalId.Back"] = value.Back,
                ["Enum.NormalId.Front"] = value.Front,
            }

            for EnumValue, IsEnabled in next, EnumValues do
                if IsEnabled then
                    table_insert(EncodedArgs, EnumValue)
                end
            end

            return "Faces.new(" .. table_concat(EncodedArgs, ValueSeperator) .. ")"
        end

        TypeCases["FloatCurveKey"] = function(value)
            return "FloatCurveKey.new(" .. Args(value.Time, value.Value, value.Interpolation) .. ")"
        end

        TypeCases["Font"] = function(value)
            return "Font.new(" .. Args(value.Family, value.Weight, value.Style) .. ")"
        end

        -- Instance refs can be evaluated to their paths (optional), but if parented to
        -- nil or some DataModel not under `game`, it'll just return nil
        TypeCases["Instance"] = function(value)
            if UseInstancePaths then
                local InstancePath = EvaluateInstancePath(value)
                if InstancePath then
                    return InstancePath
                end

                -- ^^ Now, if the path isn't accessable, falls back to the return below anyway
            end

            return "nil" .. BlankSeperator .. CommentBlock("Instance.new(" .. TypeCase("string", value.ClassName) .. ")")
        end

        TypeCases["NumberRange"] = function(value)
            return "NumberRange.new(" .. Args(value.Min, value.Max) .. ")"
        end

        TypeCases["NumberSequence"] = function(value)
            return "NumberSequence.new(" .. TypeCase("table", value.Keypoints) .. ")"
        end

        TypeCases["NumberSequenceKeypoint"] = function(value)
            return "NumberSequenceKeypoint.new(" .. Args(value.Time, value.Value, value.Envelope) .. ")"
        end

        TypeCases["OverlapParams"] = function(value)
            return Params("OverlapParams.new()", {
                FilterDescendantsInstances = value.FilterDescendantsInstances,
                FilterType = value.FilterType,
                MaxParts = value.MaxParts,
                CollisionGroup = value.CollisionGroup,
                RespectCanCollide = value.RespectCanCollide
            })
        end

        TypeCases["PathWaypoint"] = function(value)
            return "PathWaypoint.new(" .. Args(value.Position, value.Action, value.Label) .. ")"
        end

        TypeCases["PhysicalProperties"] = function(value)
            return "PhysicalProperties.new(" ..
                Args(
                    value.Density,
                    value.Friction,
                    value.Elasticity,
                    value.FrictionWeight,
                    value.ElasticityWeight
                ) ..
                ")"
        end

        TypeCases["Random"] = function()
            return "Random.new()"
        end

        TypeCases["Ray"] = function(value)
            return "Ray.new(" .. Args(value.Origin, value.Direction) .. ")"
        end

        TypeCases["RaycastParams"] = function(value)
            return Params("RaycastParams.new()", {
                FilterDescendantsInstances = value.FilterDescendantsInstances,
                FilterType = value.FilterType,
                IgnoreWater = value.IgnoreWater,
                CollisionGroup = value.CollisionGroup,
                RespectCanCollide = value.RespectCanCollide
            })
        end

        TypeCases["Rect"] = function(value)
            return "Rect.new(" .. Args(value.Min, value.Max) .. ")"
        end

        -- Roblox doesn't provide read properties for min/max on `Region3`, but they do on Region3int16.. Anyway,
        -- we CAN calculate the min/max of a Region3 from just .CFrame and .Size.. Thanks to wally for linking me
        -- the thread for this method lol
        TypeCases["Region3"] = function(value)
            local ValueCFrame = value.CFrame
            local ValueSize = value.Size

            return "Region3.new(" ..
                Args(
                    ValueCFrame * CFrame.new(-ValueSize / 2), -- Minimum
                    ValueCFrame * CFrame.new(ValueSize / 2) -- Maximum
                ) ..
                ")"
        end

        TypeCases["Region3int16"] = function(value)
            return "Region3int16.new(" .. Args(value.Min, value.Max) .. ")"
        end

        TypeCases["TweenInfo"] = function(value)
            return "TweenInfo.new(" ..
                Args(
                    value.Time,
                    value.EasingStyle,
                    value.EasingDirection,
                    value.RepeatCount,
                    value.Reverses,
                    value.DelayTime
                ) ..
                ")"
        end

        -- CURRENTLY UNDOCUMENTED*
        TypeCases["RotationCurveKey"] = function(value)
            return "RotationCurveKey.new(" .. Args(value.Time, value.Value, value.Interpolation) .. ")"
        end

        TypeCases["UDim"] = function(value)
            return "UDim.new(" .. Args(value.Scale, value.Offset) .. ")"
        end

        TypeCases["UDim2"] = function(value)
            return "UDim2.new(" ..
                Args(
                    -- Not directly using X and Y UDims for better output (i.e. would be
                    -- UDim2.new(UDim.new(1, 0), UDim.new(1, 0)) if I did)
                    value.X.Scale,
                    value.X.Offset,
                    value.Y.Scale,
                    value.Y.Offset
                ) ..
                ")"
        end

        TypeCases["Vector2"] = function(value)
            return "Vector2.new(" .. Args(value.X, value.Y) .. ")"
        end

        TypeCases["Vector2int16"] = function(value)
            return "Vector2int16.new(" .. Args(value.X, value.Y) .. ")"
        end

        TypeCases["Vector3"] = function(value)
            return "Vector3.new(" .. Args(value.X, value.Y, value.Z) .. ")"
        end

        TypeCases["Vector3int16"] = function(value)
            return "Vector3int16.new(" .. Args(value.X, value.Y, value.Z) .. ")"
        end

        -- With userdata, just encode directly
        TypeCases["userdata"] = function(value)
            if getmetatable(value) then -- Has mt
                return "newproxy(true)"
            else
                return "newproxy()" -- newproxy() defaults to false (no mt)
            end
        end
    end

    -- Setup output tbl
    local Output = ""

    for Key, Value in next, inputTable do
        local KeyType = Type(Key)
        local ValueType = Type(Value)

        if TypeCases[KeyType] and TypeCases[ValueType] then
            local EntryOutput = (Prettify and NewEntryString .. IndentString) or ""
            local ValueWasEncoded = false -- Keeping track of this for adding a "," to the EntryOutput if needed

            -- Go through and get key val
            local KeyEncodedSuccess, EncodedKeyOrError, DontEncloseInBrackets = pcall(TypeCases[KeyType], Key, true) -- The `true` represents if it's a key or not, here it is

            -- Ignoring 2nd arg (`DontEncloseInBrackets`) because this isn't the key
            local ValueEncodedSuccess, EncodedValueOrError = pcall(TypeCases[ValueType], Value, false) -- `false` because it's NOT the key, it's the value

            -- Im sorry for this logic chain here, I can't use `continue`/`continue()`.. :sob:
            -- Ignoring `if EncodedKeyOrError` because the key doesn't actually need to ALWAYS
            -- be explicitly encoded, like if it's a number of the current key index!
            if KeyEncodedSuccess and ValueEncodedSuccess and EncodedValueOrError then
                -- NOW we'll check for if the key was explicitly encoded, because we don't to stop
                -- the value from encoding, since we've already checked that and it *has* been
                local KeyValue = EncodedKeyOrError and ((DontEncloseInBrackets and EncodedKeyOrError) or string_format("[%s]", EncodedKeyOrError)) .. ((Prettify and " = ") or "=") or ""

                -- Encode key/value together, we've already checked if `EncodedValueOrError` was returned
                EntryOutput = EntryOutput .. KeyValue .. EncodedValueOrError
                ValueWasEncoded = true
            elseif OutputWarnings then -- Then `Encoded(Key/Value)OrError` is the error msg
                -- ^^ Then either the key or value wasn't properly checked or encoded, and there
                -- was an error we need to log!
                local ErrorMessage = string_format(
                    "LuaEncode: Failed to encode %s of DataType `%s`: %s",
                    (not KeyEncodedSuccess and "key") or (not ValueEncodedSuccess and "value") or "key/value", -- "key/value" for bool type fallback
                    ValueType,
                    (not KeyEncodedSuccess and SerializeString(EncodedKeyOrError)) or (not ValueEncodedSuccess and SerializeString(EncodedValueOrError)) or "(Failed to get error message)"
                )

                EntryOutput = EntryOutput .. CommentBlock(ErrorMessage)
            end

            -- If there isn't another value after the current index, add ending formatting
            if next(inputTable, Key) then
                -- Yes.. The nesting here is deliberate
                if ValueWasEncoded then
                    EntryOutput = EntryOutput .. ","
                end
            else
                -- If there isn't another value after the current index, add ending formatting
                EntryOutput = EntryOutput .. NewEntryString .. EndingIndentString
            end

            Output = Output .. EntryOutput
        end
    end

    Output = "{" .. Output .. "}"
    return Output
end

return LuaEncode
