finally make it object oriented and add shim support (nogame 0.9.0 now works)

This commit is contained in:
banepwn 2020-04-04 05:12:07 -04:00
parent f9198fec8b
commit c5dbed728c
9 changed files with 364 additions and 125 deletions

View File

@ -47,12 +47,20 @@
</tr>
<tr>
<td><a href="https://github.com/love2d/love/blob/3053278048cceb7ca71b944517e886804314a246/src/scripts/nogame.lua">LÖVE 10.0 <i>nogame.lua</i></a></td>
<td>blending modes</td>
<td>SpriteBatch</td>
<td><span class="medium">Running</span></td>
<td><span class="bad">Broken</span></td>
<td><span class="bad">Broken</span></td>
<td><span class="bad">Broken</span></td>
</tr>
<tr>
<td><a href="https://bitbucket.org/rude/love/src/63ed6cbfb2853009bb49572d975569d43cba2705/src/scripts/boot.lua">LÖVE 0.9.0 <i>nogame.lua</i></a></td>
<td>blending modes</td>
<td><span class="medium">Running</span></td>
<td><span class="bad">Broken</span></td>
<td><span class="bad">Perfect</span></td>
<td><span class="bad">Perfect</span></td>
</tr>
<tr>
<td><a href="https://github.com/TurtleP/LovePotion/tree/master/nogame">LovePotion <i>nogame<i></a></td>
<td><a href="https://github.com/TurtleP">Jeremy Postelnek</a></td>

30
main.js
View File

@ -2,35 +2,13 @@ window.infatuated = {};
(function() {
"use strict";
function assert(cond, msg) { if (!cond) { throw msg; } }
var images = {};
infatuated.newImage = function(path, b64) {
var image = new Image();
images[path] = image;
if (b64) {
image.src = "data:image/png;base64,"+path;
} else {
image.src = infatuated.game+"/"+path;
}
image.getWidth = function() { return image.width; }
image.getHeight = function() { return image.height; }
return {
"type": "Image",
"path": path,
"getWidth": function() { return image.width; },
"getHeight": function() { return image.height; }
};
}
infatuated.drawImage = function(ctx, obj, x, y) {
if (!obj.type) { return; }
assert(obj.type == "Image", "bad argument #2 to drawImage");
if (images[obj.path].complete) {
ctx.drawImage(images[obj.path], x, y);
}
infatuated.newImage = function() {
return new Image();
}
var url = new URL(location.href);
var game = url.searchParams.get("game");
infatuated.game = game ? "game-"+game : "game";
var shim = url.searchParams.get("shim");
infatuated.shim = shim ? shim : false;
})();

128
main.lua
View File

@ -61,10 +61,12 @@ infatuated.conf = {
}
}
class = require "middleclass"
infatuated.classes = {}
local js = require "js"
infatuated.js = js
local window = js.global
infatuated.path = window.infatuated.game;
local document = window.document
local canvas = document:getElementById("infatuated-canvas")
infatuated.canvas = canvas
@ -109,6 +111,9 @@ function infatuated.msgbox:hide()
end
--infatuated.msgbox:show("sample title", "the quick brown fox", {"a", "b", "c"}, oldprint)
infatuated.path = window.infatuated.game
infatuated.shim = window.infatuated.shim
print("Running game from path %q", path)
local function errorprint(...)
@ -139,7 +144,6 @@ function infatuated.parseColor(r, g, b, a)
assert(type(g) == "number", "bad argument #2 to parseColor")
assert(type(b) == "number", "bad argument #3 to parseColor")
assert(type(a) == "number", "bad argument #4 to parseColor")
--return {r, g, b, a}, string.format("rgba(%f, %f, %f, %f)", r, g, b, a)
return {r, g, b, a}, string.format("rgba(%f, %f, %f, %f)", r*255, g*255, b*255, a)
else
error("bad argument #1 to parseColor")
@ -209,66 +213,76 @@ end
print("Coroutining now")
infatuated.thread = coroutine.create(function()
require "modules.data"
require "modules.event"
require "modules.filesystem"
require "modules.graphics"
require "modules.handlers"
require "modules.image"
require "modules.math"
require "modules.system"
require "modules.timer"
require "modules.window"
love.run()
local oldrequire = require
infatuated.require = oldrequire
function require(path)
assert(type(path) == "string", "bad argument #1 to require")
if path == "love" then return love end
return oldrequire(infatuated.path.."."..path)
end
print("Importing conf")
xpcall(function()
require "modules.data"
require "modules.event"
require "modules.filesystem"
require "modules.graphics"
require "modules.handlers"
require "modules.image"
require "modules.math"
require "modules.system"
require "modules.timer"
require "modules.window"
love.run()
if infatuated.shim then
print("Shimming %s", infatuated.shim)
infatuated.shim = infatuated.shim:gsub("%.", "-")
require("shims."..infatuated.shim)
end
local oldrequire = require
infatuated.require = oldrequire
function require(path)
assert(type(path) == "string", "bad argument #1 to require")
if path == "love" then return love end
return oldrequire(infatuated.path.."."..path)
end
print("Importing conf")
local status, errmsg = pcall(oldrequire, infatuated.path..".conf")
if not status and not errmsg:find("^module '.-%.conf' not found:") then
infatuated.crash(errmsg)
end
local status, errmsg = pcall(love.conf, infatuated.conf)
if not status then infatuated.crash(errmsg) end
local status, errmsg = pcall(oldrequire, infatuated.path..".conf")
if not status and not errmsg:find("^module '.-%.conf' not found:") then
infatuated.crash(errmsg)
end
local status, errmsg = pcall(love.conf, infatuated.conf)
if not status then infatuated.crash(errmsg) end
print("Importing main")
print("Importing main")
local status, errmsg = pcall(oldrequire, infatuated.path..".main")
if not status then infatuated.crash(errmsg) end
local status, errmsg = pcall(love.run)
if not status then
infatuated.crash(errmsg)
else
infatuated.loopGuest = errmsg
end
print("Starting event loop")
infatuated.dt1 = window.performance:now()
local function resume()
coroutine.resume(infatuated.thread)
end
while true do
local dt2 = window.performance:now()
infatuated.dt = (dt2-infatuated.dt1)/1000
infatuated.dt1 = dt2
local status, errmsg = pcall(infatuated.loopGuest)
local status, errmsg = pcall(oldrequire, infatuated.path..".main")
if not status then infatuated.crash(errmsg) end
local status, errmsg = pcall(love.run)
if not status then
infatuated.crash(errmsg)
elseif errmsg then
print("Exited with code %d", errmsg or -1)
return
else
infatuated.loopGuest = errmsg
end
window:requestAnimationFrame(resume)
coroutine.yield()
end
print("Starting event loop")
infatuated.dt1 = window.performance:now()
local function resume()
coroutine.resume(infatuated.thread)
end
while true do
local dt2 = window.performance:now()
infatuated.dt = (dt2-infatuated.dt1)/1000
infatuated.dt1 = dt2
local status, errmsg = pcall(infatuated.loopGuest)
if not status then
infatuated.crash(errmsg)
elseif errmsg then
print("Exited with code %d", errmsg or -1)
return
end
window:requestAnimationFrame(resume)
coroutine.yield()
end
end, function(errmsg)
infatuated.crash(errmsg)
end)
end)
coroutine.resume(infatuated.thread)

