From d1809b35eefcfec53831eed8890f8e043478e84e Mon Sep 17 00:00:00 2001 From: reidlab Date: Sat, 15 Mar 2025 15:28:46 -0700 Subject: [PATCH] init (again) i accidentally [doxxed myself](https://github.com/JannisX11/blockbench/issues/1322), thanks blockbench! --- .editorconfig | 11 + .gitignore | 4 + README.md | 11 + avatar.json | 14 + avatar.png | Bin 0 -> 1051 bytes models/main.bbmodel | 1 + scripts/libs/SquAPI.lua | 1178 ++++++++++++++++++++++++++++++++++++ scripts/libs/SquAssets.lua | 428 +++++++++++++ scripts/libs/gradient.lua | 370 +++++++++++ scripts/libs/oklab.lua | 113 ++++ scripts/libs/utils.lua | 17 + scripts/main.lua | 7 + scripts/nameplate.lua | 98 +++ scripts/physics.lua | 39 ++ scripts/soggy.lua | 34 ++ scripts/vanilla_model.lua | 6 + scripts/wheels/main.lua | 18 + scripts/wheels/toggles.lua | 14 + textures/extras.png | Bin 0 -> 739 bytes textures/main.png | Bin 0 -> 1753 bytes 20 files changed, 2363 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100644 avatar.json create mode 100644 avatar.png create mode 100644 models/main.bbmodel create mode 100644 scripts/libs/SquAPI.lua create mode 100644 scripts/libs/SquAssets.lua create mode 100644 scripts/libs/gradient.lua create mode 100644 scripts/libs/oklab.lua create mode 100644 scripts/libs/utils.lua create mode 100644 scripts/main.lua create mode 100644 scripts/nameplate.lua create mode 100644 scripts/physics.lua create mode 100644 scripts/soggy.lua create mode 100644 scripts/vanilla_model.lua create mode 100644 scripts/wheels/main.lua create mode 100644 scripts/wheels/toggles.lua create mode 100644 textures/extras.png create mode 100644 textures/main.png diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..263e3ee --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a3646cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# figura extension files +.vscode +.luarc.json +avatar.code-workspace diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2fd064 --- /dev/null +++ b/README.md @@ -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 :) diff --git a/avatar.json b/avatar.json new file mode 100644 index 0000000..2cb9aad --- /dev/null +++ b/avatar.json @@ -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" ] +} diff --git a/avatar.png b/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..088cec65536036aaee711526b1c673f21c0ec335 GIT binary patch literal 1051 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4kiW$2Bz0tQ49U|>mi z^mSxl*x1kgCy|wbfq}EYBeIx*fp0$uGg`0)t1vJyZ}4<+46*Rf4e`y9b``11Pb+)7 z^?F&7`V1A{VAWnvRWGGhC#FnRrmX@K8w5J!ISZd(Qep{l+RM1NfrWhu*F~={4*D{) z*d`XSE|d$n)8^fI0-3gf6JL`xaGjLTb^ohM&f`|{~F?)N-%DuZ;U9a*dNzchZW zNa{`Jrn)otg|=&JX&;z;Ahe)oANQ;tyBX&+^z{1vPpMXB`25u@p<+Q(y`;oN&I_lS93OR_{GPS{{Oi4L>*F5X&;K1PU0qU~_PqFQ<-hLt zR(&N7ou^g9WA1<3Td&XV{U1k6 z@?G{aS~f>vs;15DEdHuhGKa*NOq38C*vXbVkuyS`}3pdv5qZNW<(+-44U5sHHw{dMB81e~SBbrMpx|DDKA3SC_6%`>1Y^xnh$(_dmxU z{}oC$9@UKSxwd1Xr}t%rJrNeaFS2hj?{n9^!d+f+@QyrBXWfD}#W&~gvPG&NXx%^a zYSM{>9-b|S4&8HnQ*UVanR%u6jjvN4|7=@5XVW^qS$HHoH%Km%*pJ^A5aiv-MrNF&dK-imJOP?hzXutc-`mDlXU zhAtcLs=VB=>ccCi#CLZ;JNyke9~imz$4+10%oBN&f-eg1&@S1VaA?6?v6p?5%7UM} ziM_hH^8PftzY))8FAciVRTZ7BPY?I2ShwC<9$i0{>=+SO)a0#RI=FdB` z@3YLlt)g}2on3yitJT#5%ocMhXC``c?8@38Q^E6A= 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 diff --git a/scripts/libs/gradient.lua b/scripts/libs/gradient.lua new file mode 100644 index 0000000..6af0dc3 --- /dev/null +++ b/scripts/libs/gradient.lua @@ -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, +} diff --git a/scripts/libs/oklab.lua b/scripts/libs/oklab.lua new file mode 100644 index 0000000..f12a95a --- /dev/null +++ b/scripts/libs/oklab.lua @@ -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 \ No newline at end of file diff --git a/scripts/libs/utils.lua b/scripts/libs/utils.lua new file mode 100644 index 0000000..5bd6bd7 --- /dev/null +++ b/scripts/libs/utils.lua @@ -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 diff --git a/scripts/main.lua b/scripts/main.lua new file mode 100644 index 0000000..16bb087 --- /dev/null +++ b/scripts/main.lua @@ -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) diff --git a/scripts/nameplate.lua b/scripts/nameplate.lua new file mode 100644 index 0000000..6f9e9b6 --- /dev/null +++ b/scripts/nameplate.lua @@ -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) diff --git a/scripts/physics.lua b/scripts/physics.lua new file mode 100644 index 0000000..bb2ecf1 --- /dev/null +++ b/scripts/physics.lua @@ -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 +) diff --git a/scripts/soggy.lua b/scripts/soggy.lua new file mode 100644 index 0000000..2d8c2eb --- /dev/null +++ b/scripts/soggy.lua @@ -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) diff --git a/scripts/vanilla_model.lua b/scripts/vanilla_model.lua new file mode 100644 index 0000000..f6c7877 --- /dev/null +++ b/scripts/vanilla_model.lua @@ -0,0 +1,6 @@ +for _, vanillaModel in pairs({ + vanilla_model.PLAYER, + vanilla_model.ARMOR +}) do + vanillaModel:setVisible(false) +end diff --git a/scripts/wheels/main.lua b/scripts/wheels/main.lua new file mode 100644 index 0000000..678dcf2 --- /dev/null +++ b/scripts/wheels/main.lua @@ -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 diff --git a/scripts/wheels/toggles.lua b/scripts/wheels/toggles.lua new file mode 100644 index 0000000..da9309f --- /dev/null +++ b/scripts/wheels/toggles.lua @@ -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 diff --git a/textures/extras.png b/textures/extras.png new file mode 100644 index 0000000000000000000000000000000000000000..74cf67a064fe0327fdaa22ef66aca44f04613d02 GIT binary patch literal 739 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 aenCsO8TW1#PGMkRVDNPHb6Mw<&;$V6=|=|u literal 0 HcmV?d00001