From 75876607e92d215f1e0879c0e81cbbd1f41ca032 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 19 Sep 2022 19:15:08 +0300 Subject: [PATCH] init commit - move things over from uranium-template to uranium-core --- README.md | 34 +++ actors.xml | 6 + main.xml | 528 ++++++++++++++++++++++++++++++++++++++++++++ stdlib/bitop.lua | 343 ++++++++++++++++++++++++++++ stdlib/color.lua | 300 +++++++++++++++++++++++++ stdlib/easable.lua | 80 +++++++ stdlib/easable2.lua | 90 ++++++++ stdlib/ease.lua | 338 ++++++++++++++++++++++++++++ stdlib/index.lua | 10 + stdlib/input.lua | 39 ++++ stdlib/rng.lua | 172 +++++++++++++++ stdlib/util.lua | 157 +++++++++++++ stdlib/uwuify.lua | 23 ++ stdlib/vector2D.lua | 142 ++++++++++++ typings.lua | 85 +++++++ 15 files changed, 2347 insertions(+) create mode 100644 README.md create mode 100644 actors.xml create mode 100644 main.xml create mode 100644 stdlib/bitop.lua create mode 100644 stdlib/color.lua create mode 100644 stdlib/easable.lua create mode 100644 stdlib/easable2.lua create mode 100644 stdlib/ease.lua create mode 100644 stdlib/index.lua create mode 100644 stdlib/input.lua create mode 100644 stdlib/rng.lua create mode 100644 stdlib/util.lua create mode 100644 stdlib/uwuify.lua create mode 100644 stdlib/vector2D.lua create mode 100644 typings.lua diff --git a/README.md b/README.md new file mode 100644 index 0000000..45ea408 --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +#### This is the development-oriented README. If you're looking to learn how to install and use the template, see [MANUAL.MD](/MANUAL.md). + +--- + +
+ +
+

+ + Uranium Template +

