init (again)

i accidentally [doxxed myself](https://github.com/JannisX11/blockbench/issues/1322), thanks blockbench!
This commit is contained in:
Reid 2025-03-15 15:28:46 -07:00
commit d1809b35ee
Signed by: reidlab
GPG key ID: 6C9EAA3364F962C8
20 changed files with 2363 additions and 0 deletions

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.nix]
indent_size = 2

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
# figura extension files
.vscode
.luarc.json
avatar.code-workspace

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# figura-skin
my figura avatar!! this currently targets version **0.1.5**
`textures/main.png` contains the skins base, which can be used as your default minecraft skin if you so choose
## development
to get proper typings, find the correct branch for your figura version [on this repository](github.com/GrandpaScout/FiguraRewriteVSDocs/). then, copy everything from the `src/` folder into your current working folder here
unfortunately, 0.1.5 typings aren't done yet, so just make sure it works :)

14
avatar.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "reidlab's avatar",
"description": "meee",
"authors": [
"reidlab",
"mrsirsquishy: Squishy's API",
"Agapurnis: Gradient and nameplate script",
"skyevg: Oklab script",
"adristel: Wet Clothes/Fur Script"
],
"color": "#d87b5a",
"version": "0.1.5",
"autoScripts": [ "scripts/main.lua" ]
}

BIN
avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

1
models/main.bbmodel Normal file

File diff suppressed because one or more lines are too long

1178
scripts/libs/SquAPI.lua Normal file

File diff suppressed because it is too large Load diff

428
scripts/libs/SquAssets.lua Normal file
View file