183
middleclass.lua Normal file
View File

@ -0,0 +1,183 @@
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}
local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]
if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:new(...) end
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,
initialize = function(self, ...) end,
isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,
static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
subclassed = function(self, other) end,
isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
return middleclass

View File

@ -1,12 +1,13 @@
love.filesystem = {}
local FileData = class("FileData")
infatuated.classes.FileData = FileData
function FileData:initialize(contents, name)
self.contents = contents
self.name = name
end
function love.filesystem.newFileData(contents, name)
if name then
-- TODO: use an actual class
return {
type = "FileData",
data = contents,
name = name
}
return infatuated.classes.FileData:new(contents, name)
else
return nil, "not implemented"
end

View File

@ -85,33 +85,71 @@ function love.graphics.setLineWidth(width)
infatuated.lineWidth = width
end
function love.graphics.newSpriteBatch(image, maxsprites, usage)
maxsprites = maxsprites or 1000
assert(type(image) == "userdata" and image.type == "Image", "bad argument #1 to newSpriteBatch (expected Image)")
assert(type(maxsprites) == "number" and maxsprites > 0, "bad argument #2 to newSpriteBatch")
return {
type = "SpriteBatch",
maxsprites = maxsprites,
getBufferSize = function(self) return self.maxsprites end,
setBufferSize = function(self, maxsprites)
assert(type(maxsprites) == "number" and maxsprites > 0, "bad argument #2 to setBufferSize (expected number)")
self.maxsprites = maxsprites
end,
add = function() end,
flush = function() end,
clear = function() end
}
local SpriteBatch = class("SpriteBatch")
infatuated.classes.SpriteBatch = SpriteBatch
function SpriteBatch:initialize(image, max)
self._max = max
end
function love.graphics.newImage(path)
if path.type == "ImageData" then
return newImage(nil, path, true)
function SpriteBatch:getBufferSize()
return self._max
end
function SpriteBatch:setBufferSize(maxsprites)
assert(type(maxsprites) == "number", "bad argument #1 to setBufferSize (expected number)")
assert(maxsprites > 0, "maxsprites must be greater than 0")
self._max = maxsprites
end
function SpriteBatch:add() end
function SpriteBatch:flush() end
function SpriteBatch:clear() end
function love.graphics.newSpriteBatch(image, maxsprites)
maxsprites = maxsprites or 1000
assert(type(image) == "table" and tostring(image.class) == "class Image", "bad argument #1 to newSpriteBatch (expected Image)")
assert(type(maxsprites) == "number", "bad argument #2 to newSpriteBatch (expected number)")
assert(maxsprites > 0, "maxsprites must be greater than 0")
return SpriteBatch:new(image, maxsprites, usage)
end
local Image = class("Image")
infatuated.classes.Image = Image
function Image:initialize(a)
if type(a) == "string" then
infatuated.print("Loading image from %q", a)
local image = newImage()
a = ("/"..a):gsub("/%.%.", "/")
image.src = "game"..a
image.onload = function(self)
infatuated.print("Loaded image")
coroutine.resume(infatuated.thread)
end
coroutine.yield()
self.image = image
else
assert(type(path) == "string", "bad argument #1 to newImage")
return newImage(nil, path)
infatuated.print("Loading image from ImageData")
local image = newImage()
image.src = "data:image/png;base64,"..a.data
image.onload = function(self)
infatuated.print("Loaded image")
coroutine.resume(infatuated.thread)
end
coroutine.yield()
self.image = image
end
end
function Image:getWidth()
return self.image.width
end
function Image:getHeight()
return self.image.height
end
function love.graphics.newImage(a)
local t = type(a)
assert(t == "string" or (t == "table" and tostring(a.class) == "class ImageData"), "bad argument #1 to newImage (expected string or ImageData)")
return Image:new(a)
end
function love.graphics.draw(obj, x, y, r, sx, sy, ox, oy)
if type(obj) == "table" and tostring(obj.class) == "class SpriteBatch" then return end
assert(type(obj) == "table" and tostring(obj.class) == "class Image", "bad argument #1 to draw (expected Image)")
r = r or 0
ox = ox or 0
oy = oy or 0
@ -126,7 +164,7 @@ function love.graphics.draw(obj, x, y, r, sx, sy, ox, oy)
ctx:rotate(r)
-- color tinting looks HARD!
ctx.globalAlpha = infatuated.color[4]
drawImage(nil, ctx, obj, -ox, -oy)
ctx:drawImage(obj.image, -ox, -oy)
ctx:restore()
end
function love.graphics.print(text, x, y, r, sx, sy, ox, oy, kx, ky)

