local start = tick()
local client = game:GetService('Players').LocalPlayer;
local set_identity = (type(syn) == 'table' and syn.set_thread_identity) or setidentity or setthreadcontext
local executor = identifyexecutor and identifyexecutor() or 'Unknown'
local function fail(r) return client:Kick(r) end
-- gracefully handle errors when loading external scripts
-- added a cache to make hot reloading a bit faster
local usedCache = shared.__urlcache and next(shared.__urlcache) ~= nil
shared.__urlcache = shared.__urlcache or {}
local function urlLoad(url)
local success, result
if shared.__urlcache[url] then
success, result = true, shared.__urlcache[url]
else
success, result = pcall(game.HttpGet, game, url)
end
if (not success) then
return fail(string.format('Failed to GET url %q for reason: %q', url, tostring(result)))
end
local fn, err = loadstring(result)
if (type(fn) ~= 'function') then
return fail(string.format('Failed to loadstring url %q for reason: %q', url, tostring(err)))
end
local results = { pcall(fn) }
if (not results[1]) then
return fail(string.format('Failed to initialize url %q for reason: %q', url, tostring(results[2])))
end
shared.__urlcache[url] = result
return unpack(results, 2)
end
-- attempt to block imcompatible exploits
-- rewrote because old checks literally did not work
if type(set_identity) ~= 'function' then return fail('Unsupported exploit (missing "set_thread_identity")') end
if type(getconnections) ~= 'function' then return fail('Unsupported exploit (missing "getconnections")') end
if type(getloadedmodules) ~= 'function' then return fail('Unsupported exploit (misssing "getloadedmodules")') end
if type(getgc) ~= 'function' then return fail('Unsupported exploit (misssing "getgc")') end
local getinfo = debug.getinfo or getinfo;
local getupvalue = debug.getupvalue or getupvalue;
local getupvalues = debug.getupvalues or getupvalues;
local setupvalue = debug.setupvalue or setupvalue;
if type(setupvalue) ~= 'function' then return fail('Unsupported exploit (misssing "debug.setupvalue")') end
if type(getupvalue) ~= 'function' then return fail('Unsupported exploit (misssing "debug.getupvalue")') end
if type(getupvalues) ~= 'function' then return fail('Unsupported exploit (missing "debug.getupvalues")') end
-- free exploit bandaid fix
if type(getinfo) ~= 'function' then
local debug_info = debug.info;
if type(debug_info) ~= 'function' then
-- if your exploit doesnt have getrenv you have no hope
if type(getrenv) ~= 'function' then return fail('Unsupported exploit (missing "getrenv")') end
debug_info = getrenv().debug.info
end
getinfo = function(f)
assert(type(f) == 'function', string.format('Invalid argument #1 to debug.getinfo (expected %s got %s', 'function', type(f)))
local results = { debug.info(f, 'slnfa') }
local _, upvalues = pcall(getupvalues, f)
if type(upvalues) ~= 'table' then
upvalues = {}
end
local nups = 0
for k in next, upvalues do
nups = nups + 1
end
-- winning code
return {
source = '@' .. results[1],
short_src = results[1],
what = results[1] == '[C]' and 'C' or 'Lua',
currentline = results[2],
name = results[3],
func = results[4],
numparams = results[5],
is_vararg = results[6], -- 'a' argument returns 2 values :)
nups = nups,
}
end
end
local UI = urlLoad("https://raw.githubusercontent.com/wally-rblx/LinoriaLib/main/Library.lua")
local themeManager = urlLoad("https://raw.githubusercontent.com/wally-rblx/LinoriaLib/main/addons/ThemeManager.lua")
local metadata = urlLoad("https://raw.githubusercontent.com/wally-rblx/funky-friday-autoplay/main/metadata.lua")
local httpService = game:GetService('HttpService')
local framework, scrollHandler, network
local counter = 0
while true do
for _, obj in next, getgc(true) do
if type(obj) == 'table' then
if rawget(obj, 'GameUI') then
framework = obj;
elseif type(rawget(obj, 'Server')) == 'table' then
network = obj;
end
end
if network and framework then break end
end
for _, module in next, getloadedmodules() do
if module.Name == 'ScrollHandler' then
scrollHandler = module;
break;
end
end
if (type(framework) == 'table' and typeof(scrollHandler) == 'Instance' and type(network) == 'table') then
break
end
counter = counter + 1
if counter > 6 then
fail(string.format('Failed to load game dependencies. Details: %s, %s, %s', type(framework), typeof(scrollHandler), type(network)))
end
wait(1)
end
local runService = game:GetService('RunService')
local userInputService = game:GetService('UserInputService')
local virtualInputManager = game:GetService('VirtualInputManager')
local random = Random.new()
local task = task or getrenv().task;
local fastWait, fastSpawn = task.wait, task.spawn;
-- firesignal implementation
-- hitchance rolling
local fireSignal, rollChance do
-- updated for script-ware or whatever
-- attempted to update for krnl
function fireSignal(target, signal, ...)
-- getconnections with InputBegan / InputEnded does not work without setting Synapse to the game's context level
set_identity(2)
local didFire = false
for _, signal in next, getconnections(signal) do
if type(signal.Function) == 'function' and islclosure(signal.Function) then
local scr = rawget(getfenv(signal.Function), 'script')
if scr == target then
didFire = true
pcall(signal.Function, ...)
end
end
end
-- if not didFire then fail"couldnt fire input signal" end
set_identity(7)
end
-- uses a weighted random system
-- its a bit scuffed rn but it works good enough
function rollChance()
-- if (//library.flags.autoPlayerMode == 'Manual') then
if Options.AutoplayerMode.Value == 'Manual' then
if (Options.SickBind:GetState()) then return 'Sick' end
if (Options.GoodBind:GetState()) then return 'Good' end
if (Options.OkayBind:GetState()) then return 'Ok' end
if (Options.BadBind:GetState()) then return 'Bad' end
return 'Bad' -- incase if it cant find one
end
local chances = {
{ 'Sick', Options.SickChance.Value },
{ 'Good', Options.GoodChance.Value },
{ 'Ok', Options.OkChance.Value },
{ 'Bad', Options.BadChance.Value },
{ 'Miss' , Options.MissChance.Value },
}
table.sort(chances, function(a, b)
return a[2] > b[2]
end)
local sum = 0;
for i = 1, #chances do
sum += chances[i][2]
end
if sum == 0 then
return chances[random:NextInteger(1, #chances)][1]
end
local initialWeight = random:NextInteger(0, sum)
local weight = 0;
for i = 1, #chances do
weight = weight + chances[i][2]
if weight > initialWeight then
return chances[i][1]
end
end
return 'Sick'
end
end
-- autoplayer
local chanceValues do
chanceValues = {
Sick = 96,
Good = 92,
Ok = 87,
Bad = 75,
}
local keyCodeMap = {}
for _, enum in next, Enum.KeyCode:GetEnumItems() do
keyCodeMap[enum.Value] = enum
end
if shared._unload then
pcall(shared._unload)
end
function shared._unload()
if shared._id then
pcall(runService.UnbindFromRenderStep, runService, shared._id)
end
UI:Unload()
for i = 1, #shared.threads do
coroutine.close(shared.threads[i])
end
for i = 1, #shared.callbacks do
task.spawn(shared.callbacks[i])
end
end
shared.threads = {}
shared.callbacks = {}
shared._id = httpService:GenerateGUID(false)
local rng = Random.new()
runService:BindToRenderStep(shared._id, 1, function()
--if (not library.flags.autoPlayer) then return end
if (not Toggles.Autoplayer) or (not Toggles.Autoplayer.Value) then
return
end
local currentlyPlaying = framework.SongPlayer.CurrentlyPlaying
if typeof(currentlyPlaying) ~= 'Instance' or not currentlyPlaying:IsA('Sound') then
return
end
local arrows = framework.UI:GetNotes()
local count = framework.SongPlayer:GetKeyCount()
local mode = count .. 'Key'
local arrowData = framework.ArrowData[mode].Arrows
for i, arrow in next, arrows do
-- todo: switch to this (https://i.imgur.com/pEVe6Tx.png)
local ignoredNoteTypes = { Death = true, Mechanic = true, Poison = true }
if type(arrow.NoteDataConfigs) == 'table' then
if ignoredNoteTypes[arrow.NoteDataConfigs.Type] then
continue
end
end
if (arrow.Side == framework.UI.CurrentSide) and (not arrow.Marked) and currentlyPlaying.TimePosition > 0 then
local position = (arrow.Data.Position % count) .. ''
local hitboxOffset = 0 do
local settings = framework.Settings;
local offset = type(settings) == 'table' and settings.HitboxOffset;
local value = type(offset) == 'table' and offset.Value;
if type(value) == 'number' then
hitboxOffset = value;
end
hitboxOffset = hitboxOffset / 1000
end
local songTime = framework.SongPlayer.CurrentTime do
local configs = framework.SongPlayer.CurrentSongConfigs
local playbackSpeed = type(configs) == 'table' and configs.PlaybackSpeed
if type(playbackSpeed) ~= 'number' then
playbackSpeed = 1
end
songTime = songTime / playbackSpeed
end
local noteTime = math.clamp((1 - math.abs(arrow.Data.Time - (songTime + hitboxOffset))) * 100, 0, 100)
local result = rollChance()
arrow._hitChance = arrow._hitChance or result;
local hitChance = (Options.AutoplayerMode.Value == 'Manual' and result or arrow._hitChance)
if hitChance ~= "Miss" and noteTime >= chanceValues[arrow._hitChance] then
fastSpawn(function()
arrow.Marked = true;
local keyCode = keyCodeMap[arrowData[position].Keybinds.Keyboard[1]]
if Options.PressMode.Value == 'Key press' then
virtualInputManager:SendKeyEvent(true, keyCode, false, nil)
else
fireSignal(scrollHandler, userInputService.InputBegan, { KeyCode = keyCode, UserInputType = Enum.UserInputType.Keyboard }, false)
end
local arrowLength = arrow.Data.Length or 0
local isHeld = arrowLength > 0
local delayMode = Options.DelayMode.Value
local minDelay = isHeld and Options.HeldDelayMin or Options.NoteDelayMin;
local maxDelay = isHeld and Options.HeldDelayMax or Options.NoteDelayMax;
local noteDelay = isHeld and Options.HeldDelay or Options.ReleaseDelay
if Options.DelayMode.Value == 'Random' then
task.wait(arrowLength + rng:NextNumber(minDelay.Value, maxDelay.Value) / 1000)
else
task.wait(arrowLength + (noteDelay.Value / 1000))
end
if Options.PressMode.Value == 'Key press' then
virtualInputManager:SendKeyEvent(false, keyCode, false, nil)
else
fireSignal(scrollHandler, userInputService.InputEnded, { KeyCode = keyCode, UserInputType = Enum.UserInputType.Keyboard }, false)
end
arrow.Marked = nil;
end)
end
end
end
end)
end
local ActivateUnlockables do
-- Note: I know you can do this with UserId but it only works if you run it before opening the notes menu
-- My script should work no matter the order of which you run things :)
local loadStyle = nil
local function loadStyleProxy(...)
-- This forces the styles to reload every time
local upvalues = getupvalues(loadStyle)
for i, upvalue in next, upvalues do
if type(upvalue) == 'table' and rawget(upvalue, 'Style') then
rawset(upvalue, 'Style', nil);
setupvalue(loadStyle, i, upvalue)
end
end
return loadStyle(...)
end
local function applyLoadStyleProxy(...)
local gc = getgc()
for i = 1, #gc do
local obj = gc[i]
if type(obj) == 'function' then
-- goodbye nups numeric loop because script-ware is weird
local upvalues = getupvalues(obj)
for i, upv in next, upvalues do
if type(upv) == 'function' and getinfo(upv).name == 'LoadStyle' then
-- ugly but it works, we don't know every name for is_synapse_function and similar
if getinfo(obj).source:match('%.ArrowSelector%.Customize$') and getinfo(upv).source:match('%.ArrowSelector%.Customize$') then
-- avoid non-game functions :)
loadStyle = loadStyle or upv
setupvalue(obj, i, loadStyleProxy)
table.insert(shared.callbacks, function()
assert(pcall(setupvalue, obj, i, loadStyle))
end)
end
end
end
end
end
end
local success, error = pcall(applyLoadStyleProxy)
if not success then
return fail(string.format('Failed to hook LoadStyle function. Error(%q)\nExecutor(%q)\n', error, executor))
end
function ActivateUnlockables()
local idx = table.find(framework.SongsWhitelist, client.UserId)
if idx then return end
UI:Notify('Developer arrows have been unlocked!', 3)
table.insert(framework.SongsWhitelist, client.UserId)
end
end
-- UpdateScore hook
do
local roundManager = nil;
repeat
task.wait()
roundManager = network.Server.RoundManager
until roundManager;
local oldUpdateScore = type(roundManager) == 'table' and roundManager.UpdateScore;
function roundManager.UpdateScore(...)
local args = { ... }
local score = args[2]
if type(score) == 'number' and Options.ScoreModifier then
if Options.ScoreModifier.Value == 'No decrease on miss' then
args[2] = 0
elseif Options.ScoreModifier.Value == 'Increase score on miss' then
args[2] = math.abs(score)
end
end
return oldUpdateScore(unpack(args))
end
table.insert(shared.callbacks, function()
roundManager.UpdateScore = oldUpdateScore
end)
end
local SaveManager = {} do
SaveManager.Ignore = {}
SaveManager.Parser = {
Toggle = {
Save = function(idx, object)
return { type = 'Toggle', idx = idx, value = object.Value }
end,
Load = function(idx, data)
if Toggles[idx] then
Toggles[idx]:SetValue(data.value)
end
end,
},
Slider = {
Save = function(idx, object)
return { type = 'Slider', idx = idx, value = tostring(object.Value) }
end,
Load = function(idx, data)
if Options[idx] then
Options[idx]:SetValue(data.value)
end
end,
},
Dropdown = {
Save = function(idx, object)
return { type = 'Dropdown', idx = idx, value = object.Value, mutli = object.Multi }
end,
Load = function(idx, data)
if Options[idx] then
Options[idx]:SetValue(data.value)
end
end,
},
ColorPicker = {
Save = function(idx, object)
return { type = 'ColorPicker', idx = idx, value = object.Value:ToHex() }
end,
Load = function(idx, data)
if Options[idx] then
Options[idx]:SetValueRGB(Color3.fromHex(data.value))
end
end,
},
KeyPicker = {
Save = function(idx, object)
return { type = 'KeyPicker', idx = idx, mode = object.Mode, key = object.Value }
end,
Load = function(idx, data)
if Options[idx] then
Options[idx]:SetValue({ data.key, data.mode })
end
end,
}
}
function SaveManager:Save(name)
local fullPath = 'funky_friday_autoplayer/configs/' .. name .. '.json'
local data = {
version = 2,
objects = {}
}
for idx, toggle in next, Toggles do
if self.Ignore[idx] then continue end
table.insert(data.objects, self.Parser[toggle.Type].Save(idx, toggle))
end
for idx, option in next, Options do
if not self.Parser[option.Type] then continue end
if self.Ignore[idx] then continue end
table.insert(data.objects, self.Parser[option.Type].Save(idx, option))
end
local success, encoded = pcall(httpService.JSONEncode, httpService, data)
if not success then
return false, 'failed to encode data'
end
writefile(fullPath, encoded)
return true
end
function SaveManager:Load(name)
local file = 'funky_friday_autoplayer/configs/' .. name .. '.json'
if not isfile(file) then return false, 'invalid file' end
local success, decoded = pcall(httpService.JSONDecode, httpService, readfile(file))
if not success then return false, 'decode error' end
if decoded.version ~= 2 then return false, 'invalid version' end
for _, option in next, decoded.objects do
if self.Parser[option.type] then
self.Parser[option.type].Load(option.idx, option)
end
end
return true
end
function SaveManager.Refresh()
local list = listfiles('funky_friday_autoplayer/configs')
local out = {}
for i = 1, #list do
local file = list[i]
if file:sub(-5) == '.json' then
-- i hate this but it has to be done ...
local pos = file:find('.json', 1, true)
local start = pos
local char = file:sub(pos, pos)
while char ~= '/' and char ~= '\\' and char ~= '' do
pos = pos - 1
char = file:sub(pos, pos)
end
if char == '/' or char == '\\' then
table.insert(out, file:sub(pos + 1, start - 1))
end
end
end
Options.ConfigList.Values = out;
Options.ConfigList:SetValues()
Options.ConfigList:Display()
return out
end
function SaveManager:Delete(name)
local file = 'funky_friday_autoplayer/configs/' .. name .. '.json'
if not isfile(file) then return false, string.format('Config %q does not exist', name) end
local succ, err = pcall(delfile, file)
if not succ then
return false, string.format('error occured during file deletion: %s', err)
end
return true
end
function SaveManager:SetIgnoreIndexes(list)
for i = 1, #list do
table.insert(self.Ignore, list[i])
end
end
function SaveManager.Check()
local list = listfiles('funky_friday_autoplayer/configs')
for _, file in next, list do
if isfolder(file) then continue end
local data = readfile(file)
local success, decoded = pcall(httpService.JSONDecode, httpService, data)
if success and type(decoded) == 'table' and decoded.version ~= 2 then
pcall(delfile, file)
end
end
end
end
local Window = UI:CreateWindow({
Title = string.format('funky friday autoplayer - version %s | updated: %s', metadata.version, metadata.updated),
AutoShow = true,
Center = true,
Size = UDim2.fromOffset(550, 627),
})
local Tabs = {}
local Groups = {}
Tabs.Main = Window:AddTab('Main')
Tabs.Miscellaneous = Window:AddTab('Miscellaneous')
Groups.Autoplayer = Tabs.Main:AddLeftGroupbox('Autoplayer')
Groups.Autoplayer:AddToggle('Autoplayer', { Text = 'Autoplayer' }):AddKeyPicker('AutoplayerBind', { Default = 'End', NoUI = true, SyncToggleState = true })
Groups.Autoplayer:AddDropdown('PressMode', { Text = 'Key press mode', Default = 'Fire signal', Values = { 'Fire signal', 'Key press' }, Tooltip = 'Set this to "Key press" if the other mode does not work' })
Groups.Autoplayer:AddDivider()
Groups.Autoplayer:AddDropdown('AutoplayerMode', { Text = 'Autoplayer mode', Default = 1, Values = { 'Chances', 'Manual' } })
Groups.Autoplayer:AddDropdown('DelayMode', { Text = 'Delay mode', Default = 1, Values = { 'Manual', 'Random' } })
Groups.Autoplayer:AddDivider()
Groups.Autoplayer:AddDropdown('ScoreModifier', {
Text = 'Score modifications',
Default = 1,
Values = { 'Do nothing', 'No decrease on miss', 'Increase score on miss' },
Tooltip = 'Modifies certain game functions to help you keep your score up!',
})
Groups.HitChances = Tabs.Main:AddLeftGroupbox('Hit chances')
Groups.HitChances:AddSlider('SickChance', { Text = 'Sick chance', Min = 0, Max = 100, Default = 100, Suffix = '%', Rounding = 0 })
Groups.HitChances:AddSlider('GoodChance', { Text = 'Good chance', Min = 0, Max = 100, Default = 0, Suffix = '%', Rounding = 0 })
Groups.HitChances:AddSlider('OkChance', { Text = 'Ok chance', Min = 0, Max = 100, Default = 0, Suffix = '%', Rounding = 0 })
Groups.HitChances:AddSlider('BadChance', { Text = 'Bad chance', Min = 0, Max = 100, Default = 0, Suffix = '%', Rounding = 0 })
Groups.HitChances:AddSlider('MissChance', { Text = 'Miss chance', Min = 0, Max = 100, Default = 0, Suffix = '%', Rounding = 0 })
Groups.HitTiming = Tabs.Main:AddRightTabbox('Hit timing')
Groups.ManualTiming = Groups.HitTiming:AddTab('Manual delay')
Groups.ManualTiming:AddSlider('ReleaseDelay', { Text = 'Release delay (ms)', Min = 0, Max = 500, Default = 20, Rounding = 0 })
Groups.ManualTiming:AddSlider('HeldDelay', { Text = 'Held delay (ms)', Min = -20, Max = 100, Default = 0, Rounding = 0 })
Groups.RandomTiming = Groups.HitTiming:AddTab('Random delay')
Groups.RandomTiming:AddSlider('NoteDelayMin', { Text = 'Minimum note delay (ms)', Min = 0, Max = 500, Default = 0, Rounding = 0 })
Groups.RandomTiming:AddSlider('NoteDelayMax', { Text = 'Maximum note delay (ms)', Min = 0, Max = 100, Default = 20, Rounding = 0 })
Groups.RandomTiming:AddSlider('HeldDelayMin', { Text = 'Minimum held note delay (ms)', Min = 0, Max = 500, Default = 0, Rounding = 0 })
Groups.RandomTiming:AddSlider('HeldDelayMax', { Text = 'Maximum held note delay (ms)', Min = 0, Max = 100, Default = 20, Rounding = 0 })
Groups.Keybinds = Tabs.Main:AddRightGroupbox('Keybinds')
Groups.Keybinds:AddLabel('Sick'):AddKeyPicker('SickBind', { Default = 'One', NoUI = true })
Groups.Keybinds:AddLabel('Good'):AddKeyPicker('GoodBind', { Default = 'Two', NoUI = true })
Groups.Keybinds:AddLabel('Ok'):AddKeyPicker('OkayBind', { Default = 'Three', NoUI = true })
Groups.Keybinds:AddLabel('Bad'):AddKeyPicker('BadBind', { Default = 'Four', NoUI = true })
Groups.Configs = Tabs.Miscellaneous:AddRightGroupbox('Configs')
Groups.Credits = Tabs.Miscellaneous:AddRightGroupbox('Credits')
Groups.Credits:AddLabel('wally - script')
Groups.Credits:AddLabel('Sezei - contributor')
Groups.Credits:AddLabel('Inori - ui library')
Groups.Credits:AddLabel('Jan - old ui library')
Groups.Unlockables = Tabs.Miscellaneous:AddRightGroupbox('Unlockables')
Groups.Unlockables:AddButton('Unlock developer notes', ActivateUnlockables)
Groups.Misc = Tabs.Miscellaneous:AddRightGroupbox('Miscellaneous')
Groups.Misc:AddLabel(metadata.message or 'no message found!', true)
Groups.Misc:AddDivider()
Groups.Misc:AddButton('Unload script', function() pcall(shared._unload) end)
Groups.Misc:AddButton('Copy discord', function()
if pcall(setclipboard, "https://wally.cool/discord") then
UI:Notify('Successfully copied discord link to your clipboard!', 5)
end
end)
Groups.Misc:AddLabel('Menu toggle'):AddKeyPicker('MenuToggle', { Default = 'Delete', NoUI = true })
UI.ToggleKeybind = Options.MenuToggle
if type(readfile) == 'function' and type(writefile) == 'function' and type(makefolder) == 'function' and type(isfolder) == 'function' then
makefolder('funky_friday_autoplayer')
makefolder('funky_friday_autoplayer\\configs')
Groups.Configs:AddDropdown('ConfigList', { Text = 'Config list', Values = {} })
Groups.Configs:AddInput('ConfigName', { Text = 'Config name' })
Groups.Configs:AddDivider()
Groups.Configs:AddButton('Save config', function()
local name = Options.ConfigName.Value;
if name:gsub(' ', '') == '' then
return UI:Notify('Invalid config name.', 3)
end
local success, err = SaveManager:Save(name)
if not success then
return UI:Notify(tostring(err), 5)
end
UI:Notify(string.format('Saved config %q', name), 5)
task.defer(SaveManager.Refresh)
end)
Groups.Configs:AddButton('Load', function()
local name = Options.ConfigList.Value
local success, err = SaveManager:Load(name)
if not success then
return UI:Notify(tostring(err), 5)
end
UI:Notify(string.format('Loaded config %q', name), 5)
end):AddButton('Delete', function()
local name = Options.ConfigList.Value
if name:gsub(' ', '') == '' then
return UI:Notify('Invalid config name.', 3)
end
local success, err = SaveManager:Delete(name)
if not success then
return UI:Notify(tostring(err), 5)
end
UI:Notify(string.format('Deleted config %q', name), 5)
task.spawn(Options.ConfigList.SetValue, Options.ConfigList, nil)
task.defer(SaveManager.Refresh)
end)
Groups.Configs:AddButton('Refresh list', SaveManager.Refresh)
task.defer(SaveManager.Refresh)
task.defer(SaveManager.Check)
else
Groups.Configs:AddLabel('Your exploit is missing file functions so you are unable to use configs.', true)
--UI:Notify('Failed to create configs tab due to your exploit missing certain file functions.', 2)
end
-- Themes
do
local latestThemeIndex = 0
for i, theme in next, themeManager.BuiltInThemes do
if theme[1] > latestThemeIndex then
latestThemeIndex = theme[1]
end
end
latestThemeIndex = latestThemeIndex + 1
local linoriaTheme = themeManager.BuiltInThemes.Default[2]
local funkyFridayTheme = table.clone(themeManager.BuiltInThemes.Default[2])
funkyFridayTheme.AccentColor = Color3.fromRGB(255, 65, 65):ToHex()
themeManager.BuiltInThemes['Linoria'] = { latestThemeIndex, linoriaTheme }
themeManager.BuiltInThemes['Default'] = { 1, funkyFridayTheme }
themeManager:SetLibrary(UI)
themeManager:SetFolder('funky_friday_autoplayer')
themeManager:ApplyToGroupbox(Tabs.Miscellaneous:AddLeftGroupbox('Themes'))
SaveManager:SetIgnoreIndexes({
"BackgroundColor", "MainColor", "AccentColor", "OutlineColor", "FontColor", -- themes
"ThemeManager_ThemeList", 'ThemeManager_CustomThemeList', 'ThemeManager_CustomThemeName', -- themes
})
end
UI:Notify(string.format('Loaded script in %.4f second(s)!', tick() - start), 3)