@ -0,0 +1,428 @@
--[[--------------------------------------------------------------------------------------
____ _ _ _ _
/ ___| __ _ _ _(_)___| |__ _ _ / \ ___ ___ ___| |_ ___
\___ \ / _` | | | | / __| '_ \| | | | / _ \ / __/ __|/ _ \ __/ __|
___) | (_| | |_| | \__ \ | | | |_| | / ___ \\__ \__ \ __/ |_\__ \
|____/ \__, |\__,_|_|___/_| |_|\__, | /_/ \_\___/___/\___|\__|___/
|_| |___/
--]]--------------------------------------------------------------------------------------Standard
--[[
-- Author: Squishy
-- Discord tag: @mrsirsquishy
-- Version: 1.0.0
-- Legal: ARR
Framework Functions and classes for SquAPI.
This contains some math functions, some simplified calls to figura features, some debugging scripts for convenience, and classes used in SquAPI or for debugging.
You can also make use of these functions, however it's for more advanced scripters. remember to call: local squassets = require("SquAssets")
]]
local squassets = {}
--Useful Calls
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--detects the fluid the player is in(air is nil), and if they are fully submerged in that fluid.
--vanilla player has an eye height of 1.5 which is used by default for checking if it's submerged, but you can optionally modify this for different height avatars
function squassets.getFluid(eyeHeight)
local fluid
local B = world.getBlockState(player:getPos() + vec(0, eyeHeight or 1.5, 0))
local submerged = B.id == "minecraft:water" or B.id == "minecraft:lava"
if player:isInWater() then
fluid = "WATER"
elseif player:isInLava() then
fluid = "LAVA"
end
return fluid, submerged
end
--better isOnGround, taken from the figura wiki
function squassets.isOnGround()
return world.getBlockState(thisEntity:getPos():add(0, -0.1, 0)):isSolidBlock()
end
-- returns how fast the player moves forward, negative means backward
function squassets.forwardVel()
return player:getVelocity():dot((player:getLookDir().x_z):normalize())
end
-- returns y velocity(negative is down)
function squassets.verticalVel()
return player:getVelocity()[2]
end
-- returns how fast player moves sideways, negative means left
-- Courtesy of @auriafoxgirl on discord
function squassets.sideVel()
return (player:getVelocity() * matrices.rotation3(0, player:getRot().y, 0)).x
end
--returns a cleaner vanilla head rotation value to use
function squassets.getHeadRot()
return (vanilla_model.HEAD:getOriginRot()+180)%360-180
end
--Math Functions
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--polar to cartesian coordiantes
function squassets.PTOC(r, theta)
return r*math.cos(theta), r*math.sin(theta)
end
--cartesian to polar coordinates
function squassets.CTOP(x, y)
return squassets.pyth(x, y), math.atan(y/x)
end
--3D polar to cartesian coordiantes(returns vec3)
function squassets.PTOC3(R, theta, phi)
local r, y = squassets.PTOC(R, phi)
local x, z = squassets.PTOC(r, theta)
return vec(x, y, z)
end
--3D cartesian to polar coordinates
function squassets.CTOP3(x, y, z)
local v
if type(x) == "Vector3" then
v = x
else
v = vec(x, y, z)
end
local R = v:length()
return R, math.atan2(v.z, v.x), math.asin(v.y/R)
end
--pythagorean theoremn
function squassets.pyth(a, b)
return math.sqrt(a^2 + b^2)
end
--checks if a point is within a box
function squassets.pointInBox(point, corner1, corner2)
if not (point and corner1 and corner2) then return false end
return
point.x >= corner1.x and point.x <= corner2.x and
point.y >= corner1.y and point.y <= corner2.y and
point.z >= corner1.z and point.z <= corner2.z
end
--returns true if the number is within range, false otherwise
function squassets.inRange(lower, num, upper)
return lower <= num and num <= upper
end
-- Linear graph
-- locally generates a graph between two points, returns the y value at t on that graph
function squassets.lineargraph(x1, y1, x2, y2, t)
local slope = (y2-y1)/(x2-x1)
local inter = y2 - slope*x2
return slope*t + inter
end
--Parabolic graph
--locally generates a parabolic graph between three points, returns the y value at t on that graph
function squassets.parabolagraph(x1, y1, x2, y2, x3, y3, t)
local denom = (x1 - x2) * (x1 - x3) * (x2 - x3)
local a = (x3 * (y2 - y1) + x2 * (y1 - y3) + x1 * (y3 - y2)) / denom
local b = (x3^2 * (y1 - y2) + x2^2 * (y3 - y1) + x1^2 * (y2 - y3)) / denom
local c = (x2 * x3 * (x2 - x3) * y1 + x3 * x1 * (x3 - x1) * y2 + x1 * x2 * (x1 - x2) * y3) / denom
return a * t^2 + b * t + c
end
--returns 1 if num is >= 0, returns -1 if less than 0
function squassets.sign(num)
if num < 0 then
return -1
end
return 1
end
--returns a vector with the signs of each vector(shows the direction of each vector)
function squassets.Vec3Dir(v)
return vec(squassets.sign(v.x), squassets.sign(v.y), squassets.sign(v.z))
end
--raises all values in a vector to a power
function squassets.Vec3Pow(v, power)
local power = power or 2
return vec(math.pow(v.x, power), math.pow(v.y, power), math.pow(v.z, power))
end
--Debug/Display Functions
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--displays the corners of a bounding box, good for debugging
---@param corner1 vector coordinate of first corner
---@param corner2 vector coordinate of second corner
---@param color vector of the color, or a string of one of the preset colors
function squassets.bbox(corner1, corner2, color)
dx = corner2[1] - corner1[1]
dy = corner2[2] - corner1[2]
dz = corner2[3] - corner1[3]
squassets.pointMarker(corner1, color)
squassets.pointMarker(corner2, color)
squassets.pointMarker(corner1 + vec(dx,0,0), color)
squassets.pointMarker(corner1 + vec(dx,dy,0), color)
squassets.pointMarker(corner1 + vec(dx,0,dz), color)
squassets.pointMarker(corner1 + vec(0,dy,0), color)
squassets.pointMarker(corner1 + vec(0,dy,dz), color)
squassets.pointMarker(corner1 + vec(0,0,dz), color)
end
--draws a sphere
function squassets.sphereMarker(pos, radius, color, colorCenter, quality)
local pos = pos or vec(0, 0, 0)
local r = radius or 1
local quality = (quality or 1)*10
local colorCenter = colorCenter or color
-- Draw the center point
squassets.pointMarker(pos, colorCenter)
-- Draw surface points
for i = 1, quality do
for j = 1, quality do
local theta = (i / quality) * 2 * math.pi
local phi = (j / quality) * math.pi
local x = pos.x + r * math.sin(phi) * math.cos(theta)
local y = pos.y + r * math.sin(phi) * math.sin(theta)
local z = pos.z + r * math.cos(phi)
squassets.pointMarker(vec(x, y, z), color)
end
end
end
--draws a line between two points with particles, higher density is more particles
function squassets.line(corner1, corner2, color, density)
local l = (corner2 - corner1):length() -- Length of the line
local direction = (corner2 - corner1):normalize() -- Direction vector
local density = density or 10
for i = 0, l, 1/density do
local pos = corner1 + direction * i -- Interpolate position
squassets.pointMarker(pos, color) -- Create a particle at the interpolated position
end
end
--displays a particle at a point, good for debugging
---@param pos vector coordinate where it will render
---@param color vector of the color, or a string of one of the preset colors
function squassets.pointMarker(pos, color)
if type(color) == "string" then
if color == "R" then color = vec(1, 0, 0)
elseif color == "G" then color = vec(0, 1, 0)
elseif color == "B" then color = vec(0, 0, 1)
elseif color == "yellow" then color = vec(1, 1, 0)
elseif color == "purple" then color = vec(1, 0, 1)
elseif color == "cyan" then color = vec(0, 1, 1)
elseif color == "black" then color = vec(0, 0, 0)
else
color = vec(1,1,1)
end
else
color = color or vec(1,1,1)
end
particles:newParticle("minecraft:wax_on", pos):setSize(0.5):setLifetime(0):setColor(color)
end
--Classes
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------
squassets.vanillaElement = {}
squassets.vanillaElement.__index = squassets.vanillaElement
function squassets.vanillaElement:new(element, strength, keepPosition)
local self = setmetatable({}, squassets.vanillaElement)
-- INIT -------------------------------------------------------------------------
self.keepPosition = keepPosition
if keepPosition == nil then self.keepPosition = true end
self.element = element
self.element:setParentType("NONE")
self.strength = strength or 1
self.rot = vec(0,0,0)
self.pos = vec(0,0,0)
-- CONTROL -------------------------------------------------------------------------
self.enabled = true
function self:disable()
self.enabled = false
end
function self:enable()
self.enabled = true
end
function self:toggle()
self.enabled = not self.enabled
end
--returns it to normal attributes
function self:zero()
self.element:setOffsetRot(0, 0, 0)
self.element:setPos(0, 0, 0)
end
--get the current rot/pos
function self:getPos()
return self.pos
end
function self:getRot()
return self.rot
end
-- UPDATES -------------------------------------------------------------------------
function self:render(dt, context)
if self.enabled then
local rot, pos = self:getVanilla()
self.element:setOffsetRot(rot*self.strength)
if self.keepPosition then
self.element:setPos(pos)
end
end
end
return self
end
squassets.BERP3D = {}
squassets.BERP3D.__index = squassets.BERP3D
function squassets.BERP3D:new(stiff, bounce, lowerLimit, upperLimit, initialPos, initialVel)
local self = setmetatable({}, squassets.BERP3D)
self.stiff = stiff or 0.1
self.bounce = bounce or 0.1
self.pos = initialPos or vec(0, 0, 0)
self.vel = initialVel or vec(0, 0, 0)
self.acc = vec(0, 0, 0)
self.lower = lowerLimit or {nil, nil, nil}
self.upper = upperLimit or {nil, nil, nil}
--target is the target position
--dt, or delta time, the time between now and the last update(delta from the events.update() function)
--if you want it to have a different stiff or bounce when run input a different stiff bounce
function self:berp(target, dt, stiff, bounce)
local target = target or vec(0,0,0)
local dt = dt or 1
for i = 1, 3 do
--certified bouncy math
local dif = (target[i]) - self.pos[i]
self.acc[i] = ((dif * math.min(stiff or self.stiff, 1)) * dt) --based off of spring force F = -kx
self.vel[i] = self.vel[i] + self.acc[i]
--changes the position, but adds a bouncy bit that both overshoots and decays the movement
self.pos[i] = self.pos[i] + (dif * (1-math.min(bounce or self.bounce, 1)) + self.vel[i]) * dt
--limits range
if self.upper[i] and self.pos[i] > self.upper[i] then
self.pos[i] = self.upper[i]
self.vel[i] = 0
elseif self.lower[i] and self.pos[i] < self.lower[i] then
self.pos[i] = self.lower
self.vel[i] = 0
end
end
--returns position so that you can immediately apply the position as it is changed.
return self.pos
end
return self
end
--stiffness factor, > 0
--bounce factor, reccomended when in range of 0-1. bigger is bouncier.
--if you want to limit the positioning, use lowerlimit and upperlimit, or leave nil
squassets.BERP = {}
squassets.BERP.__index = squassets.BERP
function squassets.BERP:new(stiff, bounce, lowerLimit, upperLimit, initialPos, initialVel)
local self = setmetatable({}, squassets.BERP)
self.stiff = stiff or 0.1
self.bounce = bounce or 0.1
self.pos = initialPos or 0
self.vel = initialVel or 0
self.acc = 0
self.lower = lowerLimit or nil
self.upper = upperLimit or nil
--target is the target position
--dt, or delta time, the time between now and the last update(delta from the events.update() function)
--if you want it to have a different stiff or bounce when run input a different stiff bounce
function self:berp(target, dt, stiff, bounce)
local dt = dt or 1
--certified bouncy math
local dif = (target or 10) - self.pos
self.acc = ((dif * math.min(stiff or self.stiff, 1)) * dt) --based off of spring force F = -kx
self.vel = self.vel + self.acc
--changes the position, but adds a bouncy bit that both overshoots and decays the movement
self.pos = self.pos + (dif * (1-math.min(bounce or self.bounce, 1)) + self.vel) * dt
--limits range
if self.upper and self.pos > self.upper then
self.pos = self.upper
self.vel = 0
elseif self.lower and self.pos < self.lower then
self.pos = self.lower
self.vel = 0
end
--returns position so that you can immediately apply the position as it is changed.
return self.pos
end
return self
end
return squassets

370
scripts/libs/gradient.lua Normal file
View file

@ -0,0 +1,370 @@
-- work in pewgross
-- do not share !!
---@class UnitRGBA: Vector4 [r, g, b, a]; all values are 0-1.
local UnitRGBA = {};
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
---@return UnitRGBA? output nil the vector with the new metatable if valid, otherwise nil
---@return string? error a string describing the error if invalid, otherwise nil
---@nodiscard
---@see UnitRGBA.fromUnitStrict to throw an error instead of returning nil
function UnitRGBA.fromUnit(vector)
for i, v in ipairs({vector:unpack()}) do
if type(v) ~= "number" then
return nil, "Vector component " .. i .. " is not a number";
end
if v < 0 or v > 1 then
return nil, "Vector component " .. i .. " is not in the range 0-1; got " .. v;
end
end
return setmetatable({ vector = vector }, UnitRGBA), nil;
end
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
---@return UnitRGBA vector the vector with the new metatable if valid. otherwise, an error is thrown
---@nodiscard
---@see UnitRGBA.fromUnit to return nil instead of throwing an error
function UnitRGBA.fromUnitStrict(vector)
local result, why = UnitRGBA.fromUnit(vector);
if result == nil then
error("Invalid vector: " .. why);
end
return result;
end
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-255)
---@return UnitRGBA? output nil the vector with the new metatable if valid, otherwise nil
---@return string? error a string describing the error if invalid, otherwise nil
---@nodiscard
---@see UnitRGBA.fromU8Strict to throw an error instead of returning nil
function UnitRGBA.fromU8(vector)
for i, v in ipairs(vector) do
if type(v) ~= "number" then
return nil, "Vector component " .. i .. " is not a number";
end
if v < 0 or v > 255 then
return nil, "Vector component " .. i .. " is not in the range 0-255";
end
end
return setmetatable({ vector = vector / 255 }, UnitRGBA), nil;
end
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-255)
---@return UnitRGBA vector the vector with the new metatable if valid. otherwise, an error is thrown
---@nodiscard
---@see UnitRGBA.fromU8 to return nil instead of throwing an error
function UnitRGBA.fromU8Strict(vector)
local result, why = UnitRGBA.fromU8(vector);
if result == nil then
error("Invalid vector: " .. why);
end
return result;
end
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
---@return UnitRGBA vector the vector with the new metatable
---@nodiscard
function UnitRGBA.fromUnitClamping(vector)
vector[1] = math.clamp(vector[1], 0, 1);
vector[2] = math.clamp(vector[2], 0, 1);
vector[3] = math.clamp(vector[3], 0, 1);
vector[4] = math.clamp(vector[4], 0, 1);
return setmetatable({ vector = vector }, UnitRGBA);
end
---@param hex string a color in hexadecimal format (e.g. "#FF00FF")
---@return UnitRGBA? output the color with the new metatable if valid, otherwise nil
---@return string? error a string describing the error if invalid, otherwise nil
---@nodiscard
---@see UnitRGBA.fromHexStrict to throw an error instead of returning nil
function UnitRGBA.fromHex(hex)
if type(hex) ~= "string" then
return nil, "not a string";
end
if hex:sub(1, 1) == "#" then
hex = hex:sub(2);
end
if hex:len() ~= 6 and hex:len() ~= 8 then
return nil, "not 6 or 8 characters long";
end
local vector = vec(0, 0, 0, 1);
for i = 1, 3 do
local value = tonumber(hex:sub(i * 2 - 1, i * 2), 16);
if value == nil then
return nil, "contains non-hex characters";
end
vector[i] = value / 255;
end
if hex:len() == 8 then
local value = tonumber(hex:sub(7, 8), 16);
if value == nil then
return nil, "contains non-hex characters";
end
vector[4] = value / 255;
else
vector[4] = 1;
end
return setmetatable({ vector = vector }, UnitRGBA), nil;
end
---@param hex string a color in hexadecimal format (e.g. "#FF00FF")
---@return UnitRGBA color the color with the new metatable if valid. otherwise, an error is thrown
---@nodiscard
---@see UnitRGBA.fromHex to return nil instead of throwing an error
function UnitRGBA.fromHexStrict(hex)
local result, why = UnitRGBA.fromHex(hex);
if result == nil then
error("Invalid hex color: " .. why);
end
return result;
end
---@return boolean opaque whether the color is *fully opaque* (as in, a == 1, not just a > 0).
---@see UnitRGBA:isTranslucent to see if the color is partially or fully transparent.
---@see UnitRGBA:isTransparent to see if the color is fully transparent.
---@nodiscard
function UnitRGBA:isOpaque()
return self.vector[4] == 1;
end
---@return boolean transparent whether the color is *fully transparent* (as in, a == 0, not just a < 1).
---@see UnitRGBA:isTranslucent to see if the color is partially transparent.
---@see UnitRGBA:isOpaque to see if the color is fully opaque.
---@nodiscard
function UnitRGBA:isTransparent()
return self.vector[4] == 0;
end
---@return boolean translucent whether the color is *translucent* (as in, a < 1).
---@see UnitRGBA:isTransparent to see if the color is fully transparent.
---@see UnitRGBA:isOpaque to see if the color is fully opaque.
---@nodiscard
function UnitRGBA:isTranslucent()
return self.vector[4] < 1;
end
function UnitRGBA:toVec3()
return vec(self.vector[1], self.vector[2], self.vector[3]);
end
---@return Vector4 vector Vector4 with format ([r, g, b, a]; all values are 0-255)
---@nodiscard
function UnitRGBA:toU8vec4()
return vec(self.vector[1] * 255, self.vector[2] * 255, self.vector[3] * 255, self.vector[4] * 255);
end
---@return Vector3 vector Vector3 with format ([r, g, b]; all values are 0-255)
---@nodiscard
function UnitRGBA:toU8vec3()
local a = vec(self.vector[1] * 255, self.vector[2] * 255, self.vector[3] * 255)
return a
end
---@param hash? boolean? whether to include the hash symbol (#) in the output. defaults to true
---@param alpha? boolean? whether to include the alpha channel in the output. defaults to true
---@return string hex the color in RGBA hexadecimal format (e.g. "#FF00FF")
---@nodiscard
function UnitRGBA:toHex(hash, alpha)
local hex = (hash == nil or hash) and "#" or "";
local iter = (alpha == nil or alpha) and 4 or 3;
for i = 1, iter do
local value = math.round(self.vector[i] * 255);
hex = hex .. string.format("%02X", value);
end
return hex;
end
function UnitRGBA:__index(key)
return rawget(UnitRGBA, key) or self.vector[key];
end
function UnitRGBA:__tostring()
return "UnitRGBA(" .. table.concat(self.vector, ", ") .. ")";
end
function UnitRGBA:__sub(other)
return UnitRGBA.fromUnitClamping(vec(
self.vector[1] - other.vector[1],
self.vector[2] - other.vector[2],
self.vector[3] - other.vector[3],
self.vector[4] - other.vector[4]
));
end
--- Transparent white.
UnitRGBA.TRANSPARENT_WHITE = UnitRGBA.fromUnitStrict(vec(1, 1, 1, 0));
--- Transparent black.
UnitRGBA.TRANSPARENT_BLACK = UnitRGBA.fromUnitStrict(vec(0, 0, 0, 0));
--- Opaque white.
UnitRGBA.WHITE = UnitRGBA.fromUnitStrict(vec(1, 1, 1, 1));
--- Opaque black.
UnitRGBA.BLACK = UnitRGBA.fromUnitStrict(vec(0, 0, 0, 1));
---@class Keypoint - A keypoint (interpolation point definition) in a gradient.
---@field public color UnitRGBA color of the keypoint
---@field public time number a number between 0 and 1 (inclusive on both ends)
local Keypoint = {};
---Returns a new Keypoint object.
---@param color UnitRGBA color of the keypoint
---@param time number a number between 0 and 1 (inclusive on both ends)
---@return Keypoint
---@nodiscard
function Keypoint.new(color, time)
return setmetatable({
color = color,
time = time
}, Keypoint);
end
---@class Gradient A gradient definition.
---@field public keypoints Keypoint[] a list of keypoint objects
---@field public loop boolean whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown.
local Gradient = {};
---Returns a new Gradient object.
---@param keypoints Keypoint[] a list of keypoint objects
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
---@return Gradient
---@nodiscard
function Gradient.new(keypoints, loop)
return setmetatable({
keypoints = keypoints,
loop = loop == nil or loop
}, Gradient);
end
local function easeInOutQuad(t)
return t < 0.5 and 2 * t * t or 1 - (-2 * t + 2)^2 / 2
end
local oklab = require("scripts.libs.oklab")
---Returns the color of the gradient at a given time.
---@param time number a number between 0 and 1 (inclusive on both ends) (can extend beyond this range if Gradient.loop is true)
---@return UnitRGBA? color the color of the gradient at the given time, or nil if the time is out of bounds and Gradient.loop is false
function Gradient:at(time)
if time < 0 or time > 1 then
if not self.loop then
return nil;
end
time = time % 1;
end
if time == 0 then return self.keypoints[1].color
elseif time == 1 then return self.keypoints[#self.keypoints].color
end
for i = 1, #self.keypoints - 1 do
local this = self.keypoints[i]
local next = self.keypoints[i + 1]
if time >= this.time and time < next.time then
local t = easeInOutQuad((time - this.time) / (next.time - this.time));
local alpha = math.lerp(this.color.a, next.color.a, t)
local La, Aa, Ba = oklab.srgbToLinear(this.color.xyz):unpack()
local Lb, Ab, Bb = oklab.srgbToLinear(next.color.xyz):unpack()
local mixed = vec(
math.lerp(La, Lb, easeInOutQuad(t)),
math.lerp(Aa, Ab, t),
math.lerp(Ba, Bb, t)
)
mixed = oklab.linearToSrgb(mixed);
mixed = vec(mixed.x, mixed.y, mixed.z, alpha)
return UnitRGBA.fromUnitClamping(mixed);
end
end
if #self.keypoints == 1 then
return self.keypoints[1].color;
end
error("Gradient.at: time " .. time .. " is out of bounds");
end
---@param colors string[] a list of colors in hexadecimal format (e.g. "#FF00FF")
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
---@return Gradient gradient the gradient with the new metatable if valid. otherwise, an error is thrown
function Gradient.distributedHex(colors, loop)
local keypoints = {};
for i, hex in ipairs(colors) do
local time = (i - 1) / (#colors - 1);
local color = UnitRGBA.fromHexStrict(hex);
table.insert(keypoints, Keypoint.new(color, time));
end
return Gradient.new(keypoints, loop);
end
function Gradient:__index(key)
return rawget(Gradient, key) or self[key];
end
---@class SimpleGradientBuilder
---@field public colors UnitRGBA[] a list of colors
local SimpleGradientBuilder = {};
---@return SimpleGradientBuilder builder a simple gradient builder
function SimpleGradientBuilder.new()
return setmetatable({ colors = {} }, { __index = SimpleGradientBuilder });
end
---Reflect the colors of the gradient; this makes it nicer when looping.
---@param reflect_last boolean? whether to also reflect the final color, such that it would appear twice in a row at the middle. if false, it'll only be present once
---@return SimpleGradientBuilder self builder for chaining
function SimpleGradientBuilder:reflect(reflect_last)
---@type UnitRGBA[]
local reflected = {};
for i = 1, #self.colors - 1 do
table.insert(reflected, self.colors[i]);
end
table.insert(reflected, self.colors[#self.colors])
if reflect_last then
table.insert(reflected, self.colors[#self.colors])
end
for i = #self.colors - 1, 1, -1 do
table.insert(reflected, self.colors[i])
end
self.colors = reflected
return self;
end
---"Thicken" the gradient by duplicating every keypoint. This means that the actual colors themselves will have as much presence as the transitions.
---@param amount? integer how many times to duplicate each keypoint. defaults to 1.
---@return SimpleGradientBuilder self builder for chaining
function SimpleGradientBuilder:thicken(amount)
amount = amount or 1;
local thickened = {};
for i = 1, #self.colors do
for _ = 0, amount do
table.insert(thickened, self.colors[i])
end
end
self.colors = thickened;
return self;
end
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
---@return Gradient gradient the constructed gradient
function SimpleGradientBuilder:build(loop)
loop = loop == nil or loop;
---@type Keypoint[]
local keypoints = {};
for i, color in ipairs(self.colors) do
table.insert(keypoints, Keypoint.new(color, (i - 1) / (#self.colors - 1)));
end
return setmetatable({
keypoints = keypoints,
loop = loop
}, Gradient);
end
---@param colors string[] | string hexadecimal color(s) to add
function SimpleGradientBuilder:add(colors)
if type(colors) == "string" then
colors = {colors}
end
for _, color in ipairs(colors) do
table.insert(self.colors, UnitRGBA.fromHexStrict(color))
end
return self;
end
return {
UnitRGBA = UnitRGBA,
Keypoint = Keypoint,
Gradient = Gradient,
SimpleGradientBuilder = SimpleGradientBuilder,
}

113
scripts/libs/oklab.lua Normal file
View file

@ -0,0 +1,113 @@
local lib = {}
---@param x number
---@return number
local function fromLinear(x)
if x >= 0.0031308 then
return 1.055 * math.pow(x, 1.0/2.4) - 0.055
else
return 12.92 * x
end
end
---@param x number
---@return number
local function toLinear(x)
if x >= 0.04045 then
return math.pow((x + 0.055)/(1 + 0.055), 2.4)
else
return x / 12.92
end
end
---Converts from sRGB to Linear sRGB
---@param color Vector3
---@return Vector3
function lib.srgbToLinear(color)
return vec(toLinear(color.x), toLinear(color.y), toLinear(color.z))
end
---Converts from Linear sRGB to sRGB
---@param color Vector3
---@return Vector3
function lib.linearToSrgb(color)
return vec(fromLinear(color.x), fromLinear(color.y), fromLinear(color.z))
end
---Converts from Linear sRGB to OKLAB
---@param color Vector3
---@return Vector3
function lib.linearToOklab(color)
local l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b
local m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b
local s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b
local l_ = math.pow(l, 1/3)
local m_ = math.pow(m, 1/3)
local s_ = math.pow(s, 1/3)
return vec(
0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_,
1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_,
0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_
)
end
---Converts from OKLAB to Linear sRGB
---@param color Vector3
---@return Vector3
function lib.oklabToLinear(color)
local l_ = color.x + 0.3963377774 * color.y + 0.2158037573 * color.z;
local m_ = color.x - 0.1055613458 * color.y - 0.0638541728 * color.z;
local s_ = color.x - 0.0894841775 * color.y - 1.2914855480 * color.z;
local l = l_*l_*l_;
local m = m_*m_*m_;
local s = s_*s_*s_;
return vec(
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
)
end
---Converts from LAB to LCh
---@param color Vector3
---@return Vector3
function lib.labToLch(color)
return vec(
color.x,
math.sqrt(color.y * color.y + color.z * color.z),
math.deg(math.atan2(color.z, color.y))
)
end
---Converts from LCh to LAB
---@param color Vector3
---@return Vector3
function lib.lchToLab(color)
return vec(
color.x,
color.y * math.cos(math.rad(color.z)),
color.y * math.sin(math.rad(color.z))
)
end
---Converts from sRGB to OKLCh
---@param color Vector3
---@return Vector3
function lib.srgbToOklch(color)
return lib.labToLch(lib.linearToOklab(lib.srgbToLinear(color)))
end
---Converts from OKLCh to sRGB
---@param color Vector3
---@return Vector3
function lib.oklchToSrgb(color)
return lib.linearToSrgb(lib.oklabToLinear(lib.lchToLab(color)))
end
return lib

17
scripts/libs/utils.lua Normal file
View file

@ -0,0 +1,17 @@
local module = {}
--- @param model ModelPart
--- @param func fun(model: ModelPart)
function module.forEachNonGroup(model, func)
if model:getType() == "GROUP" then
for _, child in pairs(model:getChildren()) do
module.forEachNonGroup(child, func)
end
end
if model:getType() == "CUBE" or model:getType() == "MESH" then
func(model)
end
end
return module

7
scripts/main.lua Normal file
View file

@ -0,0 +1,7 @@
events.ENTITY_INIT:register(function ()
require("scripts.vanilla_model")
require("scripts.nameplate")
require("scripts.soggy")
require("scripts.physics")
require("scripts.wheels.main")
end)

98
scripts/nameplate.lua Normal file
View file

@ -0,0 +1,98 @@
local g = require("scripts.libs.gradient")
---@class ColoredText
---@field text string
---@field color string
---@param name string
---@param gradient Gradient
---@param count number number of gradients to be present in the span
---@param phase_shift number shift of the gradient
---@returns string
local function render_gradient_name(name, gradient, count, phase_shift)
local string_width = client.getTextWidth(name) / count;
local string_width_chars = {};
for i = 1, #name do
string_width_chars[i] = client.getTextWidth(name:sub(i, i))
end
---@type ColoredText[]
local result = {}
local acc = 0;
for i = 1, #name do
local offset = acc + string_width_chars[i];
local color = gradient:at((offset / string_width) + phase_shift);
acc = offset
result[i] = {
text = name:sub(i, i),
color = color:toHex(true, false)
}
end
return toJson(result)
end
---@param name string
---@param gradient Gradient
---@param nameplates Nameplate | Nameplate[] where to use the animated name
---@param count number number of gradients to be present in the span
---@param phase_shift_rate number how fast to shift the gradient
---@returns fun():void # function to unregister
local function animate_gradient_name(name, gradient, nameplates, count, phase_shift_rate)
if type(nameplates) ~= "table" then
nameplates = {nameplates}
end
local phase_shift = 0;
local function render()
if client.isPaused() then
return
end
phase_shift = phase_shift + phase_shift_rate
local result = render_gradient_name(name, gradient, count, phase_shift)
for _, nameplate in pairs(nameplates) do
nameplate:setText(result)
end
end
events.render:register(render)
return function ()
events.render:remove(render)
end
end
---@param name string
---@param gradient Gradient
---@param nameplates Nameplate | Nameplate[] where to use the static name
---@param count number number of gradients to be present in the span
---@param phase_shift number shift of the gradient
local function static_gradient_name(name, gradient, nameplates, count, phase_shift)
if type(nameplates) ~= "table" then
nameplates = {nameplates}
end
local result = render_gradient_name(name, gradient, count, phase_shift);
for _, nameplate in pairs(nameplates) do
nameplate:setText(result)
end
end
local name = "reidlab!"
local gradient_count = 1
local gradient = g.SimpleGradientBuilder.new()
:add({ "#d87b5a", "#e0ab91" })
:reflect(false)
:build()
animate_gradient_name(name, gradient, {
nameplate.ENTITY,
nameplate.LIST
}, gradient_count, 0.005)
static_gradient_name(name, gradient, {
nameplate.CHAT
}, gradient_count, 0)

39
scripts/physics.lua Normal file
View file

@ -0,0 +1,39 @@
local squapi = require("scripts.libs.SquAPI")
-- ear physics
squapi.ear:new(
models.models.main.Head.Ears.LeftEar,
models.models.main.Head.Ears.RightEar,
0.2,
nil,
nil,
false
)
squapi.ear:new(
models.models.main.Head.Ears.LeftEar.LeftEar2,
models.models.main.Head.Ears.RightEar.RightEar2,
-0.30,
nil,
nil,
false
)
-- tail physics
local tail_segments = {
models.models.main.Body.Tail,
models.models.main.Body.Tail2,
models.models.main.Body.Tail3
}
squapi.tail:new(
tail_segments,
nil,
nil,
nil,
nil,
1,
5
)

34
scripts/soggy.lua Normal file
View file

@ -0,0 +1,34 @@
local utils = require("scripts.libs.utils")
local tw = 1 --top wetness --wet effect
local bw = 1 --bottom wetness
local driptime = 4 --recommended to change based on how low you have TW/BW
local offset = vec(0,0,0)
local offset2 = vec(0,0,0)
events.TICK:register(function ()
if player:isInRain() then --makes clothes wet if in rain
tw = tw - 0.005
bw = bw - 0.005
if tw <= 0.6 then tw = 0.6 end
if bw <= 0.6 then bw = 0.6 end
end
offset = vec((math.random()-0.5), math.random(), (math.random()-0.5)) --random offset of particles
offset2 = vec(math.random(-1,1),math.random(-1,1),math.random(-1,1)) -- velocity
if player:isInWater() then bw = 0.6 end --if player is standing in water, make bottom clothes wet
if player:isUnderwater() then tw = 0.6 end --if player is submerged in water, make top clothes wet
if not player:isUnderwater() and tw ~= 1 and not player:isInRain() then tw = tw + 0.005 end --if not submerged in water, dry top clothes
if not player:isInWater() and bw ~= 1 and not player:isInRain() then bw = bw + 0.005 end --if not standing in water, dry bottom clothes
if bw >= 1 then bw = 1 end
if tw >= 1 then tw = 1 end
if world.getTime() % driptime == 0 and tw ~= 1 and not (player:isUnderwater()) then for _ = 0, driptime*0.5 do particles:newParticle("falling_dripstone_water",player:getPos():add(offset+vec(0,0.7,0)),offset2) end end
if world.getTime() % driptime == 0 and bw ~= 1 and not (player:isInWater()) then for _ = 0, driptime*0.5 do particles:newParticle("falling_dripstone_water",player:getPos():add(offset),offset2:mul(0.5)) end end
utils.forEachNonGroup(models.models.main.LeftArm, function (part) part:setColor(tw,tw,tw) end)
utils.forEachNonGroup(models.models.main.RightArm, function (part) part:setColor(tw,tw,tw) end)
utils.forEachNonGroup(models.models.main.Head, function (part) part:setColor(tw,tw,tw) end)
utils.forEachNonGroup(models.models.main.Body, function (part) part:setColor(tw,tw,tw) end)
utils.forEachNonGroup(models.models.main.LeftLeg, function (part) part:setColor(bw,bw,bw) end)
utils.forEachNonGroup(models.models.main.RightLeg, function (part) part:setColor(bw,bw,bw) end)
end)

View file

@ -0,0 +1,6 @@
for _, vanillaModel in pairs({
vanilla_model.PLAYER,
vanilla_model.ARMOR
}) do
vanillaModel:setVisible(false)
end

18
scripts/wheels/main.lua Normal file
View file

@ -0,0 +1,18 @@
local toggles = require("scripts.wheels.toggles")
local wheels = {
toggles
}
for i, v in pairs(wheels) do
if i == 1 then action_wheel:setPage(v) end
if #wheels ~= 1 then
v:newAction()
:title("to next page")
:item("minecraft:arrow")
:onLeftClick(function ()
local index = (i + 1) > #wheels and 1 or (i + 1)
action_wheel:setPage(wheels[index])
end)
end
end

View file

@ -0,0 +1,14 @@
local toggles = action_wheel:newPage()
function pings.toggleArmor(state)
vanilla_model.ARMOR:setVisible(state)
end
local toggle_armor = toggles:newAction()
:setToggled(false)
:setOnToggle(pings.toggleArmor)
:title("toggle armor")
:item("red_wool")
:toggleItem("green_wool")
return toggles

BIN
textures/extras.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

BIN
textures/main.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB