Skip to content

Instantly share code, notes, and snippets.

@markandgo
Last active December 30, 2021 05:29
Show Gist options
  • Save markandgo/dfa7d4c1fc7b81da2ed5 to your computer and use it in GitHub Desktop.
Save markandgo/dfa7d4c1fc7b81da2ed5 to your computer and use it in GitHub Desktop.
Loadable love in Lua
package.cpath = package.cpath..';C:/Program Files/LOVE/?.dll;./?.dll'
-- Modification of boot.lua
-- This file loads love but does not execute love.run or open a window
-- This file must be located in the root game directory
-- The root game directory must also be the working directory
-- Make sure love exists.
local love = require("love")
-- Used for setup:
love.path = {}
love.arg = {}
-- Replace any \ with /.
function love.path.normalslashes(p)
return string.gsub(p, "\\", "/")
end
-- Makes sure there is a slash at the end
-- of a path.
function love.path.endslash(p)
if string.sub(p, string.len(p)-1) ~= "/" then
return p .. "/"
else
return p
end
end
-- Checks whether a path is absolute or not.
function love.path.abs(p)
local tmp = love.path.normalslashes(p)
-- Path is absolute if it starts with a "/".
if string.find(tmp, "/") == 1 then
return true
end
-- Path is absolute if it starts with a
-- letter followed by a colon.
if string.find(tmp, "%a:") == 1 then
return true
end
-- Relative.
return false
end
-- Converts any path into a full path.
function love.path.getfull(p)
if love.path.abs(p) then
return love.path.normalslashes(p)
end
local cwd = love.filesystem.getWorkingDirectory()
cwd = love.path.normalslashes(cwd)
cwd = love.path.endslash(cwd)
-- Construct a full path.
local full = cwd .. love.path.normalslashes(p)
-- Remove trailing /., if applicable
return full:match("(.-)/%.$") or full
end
-- Returns the leaf of a full path.
function love.path.leaf(p)
p = love.path.normalslashes(p)
local a = 1
local last = p
while a do
a = string.find(p, "/", a+1)
if a then
last = string.sub(p, a+1)
end
end
return last
end
-- Finds the key in the table with the lowest integral index. The lowest
-- will typically the executable, for instance "lua5.1.exe".
function love.arg.getLow(a)
local m = math.huge
for k,v in pairs(a) do
if k < m then
m = k
end
end
return a[m]
end
love.arg.options = {
console = { a = 0 },
fused = {a = 0 },
game = { a = 1 }
}
function love.arg.parse_option(m, i)
m.set = true
if m.a > 0 then
m.arg = {}
for j=i,i+m.a-1 do
table.insert(m.arg, arg[j])
i = j
end
end
return i
end
function love.arg.parse_options()
local game
local argc = #arg
for i=1,argc do
-- Look for options.
local s, e, m = string.find(arg[i], "%-%-(.+)")
if m and love.arg.options[m] then
i = love.arg.parse_option(love.arg.options[m], i+1)
elseif not game then
game = i
end
end
if not love.arg.options.game.set then
love.arg.parse_option(love.arg.options.game, game or 0)
end
end
function love.createhandlers()
-- Standard callback handlers.
love.handlers = setmetatable({
keypressed = function (b, u)
if love.keypressed then return love.keypressed(b, u) end
end,
keyreleased = function (b)
if love.keyreleased then return love.keyreleased(b) end
end,
textinput = function (t)
if love.textinput then return love.textinput(t) end
end,
textedit = function (t,s,l)
if love.textedit then return love.textedit(t,s,l) end
end,
mousemoved = function (x,y,xrel,yrel)
if love.mousemoved then return love.mousemoved(x,y,xrel,yrel) end
end,
mousepressed = function (x,y,b)
if love.mousepressed then return love.mousepressed(x,y,b) end
end,
mousereleased = function (x,y,b)
if love.mousereleased then return love.mousereleased(x,y,b) end
end,
joystickpressed = function (j,b)
if love.joystickpressed then return love.joystickpressed(j,b) end
end,
joystickreleased = function (j,b)
if love.joystickreleased then return love.joystickreleased(j,b) end
end,
joystickaxis = function (j,a,v)
if love.joystickaxis then return love.joystickaxis(j,a,v) end
end,
joystickhat = function (j,h,v)
if love.joystickhat then return love.joystickhat(j,h,v) end
end,
gamepadpressed = function (j,b)
if love.gamepadpressed then return love.gamepadpressed(j,b) end
end,
gamepadreleased = function (j,b)
if love.gamepadreleased then return love.gamepadreleased(j,b) end
end,
gamepadaxis = function (j,a,v)
if love.gamepadaxis then return love.gamepadaxis(j,a,v) end
end,
joystickadded = function (j)
if love.joystickadded then return love.joystickadded(j) end
end,
joystickremoved = function(j)
if love.joystickremoved then return love.joystickremoved(j) end
end,
focus = function (f)
if love.focus then return love.focus(f) end
end,
mousefocus = function (f)
if love.mousefocus then return love.mousefocus(f) end
end,
visible = function (v)
if love.visible then return love.visible(v) end
end,
quit = function ()
return
end,
threaderror = function (t, err)
if love.threaderror then return love.threaderror(t, err) end
end,
resize = function(w, h)
if love.resize then return love.resize(w, h) end
end,
}, {
__index = function(self, name)
error("Unknown event: " .. name)
end,
})
end
local function uridecode(s)
return s:gsub("%%%x%x", function(str)
return string.char(tonumber(str:sub(2), 16))
end)
end
local no_game_code = false
-- This can't be overriden.
function love.boot()
-- This is absolutely needed.
require("love.filesystem")
-- love.arg.parse_options()
-- local o = love.arg.options
-- local arg0 = love.arg.getLow(arg)
local arg0 = love.filesystem.getWorkingDirectory()
love.filesystem.init(arg0)
-- Is this one of those fancy "fused" games?
local can_has_game = pcall(love.filesystem.setSource, arg0)
local is_fused_game = can_has_game
if love.arg.options.fused.set then
is_fused_game = true
end
love.filesystem.setFused(is_fused_game)
--[[
local identity = ""
if not can_has_game and o.game.set and o.game.arg[1] then
local nouri = o.game.arg[1]
if nouri:sub(1, 7) == "file://" then
nouri = uridecode(nouri:sub(8))
end
local full_source = love.path.getfull(nouri)
can_has_game = pcall(love.filesystem.setSource, full_source)
-- Use the name of the source .love as the identity for now.
identity = love.path.leaf(full_source)
else
-- Use the name of the exe as the identity for now.
identity = love.path.leaf(arg0)
end
--]]
identity = love.path.leaf(arg0)
identity = identity:gsub("^([%.]+)", "") -- strip leading "."'s
identity = identity:gsub("%.([^%.]+)$", "") -- strip extension
identity = identity:gsub("%.", "_") -- replace remaining "."'s with "_"
identity = #identity > 0 and identity or "lovegame"
-- When conf.lua is initially loaded, the main source should be checked
-- before the save directory (the identity should be appended.)
pcall(love.filesystem.setIdentity, identity, true)
if can_has_game and not (love.filesystem.isFile("main.lua") or love.filesystem.isFile("conf.lua")) then
no_game_code = true
end
if not can_has_game then
love.nogame()
end
end
function love.init()
-- Create default configuration settings.
-- NOTE: Adding a new module to the modules list
-- will NOT make it load, see below.
local c = {
title = "Untitled",
version = love._version,
window = {
width = 800,
height = 600,
x = nil,
y = nil,
minwidth = 1,
minheight = 1,
fullscreen = false,
fullscreentype = "normal",
display = 1,
vsync = true,
msaa = 0,
fsaa = 0, -- For backward-compatibility. TODO: remove!
borderless = false,
resizable = false,
centered = true,
highdpi = false,
srgb = false,
},
modules = {
event = true,
keyboard = true,
mouse = true,
timer = true,
joystick = true,
image = true,
graphics = true,
audio = true,
math = true,
physics = true,
sound = true,
system = true,
font = true,
thread = true,
window = true,
},
console = false, -- Only relevant for windows.
identity = false,
appendidentity = false,
}
-- Console hack, part 1.
local openedconsole = false
if love.arg.options.console.set and love._openConsole then
love._openConsole()
openedconsole = true
end
-- If config file exists, load it and allow it to update config table.
if not love.conf and love.filesystem and love.filesystem.isFile("conf.lua") then
require("conf")
end
-- Yes, conf.lua might not exist, but there are other ways of making
-- love.conf appear, so we should check for it anyway.
local confok, conferr
if love.conf then
confok, conferr = pcall(love.conf, c)
-- If love.conf errors, we'll trigger the error after loading modules so
-- the error message can be displayed in the window.
end
-- Console hack, part 2.
if c.console and love._openConsole and not openedconsole then
love._openConsole()
end
-- Gets desired modules.
for k,v in ipairs{
"thread",
"timer",
"event",
"keyboard",
"joystick",
"mouse",
"sound",
"system",
"audio",
"image",
"font",
"window",
"graphics",
"math",
"physics",
} do
if c.modules[v] then
require("love." .. v)
end
end
if love.event then
love.createhandlers()
end
if not confok and conferr then
error(conferr)
end
-- Setup window here.
--[[
if c.window and c.modules.window then
assert(love.window.setMode(c.window.width, c.window.height,
{
fullscreen = c.window.fullscreen,
fullscreentype = c.window.fullscreentype,
vsync = c.window.vsync,
fsaa = c.window.fsaa, -- For backward-compatibility. TODO: remove!
msaa = c.window.msaa,
resizable = c.window.resizable,
minwidth = c.window.minwidth,
minheight = c.window.minheight,
borderless = c.window.borderless,
centered = c.window.centered,
display = c.window.display,
highdpi = c.window.highdpi,
srgb = c.window.srgb,
x = c.window.x,
y = c.window.y,
}), "Could not set window mode")
love.window.setTitle(c.window.title or c.title)
if c.window.icon then
assert(love.image, "If an icon is set in love.conf, love.image has to be loaded!")
love.window.setIcon(love.image.newImageData(c.window.icon))
end
end
--]]
-- Our first timestep, because window creation can take some time
if love.timer then
love.timer.step()
end
if love.filesystem then
love.filesystem.setIdentity(c.identity or love.filesystem.getIdentity(), c.appendidentity)
if love.filesystem.isFile("main.lua") then
require("main")
end
end
if no_game_code then
error("No code to run\nYour game might be packaged incorrectly\nMake sure main.lua is at the top level of the zip")
end
-- Check the version
local compat = false
c.version = tostring(c.version)
for i, v in ipairs(love._version_compat) do
if c.version == v then
compat = true
break
end
end
if not compat then
local major, minor, revision = c.version:match("^(%d+)%.(%d+)%.(%d+)$")
if (not major or not minor or not revision) or (major ~= love._version_major and minor ~= love._version_minor) then
local msg = "This game was made for a different version of LÖVE.\n"..
"It may not be not be compatible with the running version ("..love._version..")."
print(msg)
local can_display = love.window and love.window.isCreated()
can_display = can_display and love.graphics and love.graphics.isCreated()
if can_display and love.timer and love.event then
love.graphics.setBackgroundColor(89, 157, 220)
love.graphics.origin()
local start = love.timer.getTime()
while love.timer.getTime() < start + 4 do
love.event.pump()
love.graphics.clear()
love.graphics.print(msg, 70, 70)
love.graphics.present()
love.timer.sleep(1/20)
end
love.graphics.setBackgroundColor(0, 0, 0)
end
end
end
end
function love.run()
if love.math then
love.math.setRandomSeed(os.time())
for i=1,3 do love.math.random() end
end
if love.load then love.load(arg) end
-- We don't want the first frame's dt to include time taken by love.load.
if love.timer then love.timer.step() end
local dt = 0
-- Main loop time.
while true do
-- Process events.
if love.event then
love.event.pump()
for e,a,b,c,d in love.event.poll() do
if e == "quit" then
if not love.quit or not love.quit() then
return
end
end
love.handlers[e](a,b,c,d)
end
end
-- Update dt, as we'll be passing it to update
if love.timer then
love.timer.step()
dt = love.timer.getDelta()
end
-- Call update and draw
if love.update then love.update(dt) end -- will pass 0 if love.timer is disabled
if love.window and love.graphics and love.window.isCreated() then
love.graphics.clear()
love.graphics.origin()
if love.draw then love.draw() end
love.graphics.present()
end
if love.timer then love.timer.sleep(0.001) end
end
end
local function deferErrhand(...)
local handler = love.errhand or error_printer
return handler(...)
end
-----------------------------------------------------------
-- Error screen.
-----------------------------------------------------------
local debug, print = debug, print
local function error_printer(msg, layer)
print((debug.traceback("Error: " .. tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", "")))
end
function love.errhand(msg)
msg = tostring(msg)
error_printer(msg, 2)
if not love.window or not love.graphics or not love.event then
return
end
if not love.graphics.isCreated() or not love.window.isCreated() then
local success, status = pcall(love.window.setMode, 800, 600)
if not success or not status then
return
end
end
-- Reset state.
if love.mouse then
love.mouse.setVisible(true)
love.mouse.setGrabbed(false)
end
if love.joystick then
-- Stop all joystick vibrations.
for i,v in ipairs(love.joystick.getJoysticks()) do
v:setVibration()
end
end
if love.audio then love.audio.stop() end
love.graphics.reset()
local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
local sRGB = select(3, love.window.getMode()).srgb
if sRGB and love.math then
love.graphics.setBackgroundColor(love.math.gammaToLinear(89, 157, 220))
else
love.graphics.setBackgroundColor(89, 157, 220)
end
love.graphics.setColor(255, 255, 255, 255)
local trace = debug.traceback()
love.graphics.clear()
love.graphics.origin()
local err = {}
table.insert(err, "Error\n")
table.insert(err, msg.."\n\n")
for l in string.gmatch(trace, "(.-)\n") do
if not string.match(l, "boot.lua") then
l = string.gsub(l, "stack traceback:", "Traceback\n")
table.insert(err, l)
end
end
local p = table.concat(err, "\n")
p = string.gsub(p, "\t", "")
p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
local function draw()
local pos = love.window.toPixels(70)
love.graphics.clear()
love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
love.graphics.present()
end
while true do
love.event.pump()
for e, a, b, c in love.event.poll() do
if e == "quit" then
return
end
if e == "keypressed" and a == "escape" then
return
end
end
draw()
if love.timer then
love.timer.sleep(0.1)
end
end
end
require 'nogame'
-----------------------------------------------------------
-- The root of all calls.
-----------------------------------------------------------
--[[
return function()
local result = xpcall(love.boot, error_printer)
if not result then return 1 end
local result = xpcall(love.init, deferErrhand)
if not result then return 1 end
-- local result, retval = xpcall(love.run, deferErrhand)
-- if not result then return 1 end
return tonumber(retval) or 0
end
--]]
xpcall(love.boot, error_printer)
xpcall(love.init, deferErrhand)
return love
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment