figura-protogen/script.lua

1211 lines
32 KiB
Lua

-- vim: set foldmethod=marker ts=4 sw=4 :
-- TODO rewrite variables: armor_model, model
--- Initial definitions ---
-- player model backwards compatibility
model=models.player_model
armor_model={
["BOOTS"]=vanilla_model.BOOTS,
["LEGGINGS"]=vanilla_model.LEGGINGS,
["CHESTPLATE"]=vanilla_model.CHESTPLATE,
["HELMET"]=vanilla_model.HELMET
}
ping=pings
-- Texture dimensions --
TEXTURE_WIDTH = 256
TEXTURE_HEIGHT = 256
LOG_LEVEL="INFO"
-- 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
-- }}}
-- syncState {{{
do
local counter=0
function syncState()
-- ping.setSnoring(skin_state.snore_enabled)
if counter < 3 then
ping.syncState((setLocalState()))
counter=counter+1
end
end
local function cooldownDecay()
if counter>0 and world.getTime() % 4 == 0 then
counter = counter - 1
end
end
events.TICK:register(cooldownDecay,"syncStateCooldown")
end
do
local pm_refresh=false
function pmRefresh()
pm_refresh=true
end
function doPmRefresh()
if pm_refresh then
PartsManager.refreshAll()
pm_refresh=false
end
end
end
function ping.syncState(tbl)
logging.debug("ping.syncState")
for k, v in pairs(tbl) do
local_state[k]=v
end
pmRefresh()
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={}
old_state={}
-- master state variables and configuration (do not access within pings) --
do
local is_host=host:isHost()
local defaults={
["armor_enabled"]=true,
["vanilla_enabled"]=false,
["snore_enabled"]=true,
["print_settings"]=false,
["vanilla_partial"]=false,
["tail_enabled"]=true,
["aquatic_enabled"]=true,
["aquatic_override"]=false
}
function setLocalState()
if is_host then
for k, v in pairs(skin_state) do
local_state[k]=v
end
else
for k, v in pairs(defaults) do
if local_state[k] == nil then local_state[k]=v end
end
end
return local_state
end
-- TODO reimplement with new data API
-- if is_host then
if false then
local savedData=data.loadAll()
if savedData == nil then
for k, v in pairs(defaults) do
data.save(k, v)
end
savedData=data.loadAll()
end
skin_state=mergeTable(
map(unstring,data.loadAll()),
defaults)
else
skin_state=defaults
end
setLocalState()
end
function printSettings()
print("Settings:")
for k, v in pairs(skin_state) do
print(tostring(k)..": "..tostring(v))
end
end
if skin_state.print_settings==true then
printSettings()
end
function setState(name, state)
if state == nil then
skin_state[name]=not skin_state[name]
else
skin_state[name]=state
end
-- TODO
-- data.save(name, skin_state[name])
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
SHATTER=model.Head.Shatter
VANILLA_PARTIAL={}
VANILLA_GROUPS={
["HEAD"]={vanilla_model.HEAD, vanilla_model.HAT},
["BODY"]={vanilla_model.BODY, vanilla_model.JACKET},
["LEFT_ARM"]={vanilla_model.LEFT_ARM, vanilla_model.LEFT_SLEEVE},
["RIGHT_ARM"]={vanilla_model.RIGHT_ARM, vanilla_model.RIGHT_SLEEVE},
["LEFT_LEG"]={vanilla_model.LEFT_LEG, vanilla_model.LEFT_PANTS_LEG},
["RIGHT_LEG"]={vanilla_model.RIGHT_LEG, vanilla_model.RIGHT_PANTS_LEG},
["OUTER"]={ vanilla_model.HAT, vanilla_model.JACKET, vanilla_model.LEFT_SLEEVE, vanilla_model.RIGHT_SLEEVE, vanilla_model.LEFT_PANTS_LEG, vanilla_model.RIGHT_PANTS_LEG },
["INNER"]={ vanilla_model.HEAD, vanilla_model.BODY, vanilla_model.LEFT_ARM, vanilla_model.RIGHT_ARM, vanilla_model.LEFT_LEG, vanilla_model.RIGHT_LEG },
["ALL"]={ vanilla_model.HEAD, vanilla_model.BODY, vanilla_model.LEFT_ARM, vanilla_model.RIGHT_ARM, vanilla_model.LEFT_LEG, vanilla_model.RIGHT_LEG, vanilla_model.HAT, vanilla_model.JACKET, vanilla_model.LEFT_SLEEVE, vanilla_model.RIGHT_SLEEVE, vanilla_model.LEFT_PANTS_LEG, vanilla_model.RIGHT_PANTS_LEG },
["ARMOR"]=armor_model
}
-- these are inefficient, redundancy is better in this case
-- for _, v in pairs(VANILLA_GROUPS.INNER) do table.insert(VANILLA_GROUPS.ALL,v) end
-- for _, v in pairs(VANILLA_GROUPS.OUTER) do table.insert(VANILLA_GROUPS.ALL,v) end
-- for _, v in pairs(armor_model) do table.insert(VANILLA_GROUPS.ARMOR, v) end
MAIN_GROUPS={model.Head, model.RightArm, model.LeftArm, model.RightLeg, model.LeftLeg, model.Body } -- RightArm LeftArm RightLeg LeftLeg Body Head
TAIL_LEGGINGS={
model.Body.LeggingsTop,
model.Body.LeggingsTopTrimF,
model.Body.LeggingsTopTrimB,
model.Body.MTail1.Leggings,
model.Body.MTail1.LeggingsTrim,
model.Body.MTail1.MTail2.LeggingsBottom
}
TAIL_LEGGINGS_COLOR={
model.Body.LeggingsTopTrimF,
model.Body.LeggingsTopTrimB,
model.Body.MTail1.Leggings,
model.Body.MTail1.LeggingsTrim,
model.Body.MTail1.MTail2.LeggingsBottom
}
TAIL_BOOTS={
model.Body.MTail1.MTail2.MTail3.Boot,
model.Body.MTail1.MTail2.MTail3.LeatherBoot
}
TAIL_BONES={
model.Body.MTail1,
model.Body.MTail1.MTail2,
model.Body.MTail1.MTail2.MTail3,
model.Body.MTail1.MTail2.MTail3.MTail4
}
REG_TAIL_BONES={
model.Body_Tail,
model.Body_Tail.Tail_L2,
model.Body_Tail.Tail_L2.Tail_L3,
model.Body_Tail.Tail_L2.Tail_L3.fin
}
BODY_EMISSIVES={
model.Body.MTail1.MTailDots1,
model.Body.MTail1.MTail2.MTailDots2,
model.Body.MTail1.MTail2.MTail3.MTailDots3,
model.Body.MTail1.MTail2.MTail3.MTail4.MTailDots4,
model.Body_Tail.TailDots1,
model.Body_Tail.Tail_L2.TailDots2,
model.Body_Tail.Tail_L2.Tail_L3.TailDots3,
model.Body_Tail.Tail_L2.Tail_L3.fin.TailDots4,
model.Head.EmDots,
model.LeftArm.LeftArmEm,
model.RightArm.RightArmEm,
model.LeftLeg.LeftLegEm,
model.RightLeg.RightLegEm
}
FACE_EMISSIVES={
model.Head.Face
}
EMISSIVES=mergeTable(BODY_EMISSIVES, FACE_EMISSIVES)
COLORS={}
COLORS.neutral=vec(127/255,127/255,255/255)
COLORS.hurt= vec(1, 0, 63/255)
COLORS.lava= vec(1, 128/255, 64/255)
-- prev 255 160 192
COLORS.owo= vec(1, 128/255, 160/255)
COLORS["end"]="end"
for k, v in pairs(EMISSIVES) do
v:setColor(COLORS.neutral)
end
-- }}}
-- PartsManager rules {{{
-- Vanilla rules
do
-- TODO
function getVanillaVisible()
return (not avatar:canEditVanillaModel()) or vanilla_model.PLAYER:getVisible()
end
local function vanillaPartial()
if local_state.vanilla_enabled then
return false
end
return local_state.vanilla_partial
end
local function forceVanilla()
print(vanilla_model.PLAYER:getVisible())
return not avatar:canEditVanillaModel() or local_state.vanilla_enabled or vanilla_model.PLAYER:getVisible()
end
-- eventually replace this with an instance once PartsManager becomes a class
local PM=PartsManager
--- Vanilla state
-- no cape if tail enabled (it clips)
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={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_enabled={model.Head, model.Body}
-- Show shattered only at low health
PM.addPartFunction(SHATTER, function(last) return last and local_state.health <= 5 end)
-- Enable tail setting
PM.addPartFunction(model.Body_Tail, function(last) return last and local_state.tail_enabled end)
-- no legs, regular tail in water if tail enabled
local mtail_mutually_exclusive={model.LeftLeg, model.RightLeg, model.Body_Tail, armor_model.LEGGINGS, armor_model.BOOTS}
PM.addPartGroupFunction(mtail_mutually_exclusive, function(last) return last and not aquaticTailVisible() end)
-- aquatic tail in water
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})
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)
PM.addPartFunction(model.Body.MTail1.MTail2.MTail3.LeatherBoot, function(last) return last and armor_state.leather_boots end)
PM.addPartGroupFunction(TAIL_LEGGINGS, function(last) return last and armor_state.leggings end)
-- Disable when vanilla_enabled
PM.addPartGroupFunction(MAIN_GROUPS, function(last) return last and not getVanillaVisible() end)
end
SNORES={"snore-1", "snore-2", "snore-3"}
-- }}}
-- Expression change -- {{{
do
local expressions={}
expressions.neutral=vec(0,0)
expressions["end"]=expressions.neutral
expressions.hurt=vec(0,1)
expressions.owo=vec(0,2)
local expruvm=UVManager:new(vec(8, 8), nil, expressions, FACE)
current_expression="neutral"
-- color/expression rules
function getBestColor()
if current_expression=="owo" then
return COLORS.owo
elseif player:isInLava() or player:getDimensionName()=="minecraft:the_nether" then
return COLORS.lava
else
return COLORS.neutral
end
end
function getBestExpression()
return "neutral"
end
function setColor(col)
if not lock_color then
col=(col~=nil) and col or getBestColor()
for _, v in pairs(EMISSIVES) do
v:setColor(col)
-- TODO
-- v:setShader("None")
end
end
end
-- Expression change code
function setExpression(expression)
current_expression=expression
expruvm:setUV(current_expression)
-- This expression sticks, so do not set color explicitly
setColor()
end
function changeExpression(expression, ticks)
expruvm:setUV(expression)
-- This one is for more explicit "flashes" such as player hurt
-- animations, get color explicitly
setColor(COLORS[expression])
namedWait(ticks, resetExpression, "resetExpression")
end
function resetExpression()
lock_color=false
expruvm:setUV(current_expression)
setColor()
end
function hurt()
lock_color=false
changeExpression("hurt", 10)
lock_color=true
PartsManager.refreshPart(SHATTER)
end
end
-- }}}
-- Action Wheel & Pings -- {{{
-- TODO
do
wheel={}
local wheel_index=1
function wheelScroll(change)
wheel_index=((wheel_index-1)+change)%#wheel+1
action_wheel:setPage(wheel[wheel_index])
print("page: " .. wheel_index)
end
wheel[1]=action_wheel:newPage()
action_wheel:setPage(wheel[1])
action_wheel.scroll=wheelScroll
end
wheel[1]:newAction():title('test expression'):onLeftClick(function() ping.expressionTest() end)
function ping.expressionTest()
logging.debug("ping.expressionTest")
changeExpression("hurt", 10)
end
wheel[1]:newAction():title('log health'):onLeftClick(function() print(player:getHealth()) end)
wheel[1]:newAction():title('Toggle Armor'):onLeftClick(function() setArmor() end)
wheel[1]:newAction():title('T-Pose'):onLeftClick(function() ping.tPose() end)
wheel[1]:newAction():title('UwU'):onLeftClick(function() ping.expr("owo") end)
-- action_wheel.SLOT_8.setTitle('sssss...')
-- action_wheel.SLOT_8.setItem("minecraft:creeper_head")
-- action_wheel.SLOT_8.setFunction(function() switch_model('misc/Creeper') end)
-- Pings --
--- Damage function --
function ping.expr(expr)
logging.debug("ping.expr")
local val=(expr==current_expression) and "neutral" or expr
setExpression(val)
end
function ping.oof(health) -- This is a replacement for onDamage, that function doesn't sync for some reason
logging.debug("ping.oof")
hurt()
end
--- Toggle Armor ---
function setArmor(state)
setState("armor_enabled", state)
syncState()
end
function snore() end
-- TODO re-enable snoring
-- do
-- local snore_enabled=false
-- local snore_index=1
-- function snore()
-- if snore_enabled then
-- -- TODO
-- -- sound.playCustomSound(SNORES[snore_index],
-- -- player.getPos(), vectors.of{20,1})
-- snore_index=snore_index%#SNORES+1
-- end
-- end
--
-- function setSnoring(state)
-- setState("snore_enabled", state)
-- ping.setSnoring(skin_state.snore_enabled)
-- end
--
-- function ping.setSnoring(state)
-- snore_enabled=state
-- end
-- end
--- Toggle Vanilla ---
function setVanilla(state)
setState("vanilla_enabled", state)
syncState()
end
function ping.tPose()
logging.debug("ping.tPose")
local_state.emote_vector=player:getPos()
-- TODO
-- animation.tpose.start()
end
-- }}}
-- Tail stuff {{{
function aquaticTailVisible()
tail_cooldown=tail_cooldown or 0
return (local_state.aquatic_enabled and (player:isInWater() or player:isInLava()) or local_state.aquatic_override or tail_cooldown>0) and not getVanillaVisible() end
function updateTailVisibility()
local anim=player:getPose()
local water=player:isInWater()
local lava=player:isInLava()
tail_cooldown=(tail_cooldown and tail_cooldown > 0) and tail_cooldown-1 or 0
if aquaticTailVisible() and (anim=="SLEEPING" or anim=="SPIN_ATTACK" or anim=="FALL_FLYING" or water or lava) then
tail_cooldown=anim=="SPIN_ATTACK" and 60 or (tail_cooldown >= 10 and tail_cooldown or 10)
end
if old_state.aquaticTailVisible ~= aquaticTailVisible() then pmRefresh() end
old_state.aquaticTailVisible=aquaticTailVisible()
end
-- armor {{{
armor_color={}
armor_color['leather'] = {131 /255 , 84 /255 , 50 /255}
armor_glint={}
armor_state={}
armor_state['leggings']=false
armor_state['boots']=false
armor_state['leather_boots']=false
do
local positions={}
positions['leather']=vec(0, 0)
positions['iron']=vec(0, 1)
positions['chainmail']=vec(0, 2)
positions['golden']=vec(0, 3)
positions['diamond']=vec(0, 4)
positions['netherite']=vec(0, 5)
tailuvm=UVManager:new(vec(0, 19), nil, positions)
end
-- TODO fix code after optimization in prewrite
function armor()
if true then return nil end
-- ^ hacky way to disable a function without uncommenting the entire thing to not break git vcs
-- Get equipped armor, extract name from item ID
local leggings_item = player.getEquipmentItem(4)
local boots_item = player.getEquipmentItem(3)
local leggings = string.sub(leggings_item.getType(), 11, -10)
local boots = string.sub(boots_item.getType(), 11, -7)
if local_state.armor_enabled then
if old_state.leggings ~= leggings or old_state.armor_enabled ~= local_state.armor_enabled then
-- leggings
armor_glint.leggings=leggings_item.hasGlint()
local leggings_color=colorArmor(leggings_item) or armor_color[leggings]
local uv=tailuvm:getUV(leggings)
if uv ~= nil then
armor_state.leggings=true
for k, v in pairs(TAIL_LEGGINGS) do
v.setUV(uv)
end
if leggings=="leather" then
for k, v in pairs(TAIL_LEGGINGS_COLOR) do
v.setColor(leggings_color)
end
else
for k, v in pairs(TAIL_LEGGINGS) do
v.setColor({1, 1, 1})
end
end
else
armor_state.leggings=false
end
pmRefresh()
end
if old_state.boots ~= boots or old_state.armor_enabled ~= local_state.armor_enabled then
-- boots
armor_glint.boots=boots_item.hasGlint()
local boots_color=colorArmor(boots_item) or armor_color[boots]
local uv_boots=tailuvm:getUV(boots)
if uv_boots ~= nil then
armor_state.boots=true
for k, v in pairs(TAIL_BOOTS) do
v.setUV(uv_boots)
end
if boots=="leather" then
model.Body.MTail1.MTail2.MTail3.Boot.setColor(boots_color)
armor_state.leather_boots=true
else
model.Body.MTail1.MTail2.MTail3.Boot.setColor({1, 1, 1})
armor_state.leather_boots=false
end
else
armor_state.boots=false
end
pmRefresh()
end
else
armor_glint.leggings=false
armor_glint.boots=false
end
if armor_glint.leggings then
for _, v in pairs(TAIL_LEGGINGS) do
v.setShader("Glint")
end
else
for _, v in pairs(TAIL_LEGGINGS) do
v.setShader("None")
end
end
if armor_glint.boots then
for _, v in pairs(TAIL_BOOTS) do
v.setShader("Glint")
end
else
for _, v in pairs(TAIL_BOOTS) do
v.setShader("None")
end
end
old_state.boots=boots
old_state.leggings=leggings
old_state.armor_enabled=local_state.armor_enabled
end
function colorArmor(item)
local tag = item.tag
if tag ~= nil and tag.display ~= nil and tag.display.color ~= nil then
return vectors.intToRGB(tag.display.color)
end
end
-- }}}
function resetAngles(part)
part:setRot(vec(0,0,0))
end
function animateMTail(val)
local chest_rot = 3
local per=2*math.pi
model.Body:setRot(vec( wave(val, per, 3), 0, 0 ))
-- TODO vanilla model manipulation broke, add chestplate model
-- armor_model.CHESTPLATE:setRot(vec( -wave(val, per, math.rad(3)), 0, 0 ))
-- this makes it work with partial vanilla
-- vanilla_model.BODY:setRot(vec( -wave(val, per, math.rad(3)), 0, 0 ))
-- vanilla_model.JACKET:setRot(vec( -wave(val, per, math.rad(3)), 0, 0 ))
model.Body.LeggingsTopTrimF:setRot(vec( wave(val-1, per, 4), 0, 0 ))
model.Body.LeggingsTopTrimB:setRot(vec( wave(val-1, per, 4), 0, 0 ))
TAIL_BONES[1]:setRot(vec( wave(val-1, per, 7), 0, 0 ))
TAIL_BONES[2]:setRot(vec( wave(val-2, per, 8), 0, 0 ))
TAIL_BONES[3]:setRot(vec( wave(val-3, per, 12), 0, 0 ))
TAIL_BONES[4]:setRot(vec( wave(val-4, per, 15), 0, 0 ))
end
tail_original_rot={}
for k, v in ipairs(REG_TAIL_BONES) do
tail_original_rot[k]=v:getRot()
end
function animateTail(val)
local per_y=20*4
local per_x=20*6
for k, v in ipairs(REG_TAIL_BONES) do
local cascade=(k-1)*12
REG_TAIL_BONES[k]:setRot(vec( tail_original_rot[k].x + wave(val-cascade, per_x, 3), wave(val-cascade, per_y, 12), tail_original_rot[k].z ))
end
end
anim_tick=0
anim_cycle=0
old_state.anim_cycle=0
function animateTick()
anim_tick = anim_tick + 1
if aquaticTailVisible() then
local velocity = player:getVelocity()
if aquaticTailVisible() then
old_state.anim_cycle=anim_cycle
local player_speed = math.sqrt(velocity.x^2 + velocity.y^2 + velocity.z^2)
local animation=player:getPose()
local factor=(not player:isInWater() and (animation=="FALL_FLYING" or animation=="SPIN_ATTACK")) and 0.5 or 5
anim_cycle=anim_cycle + (player_speed*factor+0.75)
-- bubble animation would go here but i don't have that (yet)
end
else
old_state.anim_cycle=anim_cycle
anim_cycle=anim_cycle+1
end
end
-- }}}
-- initialize values -- {{{
function player_init()
local_state.health=player:getHealth()
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)
-- for k, v in pairs(all_parts) do
-- v:setVisible(nil)
-- end
setLocalState()
pmRefresh()
syncState()
events.ENTITY_INIT:remove("player_init")
end
events.ENTITY_INIT:register(function() return player_init() end, "player_init")
-- Initial configuration --
-- TODO x2 fix below, this entire block may not be needed with PartsManager
if avatar:canEditVanillaModel() then
vanilla_model.PLAYER:setVisible(false)
else
model:setVisible(false)
end
anim_tick=0
-- }}}
-- Tick function -- {{{
function hostTick()
local_state.health=player:getHealth()
if local_state.health ~= old_state.health then
if local_state.health < old_state.health then
ping.oof(local_state.health)
end
syncState()
end
end
function tick()
color_check=player:isInLava() ~= (player:getDimensionName()=="minecraft:the_nether")
if old_state.color_check~=color_check then
setColor()
end
-- optimization, only execute these once a second --
if world.getTimeOfDay() % 20 == 0 then
if player:getPose() == "SLEEPING" then
if cooldown(20*4, "snore") then
snore()
end
end
-- Sync state every 10 seconds
if world.getTimeOfDay() % (20*10) == 0 then
syncState()
end
end
hostTick()
-- TODO
-- if animation.tpose.isPlaying() and local_state.emote_vector.distanceTo(player.getPos()) >= 0.5 then
-- animation.tpose.stop()
-- end
-- Refresh tail armor state
armor()
-- Implements tail cooldown conditions
updateTailVisibility()
-- Animation code resides in this function
animateTick()
-- Check for queued PartsManager refresh
doPmRefresh()
-- End of tick --
old_state.health=player:getHealth()
old_state.color_check=color_check
local_state.anim=player:getPose()
end
events.TICK:register(function() if player then tick() end end, "main_tick")
-- }}}
-- Render function {{{
function render(delta)
if aquaticTailVisible() then
animateMTail((lerp(old_state.anim_cycle, anim_cycle, delta) * 0.2))
else
resetAngles(model.Body)
-- resetAngles(vanilla_model.BODY)
-- resetAngles(vanilla_model.JACKET)
-- resetAngles(armor_model.CHESTPLATE)
animateTail((lerp(old_state.anim_cycle, anim_cycle, delta)))
end
end
-- TODO this may break animation during death
events.RENDER:register(function(delta) if player then render(delta) end end, "main_render")
-- }}}