Merge branch 'feature/library_split'
This splits off most reusable code snippets into a separate libraries submodule
This commit is contained in:
commit
394f161604
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "nulllib"]
|
||||
path = nulllib
|
||||
url = https://gitea.protogen.io/nullbite/nulllib-lua.git
|
7
TODO.md
7
TODO.md
@ -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
1
nulllib
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c2c1bc372d17cdda06f0971f94c334e011e4de72
|
504
script.lua
504
script.lua
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user