View File

@ -1,12 +1,13 @@
love.image = {}
function love.image.newImageData(w, h, f, d)
if h or f or d then
error("not implemented")
else
-- TODO: use an actual class
return {
type = "ImageData",
content = w
}
end
local ImageData = class("ImageData")
infatuated.classes.ImageData = ImageData
function ImageData:initialize(a)
self.data = a.contents
end
function love.image.newImageData(a, b, c, d)
assert(type(a) == "table" and tostring(a.class) == "class FileData", "bad argument #1 to newImageData (expected FileData)")
if b or c or d then
error("not implemented")
end
return ImageData:new(a)
end

View File

@ -27,10 +27,13 @@ local platformIDs = {
win64 = "Windows",
wince = "Windows" -- amirite
}
function love.system.getOS()
local function getOS()
if useragent:find("android") then return "Android" end -- fucking "Linux 50m3_r4nd0m_5h1t"
if useragent:find("iphone") then return "iOS" end -- fucking "general Mobile Device"
return platformIDs[platform] or platform:find("mobile") and "Android" or "Windows"
end
love._os = love.system.getOS() -- 0.8.0 compatibility
infatuated.print("Detected OS as %s", love._os)
infatuated.os = getOS()
infatuated.print("Detected OS as %s", infatuated.os)
function love.system.getOS()
return infatuated.os
end

13
shims/0-9-0.lua Normal file
View File

@ -0,0 +1,13 @@
function infatuated.parseColor(r, g, b, a)
if type(r) == "table" then
return infatuated.parseColor(r[1], r[2], r[3], r[4])
elseif type(r) == "number" then
a = a or 1
assert(type(g) == "number", "bad argument #2 to parseColor")
assert(type(b) == "number", "bad argument #3 to parseColor")
assert(type(a) == "number", "bad argument #4 to parseColor")
return {r, g, b, a}, string.format("rgba(%f, %f, %f, %f)", r, g, b, a)
else
error("bad argument #1 to parseColor")
end
end