First commit

This commit is contained in:
2023-08-12 11:36:14 +02:00
commit 7c537e3c4f
37 changed files with 5252 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
--- @class EventBus
EventBus = {
_handlers = {},
new = function(self, o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end,
---
---@param self EventBus
---@param event any
---@param handler any
---@param index any
on = function(self, event, handler, index)
if not self._handlers[event] then self._handlers[event] = {} end
self:off(event, handler)
if not index then
table.insert(self._handlers[event], handler)
else
-- insertIntoTable(this.handlers[event], handler, index)
end
end,
---
---@param self EventBus
---@param event any
---@param handler any
off = function(self, event, handler)
if not self._handlers[event] then return end
self._handlers[event] = table.filter(self._handlers[event], function(o) return o ~= handler end)
end,
---
---@param self EventBus
---@param event any
---@param ... unknown
emit = function(self, event, ...)
trace("[EventBus] Emitted " .. event)
if not self._handlers[event] then return end
for k, handler in pairs(self._handlers[event]) do
handler(...)
end
end
}
+40
View File
@@ -0,0 +1,40 @@
-- manage clicks, since tic-80 only returns a "pressed" state
Input = {
--- @type boolean
lclick_prev_state = false,
--- @type 'none'|'pressed'|'released'
lclick_state = "none"
}
function Input.update_mouse(self)
local x, y, left = mouse()
-- left btn down
if left then
-- was not pressed before
if not self.lclick_prev_state then
self.lclick_state = "pressed"
self.lclick_prev_state = true
-- was already pressed
elseif self.lclick_prev_state then
self.lclick_state = "none"
end
-- left btn up
elseif not left then
if self.lclick_prev_state then
self.lclick_state = "released"
self.lclick_prev_state = false
elseif not self.lclick_prev_state then
self.lclick_state = "none"
end
end
end
function Input.mouse_pressed(self)
return self.lclick_state == "pressed"
end
function Input.mouse_released(self)
return self.lclick_state == "released"
end
+98
View File
@@ -0,0 +1,98 @@
function draw9box(corner, edge, x, y, width, height)
-- edges
for i = 8, width - 9, 8 do
spr(edge, x + i, y, 0) -- top
spr(edge, x + i, y + height - 8, 0, 1, 0, 2) -- bottom
end
for i = 8, height - 8,8 do
spr(edge, x, y + i, 0, 1, 0, 3) -- left
spr(edge, x + width - 8, y + i, 0, 1, 0, 1) -- right
end
-- corners
spr(corner, x, y, 0) -- top left
spr(corner, x + width - 8, y, 0, 1, 0, 1) -- top right
spr(corner, x, y + height - 8, 0, 1, 0, 3) -- bottom left
spr(corner, x + width - 8, y + height - 8, 0, 1, 0, 2) -- bottom right
end
local function rot(x, y, rad)
local sa = math.sin(rad)
local ca = math.cos(rad)
return x * ca - y * sa, x * sa + y * ca
end
--- Draw a sprite using two textured triangles.
--- Apply affine transformations: scale, shear, rotate, flip
--- https://cxong.github.io/tic-80-examples/affine-sprites
---comment
---@param id number
---@param x number
---@param y number
---@param colorkey? number
---@param sx? number scale x
---@param sy? number scale y
---@param flip? number
---@param rotate? number
---@param w? number
---@param h? number
---@param ox? number
---@param oy? number
---@param shx1? number
---@param shy1? number
---@param shx2? number
---@param shy2? number
function aspr(
id, x, y, colorkey, sx, sy, flip, rotate, w, h,
ox, oy, shx1, shy1, shx2, shy2
)
colorkey = colorkey or -1
sx = sx or 1
sy = sy or 1
flip = flip or 0
rotate = math.rad(rotate or 0)
w = w or 1
h = h or 1
ox = ox or w * 8 // 2
oy = oy or h * 8 // 2
shx1 = shx1 or 0
shy1 = shy1 or 0
shx2 = shx2 or 0
shy2 = shy2 or 0
-- scale / flip
if flip % 2 == 1 then
sx = -sx
end
if flip >= 2 then
sy = -sy
end
ox = ox * -sx
oy = oy * -sy
-- shear / rotate
shx1 = shx1 * -sx
shy1 = shy1 * -sy
shx2 = shx2 * -sx
shy2 = shy2 * -sy
local rx1, ry1 = rot(ox + shx1, oy + shy1, rotate)
local rx2, ry2 = rot(((w * 8) * sx) + ox + shx1, oy + shy2, rotate)
local rx3, ry3 = rot(ox + shx2, ((h * 8) * sy) + oy + shy1, rotate)
local rx4, ry4 = rot(((w * 8) * sx) + ox + shx2, ((h * 8) * sy) + oy + shy2, rotate)
local x1 = x + rx1
local y1 = y + ry1
local x2 = x + rx2
local y2 = y + ry2
local x3 = x + rx3
local y3 = y + ry3
local x4 = x + rx4
local y4 = y + ry4
-- UV coords
local u1 = (id % 16) * 8
local v1 = id // 16 * 8
local u2 = u1 + w * 8
local v2 = v1 + h * 8
ttri(x1, y1, x2, y2, x3, y3, u1, v1, u2, v1, u1, v2, 0, colorkey)
ttri(x3, y3, x4, y4, x2, y2, u1, v2, u2, v2, u2, v1, 0, colorkey)
end
+65
View File
@@ -0,0 +1,65 @@
--- Finds the first value in a table that satisfies a condition
---@param table any
---@param cb any @function(v, i) return boolean end
---@return unknown @value or nil
---@return unknown @index or nil
function table.find(table, cb)
for i, v in pairs(table) do
if cb(v) then
return v, i
end
end
return nil, nil
end
function table.filter(table, cb)
local new = {}
for _, v in pairs(table) do
if cb(v) then
table.insert(new, v)
end
end
return new
end
function table.remove_item(list, item)
for i, v in pairs(list) do
if v == item then
table.remove(list, i)
return
end
end
end
---needle can be a value or a function
---@param list table
---@param needle function|number
---@return integer
function table.count(list, needle)
local count = 0
for _, v in pairs(list) do
if type(needle) == "function" and needle(v) or v == needle then
count = count + 1
end
end
return count
end
function table.max(list, cb)
local max = -math.huge
for _, v in pairs(list) do
local value = cb(v)
if value > max then
max = value
end
end
return max
end
function math.randomitem(tbl)
return tbl[math.random(#tbl)]
end
function table.random(tbl)
return tbl[math.random(#tbl)]
end
+400
View File
@@ -0,0 +1,400 @@
-- https://github.com/kikito/tween.lua
--[[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota, Yuichi Tateno, Emmanuel Oga
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.
]]
-- Modified by Simon Cambier to better fit TIC-80 restrictions
---@return Tween
function require_tween()
-- easing
-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits.
-- For all easing functions:
-- t = time == how much time has to pass for the tweening to complete
-- b = begin == starting property value
-- c = change == ending - beginning
-- d = duration == running time. How much time has passed *right now*
---@class Tween
local Tween = {}
local Tween_mt = { __index = Tween }
local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin
-- linear
local function linear(t, b, c, d) return c * t / d + b end
-- quad
local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end
local function outQuad(t, b, c, d)
t = t / d
return -c * t * (t - 2) + b
end
local function inOutQuad(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 2) + b end
return -c / 2 * ((t - 1) * (t - 3) - 1) + b
end
local function outInQuad(t, b, c, d)
if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end
return inQuad((t * 2) - d, b + c / 2, c / 2, d)
end
-- cubic
local function inCubic(t, b, c, d) return c * pow(t / d, 3) + b end
local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end
local function inOutCubic(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * t * t * t + b end
t = t - 2
return c / 2 * (t * t * t + 2) + b
end
local function outInCubic(t, b, c, d)
if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end
return inCubic((t * 2) - d, b + c / 2, c / 2, d)
end
-- quart
local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end
local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end
local function inOutQuart(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 4) + b end
return -c / 2 * (pow(t - 2, 4) - 2) + b
end
local function outInQuart(t, b, c, d)
if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end
return inQuart((t * 2) - d, b + c / 2, c / 2, d)
end
-- quint
local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end
local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end
local function inOutQuint(t, b, c, d)
t = t / d * 2
if t < 1 then return c / 2 * pow(t, 5) + b end
return c / 2 * (pow(t - 2, 5) + 2) + b
end
local function outInQuint(t, b, c, d)
if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end
return inQuint((t * 2) - d, b + c / 2, c / 2, d)
end
-- sine
local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end
local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end
local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end
local function outInSine(t, b, c, d)
if t < d / 2 then return outSine(t * 2, b, c / 2, d) end
return inSine((t * 2) - d, b + c / 2, c / 2, d)
end
-- expo
local function inExpo(t, b, c, d)
if t == 0 then return b end
return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001
end
local function outExpo(t, b, c, d)
if t == d then return b + c end
return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b
end
local function inOutExpo(t, b, c, d)
if t == 0 then return b end
if t == d then return b + c end
t = t / d * 2
if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end
return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b
end
local function outInExpo(t, b, c, d)
if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end
return inExpo((t * 2) - d, b + c / 2, c / 2, d)
end
-- circ
local function inCirc(t, b, c, d) return (-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end
local function outCirc(t, b, c, d) return (c * sqrt(1 - pow(t / d - 1, 2)) + b) end
local function inOutCirc(t, b, c, d)
t = t / d * 2
if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end
t = t - 2
return c / 2 * (sqrt(1 - t * t) + 1) + b
end
local function outInCirc(t, b, c, d)
if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end
return inCirc((t * 2) - d, b + c / 2, c / 2, d)
end
-- elastic
local function calculatePAS(p, a, c, d)
p, a = p or d * 0.3, a or 0
if a < abs(c) then return p, c, p / 4 end -- p, a, s
return p, a, p / (2 * pi) * asin(c / a) -- p,a,s
end
local function inElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
t = t - 1
return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b
end
local function outElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d
if t == 1 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b
end
local function inOutElastic(t, b, c, d, a, p)
local s
if t == 0 then return b end
t = t / d * 2
if t == 2 then return b + c end
p, a, s = calculatePAS(p, a, c, d)
t = t - 1
if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end
return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) * 0.5 + c + b
end
local function outInElastic(t, b, c, d, a, p)
if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end
return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p)
end
-- back
local function inBack(t, b, c, d, s)
s = s or 1.70158
t = t / d
return c * t * t * ((s + 1) * t - s) + b
end
local function outBack(t, b, c, d, s)
s = s or 1.70158
t = t / d - 1
return c * (t * t * ((s + 1) * t + s) + 1) + b
end
local function inOutBack(t, b, c, d, s)
s = (s or 1.70158) * 1.525
t = t / d * 2
if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end
t = t - 2
return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b
end
local function outInBack(t, b, c, d, s)
if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end
return inBack((t * 2) - d, b + c / 2, c / 2, d, s)
end
-- bounce
local function outBounce(t, b, c, d)
t = t / d
if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end
if t < 2 / 2.75 then
t = t - (1.5 / 2.75)
return c * (7.5625 * t * t + 0.75) + b
elseif t < 2.5 / 2.75 then
t = t - (2.25 / 2.75)
return c * (7.5625 * t * t + 0.9375) + b
end
t = t - (2.625 / 2.75)
return c * (7.5625 * t * t + 0.984375) + b
end
local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end
local function inOutBounce(t, b, c, d)
if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end
return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b
end
local function outInBounce(t, b, c, d)
if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end
return inBounce((t * 2) - d, b + c / 2, c / 2, d)
end
Tween.easing = {
linear = linear,
inQuad = inQuad,
outQuad = outQuad,
inOutQuad = inOutQuad,
outInQuad = outInQuad,
inCubic = inCubic,
outCubic = outCubic,
inOutCubic = inOutCubic,
outInCubic = outInCubic,
inQuart = inQuart,
outQuart = outQuart,
inOutQuart = inOutQuart,
outInQuart = outInQuart,
inQuint = inQuint,
outQuint = outQuint,
inOutQuint = inOutQuint,
outInQuint = outInQuint,
inSine = inSine,
outSine = outSine,
inOutSine = inOutSine,
outInSine = outInSine,
inExpo = inExpo,
outExpo = outExpo,
inOutExpo = inOutExpo,
outInExpo = outInExpo,
inCirc = inCirc,
outCirc = outCirc,
inOutCirc = inOutCirc,
outInCirc = outInCirc,
inElastic = inElastic,
outElastic = outElastic,
inOutElastic = inOutElastic,
outInElastic = outInElastic,
inBack = inBack,
outBack = outBack,
inOutBack = inOutBack,
outInBack = outInBack,
inBounce = inBounce,
outBounce = outBounce,
inOutBounce = inOutBounce,
outInBounce = outInBounce
}
-- private stuff
local function copyTables(destination, keysTable, valuesTable)
valuesTable = valuesTable or keysTable
local mt = getmetatable(keysTable)
if mt and getmetatable(destination) == nil then
setmetatable(destination, mt)
end
for k, v in pairs(keysTable) do
if type(v) == "table" then
destination[k] = copyTables({}, v, valuesTable[k])
else
destination[k] = valuesTable[k]
end
end
return destination
end
local function checkSubjectAndTargetRecursively(subject, target, path)
path = path or {}
local targetType, newPath
for k, targetValue in pairs(target) do
targetType, newPath = type(targetValue), copyTables({}, path)
table.insert(newPath, tostring(k))
if targetType == "number" then
assert(type(subject[k]) == "number",
"Parameter '" .. table.concat(newPath, "/") .. "' is missing from subject or isn't a number")
elseif targetType == "table" then
checkSubjectAndTargetRecursively(subject[k], targetValue, newPath)
else
assert(targetType == "number",
"Parameter '" .. table.concat(newPath, "/") .. "' must be a number or table of numbers")
end
end
end
local function checkNewParams(duration, subject, target, easing)
assert(type(duration) == "number" and duration > 0,
"duration must be a positive number. Was " .. tostring(duration))
local tsubject = type(subject)
assert(tsubject == "table" or tsubject == "userdata",
"subject must be a table or userdata. Was " .. tostring(subject))
assert(type(target) == "table", "target must be a table. Was " .. tostring(target))
assert(type(easing) == "function", "easing must be a function. Was " .. tostring(easing))
checkSubjectAndTargetRecursively(subject, target)
end
local function getEasingFunction(easing)
easing = easing or "linear"
if type(easing) == "string" then
local name = easing
easing = Tween.easing[name]
if type(easing) ~= "function" then
error("The easing function name '" .. name .. "' is invalid")
end
end
return easing
end
local function performEasingOnSubject(subject, target, initial, clock, duration, easing)
local t, b, c, d
for k, v in pairs(target) do
if type(v) == "table" then
performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing)
else
t, b, c, d = clock, initial[k], v - initial[k], duration
subject[k] = easing(t, b, c, d)
end
end
end
-- Tween methods
---@param clock number
---@return boolean # true if the tween has expired
function Tween:set(clock)
assert(type(clock) == "number", "clock must be a positive number or 0")
self.initial = self.initial or copyTables({}, self.target, self.subject)
self.clock = clock
if self.clock <= 0 then
self.clock = 0
copyTables(self.subject, self.initial)
elseif self.clock >= self.duration then -- the tween has expired
self.clock = self.duration
copyTables(self.subject, self.target)
else
performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing)
end
return self.clock >= self.duration
end
function Tween:reset()
return self:set(0)
end
---@param dt number
---@return boolean # true if the tween has expired
function Tween:update(dt)
assert(type(dt) == "number", "dt must be a number")
return self:set(self.clock + dt)
end
-- Public interface
function Tween.new(duration, subject, target, easing)
easing = getEasingFunction(easing)
checkNewParams(duration, subject, target, easing)
return setmetatable({
duration = duration,
subject = subject,
target = target,
easing = easing,
clock = 0
}, Tween_mt)
end
return Tween
end
+175
View File
@@ -0,0 +1,175 @@
include "globals"
include "utils.tables"
include "utils.rendering"
--- Print with outline
function print_border(text, x, y, color, outline, fixed, scale, smallfont)
local outline = outline == nil
and ((color == 0 or color == nil) and 12 or 0)
or outline
-- diagonals
print(text, x + 1, y + 1, outline, fixed, scale, smallfont)
print(text, x + 1, y - 1, outline, fixed, scale, smallfont)
print(text, x - 1, y + 1, outline, fixed, scale, smallfont)
print(text, x - 1, y - 1, outline, fixed, scale, smallfont)
-- cardinals
print(text, x + 1, y, outline, fixed, scale, smallfont)
print(text, x - 1, y, outline, fixed, scale, smallfont)
print(text, x, y + 1, outline, fixed, scale, smallfont)
print(text, x, y - 1, outline, fixed, scale, smallfont)
print(text, x, y, color, fixed, scale, smallfont)
end
--- Print debug
-- function printd(text, x, y)
-- print_border(text, x, y, 12, 2)
-- end
--- Like print(), but with font() and alignment
---@param txt string
---@param x number
---@param y number
---@param color number
---@param align? number
---@param scale? number
---@return number
function prn(txt, x, y, color, align, scale)
align = align or ALIGN.Left
scale = scale or 1
set1bpp()
if align == ALIGN.Right then
x = x - prn_len(txt, scale)
elseif align == ALIGN.Center then
x = x - prn_len(txt, scale) / 2
end
if color ~= nil then
swap_color(1, color)
end
local len = font(txt, x, y, 0, 4, 0, false, scale)
swap_color(1, 1)
set4bpp()
return len
end
---Like prn() but with an outline
---@param any string
---@param x number
---@param y number
---@param color number
---@param align? number
---@param border_color? number
---@param scale? number
---@return number
function prn_border(txt, x, y, color, align, border_color, scale)
if not border_color then
return prn(txt, x, y, color, align, scale)
end
align = align or ALIGN.Left
border_color = border_color or 0
scale = scale or 1
-- diagonals
prn(txt, x + 1, y + 1, border_color, align, scale)
prn(txt, x + 1, y - 1, border_color, align, scale)
prn(txt, x - 1, y + 1, border_color, align, scale)
prn(txt, x - 1, y - 1, border_color, align, scale)
-- cardinals
prn(txt, x + 1, y, border_color, align, scale)
prn(txt, x - 1, y, border_color, align, scale)
prn(txt, x, y + 1, border_color, align, scale)
prn(txt, x, y - 1, border_color, align, scale)
return prn(txt, x, y, color, align, scale)
end
---like prn_border() but with floaty text
---@param txt any
---@param x any
---@param y any
---@param color any
---@param align any
---@param border_color? any
---@param scale? any
function prn_border_floaty(txt, x, y, color, align, border_color, scale, delay)
delay = delay or 0
align = align or ALIGN.Left
border_color = border_color or 0
local len = prn_len(txt, scale)
prn_len(txt, scale)
if align == ALIGN.Right then
x = x - len
elseif align == ALIGN.Center then
x = x - len / 2
end
for i = 1, #txt do
local c = txt:sub(i, i)
local y = y + math.sin(((i+delay) * 100 + time()) / 200) * 3
x = x + prn_border(c, x, y, color, ALIGN.Left, border_color, scale)
end
return len
end
function swap_color(index, color)
poke4(PALETTE_MAP * 2 + index, color)
end
function reset_colors(...)
args = { ... }
if #args > 0 then
for _, c in ipairs(args) do
swap_color(c, c)
end
else
for i = 0, 16 do
poke4(PALETTE_MAP * 2 + i, i)
end
end
end
local _cachedPrintLen = {}
function print_len(txt)
if _cachedPrintLen[txt] then return _cachedPrintLen[txt] end
local len = print(txt, -1000, -1000)
_cachedPrintLen[txt] = len
return len
end
local _cachedFontLen = {}
function prn_len(txt, scale)
scale = scale or 1
if scale == 0 then scale = 1 end
local bpp = peek4(2 * 0x3ffc) -- get the current bpp value to reset it after
set1bpp()
if _cachedFontLen[txt] then return _cachedFontLen[txt] end
local len = font(txt, -1000, -1000, 0, 4, 0, false, scale)
_cachedFontLen[txt] = len
poke4(2 * 0x3ffc, bpp)
return len
end
function set1bpp()
poke4(2 * 0x3ffc, 8) -- 0b1000
end
function set4bpp()
poke4(2 * 0x3ffc, 2) -- 0b0010
end
function point_in_rect(x, y, rx, ry, rw, rh)
return x >= rx and x <= rx + rw and y >= ry and y <= ry + rh
end
function mouse_in_rect(rect)
local mx, my = mouse()
return point_in_rect(mx, my, rect.x, rect.y, rect.width, rect.height)
end
function clamp(min, val, max)
return math.max(math.min(max, val), min)
end
function normalize(val, min, max)
min = min or 0
max = max or val
return (val - min) / (max - min)
end