figura-skin/scripts/libs/SquAPI_modules/ear.lua
2025-11-23 01:10:42 -08:00

145 lines
No EOL
5 KiB
Lua

---@meta _
local squassets
local assetPath = "./SquAssets"
if pcall(require, assetPath) then squassets = require(assetPath) end
assert(squassets, "§4The ear module requires SquAssets, which was not found!§c")
---@class ear
local ear = {}
ear.all = {}
---*CONSTRUCTOR
---@param leftEar ModelPart The left ear's model path.
---@param rightEar? ModelPart The right ear's model path, if you don't have a right ear, just leave this blank or set to nil.
---@param rangeMultiplier? number Defaults to `1`, how far the ears should rotate with your head.
---@param horizontalEars? boolean Defaults to `false`, if you have elf-like ears(ears that stick out horizontally), set this to true.
---@param bendStrength? number Defaults to `2`, how much the ears should move when you move.
---@param doEarFlick? boolean Defaults to `true`, whether or not the ears should randomly flick.
---@param earFlickChance? number Defaults to `400`, how often the ears should flick in ticks, timer is random between 0 to n ticks.
---@param earStiffness? number Defaults to `0.1`, how stiff the ears should be.
---@param earBounce? number Defaults to `0.8`, how bouncy the ears should be.
---@return SquAPI.Ear
function ear:new(leftEar, rightEar, rangeMultiplier, horizontalEars, bendStrength, doEarFlick,
earFlickChance, earStiffness, earBounce)
---@class SquAPI.ear
local self = {}
assert(leftEar, "§4The first ear's model path is incorrect.§c")
self.leftEar = leftEar
self.rightEar = rightEar
self.horizontalEars = horizontalEars
self.rangeMultiplier = rangeMultiplier or 1
if self.horizontalEars then self.rangeMultiplier = self.rangeMultiplier / 2 end
self.bendStrength = bendStrength or 2
earStiffness = earStiffness or 0.1
earBounce = earBounce or 0.8
if doEarFlick == nil then doEarFlick = true end
self.doEarFlick = doEarFlick
self.earFlickChance = earFlickChance or 400
self.eary = squassets.BERP:new(earStiffness, earBounce)
self.earx = squassets.BERP:new(earStiffness, earBounce)
self.earz = squassets.BERP:new(earStiffness, earBounce)
self.targets = { 0, 0, 0 }
self.oldpose = "STANDING"
self.enabled = true
self = setmetatable(self, {__index = ear})
table.insert(ear.all, self)
return self
end
--*CONTROL FUNCTIONS
---ear enable handling
function ear:disable() self.enabled = false return self end
function ear:enable() self.enabled = true return self end
function ear:toggle() self.enabled = not self.enabled return self end
---@param bool boolean
function ear:setEnabled(bool)
self.enabled = bool or false
return self
end
--*UPDATE FUNCTIONS
function ear:tick()
if self.enabled then
local vel = math.min(math.max(-0.75, squassets.forwardVel()), 0.75)
local yvel = math.min(math.max(-1.5, squassets.verticalVel()), 1.5) * 5
local svel = math.min(math.max(-0.5, squassets.sideVel()), 0.5)
local headrot = squassets.getHeadRot()
local bend = self.bendStrength
if headrot[1] < -22.5 then bend = -bend end
--gives the ears a short push when crouching/uncrouching
local pose = player:getPose()
if pose == "CROUCHING" and self.oldpose == "STANDING" then
self.eary.vel = self.eary.vel + 5 * self.bendStrength
elseif pose == "STANDING" and self.oldpose == "CROUCHING" then
self.eary.vel = self.eary.vel - 5 * self.bendStrength
end
self.oldpose = pose
--main physics
if self.horizontalEars then
local rot = 10 * bend * (yvel + vel * 10) + headrot[1] * self.rangeMultiplier
local addrot = headrot[2] * self.rangeMultiplier
self.targets[2] = rot + addrot
self.targets[3] = -rot + addrot
else
self.targets[1] = headrot[1] * self.rangeMultiplier + 2 * bend * (yvel + vel * 15)
self.targets[2] = headrot[2] * self.rangeMultiplier - svel * 100 * self.bendStrength
self.targets[3] = self.targets[2]
end
--ear flicking
if self.doEarFlick then
if math.random(0, self.earFlickChance) == 1 then
if math.random(0, 1) == 1 then
self.earx.vel = self.earx.vel + 50
else
self.earz.vel = self.earz.vel - 50
end
end
end
else
self.leftEar:setOffsetRot(0, 0, 0)
self.rightEar:setOffsetRot(0, 0, 0)
end
end
---@param dt number Tick delta
function ear:render(dt, _)
if self.enabled then
self.eary:berp(self.targets[1], dt)
self.earx:berp(self.targets[2], dt)
self.earz:berp(self.targets[3], dt)
local rot3 = self.earx.pos / 4
local rot3b = self.earz.pos / 4
if self.horizontalEars then
local y = self.eary.pos / 4
self.leftEar:setOffsetRot(y, self.earx.pos / 3, rot3)
if self.rightEar then
self.rightEar:setOffsetRot(y, self.earz.pos / 3, rot3b)
end
else
self.leftEar:setOffsetRot(self.eary.pos, rot3, rot3)
if self.rightEar then
self.rightEar:setOffsetRot(self.eary.pos, rot3b, rot3b)
end
end
end
end
return ear