+
+ +
+ +**Uranium Template** is a Love2D-inspired NotITG game development template, focusing on keeping things as **Lua-pure** as possible with enough abstractions to make you feel like you're not dealing with Stepmania jank at all. + +But you probably already knew that; how do you develop this darn thing? + +## Installation + +Installation follows the same steps as [the manual installation](/MANUAL.md#installation) - extract as a modfile, run as a modfile. + +## Distribution + +During distribution, there are a couple of files that should be left out: + +- This very `README.md` - keeping it will be a little confusing, I think! + +## Development + +Everything related to the core functionality - loading actors, callbacks - goes in `template/main.xml`. It's not the most convinient, but I want to eventually move this out to its own Lua file. + +Everything related to the standard library should go into `template/stdlib/` - all files there are manually required in `template/stdlib/index.lua`. \ No newline at end of file diff --git a/actors.xml b/actors.xml new file mode 100644 index 0000000..2adbbe7 --- /dev/null +++ b/actors.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/main.xml b/main.xml new file mode 100644 index 0000000..333fc5a --- /dev/null +++ b/main.xml @@ -0,0 +1,528 @@ + + + + \ No newline at end of file diff --git a/stdlib/bitop.lua b/stdlib/bitop.lua new file mode 100644 index 0000000..a40b31c --- /dev/null +++ b/stdlib/bitop.lua @@ -0,0 +1,343 @@ +local M = {_TYPE='module', _NAME='bitop.funcs', _VERSION='1.0-0'} + +local floor = math.floor + +local MOD = math.pow(2, 32) +local MODM = MOD-1 + +local function memoize(f) + + local mt = {} + local t = setmetatable({}, mt) + + function mt:__index(k) + local v = f(k) + t[k] = v + return v + end + + return t +end + +local function make_bitop_uncached(t, m) + local function bitop(a, b) + local res,p = 0,1 + while a ~= 0 and b ~= 0 do + local am, bm = a%m, b%m + res = res + t[am][bm]*p + a = (a - am) / m + b = (b - bm) / m + p = p*m + end + res = res + (a+b) * p + return res + end + return bitop +end + +local function make_bitop(t) + local op1 = make_bitop_uncached(t, math.pow(2, 1)) + local op2 = memoize(function(a) + return memoize(function(b) + return op1(a, b) + end) + end) + return make_bitop_uncached(op2, math.pow(2, (t.n or 1))) +end + +-- ok? probably not if running on a 32-bit int Lua number type platform +function M.tobit(x) + return x % math.pow(2, 32) +end + +M.bxor = make_bitop {[0]={[0]=0,[1]=1},[1]={[0]=1,[1]=0}, n=4} +local bxor = M.bxor + +function M.bnot(a) return MODM - a end +local bnot = M.bnot + +function M.band(a,b) return ((a+b) - bxor(a,b))/2 end +local band = M.band + +function M.bor(a,b) return MODM - band(MODM - a, MODM - b) end +local bor = M.bor + +local lshift, rshift -- forward declare + +function M.rshift(a,disp) -- Lua5.2 insipred + if disp < 0 then return lshift(a,-disp) end + return floor(a % math.pow(2, 32) / math.pow(2, disp)) +end +rshift = M.rshift + +function M.lshift(a,disp) -- Lua5.2 inspired + if disp < 0 then return rshift(a,-disp) end + return (a * math.pow(2, disp)) % math.pow(2, 32) +end +lshift = M.lshift + +function M.tohex(x, n) -- BitOp style + n = n or 8 + local up + if n <= 0 then + if n == 0 then return '' end + up = true + n = - n + end + x = band(x, math.pow(16, n-1)) + return ('%0'..n..(up and 'X' or 'x')):format(x) +end +local tohex = M.tohex + +function M.extract(n, field, width) -- Lua5.2 inspired + width = width or 1 + return band(rshift(n, field), math.pow(2, width-1)) +end +local extract = M.extract + +function M.replace(n, v, field, width) -- Lua5.2 inspired + width = width or 1 + local mask1 = math.pow(2, width-1) + v = band(v, mask1) -- required by spec? + local mask = bnot(lshift(mask1, field)) + return band(n, mask) + lshift(v, field) +end +local replace = M.replace + +function M.bswap(x) -- BitOp style + local a = band(x, 0xff); x = rshift(x, 8) + local b = band(x, 0xff); x = rshift(x, 8) + local c = band(x, 0xff); x = rshift(x, 8) + local d = band(x, 0xff) + return lshift(lshift(lshift(a, 8) + b, 8) + c, 8) + d +end +local bswap = M.bswap + +function M.rrotate(x, disp) -- Lua5.2 inspired + disp = disp % 32 + local low = band(x, math.pow(2, disp-1)) + return rshift(x, disp) + lshift(low, 32-disp) +end +local rrotate = M.rrotate + +function M.lrotate(x, disp) -- Lua5.2 inspired + return rrotate(x, -disp) +end +local lrotate = M.lrotate + +M.rol = M.lrotate -- LuaOp inspired +M.ror = M.rrotate -- LuaOp insipred + + +function M.arshift(x, disp) -- Lua5.2 inspired + local z = rshift(x, disp) + if x >= 0x80000000 then z = z + lshift(math.pow(2, disp-1), 32-disp) end + return z +end +local arshift = M.arshift + +function M.btest(x, y) -- Lua5.2 inspired + return band(x, y) ~= 0 +end + +-- +-- Start Lua 5.2 "bit32" compat section. +-- + +M.bit32 = {} -- Lua 5.2 'bit32' compatibility + + +local function bit32_bnot(x) + return (-1 - x) % MOD +end +M.bit32.bnot = bit32_bnot + +-- something here causes a syntax error so im just commenting out since i dont need it anyways +--[[ +local function bit32_bxor(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = bxor(a, b) + if c then + z = bit32_bxor(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return 0 + end +end +M.bit32.bxor = bit32_bxor + +local function bit32_band(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = ((a+b) - bxor(a,b)) / 2 + if c then + z = bit32_band(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return MODM + end +end +M.bit32.band = bit32_band + +local function bit32_bor(a, b, c, ...) + local z + if b then + a = a % MOD + b = b % MOD + z = MODM - band(MODM - a, MODM - b) + if c then + z = bit32_bor(z, c, ...) + end + return z + elseif a then + return a % MOD + else + return 0 + end +end +M.bit32.bor = bit32_bor + +function M.bit32.btest(...) + return bit32_band(...) ~= 0 +end + +function M.bit32.lrotate(x, disp) + return lrotate(x % MOD, disp) +end + +function M.bit32.rrotate(x, disp) + return rrotate(x % MOD, disp) +end + +function M.bit32.lshift(x,disp) + if disp > 31 or disp < -31 then return 0 end + return lshift(x % MOD, disp) +end + +function M.bit32.rshift(x,disp) + if disp > 31 or disp < -31 then return 0 end + return rshift(x % MOD, disp) +end + +function M.bit32.arshift(x,disp) + x = x % MOD + if disp >= 0 then + if disp > 31 then + return (x >= 0x80000000) and MODM or 0 + else + local z = rshift(x, disp) + if x >= 0x80000000 then z = z + lshift(math.pow(2, disp-1), 32-disp) end + return z + end + else + return lshift(x, -disp) + end +end + +function M.bit32.extract(x, field, ...) + local width = ... or 1 + if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end + x = x % MOD + return extract(x, field, ...) +end + +function M.bit32.replace(x, v, field, ...) + local width = ... or 1 + if field < 0 or field > 31 or width < 0 or field+width > 32 then error 'out of range' end + x = x % MOD + v = v % MOD + return replace(x, v, field, ...) +end + + +-- +-- Start LuaBitOp "bit" compat section. +-- + +M.bit = {} -- LuaBitOp "bit" compatibility + +function M.bit.tobit(x) + x = x % MOD + if x >= 0x80000000 then x = x - MOD end + return x +end +local bit_tobit = M.bit.tobit + +function M.bit.tohex(x, ...) + return tohex(x % MOD, ...) +end + +function M.bit.bnot(x) + return bit_tobit(bnot(x % MOD)) +end + +local function bit_bor(a, b, c, ...) + if c then + return bit_bor(bit_bor(a, b), c, ...) + elseif b then + return bit_tobit(bor(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.bor = bit_bor + +local function bit_band(a, b, c, ...) + if c then + return bit_band(bit_band(a, b), c, ...) + elseif b then + return bit_tobit(band(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.band = bit_band + +local function bit_bxor(a, b, c, ...) + if c then + return bit_bxor(bit_bxor(a, b), c, ...) + elseif b then + return bit_tobit(bxor(a % MOD, b % MOD)) + else + return bit_tobit(a) + end +end +M.bit.bxor = bit_bxor + +function M.bit.lshift(x, n) + return bit_tobit(lshift(x % MOD, n % 32)) +end + +function M.bit.rshift(x, n) + return bit_tobit(rshift(x % MOD, n % 32)) +end + +function M.bit.arshift(x, n) + return bit_tobit(arshift(x % MOD, n % 32)) +end + +function M.bit.rol(x, n) + return bit_tobit(lrotate(x % MOD, n % 32)) +end + +function M.bit.ror(x, n) + return bit_tobit(rrotate(x % MOD, n % 32)) +end + +function M.bit.bswap(x) + return bit_tobit(bswap(x % MOD)) +end +]] + +return M diff --git a/stdlib/color.lua b/stdlib/color.lua new file mode 100644 index 0000000..a1da8c4 --- /dev/null +++ b/stdlib/color.lua @@ -0,0 +1,300 @@ + +--[[ + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 1]. +]] +local function hslToRgb(h, s, l) + local r, g, b + + if s == 0 then + r, g, b = l, l, l -- achromatic + else + function hue2rgb(p, q, t) + if t < 0 then t = t + 1 end + if t > 1 then t = t - 1 end + if t < 1/6 then return p + (q - p) * 6 * t end + if t < 1/2 then return q end + if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end + return p + end + + local q + if l < 0.5 then q = l * (1 + s) else q = l + s - l * s end + local p = 2 * l - q + + r = hue2rgb(p, q, h + 1/3) + g = hue2rgb(p, q, h) + b = hue2rgb(p, q, h - 1/3) + end + + return r, g, b +end + +--[[ + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 1]. +]] +local function rgbToHsl(r, g, b) + local max, min = math.max(r, g, b), math.min(r, g, b) + local h, s, l + + l = (max + min) / 2 + if max == 0 then s = 0 else s = (max - min) / max end + + if max == min then + h, s = 0, 0 -- achromatic + else + local d = max - min + local s + if l > 0.5 then s = d / (2 - max - min) else s = d / (max + min) end + if max == r then + h = (g - b) / d + if g < b then h = h + 6 end + elseif max == g then h = (b - r) / d + 2 + elseif max == b then h = (r - g) / d + 4 + end + h = h / 6 + end + + return h, s, l +end + + +--[[ + * Converts an RGB color value to HSV. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes r, g, and b are contained in the set [0, 1] and + * returns h, s, and v in the set [0, 1]. +]] +local function rgbToHsv(r, g, b) + local max, min = math.max(r, g, b), math.min(r, g, b) + local h, s, v + v = max + + local d = max - min + if max == 0 then s = 0 else s = d / max end + + if max == min then + h = 0 -- achromatic + else + if max == r then + h = (g - b) / d + if g < b then h = h + 6 end + elseif max == g then h = (b - r) / d + 2 + elseif max == b then h = (r - g) / d + 4 + end + h = h / 6 + end + + return h, s, v +end + +--[[ + * Converts an HSV color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSV_color_space. + * Assumes h, s, and v are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 1]. +]] +local function hsvToRgb(h, s, v) + local r, g, b + + local i = math.floor(h * 6); + local f = h * 6 - i; + local p = v * (1 - s); + local q = v * (1 - f * s); + local t = v * (1 - (1 - f) * s); + + i = i % 6 + + if i == 0 then r, g, b = v, t, p + elseif i == 1 then r, g, b = q, v, p + elseif i == 2 then r, g, b = p, v, t + elseif i == 3 then r, g, b = p, q, v + elseif i == 4 then r, g, b = t, p, v + elseif i == 5 then r, g, b = v, p, q + end + + return r, g, b +end + +---@class color +---@field r number @red, 0.0 - 1.0 +---@field g number @green, 0.0 - 1.0 +---@field b number @blue, 0.0 - 1.0 +---@field a number @alpha, 0.0 - 1.0 +---@operator add(color): color +---@operator add(number): color +---@operator sub(color): color +---@operator sub(number): color +---@operator mul(color): color +---@operator mul(number): color +---@operator div(color): color +---@operator div(number): color +local col = {} + +--- for use in actor:diffuse(col:unpack()) +---@return number, number, number, number +function col:unpack() + return self.r, self.g, self.b, self.a +end + +-- conversions + +---@return number, number, number +function col:rgb() + return self.r, self.g, self.b +end + +---@return number, number, number +function col:hsl() + return rgbToHsl(self.r, self.g, self.b) +end + +---@return number, number, number +function col:hsv() + return rgbToHsv(self.r, self.g, self.b) +end + +---@return string +function col:hex() + return string.format('%02x%02x%02x', + math.floor(self.r * 255), + math.floor(self.g * 255), + math.floor(self.b * 255)) +end + +-- setters + +---@return color +function col:hue(h) + local _, s, v = self:hsv() + return hsv(h % 1, s, v, self.a) +end + +---@return color +function col:huesmooth(h) + local _, s, v = self:hsv() + return shsv(h % 1, s, v, self.a) +end + +---@return color +function col:alpha(a) + return rgb(self.r, self.g, self.b, a) +end + +--- multiplies current alpha by provided value +---@return color +function col:malpha(a) + return rgb(self.r, self.g, self.b, self.a * a) +end + +-- effects + +---@return color +function col:invert() + return rgb(1 - self.r, 1 - self.g, 1 - self.b, self.a) +end + +---@return color +function col:grayscale() + return rgb(self.r * 0.299 + self.g * 0.587 + self.b * 0.114, self.a) +end + +---@return color +function col:hueshift(a) + local h, s, v = self:hsv() + return hsv((h + a) % 1, s, v, self.a) +end + +local colmeta = {} + +function colmeta:__index(i) + if i == 1 then return self.r end + if i == 2 then return self.g end + if i == 3 then return self.b end + if i == 4 then return self.a end + return col[i] +end + +local function typ(a) + return (type(a) == 'table' and a.r and a.g and a.b and a.a) and 'color' or type(a) +end + +local function genericop(a, b, f, name) + local typea = typ(a) + local typeb = typ(b) + if typea == 'number' then + return rgb(f(b.r, a), f(b.g, a), f(b.b, a), b.a) + elseif typeb == 'number' then + return rgb(f(a.r, b), f(a.g, b), f(a.b, b), a.a) + elseif typea == 'color' and typeb == 'color' then + return rgb(f(a.r, b.r), f(a.g, b.g), f(a.b, b.b), f(a.a, b.a)) + end + error('cant apply ' .. name .. ' to ' .. typea .. ' and ' .. typeb, 3) +end + +function colmeta.__add(a, b) + return genericop(a, b, function(a, b) return a + b end, 'add') +end +function colmeta.__sub(a, b) + return genericop(a, b, function(a, b) return a - b end, 'sub') +end +function colmeta.__mul(a, b) + return genericop(a, b, function(a, b) return a * b end, 'mul') +end +function colmeta.__div(a, b) + return genericop(a, b, function(a, b) return a / b end, 'div') +end + +function colmeta.__eq(a, b) + return (typ(a) == 'color' and typ(b) == 'color') and (a.r == b.r and a.g == b.g and a.b == b.b and a.a == b.a) +end + +function colmeta:__tostring() + return '#' .. self:hex() +end +colmeta.__name = 'color' + +-- constructors + +---@return color +function rgb(r, g, b, a) + a = a or 1 + return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) +end + +---@return color +function hsl(h, s, l, a) + a = a or 1 + local r, g, b = hslToRgb(h % 1, s, l) + return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) +end + +---@return color +function hsv(h, s, v, a) + a = a or 1 + local r, g, b = hsvToRgb(h % 1, s, v) + return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) +end + +--- smoother hsv. not correct but looks nicer +---@return color +function shsv(h, s, v, a) + h = h % 1 + return hsv(h * h * (3 - 2 * h), s, v, a) +end + +---@param hex string +---@return color +function hex(hex) + hex = string.gsub(hex, '#', '') + if string.len(hex) == 3 then + return rgb((tonumber('0x' .. string.sub(hex, 1, 1)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 2, 2)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 3, 3)) * 17) / 255) + else + return rgb(tonumber('0x' .. string.sub(hex, 1, 2)) / 255, tonumber('0x' .. string.sub(hex, 3, 4)) / 255, tonumber('0x' .. string.sub(hex, 5, 6)) / 255) + end +end \ No newline at end of file diff --git a/stdlib/easable.lua b/stdlib/easable.lua new file mode 100644 index 0000000..fbf07c9 --- /dev/null +++ b/stdlib/easable.lua @@ -0,0 +1,80 @@ +---@class easable +---@field public a number @the eased value +---@field public toa number @the target, uneased value +---@field protected onUpdateFuncs fun():nil[] +local eas = {} + +---@param new number @New value to ease to +---@return void +function eas:set(new) + self.toa = new +end + +---@param new number @New value +---@return void +function eas:reset(new) + self.toa = new + self.a = new +end + +---@param new number @How much to add to current value to ease to +---@return void +function eas:add(new) + self.toa = self.toa + new +end + +---@param func fun(a: number):void @Adds a callback function that will run each time the eased value changes +---@return void +function eas:onUpdate(func) + table.insert(self.onUpdateFuncs, func) +end + +local easmeta = {} + +easmeta.__index = eas +easmeta.__name = 'easable' + +function easmeta.__add(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) + ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__sub(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) - ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__mul(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) * ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__div(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) / ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__mod(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) % ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__eq(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) == ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__lt(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) < ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__le(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) <= ((type(b) == 'table' and b.a) and b.a or b) +end + +function easmeta:__call(dt) + self.a = mix(self.a, self.toa, dt) + for _, callback in ipairs(self.onUpdateFuncs) do + callback(self.a) + end +end +function easmeta:__tostring() + return tostring(self.a) +end +function easmeta:__unm(self) + return -self.a +end + +---@param default number +---@return easable +function easable(default) + default = default or 0 + return setmetatable({a = default, toa = default, onUpdateFuncs = {}}, easmeta) +end \ No newline at end of file diff --git a/stdlib/easable2.lua b/stdlib/easable2.lua new file mode 100644 index 0000000..31f64d2 --- /dev/null +++ b/stdlib/easable2.lua @@ -0,0 +1,90 @@ +---@class easable2 +---@field public a number @the eased value +---@field public toa number @the target, uneased value +---@field public ease fun(a:number):number @the ease to use +---@field protected onUpdateFuncs fun(a:number):nil[] +---@field protected _a number @the internal value, linearly eased +local eas = {} + +---@param new number @New value to ease to +---@return void +function eas:set(new) + self.toa = new +end + +---@param new number @New value +---@return void +function eas:reset(new) + self.toa = new + self._a = new +end + +---@param new number @How much to add to current value to ease to +---@return void +function eas:add(new) + self.toa = self.toa + new +end + +---@param func fun(a: number):void @Adds a callback function that will run each time the eased value changes +---@return void +function eas:onUpdate(func) + table.insert(self.onUpdateFuncs, func) +end + +local easmeta = {} + +easmeta.__index = eas +easmeta.__name = 'easable2' + +function easmeta.__add(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) + ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__sub(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) - ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__mul(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) * ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__div(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) / ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__mod(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) % ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__eq(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) == ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__lt(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) < ((type(b) == 'table' and b.a) and b.a or b) +end +function easmeta.__le(a, b) + return ((type(a) == 'table' and a.a) and a.a or a) <= ((type(b) == 'table' and b.a) and b.a or b) +end + +function easmeta:__call(dt) + if self._a == self.toa then + -- do nothing + elseif self._a < self.toa then + self._a = self._a + math.min(dt, math.abs(self._a - self.toa)) + else + self._a = self._a - math.min(dt, math.abs(self._a - self.toa)) + end + self.a = self.ease(self._a) + + for _, callback in ipairs(self.onUpdateFuncs) do + callback(self.a) + end +end +function easmeta:__tostring() + return tostring(self.a) +end +function easmeta:__unm(self) + return -self.a +end + +---@param default number +---@return easable2 +function easable2(default, ease) + default = default or 0 + return setmetatable({a = default, toa = default, onUpdateFuncs = {}, ease = ease or outSine, _a = default}, easmeta) +end \ No newline at end of file diff --git a/stdlib/ease.lua b/stdlib/ease.lua new file mode 100644 index 0000000..6a98a28 --- /dev/null +++ b/stdlib/ease.lua @@ -0,0 +1,338 @@ +-- nabbed straight from mirin template: +-- https://github.com/XeroOl/notitg-mirin/blob/d1e9a8e71026aeabe81c682a114ce265cbd6362a/template/ease.lua + +local sqrt = math.sqrt +local sin = math.sin +local asin = math.asin +local cos = math.cos +local pow = math.pow +local exp = math.exp +local pi = math.pi +local abs = math.abs + +-- ===================================================================== -- + +-- Utility functions + +--- Flip any easing function, making it go from 1 to 0 +-- Example use: +-- ```lua +-- ease {0, 20, flip(outQuad), 50, 'modname'} +-- ``` +flip = setmetatable({}, { + __call = function(self, fn) + self[fn] = self[fn] or function(x) return 1 - fn(x) end + return self[fn] + end +}) + +-- Mix two easing functions together into a new ease +-- the new ease starts by acting like the first argument, and then ends like the second argument +-- Example: ease {0, 20, blendease(inQuad, outQuad), 100, 'modname'} +blendease = setmetatable({}, { + __index = function(self, key) + self[key] = {} + return self[key] + end, + __call = function(self, fn1, fn2) + if not self[fn1][fn2] then + local transient1 = fn1(1) <= 0.5 + local transient2 = fn2(1) <= 0.5 + if transient1 and not transient2 then + error('blendease: the first argument is a transient ease, but the second argument doesn\'t match') + end + if transient2 and not transient1 then + error('blendease: the second argument is a transient ease, but the first argument doesn\'t match') + end + self[fn1][fn2] = function(x) + local mixFactor = 3*x^2-2*x^3 + return (1 - mixFactor) * fn1(x) + mixFactor * fn2(x) + end + end + return self[fn1][fn2] + end +}) + +local function param1cache(self, param1) + self.cache[param1] = self.cache[param1] or function(x) + return self.fn(x, param1) + end + return self.cache[param1] +end + +local param1mt = { + __call = function(self, x, param1) + return self.fn(x, param1 or self.dp1) + end, + __index = { + param = param1cache, + params = param1cache, + } +} + +-- Declare an easing function taking one custom parameter +function with1param(fn, defaultparam1) + return setmetatable({ + fn = fn, + dp1 = defaultparam1, + cache = {}, + }, param1mt) +end + +local function param2cache(self, param1, param2) + self.cache[param1] = self.cache[param1] or {} + self.cache[param1][param2] = self.cache[param1][param2] or function(x) + return self.fn(x, param1, param2) + end + return self.cache[param1][param2] +end + +local param2mt = { + __call = function(self, x, param1, param2) + return self.fn(x, param1 or self.dp1, param2 or self.dp2) + end, + __index = { + param=param2cache, + params=param2cache, + } +} + +-- Declare an easing function taking two custom parameters +function with2params(fn, defaultparam1, defaultparam2) + return setmetatable({ + fn = fn, + dp1 = defaultparam1, + dp2 = defaultparam2, + cache = {}, + }, param2mt) +end + +-- ===================================================================== -- + +-- Easing functions + +function bounce(t) return 4 * t * (1 - t) end +function tri(t) return 1 - abs(2 * t - 1) end +function bell(t) return inOutQuint(tri(t)) end +function pop(t) return 3.5 * (1 - t) * (1 - t) * sqrt(t) end +function tap(t) return 3.5 * t * t * sqrt(1 - t) end +function pulse(t) return t < .5 and tap(t * 2) or -pop(t * 2 - 1) end + +function spike(t) return exp(-10 * abs(2 * t - 1)) end +function inverse(t) return t * t * (1 - t) * (1 - t) / (0.5 - t) end + +local function popElasticInternal(t, damp, count) + return (1000 ^ -(t ^ damp) - 0.001) * sin(count * pi * t) +end + +local function tapElasticInternal(t, damp, count) + return (1000 ^ -((1 - t) ^ damp) - 0.001) * sin(count * pi * (1 - t)) +end + +local function pulseElasticInternal(t, damp, count) + if t < .5 then + return tapElasticInternal(t * 2, damp, count) + else + return -popElasticInternal(t * 2 - 1, damp, count) + end +end + +popElastic = with2params(popElasticInternal, 1.4, 6) +tapElastic = with2params(tapElasticInternal, 1.4, 6) +pulseElastic = with2params(pulseElasticInternal, 1.4, 6) + +impulse = with1param(function(t, damp) + t = t ^ damp + return t * (1000 ^ -t - 0.001) * 18.6 +end, 0.9) + +function instant() return 1 end +function linear(t) return t end +function inQuad(t) return t * t end +function outQuad(t) return -t * (t - 2) end +function inOutQuad(t) + t = t * 2 + if t < 1 then + return 0.5 * t ^ 2 + else + return 1 - 0.5 * (2 - t) ^ 2 + end +end +function outInQuad(t) + t = t * 2 + if t < 1 then + return 0.5 - 0.5 * (1 - t) ^ 2 + else + return 0.5 + 0.5 * (t - 1) ^ 2 + end +end +function inCubic(t) return t * t * t end +function outCubic(t) return 1 - (1 - t) ^ 3 end +function inOutCubic(t) + t = t * 2 + if t < 1 then + return 0.5 * t ^ 3 + else + return 1 - 0.5 * (2 - t) ^ 3 + end +end +function outInCubic(t) + t = t * 2 + if t < 1 then + return 0.5 - 0.5 * (1 - t) ^ 3 + else + return 0.5 + 0.5 * (t - 1) ^ 3 + end +end +function inQuart(t) return t * t * t * t end +function outQuart(t) return 1 - (1 - t) ^ 4 end +function inOutQuart(t) + t = t * 2 + if t < 1 then + return 0.5 * t ^ 4 + else + return 1 - 0.5 * (2 - t) ^ 4 + end +end +function outInQuart(t) + t = t * 2 + if t < 1 then + return 0.5 - 0.5 * (1 - t) ^ 4 + else + return 0.5 + 0.5 * (t - 1) ^ 4 + end +end +function inQuint(t) return t ^ 5 end +function outQuint(t) return 1 - (1 - t) ^ 5 end +function inOutQuint(t) + t = t * 2 + if t < 1 then + return 0.5 * t ^ 5 + else + return 1 - 0.5 * (2 - t) ^ 5 + end +end +function outInQuint(t) + t = t * 2 + if t < 1 then + return 0.5 - 0.5 * (1 - t) ^ 5 + else + return 0.5 + 0.5 * (t - 1) ^ 5 + end +end +function inExpo(t) return 1000 ^ (t - 1) - 0.001 end +function outExpo(t) return 1.001 - 1000 ^ -t end +function inOutExpo(t) + t = t * 2 + if t < 1 then + return 0.5 * 1000 ^ (t - 1) - 0.0005 + else + return 1.0005 - 0.5 * 1000 ^ (1 - t) + end +end +function outInExpo(t) + if t < 0.5 then + return outExpo(t * 2) * 0.5 + else + return inExpo(t * 2 - 1) * 0.5 + 0.5 + end +end +function inCirc(t) return 1 - sqrt(1 - t * t) end +function outCirc(t) return sqrt(-t * t + 2 * t) end +function inOutCirc(t) + t = t * 2 + if t < 1 then + return 0.5 - 0.5 * sqrt(1 - t * t) + else + t = t - 2 + return 0.5 + 0.5 * sqrt(1 - t * t) + end +end +function outInCirc(t) + if t < 0.5 then + return outCirc(t * 2) * 0.5 + else + return inCirc(t * 2 - 1) * 0.5 + 0.5 + end +end +function outBounce(t) + if t < 1 / 2.75 then + return 7.5625 * t * t + elseif t < 2 / 2.75 then + t = t - 1.5 / 2.75 + return 7.5625 * t * t + 0.75 + elseif t < 2.5 / 2.75 then + t = t - 2.25 / 2.75 + return 7.5625 * t * t + 0.9375 + else + t = t - 2.625 / 2.75 + return 7.5625 * t * t + 0.984375 + end +end +function inBounce(t) return 1 - outBounce(1 - t) end +function inOutBounce(t) + if t < 0.5 then + return inBounce(t * 2) * 0.5 + else + return outBounce(t * 2 - 1) * 0.5 + 0.5 + end +end +function outInBounce(t) + if t < 0.5 then + return outBounce(t * 2) * 0.5 + else + return inBounce(t * 2 - 1) * 0.5 + 0.5 + end +end +function inSine(x) return 1 - cos(x * (pi * 0.5)) end +function outSine(x) return sin(x * (pi * 0.5)) end +function inOutSine(x) + return 0.5 - 0.5 * cos(x * pi) +end +function outInSine(t) + if t < 0.5 then + return outSine(t * 2) * 0.5 + else + return inSine(t * 2 - 1) * 0.5 + 0.5 + end +end + +function outElasticInternal(t, a, p) + return a * pow(2, -10 * t) * sin((t - p / (2 * pi) * asin(1/a)) * 2 * pi / p) + 1 +end +local function inElasticInternal(t, a, p) + return 1 - outElasticInternal(1 - t, a, p) +end +function inOutElasticInternal(t, a, p) + return t < 0.5 + and 0.5 * inElasticInternal(t * 2, a, p) + or 0.5 + 0.5 * outElasticInternal(t * 2 - 1, a, p) +end +function outInElasticInternal(t, a, p) + return t < 0.5 + and 0.5 * outElasticInternal(t * 2, a, p) + or 0.5 + 0.5 * inElasticInternal(t * 2 - 1, a, p) +end + +inElastic = with2params(inElasticInternal, 1, 0.3) +outElastic = with2params(outElasticInternal, 1, 0.3) +inOutElastic = with2params(inOutElasticInternal, 1, 0.3) +outInElastic = with2params(outInElasticInternal, 1, 0.3) + +function inBackInternal(t, a) return t * t * (a * t + t - a) end +function outBackInternal(t, a) t = t - 1 return t * t * ((a + 1) * t + a) + 1 end +function inOutBackInternal(t, a) + return t < 0.5 + and 0.5 * inBackInternal(t * 2, a) + or 0.5 + 0.5 * outBackInternal(t * 2 - 1, a) +end +function outInBackInternal(t, a) + return t < 0.5 + and 0.5 * outBackInternal(t * 2, a) + or 0.5 + 0.5 * inBackInternal(t * 2 - 1, a) +end + +inBack = with1param(inBackInternal, 1.70158) +outBack = with1param(outBackInternal, 1.70158) +inOutBack = with1param(inOutBackInternal, 1.70158) +outInBack = with1param(outInBackInternal, 1.70158) \ No newline at end of file diff --git a/stdlib/index.lua b/stdlib/index.lua new file mode 100644 index 0000000..7ca0a96 --- /dev/null +++ b/stdlib/index.lua @@ -0,0 +1,10 @@ +require('input') +bitop = require('bitop') -- TODO: tons of this is commented out because of '...'. FIX. IT. +require('rng') +require('easable') +require('easable2') +require('color') +require('vector2D') +require('ease') +uwuify = require('uwuify') +require('util') \ No newline at end of file diff --git a/stdlib/input.lua b/stdlib/input.lua new file mode 100644 index 0000000..3b4c9ee --- /dev/null +++ b/stdlib/input.lua @@ -0,0 +1,39 @@ +inputs = { -- -1 for not pressed, time for time of press + Left = -1, + Down = -1, + Up = -1, + Right = -1 +} +rawInputs = { + Left = -1, + Down = -1, + Up = -1, + Right = -1 +} + +directions = { + Left = {-1, 0}, + Down = {0, 1}, + Up = {0, -1}, + Right = {1, 0} +} + +function uranium.init() + for pn = 1, 2 do + for i,j in ipairs({'Left', 'Down', 'Up', 'Right'}) do + local i = i -- lua scope funnies + local j = j + + _main:addcommand('StepP' .. pn .. j .. 'PressMessage', function() + rawInputs[j] = t + if uranium:call('press', j) then return end + inputs[j] = t + end) + _main:addcommand('StepP' .. pn .. j .. 'LiftMessage', function() + if uranium:call('release', j) then return end + inputs[j] = -1 + rawInputs[j] = -1 + end) + end + end +end \ No newline at end of file diff --git a/stdlib/rng.lua b/stdlib/rng.lua new file mode 100644 index 0000000..dad337c --- /dev/null +++ b/stdlib/rng.lua @@ -0,0 +1,172 @@ +-- xoshiro128** 1.1 by David Blackman and Sebastiano Vigna (vigna@acm.org) https://prng.di.unimi.it/xoshiro128starstar.c +-- Lua implementation by Jill "oatmealine" Monoids +-- Licensed under CC-BY-SA + +local RAND_MAX = 4294967295 + +---@param x int +---@param k int +local function rotl(x, k) + return bitop.bor(bitop.lshift(x, k), bitop.rshift(x, (32 - k))) +end + +---@param state int[] @array of size 4; will be mutated +local function next(state) + local result = rotl(state[2] * 5, 7) * 9 + local t = bitop.lshift(state[2], 9) + + state[3] = bitop.bxor(state[3], state[1]) + state[4] = bitop.bxor(state[4], state[2]) + state[2] = bitop.bxor(state[2], state[3]) + state[1] = bitop.bxor(state[1], state[4]) + + state[2] = bitop.bxor(state[3], t) + + state[3] = rotl(state[4], 11) + + return result +end + +local JUMP = { 0x8764000b, 0xf542d2d3, 0x6fa035c3, 0x77f2db5b } + +---@param state int[] @array of size 4; will be mutated +local function jump(state) + local s0 = 0 + local s1 = 0 + local s2 = 0 + local s3 = 0 + + for _, j in ipairs(JUMP) do + for b = 0, 31 do + if bitop.band(j, bitop.lshift(1, b)) ~= 0 then + s0 = bitop.bxor(s0, state[1]) + s1 = bitop.bxor(s1, state[2]) + s2 = bitop.bxor(s2, state[3]) + s3 = bitop.bxor(s3, state[4]) + end + next(state) + end + end + + state[1] = s0 + state[2] = s1 + state[3] = s2 + state[4] = s3 +end + +local LONG_JUMP = { 0xb523952e, 0x0b6f099f, 0xccf5a0ef, 0x1c580662 } + +---@param state int[] @array of size 4; will be mutated +local function long_jump(state) + local s0 = 0 + local s1 = 0 + local s2 = 0 + local s3 = 0 + + for _, j in ipairs(LONG_JUMP) do + for b = 0, 31 do + if bitop.band(j, bitop.lshift(1, b)) ~= 0 then + s0 = bitop.bxor(s0, state[1]) + s1 = bitop.bxor(s1, state[2]) + s2 = bitop.bxor(s2, state[3]) + s3 = bitop.bxor(s3, state[4]) + end + next(state) + end + end + + state[1] = s0 + state[2] = s1 + state[3] = s2 + state[4] = s3 +end + +---@class rng a xoshiro128** pseudorandom implementation +---@field public state int[] the current state, size of 4 +rng = {} + +--- gets the next pseudo-random value; recommended to use abstractions (like __call) over this +---@return int +function rng:next() + return next(self.state) +end + +--- This is the jump function for the generator. It is equivalent +--- to 2^64 calls to next(); it can be used to generate 2^64 +--- non-overlapping subsequences for parallel computations. +---@return void +function rng:jump() + return jump(self.state) +end + +--- This is the long-jump function for the generator. It is equivalent to +--- 2^96 calls to next(); it can be used to generate 2^32 starting points, +--- from each of which jump() will generate 2^32 non-overlapping +--- subsequences for parallel distributed computations. +---@return void +function rng:longJump() + return long_jump(self.state) +end + +--- if `max` is not given, `min` will be used as the maximum and the minimum will be 1 +--- if min is 1 and max is 4, the returned value can be 1, 2, 3 or 4 +---@param min int +---@param max int +---@return int +function rng:int(min, max) + if not max then + local m = min + min = 1 + max = m + end + + local _min = min + local _max = max + min = math.min(_min, _max) + max = math.max(_min, _max) + + return min + (self:next() % (max - min + 1)) +end + +--- if `max` is not given, it will be 1 +---@param max float +---@return float +function rng:float(max) + return ((self:next() % RAND_MAX) / RAND_MAX) * (max or 1) +end + + +---@return boolean +function rng:bool() + return self:next() % 2 == 0 +end + +function rng:seed(seed) + self.state = {seed, seed, seed, seed} + self:next() +end + +local rngmeta = {} + +--- acts identical to math.random() +function rngmeta:__call(a, b) + if a then + return self:int(a, b) + end + return self:float() +end + +rngmeta.__index = rng + +--- creates a new RNG object +---@param seed int[] @array of size 4, will default to os.time() if not given +---@return rng +function rng.init(seed) + seed = seed or os.time() + local state = {seed, seed, seed, seed} + local this = setmetatable({state = state}, rngmeta) + this:next() -- just to prevent the state from being all the same number; i dont know a cleaner way of doing this + return this +end + +return rng \ No newline at end of file diff --git a/stdlib/util.lua b/stdlib/util.lua new file mode 100644 index 0000000..7f1dbd7 --- /dev/null +++ b/stdlib/util.lua @@ -0,0 +1,157 @@ +---@param a number +---@param b number +---@param x number +---@return number +function mix(a, b, x) + return a * (1 - x) + b * x +end +lerp = mix + +---@param x number +---@return number +function sign(x) + if x > 0 then return 1 end + if x < 0 then return -1 end + return 0 +end + +function deepcopy(obj) + if type(obj) ~= 'table' then return obj end + local res = {} + for k, v in pairs(obj) do res[deepcopy(k)] = deepcopy(v) end + return res +end + +---@param a number +---@param x number +---@param y number +---@return number +function clamp(a, x, y) + return math.max(math.min(a, math.max(x, y)), math.min(x, y)) +end + +---@param tab any[] +---@param elem any +function includes(tab, elem) + if not tab then error('bad argument #1 (expected table, got nil)', 2) end + for _, v in pairs(tab) do + if elem == v then return true end + end + return false +end + +---@param text string +---@param length int +function truncate(text, length) + local addellipses = false + + local firstLine = nil + for line in string.gfind(text, '([^\n]*)\n?') do + if not firstLine then + firstLine = line + elseif line ~= '' then + addellipses = true + break + end + end + + text = firstLine + if string.len(text) > length then + text = string.sub(text, 1, length) + addellipses = true + end + + if addellipses then text = text .. '...' end + return text +end + +function padLeft(str, num, fill) + local s = {} + for i = 1, num - #str do + table.insert(s, fill) + end + table.insert(s, str) + return table.concat(s, '') +end + +function padLeft(str, num, fill) + local s = {} + table.insert(s, str) + for i = 1, num - #str do + table.insert(s, fill) + end + return table.concat(s, '') +end + +local whitespaces = {' ', '\n', '\r'} + +---@param str string +function trimLeft(str) + while includes(whitespaces, string.sub(str, 1, 1)) do + str = string.sub(str, 2) + end + return str +end + +---@param str string +function trimRight(str) + while includes(whitespaces, string.sub(str, -1, -1)) do + str = string.sub(str, 1, -2) + end + return str +end + +---@param str string +function trim(str) + return trimRight(trimLeft(str)) +end + +---@param o any +---@return string +--- stringify an object +function fullDump(o, r) + if type(o) == 'table' and (not r or r > 0) then + local s = '{' + local first = true + for k,v in pairs(o) do + if not first then + s = s .. ', ' + end + local nr = nil + if r then + nr = r - 1 + end + if type(k) ~= 'number' then + s = s .. tostring(k) .. ' = ' .. fullDump(v, nr) + else + s = s .. fullDump(v, nr) + end + first = false + end + return s .. '}' + elseif type(o) == 'string' then + return '"' .. o .. '"' + else + return tostring(o) + end +end + +---@param t1 any[] +---@param t2 any[] +---@return any[] +function tableConcat(t1, t2) + for i = 1, #t2 do + t1[#t1 + 1] = t2[i] + end + return t1 +end + +---@param tab table +function clearMetatables(tab) + setmetatable(tab, nil) + for _, obj in pairs(tab) do + if type(obj) == 'table' then + clearMetatables(obj) + end + end +end \ No newline at end of file diff --git a/stdlib/uwuify.lua b/stdlib/uwuify.lua new file mode 100644 index 0000000..acc933a --- /dev/null +++ b/stdlib/uwuify.lua @@ -0,0 +1,23 @@ +local endings = {'rawr x3', 'OwO', 'UwU', 'o.O', '-.-', '>w<', '(˘ω˘)', 'σωσ', 'ʘwʘ', ':3', 'XD', 'nyaa~~', 'mya', '>_<', 'rawr', '^^', '^^;;', '(^•ω•^)'} + +return function(str) + str = string.lower(str) + + str = string.gsub(str, 'small', 'smol') + str = string.gsub(str, 'cute', 'kawaii~') + str = string.gsub(str, 'fluff', 'floof') + str = string.gsub(str, 'love', 'luv') + str = string.gsub(str, 'stupid', 'baka') + str = string.gsub(str, 'meow', 'nya~') + + str = string.gsub(str, 'l', 'w') + str = string.gsub(str, 'r', 'w') + + str = string.gsub(str, 'n([aeiou])', 'ny%1') + + str = string.gsub(str, '[.!?]%s', function(e) return e .. endings[math.random(1, #endings)] .. ' ' end) + + str = string.gsub(str, '(%s)(%a)(%a)', function(space, rep, other) if math.random() < 0.05 then return space .. rep .. '-' .. rep .. other else return space .. rep .. other end end) + + return str +end \ No newline at end of file diff --git a/stdlib/vector2D.lua b/stdlib/vector2D.lua new file mode 100644 index 0000000..29dce1c --- /dev/null +++ b/stdlib/vector2D.lua @@ -0,0 +1,142 @@ +---@class vector2D A vector can be defined as a set of 2 coordinates. They can be obtained by doing either vect.x and vect.y or vect[1] and vect[2], for compatibility purposes. +---The reason such a simple class exists is to do simplified math with it - math abstracted as :length(), :angle(), etc is much easier to read. +---@field public x number @x coordinate +---@field public y number @y coordinate +---@operator add(vector2D): vector2D +---@operator add(number): vector2D +---@operator sub(vector2D): vector2D +---@operator sub(number): vector2D +---@operator mul(vector2D): vector2D +---@operator mul(number): vector2D +---@operator div(vector2D): vector2D +---@operator div(number): vector2D +---@operator unm: vector2D +local vect = {} + +---@return number +function vect:length() + return math.sqrt(self.x * self.x + self.y * self.y) +end + +---@return number +function vect:lengthSquared() + return self.x * self.x + self.y * self.y +end + +---@return number +function vect:angle() + return math.atan2(self.y, self.x) +end + +---@return vector2D +function vect:normalize() + local len = self:length() + if len ~= 0 and len ~= 1 then + return vector2D(self.x / len, self.y / len) + else + return self + end +end + +---@return vector2D +function vect:resize(x) + local n = self:normalize() + return vector2D(n.x * x, n.y * x) +end + +---@return vector2D +function vect:rotate(ang) + local a = self:angle() + local len = self:length() + return vectorFromAngle(a + ang, len) +end + +---@return number, number +function vect:unpack() + return self.x, self.y +end + +---@param v2 vector2D +---@return number +function vect:distance(v2) + return (self - v2):length() +end + +---@param v2 vector2D +---@return number +function vect:distanceSquared(v2) + return (self - v2):lengthSquared() +end + +local vectmeta = {} + +local function typ(a) + return (type(a) == 'table' and a.x and a.y) and 'vector' or type(a) +end + +local function genericop(a, b, f, name) + local typea = typ(a) + local typeb = typ(b) + if typea == 'number' then + return vector2D(f(b.x, a), f(b.y, a)) + elseif typeb == 'number' then + return vector2D(f(a.x, b), f(a.y, b)) + elseif typea == 'vector' and typeb == 'vector' then + return vector2D(f(a.x, b.x), f(a.y, b.y)) + end + error('cant apply ' .. name .. ' to ' .. typea .. ' and ' .. typeb, 3) +end + +function vectmeta.__add(a, b) + return genericop(a, b, function(a, b) return a + b end, 'add') +end +function vectmeta.__sub(a, b) + return genericop(a, b, function(a, b) return a - b end, 'sub') +end +function vectmeta.__mul(a, b) + return genericop(a, b, function(a, b) return a * b end, 'mul') +end +function vectmeta.__div(a, b) + return genericop(a, b, function(a, b) return a / b end, 'div') +end + +function vectmeta.__eq(a, b) + return (typ(a) == 'vector' and typ(b) == 'vector') and (a.x == b.x and a.y == b.y) +end + +function vectmeta:__unm() + return vector2D(-self.x, -self.y) +end + +function vectmeta:__tostring() + return '(' .. self.x .. ', ' .. self.y .. ')' +end +vectmeta.__name = 'vector' + +function vectmeta:__index(i) + if i == 1 then return self.x end + if i == 2 then return self.y end + return vect[i] +end + +--- create a new vector +---@param x number | nil +---@param y number | nil +---@return vector2D +function vector2D(x, y) + x = x or 0 + y = y or x + return setmetatable({x = x, y = y}, vectmeta) +end + +--- create a new vector from an angle +---@param ang number | nil @angle in degrees +---@param amp number | nil +---@return vector2D +function vectorFromAngle(ang, amp) + ang = math.rad(ang or 0) + amp = amp or 1 + return vector2D(math.cos(ang) * amp, math.sin(ang) * amp) +end + +vector = vector2D \ No newline at end of file diff --git a/typings.lua b/typings.lua new file mode 100644 index 0000000..28907e3 --- /dev/null +++ b/typings.lua @@ -0,0 +1,85 @@ +---@meta + +-- cleaning up some notitg typing jank... ehe +---@alias int number +---@alias float number +---@alias Quad Actor +---@alias void nil + +---@return Quad +--- Defines a Quad actor. +function Quad() end +---@return ActorProxy +--- Defines an ActorProxy actor. +function ActorProxy() end +---@return Polygon +--- Defines a Polygon actor. +function Polygon() end +---@param file string +---@return Sprite +--- Defines a Sprite actor. +function Sprite(file) end +---@param file string +---@return Model +--- Defines a Model actor. +function Model(file) end +---@param font string? +---@param text string? +---@return BitmapText +--- Defines a BitmapText actor. +function BitmapText(font, text) end +---@param file string +---@return ActorSound +--- Defines an ActorSound actor. +function ActorSound(file) end + +---@param actor Actor +--- Resets an actor to its initial state +function reset(actor) end + +---@type number +--- A simple timer. Ticks upwards at a rate of 1/sec. +--- +--- **The start time is undefined!** This uses `os.clock()`, meaning this will be inconsistent between modfile starts. +--- +--- It's recommended to only use this for eg. `math.sin`, rotations, and other similar visual effects. If you want a proper timer, see `b`. +t = 0 + +---@type number +--- The amount of beats that have passed since the start of the file. +b = 0 + +---@type ActorFrame +--- The root ActorFrame. Use this for `addcommand` and similar! +_main = {} + +---@type number +--- The center of the screen on the X axis. Equal to `SCREEN_CENTER_X`. +scx = 0 +---@type number +--- The center of the screen on the Y axis. Equal to `SCREEN_CENTER_Y`. +scy = 0 +---@type number +--- The screen width. Equal to `SCREEN_WIDTH`. +sw = 0 +---@type number +--- The screen height. Equal to `SCREEN_HEIGHT`. +sh = 0 + +--- The Uranium Template table! Mostly callback-related stuff goes here. +uranium = {} + +--- A callback for initialization. Called on `OnCommand`. +uranium.init = function() end +--- A callback for updates. Called every frame. Draw stuff here! +uranium.update = function() end + +---@param event string +---@param ... any +---@return any +--- Call a defined callback. +function uranium:call(event, ...) end + +--- Equivalent to a modfile-sandboxed `_G`, similar to Mirin's `xero`. You shouldn't need this; and if you do, *what are you doing?* +---@type table +oat = {} \ No newline at end of file