160 lines
No EOL
6.6 KiB
Lua
160 lines
No EOL
6.6 KiB
Lua
---@meta _
|
|
local squassets
|
|
local assetPath = "./SquAssets"
|
|
if pcall(require, assetPath) then squassets = require(assetPath) end
|
|
assert(squassets, "§4The tail module requires SquAssets, which was not found!§c")
|
|
|
|
|
|
---@class tail
|
|
local tail = {}
|
|
tail.all = {}
|
|
|
|
---*CONSTRUCTOR
|
|
---@param tailSegmentList table<ModelPart> The list of each individual tail segment of your tail.
|
|
---@param idleXMovement? number Defaults to `15`, how much the tail should sway side to side.
|
|
---@param idleYMovement? number Defaults to `5`, how much the tail should sway up and down.
|
|
---@param idleXSpeed? number Defaults to `1.2`, how fast the tail should sway side to side.
|
|
---@param idleYSpeed? number Defaults to `2`, how fast the tail should sway up and down.
|
|
---@param bendStrength? number Defaults to `2`, how strongly the tail moves when you move.
|
|
---@param velocityPush? number Defaults to `0`, this will cause the tail to bend when you move forward/backward, good if your tail is bent downward or upward.
|
|
---@param initialMovementOffset? number Defaults to `0`, this will offset the tails initial sway, this is good for when you have multiple tails and you want to desync them.
|
|
---@param offsetBetweenSegments? number Defaults to `1`, how much each tail segment should be offset from the previous one.
|
|
---@param stiffness? number Defaults to `0.005`, how stiff the tail should be.
|
|
---@param bounce? number Defaults to `0.9`, how bouncy the tail should be.
|
|
---@param flyingOffset? number Defaults to `90`, when flying, riptiding, or swimming, it may look strange to have the tail stick out, so instead it will rotate to this value(so use this to flatten your tail during these movements).
|
|
---@param downLimit? number Defaults to `-90`, the lowest each tail segment can rotate.
|
|
---@param upLimit? number Defaults to `45`, the highest each tail segment can rotate.
|
|
---@return SquAPI.Tail
|
|
function tail:new(tailSegmentList, idleXMovement, idleYMovement, idleXSpeed, idleYSpeed,
|
|
bendStrength, velocityPush, initialMovementOffset, offsetBetweenSegments,
|
|
stiffness, bounce, flyingOffset, downLimit, upLimit)
|
|
---@class SquAPI.tail
|
|
local self = setmetatable({}, {__index = tail})
|
|
|
|
--error checker
|
|
self.tailSegmentList = tailSegmentList
|
|
if type(self.tailSegmentList) == "ModelPart" then
|
|
self.tailSegmentList = {self.tailSegmentList}
|
|
end
|
|
assert(type(self.tailSegmentList) == "table",
|
|
"your tailSegmentList table seems to to be incorrect")
|
|
|
|
self.berps = {}
|
|
self.targets = {}
|
|
self.stiffness = stiffness or .005
|
|
self.bounce = bounce or .9
|
|
|
|
if type(self.tailSegmentList[2]) == "number" then --ah I see you stumbled across my custom tail list creator, if you curious ask me. tail must be >= 3 segments. Naming: tail, tailseg, tailseg2, tailseg3..., tailtip
|
|
local range = self.tailSegmentList[2]
|
|
local str = ""
|
|
if self.tailSegmentList[3] then
|
|
str = self.tailSegmentList[3]
|
|
end
|
|
|
|
self.tailSegmentList[2] = self.tailSegmentList[1][str .. "tailseg"]
|
|
for i = 2, range - 2 do
|
|
self.tailSegmentList[i + 1] = self.tailSegmentList[i][str .. "tailseg" .. i]
|
|
end
|
|
self.tailSegmentList[range] = self.tailSegmentList[range - 1][str .. "tailtip"]
|
|
end
|
|
|
|
for i = 1, #self.tailSegmentList do
|
|
assert(self.tailSegmentList[i]:getType() == "GROUP",
|
|
"§4The tail segment at position "..i.." of the table is not a group. The tail segments need to be groups that are nested inside the previous segment.§c")
|
|
self.berps[i] = {squassets.BERP:new(self.stiffness, self.bounce), squassets.BERP:new(self.stiffness, self.bounce, self.downLimit, self.upLimit)}
|
|
self.targets[i] = {0, 0}
|
|
end
|
|
|
|
self.downLimit = downLimit or -90
|
|
self.upLimit = upLimit or 45
|
|
self.tailSegmentList = tailSegmentList
|
|
self.idleXMovement = idleXMovement or 15
|
|
self.idleYMovement = idleYMovement or 5
|
|
self.idleXSpeed = idleXSpeed or 1.2
|
|
self.idleYSpeed = idleYSpeed or 2
|
|
self.bendStrength = bendStrength or 2
|
|
self.velocityPush = velocityPush or 0
|
|
self.initialMovementOffset = initialMovementOffset or 0
|
|
self.flyingOffset = flyingOffset or 90
|
|
self.offsetBetweenSegments = offsetBetweenSegments or 1
|
|
|
|
self.currentBodyRot = 0
|
|
self.oldBodyRot = 0
|
|
self.bodyRotSpeed = 0
|
|
|
|
self.enabled = true
|
|
|
|
table.insert(tail.all, self)
|
|
return self
|
|
end
|
|
|
|
|
|
--*CONTROL FUNCTIONS
|
|
|
|
---tail enable handling
|
|
function tail:disable() self.enabled = false return self end
|
|
function tail:enable() self.enabled = true return self end
|
|
function tail:toggle() self.enabled = not self.enabled return self end
|
|
|
|
---@param bool boolean
|
|
function tail:setEnabled(bool)
|
|
self.enabled = bool or false
|
|
return self
|
|
end
|
|
|
|
function tail:zero()
|
|
for _, v in pairs(self.tailSegmentList) do
|
|
v:setOffsetRot(0, 0, 0)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--*UPDATE FUNCTIONS
|
|
|
|
function tail:tick()
|
|
if self.enabled then
|
|
self.oldBodyRot = self.currentBodyRot
|
|
self.currentBodyRot = player:getBodyYaw()
|
|
self.bodyRotSpeed = math.max(math.min(self.currentBodyRot-self.oldBodyRot, 20), -20)
|
|
|
|
local time = world.getTime()
|
|
local vel = squassets.forwardVel()
|
|
local yvel = squassets.verticalVel()
|
|
local svel = squassets.sideVel()
|
|
local bendStrength = self.bendStrength/(math.abs((yvel*30))+vel*30 + 1)
|
|
local pose = player:getPose()
|
|
|
|
for i = 1, #self.tailSegmentList do
|
|
self.targets[i][1] = math.sin((time * self.idleXSpeed)/10 - (i * self.offsetBetweenSegments)) * self.idleXMovement
|
|
self.targets[i][2] = math.sin((time * self.idleYSpeed)/10 - (i * self.offsetBetweenSegments) + self.initialMovementOffset) * self.idleYMovement
|
|
|
|
self.targets[i][1] = self.targets[i][1] + self.bodyRotSpeed*self.bendStrength + svel*self.bendStrength*40
|
|
self.targets[i][2] = self.targets[i][2] + yvel * 15 * self.bendStrength - vel*self.bendStrength*15*self.velocityPush
|
|
|
|
if i == 1 then
|
|
if pose == "FALL_FLYING" or pose == "SWIMMING" or player:riptideSpinning() then
|
|
self.targets[i][2] = self.flyingOffset
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---@param dt number Tick delta
|
|
function tail:render(dt, _)
|
|
if self.enabled then
|
|
local pose = player:getPose()
|
|
if pose ~= "SLEEPING" then
|
|
for i, tail in ipairs(self.tailSegmentList) do
|
|
tail:setOffsetRot(
|
|
self.berps[i][2]:berp(self.targets[i][2], dt),
|
|
self.berps[i][1]:berp(self.targets[i][1], dt),
|
|
0
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return tail |