145 lines
No EOL
5 KiB
Lua
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 |