From ec0d606011b8f0906efc2b903953668190f8ba28 Mon Sep 17 00:00:00 2001 From: Pentatrate <190233437+Pentatrate@users.noreply.github.com> Date: Sun, 16 Nov 2025 22:35:14 +0100 Subject: [PATCH 1/5] 2.0.0 Imgui and mini mod menu - different way of intercepting game launch - use Imgui for new menu - enabling/disabling in the launcher and profiles --- .gitignore | 4 + config.lua | 15 +++ lovely/prelaunch.toml | 19 +++- mod.json | 18 +++- prelaunch.lua | 84 +-------------- states/Launcher.lua | 239 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 291 insertions(+), 88 deletions(-) create mode 100644 .gitignore create mode 100644 config.lua create mode 100644 states/Launcher.lua diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9cfbe1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vscode/ +.lovelyignore +.nolovelyignore +config.json \ No newline at end of file diff --git a/config.lua b/config.lua new file mode 100644 index 0000000..9760892 --- /dev/null +++ b/config.lua @@ -0,0 +1,15 @@ +mod.config.showVanilla = helpers.InputBool("Launch Vanilla button", mod.config.showVanilla) +mod.config.showConsole = helpers.InputBool("Launch with console button", mod.config.showConsole) +mod.config.showConsoleless = helpers.InputBool("Launch without console button", mod.config.showConsoleless) +mod.config.showRelaunch = helpers.InputBool("Relaunch Launcher button", mod.config.showRelaunch) + +imgui.Separator() + +mod.config.useBeatblockPlusStyle = helpers.InputBool("Use BBP style in launcher", mod.config.useBeatblockPlusStyle or false) + +imgui.Separator() + +if bs.states.Launcher and imgui.Button("Open Launcher") then + cs = bs.load("Launcher") + cs:init() +end \ No newline at end of file diff --git a/lovely/prelaunch.toml b/lovely/prelaunch.toml index 2563363..af89869 100644 --- a/lovely/prelaunch.toml +++ b/lovely/prelaunch.toml @@ -12,11 +12,22 @@ sources = ["prelaunch.lua"] [[patches]] [patches.pattern] target = "main.lua" -pattern = "function love.load()" -position = "after" +pattern = "dofile('preload/states.lua')" +position = "at" +payload = "-- dofile('preload/states.lua') -- do it later to maybe prevent mod conflicts crashing the game" +match_indent = true + +[[patches]] +[patches.pattern] +target = "main.lua" +pattern = "cs = bs.load(project.initState)" +position = "at" payload = ''' if not launch then - return + cs = bs.load("Launcher") +else + if bs.states.Menu == nil then dofile('preload/states.lua') end + cs = bs.load(project.initState) -- i'm scared this conflicts with the bbp mod loading end ''' -match_indent = true +match_indent = true \ No newline at end of file diff --git a/mod.json b/mod.json index c4fb913..508159f 100644 --- a/mod.json +++ b/mod.json @@ -1 +1,17 @@ -{"description":"Mod launcher for BeatblockPlus","enabled":true,"config":[],"version":"1.0.0","id":"beatblock-plus-launcher","name":"Beatblock Plus Launcher","author":"erenkarakal"} \ No newline at end of file +{ + "version": "2.0.0", + "description": "Mod launcher for BeatblockPlus", + "enabled": false, + "id": "beatblock-plus-launcher", + "config": { + "useBeatblockPlusStyle": false, + "currentProfile": "Enable All", + "profiles": {}, + "showRelaunch": false, + "showVanilla": true, + "showConsole": true, + "showConsoleless": true + }, + "name": "Beatblock Plus Launcher", + "author": "erenkarakal & Pentatrate" +} \ No newline at end of file diff --git a/prelaunch.lua b/prelaunch.lua index 9b57622..fdcbdad 100644 --- a/prelaunch.lua +++ b/prelaunch.lua @@ -1,84 +1,2 @@ launch = table.concat(arg, " "):find("--launch") - -if not launch then - buttons = {} - padding = 20 - - function addButton(label, callback) - local buttonWidth = love.graphics.getWidth() * 0.85 - local buttonHeight = love.graphics.getHeight() * 0.1 - - table.insert(buttons, { - label = label, - x = (love.graphics.getWidth() - buttonWidth) / 2, - y = (#buttons + 1) * (padding + buttonHeight) + 100, - width = buttonWidth, - height = buttonHeight, - callback = callback - }) - end - - function launchGame(args) - local osName = love.system.getOS() - local command - - if osName == "Windows" then - command = "start beatblock.exe " .. args - elseif osName == "OS X" then - command = "open beatblock.app " .. args .. " &" - else -- assume Linux - command = "./beatblock " .. args .. " &" - end - - love.window.close() - os.execute(command) - love.event.quit() - end - - love.window.setTitle("Select a launch option") - love.graphics.setBackgroundColor(1, 1, 1) -- white - - logo = love.graphics.newImage("assets/title/logo.png") - logo:setFilter('nearest', 'nearest') - - love.graphics.setDefaultFilter('nearest', 'nearest') - love.graphics.setLineStyle('rough') - love.graphics.setLineJoin('miter') - - font = love.graphics.newFont("assets/fonts/DigitalDisco-Thin.ttf", 32) - font:setFilter('nearest', 'nearest', 0) - - addButton("Launch Vanilla", function() - launchGame("--launch --disable-mods") - end) - addButton("Launch Modded", function() - launchGame("--launch") - end) - addButton("Launch Modded Without Console", function() - launchGame("--launch --disable-console") - end) - - function love.mousepressed(x, y) - for _, btn in ipairs(buttons) do - if x >= btn.x and x <= btn.x + btn.width and y >= btn.y and y <= btn.y + btn.height then - btn.callback() - end - end - end - - function love.draw() - love.graphics.setColor(1, 1, 1) - local scale = 1.5 - love.graphics.draw(logo, (love.graphics.getWidth() - logo:getWidth() * scale) / 2, 46, 0, scale, scale) - - for _, btn in ipairs(buttons) do - love.graphics.setColor(0, 0, 0) -- black - love.graphics.rectangle("line", btn.x, btn.y, btn.width, btn.height) - - love.graphics.setFont(font) - love.graphics.printf(btn.label, btn.x, btn.y + (btn.height / 6), btn.width, "center") - end - end - - return -end +print("launch args: " .. table.concat(arg, " ")) \ No newline at end of file diff --git a/states/Launcher.lua b/states/Launcher.lua new file mode 100644 index 0000000..4eeb358 --- /dev/null +++ b/states/Launcher.lua @@ -0,0 +1,239 @@ +local st = Gamestate:new("Launcher") + +function st.moveDirectory(source, target) + love.filesystem.createDirectory(target) + + local files = love.filesystem.getDirectoryItems(source) + for _, file in pairs(files) do + local sourceFilePath = source .. file + local targetFilePath = target .. file + + local contents = love.filesystem.read(sourceFilePath) + if contents then + local targetFile = love.filesystem.newFile(targetFilePath) + targetFile:open("w") + targetFile:write(contents) + targetFile:flush() + love.filesystem.remove(sourceFilePath) + end + end + + love.filesystem.remove(source) +end + +function st:reloadModList() + self.modList = {} + for _, v in pairs(mods) do if st.checkValid(v.id) then table.insert(self.modList, v) end end + table.sort(self.modList, function(a, b) + if self.getModEnabled(a) ~= self.getModEnabled(b) then + return self.getModEnabled(a) + elseif a.name:lower() ~= b.name:lower() then + return a.name:lower() < b.name:lower() + elseif a.author:lower() ~= b.author:lower() then + return a.author:lower() < b.author:lower() + else + return a.id < b.id + end + end) +end + +function st.checkValid(name) + return not ({ ["beatblock-plus"] = true, ["beatblock-plus-launcher"] = true })[name] +end + +function st.getModEnabled(mod) + return mods["beatblock-plus-launcher"].config.profiles[mods["beatblock-plus-launcher"].config.currentProfile][mod.id] +end + +st:setInit(function(self) + self.initing = true + self.size = 1 + self.command = "" + mods["beatblock-plus-launcher"].config.currentProfile = mods["beatblock-plus-launcher"].config.currentProfile or "Enable All" + + self.modList = {} + for _, v in pairs(mods) do if st.checkValid(v.id) then table.insert(self.modList, v) end end + + mods["beatblock-plus-launcher"].config.profiles = mods["beatblock-plus-launcher"].config.profiles or {} + if mods["beatblock-plus-launcher"].config.profiles["Enable All"] == nil then + mods["beatblock-plus-launcher"].config.profiles["Enable All"] = {} + end + for i, mod in ipairs(self.modList) do + for k, v in pairs(mods["beatblock-plus-launcher"].config.profiles) do + if v[mod.id] == nil then v[mod.id] = k == "Enable All" and true or mod.enabled end + end + end + + table.sort(self.modList, function(a, b) + if a.enabled ~= b.enabled then + return a.enabled + elseif a.name:lower() ~= b.name:lower() then + return a.name:lower() < b.name:lower() + elseif a.author:lower() ~= b.author:lower() then + return a.author:lower() < b.author:lower() + else + return a.version < b.version + end + end) +end) + +st:setUpdate(function(self, dt) + if self.doRelaunch then + local lastLaunchArgs = helpers.copy(arg) + local lauchArgsString = table.concat(arg, " ") + local restartGame = false -- restart the game fully + local launchVanilla = false -- dont enable/disable mods + + for k, _ in pairs({ ["--launch"] = true, ["--disable-mods"] = true, ["--disable-console"] = true }) do + if self.doRelaunch[k] then + if k == "--disable-mods" then launchVanilla = true restartGame = true end + if not lauchArgsString:find(k) then + if k ~= "--launch" then restartGame = true end + table.insert(lastLaunchArgs, k) + print("Added arg " .. k) + end + else -- launch without + if k == "--launch" then restartGame = true end -- you are in the launcher and want to restart but dont want to leave + if lauchArgsString:find(k) then + restartGame = true + for i = 1, #lastLaunchArgs do if lastLaunchArgs[i] == k then table.remove(lastLaunchArgs, i) break end end + print("Removed arg " .. k) + end + end + end + + if not launchVanilla then + for i, mod in ipairs(self.modList) do + if mod.enabled ~= self.getModEnabled(mod) then + mod.enabled = self.getModEnabled(mod) + restartGame = true + end + end + end + dpf.saveJson(mods["beatblock-plus-launcher"].path .. "/config.json", mods["beatblock-plus-launcher"].config) + + if restartGame then + local launchArgs = table.concat(lastLaunchArgs, " ") + + local osName = love.system.getOS() + + if osName == "Windows" then + self.command = "start beatblock.exe " .. launchArgs + elseif osName == "OS X" then + self.command = "open beatblock.app " .. launchArgs .. " &" + else -- assume Linux + self.command = "./beatblock " .. launchArgs .. " &" + end + + love.window.close() + self.timer = 20 + self.doRelaunch = nil + else -- nothing much changed, no need to restart + if bs.states.Menu == nil then dofile('preload/states.lua') end + cs = bs.load(project.initState) + cs:init() + end + end + if self.timer then + self.timer = self.timer - dt + if self.timer <= 0 then + self.timer = nil + os.execute(self.command) + love.event.quit() + end + end +end) + +st:setFgDraw(function(self) + if mods["beatblock-plus-launcher"].config.useBeatblockPlusStyle then bbp.gui.pushStyle() end + + helpers.SetNextWindowPos(0, 0, "ImGuiCond_Always") + helpers.SetNextWindowSize(1200, 720, "ImGuiCond_Always") + if imgui.Begin("Launcher", true, bit.bor(imgui.ImGuiWindowFlags_NoMove, imgui.ImGuiWindowFlags_NoResize, imgui.ImGuiWindowFlags_NoTitleBar)) then + local buttons = {} + if mods["beatblock-plus-launcher"].config.showVanilla then table.insert(buttons, { "Launch Vanilla", { ["--launch"] = true, ["--disable-mods"] = true } }) end + if mods["beatblock-plus-launcher"].config.showConsole then table.insert(buttons, { "Launch with console", { ["--launch"] = true } }) end + if mods["beatblock-plus-launcher"].config.showConsoleless then table.insert(buttons, { "Launch without console", { ["--launch"] = true, ["--disable-console"] = true } }) end + if mods["beatblock-plus-launcher"].config.showRelaunch then table.insert(buttons, { "Restart Launcher", {} }) end + + local width = (imgui.GetContentRegionAvail().x - imgui.GetStyle().ItemSpacing.x * (#buttons - 1)) / #buttons + for i = 1, #buttons do + if i ~= 1 then imgui.SameLine() end + local label = buttons[i][1] + local args = buttons[i][2] + if imgui.Button(label, imgui.ImVec2_Float(width, 33 * self.size + imgui.GetStyle().ItemSpacing.y)) then self.doRelaunch = args end + end + + if imgui.BeginTabBar("beatblockPlusLauncherProfiles", imgui.ImGuiTabBarFlags_AutoSelectNewTabs + imgui.ImGuiTabBarFlags_Reorderable) then + for name, data in pairs(mods["beatblock-plus-launcher"].config.profiles) do + if name ~= "Create Profile" then + local notDeleted = ffi.new("bool[1]", { true }) + if imgui.BeginTabItem(name, name ~= "Enable All" and notDeleted or nil, self.initing and mods["beatblock-plus-launcher"].config.currentProfile == name and imgui.ImGuiTabItemFlags_SetSelected or 0) then + if name ~= "Enable All" then + local v = ffi.new("char[?]", 2 ^ 16) + ffi.copy(v, name, #name) + imgui.SetNextItemWidth(-1e-9) + imgui.InputText("##profileName", v, 2 ^ 16, imgui.ImGuiInputTextFlags_AutoSelectAll) + if imgui.IsItemDeactivatedAfterEdit() and name ~= ffi.string(v) and not ({ ["Enable All"] = true, ["New Profile"] = true, ["Create Profile"] = true })[ffi.string(v)] and mods["beatblock-plus-launcher"].config.profiles[ffi.string(v)] == nil then + mods["beatblock-plus-launcher"].config.profiles[ffi.string(v)] = data + mods["beatblock-plus-launcher"].config.profiles[name] = nil + mods["beatblock-plus-launcher"].config.currentProfile = ffi.string(v) + name = ffi.string(v) + self:reloadModList() + end + end + if mods["beatblock-plus-launcher"].config.currentProfile ~= name then + mods["beatblock-plus-launcher"].config.currentProfile = name + self:reloadModList() + end + imgui.EndTabItem(name) + end + if name ~= "Enable All" and not notDeleted[0] then + mods["beatblock-plus-launcher"].config.profiles[name] = nil + if mods["beatblock-plus-launcher"].config.currentProfile == name then + mods["beatblock-plus-launcher"].config.currentProfile = "Enable All" + self:reloadModList() + self.justDeleted = true + end + end + end + end + if imgui.BeginTabItem("Enable All", nil, imgui.ImGuiTabItemFlags_Leading + (self.justDeleted and imgui.ImGuiTabItemFlags_SetSelected or 0)) then imgui.EndTabItem("Enable All") end -- need this to prevent tab deletion and instant recreation + if mods["beatblock-plus-launcher"].config.profiles["New Profile"] == nil and imgui.BeginTabItem("Create Profile", nil, imgui.ImGuiTabItemFlags_Trailing) then + if not self.justDeleted then + mods["beatblock-plus-launcher"].config.profiles["New Profile"] = {} + for i, mod in ipairs(self.modList) do mods["beatblock-plus-launcher"].config.profiles["New Profile"][mod.id] = true end + end + imgui.EndTabItem("Create Profile") + end + self.justDeleted = false + end + + for i, mod in ipairs(self.modList) do + if not self.getModEnabled(mod) then + imgui.PushStyleColor_Vec4(imgui.ImGuiCol_Button, imgui.ImVec4_Float(0.25, 0, 0, 1)) + imgui.PushStyleColor_Vec4(imgui.ImGuiCol_ButtonHovered, imgui.ImVec4_Float(0.35, 0, 0, 1)) + imgui.PushStyleColor_Vec4(imgui.ImGuiCol_ButtonActive, imgui.ImVec4_Float(0.5, 0, 0, 1)) + end + imgui.PushStyleColor_Vec4(imgui.ImGuiCol_Button, imgui.ImVec4_Float(0, 0, 0, 0)) + local pressed = imgui.ImageButton("##imageButton" .. i, mod.icon or sprites.bbp.missing, imgui.ImVec2_Float(73 * self.size, 33 * self.size)) + if mods["beatblock-plus-launcher"].config.currentProfile == "Enable All" then imgui.SetItemTooltip("Profile doesnt allow toggling") end + imgui.PopStyleColor(1) + imgui.SameLine() + local pressed2 = imgui.Button(mod.name .. " (" .. mod.version .. ") by " .. mod.author .. "\n" .. mod.description .. "##".. i, imgui.ImVec2_Float(-1e-9, 33 * self.size + imgui.GetStyle().ItemSpacing.y)) + if mods["beatblock-plus-launcher"].config.currentProfile == "Enable All" then imgui.SetItemTooltip("Profile doesnt allow toggling") end + if not self.getModEnabled(mod) then imgui.PopStyleColor(3) end + + if mods["beatblock-plus-launcher"].config.currentProfile ~= "Enable All" and (pressed or pressed2) then + mods["beatblock-plus-launcher"].config.profiles[mods["beatblock-plus-launcher"].config.currentProfile][mod.id] = not self.getModEnabled(mod) + self:reloadModList() + end + end + imgui.End() + end + if mods["beatblock-plus-launcher"].config.useBeatblockPlusStyle then bbp.gui.popStyle() end + + self.initing = nil +end) + +return st \ No newline at end of file From ef5a4188d207f8d0c2371e1f22f56431c607161a Mon Sep 17 00:00:00 2001 From: Pentatrate <190233437+Pentatrate@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:38:49 +0100 Subject: [PATCH 2/5] Immediate relaunch + Relaunch button in config --- config.lua | 14 +++++++++++--- states/Launcher.lua | 11 ++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/config.lua b/config.lua index 9760892..26ae8f1 100644 --- a/config.lua +++ b/config.lua @@ -9,7 +9,15 @@ mod.config.useBeatblockPlusStyle = helpers.InputBool("Use BBP style in launcher" imgui.Separator() -if bs.states.Launcher and imgui.Button("Open Launcher") then - cs = bs.load("Launcher") - cs:init() +if bs.states.Launcher then + if imgui.Button("Open Launcher") then + cs = bs.load("Launcher") + cs:init() + end + imgui.SameLine() + if imgui.Button("Relaunch") then + cs = bs.load("Launcher") + cs.doRelaunch = {} + cs:init() + end end \ No newline at end of file diff --git a/states/Launcher.lua b/states/Launcher.lua index 4eeb358..9334fe5 100644 --- a/states/Launcher.lua +++ b/states/Launcher.lua @@ -126,22 +126,15 @@ st:setUpdate(function(self, dt) end love.window.close() - self.timer = 20 self.doRelaunch = nil + os.execute(self.command) + love.event.quit() else -- nothing much changed, no need to restart if bs.states.Menu == nil then dofile('preload/states.lua') end cs = bs.load(project.initState) cs:init() end end - if self.timer then - self.timer = self.timer - dt - if self.timer <= 0 then - self.timer = nil - os.execute(self.command) - love.event.quit() - end - end end) st:setFgDraw(function(self) From b2ef3c63358906e0dfb794b5d18b07c46c1ad676 Mon Sep 17 00:00:00 2001 From: Pentatrate <190233437+Pentatrate@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:19:48 +0100 Subject: [PATCH 3/5] Fix state bg being transparent --- states/Launcher.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/states/Launcher.lua b/states/Launcher.lua index 9334fe5..631bc59 100644 --- a/states/Launcher.lua +++ b/states/Launcher.lua @@ -138,6 +138,9 @@ st:setUpdate(function(self, dt) end) st:setFgDraw(function(self) + shuv.resetPal() + color(1) + love.graphics.rectangle('fill', 0, 0, 600, 360) if mods["beatblock-plus-launcher"].config.useBeatblockPlusStyle then bbp.gui.pushStyle() end helpers.SetNextWindowPos(0, 0, "ImGuiCond_Always") From 6d12bd661dd92370411d761588cdcdea237fb8d9 Mon Sep 17 00:00:00 2001 From: Pentatrate <190233437+Pentatrate@users.noreply.github.com> Date: Mon, 29 Dec 2025 20:51:35 +0100 Subject: [PATCH 4/5] 2.0.1 Fixed cursor visual bug --- mod.json | 2 +- states/Launcher.lua | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/mod.json b/mod.json index 508159f..e605a67 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "2.0.1", "description": "Mod launcher for BeatblockPlus", "enabled": false, "id": "beatblock-plus-launcher", diff --git a/states/Launcher.lua b/states/Launcher.lua index 631bc59..3c24f4b 100644 --- a/states/Launcher.lua +++ b/states/Launcher.lua @@ -75,6 +75,8 @@ st:setInit(function(self) return a.version < b.version end end) + + love.mouse.setVisible(true) end) st:setUpdate(function(self, dt) @@ -131,6 +133,11 @@ st:setUpdate(function(self, dt) love.event.quit() else -- nothing much changed, no need to restart if bs.states.Menu == nil then dofile('preload/states.lua') end + if savedata.options.game.customCursorInMenu and (savedata.options.game.cursorMode ~= "default") then + love.mouse.setVisible(false) + else + love.mouse.setVisible(true) + end cs = bs.load(project.initState) cs:init() end From a0ff0523642ed9076ed4fad997faf3069eac2fd1 Mon Sep 17 00:00:00 2001 From: Pentatrate <190233437+Pentatrate@users.noreply.github.com> Date: Mon, 29 Dec 2025 21:13:51 +0100 Subject: [PATCH 5/5] 2.0.2 Enable the launcher by default --- mod.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod.json b/mod.json index e605a67..dc0e2f4 100644 --- a/mod.json +++ b/mod.json @@ -1,7 +1,7 @@ { - "version": "2.0.1", + "version": "2.0.2", "description": "Mod launcher for BeatblockPlus", - "enabled": false, + "enabled": true, "id": "beatblock-plus-launcher", "config": { "useBeatblockPlusStyle": false,