Merge branch 'feature/library_split'

This splits off most reusable code snippets into a separate libraries
submodule
This commit is contained in:
NullBite 2023-06-21 21:28:24 -04:00
commit 394f161604
Signed by: nullbite
GPG Key ID: 6C4D545385D4925A
4 changed files with 26 additions and 489 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "nulllib"]
path = nulllib
url = https://gitea.protogen.io/nullbite/nulllib-lua.git

View File

@ -10,6 +10,7 @@
## Rewrite
- [ ] fix data api
- [ ] ConfigAPI
- [ ] reimplement avatar settings
- [ ] fix sound
- [ ] fix Blockbench animations
@ -24,9 +25,9 @@
- [ ] fix UVManager with matrices or something
- [ ] add swap to skin texture
- [ ] split off large snippets of code into separate files
- [ ] PartsManager
- [ ] UVManager
- [ ] utility functions
- [.] PartsManager
- [.] UVManager
- [.] utility functions
- [ ] cleanup tick functions, timers
- [ ] cleanup armor code to make less redundant

1
nulllib Submodule

@ -0,0 +1 @@
Subproject commit c2c1bc372d17cdda06f0971f94c334e011e4de72

View File

@ -14,317 +14,16 @@ ping=pings
TEXTURE_WIDTH = 256
TEXTURE_HEIGHT = 256
LOG_LEVEL="INFO"
util = require("nulllib.util")
logging = require("nulllib.logging")
timers=require("nulllib.timers")
nmath=require("nulllib.math")
PartsManager=require("nulllib.PartsManager")
UVManager=require("nulllib.UVManager")
-- Basic logging --{{{
do
logging = {}
local loglevels={
["SILENT"]=0,
["FATAL"]=1,
["ERROR"]=2,
["WARN"]=3,
["INFO"]=4,
["DEBUG"]=5,
["TRACE"]=6
}
-- default log level
local loglevel="INFO"
function setLogLevel(level)
loglevel=loglevels[level] and level or loglevel
end
setLogLevel(LOG_LEVEL)
local function printLog(severity, message)
if (loglevels[loglevel]) >= severity then
log("[" .. loglevel .. "] " .. message)
end
end
function logging.fatal(message) printLog(1, message) end
function logging.error(message) printLog(2, message) end
function logging.warn(message) printLog(3, message) end
function logging.info(message) printLog(4, message) end
function logging.debug(message) printLog(5, message) end
function logging.trace(message) printLog(6, message) end
end
-- }}}
-- utility functions -- {{{
--- Create a string representation of a table
--- @param o table
function dumpTable(o)
if type(o) == 'table' then
local s = '{ '
local first_loop=true
for k,v in pairs(o) do
if not first_loop then
s = s .. ', '
end
first_loop=false
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. dumpTable(v)
end
return s .. '} '
else
return tostring(o)
end
end
do
local function format_any_value(obj, buffer)
local _type = type(obj)
if _type == "table" then
buffer[#buffer + 1] = '{"'
for key, value in next, obj, nil do
buffer[#buffer + 1] = tostring(key) .. '":'
format_any_value(value, buffer)
buffer[#buffer + 1] = ',"'
end
buffer[#buffer] = '}' -- note the overwrite
elseif _type == "string" then
buffer[#buffer + 1] = '"' .. obj .. '"'
elseif _type == "boolean" or _type == "number" then
buffer[#buffer + 1] = tostring(obj)
else
buffer[#buffer + 1] = '"???' .. _type .. '???"'
end
end
--- Dumps object as UNSAFE json, i stole this from stackoverflow so i could use json.tool to format it so it's easier to read
function dumpJSON(obj)
if obj == nil then return "null" else
local buffer = {}
format_any_value(obj, buffer)
return table.concat(buffer)
end
end
end
-- TODO: accept model part to determine texture width and height
---@param uv table
function UV(uv)
return vec(
uv[1]/TEXTURE_WIDTH,
uv[2]/TEXTURE_HEIGHT
)
end
---@param inputstr string
---@param sep string
function splitstring (inputstr, sep)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
---@param input string
function unstring(input)
if input=="nil" then
return nil
elseif input == "true" or input == "false" then
return input=="true"
elseif tonumber(input) ~= nil then
return tonumber(input)
else
return input
end
end
---@param func function
---@param table table
function map(func, table)
local t={}
for k, v in pairs(table) do
t[k]=func(v)
end
return t
end
---@param func function
---@param table table
function filter(func, table)
local t={}
for k, v in pairs(table) do
if func(v) then
t[k]=v
end
end
return t
end
---@param tbl table
---@param val any
function has_value(tbl, val)
for _, v in pairs(tbl) do
if v==val then return true end
end
return false
end
--- Unordered reduction, only use when working with dictionaries and
--- execution order does not matter
---@param tbl table Table to reduce
---@param func function Function used to reduce table
---@param init any Initial operand for reduce function
function reduce(func, tbl, init)
local result = init
local first_loop = true
for _, v in pairs(tbl) do
if first_loop and init == nil then
result=v
else
result = func(result, v)
end
first_loop=false
end
return result
end
--- Ordered reduction, does not work with dictionaries
---@param tbl table Table to reduce
---@param func function Function used to reduce table
---@param init any Initial operand for reduce function
function ireduce(func, tbl, init)
local result = init
local first_loop = true
for _, v in ipairs(tbl) do
if first_loop and init == nil then
result=v
else
result = func(result, v)
end
first_loop=false
end
return result
end
--- Merge two tables. First table value takes precedence when conflict occurs.
---@param tb1 table
---@param tb2 table
function mergeTable(tb1, tb2)
local t={}
for k, v in pairs(tb1) do
t[k]=v
end
for k, v in pairs(tb2) do
if type(k)=="number" then
table.insert(t, v)
else
if t[k]==nil then
t[k]=v
end
end
end
return t
end
function debugPrint(var)
print(var)
return var
end
function debugPrintTable(var)
printTable(var)
return var
end
--- Recursively walk a model tree and return a table containing the group and each of its sub-groups
--- @param group table The group to recurse
--- @return table Resulting table
function recurseModelGroup(group)
local t={}
table.insert(t, group)
if group:getType()=="GROUP" then
for k, v in pairs(group:getChildren()) do
for _, v2 in pairs(recurseModelGroup(v)) do
table.insert(t, v2)
end
end
end
return t
end
-- }}}
-- Timer (not mine lol) -- {{{
-- TODO investigate if events can replace some of this
do
local timers = {}
function wait(ticks,next)
table.insert(timers, {t=world.getTime()+ticks,n=next})
end
local function tick()
for key,timer in pairs(timers) do
if world.getTime() >= timer.t then
timer.n()
table.remove(timers,key)
end
end
end
events.TICK:register(function() if player then tick() end end, "timer")
end
-- named timers (this one is mine but heavily based on the other) --
-- if timer is armed twice before expiring it will only be called once) --
do
local timers = {}
function namedWait(ticks, next, name)
-- main difference, this will overwrite an existing timer with
-- the same name
timers[name]={t=world.getTime()+ticks,n=next}
end
local function tick()
for key, timer in pairs(timers) do
if world.getTime() >= timer.t then
timer.n()
timers[key]=nil
end
end
end
events.TICK:register(function() if player then tick() end end, "named_timer")
end
-- named cooldowns
do
local timers={}
function cooldown(ticks, name)
if timers[name] == nil then
timers[name]={t=world.getTime()+ticks}
return true
end
return false
end
local function tick()
for key, timer in pairs(timers) do
if world.getTime() >= timer.t then
timers[key]=nil
end
end
end
events.TICK:register(function() if player then tick() end end, "cooldown")
end
function rateLimit(ticks, next, name)
if cooldown(ticks+1, name) then
namedWait(ticks, next, name)
end
end
-- }}}
-- math functions
lerp=math.lerp -- this is implemented in figura now
wave=nmath.wave
-- syncState {{{
do
@ -369,15 +68,6 @@ function ping.syncState(tbl)
end
-- }}}
-- Math {{{
--- Sine function with period and amplitude
--- @param x number Input value
--- @param period number Period of sine wave
--- @param amp number Peak amplitude of sine wave
function wave(x, period, amp) return math.sin((2/period)*math.pi*(x%period))*amp end
function lerp(a, b, t) return a + ((b - a) * t) end
-- }}}
-- Master and local state variables -- {{{
-- Local State (these are copied by pings at runtime) --
local_state={}
@ -417,8 +107,8 @@ do
end
savedData=data.loadAll()
end
skin_state=mergeTable(
map(unstring,data.loadAll()),
skin_state=util.mergeTable(
util.map(util.parse,data.loadAll()),
defaults)
else
skin_state=defaults
@ -448,164 +138,6 @@ end
-- }}}
-- PartsManager -- {{{
do
PartsManager={}
local pm={}
--- ensure part is initialized
local function initPart(part)
local part_key=part
if pm[part_key] == nil then
pm[part_key]={}
end
pm[part_key].part=part
if pm[part_key].functions == nil then
pm[part_key].functions = {}
end
if pm[part_key].init==nil then
pm[part_key].init="true"
end
end
--- Add function to part in PartsManager.
--- @param part table Any object with a setEnabled() method.
--- @param func function Function to add to model part's function chain.
--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
function PartsManager.addPartFunction(part, func, init)
initPart(part)
local part_key=part
if init ~= nil then
pm[part_key].init=init
end
table.insert(pm[part_key]["functions"], func)
end
--- Set initial value for chain.
--- @param part table Any object with a setEnabled() method.
--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
function PartsManager.setInitialValue(part, init)
assert(init~=nil)
initPart(part)
local part_key=part
pm[part_key].init=init
end
--- Set initial value for chain on all objects in table.
--- @param group table A table containing objects with a setEnabled() method.
--- @param init? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
function PartsManager.setGroupInitialValue(group, init)
assert(init~=nil)
for _, v in pairs(group) do
PartsManager.setInitialValue(v, init)
end
end
--- Evaluate a part's chain to determine if it should be visible.
--- @param part table An object managed by PartsManager.
function PartsManager.evaluatePart(part)
local part_key=part
assert(pm[part_key] ~= nil)
local evalFunc=function(x, y) return y(x) end
local init=pm[part_key].init
return ireduce(evalFunc, pm[part_key].functions, true)
end
local evaluatePart=PartsManager.evaluatePart
--- Refresh (enable or disable) a part based on the result of it's chain.
--- @param part table An object managed by PartsManager.
function PartsManager.refreshPart(part)
local part_enabled=evaluatePart(part)
part:setVisible(part_enabled)
return part_enabled
end
--- Refresh all parts managed by PartsManager.
function PartsManager.refreshAll()
for _, v in pairs(pm) do
PartsManager.refreshPart(v.part)
end
end
--- Add function to list of parts in PartsManager
--- @param group table A table containing objects with a setEnabled() method.
--- @param func function Function to add to each model part's function chain.
--- @param default? boolean Default value for chain. Should only be set once, subsequent uses overwrite the entire chain's initial value.
function PartsManager.addPartGroupFunction(group, func, default)
for _, v in ipairs(group) do
PartsManager.addPartFunction(v, func, default)
end
end
end
-- }}}
-- UVManager {{{
--
-- TODO: accept model part for built-in UV management, automatic texture size
do
local mt={}
--- @class UVManager
UVManager = {
step=vec(0,0),
offset=vec(0,0),
positions={},
part=nil,
dimensions=nil
}
mt.__index=UVManager
--- @return UVManager
--- @param step Vector2 A vector representing the distance between UVs
--- @param offset Vector2 A vector represnting the starting point for UVs, or nil
--- @param positions table A dictionary of names and offset vectors
--- @param part ModelPart Model part to manage
function UVManager.new(self, step, offset, positions, part)
local t={}
if step ~= nil then t.step=step end
if offset ~= nil then t.offset=offset end
if positions ~= nil then t.positions=positions end
if part ~= nil then
UVManager.setPart(t, part)
end
t=setmetatable(t, mt)
return t
end
--- @param part ModelPart Model part to manage
function UVManager.setPart(self, part)
self.part=part
self.dimensions=part:getTextureSize()
end
function UVManager.getUV(self, input)
local vect={}
local stp=self.step
local offset=self.offset
if type(input) == "string" then
if self.positions[input] == nil then return nil end
vect=self.positions[input]
else
vect=vectors.of(input)
end
local u=offset.x+(vect.x*stp.x)
local v=offset.y+(vect.y*stp.y)
if self.dimensions ~= nil then
-- TODO override for my specific texture, replace this with matrix stuff
-- (get rid of division once you figure out how setUVMatrix works)
return vec(u/(self.dimensions.x/2), v/(self.dimensions.y/2))
else
return UV{u, v}
end
end
function UVManager.setUV(self, input)
if self.part == nil then return false end
self.part:setUV(self:getUV(input))
end
end
-- }}}
-- Parts, groups, other constants -- {{{
HEAD=model.Head.Head
FACE=model.Head.Face
@ -680,7 +212,7 @@ BODY_EMISSIVES={
FACE_EMISSIVES={
model.Head.Face
}
EMISSIVES=mergeTable(BODY_EMISSIVES, FACE_EMISSIVES)
EMISSIVES=util.mergeTable(BODY_EMISSIVES, FACE_EMISSIVES)
COLORS={}
COLORS.neutral=vec(127/255,127/255,255/255)
COLORS.hurt= vec(1, 0, 63/255)
@ -724,11 +256,11 @@ do
PM.addPartFunction(vanilla_model.CAPE, function(last) return last and not local_state.tail_enabled end)
--- Custom state
-- local tail_parts=mergeTable({model.Body.TailBase}, TAIL_BONES)
-- local tail_parts=util.mergeTable({model.Body.TailBase}, TAIL_BONES)
local tail_parts={model.Body.MTail1, model.Body.TailBase}
-- TODO: old vanilla_partial groups, use these for texture swap
-- local vanilla_partial_disabled=mergeTable(MAIN_GROUPS, {model.Body.Body, model.Body.BodyLayer})
-- local vanilla_partial_disabled=util.mergeTable(MAIN_GROUPS, {model.Body.Body, model.Body.BodyLayer})
-- local vanilla_partial_enabled={model.Head, model.Body}
-- Show shattered only at low health
@ -743,7 +275,7 @@ do
PM.addPartGroupFunction(tail_parts, function(last) return last and aquaticTailVisible() end)
--- Armor state
local all_armor=reduce(mergeTable, {VANILLA_GROUPS.ARMOR, TAIL_LEGGINGS, TAIL_BOOTS})
local all_armor=util.reduce(util.mergeTable, {VANILLA_GROUPS.ARMOR, TAIL_LEGGINGS, TAIL_BOOTS})
PM.addPartGroupFunction(all_armor, function(last) return last and local_state.armor_enabled end)
-- Only show armor if equipped
PM.addPartFunction(model.Body.MTail1.MTail2.MTail3.Boot, function(last) return last and armor_state.boots end)
@ -804,7 +336,7 @@ do
-- This one is for more explicit "flashes" such as player hurt
-- animations, get color explicitly
setColor(COLORS[expression])
namedWait(ticks, resetExpression, "resetExpression")
timers.namedWait(ticks, resetExpression, "resetExpression")
end
function resetExpression()
lock_color=false
@ -1113,7 +645,7 @@ function player_init()
old_state.health=local_state.health
-- TODO possibly reconsider if this should be redone
-- actually it's probably fine, it's jsut here because i forget visibility settings
-- local all_parts=recurseModelGroup(model)
-- local all_parts=util.recurseModelGroup(model)
-- for k, v in pairs(all_parts) do
-- v:setVisible(nil)
@ -1156,7 +688,7 @@ function tick()
if world.getTimeOfDay() % 20 == 0 then
if player:getPose() == "SLEEPING" then
if cooldown(20*4, "snore") then
if timers.cooldown(20*4, "snore") then
snore()
end
end