├── src └── ReplicatedStorage │ └── Animations │ ├── Package │ ├── v2.5.1.lua │ ├── Util │ │ ├── CustomAssert.lua │ │ ├── Queue.lua │ │ ├── ChildFromPath.lua │ │ ├── GetAttributeAsync.lua │ │ ├── Zip.lua │ │ ├── AsyncInfoCache.lua │ │ ├── AnimationsClass │ │ │ ├── AnimatedObjectsCache.lua │ │ │ ├── AnimatedObject.lua │ │ │ └── init.lua │ │ ├── AnimationIdsUtil.lua │ │ ├── Signal.lua │ │ ├── Await.lua │ │ └── Types.lua │ ├── AnimationsServer.lua │ └── AnimationsClient.lua │ └── Deps │ ├── AutoCustomRBXAnimationIds.lua │ └── AnimationIds.lua ├── CONTRIBUTING.md ├── .gitignore ├── default.project.json ├── aftman.toml ├── docs ├── intro.md └── basic-usage.md ├── README.md ├── .github └── FUNDING.yml ├── moonwave.toml ├── LICENSE └── CHANGELOG.md /src/ReplicatedStorage/Animations/Package/v2.5.1.lua: -------------------------------------------------------------------------------- 1 | -- Github: https://github.com/wrello/Animations 2 | -- DevForum: https://devforum.roblox.com/2504740 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 1. Discover a bug, documentation error, or have an idea for an enhancement. 2 | 2. Create an issue outlining the bug, documentation error, or idea. -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/CustomAssert.lua: -------------------------------------------------------------------------------- 1 | local function CustomAssert(check, ...) 2 | if not check then 3 | print("ERROR:", ...) 4 | error("read above message for information") 5 | end 6 | end 7 | 8 | return CustomAssert -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | *.rbxlx 3 | *.rbxl 4 | *.rbxl.lock 5 | *.rbxmx 6 | *.rbxm 7 | *.json 8 | *.lock 9 | *.toml 10 | *.moonwave 11 | /docs/ 12 | /build/ 13 | Build-Wally.ps1 14 | /Animations* 15 | /Packages/ 16 | /test/ 17 | /Build* 18 | /luau_packages/ 19 | /roblox_packages/ -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "animations", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ReplicatedStorage": { 6 | "$className": "ReplicatedStorage", 7 | "$ignoreUnknownInstances": true, 8 | "$path": "src/ReplicatedStorage" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | # This file lists tools managed by Aftman, a cross-platform toolchain manager. 2 | # For more information, see https://github.com/LPGhatguy/aftman 3 | 4 | # To add a new tool, add an entry to this table. 5 | [tools] 6 | rojo = "rojo-rbx/rojo@7.3.0" 7 | wally = "UpliftGames/wally@0.3.2" 8 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Install 6 | 7 | --- 8 | # Roblox model 9 | Get the [roblox model](https://create.roblox.com/marketplace/asset/14292949504/Animations) and put it into your `ReplicatedStorage`, 10 | 11 | # Command bar 12 | or paste the following command into your roblox studio command bar: 13 | ```lua 14 | game:GetService("InsertService"):LoadAsset(14292949504).Animations.Parent = game.ReplicatedStorage 15 | ``` 16 | 17 | - [Basic Usage](/docs/basic-usage) -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/Queue.lua: -------------------------------------------------------------------------------- 1 | local Queue = {} 2 | Queue.__index = Queue 3 | 4 | function Queue.new() 5 | return setmetatable({ _array = {} }, Queue) 6 | end 7 | 8 | function Queue:Enqueue(v: any) 9 | table.insert(self._array, v) 10 | end 11 | 12 | function Queue:Dequeue(): any 13 | local first = self._array[1] 14 | 15 | table.remove(self._array, 1) 16 | 17 | return first 18 | end 19 | 20 | function Queue:DequeueIter() 21 | return function() 22 | return self:Dequeue() 23 | end 24 | end 25 | 26 | return Queue -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/ChildFromPath.lua: -------------------------------------------------------------------------------- 1 | -- made by wrello 2 | -- v2.0.1 3 | 4 | local function ChildFromPath(parent, path) 5 | local child = parent 6 | 7 | if type(path) == "string" then 8 | local function forEachMatch(m) 9 | local v = child[m] 10 | child = if v ~= nil then v else child[tonumber(m)] 11 | end 12 | 13 | string.gsub(path, "[^.]+", forEachMatch) 14 | else 15 | for _, token in ipairs(path) do 16 | child = child[token] 17 | end 18 | end 19 | 20 | return child 21 | end 22 | 23 | return ChildFromPath -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/GetAttributeAsync.lua: -------------------------------------------------------------------------------- 1 | local function GetAttributeAsync(obj: Instance, attrName: string) 2 | local v = obj:GetAttribute(attrName) 3 | 4 | if v == nil then 5 | task.delay(3, function() 6 | if v == nil then 7 | warn(`Infinite yield possible on '{obj.Name}:GetAttributeChangedSignal("{attrName}"):Wait()'`) 8 | end 9 | end) 10 | 11 | obj:GetAttributeChangedSignal(attrName):Wait() 12 | 13 | v = obj:GetAttribute(attrName) 14 | end 15 | 16 | return v 17 | end 18 | 19 | return GetAttributeAsync -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![AnimationsLogo250x250_75%](https://github.com/wrello/Animations/assets/89281328/39310186-2e21-4358-adea-10a5538f5426) 2 | 3 | ![hits](https://hits.aprilnea.com/hits?url=https://github.com/AprilNEA) 4 | 5 | # Animations 6 | A robust and easy to use module for playing & pre-loading animations on rigs in Roblox. 7 | 8 | Website: https://wrello.github.io/Animations/ 9 | 10 | | Install Type | Item | 11 | | --- | --- | 12 | | Roblox model | https://create.roblox.com/marketplace/asset/14292949504 | 13 | | Command | `game:GetService("InsertService"):LoadAsset(14292949504).Animations.Parent = game.ReplicatedStorage` | 14 | | Wally | `Animations = "wrello/animations@2.5.1"` | 15 | | Pesde | `Animations = { name = "wrello/animations", version = "^2.5.1" }` | 16 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: wrello 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /moonwave.toml: -------------------------------------------------------------------------------- 1 | title = "Animations" 2 | gitRepoUrl = "https://github.com/wrello/Animations" 3 | 4 | gitSourceBranch = "master" 5 | changelog = true 6 | classOrder = [ 7 | "AnimationsClient", 8 | "AnimationsServer", 9 | "AnimationIds", 10 | "AutoCustomRBXAnimationIds" 11 | ] 12 | 13 | [home] 14 | enabled = true 15 | includeReadme = false 16 | 17 | [docusaurus] 18 | onBrokenLinks = "throw" 19 | onBrokenMarkdownLinks = "warn" 20 | tagline = "An easy to use module for playing and pre-loading animations" 21 | favicon = "/images/AnimationsLogo250x250.png" 22 | 23 | [[navbar.items]] 24 | href = "https://devforum.roblox.com/t/animations-a-robust-easy-to-use-animation-player-pre-loader/2504740" 25 | label = "Roblox DevForum" 26 | position = "right" 27 | 28 | [footer] 29 | style = "dark" 30 | copyright = "Built with Moonwave and Docusaurus" 31 | 32 | [[footer.links]] 33 | title = "Links" 34 | 35 | [[footer.links.items]] 36 | label = "Buy me a coffee" 37 | href = "https://buymeacoffee.com/wrello" -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/Zip.lua: -------------------------------------------------------------------------------- 1 | -- made by wrello 2 | -- v1.2.0 3 | 4 | local Zip = {} 5 | 6 | function Zip.children(container, initFunction) 7 | local zipped = {} 8 | 9 | for _, moduleScript in ipairs(container:GetChildren()) do 10 | if moduleScript.ClassName == "ModuleScript" then 11 | local required = require(moduleScript) 12 | 13 | if initFunction then 14 | required = initFunction(moduleScript, required) 15 | end 16 | 17 | zipped[type(required) == "table" and required.Name or moduleScript.Name] = required 18 | end 19 | end 20 | 21 | return zipped 22 | end 23 | 24 | function Zip.descendants(container, initFunction) 25 | local zipped = {} 26 | 27 | for _, moduleScript in ipairs(container:GetDescendants()) do 28 | if moduleScript.ClassName == "ModuleScript" then 29 | local required = require(moduleScript) 30 | 31 | if initFunction then 32 | required = initFunction(moduleScript, required) 33 | end 34 | 35 | zipped[type(required) == "table" and required.Name or moduleScript.Name] = required 36 | end 37 | end 38 | 39 | return zipped 40 | end 41 | 42 | return Zip -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 wrello 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/AsyncInfoCache.lua: -------------------------------------------------------------------------------- 1 | -- made by wrello 2 | 3 | local AsyncInfoCache = {} 4 | 5 | local cache = {} 6 | 7 | local function retryPcall(count, obj, methodName, ...) 8 | local ok, result = nil, nil 9 | local i = 0 10 | 11 | repeat 12 | i += 1 13 | ok, result = pcall(obj[methodName], obj, ...) 14 | until ok or i == 3 or not task.wait(1) 15 | 16 | return ok, result 17 | end 18 | 19 | function AsyncInfoCache.asyncCall(retryCount, service, methodName, args, processResultFn) 20 | local idx = args[1] 21 | 22 | local _cache = cache[methodName] 23 | if not _cache then 24 | _cache = {} 25 | cache[methodName] = _cache 26 | end 27 | 28 | if not _cache[idx] then 29 | local ok, result = retryPcall(retryCount or 3, service, methodName, idx) 30 | 31 | if ok then 32 | _cache[idx] = if processResultFn then processResultFn(result) else result 33 | else 34 | warn(`[AsyncInfoCache] Error when calling {service.Name}:{methodName}({unpack(args) .. (#args > 2 and ("... and " .. #args-1 .. " more") or "")}): {result}`) 35 | end 36 | end 37 | 38 | return _cache[idx] 39 | end 40 | 41 | return AsyncInfoCache -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/AnimationsClass/AnimatedObjectsCache.lua: -------------------------------------------------------------------------------- 1 | local function pathToString(path) 2 | if type(path) == "table" then 3 | local concatStr = "" 4 | for _, v in ipairs(path) do 5 | concatStr ..= tostring(v) 6 | end 7 | return concatStr 8 | end 9 | 10 | return path 11 | end 12 | 13 | local AnimatedObjectsCache = {} 14 | AnimatedObjectsCache.__index = AnimatedObjectsCache 15 | 16 | function AnimatedObjectsCache.new() 17 | local self = setmetatable({}, AnimatedObjectsCache) 18 | 19 | self.Cache = {} 20 | 21 | return self 22 | end 23 | 24 | function AnimatedObjectsCache:Get(animatedObjectPath) 25 | local pathStr = pathToString(animatedObjectPath) 26 | 27 | return self.Cache[pathStr] 28 | end 29 | 30 | function AnimatedObjectsCache:Map(animatedObjectPath, animatedObjectTable) 31 | local pathStr = pathToString(animatedObjectPath) 32 | 33 | if self.Cache[pathStr] then 34 | return 35 | end 36 | 37 | self.Cache[pathStr] = animatedObjectTable 38 | end 39 | 40 | function AnimatedObjectsCache:Remove(animatedObjectPath) 41 | local pathStr = pathToString(animatedObjectPath) 42 | 43 | if not self.Cache[pathStr] then 44 | return 45 | end 46 | 47 | self.Cache[pathStr] = nil 48 | end 49 | 50 | return AnimatedObjectsCache -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/AnimationIdsUtil.lua: -------------------------------------------------------------------------------- 1 | local AnimationIdsUtil = {} 2 | 3 | function AnimationIdsUtil.HasProperties( 4 | animationId: number | {}, 5 | propertiesSettings: { 6 | Priority: Enum.AnimationPriority?, 7 | Looped: boolean?, 8 | StartSpeed: number?, 9 | MarkerTimes: boolean?, 10 | DoUnpack: boolean? 11 | } 12 | ): {} 13 | local doUnpack = propertiesSettings.DoUnpack 14 | propertiesSettings.DoUnpack = nil 15 | 16 | if type(animationId) == "table" then 17 | animationId._doUnpack = doUnpack 18 | animationId._runtimeProps = propertiesSettings 19 | 20 | return animationId 21 | else 22 | return { 23 | _runtimeProps = propertiesSettings, 24 | _singleAnimationId = animationId 25 | } 26 | end 27 | end 28 | 29 | function AnimationIdsUtil.HasAnimatedObject( 30 | animationId: number | {}, 31 | animatedObjectPath: {any} | string, 32 | animatedObjectSettings: { 33 | AutoAttach: boolean?, 34 | AutoDetach: boolean?, 35 | DoUnpack: boolean? 36 | } 37 | ): {} 38 | local doUnpack = animatedObjectSettings.DoUnpack 39 | animatedObjectSettings.DoUnpack = nil 40 | 41 | local animatedObjectInfo = { 42 | AnimatedObjectPath = animatedObjectPath, 43 | AnimatedObjectSettings = animatedObjectSettings 44 | } 45 | 46 | if type(animationId) == "table" then 47 | animationId._doUnpack = doUnpack 48 | animationId._animatedObjectInfo = animatedObjectInfo 49 | 50 | return animationId 51 | else 52 | return { 53 | _animatedObjectInfo = animatedObjectInfo, 54 | _singleAnimationId = animationId 55 | } 56 | end 57 | end 58 | 59 | return AnimationIdsUtil -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Deps/AutoCustomRBXAnimationIds.lua: -------------------------------------------------------------------------------- 1 | local Types = require(script.Parent.Parent.Package.Util.Types) 2 | 3 | --[=[ 4 | @tag Read Only 5 | @class AutoCustomRBXAnimationIds 6 | 7 | :::note 8 | Roblox model path: `Animations.Deps.AutoCustomRBXAnimationIds` 9 | ::: 10 | 11 | A table of animation ids to apply to player character's animate script, replacing default roblox animation ids on spawn if [`EnableAutoCustomRBXAnimationIds`](/api/AnimationsServer#EnableAutoCustomRBXAnimationIds) is enabled. 12 | 13 | ```lua 14 | -- All optional number values 15 | local AutoCustomRBXAnimationIds = { 16 | [Enum.HumanoidRigType.R6] = { -- [string]: number? | { [string]: number? } 17 | run = nil, 18 | walk = nil, 19 | jump = nil, 20 | idle = { 21 | Animation1 = nil, 22 | Animation2 = nil 23 | }, 24 | fall = nil, 25 | swim = nil, 26 | swimIdle = nil, 27 | climb = nil 28 | }, 29 | [Enum.HumanoidRigType.R15] = { -- [string]: number? | { [string]: number? } 30 | run = nil, 31 | walk = nil, 32 | jump = nil, 33 | idle = { 34 | Animation1 = nil, 35 | Animation2 = nil 36 | }, 37 | fall = nil, 38 | swim = nil, 39 | swimIdle = nil, 40 | climb = nil 41 | } 42 | } 43 | ``` 44 | 45 | :::info 46 | Roblox applies the `"walk"` animation id for `R6` characters and the `"run"` animation id for `R15` characters (instead of both). 47 | ::: 48 | 49 | :::caution 50 | You should not delete the `key = nil` key-value pairs. They are meant to stay for ease of modification. 51 | ::: 52 | ]=] 53 | 54 | local AutoCustomRBXAnimationIds = { 55 | [Enum.HumanoidRigType.R6] = { 56 | run = nil, 57 | walk = nil, 58 | jump = nil, 59 | idle = { 60 | Animation1 = nil, 61 | Animation2 = nil 62 | }, 63 | fall = nil, 64 | swim = nil, 65 | swimIdle = nil, 66 | climb = nil 67 | }, 68 | [Enum.HumanoidRigType.R15] = { 69 | run = nil, 70 | walk = nil, 71 | jump = nil, 72 | idle = { 73 | Animation1 = nil, 74 | Animation2 = nil 75 | }, 76 | fall = nil, 77 | swim = nil, 78 | swimIdle = nil, 79 | climb = nil 80 | } 81 | } 82 | 83 | return AutoCustomRBXAnimationIds :: Types.HumanoidRigTypeToCustomRBXAnimationIdsType -------------------------------------------------------------------------------- /docs/basic-usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Basic Usage 6 | 7 | --- 8 | 9 | # AnimationIds 10 | Configure your [`AnimationIds`](/api/AnimationIds) module: 11 | ```lua 12 | -- Inside of `ReplicatedStorage.Animations.Deps.AnimationIds` 13 | 14 | local ninjaJumpR15AnimationId = 656117878 15 | 16 | local AnimationIds = { 17 | Player = { -- Rig type of "Player" (required for any animations that will run on player characters) 18 | Jump = ninjaJumpR15AnimationId -- Path of "Jump" 19 | } 20 | } 21 | 22 | return AnimationIds 23 | ``` 24 | # AnimationsServer 25 | Playing an animation on the server on a **Player**: 26 | ```lua 27 | -- In a ServerScript 28 | 29 | local Players = game:GetService("Players") 30 | 31 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsServer) 32 | 33 | Animations:Init({ 34 | AutoLoadAllPlayerTracks = true, -- Defaults to false 35 | TimeToLoadPrints = true -- Defaults to true 36 | }) 37 | 38 | local function onPlayerAdded(player) 39 | Animations:AwaitAllTracksLoaded(player) 40 | 41 | print("Finished loading animations for", player.Name) 42 | 43 | -- Roblox's r15 ninja jump animation is looped. 44 | 45 | -- `AnimationTrack.Looped = false` doesn't replicate to clients, so 46 | -- it is impossible to make this not loop from the server. 47 | 48 | print("Playing looped ninja jump animation for", player.Name) 49 | Animations:PlayTrack(player, "Jump") 50 | end 51 | 52 | for _, player in ipairs(Players:GetPlayers()) do 53 | task.spawn(onPlayerAdded, player) 54 | end 55 | 56 | Players.PlayerAdded:Connect(onPlayerAdded) 57 | ``` 58 | 59 | # AnimationsClient 60 | Playing an animation on the client on the **LocalPlayer**: 61 | ```lua 62 | -- In a LocalScript 63 | 64 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsClient) 65 | 66 | Animations:Init({ 67 | AutoLoadAllPlayerTracks = true, -- Defaults to false 68 | TimeToLoadPrints = true -- Defaults to true 69 | }) 70 | 71 | local player = game.Players.LocalPlayer 72 | 73 | local function onCharacterAdded(char) 74 | Animations:AwaitAllTracksLoaded() 75 | 76 | print("Finished loading client animations") 77 | 78 | Animations:GetTrack("Jump").Looped = false -- Roblox's r15 ninja jump animation is looped. 79 | 80 | while true do 81 | print("Playing ninja jump client animation") 82 | Animations:PlayTrack("Jump") 83 | 84 | task.wait(3) 85 | end 86 | end 87 | 88 | if player.Character then 89 | onCharacterAdded(player.Character) 90 | end 91 | 92 | player.CharacterAdded:Connect(onCharacterAdded) 93 | ``` 94 | 95 | - [Basic Usage Monster/NPC](/docs/basic-usage-monster-npc) -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Deps/AnimationIds.lua: -------------------------------------------------------------------------------- 1 | local Types = require(script.Parent.Parent.Package.Util.Types) 2 | local AnimationIdsUtil = require(script.Parent.Parent.Package.Util.AnimationIdsUtil) 3 | 4 | local HasAnimatedObject = AnimationIdsUtil.HasAnimatedObject 5 | local HasProperties = AnimationIdsUtil.HasProperties 6 | 7 | --[=[ 8 | @type rigType string 9 | @within AnimationIds 10 | 11 | The first key in the `AnimationIds` module that indicates the type of rig the paired animation id table belongs to. 12 | 13 | ```lua 14 | local AnimationIds = { 15 | Player = { -- `rigType` of "Player" 16 | Dodge = { 17 | [Enum.KeyCode.W] = 0000000, 18 | [Enum.KeyCode.S] = 0000000, 19 | [Enum.KeyCode.A] = 0000000, 20 | [Enum.KeyCode.D] = 0000000, 21 | }, 22 | Run = 0000000, 23 | Walk = 0000000, 24 | Idle = 0000000 25 | } 26 | } 27 | ``` 28 | 29 | :::info 30 | The only preset `rigType` is that of **"Player"** for all player/client animation ids. 31 | ::: 32 | ]=] 33 | 34 | --[=[ 35 | @type animationId number 36 | @within AnimationIds 37 | ]=] 38 | 39 | --[=[ 40 | @interface idTable 41 | @within AnimationIds 42 | .[any] idTable | animationId 43 | ]=] 44 | 45 | --[=[ 46 | @interface propertiesSettings 47 | @within AnimationIds 48 | .Priority Enum.AnimationPriority? 49 | .Looped boolean? 50 | .StartSpeed number? -- Auto set animation speed through [`Animations:PlayTrack()`](/api/AnimationsServer#PlayTrack) related methods 51 | .DoUnpack boolean? -- Set the key-value pairs of [`animationId`](#HasProperties) (if it's a table) *in the parent table* 52 | .MarkerTimes boolean? -- Support [`Animations:GetTimeOfMarker()`](/api/AnimationsServer#GetTimeOfMarker) 53 | 54 | :::caution *changed in version 2.1.0* 55 | Added `MarkerTimes` property 56 | ::: 57 | 58 | :::caution *changed in version 2.3.0* 59 | Added `StartSpeed` property 60 | ::: 61 | ]=] 62 | 63 | --[=[ 64 | @type HasProperties (animationId: idTable, propertiesSettings: propertiesSettings): {} 65 | @within AnimationIds 66 | 67 | :::tip *added in version 2.0.0* 68 | ::: 69 | 70 | ```lua 71 | local AnimationIds = { 72 | Player = { -- `rigType` of "Player" 73 | Dodge = { 74 | [Enum.KeyCode.W] = 0000000, 75 | [Enum.KeyCode.S] = 0000000, 76 | [Enum.KeyCode.A] = 0000000, 77 | [Enum.KeyCode.D] = 0000000, 78 | }, 79 | Run = 0000000, 80 | Walk = 0000000, 81 | Idle = 0000000, 82 | Sword = { 83 | -- Now when the "Sword.Walk" animation plays it will 84 | -- automatically have `Enum.AnimationPriority.Action` priority 85 | Walk = HasProperties(0000000, { Priority = Enum.AnimationPriority.Action }) 86 | 87 | Idle = 0000000, 88 | Run = 0000000, 89 | 90 | -- Now when {"Sword", "AttackCombo", 1 or 2 or 3} animation 91 | -- plays it will automatically have `Enum.AnimationPriority.Action` priority and 92 | -- will support `Animations:GetTimeOfMarker()` 93 | AttackCombo = HasProperties({ 94 | [1] = 0000000, 95 | [2] = 0000000, 96 | [3] = 0000000 97 | }, { Priority = Enum.AnimationPriority.Action, MarkerTimes = true }) 98 | } 99 | }, 100 | } 101 | ``` 102 | ]=] 103 | 104 | --[=[ 105 | @interface animatedObjectSettings 106 | @within AnimationIds 107 | .AutoAttach boolean? 108 | .AutoDetach boolean? 109 | .DoUnpack boolean? -- Set the key-value pairs of [`animationId`](#HasAnimatedObject) (if it's a table) *in the parent table* 110 | ]=] 111 | 112 | --[=[ 113 | @tag Beta 114 | @type HasAnimatedObject (animationId: idTable, animatedObjectPath: path, animatedObjectSettings: animatedObjectSettings): {} 115 | @within AnimationIds 116 | 117 | ```lua 118 | local AnimationIds = { 119 | Player = { -- `rigType` of "Player" 120 | Dodge = { 121 | [Enum.KeyCode.W] = 0000000, 122 | [Enum.KeyCode.S] = 0000000, 123 | [Enum.KeyCode.A] = 0000000, 124 | [Enum.KeyCode.D] = 0000000, 125 | }, 126 | Run = 0000000, 127 | Walk = 0000000, 128 | Idle = 0000000, 129 | Sword = { 130 | -- Now when the "Sword.Walk" animation plays "Sword" will 131 | -- auto attach to the player and get animated 132 | Walk = HasAnimatedObject(0000000, "Sword", { AutoAttach = true }) 133 | 134 | Idle = 0000000, 135 | Run = 0000000, 136 | 137 | -- Now when {"Sword", "AttackCombo", 1 or 2 or 3} animation 138 | -- plays "Sword" will auto attach to the player and get 139 | -- animated 140 | AttackCombo = HasAnimatedObject({ 141 | [1] = 0000000, 142 | [2] = 0000000, 143 | [3] = 0000000 144 | }, "Sword", { AutoAttach = true }) 145 | } 146 | }, 147 | } 148 | ``` 149 | 150 | :::info 151 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 152 | ::: 153 | ]=] 154 | 155 | --[=[ 156 | @interface AnimationIds 157 | @within AnimationIds 158 | .[rigType] idTable 159 | 160 | ```lua 161 | local AnimationIds = { 162 | Player = { -- `rigType` of "Player" 163 | Dodge = { 164 | [Enum.KeyCode.W] = 0000000, 165 | [Enum.KeyCode.S] = 0000000, 166 | [Enum.KeyCode.A] = 0000000, 167 | [Enum.KeyCode.D] = 0000000, 168 | }, 169 | Run = 0000000, 170 | Walk = 0000000, 171 | Idle = 0000000 172 | }, 173 | 174 | BigMonster = { -- `rigType` of "BigMonster" 175 | HardMode = { 176 | Attack1 = 0000000, 177 | Attack2 = 0000000 178 | }, 179 | EasyMode = { 180 | Attack1 = 0000000, 181 | Attack2 = 0000000 182 | } 183 | }, 184 | 185 | SmallMonster = { -- `rigType` of "SmallMonster" 186 | HardMode = { 187 | Attack1 = 0000000, 188 | Attack2 = 0000000 189 | }, 190 | EasyMode = { 191 | Attack1 = 0000000, 192 | Attack2 = 0000000 193 | } 194 | } 195 | } 196 | ``` 197 | ]=] 198 | 199 | --[=[ 200 | @tag Read Only 201 | @class AnimationIds 202 | 203 | :::note 204 | Roblox model path: `Animations.Deps.AnimationIds` 205 | ::: 206 | ]=] 207 | local AnimationIds = { 208 | 209 | } 210 | 211 | return AnimationIds :: Types.AnimationIdsType -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/Signal.lua: -------------------------------------------------------------------------------- 1 | --!optimize 2 2 | --!nocheck 3 | --!native 4 | 5 | export type Connection = { 6 | Connected: boolean, 7 | 8 | Disconnect: (self: Connection) -> (), 9 | Reconnect: (self: Connection) -> (), 10 | } 11 | 12 | export type Signal = { 13 | RBXScriptConnection: RBXScriptConnection?, 14 | 15 | Connect: (self: Signal, fn: (...any) -> (), U...) -> Connection, 16 | Once: (self: Signal, fn: (...any) -> (), U...) -> Connection, 17 | Wait: (self: Signal) -> T..., 18 | Fire: (self: Signal, T...) -> (), 19 | DisconnectAll: (self: Signal) -> (), 20 | Destroy: (self: Signal) -> (), 21 | } 22 | 23 | local freeThreads: { thread } = {} 24 | 25 | local function runCallback(callback, thread, ...) 26 | callback(...) 27 | table.insert(freeThreads, thread) 28 | end 29 | 30 | local function yielder() 31 | while true do 32 | runCallback(coroutine.yield()) 33 | end 34 | end 35 | 36 | local Connection = {} 37 | Connection.__index = Connection 38 | 39 | local function disconnect(self: Connection) 40 | if not self.Connected then 41 | return 42 | end 43 | self.Connected = false 44 | 45 | local next = self._next 46 | local prev = self._prev 47 | 48 | if next then 49 | next._prev = prev 50 | end 51 | if prev then 52 | prev._next = next 53 | end 54 | 55 | local signal = self._signal 56 | if signal._head == self then 57 | signal._head = next 58 | end 59 | end 60 | 61 | local function reconnect(self: Connection) 62 | if self.Connected then 63 | return 64 | end 65 | self.Connected = true 66 | 67 | local signal = self._signal 68 | local head = signal._head 69 | if head then 70 | head._prev = self 71 | end 72 | signal._head = self 73 | 74 | self._next = head 75 | self._prev = false 76 | end 77 | 78 | Connection.Disconnect = disconnect 79 | Connection.Reconnect = reconnect 80 | 81 | --\\ Signal //-- 82 | local Signal = {} 83 | Signal.__index = Signal 84 | 85 | -- stylua: ignore 86 | local rbxConnect, rbxDisconnect do 87 | if task then 88 | local bindable = Instance.new("BindableEvent") 89 | rbxConnect = bindable.Event.Connect 90 | rbxDisconnect = bindable.Event:Connect(function() end).Disconnect 91 | bindable:Destroy() 92 | end 93 | end 94 | 95 | local function connect(self: Signal, fn: (...any) -> (), ...: U...): Connection 96 | local head = self._head 97 | local cn = setmetatable({ 98 | Connected = true, 99 | _signal = self, 100 | _fn = fn, 101 | _varargs = if not ... then false else { ... }, 102 | _next = head, 103 | _prev = false, 104 | }, Connection) 105 | 106 | if head then 107 | head._prev = cn 108 | end 109 | self._head = cn 110 | 111 | return cn 112 | end 113 | 114 | local function once(self: Signal, fn: (...any) -> (), ...: U...) 115 | local cn 116 | cn = connect(self, function(...) 117 | disconnect(cn) 118 | fn(...) 119 | end, ...) 120 | return cn 121 | end 122 | 123 | local wait = if task 124 | then function(self: Signal): ...any 125 | local thread = coroutine.running() 126 | local cn 127 | cn = connect(self, function(...) 128 | disconnect(cn) 129 | task.spawn(thread, ...) 130 | end) 131 | return coroutine.yield() 132 | end 133 | else function(self: Signal): ...any 134 | local thread = coroutine.running() 135 | local cn 136 | cn = connect(self, function(...) 137 | disconnect(cn) 138 | local passed, message = coroutine.resume(thread, ...) 139 | if not passed then 140 | error(message, 0) 141 | end 142 | end) 143 | return coroutine.yield() 144 | end 145 | 146 | local fire = if task 147 | then function(self: Signal, ...: any) 148 | local cn = self._head 149 | while cn do 150 | local thread 151 | if #freeThreads > 0 then 152 | thread = freeThreads[#freeThreads] 153 | freeThreads[#freeThreads] = nil 154 | else 155 | thread = coroutine.create(yielder) 156 | coroutine.resume(thread) 157 | end 158 | 159 | if not cn._varargs then 160 | task.spawn(thread, cn._fn, thread, ...) 161 | else 162 | local args = cn._varargs 163 | local len = #args 164 | local count = len 165 | for _, value in { ... } do 166 | count += 1 167 | args[count] = value 168 | end 169 | 170 | task.spawn(thread, cn._fn, thread, table.unpack(args)) 171 | 172 | for i = count, len + 1, -1 do 173 | args[i] = nil 174 | end 175 | end 176 | 177 | cn = cn._next 178 | end 179 | end 180 | else function(self: Signal, ...: any) 181 | local cn = self._head 182 | while cn do 183 | local thread 184 | if #freeThreads > 0 then 185 | thread = freeThreads[#freeThreads] 186 | freeThreads[#freeThreads] = nil 187 | else 188 | thread = coroutine.create(yielder) 189 | coroutine.resume(thread) 190 | end 191 | 192 | if not cn._varargs then 193 | local passed, message = coroutine.resume(thread, cn._fn, thread, ...) 194 | if not passed then 195 | print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) 196 | end 197 | else 198 | local args = cn._varargs 199 | local len = #args 200 | local count = len 201 | for _, value in { ... } do 202 | count += 1 203 | args[count] = value 204 | end 205 | 206 | local passed, message = coroutine.resume(thread, cn._fn, thread, table.unpack(args)) 207 | if not passed then 208 | print(string.format("%s\nstacktrace:\n%s", message, debug.traceback())) 209 | end 210 | 211 | for i = count, len + 1, -1 do 212 | args[i] = nil 213 | end 214 | end 215 | 216 | cn = cn._next 217 | end 218 | end 219 | 220 | local function disconnectAll(self: Signal) 221 | local cn = self._head 222 | while cn do 223 | disconnect(cn) 224 | cn = cn._next 225 | end 226 | end 227 | 228 | local function destroy(self: Signal) 229 | disconnectAll(self) 230 | local cn = self.RBXScriptConnection 231 | if cn then 232 | rbxDisconnect(cn) 233 | self.RBXScriptConnection = nil 234 | end 235 | end 236 | 237 | --\\ Constructors 238 | function Signal.new(): Signal 239 | return setmetatable({ _head = false }, Signal) 240 | end 241 | 242 | function Signal.wrap(signal: RBXScriptSignal): Signal 243 | local wrapper = setmetatable({ _head = false }, Signal) 244 | wrapper.RBXScriptConnection = rbxConnect(signal, function(...) 245 | fire(wrapper, ...) 246 | end) 247 | return wrapper 248 | end 249 | 250 | --\\ Methods 251 | Signal.Connect = connect 252 | Signal.Once = once 253 | Signal.Wait = wait 254 | Signal.Fire = fire 255 | Signal.DisconnectAll = disconnectAll 256 | Signal.Destroy = destroy 257 | 258 | return { new = Signal.new, wrap = Signal.wrap } -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/Await.lua: -------------------------------------------------------------------------------- 1 | -- made by wrello 2 | -- v1.2.0 3 | 4 | type EventWithSettingsType = {} -- {RBXScriptSignal, WinnerKey?, ...any?} 5 | type TimeoutType = (number | RBXScriptSignal)? 6 | 7 | local WINNER_KEY_FLAG = "winner key" 8 | local ALL_ARGUMENTS_CHECK_FLAG = "all arguments check" 9 | 10 | local function isWinnerKey(t) 11 | if type(t) == "table" then 12 | return t[1] == WINNER_KEY_FLAG 13 | end 14 | end 15 | 16 | local function isAllArgumentsCheck(t) 17 | if type(t) == "table" then 18 | return t[1] == ALL_ARGUMENTS_CHECK_FLAG 19 | end 20 | end 21 | 22 | local function argsMatchParams(params, ...) 23 | local args = {...} 24 | 25 | for i, param in ipairs(params) do 26 | if isAllArgumentsCheck(param) then 27 | if not param[2](...) then 28 | return false 29 | end 30 | elseif type(param) == "function" then 31 | if not param(args[i]) then -- Typechecking 32 | return false 33 | end 34 | elseif param ~= args[i] then 35 | return false 36 | end 37 | end 38 | 39 | return true 40 | end 41 | 42 | local Await = {} 43 | 44 | -- The typechecker is not required, but it adds more functionality 45 | if script:FindFirstChild("t") then 46 | Await.t = require(script.t) 47 | end 48 | 49 | -- Creates a winner key which can be used to determine the winner in an event race 50 | -- @return { WINNER_KEY_FLAG, token} 51 | -- Example: 52 | --[[ 53 | 54 | (see Await.First() example) 55 | 56 | ]] 57 | function Await.WinnerKey(token) 58 | return {WINNER_KEY_FLAG, token} 59 | end 60 | 61 | function Await.AllArgumentsCheck(fn) 62 | return {ALL_ARGUMENTS_CHECK_FLAG, fn} 63 | end 64 | 65 | -- Waits for the event with specific args to fire 66 | -- @return ( timedOut, <...any?> ...passed args) 67 | -- Example: 68 | --[[ 69 | 70 | local Players = game:GetService("Players") 71 | 72 | local timeoutDuration = nil -- The timeout duration is optional 73 | 74 | Await.Args(timeoutDuration, Players.PlayerRemoving, function(player) 75 | return player.Name == "wrello" 76 | end) 77 | 78 | print("wrello left!") 79 | 80 | ]] 81 | function Await.Args(timeout: TimeoutType, event: RBXScriptSignal, ...: any) 82 | local thread = coroutine.running() 83 | local params = {...} 84 | 85 | local conn 86 | 87 | local function done(...) 88 | task.spawn(thread, ...) 89 | conn:Disconnect() 90 | end 91 | 92 | conn = event:Connect(function(...) 93 | if conn.Connected then 94 | if argsMatchParams(params, ...) then 95 | done(false, ...) 96 | end 97 | end 98 | end) 99 | 100 | if timeout then 101 | if type(timeout) == "number" then 102 | task.delay(timeout, function() 103 | if conn.Connected then 104 | done(true) 105 | end 106 | end) 107 | else 108 | timeout:Once(function() 109 | if conn.Connected then 110 | done(true) 111 | end 112 | end) 113 | end 114 | end 115 | 116 | return coroutine.yield() 117 | end 118 | 119 | -- Waits for the event to fire 120 | -- @return ( timedOut, <...any?> ...passed args) 121 | -- Example: 122 | --[[ 123 | 124 | local part = Instance.new("Part") 125 | part.Parent = workspace 126 | 127 | local timeoutDuration = 5 -- The timeout duration is optional 128 | 129 | local timedOut, hitPart = Await.Event(timeoutDuration, part.Touched) 130 | 131 | if not timedOut then 132 | print("touched", hitPart) 133 | else 134 | print("did not touch anything after", timeoutDuration, "seconds") 135 | end 136 | 137 | ]] 138 | function Await.Event(timeout: TimeoutType, event: RBXScriptSignal) 139 | local thread = coroutine.running() 140 | 141 | local conn 142 | 143 | local function done(...) 144 | conn:Disconnect() 145 | task.spawn(thread, ...) 146 | end 147 | 148 | conn = event:Connect(function(...) 149 | if conn.Connected then 150 | done(false, ...) 151 | end 152 | end) 153 | 154 | if timeout then 155 | if type(timeout) == "number" then 156 | task.delay(timeout, function() 157 | if conn.Connected then 158 | done(true) 159 | end 160 | end) 161 | else 162 | timeout:Once(function() 163 | if conn.Connected then 164 | done(true) 165 | end 166 | end) 167 | end 168 | end 169 | 170 | return coroutine.yield() 171 | end 172 | 173 | -- Waits for the first event of multiple to fire 174 | -- @return ( timedOut, winnerKey, <{...any?}> winnerArgs) 175 | -- Example: 176 | --[[ 177 | 178 | local part = Instance.new("Part") 179 | part.Parent = workspace 180 | 181 | local eventRaceArgs = { 182 | 183 | {part.Destroying, Await.WinnerKey("part destroyed")}, 184 | 185 | {part.Touched, Await.WinnerKey("touched by hrp"), function(hitPart) 186 | return hitPart.Name == "HumanoidRootPart" 187 | end}, 188 | 189 | part:GetPropertyChangedSignal("Name") -- The winner key would default to 2 in this case (that's this event's position in the 'eventRaceArgs' table) 190 | 191 | } 192 | 193 | local timeoutDuration = nil -- The timeout duration is optional 194 | 195 | -- Wait for the first one to fire 196 | local timedOut, winnerKey, winnerArgs = Await.First(timeoutDuration, unpack(eventRaceArgs)) 197 | 198 | 199 | -- Use this if you set the timeoutDuration to not nil 200 | 201 | --if not timedOut then 202 | -- print(winnerKey, "was the first event to fire! Passed args:", winnerArgs) 203 | --else 204 | -- print("no events fired after", timeoutDuration, "seconds") 205 | --end 206 | 207 | 208 | print(winnerKey, "was the first event to fire! Passed args:", ...) 209 | 210 | ]] 211 | function Await.First(timeout: TimeoutType, ...: RBXScriptSignal | EventWithSettingsType) 212 | local thread = coroutine.running() 213 | 214 | local conns = {} 215 | 216 | local function done(...) 217 | for _, conn in ipairs(conns) do 218 | conn:Disconnect() 219 | end 220 | 221 | task.spawn(thread, ...) 222 | end 223 | 224 | for i, event in ipairs({...}) do 225 | local conn 226 | 227 | if event.Connect then 228 | conn = event:Connect(function(...) 229 | if conn.Connected then 230 | done(false, i, {...}) 231 | end 232 | end) 233 | else 234 | local winnerKey = nil 235 | 236 | local params = {} do 237 | local len = #event 238 | for i = 2, len do 239 | if isWinnerKey(event[i]) then 240 | winnerKey = event[i][2] 241 | else 242 | table.insert(params, event[i]) 243 | end 244 | end 245 | end 246 | 247 | conn = event[1]:Connect(function(...) 248 | if conn.Connected then 249 | if argsMatchParams(params, ...) then 250 | done(false, winnerKey or i, {...}) 251 | end 252 | end 253 | end) 254 | end 255 | 256 | table.insert(conns, conn) 257 | end 258 | 259 | if timeout then 260 | if type(timeout) == "number" then 261 | task.delay(timeout, function() 262 | if conns[1].Connected then 263 | done(true) 264 | end 265 | end) 266 | else 267 | timeout:Once(function() 268 | if conns[1].Connected then 269 | done(true) 270 | end 271 | end) 272 | end 273 | end 274 | 275 | return coroutine.yield() 276 | end 277 | 278 | return Await -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/Types.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | 3 | type EventType = { 4 | Wait: (self: EventType) -> (...any), 5 | Connect: (self: EventType, handler: T) -> RBXScriptConnection, 6 | Once: (self: EventType, handler: T) -> RBXScriptConnection, 7 | } 8 | 9 | export type RanFullMethodType = boolean -- Will be false if the method returned instantly 10 | 11 | export type AnimationsServerType = { 12 | PreloadAsyncProgressed: EventType<(n: number, total: number, loadedAnimInstance: Animation) -> ()>, 13 | 14 | Init: (self: AnimationsServerType, initOptions: AnimationsServerInitOptionsType?) -> (), 15 | 16 | AwaitPreloadAsyncFinished: (self: AnimationsServerType) -> {Animation?} | RanFullMethodType, 17 | 18 | GetTrackStartSpeed: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string) -> number?, 19 | 20 | LoadTracksAt: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string) -> RanFullMethodType, 21 | LoadAllTracks: (self: AnimationsServerType, player_or_rig: Player | Model) -> RanFullMethodType, 22 | 23 | AreTracksLoadedAt: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string) -> boolean, 24 | AreAllTracksLoaded: (self: AnimationsServerType, player_or_rig: Player | Model) -> boolean, 25 | 26 | AwaitTracksLoadedAt: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string) -> RanFullMethodType, 27 | AwaitAllTracksLoaded: (self: AnimationsServerType, player_or_rig: Player | Model) -> RanFullMethodType, 28 | 29 | Register: (self: AnimationsServerType, player_or_rig: Player | Model, rigType: string) -> (), 30 | AwaitRegistered: (self: AnimationsServerType, player_or_rig: Player | Model) -> RanFullMethodType, 31 | IsRegistered: (self: AnimationsServerType, player_or_rig: Player | Model) -> boolean, 32 | 33 | GetTrack: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string) -> AnimationTrack?, 34 | PlayTrack: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 35 | StopTrack: (self: AnimationsServerType, player_or_rig: Player | Model, path: {any} | string, fadeTime: number?) -> AnimationTrack, 36 | 37 | FindFirstRigPlayingTrack: (self: AnimationsServerType, rig: Model, path: {any} | string) -> AnimationTrack?, 38 | WaitForRigPlayingTrack: (self: AnimationsServerType, rig: Model, path: {any} | string, timeout: number?) -> AnimationTrack?, 39 | 40 | GetTimeOfMarker: (self: AnimationsServerType, animTrack_or_IdString: AnimationTrack | string, markerName: string) -> number?, 41 | GetAnimationIdString: (self: AnimationsServerType, rigType: string, path: {any} | string) -> string, 42 | 43 | StopPlayingTracks: (self: AnimationsServerType, player_or_rig: Player | Model, fadeTime: number?) -> {AnimationTrack?}, 44 | GetPlayingTracks: (self: AnimationsServerType, player_or_rig: Player | Model) -> {AnimationTrack?}, 45 | StopTracksOfPriority: (self: AnimationsServerType, player_or_rig: Player | Model, animationPriority: Enum.AnimationPriority, fadeTime: number?) -> {AnimationTrack?}, 46 | 47 | GetTrackFromAlias: (self: AnimationsServerType, player_or_rig: Player | Model, alias: any) -> AnimationTrack?, 48 | PlayTrackFromAlias: (self: AnimationsServerType, player_or_rig: Player | Model, alias: any, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 49 | StopTrackFromAlias: (self: AnimationsServerType, player_or_rig: Player | Model, alias: any, fadeTime: number?) -> AnimationTrack, 50 | 51 | SetTrackAlias: (self: AnimationsServerType, player_or_rig: Player | Model, alias: any, path: {any} | string) -> (), 52 | RemoveTrackAlias: (self: AnimationsServerType, player_or_rig: Player | Model, alias: any) -> (), 53 | 54 | AttachAnimatedObject: (self: AnimationsServerType, player_or_rig: Player | Model, animatedObjectPath: {any} | string) -> (), 55 | DetachAnimatedObject: (self: AnimationsServerType, player_or_rig: Player | Model, animatedObjectPath: {any} | string) -> (), 56 | 57 | EquipAnimatedTool: (self: AnimationsServerType, player_or_rig: Player | Model, tool: Tool, motor6dName: string) -> (), 58 | 59 | ApplyCustomRBXAnimationIds: (self: AnimationsServerType, player_or_rig: Player | Model, humanoidRigTypeCustomRBXAnimationIds: HumanoidRigTypeToCustomRBXAnimationIdsType) -> (), 60 | 61 | GetAppliedProfileName: (self: AnimationsServerType, player_or_rig: Player | Model) -> string?, 62 | GetAnimationProfile: (self: AnimationsServerType, animationProfileName: string) -> HumanoidRigTypeToCustomRBXAnimationIdsType?, 63 | ApplyAnimationProfile: (self: AnimationsServerType, player_or_rig: Player | Model, animationProfileName: string) -> () 64 | } & AnimationsServerInitOptionsType 65 | export type AnimationsClientType = { 66 | PreloadAsyncProgressed: EventType<(n: number, total: number, loadedAnimInstance: Animation) -> ()>, 67 | 68 | Init: (self: AnimationsClientType, initOptions: AnimationsClientInitOptionsType?) -> (), 69 | 70 | AwaitPreloadAsyncFinished: (self: AnimationsClientType) -> {Animation?} | RanFullMethodType, 71 | 72 | GetTrackStartSpeed: (self: AnimationsClientType, path: {any} | string) -> number?, 73 | GetRigTrackStartSpeed: (self: AnimationsClientType, rig: Model, path: {any} | string) -> number?, 74 | 75 | AreTracksLoadedAt: (self: AnimationsClientType, path: {any} | string) -> boolean, 76 | AreRigTracksLoadedAt: (self: AnimationsClientType, rig: Model, path: {any} | string) -> boolean, 77 | 78 | AreAllTracksLoaded: (self: AnimationsClientType) -> boolean, 79 | AreAllRigTracksLoaded: (self: AnimationsClientType, rig: Model) -> boolean, 80 | 81 | LoadTracksAt: (self: AnimationsClientType, path: {any} | string) -> RanFullMethodType, 82 | LoadRigTracksAt: (self: AnimationsClientType, rig: Model, path: {any} | string) -> RanFullMethodType, 83 | 84 | LoadAllTracks: (self: AnimationsClientType) -> RanFullMethodType, 85 | LoadAllRigTracks: (self: AnimationsClientType, rig: Model) -> RanFullMethodType, 86 | 87 | AwaitTracksLoadedAt: (self: AnimationsClientType, path: {any} | string) -> RanFullMethodType, 88 | AwaitRigTracksLoadedAt: (self: AnimationsClientType, rig: Model, path: {any} | string) -> RanFullMethodType, 89 | 90 | AwaitAllTracksLoaded: (self: AnimationsClientType) -> RanFullMethodType, 91 | AwaitAllRigTracksLoaded: (self: AnimationsClientType, rig: Model) -> RanFullMethodType, 92 | 93 | Register: (self: AnimationsClientType) -> (), 94 | RegisterRig: (self: AnimationsClientType, rig: Model, rigType: string) -> (), 95 | 96 | AwaitRegistered: (self: AnimationsClientType) -> RanFullMethodType, 97 | AwaitRigRegistered: (self: AnimationsClientType, rig: Model) -> RanFullMethodType, 98 | 99 | IsRegistered: (self: AnimationsClientType) -> boolean, 100 | IsRigRegistered: (self: AnimationsClientType, rig: Model) -> boolean, 101 | 102 | GetTrack: (self: AnimationsClientType, path: {any} | string) -> AnimationTrack?, 103 | GetRigTrack: (self: AnimationsClientType, rig: Model, path: {any} | string) -> AnimationTrack?, 104 | 105 | PlayTrack: (self: AnimationsClientType, path: {any} | string, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 106 | PlayRigTrack: (self: AnimationsClientType, rig: Model, path: {any} | string, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 107 | 108 | StopTrack: (self: AnimationsClientType, path: {any} | string, fadeTime: number?) -> AnimationTrack, 109 | StopRigTrack: (self: AnimationsClientType, rig: Model, path: {any} | string, fadeTime: number?) -> AnimationTrack, 110 | 111 | StopPlayingTracks: (self: AnimationsClientType, fadeTime: number?) -> {AnimationTrack?}, 112 | StopRigPlayingTracks: (self: AnimationsClientType, rig: Model, fadeTime: number?) -> {AnimationTrack?}, 113 | 114 | FindFirstRigPlayingTrack: (self: AnimationsClientType, rig: Model, path: {any} | string) -> AnimationTrack?, 115 | WaitForRigPlayingTrack: (self: AnimationsClientType, rig: Model, path: {any} | string, timeout: number?) -> AnimationTrack?, 116 | 117 | GetPlayingTracks: (self: AnimationsClientType) -> {AnimationTrack?}, 118 | GetRigPlayingTracks: (self: AnimationsClientType, rig: Model) -> {AnimationTrack?}, 119 | 120 | StopTracksOfPriority: (self: AnimationsClientType, animationPriority: Enum.AnimationPriority, fadeTime: number?) -> {AnimationTrack?}, 121 | StopRigTracksOfPriority: (self: AnimationsClientType, rig: Model, animationPriority: Enum.AnimationPriority, fadeTime: number?) -> {AnimationTrack?}, 122 | 123 | GetTrackFromAlias: (self: AnimationsClientType, alias: any) -> AnimationTrack?, 124 | GetRigTrackFromAlias: (self: AnimationsClientType, rig: Model, alias: any) -> AnimationTrack?, 125 | 126 | PlayTrackFromAlias: (self: AnimationsClientType, alias: any, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 127 | PlayRigTrackFromAlias: (self: AnimationsClientType, rig: Model, alias: any, fadeTime: number?, weight: number?, speed: number?) -> AnimationTrack, 128 | 129 | GetTimeOfMarker: (self: AnimationsClientType, animTrack_or_IdString: AnimationTrack | string, markerName: string) -> number?, 130 | GetAnimationIdString: (self: AnimationsClientType, rigType: string, path: {any} | string) -> string, 131 | 132 | StopTrackFromAlias: (self: AnimationsClientType, alias: any, fadeTime: number?) -> AnimationTrack, 133 | StopRigTrackFromAlias: (self: AnimationsClientType, rig: Model, alias: any, fadeTime: number?) -> AnimationTrack, 134 | 135 | SetTrackAlias: (self: AnimationsClientType, alias: any, path: {any} | string) -> (), 136 | SetRigTrackAlias: (self: AnimationsClientType, rig: Model, alias: any, path: {any} | string) -> (), 137 | 138 | RemoveTrackAlias: (self: AnimationsClientType, alias: any) -> (), 139 | RemoveRigTrackAlias: (self: AnimationsClientType, rig: Model, alias: any) -> (), 140 | 141 | AttachAnimatedObject: (self: AnimationsClientType, animatedObjectPath: {any} | string) -> (), 142 | AttachRigAnimatedObject: (self: AnimationsClientType, rig: Model, animatedObjectPath: {any} | string) -> (), 143 | 144 | DetachAnimatedObject: (self: AnimationsClientType, animatedObjectPath: {any} | string) -> (), 145 | DetachRigAnimatedObject: (self: AnimationsClientType, rig: Model, animatedObjectPath: {any} | string) -> (), 146 | 147 | -- EquipAnimatedTool: (self: AnimationsClientType, tool: Tool, motor6dName: string) -> (), 148 | EquipRigAnimatedTool: (self: AnimationsClientType, rig: Model, tool: Tool, motor6dName: string) -> (), 149 | 150 | ApplyCustomRBXAnimationIds: (self: AnimationsClientType, humanoidRigTypeCustomRBXAnimationIds: HumanoidRigTypeToCustomRBXAnimationIdsType) -> (), 151 | ApplyRigCustomRBXAnimationIds: (self: AnimationsClientType, rig: Model, humanoidRigTypeCustomRBXAnimationIds: HumanoidRigTypeToCustomRBXAnimationIdsType) -> (), 152 | 153 | GetAppliedProfileName: (self: AnimationsClientType) -> string?, 154 | GetRigAppliedProfileName: (self: AnimationsClientType, rig: Model) -> string?, 155 | 156 | GetAnimationProfile: (self: AnimationsClientType, animationProfileName: string) -> HumanoidRigTypeToCustomRBXAnimationIdsType?, 157 | 158 | ApplyAnimationProfile: (self: AnimationsClientType, animationProfileName: string) -> (), 159 | ApplyRigAnimationProfile: (self: AnimationsClientType, rig: Model, animationProfileName: string) -> (), 160 | } & AnimationsClientInitOptionsType 161 | 162 | export type CustomRBXAnimationIdsType = { 163 | run: number?, 164 | walk: number?, 165 | jump: number?, 166 | idle: {Animation1: number?, Animation2: number?}?, 167 | fall: number?, 168 | swim: number?, 169 | swimIdle: number?, 170 | climb: number? 171 | } 172 | 173 | export type HumanoidRigTypeToCustomRBXAnimationIdsType = { 174 | [Enum.HumanoidRigType]: CustomRBXAnimationIdsType? 175 | } 176 | 177 | type rigType = string 178 | type animationId = number 179 | type idTable = { 180 | [any]: idTable | animationId 181 | } 182 | 183 | export type AnimationIdsType = { 184 | [rigType]: idTable 185 | } 186 | 187 | type AnimationsSharedInitOptionsType = { 188 | DepsFolderPath: string?, 189 | AutoLoadAllPlayerTracks: boolean?, 190 | TimeToLoadPrints: boolean?, 191 | AnimatedObjectsDebugMode: boolean?, 192 | EnableAutoCustomRBXAnimationIds: boolean? 193 | } 194 | export type AnimationsClientInitOptionsType = { 195 | 196 | } & AnimationsSharedInitOptionsType 197 | export type AnimationsServerInitOptionsType = { 198 | 199 | } & AnimationsSharedInitOptionsType 200 | 201 | return {} -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/AnimationsClass/AnimatedObject.lua: -------------------------------------------------------------------------------- 1 | local RunService = game:GetService("RunService") 2 | 3 | local CustomAssert = require(script.Parent.Parent.CustomAssert) 4 | 5 | local ANIMATED_OBJECT_MOTOR6D_NAME = "AnimatedObjectMotor6D" 6 | local TOOL_ALIASES_MODULE_NAME = "ToolName" 7 | local SERVER_ANIM_OBJ_AUTO_ATTACH_STR = "server_auto_attach_animated_object_path_type" 8 | 9 | local rigToRightGripDisabledCount = {} 10 | 11 | function rigToRightGripDisabledCount.track(rig) 12 | if not rigToRightGripDisabledCount[rig] then 13 | rigToRightGripDisabledCount[rig] = 0 14 | 15 | local conn; conn = rig.AncestryChanged:Connect(function(_, newParent) 16 | if newParent == nil then 17 | conn:Disconnect() 18 | rigToRightGripDisabledCount[rig] = nil 19 | end 20 | end) 21 | end 22 | end 23 | 24 | local function setupMotor6d(motor6d, rig, animatedObject) 25 | -- Setting the motor6d's Part0 to the correct body part so it will work with 26 | -- the animation 27 | local part0NameStrVal = motor6d:FindFirstChild("Part0Name") 28 | CustomAssert(part0NameStrVal, `No "Part0Name" string value found under animated object motor6d for animated object [ {animatedObject:GetFullName()} ]`) 29 | motor6d.Part0 = rig:FindFirstChild(part0NameStrVal.Value, true) 30 | 31 | -- Setting the motor6d's Part1 to the correct animated object part so it 32 | -- will work with the animation 33 | if animatedObject:IsA("BasePart") then 34 | motor6d.Part1 = animatedObject 35 | else 36 | local part1NameStrVal = motor6d:FindFirstChild("Part1Name") 37 | CustomAssert(part1NameStrVal, `No "Part1Name" string value found under animated object motor6d for animated object [ {animatedObject:GetFullName()} ]`) 38 | motor6d.Part1 = animatedObject:FindFirstChild(part1NameStrVal.Value, true) 39 | end 40 | end 41 | 42 | local AnimatedObject = {} 43 | AnimatedObject.__index = AnimatedObject 44 | 45 | AnimatedObject.SERVER_ANIM_OBJ_AUTO_ATTACH_STR = SERVER_ANIM_OBJ_AUTO_ATTACH_STR 46 | 47 | function AnimatedObject.new(rig, animatedObjectSrc, animatedObjectInfo, debugPrints) 48 | local self = setmetatable({}, AnimatedObject) 49 | 50 | self._debugPrints = debugPrints 51 | self._animatedObjectSrc = animatedObjectSrc 52 | self._animatedObjectInfo = animatedObjectInfo 53 | self._listenToDetachHandlers = {} 54 | self._rightGripWeldEnabled = true 55 | self._rig = rig 56 | 57 | if animatedObjectSrc.ClassName == "Motor6D" then 58 | local toolAliasesModule = animatedObjectSrc:FindFirstChild(TOOL_ALIASES_MODULE_NAME) 59 | if toolAliasesModule then 60 | self._toolAliases = require(toolAliasesModule) 61 | CustomAssert(typeof(self._toolAliases) == "table" and (#self._toolAliases == 0 or typeof(self._toolAliases[1]) == "string"), "'ToolName' module must return an array of strings") 62 | table.insert(self._toolAliases, animatedObjectSrc.Name) 63 | end 64 | end 65 | 66 | rigToRightGripDisabledCount.track(rig) 67 | 68 | return self 69 | end 70 | 71 | function AnimatedObject:_setRightGripWeldEnabled(enabled) 72 | if enabled == self._rightGripWeldEnabled then 73 | return 74 | end 75 | 76 | self._rightGripWeldEnabled = enabled 77 | 78 | if not enabled then 79 | rigToRightGripDisabledCount[self._rig] += 1 80 | else 81 | rigToRightGripDisabledCount[self._rig] -= 1 82 | end 83 | 84 | if self._debugPrints then 85 | print(`[ANIM_OBJ_DEBUG] Trying set right grip weld enabled [ {enabled} ]. New right grip weld disabled count [ {rigToRightGripDisabledCount[self._rig]} ]`) 86 | end 87 | 88 | local rightGripWeld = (self._rig:FindFirstChild("Right Arm") or self._rig:FindFirstChild("RightHand")):FindFirstChild("RightGrip") 89 | 90 | if not rightGripWeld or rightGripWeld.Enabled == enabled then 91 | if self._debugPrints then 92 | print("[ANIM_OBJ_DEBUG] ") 93 | end 94 | return 95 | end 96 | 97 | if enabled 98 | 99 | -- Re-enable right grip weld if the count is back down to only one 100 | -- animated object that has it disabled 101 | and rigToRightGripDisabledCount[self._rig] ~= 0 102 | 103 | then 104 | if self._debugPrints then 105 | print("[ANIM_OBJ_DEBUG] ") 106 | end 107 | return 108 | end 109 | 110 | if self._debugPrints then 111 | print("[ANIM_OBJ_DEBUG] ") 112 | end 113 | 114 | rightGripWeld.Enabled = enabled 115 | end 116 | 117 | function AnimatedObject:_createAnimatedObjectClone() 118 | if self._animatedObjectSrc.ClassName == "Motor6D" then -- motor6d only 119 | self._animatedObjectClone = self._animatedObjectSrc:Clone() 120 | elseif self._animatedObjectSrc.ClassName == "Tool" -- tool, model, basepart 121 | or self._animatedObjectSrc.ClassName == "Model" 122 | or self._animatedObjectSrc:IsA("BasePart") 123 | then 124 | self._animatedObjectClone = self._animatedObjectSrc:Clone() 125 | elseif self._animatedObjectSrc.ClassName == "Folder" then 126 | self._animatedObjectClone = self._animatedObjectSrc:Clone() 127 | else 128 | CustomAssert(false, `Animated object [ {self._animatedObjectSrc:GetFullName()} ] of class [ {self._animatedObjectSrc.ClassName} ] is not a supported animated object type`) 129 | end 130 | 131 | if self._animatedObjectInfo 132 | and RunService:IsServer() 133 | then 134 | if self._animatedObjectInfo.AnimatedObjectSettings.AutoAttach then 135 | self._animatedObjectClone:SetAttribute(SERVER_ANIM_OBJ_AUTO_ATTACH_STR, type(self._animatedObjectInfo.AnimatedObjectPath)) 136 | end 137 | end 138 | 139 | return self._animatedObjectClone 140 | end 141 | 142 | function AnimatedObject:_setLifelineAnimatedObject(lifelineAnimatedObject) 143 | if self._lifelineConn then 144 | self._lifelineConn:Disconnect() 145 | end 146 | 147 | -- When our lifeline is detached (unparented from the rig) we will detach as 148 | -- well. 149 | self._lifelineConn = lifelineAnimatedObject.AncestryChanged:Connect(function(_, newParent) 150 | if newParent ~= self._rig then 151 | self._lifelineConn:Disconnect() 152 | self:Detach() 153 | end 154 | end) 155 | end 156 | 157 | function AnimatedObject:Attach(toolToEquip) 158 | local lifelineAnimatedObject = nil 159 | 160 | if self._animatedObjectSrc.ClassName == "Motor6D" then -- motor6d only 161 | -- WARNING: If the tool does not have a "Handle" and therefore does not 162 | -- automatically create a "RightGrip" weld (i.e. doesn't attach it to 163 | -- the character's hand before it's parented to the character) this 164 | -- motor6d will not save it from appearing floating in space for a split 165 | -- second before it attaches to the correct animated position. 166 | 167 | if self._debugPrints then 168 | print("[ANIM_OBJ_DEBUG] Attaching animated object motor6d only [", self._animatedObjectSrc:GetFullName(), "]") 169 | end 170 | 171 | local equippedTool 172 | if toolToEquip then 173 | equippedTool = toolToEquip 174 | else 175 | if not self._toolAliases then 176 | equippedTool = self._rig:FindFirstChild(self._animatedObjectSrc.Name) 177 | if not equippedTool then 178 | if self._debugPrints then 179 | print(`[ANIM_OBJ_DEBUG] Unable to attach animated object - no equipped tool found with same name of motor6d [ {self._animatedObjectSrc.Name} ]`) 180 | end 181 | return 182 | end 183 | else 184 | for _, v in self._toolAliases do 185 | equippedTool = self._rig:FindFirstChild(v) 186 | if equippedTool then 187 | break 188 | end 189 | end 190 | if not equippedTool then 191 | if self._debugPrints then 192 | print(`[ANIM_OBJ_DEBUG] Unable to attach animated object - no equipped tool found with same name of motor6d aliases [ {table.concat(self._toolAliases, ", ")} ]`) 193 | end 194 | return 195 | end 196 | end 197 | end 198 | 199 | if self._debugPrints then 200 | print(`[ANIM_OBJ_DEBUG] Attached animated object - equipped tool found [ {equippedTool:GetFullName()} ]`) 201 | end 202 | 203 | self:_createAnimatedObjectClone() 204 | 205 | setupMotor6d(self._animatedObjectClone, self._rig, equippedTool) 206 | 207 | if equippedTool.ClassName == "Tool" then 208 | self:_setRightGripWeldEnabled(false) 209 | end 210 | 211 | lifelineAnimatedObject = equippedTool 212 | elseif self._animatedObjectSrc.ClassName == "Tool" -- tool, model, basepart 213 | or self._animatedObjectSrc.ClassName == "Model" 214 | or self._animatedObjectSrc:IsA("BasePart") 215 | then 216 | if self._debugPrints then 217 | print("[ANIM_OBJ_DEBUG] Attaching animated object tool, model, basepart [", self._animatedObjectSrc:GetFullName(), "]") 218 | end 219 | 220 | local animatedObjectMotor6d = self._animatedObjectSrc:FindFirstChild(ANIMATED_OBJECT_MOTOR6D_NAME, true) 221 | 222 | if not animatedObjectMotor6d then 223 | if self._debugPrints then 224 | print(`[ANIM_OBJ_DEBUG] Unable to attach animated object - no animated object motor6d \"{ANIMATED_OBJECT_MOTOR6D_NAME}\" found in animated object [ {self._animatedObjectSrc:GetFullName()} ]`) 225 | end 226 | return 227 | end 228 | 229 | self:_createAnimatedObjectClone() 230 | 231 | setupMotor6d(self._animatedObjectClone:FindFirstChild(ANIMATED_OBJECT_MOTOR6D_NAME, true), self._rig, self._animatedObjectClone) 232 | 233 | if self._animatedObjectSrc.ClassName == "Tool" then 234 | self:_setRightGripWeldEnabled(false) 235 | end 236 | 237 | lifelineAnimatedObject = self._animatedObjectClone 238 | elseif self._animatedObjectSrc.ClassName == "Folder" then 239 | if self._debugPrints then 240 | print("[ANIM_OBJ_DEBUG] Attaching animated object folder [", self._animatedObjectSrc:GetFullName(), "]") 241 | end 242 | 243 | for _, animatedObject in ipairs(self._animatedObjectSrc:GetChildren()) do 244 | local animatedObjectMotor6d = animatedObject:FindFirstChild(ANIMATED_OBJECT_MOTOR6D_NAME, true) 245 | 246 | if not animatedObjectMotor6d then 247 | if self._debugPrints then 248 | print(`[ANIM_OBJ_DEBUG] Unable to attach animated object - no animated object motor6d \"{ANIMATED_OBJECT_MOTOR6D_NAME}\" found in animated object [ {animatedObject:GetFullName()} ]`) 249 | end 250 | return 251 | end 252 | end 253 | 254 | self:_createAnimatedObjectClone() 255 | 256 | for _, animatedObject in ipairs(self._animatedObjectClone:GetChildren()) do 257 | local animatedObjectMotor6d = animatedObject:FindFirstChild(ANIMATED_OBJECT_MOTOR6D_NAME, true) 258 | 259 | setupMotor6d(animatedObjectMotor6d, self._rig, animatedObject) 260 | 261 | if animatedObject.ClassName == "Tool" then 262 | self:_setRightGripWeldEnabled(false) 263 | end 264 | end 265 | 266 | lifelineAnimatedObject = self._animatedObjectClone 267 | else 268 | CustomAssert(false, `Animated object [ {self._animatedObjectSrc:GetFullName()} ] of class [ {self._animatedObjectSrc.ClassName} ] is not a supported animated object type`) 269 | end 270 | 271 | self:_setLifelineAnimatedObject(lifelineAnimatedObject) 272 | 273 | self._animatedObjectClone.Parent = self._rig 274 | 275 | return true 276 | end 277 | 278 | function AnimatedObject:Detach() 279 | if self._detached then 280 | return 281 | end 282 | 283 | self._detached = true 284 | 285 | if self._debugPrints then 286 | print("[ANIM_OBJ_DEBUG] Detaching animated object [", self._animatedObjectSrc:GetFullName(), "]") 287 | end 288 | 289 | -- Try to decrease the right grip weld disabled counter if we had increased 290 | -- it at the start 291 | self:_setRightGripWeldEnabled(true) 292 | 293 | -- Destroy the animated object clone 294 | if self._animatedObjectClone.Parent == self._rig then 295 | self._animatedObjectClone:Destroy() 296 | end 297 | 298 | -- Invoke ":ListenToDetach()" callbacks 299 | for _, fn in ipairs(self._listenToDetachHandlers) do 300 | task.spawn(fn) 301 | end 302 | 303 | table.clear(self._listenToDetachHandlers) 304 | end 305 | 306 | function AnimatedObject:ListenToDetach(fn) 307 | table.insert(self._listenToDetachHandlers, fn) 308 | end 309 | 310 | -- Client only 311 | function AnimatedObject:TransferToServerAnimatedObject(serverAnimatedObject) 312 | self:_setLifelineAnimatedObject(serverAnimatedObject) -- If the server animated object gets removed for some reason we detach now 313 | self._animatedObjectClone:Destroy() -- We destroy the client animated object immediately 314 | self._animatedObjectClone = serverAnimatedObject -- We destroy the server animated object when we detach now 315 | end 316 | 317 | return AnimatedObject -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.5.1 2 | > ###### 8/10/2025 3 | ---- 4 | 5 | - Fixes 6 | - Fixed animations freezing when [`Animations:ApplyAnimationProfile()`](/api/AnimationsServer#ApplyAnimationProfile) was called mid free fall. 7 | - Improved the animation stutter caused by [`Animations:ApplyAnimationProfile()`](/api/AnimationsServer#ApplyAnimationProfile) by removing an unecessary `RunService.Stepped:Wait()`. 8 | 9 | ## v2.5.0 10 | > ###### 7/9/2025 11 | ---- 12 | - Enhancements 13 | - Improved preload async time by preloading animations concurrently. 14 | 15 | ## v2.4.0 16 | > ###### 5/28/2025 17 | ---- 18 | - Enhancements 19 | - Added [basic usage monster/npc tutorial](/docs/basic-usage-monster-npc) 20 | - **[Beta]** Added [`AnimationsServer:EquipAnimatedTool()`](/api/AnimationsServer#EquipAnimatedTool). [Issue #73](https://github.com/wrello/Animations/issues/73) 21 | - **[Beta]** You can now set a custom tool name(s) for "motor6d only" animated objects. Learn about this in the [animated objects tutorial](/docs/animated-objects#motor6d-only-toolname-module). [Issue #73](https://github.com/wrello/Animations/issues/73) 22 | - Added an infinite yield warning if animator is not descendant of game for too long. [Issue #74](https://github.com/wrello/Animations/issues/74) 23 | - Changes (breaking) 24 | - **[Beta]** You can no longer use [`Animations:AttachAnimatedObject()`](/api/AnimationsServer#AttachAnimatedObject) with a single motor6d. This is because after equipping a tool with `humanoid:EquipTool()`, the tool will most likely not immediately be parented to the character ([Roblox quirk that is discussed here](https://devforum.roblox.com/t/tools-new-parent-is-delayed-when-using-humanoidequiptool-potential-studio-bug/3667271)), so [`Animations:AttachAnimatedObject()`](/api/AnimationsServer#AttachAnimatedObject) will fail to attach the motor6d. 25 | - Fixes 26 | - Fixed an issue where auto attaching of animated objects would fail due to mismatched animation ids. 27 | 28 | ## v2.3.0 29 | > ###### 4/3/2025 30 | 31 | ---- 32 | 33 | - Enhancements 34 | - Added `StartSpeed` to [`HasProperties`](/api/AnimationIds#HasProperties) and corresponding [`Animations:GetTrackStartSpeed()`](/api/AnimationsServer#GetTrackStartSpeed) method. [Issue #70](https://github.com/wrello/Animations/issues/70) 35 | - Added better documentation for [`HasProperties`](/api/AnimationIds#propertiesSettings) and [`HasAnimatedObject`](/api/AnimationIds#animatedObjectSettings). 36 | - Added code example for [`AnimationsClient:PlayRigTrack()`](/api/AnimationsClient#PlayRigTrack). 37 | 38 | ## v2.2.0 39 | > ###### 3/21/2025 40 | 41 | ---- 42 | 43 | - Enhancements 44 | - Added pesde support. 45 | - Added better Wally support. You can now require `Animations` like so (if installed as a Wally dependency): 46 | ```lua 47 | local Animations = require(game.ReplicatedStorage.Packages.Animations) 48 | ``` 49 | [Issue #67](https://github.com/wrello/Animations/issues/67) 50 | - Switched to using [`LemonSignal`](https://github.com/Data-Oriented-House/LemonSignal) for better stability and performance. [Issue #69](https://github.com/wrello/Animations/issues/69) 51 | 52 | 53 | ## v2.1.0 54 | > ###### 12/27/2024 55 | 56 | ---- 57 | 58 | - Enhancements 59 | - Set animation track names to be the same as their source animation instance names for convenience. 60 | - Added [`Animations:WaitForRigPlayingTrack()`](/api/AnimationsServer/#WaitForRigPlayingTrack)/[`Animations:FindFirstRigPlayingTrack()`](/api/AnimationsServer/#FindFirstRigPlayingTrack). [Issue #62](https://github.com/wrello/Animations/issues/62) 61 | - **[Beta]** Added [`Animations:GetTimeOfMarker()`](/api/AnimationsServer/#GetTimeOfMarker). [Issue #64](https://github.com/wrello/Animations/issues/64) 62 | - Added [`Animations:GetAnimationIdString()`](/api/AnimationsServer/#GetAnimationIdString). 63 | 64 | 65 | - Changes (non-breaking) 66 | - Removed `Animations.AutoRegisterPlayers` on client and server. Automatic registration will always happen through `player.CharacterAdded` events. This decision was made for convenience and to eliminate the redudancy with `Animations.AutoLoadAllPlayerTracks` which *also* automatically registers the players when their character gets added. 67 | 68 | 69 | - Fixes 70 | - Utility module `ChildFromPath` bug. [Issue #59](https://github.com/wrello/Animations/issues/59) 71 | - Documentation fixes. [Issue #58](https://github.com/wrello/Animations/issues/58) 72 | 73 | 74 | ## v2.0.0 75 | > ###### 8/24/2024 76 | 77 | ---- 78 | 79 | - Changes (breaking) 80 | - Changed method of requiring [`HasAnimatedObject()`](/api/AnimationIds/#HasAnimatedObject) function in [`AnimationIds`](/api/AnimationIds) module. After replacing the empty deps with your current ones, the directory structure should look like this: 81 | ``` 82 | Animations/ (new) 83 | Deps/ (mix) 84 | AnimationIds (new with copied table from old) 85 | AnimationProfiles (old) 86 | AnimatedObjects (old) 87 | AutoCustomRBXAnimationIds (old) 88 | Package/ (new) 89 | ... 90 | ``` 91 | 92 | 93 | - Ehancements 94 | - Added [`HasProperties()`](/api/AnimationIds/#HasProperties) function in [`AnimationIds`](/api/AnimationIds) module. [Issue #54](https://github.com/wrello/Animations/issues/54) 95 | - Added [`Animations:GetAppliedProfileName()`](/api/AnimationsServer/#GetAppliedProfileName). [Issue #53](https://github.com/wrello/Animations/issues/53) 96 | 97 | 98 | - Fixes 99 | - Fixed [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds) not working. [Issue #55](https://github.com/wrello/Animations/issues/55) 100 | - Fixed waiting for `player.CharacterAdded` event that might never happen if `Players.CharacterAutoLoads` is disabled. [Issue #52](https://github.com/wrello/Animations/issues/52) 101 | - Fixed "Cannot load the AnimationClipProvider Service" error/bug. [Issue #51](https://github.com/wrello/Animations/issues/51) 102 | - Fixed documentation issues. [Issue #49](https://github.com/wrello/Animations/issues/49), [Issue #50](https://github.com/wrello/Animations/issues/50) 103 | 104 | 105 | ## v2.0.0-rc1 106 | > ###### 7/25/2024 107 | 108 | ---- 109 | 110 | [Migrate to v2.0.0 guide](/docs/migrate-to-2.0.0) 111 | 112 | - Changes (breaking) 113 | - Changed init option ~~`Animations.AutoLoadPlayerTracks`~~ -> to -> [`Animations.AutoLoadAllPlayerTracks`](/api/AnimationsServer/#AutoLoadAllPlayerTracks) in order to match the new [`Animations:LoadAllTracks()`](/api/AnimationsServer/#LoadAllTracks). [Issue #43](https://github.com/wrello/Animations/issues/43) 114 | - Changed ~~`Animations:LoadTracks`~~ -> to -> [`Animations:LoadAllTracks()`](/api/AnimationsServer/#LoadAllTracks). [Issue #43](https://github.com/wrello/Animations/issues/43) 115 | - Changed ~~`Animations:AwaitLoaded()`~~ -> to -> [`Animations:AwaitAllTracksLoaded()`](/api/AnimationsServer/#AwaitAllTracksLoaded). [Issue #43](https://github.com/wrello/Animations/issues/43) 116 | - Changed ~~`Animations:AreTracksLoaded()`~~ -> to -> [`Animations:AreAllTracksLoaded()`](/api/AnimationsServer/#AreAllTracksLoaded). [Issue #43](https://github.com/wrello/Animations/issues/43) 117 | - Changed ~~`Animations:StopAllTracks()`~~ -> to -> [`Animations:StopPlayingTracks()`](/api/AnimationsServer/#StopPlayingTracks) in order to match the new [`Animations:GetPlayingTracks()`](/api/AnimationsServer/#GetPlayingTracks). [Issue #42](https://github.com/wrello/Animations/issues/42) 118 | 119 | 120 | - Enhancements 121 | - Wally support. 122 | - Added method [`Animations:AwaitPreloadAsyncFinished()`](/api/AnimationsServer/#AwaitPreloadAsyncFinished). 123 | - Added methods [`Animations:Register()`](/api/AnimationsServer/#Register)/[`Animations:AwaitRegistered()`](/api/AnimationsServer/#AwaitRegistered)/[`Animations:IsRegistered()`](/api/AnimationsServer/#IsRegistered). [Issue #43](https://github.com/wrello/Animations/issues/43) 124 | - Added methods [`Animations:LoadTracksAt()`](/api/AnimationsServer/#LoadTracksAt)/[`Animations:AwaitTracksLoadedAt()`](/api/AnimationsServer/#AwaitTracksLoadedAt)/[`Animations:AreTracksLoadedAt()`](/api/AnimationsServer/#AreTracksLoadedAt). [Issue #43](https://github.com/wrello/Animations/issues/43) 125 | - Added event [`Animations.PreloadAsyncProgressed`](/api/AnimationsServer/#PreloadAsyncProgressed). 126 | - Added init option [`Animations.DepsFolderPath`](/api/AnimationsServer/#DepsFolderPath). [Issue #46](https://github.com/wrello/Animations/issues/46) 127 | - Added [`Animations:GetPlayingTracks()`](/api/AnimationsServer/#GetPlayingTracks). [Issue #42](https://github.com/wrello/Animations/issues/42) 128 | - Added init option [`AnimationsClient.EnableAutoCustomRBXAnimationIds`](/api/AnimationsClient/#EnableAutoCustomRBXAnimationIds). 129 | - Added init option [`AnimationsClient.AutoRegisterPlayer`](/api/AnimationsClient/#AutoRegisterPlayer)/[`AnimationsServer.AutoRegisterPlayers`](/api/AnimationsServer/#AutoRegisterPlayers). [Issue #43](https://github.com/wrello/Animations/issues/43) 130 | 131 | 132 | - Changes (non-breaking) 133 | - Changed init option [`AnimationsServer.TimeToLoadPrints`](/api/AnimationsServer/#TimeToLoadPrints) to default to `true` because it's important to realize that initialization can yield for quite some time during `ContentProvider:PreloadAsync()` on all animations in the [`AnimationIds`](/api/AnimationIds) module. [Issue #44](https://github.com/wrello/Animations/issues/44) 134 | 135 | 136 | - Fixes 137 | - Fixed `ContentProvider:PreloadAsync()` being called every time tracks got loaded for a rig. [Issue #44](https://github.com/wrello/Animations/issues/44) 138 | - Fixed animations server editing players' animate scripts when it wasn't necessary. [Issue #45](https://github.com/wrello/Animations/issues/45) 139 | 140 | 141 | - Notes 142 | - It was impossible to load tracks seperately with the `Animations:LoadTracks()` method. This is now possible and means that loading tracks could happen many times during one rig's lifetime forcing us to seperate registration into [`Animations:Register()`](/api/AnimationsServer/#Register) so that it only happens once before any track loading does. 143 | 144 | 145 | ## v2.0.0-alpha 146 | > ###### 6/23/2024 147 | 148 | ---- 149 | 150 | - Changes (breaking) 151 | - **[Beta]** Changed the animated object specifier parameter of [`Animations:AttachAnimatedObject()`](/api/AnimationsServer/#AttachAnimatedObject) and [`Animations:DetachAnimatedObject()`](/api/AnimationsServer/#DetachAnimatedObject) to just a [`path`](/api/AnimationsServer/#path) type. 152 | - **[Beta]** Changed [`HasAnimatedObject()`](/api/AnimationIds/#HasAnimatedObject) *optional* parameter `autoAttachDetachSettings?` -> to -> *required* parameter `animatedObjectSettings`. 153 | 154 | 155 | - Enhancements 156 | - Added [`Animations:StopAllTracks()`](/api/AnimationsServer/#StopAllTracks). [Issue #39](https://github.com/wrello/Animations/issues/39) 157 | - Added [`Animations:GetAnimationProfile()`](/api/AnimationsServer/#GetAnimationProfile). [Issue #29](https://github.com/wrello/Animations/issues/29) 158 | - Added information on the all too common [`"Animator.EvaluationThrottled"`](/docs/animator-error) error. [Issue #38](https://github.com/wrello/Animations/issues/38) 159 | - Added R6/R15 NPC support for [`Animations:ApplyCustomRBXAnimationIds()`](/api/AnimationsServer/#ApplyCustomRBXAnimationIds) and [`Animations:ApplyAnimationProfile()`](/api/AnimationsServer/#ApplyAnimationProfile) on client & server. There are caveats when using these, be sure to read documentation. [Issue #41](https://github.com/wrello/Animations/issues/41) 160 | - Added more clear usage description of [`animation profiles`](/docs/animation-profiles). [Issue #34](https://github.com/wrello/Animations/issues/34) 161 | - **[Beta]** Added optional parameter `DoUnpack?` for [`HasAnimatedObject()`](/api/AnimationIds/#HasAnimatedObject). 162 | - Added warning and yield until the server initializes in [`AnimationsClient:Init()`](/api/AnimationsClient/#Init). [Issue #37](https://github.com/wrello/Animations/issues/37) 163 | - [`AnimationsServer:LoadTracks()`](/api/AnimationsServer/#LoadTracks) now automatically applies `rigType` of `"Player"` if no `rigType` is specified and the `player_or_rig` is a player or player's character. 164 | - Rewrite of animated objects system (still in beta). 165 | 166 | 167 | - Changes (non-breaking) 168 | - **[Beta]** Changed [`HasAnimatedObject()`](/api/AnimationIds/#HasAnimatedObject) to no longer require a `RunContext` in `autoAttachDetachSettings?` (which is now `animatedObjectSettings`). It will now automatically run on client & server. [Issue #31](https://github.com/wrello/Animations/issues/31) 169 | 170 | 171 | - Fixes 172 | - Fixed memory leak associated with trusting that the `Player.Character.Destroying` event would fire when a character model got removed from the game. 173 | - Fixed [`"Animator.EvaluationThrottled"`](/docs/animator-error) error caused by [`Animations.TimeToLoadPrints`](/api/AnimationsServer/#initOptions). 174 | - Fixed problem with string paths involving utility function `GetChildFromPath()`. [Issue #40](https://github.com/wrello/Animations/issues/40) 175 | - Fixed auto detach of animated objects being bugged. [Issue #36](https://github.com/wrello/Animations/issues/36) 176 | - Fixed right grip weld not getting disabled for multiple animated object equips. [Issue #30](https://github.com/wrello/Animations/issues/30) 177 | - Fixed animation ids not being able to attach to multiple animated objects. [Issue #32](https://github.com/wrello/Animations/issues/32) 178 | - Fixed incorrect type for [`AnimationsServer:StopTracksOfPriority()`](/api/AnimationsServer/#StopTracksOfPriority). [Issue #33](https://github.com/wrello/Animations/issues/33) 179 | - Fixed documentation mistakes. [Issue #35](https://github.com/wrello/Animations/issues/35) 180 | 181 | ## v1.3.0 182 | > ###### 5/6/2024 183 | 184 | ---- 185 | 186 | - Enhancements 187 | - Added [`animation profiles`](docs/animation-profiles). [Issue #22](https://github.com/wrello/Animations/issues/22) 188 | - Added [`Animations:StopTracksOfPriority()`](/api/AnimationsServer/#StopTracksOfPriority). [Issue #26](https://github.com/wrello/Animations/issues/26) 189 | - Errors if no "animator parent" (`Humanoid` or `AnimationController`) exists in the rig. [Issue #27](https://github.com/wrello/Animations/issues/27) 190 | 191 | 192 | - Fixes 193 | - Fixed calling `Humanoid:ChangeState()` when it shouldn't be called. [Issue #28](https://github.com/wrello/Animations/issues/28) 194 | - Fixed no documentation on [`AnimationsClient:DetachAnimatedObject()`](/api/AnimationsClient/#DetachAnimatedObject). [Issue #25](https://github.com/wrello/Animations/issues/25) 195 | - Fixed basic usage document mistakes. [Issue #24](https://github.com/wrello/Animations/issues/24), [Issue #23](https://github.com/wrello/Animations/issues/23) 196 | 197 | ## v1.2.0 198 | > ###### 2/20/2024 199 | 200 | ---- 201 | 202 | - Enhancements 203 | - Added [`AnimationsClient:ApplyCustomRBXAnimationIds()`](/api/AnimationsClient/#ApplyCustomRBXAnimationIds). [Issue #21](https://github.com/wrello/Animations/issues/21) 204 | 205 | 206 | - Fixes 207 | - Fixed [`AnimationsServer:ApplyCustomRBXAnimationIds()`](/api/AnimationsServer/#ApplyCustomRBXAnimationIds) not updating the animations until after the player moved. 208 | 209 | ## v1.1.0 210 | > ###### 2/18/2024 211 | 212 | ---- 213 | 214 | - Enhancements 215 | - **[Beta]** Added [`Animations:AttachAnimatedObject()`](/api/AnimationsServer/#AttachAnimatedObject), [`Animations:DetachAnimatedObject()`](/api/AnimationsServer/#DetachAnimatedObject) methods, [`HasAnimatedObject`](/api/AnimationIds/#HasAnimatedObject) function in the [`AnimationIds`](/api/AnimationIds) module, and an [animated objects tutorial](/docs/animated-objects). [Issue #15](https://github.com/wrello/Animations/issues/15) 216 | - Added a tip that `"walk"` is the animation id that is applied for `R6` characters and `"run"` is the animation id that is applied for `R15` characters when using [`AnimationsServer:ApplyCustomRBXAnimationIds()`](/api/AnimationsServer#ApplyCustomRBXAnimationIds) and [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds). [Issue #20](https://github.com/wrello/Animations/issues/20) 217 | 218 | 219 | - Changes (non-breaking) 220 | - Changed `ASSET_ID_STR` to format an integer instead of a float. [Issue #19](https://github.com/wrello/Animations/issues/19) 221 | 222 | 223 | - Fixes 224 | - Fixed lots of documentation and typing errors, especially related to `CustomRBXAnimationIds` which is now [`humanoidRigTypeToCustomRBXAnimationIds`](/api/AnimationsServer/#humanoidRigTypeToCustomRBXAnimationIds). 225 | - Fixed [`AnimationsServer:ApplyCustomRBXAnimationIds()`](/api/AnimationsServer#ApplyCustomRBXAnimationIds) and [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds) not working. [Issue #18](https://github.com/wrello/Animations/issues/18) 226 | - Fixed bad type annotation for [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds). [Issue #16](https://github.com/wrello/Animations/issues/16) 227 | 228 | ## v1.0.5 229 | > ###### 1/31/2024 230 | 231 | ---- 232 | 233 | - Fixes 234 | - Fixed all methods having no auto-complete (bad types). [Issue #12](https://github.com/wrello/Animations/issues/12) 235 | - Fixed missing weight and speed parameters for auto-complete in some methods (bad types). [Issue #13](https://github.com/wrello/Animations/issues/13) 236 | - Fixed an overcomplicated private method `:_getAnimatorOrAnimationController()` in the `AnimationsClass`. Its new name is `:_getAnimator()`. [Issue #14](https://github.com/wrello/Animations/issues/14) 237 | 238 | ## v1.0.4 239 | > ###### 11/22/2023 240 | 241 | ---- 242 | 243 | - Enhancements 244 | - Put both client & server animation modules in `--!strict` mode. This allowed for a lot of typing fixes. 245 | - Added a warning `Infinite yield possible on 'player_or_rig.CharacterAdded():Wait()'` that occurs whenever the `getRig()` helper function is called if the player's character doesn't exist after 5 seconds. This is helpful if `Players.CharacterAutoLoads` is not enabled and `getRig()` gets called. 246 | 247 | 248 | - Fixes 249 | - Fixed `initOptions.AutoCustomRBXAnimationIds` not working because it was named incorrectly. New name is `initOptions.EnableAutoCustomRBXAnimationIds`. [AnimationsServer initOptions](/api/AnimationsServer/#initOptions) 250 | - Fixed multiple references to the same animation id's table causing an error. [Issue #11](https://github.com/wrello/Animations/issues/11) 251 | - Fixed incorrect types. 252 | 253 | ## v1.0.3 254 | > ###### 11/22/2023 255 | 256 | ---- 257 | 258 | - Enhancements 259 | - [`Animations:LoadTracks()`](/api/AnimationsClient#LoadTracks) now automatically gives the rig an attribute `AnimationsRigType` set to the given [`rigType`](/api/AnimationIds#rigType) (which is "Player" when the client calls it). [Issue #9](https://github.com/wrello/Animations/issues/9) 260 | - Explained a convenient feature when using [`Animations:SetTrackAlias()`](/api/AnimationsClient#SetTrackAlias) in the documentation of the function. 261 | 262 | 263 | - Fixes 264 | - Fixed [`Animations:GetTrackFromAlias()`](/api/AnimationsClient#GetTrackFromAlias) not working. [Issue #10](https://github.com/wrello/Animations/issues/10) 265 | - Fixed `Util.ChildFromPath` bug that caused errors when the `parent` became nil during the recursion. [Issue #7](https://github.com/wrello/Animations/issues/7) 266 | 267 | ## v1.0.2 268 | > ###### 8/31/2023 269 | 270 | ---- 271 | 272 | - Enhancements 273 | - Added assertions to check `AnimationsClient` and `AnimationsServer` are being required on the client & server respectively. [Issue #4](https://github.com/wrello/Animations/issues/4) 274 | 275 | 276 | - Fixes 277 | - Fixed subsequent `Animations:AwaitLoaded()` calls getting discarded. [Issue #5](https://github.com/wrello/Animations/issues/5) 278 | - Fixed documentation. [Issue #3](https://github.com/wrello/Animations/issues/3) 279 | - Fixed documentation. [Issue #2](https://github.com/wrello/Animations/issues/2) 280 | - Removed an unecessary `:`. [Issue #1](https://github.com/wrello/Animations/issues/1) 281 | 282 | ## v1.0.1 283 | > ###### 8/3/2023 284 | 285 | ---- 286 | 287 | - Fixes 288 | - Fixed Animation:Init() error when called without optional `initOptions`. 289 | 290 | ## v1.0.0 291 | > ###### 8/3/2023 292 | 293 | ---- 294 | 295 | - Initial release -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/AnimationsServer.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- made by wrello 3 | -- GitHub: https://github.com/wrello/Animations 4 | 5 | assert(game:GetService("RunService"):IsServer(), "Attempt to require AnimationsServer on the client") 6 | 7 | local Players = game:GetService("Players") 8 | 9 | local Types = require(script.Parent.Util.Types) 10 | local AnimationsClass = require(script.Parent.Util.AnimationsClass) 11 | local ChildFromPath = require(script.Parent.Util.ChildFromPath) 12 | 13 | local AutoCustomRBXAnimationIds = nil 14 | 15 | --[=[ 16 | @interface initOptions 17 | @within AnimationsServer 18 | .AutoLoadAllPlayerTracks false 19 | .TimeToLoadPrints false 20 | .EnableAutoCustomRBXAnimationIds false 21 | .AnimatedObjectsDebugMode false 22 | .DepsFolderPath string? -- Only use if you've moved the 'Deps' folder from its original location. 23 | 24 | Gets applied to [`Properties`](#properties). 25 | ]=] 26 | type AnimationsServerInitOptionsType = Types.AnimationsServerInitOptionsType 27 | 28 | --[=[ 29 | @type path {any} | string 30 | @within AnimationsServer 31 | 32 | ```lua 33 | -- In a ServerScript 34 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsServer) 35 | 36 | 37 | -- These are all valid options for retrieving an animation track 38 | local animationPath = "Jump" -- A single key (any type) 39 | 40 | local animationPath = {"Dodge", Vector3.xAxis} -- An array path (values of any type) 41 | 42 | local animationPath = "Climb.Right" -- A path seperated by "." (string) 43 | 44 | 45 | local animationTrack = Animations:GetTrack(player, animationPath) 46 | ``` 47 | ]=] 48 | 49 | local Animations = AnimationsClass.new(script.Name) 50 | 51 | --[=[ 52 | @class AnimationsServer 53 | @server 54 | 55 | :::note 56 | Roblox model path: `Animations.Package.AnimationsServer` 57 | ::: 58 | ]=] 59 | local AnimationsServer = Animations 60 | 61 | --[=[ 62 | @prop DepsFolderPath nil 63 | @within AnimationsServer 64 | 65 | Set the path to the dependencies folder if you have moved it from its original location inside of the root `Animations` folder. 66 | 67 | :::tip *added in version 2.0.0-rc1* 68 | ::: 69 | ]=] 70 | AnimationsServer.DepsFolderPath = nil 71 | 72 | --[=[ 73 | @prop AutoLoadAllPlayerTracks false 74 | @within AnimationsServer 75 | 76 | If set to true, all player animation tracks will be loaded for each player character on spawn. 77 | 78 | :::warning 79 | Must have animation ids under [`rigType`](/api/AnimationIds#rigType) of **"Player"** in the [`AnimationIds`](/api/AnimationIds) module. 80 | ::: 81 | 82 | :::caution *changed in version 2.0.0-rc1* 83 | Renamed: ~~`AutoLoadPlayerTracks`~~ -> `AutoLoadAllPlayerTracks` 84 | ::: 85 | ]=] 86 | AnimationsServer.AutoLoadAllPlayerTracks = false 87 | 88 | --[=[ 89 | @prop TimeToLoadPrints true 90 | @within AnimationsServer 91 | 92 | If set to true, makes helpful prints about the time it takes to pre-load and load animations. 93 | 94 | :::caution *changed in version 2.0.0-rc1* 95 | Defaults to `true`. 96 | ::: 97 | ]=] 98 | AnimationsServer.TimeToLoadPrints = true 99 | 100 | --[=[ 101 | @prop EnableAutoCustomRBXAnimationIds false 102 | @within AnimationsServer 103 | 104 | If set to true, applies the [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds) module table to each player character on spawn. 105 | ]=] 106 | AnimationsServer.EnableAutoCustomRBXAnimationIds = false 107 | 108 | --[=[ 109 | @prop AnimatedObjectsDebugMode false 110 | @within AnimationsServer 111 | 112 | If set to true, prints will be made to help debug attaching and detaching animated objects. 113 | ]=] 114 | AnimationsServer.AnimatedObjectsDebugMode = false 115 | 116 | --[=[ 117 | @yields 118 | @param initOptions initOptions? 119 | 120 | Initializes `AnimationsServer`. Clients are unable to initialize until this gets called. 121 | 122 | Yields when... 123 | - ...animations are being pre-loaded with `ContentProvider:PreloadAsync()` (could take a while). 124 | 125 | :::info 126 | Should be called once before any other method. 127 | ::: 128 | ]=] 129 | function AnimationsServer:Init(initOptions: AnimationsServerInitOptionsType?) 130 | if self._initialized then 131 | warn("AnimationsServer:Init() only needs to be called once") 132 | return 133 | end 134 | 135 | local function bootstrapDepsFolder() 136 | local depsFolder 137 | 138 | if self.DepsFolderPath then 139 | local ok 140 | ok, depsFolder = pcall(ChildFromPath, game, self.DepsFolderPath) 141 | assert(ok and depsFolder, "No animation deps folder found at path '" .. self.DepsFolderPath .. "'") 142 | else 143 | depsFolder = script.Parent.Parent.Deps 144 | end 145 | 146 | AutoCustomRBXAnimationIds = require(depsFolder.AutoCustomRBXAnimationIds) 147 | 148 | self:_bootstrapDepsFolder(depsFolder) 149 | end 150 | 151 | local function initInitOptions() 152 | if initOptions then 153 | for k, v in pairs(initOptions) do 154 | self[k] = v 155 | end 156 | end 157 | end 158 | 159 | local function initCustomRBXAnimationIdsSignal() 160 | local applyCustomRBXAnimationIdsRE = Instance.new("RemoteEvent") 161 | applyCustomRBXAnimationIdsRE.Name = "ApplyCustomRBXAnimationIds" 162 | applyCustomRBXAnimationIdsRE.Parent = script.Parent 163 | 164 | self.ApplyCustomRBXAnimationIdsSignal = applyCustomRBXAnimationIdsRE 165 | end 166 | 167 | local function initOnPlayerSpawn() 168 | local function onPlayerAdded(player) 169 | local function onCharacterAdded(char) 170 | self:Register(player, "Player") 171 | 172 | if self.AutoLoadAllPlayerTracks then 173 | self:LoadAllTracks(player) 174 | end 175 | 176 | if self.EnableAutoCustomRBXAnimationIds then 177 | self:ApplyCustomRBXAnimationIds(player, AutoCustomRBXAnimationIds) 178 | end 179 | end 180 | 181 | if player.Character then 182 | onCharacterAdded(player.Character) 183 | end 184 | 185 | player.CharacterAdded:Connect(onCharacterAdded) 186 | end 187 | 188 | for _, player in pairs(Players:GetPlayers()) do 189 | task.spawn(onPlayerAdded, player) 190 | end 191 | 192 | Players.PlayerAdded:Connect(onPlayerAdded) 193 | end 194 | 195 | initInitOptions() 196 | bootstrapDepsFolder() 197 | initCustomRBXAnimationIdsSignal() 198 | script:SetAttribute("InitializedForClients", true) -- Set initialized for clients to be able to initialize 199 | self:_animationIdsToInstances() 200 | self._initialized = true -- Need to initialize before using methods in the function below 201 | initOnPlayerSpawn() 202 | end 203 | 204 | --[=[ 205 | @tag Beta 206 | 207 | Equips the `tool` for the `player_or_rig` and then attaches the `motor6d` found in the `AnimatedObjects` folder to it. 208 | 209 | :::caution 210 | This method is in beta testing. Use with caution. 211 | ::: 212 | :::tip *added in version 2.4.0* 213 | ::: 214 | ]=] 215 | function AnimationsServer:EquipAnimatedTool(player_or_rig: Player | Model, toolToEquip: Tool, motor6dName: Motor6D) 216 | self:_initializedAssertion() 217 | 218 | self:_attachDetachAnimatedObject("attach", player_or_rig, motor6dName, nil, toolToEquip) 219 | end 220 | 221 | --[=[ 222 | @method GetTrackStartSpeed 223 | @within AnimationsServer 224 | @param player_or_rig Player | Model 225 | @param path path 226 | @return number? 227 | 228 | Returns the animation track's `StartSpeed` (if set in [`HasProperties`](/api/AnimationIds#HasProperties)) or `nil`. 229 | 230 | :::tip *added in version 2.3.0* 231 | ::: 232 | ]=] 233 | 234 | --[=[ 235 | @yields 236 | @tag Beta 237 | @method GetTimeOfMarker 238 | @within AnimationsServer 239 | @param animTrack_or_IdString AnimationTrack | string 240 | @param markerName string 241 | @return number? 242 | 243 | The only reason this would yield is if the 244 | initialization process that caches all of the marker 245 | times is still going on when this method gets called. If 246 | after 3 seconds the initialization process still has not 247 | finished, this method will return `nil`. 248 | 249 | ```lua 250 | local attackAnim = Animations:PlayTrack("Attack") 251 | local timeOfHitStart = Animations:GetTimeOfMarker(attackAnim, "HitStart") 252 | 253 | print("Time of hit start:", timeOfHitStart) 254 | 255 | -- or 256 | 257 | local animIdStr = Animations:GetAnimationIdString("Player", "Attack") 258 | local timeOfHitStart = Animations:GetTimeOfMarker(animIdStr, "HitStart") 259 | 260 | print("Time of hit start:", timeOfHitStart) 261 | ``` 262 | 263 | :::info 264 | You must first modify your 265 | [`AnimationIds`](/api/AnimationIds) module to specify 266 | which animations this method will work on. 267 | ::: 268 | :::caution 269 | This method is in beta testing. Use with caution. 270 | ::: 271 | :::tip *added in version 2.1.0* 272 | ::: 273 | ]=] 274 | --[=[ 275 | @method GetAnimationIdString 276 | @within AnimationsServer 277 | @param rigType rigType 278 | @param path path 279 | @return string 280 | 281 | Returns the animation id string under `rigType` at `path` in the [`AnimationIds`](/api/AnimationIds) module. 282 | 283 | ```lua 284 | local animIdStr = Animations:GetAnimationIdString("Player", "Run") 285 | print(animIdStr) --> "rbxassetid://89327320" 286 | ``` 287 | 288 | :::tip *added in version 2.1.0* 289 | ::: 290 | ]=] 291 | 292 | --[=[ 293 | @method FindFirstRigPlayingTrack 294 | @within AnimationsServer 295 | @param rig Model 296 | @param path path 297 | @return AnimationTrack? 298 | 299 | Returns a playing animation track found in 300 | `rig.Humanoid.Animator:GetPlayingAnimationTracks()` 301 | matching the animation id found at `path` in the 302 | [`AnimationIds`](/api/AnimationIds) module or `nil`. 303 | 304 | ```lua 305 | -- [WARNING] For this to work, `enemyCharacter` would have to be registered (on the server) and "Blocking" would need to be a valid animation name defined in the `AnimationIds` module. 306 | local isBlocking = Animations:FindFirstRigPlayingTrack(enemyCharacter, "Blocking") 307 | 308 | if isBlocking then 309 | warn("We can't hit the enemy, they're blocking!") 310 | end 311 | ``` 312 | 313 | :::tip *added in version 2.1.0* 314 | ::: 315 | ]=] 316 | --[=[ 317 | @yields 318 | @method WaitForRigPlayingTrack 319 | @within AnimationsServer 320 | @param rig Model 321 | @param path path 322 | @param timeout number? 323 | @return AnimationTrack? 324 | 325 | Yields until a playing animation track is found in 326 | `rig.Humanoid.Animator:GetPlayingAnimationTracks()` 327 | matching the animation id found at `path` in the 328 | [`AnimationIds`](/api/AnimationIds) module then returns it or returns `nil` after 329 | `timeout` seconds if provided. 330 | 331 | :::tip 332 | Especially useful on the client if the animation needs time to replicate from server to client and you want to specify a maximum time to wait until it replicates. 333 | ::: 334 | 335 | :::tip *added in version 2.1.0* 336 | ::: 337 | ]=] 338 | 339 | --[=[ 340 | @method GetAppliedProfileName 341 | @within AnimationsServer 342 | @param player_or_rig Player | Model 343 | @return string? 344 | 345 | Returns the `player_or_rig`'s currently applied animation profile name or `nil`. 346 | 347 | :::tip *added in version 2.0.0* 348 | ::: 349 | ]=] 350 | 351 | --[=[ 352 | @method AwaitPreloadAsyncFinished 353 | @yields 354 | @within AnimationsServer 355 | @return {Animation?} 356 | 357 | Yields until `ContentProvider:PreloadAsync()` finishes pre-loading all animation instances. 358 | 359 | ```lua 360 | -- In a ServerScript 361 | local loadedAnimInstances = Animations:AwaitPreloadAsyncFinished() 362 | 363 | print("ContentProvider:PreloadAsync() finished pre-loading all:", loadedAnimInstances) 364 | ``` 365 | 366 | :::tip *added in version 2.0.0-rc1* 367 | ::: 368 | ]=] 369 | --[=[ 370 | @prop PreloadAsyncProgressed RBXScriptSignal 371 | @within AnimationsServer 372 | 373 | Fires when `ContentProvider:PreloadAsync()` finishes pre-loading one animation instance. 374 | 375 | ```lua 376 | -- In a ServerScript 377 | Animations.PreloadAsyncProgressed:Connect(function(n, total, loadedAnimInstance) 378 | print("ContentProvider:PreloadAsync() finished pre-loading one:", n, total, loadedAnimInstance) 379 | end) 380 | ``` 381 | 382 | :::tip *added in version 2.0.0-rc1* 383 | ::: 384 | ]=] 385 | 386 | --[=[ 387 | @interface customRBXAnimationIds 388 | @within AnimationsServer 389 | .run number? 390 | .walk number? 391 | .jump number? 392 | .idle {Animation1: number?, Animation2: number?}? 393 | .fall number? 394 | .swim number? 395 | .swimIdle number? 396 | .climb number? 397 | 398 | A table of animation ids to replace the default roblox animation ids. 399 | 400 | :::info 401 | Roblox applies the `"walk"` animation id for `R6` characters and the `"run"` animation id for `R15` characters (instead of both). 402 | ::: 403 | ]=] 404 | 405 | --[=[ 406 | @interface humanoidRigTypeToCustomRBXAnimationIds 407 | @within AnimationsServer 408 | .[Enum.HumanoidRigType.R6] customRBXAnimationIds? 409 | .[Enum.HumanoidRigType.R15] customRBXAnimationIds? 410 | 411 | A table mapping a humanoid rig type to its supported animation ids that will replace the default roblox animation ids. 412 | ]=] 413 | 414 | --[=[ 415 | @method Register 416 | @within AnimationsServer 417 | @param player_or_rig Player | Model 418 | @param rigType string 419 | 420 | Registers the player's character/rig so that methods using animation tracks can be called. 421 | 422 | :::note 423 | All player characters get automatically registered through `player.CharacterAdded` events. 424 | ::: 425 | 426 | :::tip 427 | Automatically gives the character/rig an attribute `"AnimationsRigType"` set to the [`rigType`](/api/AnimationIds#rigType). 428 | ::: 429 | 430 | :::tip *added in version 2.0.0-rc1* 431 | ::: 432 | ]=] 433 | 434 | --[=[ 435 | @method AwaitRegistered 436 | @yields 437 | @within AnimationsServer 438 | @param player_or_rig Player | Model 439 | 440 | Yields until the `player_or_rig` gets registered. 441 | 442 | :::tip *added in version 2.0.0-rc1* 443 | ::: 444 | ]=] 445 | 446 | --[=[ 447 | @method IsRegistered 448 | @within AnimationsServer 449 | @param player_or_rig Player | Model 450 | @return boolean 451 | 452 | Returns if the `player_or_rig` is registered. 453 | 454 | :::tip *added in version 2.0.0-rc1* 455 | ::: 456 | ]=] 457 | 458 | --[=[ 459 | @method ApplyCustomRBXAnimationIds 460 | @within AnimationsServer 461 | @yields 462 | @param player_or_rig Player | Model 463 | @param humanoidRigTypeToCustomRBXAnimationIds humanoidRigTypeToCustomRBXAnimationIds 464 | 465 | Applies the animation ids specified in the [`humanoidRigTypeToCustomRBXAnimationIds`](#humanoidRigTypeToCustomRBXAnimationIds) table on the `player_or_rig`. 466 | 467 | Yields when... 468 | - ...the player's character, player or rig's humanoid, player's animator, or player or rig's animate script aren't immediately available. 469 | 470 | :::warning 471 | This function only works for players and R6/R15 NPCs that have an `"Animate"` script in their model. 472 | ::: 473 | :::tip 474 | See [`ApplyAnimationProfile()`](#ApplyAnimationProfile) for a more convenient way of overriding default roblox character animations. 475 | ::: 476 | 477 | ```lua 478 | -- In a ServerScript 479 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsServer) 480 | 481 | Animations:Init() 482 | 483 | task.wait(5) 484 | 485 | print("Applying r15 ninja jump & idle animations") 486 | 487 | -- These animations will only work if your character is R15 488 | Animations:ApplyCustomRBXAnimationIds(game.Players.YourName, { 489 | [Enum.HumanoidRigType.R15] = { 490 | jump = 656117878, 491 | idle = { 492 | Animation1 = 656117400, 493 | Animation2 = 656118341 494 | } 495 | } 496 | }) 497 | ``` 498 | ]=] 499 | 500 | --[=[ 501 | @method GetAnimationProfile 502 | @within AnimationsServer 503 | @param animationProfileName string 504 | @return animationProfile humanoidRigTypeToCustomRBXAnimationIds? 505 | 506 | Returns the [`humanoidRigTypeToCustomRBXAnimationIds`](api/AnimationsServer#humanoidRigTypeToCustomRBXAnimationIds) table found in the profile module `Deps.`, or not if it doesn't exist. 507 | ]=] 508 | --[=[ 509 | @method ApplyAnimationProfile 510 | @within AnimationsServer 511 | @yields 512 | @param player_or_rig Player | Model 513 | @param animationProfileName string 514 | 515 | Applies the animation ids found in the animation profile on the `player_or_rig`. 516 | 517 | Yields when... 518 | - ...the player's character, player or rig's humanoid, player's animator, or player or rig's animate script aren't immediately available. 519 | 520 | :::note 521 | The `player_or_rig` does not need to be registered or have its tracks loaded for this to work. 522 | ::: 523 | :::warning 524 | This function only works for players and R6/R15 NPCs that have an `"Animate"` script in their model. 525 | ::: 526 | :::info 527 | For more information on setting up animated objects check out [animation profiles tutorial](/docs/animation-profiles). 528 | ::: 529 | ]=] 530 | 531 | --[=[ 532 | @method AwaitAllTracksLoaded 533 | @yields 534 | @within AnimationsServer 535 | @param player_or_rig Player | Model 536 | 537 | Yields until all the `player_or_rig`'s animation tracks have loaded. 538 | 539 | :::caution *changed in version 2.0.0-rc1* 540 | Renamed: ~~`AwaitLoaded`~~ -> `AwaitAllTracksLoaded` 541 | ::: 542 | 543 | ```lua 544 | -- In a ServerScript 545 | -- [WARNING] For this to work you need animation ids under the rig type of "Player" in the 'AnimationIds' module 546 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsServer) 547 | 548 | Animations:Init({ 549 | AutoLoadAllPlayerTracks = true -- Defaults to false 550 | }) 551 | 552 | local player = game.Players:WaitForChild("MyName") 553 | 554 | Animations:AwaitAllTracksLoaded(player) 555 | 556 | print("Animation tracks finished loading on the server!") 557 | ``` 558 | ]=] 559 | --[=[ 560 | @method AwaitTracksLoadedAt 561 | @yields 562 | @within AnimationsServer 563 | @param player_or_rig Player | Model 564 | @param path path 565 | 566 | Yields until the `player_or_rig`'s animation tracks have loaded at `path`. 567 | 568 | :::tip *added in version 2.0.0-rc1* 569 | ::: 570 | ]=] 571 | 572 | --[=[ 573 | @method AreAllTracksLoaded 574 | @within AnimationsServer 575 | @param player_or_rig Player | Model 576 | @return boolean 577 | 578 | Returns if the `player_or_rig` has had all its animation tracks loaded. 579 | 580 | :::caution *changed in version 2.0.0-rc1* 581 | Renamed: ~~`AreTracksLoaded`~~ -> `AreAllTracksLoaded` 582 | ::: 583 | ]=] 584 | --[=[ 585 | @method AreTracksLoadedAt 586 | @within AnimationsServer 587 | @param player_or_rig Player | Model 588 | @param path path 589 | @return boolean 590 | 591 | Returns if the `player_or_rig` has had its animation tracks loaded at `path`. 592 | 593 | :::tip *added in version 2.0.0-rc1* 594 | ::: 595 | ]=] 596 | 597 | --[=[ 598 | @yields 599 | @method LoadAllTracks 600 | @within AnimationsServer 601 | @param player_or_rig Player | Model 602 | 603 | Creates animation tracks from all animation ids in [`AnimationIds`](/api/AnimationIds) for the `player_or_rig`. 604 | 605 | Yields when... 606 | - ...`player_or_rig`'s animator is not a descendant of `game`. 607 | 608 | :::caution *changed in version 2.0.0-rc1* 609 | Renamed: ~~`LoadTracks`~~ -> `LoadAllTracks` 610 | 611 | If `player_or_rig` is a rig, this requires `Animations:Register()` before usage. 612 | ::: 613 | ]=] 614 | --[=[ 615 | @yields 616 | @method LoadTracksAt 617 | @within AnimationsServer 618 | @param player_or_rig Player | Model 619 | @param path path 620 | 621 | Creates animation tracks from animation ids in [`AnimationIds`](/api/AnimationIds) for the `player_or_rig` at `path`. 622 | 623 | Yields when... 624 | - ...`player_or_rig`'s animator is not a descendant of `game`. 625 | 626 | :::tip *added in version 2.0.0-rc1* 627 | ::: 628 | ]=] 629 | 630 | --[=[ 631 | @method GetTrack 632 | @within AnimationsServer 633 | @param player_or_rig Player | Model 634 | @param path path 635 | @return AnimationTrack? 636 | 637 | Returns a `player_or_rig`'s animation track or `nil`. 638 | ]=] 639 | 640 | --[=[ 641 | @method PlayTrack 642 | @within AnimationsServer 643 | @param player_or_rig Player | Model 644 | @param path path 645 | @param fadeTime number? 646 | @param weight number? 647 | @param speed number? 648 | @return AnimationTrack 649 | 650 | Returns a playing `player_or_rig`'s animation track. 651 | ]=] 652 | 653 | --[=[ 654 | @method StopTrack 655 | @within AnimationsServer 656 | @param player_or_rig Player | Model 657 | @param path path 658 | @param fadeTime number? 659 | @return AnimationTrack 660 | 661 | Returns a stopped `player_or_rig`'s animation track. 662 | ]=] 663 | 664 | --[=[ 665 | @method StopPlayingTracks 666 | @within AnimationsServer 667 | @param player_or_rig Player | Model 668 | @param fadeTime number? 669 | @return {AnimationTrack?} 670 | 671 | Returns the stopped `player_or_rig` animation tracks. 672 | 673 | :::caution *changed in version 2.0.0-rc1* 674 | Renamed: ~~`StopAllTracks`~~ -> `StopPlayingTracks` 675 | ::: 676 | ]=] 677 | 678 | --[=[ 679 | @method GetPlayingTracks 680 | @within AnimationsServer 681 | @param player_or_rig Player | Model 682 | @return {AnimationTrack?} 683 | 684 | Returns playing `player_or_rig` animation tracks. 685 | ]=] 686 | 687 | --[=[ 688 | @method StopTracksOfPriority 689 | @within AnimationsServer 690 | @param player_or_rig Player | Model 691 | @param animationPriority Enum.AnimationPriority 692 | @param fadeTime number? 693 | @return {AnimationTrack?} 694 | 695 | Returns the stopped `player_or_rig` animation tracks. 696 | ]=] 697 | 698 | --[=[ 699 | @method GetTrackFromAlias 700 | @within AnimationsServer 701 | @param player_or_rig Player | Model 702 | @param alias any 703 | @return AnimationTrack? 704 | 705 | Returns a `player_or_rig`'s animation track or `nil`. 706 | ]=] 707 | 708 | --[=[ 709 | @method PlayTrackFromAlias 710 | @within AnimationsServer 711 | @param player_or_rig Player | Model 712 | @param alias any 713 | @param fadeTime number? 714 | @param weight number? 715 | @param speed number? 716 | @return AnimationTrack 717 | 718 | Returns a playing `player_or_rig`'s animation track. 719 | ]=] 720 | 721 | --[=[ 722 | @method StopTrackFromAlias 723 | @within AnimationsServer 724 | @param player_or_rig Player | Model 725 | @param alias any 726 | @param fadeTime number? 727 | @return AnimationTrack 728 | 729 | Returns a stopped `player_or_rig`'s animation track. 730 | ]=] 731 | 732 | --[=[ 733 | @method SetTrackAlias 734 | @within AnimationsServer 735 | @param player_or_rig Player | Model 736 | @param alias any 737 | @param path path 738 | 739 | Sets an alias to be the equivalent of the path for a `player_or_rig`'s animation track. 740 | 741 | :::tip 742 | You can use the alias as the last key in the path. Useful for a table of animations. Example: 743 | 744 | ```lua 745 | -- In ReplicatedStorage.Animations.Deps.AnimationIds 746 | local animationIds = { 747 | Player = { 748 | FistsCombat = { 749 | -- Fists 3 hit combo 750 | Combo = { 751 | [1] = 1234567, 752 | [2] = 1234567, 753 | [3] = 1234567 754 | }, 755 | 756 | -- Fists heavy attack 757 | HeavyAttack = 1234567 758 | }, 759 | 760 | SwordCombat = { 761 | -- Sword 3 hit combo 762 | Combo = { 763 | [1] = 1234567, 764 | [2] = 1234567, 765 | [3] = 1234567 766 | }, 767 | 768 | -- Sword heavy attack 769 | HeavyAttack = 1234567 770 | } 771 | } 772 | } 773 | ``` 774 | 775 | ```lua 776 | -- In a ServerScript 777 | local player = game.Players.wrello 778 | 779 | -- After the player's animation tracks are loaded... 780 | 781 | local heavyAttackAlias = "HeavyAttack" -- We want this alias in order to call Animations:PlayTrackFromAlias(player, heavyAttackAlias) regardless what weapon is equipped 782 | 783 | local currentEquippedWeapon 784 | 785 | local function updateHeavyAttackAliasPath() 786 | local alias = heavyAttackAlias 787 | local path = currentEquippedWeapon .. "Combat" 788 | 789 | Animations:SetTrackAlias(player, alias, path) -- Running this will search first "path.alias" and then search "path" if it didn't find "path.alias" 790 | end 791 | 792 | local function equipNewWeapon(weaponName) 793 | currentEquippedWeapon = weaponName 794 | 795 | updateHeavyAttackAliasPath() 796 | end 797 | 798 | equipNewWeapon("Fists") 799 | 800 | Animations:PlayTrackFromAlias(player, heavyAttackAlias) -- Plays "FistsCombat.HeavyAttack" on the player's character 801 | 802 | equipNewWeapon("Sword") 803 | 804 | Animations:PlayTrackFromAlias(player, heavyAttackAlias) -- Plays "SwordCombat.HeavyAttack" on the player's character 805 | ``` 806 | ::: 807 | ]=] 808 | 809 | --[=[ 810 | @method RemoveTrackAlias 811 | @within AnimationsServer 812 | @param player_or_rig Player | Model 813 | @param alias any 814 | 815 | Removes the alias for a `player_or_rig`'s animation track. 816 | ]=] 817 | 818 | --[=[ 819 | @tag Beta 820 | @method AttachAnimatedObject 821 | @within AnimationsServer 822 | @param player_or_rig Player | Model 823 | @param animatedObjectPath path 824 | 825 | Attaches the animated object to the `player_or_rig`. 826 | 827 | :::tip 828 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsServer/#initOptions) for detailed prints about animated objects. 829 | ::: 830 | :::info 831 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 832 | ::: 833 | :::caution 834 | This method is in beta testing. Use with caution. 835 | ::: 836 | :::warning *changed in version 2.4.0* 837 | You can no longer attach animated objects of type "motor6d only" ([explanation](/changelog#v2.4.0)). 838 | ::: 839 | ]=] 840 | 841 | --[=[ 842 | @tag Beta 843 | @method DetachAnimatedObject 844 | @within AnimationsServer 845 | @param player_or_rig Player | Model 846 | @param animatedObjectPath path 847 | 848 | Detaches the animated object from the `player_or_rig`. 849 | 850 | :::tip 851 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsServer/#initOptions) for detailed prints about animated objects. 852 | ::: 853 | :::info 854 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 855 | ::: 856 | :::caution 857 | This method is in beta testing. Use with caution. 858 | ::: 859 | ]=] 860 | 861 | return AnimationsServer :: Types.AnimationsServerType -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/AnimationsClient.lua: -------------------------------------------------------------------------------- 1 | --!strict 2 | -- made by wrello 3 | -- GitHub: https://github.com/wrello/Animations 4 | 5 | assert(game:GetService("RunService"):IsClient(), "Attempt to require AnimationsClient on the server") 6 | 7 | local Players = game:GetService("Players") 8 | local RunService = game:GetService("RunService") 9 | 10 | local Types = require(script.Parent.Util.Types) 11 | local Signal = require(script.Parent.Util.Signal) 12 | local AnimationsClass = require(script.Parent.Util.AnimationsClass) 13 | local ChildFromPath = require(script.Parent.Util.ChildFromPath) 14 | 15 | local player = Players.LocalPlayer 16 | 17 | local AutoCustomRBXAnimationIds = nil 18 | 19 | --[=[ 20 | @interface initOptions 21 | @within AnimationsClient 22 | .AutoLoadAllPlayerTracks false 23 | .TimeToLoadPrints true 24 | .EnableAutoCustomRBXAnimationIds false 25 | .AnimatedObjectsDebugMode false 26 | .DepsFolderPath string? -- Only use if you've moved the 'Deps' folder from its original location. 27 | 28 | Gets applied to [`Properties`](/api/AnimationsClient/#properties). 29 | ]=] 30 | type AnimationsClientInitOptionsType = Types.AnimationsClientInitOptionsType 31 | 32 | --[=[ 33 | @type path {any} | string 34 | @within AnimationsClient 35 | 36 | ```lua 37 | -- In a LocalScript 38 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsClient) 39 | 40 | 41 | -- These are all valid options for retrieving an animation track 42 | local animationPath = "Jump" -- A single key (any type) 43 | 44 | local animationPath = {"Dodge", Vector3.xAxis} -- An array path (values of any type) 45 | 46 | local animationPath = "Climb.Right" -- A path seperated by "." (string) 47 | 48 | 49 | local animationTrack = Animations:GetTrack(animationPath) 50 | ``` 51 | ]=] 52 | local Animations = AnimationsClass.new(script.Name) 53 | 54 | --[=[ 55 | @class AnimationsClient 56 | @client 57 | 58 | :::note 59 | Roblox model path: `Animations.Package.AnimationsClient` 60 | ::: 61 | 62 | :::info 63 | Any reference to "client animation tracks" is referring to animation ids found under [`rigType`](/api/AnimationIds#rigType) of **"Player"** in the [`AnimationIds`](/api/AnimationIds) module. 64 | ::: 65 | ]=] 66 | local AnimationsClient = Animations 67 | 68 | --[=[ 69 | @prop DepsFolderPath nil 70 | @within AnimationsClient 71 | 72 | Set the path to the dependencies folder if you have moved it from its original location inside of the root `Animations` folder. 73 | 74 | :::tip *added in version 2.0.0-rc1* 75 | ::: 76 | ]=] 77 | AnimationsClient.DepsFolderPath = nil 78 | 79 | --[=[ 80 | @prop AutoLoadAllPlayerTracks false 81 | @within AnimationsClient 82 | 83 | If set to true, client animation tracks will be loaded each time the client spawns. 84 | 85 | :::warning 86 | Must have animation ids under [`rigType`](/api/AnimationIds#rigType) of **"Player"** in the [`AnimationIds`](/api/AnimationIds) module. 87 | ::: 88 | 89 | :::caution *changed in version 2.0.0-rc1* 90 | Renamed: ~~`AutoLoadPlayerTracks`~~ -> `AutoLoadAllPlayerTracks` 91 | ::: 92 | ]=] 93 | AnimationsClient.AutoLoadAllPlayerTracks = false 94 | 95 | --[=[ 96 | @prop TimeToLoadPrints true 97 | @within AnimationsClient 98 | 99 | If set to true, makes helpful prints about the time it takes to pre-load and load animations. 100 | ]=] 101 | AnimationsClient.TimeToLoadPrints = true 102 | 103 | --[=[ 104 | @prop EnableAutoCustomRBXAnimationIds false 105 | @within AnimationsClient 106 | 107 | If set to true, applies the [`AutoCustomRBXAnimationIds`](/api/AutoCustomRBXAnimationIds) module table to the client on spawn. 108 | 109 | :::tip *added in version 2.0.0-rc1* 110 | ::: 111 | ]=] 112 | AnimationsClient.EnableAutoCustomRBXAnimationIds = false 113 | 114 | --[=[ 115 | @prop AnimatedObjectsDebugMode false 116 | @within AnimationsClient 117 | 118 | If set to true, prints will be made to help debug attaching and detaching animated objects. 119 | ]=] 120 | AnimationsClient.AnimatedObjectsDebugMode = false 121 | 122 | --[=[ 123 | @yields 124 | @param initOptions initOptions? 125 | 126 | Initializes `AnimationsClient`. 127 | 128 | Yields when... 129 | - ...server has not initialized. 130 | - ...animations are being pre-loaded with `ContentProvider:PreloadAsync()` (could take a while). 131 | 132 | :::info 133 | Should be called once before any other method. 134 | ::: 135 | ]=] 136 | function AnimationsClient:Init(initOptions: AnimationsClientInitOptionsType?) 137 | if self._initialized then 138 | warn("AnimationsClient:Init() only needs to be called once") 139 | return 140 | end 141 | 142 | local function bootstrapDepsFolder() 143 | local depsFolder 144 | 145 | if self.DepsFolderPath then 146 | local ok 147 | ok, depsFolder = pcall(ChildFromPath, game, self.DepsFolderPath) 148 | assert(ok and depsFolder, "No animation deps folder found at path '" .. self.DepsFolderPath .. "'") 149 | else 150 | depsFolder = script.Parent.Parent.Deps 151 | end 152 | 153 | AutoCustomRBXAnimationIds = require(depsFolder.AutoCustomRBXAnimationIds) 154 | 155 | self:_bootstrapDepsFolder(depsFolder) 156 | end 157 | 158 | local function awaitServerInitialized() 159 | local serverInitialized = script.Parent.AnimationsServer:GetAttribute("InitializedForClients") 160 | if not serverInitialized then 161 | task.delay(3, function() 162 | if not serverInitialized then 163 | warn("Infinite yield possible waiting for AnimationsServer:Init()") 164 | end 165 | end) 166 | 167 | script.Parent.AnimationsServer:GetAttributeChangedSignal("InitializedForClients"):Wait() 168 | 169 | serverInitialized = true 170 | end 171 | end 172 | 173 | local function initInitOptions() 174 | if initOptions then 175 | for k, v in pairs(initOptions) do 176 | self[k] = v 177 | end 178 | end 179 | end 180 | 181 | local function initMethods() 182 | local manualRigMethodNames = { 183 | ["AreAllTracksLoaded"] = "AreAllRigTracksLoaded", 184 | ["LoadAllTracks"] = "LoadAllRigTracks", 185 | ["AwaitAllTracksLoaded"] = "AwaitAllRigTracksLoaded", 186 | } 187 | 188 | local noAlternative = {"GetAnimationProfile", "AwaitPreloadAsyncFinished", "FindFirstRigPlayingTrack", "WaitForRigPlayingTrack", "GetTimeOfMarker", "GetAnimationIdString"} 189 | 190 | for k: string, v in pairs(AnimationsClass) do 191 | if type(v) == "function" and not k:match("^_") and not table.find(noAlternative, k) then 192 | local clientMethodName = k 193 | local rigMethodName = manualRigMethodNames[clientMethodName] or clientMethodName:gsub("^(%L[^%L]+)(%L?[^%L]*)", "%1Rig%2") 194 | 195 | self[clientMethodName] = function(self, ...) 196 | return v(self, player, ...) 197 | end 198 | 199 | self[rigMethodName] = function(self, rig, ...) 200 | return v(self, rig, ...) 201 | end 202 | end 203 | end 204 | end 205 | 206 | local function initOnPlayerSpawn() 207 | local function onCharacterAdded(char) 208 | self:Register("Player") 209 | 210 | if self.AutoLoadAllPlayerTracks then 211 | self:LoadAllTracks() 212 | end 213 | 214 | if self.EnableAutoCustomRBXAnimationIds then 215 | self:ApplyCustomRBXAnimationIds(AutoCustomRBXAnimationIds) 216 | end 217 | end 218 | 219 | if player.Character then 220 | onCharacterAdded(player.Character) 221 | end 222 | 223 | player.CharacterAdded:Connect(onCharacterAdded) 224 | end 225 | 226 | local function initCustomRBXAnimationIdsSignal() 227 | local function applyCustomRBXAnimationIds(humRigTypeCustomRBXAnimationIds) 228 | local hum = nil 229 | 230 | if humRigTypeCustomRBXAnimationIds then 231 | local char = player.Character or player.CharacterAdded:Wait() 232 | local animator = char:WaitForChild("Humanoid"):WaitForChild("Animator") 233 | local animateScript = char:WaitForChild("Animate") 234 | 235 | self:_editAnimateScriptValues(animator, animateScript, humRigTypeCustomRBXAnimationIds) 236 | end 237 | 238 | local char = player.Character 239 | hum = char:FindFirstChild("Humanoid") 240 | 241 | if hum then 242 | -- Here we have to hack the state system to force apply the new 243 | -- animation ids if we don't want to modify the 'Animate' script 244 | if hum:GetState() == Enum.HumanoidStateType.Freefall then -- Can't change to 'Landed' mid-air because it will freeze the character 245 | hum:ChangeState(Enum.HumanoidStateType.Running) 246 | else 247 | hum:ChangeState(Enum.HumanoidStateType.Landed) 248 | end 249 | end 250 | end 251 | 252 | self.ApplyCustomRBXAnimationIdsSignal = Signal.new() 253 | 254 | local signals = {self.ApplyCustomRBXAnimationIdsSignal, script.Parent:WaitForChild("ApplyCustomRBXAnimationIds").OnClientEvent} 255 | 256 | for _, signal in ipairs(signals) do 257 | signal:Connect(applyCustomRBXAnimationIds) 258 | end 259 | end 260 | 261 | local function destroyAnimationsServer() 262 | script.Parent.AnimationsServer:Destroy() 263 | end 264 | 265 | awaitServerInitialized() 266 | initInitOptions() 267 | bootstrapDepsFolder() 268 | self:_animationIdsToInstances() 269 | destroyAnimationsServer() 270 | initCustomRBXAnimationIdsSignal() 271 | initMethods() 272 | self._initialized = true -- Need to initialize before using methods in the function below 273 | initOnPlayerSpawn() 274 | end 275 | 276 | 277 | 278 | -- 279 | -- Note: Replication behavior of tool from client->server 280 | 281 | -- Requirement for any replication to happen from client -> server: The tool 282 | -- must be already in their character or backpack *on the server*. 283 | 284 | -- The client must only reparent the tool to their character or backpack. As 285 | -- soon as the client reparents the tool elsewhere, the replication of 286 | -- reparenting will stop. 287 | -- 288 | 289 | -- Not available on client because the motor6d attaching will not replicate but 290 | -- reparenting can so it creates weird behavior 291 | -- --[=[ 292 | -- @tag Beta 293 | -- @method EquipAnimatedTool 294 | -- @within AnimationsClient 295 | -- @param tool: Tool 296 | -- @param motor6dName: string 297 | 298 | -- Equips the `tool` for the client and then attaches the `motor6d` found in the `AnimatedObjects` folder to it. 299 | 300 | -- :::caution 301 | -- This method is in beta testing. Use with caution. 302 | -- ::: 303 | -- :::tip *added in version 2.4.0* 304 | -- ::: 305 | -- ]=] 306 | --[=[ 307 | @tag Beta 308 | 309 | Equips the `tool` for the `rig` and then attaches the `motor6d` found in the `AnimatedObjects` folder to it. 310 | 311 | :::note 312 | This does not replicate to the server. 313 | ::: 314 | :::caution 315 | This method is in beta testing. Use with caution. 316 | ::: 317 | :::tip *added in version 2.4.0* 318 | ::: 319 | ]=] 320 | function AnimationsClient:EquipRigAnimatedTool(player_or_rig: Player | Model, toolToEquip: Tool, motor6dName: Motor6D) 321 | self:_initializedAssertion() 322 | 323 | self:_attachDetachAnimatedObject("attach", player_or_rig, motor6dName, nil, toolToEquip) 324 | end 325 | 326 | --[=[ 327 | @method GetTrackStartSpeed 328 | @within AnimationsClient 329 | @param path path 330 | @return number? 331 | 332 | Returns the animation track's `StartSpeed` (if set in [`HasProperties`](/api/AnimationIds#HasProperties)) or `nil`. 333 | 334 | :::tip *added in version 2.3.0* 335 | ::: 336 | ]=] 337 | --[=[ 338 | @method GetRigTrackStartSpeed 339 | @within AnimationsClient 340 | @param rig Model 341 | @param path path 342 | @return number? 343 | 344 | Returns the animation track's `StartSpeed` (if set in [`HasProperties`](/api/AnimationIds#HasProperties)) or `nil`. 345 | 346 | :::tip *added in version 2.3.0* 347 | ::: 348 | ]=] 349 | 350 | --[=[ 351 | @yields 352 | @tag Beta 353 | @method GetTimeOfMarker 354 | @within AnimationsClient 355 | @param animTrack_or_IdString AnimationTrack | string 356 | @param markerName string 357 | @return number? 358 | 359 | The only reason this would yield is if the 360 | initialization process that caches all of the marker 361 | times is still going on when this method gets called. If 362 | after 3 seconds the initialization process still has not 363 | finished, this method will return `nil`. 364 | 365 | ```lua 366 | local attackAnim = Animations:PlayTrack("Attack") 367 | local timeOfHitStart = Animations:GetTimeOfMarker(attackAnim, "HitStart") 368 | 369 | print("Time of hit start:", timeOfHitStart) 370 | 371 | -- or 372 | 373 | local animIdStr = Animations:GetAnimationIdString("Player", "Attack") 374 | local timeOfHitStart = Animations:GetTimeOfMarker(animIdStr, "HitStart") 375 | 376 | print("Time of hit start:", timeOfHitStart) 377 | ``` 378 | 379 | :::info 380 | You must first modify your 381 | [`AnimationIds`](/api/AnimationIds) module to specify 382 | which animations this method will work on. 383 | ::: 384 | :::caution 385 | This method is in beta testing. Use with caution. 386 | ::: 387 | :::tip *added in version 2.1.0* 388 | ::: 389 | ]=] 390 | --[=[ 391 | @method GetAnimationIdString 392 | @within AnimationsClient 393 | @param rigType rigType 394 | @param path path 395 | @return string 396 | 397 | Returns the animation id string under `rigType` at `path` in the [`AnimationIds`](/api/AnimationIds) module. 398 | 399 | ```lua 400 | local animIdStr = Animations:GetAnimationIdString("Player", "Run") 401 | print(animIdStr) --> "rbxassetid://89327320" 402 | ``` 403 | 404 | :::tip *added in version 2.1.0* 405 | ::: 406 | ]=] 407 | 408 | --[=[ 409 | @method FindFirstRigPlayingTrack 410 | @within AnimationsClient 411 | @param rig Model 412 | @param path path 413 | @return AnimationTrack? 414 | 415 | Returns a playing animation track found in 416 | `rig.Humanoid.Animator:GetPlayingAnimationTracks()` 417 | matching the animation id found at `path` in the 418 | [`AnimationIds`](/api/AnimationIds) module or `nil`. 419 | 420 | ```lua 421 | -- [WARNING] For this to work, `enemyCharacter` would have to be registered (most likely on the server) and "Blocking" would need to be a valid animation name defined in the `AnimationIds` module. 422 | local isBlocking = Animations:FindFirstRigPlayingTrack(enemyCharacter, "Blocking") 423 | 424 | if isBlocking then 425 | warn("We can't hit the enemy, they're blocking!") 426 | end 427 | ``` 428 | 429 | :::tip *added in version 2.1.0* 430 | ::: 431 | ]=] 432 | --[=[ 433 | @yields 434 | @method WaitForRigPlayingTrack 435 | @within AnimationsClient 436 | @param rig Model 437 | @param path path 438 | @param timeout number? 439 | @return AnimationTrack? 440 | 441 | Yields until a playing animation track is found in 442 | `rig.Humanoid.Animator:GetPlayingAnimationTracks()` 443 | matching the animation id found at `path` in the 444 | [`AnimationIds`](/api/AnimationIds) module then returns it or returns `nil` after 445 | `timeout` seconds if provided. 446 | 447 | Especially useful if the animation needs time to replicate from server to client and you want to specify a maximum time to wait until it replicates. 448 | 449 | ```lua 450 | -- [WARNING] For this to work, `enemyCharacter` would have to be registered (on the server) and "Blocking" would need to be a valid animation name defined in the `AnimationIds` module. 451 | local isBlocking = Animations:WaitForRigPlayingTrack(enemyCharacter, "Blocking", 1) 452 | 453 | if isBlocking then 454 | warn("We can't hit the enemy, they're blocking!") 455 | end 456 | ``` 457 | 458 | :::tip *added in version 2.1.0* 459 | ::: 460 | ]=] 461 | 462 | --[=[ 463 | @method GetAppliedProfileName 464 | @within AnimationsClient 465 | @return string? 466 | 467 | Returns the client's currently applied animation profile name or `nil`. 468 | 469 | :::tip *added in version 2.0.0* 470 | ::: 471 | ]=] 472 | --[=[ 473 | @method GetRigAppliedProfileName 474 | @within AnimationsClient 475 | @param rig Model 476 | @return string? 477 | 478 | Returns the `rig`'s currently applied animation profile name or `nil`. 479 | 480 | :::tip *added in version 2.0.0* 481 | ::: 482 | ]=] 483 | 484 | --[=[ 485 | @method AwaitPreloadAsyncFinished 486 | @yields 487 | @within AnimationsClient 488 | @return {Animation?} 489 | 490 | Yields until `ContentProvider:PreloadAsync()` finishes pre-loading all animation instances. 491 | 492 | ```lua 493 | -- In a LocalScript 494 | local loadedAnimInstances = Animations:AwaitPreloadAsyncFinished() 495 | 496 | print("ContentProvider:PreloadAsync() finished pre-loading all:", loadedAnimInstances) 497 | ``` 498 | 499 | :::tip *added in version 2.0.0-rc1* 500 | ::: 501 | ]=] 502 | --[=[ 503 | @prop PreloadAsyncProgressed RBXScriptSignal 504 | @within AnimationsClient 505 | 506 | Fires when `ContentProvider:PreloadAsync()` finishes pre-loading one animation instance. 507 | 508 | ```lua 509 | -- In a LocalScript 510 | Animations.PreloadAsyncProgressed:Connect(function(n, total, loadedAnimInstance) 511 | print("ContentProvider:PreloadAsync() finished pre-loading one:", n, total, loadedAnimInstance) 512 | end) 513 | ``` 514 | 515 | :::tip *added in version 2.0.0-rc1* 516 | ::: 517 | ]=] 518 | 519 | --[=[ 520 | @interface customRBXAnimationIds 521 | @within AnimationsClient 522 | .run number? 523 | .walk number? 524 | .jump number? 525 | .idle {Animation1: number?, Animation2: number?}? 526 | .fall number? 527 | .swim number? 528 | .swimIdle number? 529 | .climb number? 530 | 531 | A table of animation ids to replace the default roblox animation ids. 532 | 533 | :::info 534 | Roblox applies the `"walk"` animation id for `R6` characters and the `"run"` animation id for `R15` characters (instead of both). 535 | ::: 536 | ]=] 537 | 538 | --[=[ 539 | @interface humanoidRigTypeToCustomRBXAnimationIds 540 | @within AnimationsClient 541 | .[Enum.HumanoidRigType.R6] customRBXAnimationIds? 542 | .[Enum.HumanoidRigType.R15] customRBXAnimationIds? 543 | 544 | A table mapping a `Enum.HumanoidRigType` to its supported animation ids that will replace the default roblox animation ids. 545 | ]=] 546 | 547 | --[=[ 548 | @method Register 549 | @within AnimationsClient 550 | 551 | Registers the client's character so that methods using animation tracks can be called. 552 | 553 | :::note 554 | The client's character gets automatically registered through the `client.CharacterAdded` event. 555 | ::: 556 | 557 | :::tip 558 | Automatically gives the `rig` (the client's character) an attribute `"AnimationsRigType"` set to the [`rigType`](/api/AnimationIds#rigType) (which is "Player" in this case). 559 | ::: 560 | 561 | :::tip *added in version 2.0.0-rc1* 562 | ::: 563 | ]=] 564 | --[=[ 565 | @method RegisterRig 566 | @within AnimationsClient 567 | @param rig Model 568 | @param rigType string 569 | 570 | Registers the `rig` so that rig methods using animation tracks can be called. 571 | 572 | :::tip 573 | Automatically gives the `rig` an attribute `"AnimationsRigType"` set to the [`rigType`](/api/AnimationIds/#rigType). 574 | ::: 575 | 576 | :::tip *added in version 2.0.0-rc1* 577 | ::: 578 | ]=] 579 | 580 | --[=[ 581 | @method AwaitRegistered 582 | @yields 583 | @within AnimationsClient 584 | 585 | Yields until the client gets registered. 586 | 587 | :::tip *added in version 2.0.0-rc1* 588 | ::: 589 | ]=] 590 | --[=[ 591 | @method AwaitRigRegistered 592 | @yields 593 | @within AnimationsClient 594 | @param rig Model 595 | 596 | Yields until the `rig` gets registered. 597 | 598 | :::tip *added in version 2.0.0-rc1* 599 | ::: 600 | ]=] 601 | 602 | --[=[ 603 | @method IsRegistered 604 | @within AnimationsClient 605 | @return boolean 606 | 607 | Returns if the client is registered. 608 | 609 | :::tip *added in version 2.0.0-rc1* 610 | ::: 611 | ]=] 612 | --[=[ 613 | @method IsRigRegistered 614 | @within AnimationsClient 615 | @param rig Model 616 | @return boolean 617 | 618 | Returns if the `rig` is registered. 619 | 620 | :::tip *added in version 2.0.0-rc1* 621 | ::: 622 | ]=] 623 | 624 | --[=[ 625 | @method ApplyCustomRBXAnimationIds 626 | @within AnimationsClient 627 | @yields 628 | @param humanoidRigTypeToCustomRBXAnimationIds humanoidRigTypeToCustomRBXAnimationIds 629 | 630 | Applies the animation ids specified in the [`humanoidRigTypeToCustomRBXAnimationIds`](#humanoidRigTypeToCustomRBXAnimationIds) table on the client's character. 631 | 632 | Yields when... 633 | - ...the client's character, humanoid, animator, or animate script aren't immediately available. 634 | 635 | :::tip 636 | See [`ApplyAnimationProfile()`](#ApplyAnimationProfile) for a more convenient way of overriding default roblox character animations. 637 | ::: 638 | 639 | ```lua 640 | -- In a LocalScript 641 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsClient) 642 | 643 | Animations:Init() 644 | 645 | task.wait(5) 646 | 647 | print("Applying r15 ninja jump & idle animations") 648 | 649 | -- These animations will only work if your character is R15 650 | Animations:ApplyCustomRBXAnimationIds({ 651 | [Enum.HumanoidRigType.R15] = { 652 | jump = 656117878, 653 | idle = { 654 | Animation1 = 656117400, 655 | Animation2 = 656118341 656 | } 657 | } 658 | }) 659 | ``` 660 | ]=] 661 | --[=[ 662 | @method ApplyRigCustomRBXAnimationIds 663 | @within AnimationsClient 664 | @yields 665 | @param rig Model 666 | @param humanoidRigTypeToCustomRBXAnimationIds humanoidRigTypeToCustomRBXAnimationIds 667 | 668 | Applies the animation ids specified in the [`humanoidRigTypeToCustomRBXAnimationIds`](#humanoidRigTypeToCustomRBXAnimationIds) table on the `rig`. 669 | 670 | Yields when... 671 | - ...the `rig`'s humanoid or animate script aren't immediately available. 672 | 673 | :::warning 674 | This function only works for R6/R15 NPCs that are local to the client or network-owned by the client and have a client-side `"Animate"` script in their model. 675 | ::: 676 | :::tip 677 | See [`ApplyRigAnimationProfile()`](#ApplyRigAnimationProfile) for a more convenient way of overriding default roblox character animations. 678 | ::: 679 | ]=] 680 | 681 | --[=[ 682 | @method GetAnimationProfile 683 | @within AnimationsClient 684 | @param animationProfileName string 685 | @return animationProfile humanoidRigTypeToCustomRBXAnimationIds? 686 | 687 | Returns the [`humanoidRigTypeToCustomRBXAnimationIds`](api/AnimationsServer#humanoidRigTypeToCustomRBXAnimationIds) table found in the profile module script `Deps.`, or not if it doesn't exist. 688 | ]=] 689 | 690 | --[=[ 691 | @method ApplyAnimationProfile 692 | @within AnimationsClient 693 | @yields 694 | @param animationProfileName string 695 | 696 | Applies the animation ids found in the animation profile on the client's character. 697 | 698 | Yields when... 699 | - ...the client's character, humanoid, animator, or animate script aren't immediately available. 700 | 701 | :::note 702 | The client does not need to be registered or have its tracks loaded for this to work. 703 | ::: 704 | :::info 705 | For more information on setting up animation profiles check out [animation profiles tutorial](/docs/animation-profiles). 706 | ::: 707 | ]=] 708 | --[=[ 709 | @method ApplyRigAnimationProfile 710 | @within AnimationsClient 711 | @yields 712 | @param rig Model 713 | @param animationProfileName string 714 | 715 | Applies the animation ids found in the animation profile on the `rig`. 716 | 717 | Yields when... 718 | - ...the `rig`'s humanoid or animate script aren't immediately available. 719 | 720 | :::note 721 | The `rig` does not need to be registered or have its tracks loaded for this to work. 722 | ::: 723 | :::warning 724 | This function only works for R6/R15 NPCs that are local to the client or network-owned by the client and have a client-side `"Animate"` script in their model. 725 | ::: 726 | :::info 727 | For more information on setting up animation profiles check out [animation profiles tutorial](/docs/animation-profiles). 728 | ::: 729 | ]=] 730 | 731 | --[=[ 732 | @method AwaitAllTracksLoaded 733 | @yields 734 | @within AnimationsClient 735 | 736 | Yields until the client has been registered and then until all animation tracks have loaded. 737 | 738 | :::caution *changed in version 2.0.0-rc1* 739 | Renamed: ~~`AwaitLoaded`~~ -> `AwaitAllTracksLoaded` 740 | ::: 741 | 742 | ```lua 743 | -- In a LocalScript 744 | -- [WARNING] For this to work you need animation ids under the rig type of "Player" in the 'AnimationIds' module 745 | local Animations = require(game.ReplicatedStorage.Animations.Package.AnimationsClient) 746 | 747 | Animations:Init({ 748 | AutoLoadAllPlayerTracks = true -- Defaults to false 749 | }) 750 | 751 | Animations:AwaitAllTracksLoaded() 752 | 753 | print("Animation tracks finished loading on the client!") 754 | ``` 755 | ]=] 756 | --[=[ 757 | @method AwaitAllRigTracksLoaded 758 | @yields 759 | @within AnimationsClient 760 | @param rig Model 761 | 762 | Yields until the `rig` has been registered and then until all animation tracks have loaded. 763 | 764 | :::caution *changed in version 2.0.0-rc1* 765 | Renamed: ~~`AwaitRigTracksLoaded`~~ -> `AwaitAllRigTracksLoaded` 766 | ::: 767 | ]=] 768 | 769 | --[=[ 770 | @method AwaitTracksLoadedAt 771 | @yields 772 | @within AnimationsClient 773 | @param path path 774 | 775 | Yields until the client has been registered and then until all animation tracks have loaded at `path`. 776 | 777 | :::tip *added in version 2.0.0-rc1* 778 | ::: 779 | ]=] 780 | --[=[ 781 | @method AwaitRigTracksLoadedAt 782 | @yields 783 | @within AnimationsClient 784 | @param rig Model 785 | @param path path 786 | 787 | Yields until the `rig` has been registered and then until all animation tracks have loaded at `path`. 788 | 789 | :::tip *added in version 2.0.0-rc1* 790 | ::: 791 | ]=] 792 | 793 | --[=[ 794 | @method AreAllTracksLoaded 795 | @within AnimationsClient 796 | @return boolean 797 | 798 | Returns if the client has had all its animation tracks loaded. 799 | 800 | :::caution *changed in version 2.0.0-rc1* 801 | Renamed: ~~`AreTracksLoaded`~~ -> `AreAllTracksLoaded` 802 | ::: 803 | ]=] 804 | --[=[ 805 | @method AreAllRigTracksLoaded 806 | @within AnimationsClient 807 | @param rig Model 808 | @return boolean 809 | 810 | Returns if the `rig` has had all its animation tracks loaded. 811 | 812 | :::caution *changed in version 2.0.0-rc1* 813 | Renamed: ~~`AreRigTracksLoaded`~~ -> `AreAllRigTracksLoaded` 814 | ::: 815 | ]=] 816 | 817 | --[=[ 818 | @method AreTracksLoadedAt 819 | @within AnimationsClient 820 | @param path path 821 | @return boolean 822 | 823 | Returns if the client has had its animation tracks loaded at `path`. 824 | 825 | :::tip *added in version 2.0.0-rc1* 826 | ::: 827 | ]=] 828 | --[=[ 829 | @method AreRigTracksLoadedAt 830 | @within AnimationsClient 831 | @param rig Model 832 | @param path path 833 | @return boolean 834 | 835 | Returns if the `rig` has had its animation tracks loaded at `path`. 836 | 837 | :::tip *added in version 2.0.0-rc1* 838 | ::: 839 | ]=] 840 | 841 | --[=[ 842 | @yields 843 | @method LoadAllTracks 844 | @within AnimationsClient 845 | 846 | Creates animation tracks from all animation ids in [`AnimationIds`](/api/AnimationIds) for the client. 847 | 848 | Yields when... 849 | - ...client's animator is not a descendant of `game`. 850 | 851 | :::caution *changed in version 2.0.0-rc1* 852 | Renamed: ~~`LoadTracks`~~ -> `LoadAllTracks` 853 | ::: 854 | ]=] 855 | --[=[ 856 | @yields 857 | @method LoadAllRigTracks 858 | @within AnimationsClient 859 | @param rig Model 860 | 861 | Creates animation tracks from all animation ids in [`AnimationIds`](/api/AnimationIds) for the `rig`. 862 | 863 | Yields when... 864 | - ...`rig`'s animator is not a descendant of `game`. 865 | 866 | :::caution *changed in version 2.0.0-rc1* 867 | Renamed: ~~`LoadRigTracks`~~ -> `LoadAllRigTracks` 868 | 869 | Requires `Animations:RegisterRig()` before usage. 870 | ::: 871 | ]=] 872 | 873 | --[=[ 874 | @yields 875 | @method LoadTracksAt 876 | @within AnimationsClient 877 | @param path path 878 | 879 | Creates animation tracks from all animation ids in [`AnimationIds`](/api/AnimationIds) for the client at `path`. 880 | 881 | Yields when... 882 | - ...client's animator is not a descendant of `game`. 883 | 884 | :::tip *added in version 2.0.0-rc1* 885 | ::: 886 | ]=] 887 | --[=[ 888 | @yields 889 | @method LoadRigTracksAt 890 | @within AnimationsClient 891 | @param rig Model 892 | @param path path 893 | 894 | Creates animation tracks from all animation ids in [`AnimationIds`](/api/AnimationIds) for the `rig` at `path`. 895 | 896 | Yields when... 897 | - ...`rig`'s animator is not a descendant of `game`. 898 | 899 | :::tip *added in version 2.0.0-rc1* 900 | ::: 901 | ]=] 902 | 903 | --[=[ 904 | @method GetTrack 905 | @within AnimationsClient 906 | @param path path 907 | @return AnimationTrack? 908 | 909 | Returns a client animation track or `nil`. 910 | ]=] 911 | --[=[ 912 | @method GetRigTrack 913 | @within AnimationsClient 914 | @param rig Model 915 | @param path path 916 | @return AnimationTrack? 917 | 918 | Returns a `rig` animation track or `nil`. 919 | ]=] 920 | 921 | --[=[ 922 | @method PlayTrack 923 | @within AnimationsClient 924 | @param path path 925 | @param fadeTime number? 926 | @param weight number? 927 | @param speed number? 928 | @return AnimationTrack 929 | 930 | Returns a playing client animation track. 931 | ]=] 932 | --[=[ 933 | @method PlayRigTrack 934 | @within AnimationsClient 935 | @param rig Model 936 | @param path path 937 | @param fadeTime number? 938 | @param weight number? 939 | @param speed number? 940 | @return AnimationTrack 941 | 942 | Returns a playing `rig` animation track. 943 | 944 | ```lua 945 | -- `rigType` of "Spider": 946 | Animations:RegisterRig(rig, "Spider") 947 | Animations:LoadAllRigTracks(rig) 948 | Animations:PlayRigTrack(rig, "Crawl") 949 | 950 | -- or if you're doing `:RegisterRig()` and 951 | -- `:LoadAllTracks()` for the `rig` somewhere else: 952 | Animations:AwaitAllRigTracksLoaded(rig) 953 | Animations:PlayRigTrack(rig) 954 | ``` 955 | ]=] 956 | 957 | --[=[ 958 | @method StopTrack 959 | @within AnimationsClient 960 | @param path path 961 | @param fadeTime number? 962 | @return AnimationTrack 963 | 964 | Returns a stopped client animation track. 965 | ]=] 966 | --[=[ 967 | @method StopRigTrack 968 | @within AnimationsClient 969 | @param rig Model 970 | @param path path 971 | @param fadeTime number? 972 | @return AnimationTrack 973 | 974 | Returns a stopped `rig` animation track. 975 | ]=] 976 | 977 | --[=[ 978 | @method StopPlayingTracks 979 | @within AnimationsClient 980 | @param fadeTime number? 981 | @return {AnimationTrack?} 982 | 983 | Returns the stopped client animation tracks. 984 | 985 | :::caution *changed in version 2.0.0-rc1* 986 | Renamed: ~~`StopAllTracks`~~ -> `StopPlayingTracks` 987 | ::: 988 | ]=] 989 | --[=[ 990 | @method StopRigPlayingTracks 991 | @within AnimationsClient 992 | @param rig Model 993 | @param fadeTime number? 994 | @return {AnimationTrack?} 995 | 996 | Returns the stopped `rig` animation tracks. 997 | 998 | :::caution *changed in version 2.0.0-rc1* 999 | Renamed: ~~`StopRigAllTracks`~~ -> `StopRigPlayingTracks` 1000 | ::: 1001 | ]=] 1002 | --[=[ 1003 | @method GetPlayingTracks 1004 | @within AnimationsClient 1005 | @return {AnimationTrack?} 1006 | 1007 | Returns the playing client animation tracks. 1008 | 1009 | :::tip *added in version 2.0.0-rc1* 1010 | ::: 1011 | ]=] 1012 | --[=[ 1013 | @method GetRigPlayingTracks 1014 | @within AnimationsClient 1015 | @param rig Model 1016 | @return {AnimationTrack?} 1017 | 1018 | Returns the playing `rig` animation tracks. 1019 | 1020 | :::tip *added in version 2.0.0-rc1* 1021 | ::: 1022 | ]=] 1023 | 1024 | --[=[ 1025 | @method StopTracksOfPriority 1026 | @within AnimationsClient 1027 | @param animationPriority Enum.AnimationPriority 1028 | @param fadeTime number? 1029 | @return {AnimationTrack?} 1030 | 1031 | Returns the stopped client animation tracks. 1032 | ]=] 1033 | --[=[ 1034 | @method StopRigTracksOfPriority 1035 | @within AnimationsClient 1036 | @param rig Model 1037 | @param animationPriority Enum.AnimationPriority 1038 | @param fadeTime number? 1039 | @return {AnimationTrack?} 1040 | 1041 | Returns the stopped `rig` animation tracks. 1042 | ]=] 1043 | 1044 | --[=[ 1045 | @method GetTrackFromAlias 1046 | @within AnimationsClient 1047 | @param alias any 1048 | @return AnimationTrack? 1049 | 1050 | Returns a client animation track or `nil`. 1051 | ]=] 1052 | --[=[ 1053 | @method GetRigTrackFromAlias 1054 | @within AnimationsClient 1055 | @param rig Model 1056 | @param alias any 1057 | @return AnimationTrack? 1058 | 1059 | Returns a `rig` animation track or `nil`. 1060 | ]=] 1061 | 1062 | --[=[ 1063 | @method PlayTrackFromAlias 1064 | @within AnimationsClient 1065 | @param alias any 1066 | @param fadeTime number? 1067 | @param weight number? 1068 | @param speed number? 1069 | @return AnimationTrack 1070 | 1071 | Returns a playing client animation track. 1072 | ]=] 1073 | --[=[ 1074 | @method PlayRigTrackFromAlias 1075 | @within AnimationsClient 1076 | @param rig Model 1077 | @param alias any 1078 | @param fadeTime number? 1079 | @param weight number? 1080 | @param speed number? 1081 | @return AnimationTrack 1082 | 1083 | Returns a playing `rig` animation track. 1084 | ]=] 1085 | 1086 | --[=[ 1087 | @method StopTrackFromAlias 1088 | @within AnimationsClient 1089 | @param alias any 1090 | @param fadeTime number? 1091 | @return AnimationTrack 1092 | 1093 | Returns a stopped client animation track. 1094 | ]=] 1095 | --[=[ 1096 | @method StopRigTrackFromAlias 1097 | @within AnimationsClient 1098 | @param rig Model 1099 | @param alias any 1100 | @param fadeTime number? 1101 | @return AnimationTrack 1102 | 1103 | Returns a stopped `rig` animation track. 1104 | ]=] 1105 | 1106 | --[=[ 1107 | @method SetTrackAlias 1108 | @within AnimationsClient 1109 | @param alias any 1110 | @param path path 1111 | 1112 | Sets an alias to be the equivalent of the path for a client animation track. 1113 | 1114 | :::tip 1115 | You can use the alias as the last key in the path. Useful for a table of animations. Example: 1116 | 1117 | ```lua 1118 | -- In ReplicatedStorage.Animations.Deps.AnimationIds 1119 | local animationIds = { 1120 | Player = { 1121 | FistsCombat = { 1122 | -- Fists 3 hit combo 1123 | Combo = { 1124 | [1] = 1234567, 1125 | [2] = 1234567, 1126 | [3] = 1234567 1127 | }, 1128 | 1129 | -- Fists heavy attack 1130 | HeavyAttack = 1234567 1131 | }, 1132 | 1133 | SwordCombat = { 1134 | -- Sword 3 hit combo 1135 | Combo = { 1136 | [1] = 1234567, 1137 | [2] = 1234567, 1138 | [3] = 1234567 1139 | }, 1140 | 1141 | -- Sword heavy attack 1142 | HeavyAttack = 1234567 1143 | } 1144 | } 1145 | } 1146 | ``` 1147 | 1148 | ```lua 1149 | -- In a LocalScript 1150 | 1151 | -- After the client's animation tracks are loaded... 1152 | 1153 | local heavyAttackAlias = "HeavyAttack" -- We want this alias in order to call Animations:PlayTrackFromAlias(heavyAttackAlias) regardless what weapon is equipped 1154 | 1155 | local currentEquippedWeapon 1156 | 1157 | local function updateHeavyAttackAliasPath() 1158 | local alias = heavyAttackAlias 1159 | local path = currentEquippedWeapon .. "Combat" 1160 | 1161 | Animations:SetTrackAlias(alias, path) -- Running this will search first "path.alias" and then search "path" if it didn't find "path.alias" 1162 | end 1163 | 1164 | local function equipNewWeapon(weaponName) 1165 | currentEquippedWeapon = weaponName 1166 | 1167 | updateHeavyAttackAliasPath() 1168 | end 1169 | 1170 | equipNewWeapon("Fists") 1171 | 1172 | Animations:PlayTrackFromAlias(heavyAttackAlias) -- Plays "FistsCombat.HeavyAttack" on the client's character 1173 | 1174 | equipNewWeapon("Sword") 1175 | 1176 | Animations:PlayTrackFromAlias(heavyAttackAlias) -- Plays "SwordCombat.HeavyAttack" on the client's character 1177 | ``` 1178 | ::: 1179 | ]=] 1180 | --[=[ 1181 | @method SetRigTrackAlias 1182 | @within AnimationsClient 1183 | @param rig Model 1184 | @param alias any 1185 | @param path path 1186 | 1187 | Sets an alias to be the equivalent of the path for a `rig` animation track. 1188 | 1189 | :::tip 1190 | Same tip for [`Animations:SetTrackAlias()`](#SetTrackAlias) applies here. 1191 | ::: 1192 | ]=] 1193 | 1194 | --[=[ 1195 | @method RemoveTrackAlias 1196 | @within AnimationsClient 1197 | @param alias any 1198 | 1199 | Removes the alias for a client animation track. 1200 | ]=] 1201 | --[=[ 1202 | @method RemoveRigTrackAlias 1203 | @within AnimationsClient 1204 | @param rig Model 1205 | @param alias any 1206 | 1207 | Removes the alias for a `rig` animation track. 1208 | ]=] 1209 | 1210 | --[=[ 1211 | @tag Beta 1212 | @method AttachAnimatedObject 1213 | @within AnimationsClient 1214 | @param animatedObjectPath path 1215 | 1216 | Attaches the animated object to the client's character. 1217 | 1218 | :::note 1219 | This does not replicate to the server. 1220 | ::: 1221 | :::tip 1222 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsClient/#initOptions) for detailed prints about animated objects. 1223 | ::: 1224 | :::info 1225 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 1226 | ::: 1227 | :::caution 1228 | This method is in beta testing. Use with caution. 1229 | ::: 1230 | :::warning *changed in version 2.4.0* 1231 | You can no longer attach animated objects of type "motor6d only" ([explanation](/changelog#v2.4.0)). 1232 | ::: 1233 | ]=] 1234 | --[=[ 1235 | @tag Beta 1236 | @method AttachRigAnimatedObject 1237 | @within AnimationsClient 1238 | @param rig Model 1239 | @param animatedObjectPath path 1240 | 1241 | Attaches the animated object to the `rig`. 1242 | 1243 | :::note 1244 | This does not replicate to the server. 1245 | ::: 1246 | :::tip 1247 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsClient/#initOptions) for detailed prints about animated objects. 1248 | ::: 1249 | :::info 1250 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 1251 | ::: 1252 | :::caution 1253 | This method is in beta testing. Use with caution. 1254 | ::: 1255 | :::warning *changed in version 2.4.0* 1256 | You can no longer attach animated objects of type "motor6d only" ([explanation](/changelog#v2.4.0)). 1257 | ::: 1258 | ]=] 1259 | 1260 | --[=[ 1261 | @tag Beta 1262 | @method DetachAnimatedObject 1263 | @within AnimationsClient 1264 | @param animatedObjectPath path 1265 | 1266 | Detaches the animated object from the client's character. 1267 | 1268 | :::note 1269 | This does not replicate to the server. 1270 | ::: 1271 | :::tip 1272 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsClient/#initOptions) for detailed prints about animated objects. 1273 | ::: 1274 | :::info 1275 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 1276 | ::: 1277 | :::caution 1278 | This method is in beta testing. Use with caution. 1279 | ::: 1280 | ]=] 1281 | --[=[ 1282 | @tag Beta 1283 | @method DetachRigAnimatedObject 1284 | @within AnimationsClient 1285 | @param rig Model 1286 | @param animatedObjectPath path 1287 | 1288 | Detaches the animated object from the `rig`. 1289 | 1290 | :::note 1291 | This does not replicate to the server. 1292 | ::: 1293 | :::tip 1294 | Enable [`initOptions.AnimatedObjectsDebugMode`](/api/AnimationsClient/#initOptions) for detailed prints about animated objects. 1295 | ::: 1296 | :::info 1297 | For more information on setting up animated objects check out [animated objects tutorial](/docs/animated-objects). 1298 | ::: 1299 | :::caution 1300 | This method is in beta testing. Use with caution. 1301 | ::: 1302 | ]=] 1303 | 1304 | return AnimationsClient :: Types.AnimationsClientType -------------------------------------------------------------------------------- /src/ReplicatedStorage/Animations/Package/Util/AnimationsClass/init.lua: -------------------------------------------------------------------------------- 1 | -- made by wrello 2 | 3 | local Players = game:GetService("Players") 4 | local ContentProvider = game:GetService("ContentProvider") 5 | local RunService = game:GetService("RunService") 6 | local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider") 7 | 8 | local AsyncInfoCache = require(script.Parent.AsyncInfoCache) 9 | local Signal = require(script.Parent.Signal) 10 | local Await = require(script.Parent.Await) 11 | local ChildFromPath = require(script.Parent.ChildFromPath) 12 | local CustomAssert = require(script.Parent.CustomAssert) 13 | local Zip = require(script.Parent.Zip) 14 | local Queue = require(script.Parent.Queue) 15 | local Types = require(script.Parent.Parent.Util.Types) 16 | local AnimatedObject = require(script.AnimatedObject) 17 | local AnimatedObjectsCache = require(script.AnimatedObjectsCache) 18 | local GetAttributeAsync = require(script.Parent.Parent.Util.GetAttributeAsync) 19 | 20 | local ANIM_ID_STR = "rbxassetid://%i" 21 | local ANIMATED_OBJECT_INFO_ARR_STR = "animated object info array" 22 | 23 | local RAN_FULL_METHOD = { 24 | Yes = true, 25 | No = false, 26 | } 27 | 28 | local DESCENDANT_ANIMS_LOADED_MARKER = newproxy(true) 29 | getmetatable(DESCENDANT_ANIMS_LOADED_MARKER).__tostring = function() 30 | return "DESCENDANT_ANIMS_LOADED_MARKER" 31 | end 32 | 33 | local ALL_ANIMS_MARKER = newproxy(true) 34 | getmetatable(ALL_ANIMS_MARKER).__tostring = function() 35 | return "ALL_ANIMS_MARKER" 36 | end 37 | 38 | local AnimationIds = nil 39 | local animatedObjectsFolder = nil 40 | local animationProfiles = nil 41 | 42 | local allMarkerTimes = {} 43 | local markerTimesLoadedSignal = Signal.new() 44 | 45 | local function isPlayer(player_or_rig) 46 | if player_or_rig.ClassName == "Player" then 47 | return true 48 | elseif Players:GetPlayerFromCharacter(player_or_rig) then 49 | return true 50 | end 51 | end 52 | 53 | local function getRig(player_or_rig) 54 | local playerCharacter 55 | 56 | if player_or_rig.ClassName == "Player" then 57 | task.delay(3, function() 58 | -- This can happen if `Players.CharacterAutoLoads` is disabled and 59 | -- this function gets called before it has a chance to load in. 60 | if not playerCharacter then 61 | warn(`Infinite yield possible on '{player_or_rig.Name}.CharacterAdded:Wait()'`) 62 | end 63 | end) 64 | 65 | playerCharacter = player_or_rig.Character or player_or_rig.CharacterAdded:Wait() 66 | end 67 | 68 | return playerCharacter or player_or_rig 69 | end 70 | 71 | local function clearTracks(currentSet) 72 | for _, track in pairs(currentSet) do 73 | local type = typeof(track) 74 | 75 | if type == "table" then 76 | clearTracks(track) 77 | elseif type == "Instance" then 78 | track:Destroy() 79 | end 80 | end 81 | 82 | table.clear(currentSet) 83 | end 84 | 85 | local function getAnimator(player_or_rig, rig) 86 | local animatorParent = (player_or_rig.ClassName == "Player" and rig:WaitForChild("Humanoid")) or rig:FindFirstChild("Humanoid") or rig:FindFirstChild("AnimationController") 87 | 88 | CustomAssert(animatorParent, "No animator parent (Humanoid or AnimationController) found [", rig:GetFullName(), "]") 89 | 90 | local animator = animatorParent:FindFirstChild("Animator") 91 | 92 | if not animator then 93 | animator = Instance.new("Animator") 94 | animator.Parent = animatorParent 95 | end 96 | 97 | CustomAssert(animator, "No animator found [", rig:GetFullName(), "]") 98 | 99 | return animator 100 | end 101 | 102 | local function isAnimatedObjectInfoArray(v) 103 | return getmetatable(v) == ANIMATED_OBJECT_INFO_ARR_STR 104 | end 105 | 106 | local function createAnimatedObjectInfoArray() 107 | return setmetatable({}, { __metatable = ANIMATED_OBJECT_INFO_ARR_STR }) 108 | end 109 | 110 | local function getAnimIdNumber(animIdString) 111 | return tonumber(animIdString:match("%d+$")) 112 | end 113 | 114 | local function deepClone(t) 115 | local clone = {} 116 | 117 | for k, v in pairs(t) do 118 | if type(v) == "table" then 119 | clone[k] = deepClone(v) 120 | else 121 | clone[k] = v 122 | end 123 | end 124 | 125 | return clone 126 | end 127 | 128 | local function isSubPath(subPath, mainPath) 129 | if type(mainPath) ~= type(subPath) then 130 | return 131 | end 132 | 133 | if type(mainPath) == "string" then 134 | return #subPath == 0 or mainPath:sub(1, #subPath) == subPath 135 | else 136 | for i, v in pairs(subPath) do 137 | if v ~= mainPath[i] then 138 | return 139 | end 140 | end 141 | 142 | return true 143 | end 144 | end 145 | 146 | local function customClientNPCEquipTool(rig, toolToEquip) 147 | -- Roblox doesn't support client-side NPC tool equipping at 148 | -- all. 149 | 150 | -- Custom client-side NPC tool equip method: 151 | 152 | -- 1. Unequip any currently held tools 153 | for _, child in ipairs(rig:GetChildren()) do 154 | if child:IsA("Tool") then 155 | child.Parent = nil 156 | end 157 | end 158 | 159 | -- Also ensure any existing grip weld/motor is removed 160 | local rightHand = rig:FindFirstChild("RightHand") or rig:FindFirstChild("Right Arm") 161 | if rightHand then 162 | local oldGrip = rightHand:FindFirstChild("RightGrip") 163 | if oldGrip then 164 | oldGrip:Destroy() 165 | end 166 | end 167 | 168 | -- 2. Equip the new tool 169 | local handle = toolToEquip:FindFirstChild("Handle") 170 | if handle and rightHand then 171 | local grip = Instance.new("Motor6D") 172 | grip.Name = "RightGrip" 173 | grip.Part0 = rightHand 174 | grip.Part1 = handle 175 | grip.C0 = toolToEquip.Grip or CFrame.new() -- Use tool's CFrame or default 176 | grip.Parent = rightHand 177 | end 178 | 179 | toolToEquip.Parent = rig 180 | end 181 | 182 | local AnimationsClass = {} 183 | AnimationsClass.__index = AnimationsClass 184 | 185 | -------------------- 186 | -- INITIALIZATION -- 187 | -------------------- 188 | function AnimationsClass:_createAnimationInstance(animationName, animationId) 189 | local animation = self.AnimationInstancesCache[animationId] 190 | if not animation then 191 | animation = Instance.new("Animation") 192 | animation.Name = tostring(animationName) 193 | animation.AnimationId = ANIM_ID_STR:format(animationId) 194 | 195 | self.AnimationInstancesCache[animationId] = animation 196 | table.insert(self._preloadAsyncArray, animation) 197 | end 198 | 199 | return animation 200 | end 201 | 202 | function AnimationsClass:_bootstrapDepsFolder(depsFolder) 203 | AnimationIds = require(depsFolder.AnimationIds) 204 | animatedObjectsFolder = depsFolder.AnimatedObjects 205 | animationProfiles = Zip.children(depsFolder.AnimationProfiles) 206 | end 207 | 208 | function AnimationsClass:_animationIdsToInstances() 209 | local s = os.clock() 210 | if self.TimeToLoadPrints then 211 | print("Started ContentProvider:PreloadAsync() on animations...") 212 | end 213 | 214 | local unpackQueue = Queue.new() 215 | 216 | local function unpackQueueDoAll() 217 | for unpackFn in unpackQueue:DequeueIter() do 218 | unpackFn() 219 | end 220 | end 221 | 222 | local function mapAnimationInstanceToAnimatedObjectInfo(animationInstance, newAnimatedObjectInfo) 223 | local oldAnimatedObjectInfo = self.AnimationInstanceToAnimatedObjectInfo[animationInstance] 224 | 225 | if oldAnimatedObjectInfo ~= nil then -- Allow multiple animated objects to be attached to the same animation 226 | if isAnimatedObjectInfoArray(oldAnimatedObjectInfo) then 227 | local animatedObjectInfoArray = oldAnimatedObjectInfo 228 | 229 | table.insert(animatedObjectInfoArray, newAnimatedObjectInfo) 230 | else 231 | local animatedObjectInfoArray = createAnimatedObjectInfoArray() 232 | 233 | table.insert(animatedObjectInfoArray, oldAnimatedObjectInfo) 234 | table.insert(animatedObjectInfoArray, newAnimatedObjectInfo) 235 | 236 | self.AnimationInstanceToAnimatedObjectInfo[animationInstance] = animatedObjectInfoArray 237 | end 238 | else 239 | self.AnimationInstanceToAnimatedObjectInfo[animationInstance] = newAnimatedObjectInfo 240 | end 241 | end 242 | 243 | local function initializeAnimationIds(idTable, state) 244 | for animationIndex, animationId in pairs(idTable) do 245 | local type = type(animationId) 246 | local newAnimationInstance = nil 247 | 248 | if type == "table" then 249 | if animationId._singleAnimationId then 250 | newAnimationInstance = self:_createAnimationInstance(animationIndex, animationId._singleAnimationId) 251 | 252 | if animationId._runtimeProps then 253 | state.runtimeProps = animationId._runtimeProps 254 | elseif animationId._animatedObjectInfo then 255 | state.animatedObjectInfo = animationId._animatedObjectInfo 256 | end 257 | else 258 | local nextIdTable = animationId -- We are inside of a normal id table 259 | 260 | -- Moving all keys out of the table sent to 261 | -- `HasAnimatedObject` and `HasProperties` functions and up 262 | -- one level. Need a queue for this because we still need it 263 | -- to happen from top down, but after all animations are 264 | -- loaded. 265 | local doUnpack = nextIdTable._doUnpack 266 | nextIdTable._doUnpack = nil 267 | if doUnpack then 268 | unpackQueue:Enqueue(function() 269 | idTable[animationIndex] = nil 270 | 271 | for k, v in pairs(nextIdTable) do 272 | idTable[k] = v 273 | end 274 | end) 275 | end 276 | 277 | -- First replace all descendant animation ids within 278 | -- `nextIdTable` with { _singleAnimation and _runtimeProps } 279 | local runtimeProps = nextIdTable._runtimeProps 280 | nextIdTable._runtimeProps = nil 281 | if runtimeProps then 282 | state.runtimeProps = runtimeProps 283 | end 284 | 285 | -- Then map all descendant animation instances (or ids -> 286 | -- instances) within `nextIdTable` to their animated object 287 | -- infos 288 | local animatedObjectInfo = nextIdTable._animatedObjectInfo 289 | nextIdTable._animatedObjectInfo = nil 290 | if animatedObjectInfo then 291 | state.animatedObjectInfo = animatedObjectInfo 292 | end 293 | 294 | -- CONTINUE RECURSION | AFTER CONFIGURING STATE FOR DESCENDANT ANIMATION IDS 295 | initializeAnimationIds(nextIdTable, state) 296 | continue 297 | end 298 | elseif type == "number" then 299 | newAnimationInstance = self:_createAnimationInstance(animationIndex, animationId) 300 | elseif type == "userdata" then -- It's an already created animation instance 301 | continue 302 | end 303 | 304 | if state.animatedObjectInfo then 305 | mapAnimationInstanceToAnimatedObjectInfo(newAnimationInstance, state.animatedObjectInfo) 306 | end 307 | 308 | local runtimeProps = state.runtimeProps 309 | if runtimeProps then 310 | if runtimeProps.MarkerTimes then -- Cache animation marker times for this animation 311 | runtimeProps.MarkerTimes = nil 312 | self:_cacheAnimationMarkerTimes(newAnimationInstance.AnimationId) 313 | end 314 | 315 | idTable[animationIndex] = { 316 | _runtimeProps = runtimeProps, 317 | _singleAnimation = newAnimationInstance 318 | } 319 | else 320 | idTable[animationIndex] = newAnimationInstance 321 | end 322 | 323 | -- END RECURSION | AFTER CONFIGURING A SINGLE ANIMATION ID 324 | end 325 | end 326 | 327 | local function preloadAsync() 328 | local len = #self._preloadAsyncArray 329 | 330 | local loaded = 0 331 | for i, animation in ipairs(self._preloadAsyncArray) do 332 | task.spawn(function() 333 | ContentProvider:PreloadAsync({animation}) 334 | loaded += 1 335 | 336 | self.PreloadAsyncProgressed:Fire(loaded, len, animation) 337 | 338 | if loaded == len then 339 | local clone = table.clone(self._preloadAsyncArray) 340 | 341 | table.clear(self._preloadAsyncArray) 342 | self._preloadAsyncArray = nil 343 | 344 | self.PreloadAsyncFinishedSignal:Fire(clone) 345 | end 346 | end) 347 | end 348 | 349 | self.PreloadAsyncFinishedSignal:Wait() 350 | end 351 | 352 | local initialized = {} 353 | for _, idTable in pairs(AnimationIds) do 354 | if initialized[idTable] then 355 | continue 356 | end 357 | 358 | initialized[idTable] = true 359 | 360 | initializeAnimationIds(idTable, {}) 361 | end 362 | 363 | unpackQueueDoAll() 364 | preloadAsync() 365 | 366 | if self.TimeToLoadPrints then 367 | print(string.format("Finished ContentProvider:PreloadAsync() on animations after %.2f seconds", os.clock() - s)) 368 | end 369 | end 370 | 371 | ----------------- 372 | -- CONSTRUCTOR -- 373 | ----------------- 374 | function AnimationsClass.new(moduleName) 375 | local self = setmetatable({}, AnimationsClass) 376 | 377 | self._moduleName = moduleName 378 | self._preloadAsyncArray = {} 379 | 380 | self.PreloadAsyncProgressed = Signal.new() 381 | 382 | self.PreloadAsyncFinishedSignal = Signal.new() 383 | self.FinishedLoadingSignal = Signal.new() 384 | self.RegisteredRigSignal = Signal.new() 385 | self.AnimationInstanceToAnimatedObjectInfo = {} 386 | self.AnimationInstancesCache = {} 387 | 388 | self.Aliases = {} -- Per player and npc rig 389 | 390 | self.PerPlayer = { 391 | Connections = {} 392 | } 393 | 394 | self.PerRig = { 395 | AppliedProfile = {}, 396 | TrackToAnimatedObjectInfo = {}, 397 | LoadedTracks = {}, 398 | Connections = {}, 399 | AnimatedObjectsCache = {}, 400 | IsRegistered = {}, 401 | } 402 | 403 | return self 404 | end 405 | 406 | ------------- 407 | -- PRIVATE -- 408 | ------------- 409 | function AnimationsClass:_rigRegisteredAssertion(rig, ...) 410 | CustomAssert(self.PerRig.IsRegistered[rig], "Rig not registered error [", rig:GetFullName(), "] -", ...) 411 | end 412 | 413 | function AnimationsClass:_cacheAnimationMarkerTimes(animId) 414 | if allMarkerTimes[animId] == nil then 415 | task.spawn(function() 416 | allMarkerTimes[animId] = false -- Mark that we are loading this particular `animId` 417 | 418 | local keyframeSequence: KeyframeSequence = AsyncInfoCache.asyncCall(3, KeyframeSequenceProvider, "GetKeyframeSequenceAsync", {animId}) 419 | 420 | if keyframeSequence then 421 | local thisMarkerTimes = {} 422 | local keyframes = keyframeSequence:GetKeyframes() 423 | 424 | for _, v: Keyframe in ipairs(keyframes) do 425 | local markers = v:GetMarkers() 426 | 427 | for _, m: KeyframeMarker in ipairs(markers) do 428 | thisMarkerTimes[m.Name] = v.Time 429 | end 430 | end 431 | 432 | allMarkerTimes[animId] = thisMarkerTimes 433 | markerTimesLoadedSignal:Fire(animId, thisMarkerTimes) 434 | else 435 | allMarkerTimes[animId] = nil 436 | warn(self._moduleName, "failed to cache animation marker times for animation id", animId) 437 | end 438 | end) 439 | end 440 | end 441 | 442 | function AnimationsClass:_initializedAssertion() 443 | assert(self._initialized, "Call " .. self._moduleName .. ":Init() before calling any methods") 444 | end 445 | 446 | function AnimationsClass:_aliasesRegisterPlayer(player) 447 | if self.Aliases[player] then 448 | return 449 | end 450 | 451 | self.Aliases[player] = {} 452 | 453 | local connections = {} 454 | self.PerPlayer.Connections[player] = connections 455 | 456 | table.insert(connections, player.AncestryChanged:Connect(function(_, newParent) 457 | if newParent == nil then 458 | for _, conn in ipairs(connections) do 459 | conn:Disconnect() 460 | end 461 | 462 | -- Release memory 463 | self.PerPlayer.Connections[player] = nil 464 | self.Aliases[player] = nil 465 | end 466 | end)) 467 | end 468 | 469 | function AnimationsClass:_getTrack(player_or_rig, path, isAlias) 470 | CustomAssert(path ~= nil, "Path is nil") 471 | 472 | local rig = getRig(player_or_rig) 473 | self:_rigRegisteredAssertion(rig, "unable to get track at path [", path, "]") 474 | 475 | local parent 476 | if isAlias then 477 | parent = self.Aliases[player_or_rig] 478 | else 479 | parent = self.PerRig.LoadedTracks[rig] 480 | end 481 | 482 | local areTracksLoaded, track_or_table = self:_areTracksLoadedAt(player_or_rig, path, parent, true) 483 | CustomAssert(areTracksLoaded, "Track(s) not loaded at path [", path, "in", parent, "]") 484 | 485 | return track_or_table 486 | end 487 | 488 | function AnimationsClass:_playStopTrack(play_stop, player_or_rig, path, isAlias, fadeTime, weight, speed) 489 | CustomAssert(path ~= nil, "Path is nil") 490 | 491 | local track = self:_getTrack(player_or_rig, path, isAlias) 492 | CustomAssert(track, "No track found at path [", path, "]", "for [", player_or_rig:GetFullName(), "]") 493 | 494 | track[play_stop](track, fadeTime, weight, speed or track:GetAttribute("StartSpeed")) 495 | 496 | return track 497 | end 498 | 499 | function AnimationsClass:_setTrackAlias(player_or_rig, alias, path) 500 | CustomAssert(path ~= nil, "Path is nil") 501 | 502 | local modifiedPath 503 | 504 | if type(path) == "table" then 505 | modifiedPath = table.clone(path) 506 | 507 | table.insert(modifiedPath, alias) 508 | else 509 | modifiedPath = path 510 | 511 | modifiedPath ..= "." .. alias 512 | end 513 | 514 | local track_or_table = self:_getTrack(player_or_rig, modifiedPath) or self:_getTrack(player_or_rig, path) 515 | CustomAssert(track_or_table, "No track or table found at path [", path, "] with alias [", alias, "] for [", player_or_rig:GetFullName(), "]") 516 | 517 | self.Aliases[player_or_rig][alias] = track_or_table 518 | end 519 | 520 | function AnimationsClass:_removeTrackAlias(player_or_rig, alias) 521 | self.Aliases[player_or_rig][alias] = nil 522 | end 523 | 524 | function AnimationsClass:_attachDetachAnimatedObject(attach_detach, player_or_rig, animatedObjectPath, animatedObjectInfo, toolToEquip) 525 | CustomAssert(animatedObjectPath ~= nil, "Path is nil") 526 | 527 | local rig = getRig(player_or_rig) 528 | self:_rigRegisteredAssertion(rig, "unable to", attach_detach, "animated object [", animatedObjectPath, "]") 529 | 530 | local animatedObjectsCache = self.PerRig.AnimatedObjectsCache[rig] 531 | 532 | if attach_detach == "detach" then 533 | local animatedObjectTable = animatedObjectsCache:Get(animatedObjectPath) 534 | 535 | if not animatedObjectTable then 536 | if self.AnimatedObjectsDebugMode then 537 | print("[ANIM_OBJ_DEBUG] Unable to detach animated object - no attached animated objects found under path [", animatedObjectPath, "]") 538 | end 539 | return 540 | end 541 | 542 | animatedObjectTable:Detach() 543 | elseif attach_detach == "attach" then 544 | local ok, animatedObjectSrc = pcall(ChildFromPath, animatedObjectsFolder, animatedObjectPath) 545 | CustomAssert(ok, "Unable to attach animated object to [", rig:GetFullName(), "] - no animated objects found under path [", animatedObjectPath, "]") 546 | 547 | if toolToEquip then 548 | if RunService:IsServer() then 549 | if isPlayer(player_or_rig) then 550 | CustomAssert(toolToEquip:IsDescendantOf(game), "Unable to equip animated tool to [", rig:GetFullName(), "] - tool [", toolToEquip:GetFullName(), "] must be a descendant of the game") 551 | end 552 | 553 | rig.Humanoid:EquipTool(toolToEquip) -- Need to do this before 'animatedObjectsCache:Get(animatedObjectPath)' below because this unequips the current tools which will potentially uncache an animated object with the same motor6d name allowing us to continue past it 554 | elseif RunService:IsClient() then 555 | -- if isPlayer(player_or_rig) then 556 | -- local client = player_or_rig 557 | -- CustomAssert(toolToEquip.Parent == client.Backpack or toolToEquip.Parent == rig, "Unable to equip animated tool to [", rig:GetFullName(), "] - tool [", toolToEquip:GetFullName(), "] must be in client's backpack or character") 558 | 559 | -- rig.Humanoid:EquipTool(toolToEquip) -- Need to do this before 'animatedObjectsCache:Get(animatedObjectPath)' below because this unequips the current tools which will potentially uncache an animated object with the same motor6d name allowing us to continue past it 560 | -- else 561 | customClientNPCEquipTool(rig, toolToEquip) -- Need to do this before 'animatedObjectsCache:Get(animatedObjectPath)' below because this unequips the current tools which will potentially uncache an animated object with the same motor6d name allowing us to continue past it 562 | -- end 563 | end 564 | else 565 | local isAutoAttaching = animatedObjectInfo 566 | local motor6dOnly = animatedObjectSrc.ClassName == "Motor6D" and not isAutoAttaching 567 | if RunService:IsServer() then 568 | CustomAssert(not motor6dOnly, "Unable to attach animated object to [", rig:GetFullName(), "] - attaching single motor6d could fail to find the equipped tool if directly after 'humanoid:EquipTool()'. Use 'Animations:EquipAnimatedTool()' instead.") 569 | elseif RunService:IsClient() then 570 | CustomAssert(not motor6dOnly, "Unable to attach animated object to [", rig:GetFullName(), "] - attaching single motor6d manually is not available on client. Use 'Animations:EquipAnimatedTool()' on server instead.") 571 | end 572 | end 573 | 574 | if animatedObjectsCache:Get(animatedObjectPath) then 575 | if self.AnimatedObjectsDebugMode then 576 | print("[ANIM_OBJ_DEBUG] Unable to attach animated object - attached animated object already found under path [", animatedObjectPath, "]") 577 | end 578 | return 579 | end 580 | 581 | local animatedObjectTable = AnimatedObject.new(rig, animatedObjectSrc, animatedObjectInfo, self.AnimatedObjectsDebugMode) 582 | 583 | -- No need to error because if an animation id is auto attaching 584 | -- multiple animated objects then one is bound to fail attaching 585 | if not animatedObjectTable:Attach(toolToEquip) then 586 | return 587 | end 588 | 589 | animatedObjectTable:ListenToDetach(function() 590 | animatedObjectsCache:Remove(animatedObjectPath) 591 | end) 592 | 593 | animatedObjectsCache:Map(animatedObjectPath, animatedObjectTable) 594 | 595 | if self.AnimatedObjectsDebugMode then 596 | print(`[ANIM_OBJ_DEBUG] Cache after adding new {RunService:IsClient() and "client" or "server"} sided animated object`, animatedObjectsCache.Cache) 597 | end 598 | 599 | return true 600 | end 601 | end 602 | 603 | function AnimationsClass:_editAnimateScriptValues(animator, animateScript, humRigTypeCustomRBXAnimationIds) 604 | for _, track: AnimationTrack in ipairs(animator:GetPlayingAnimationTracks()) do 605 | track:Stop(0) 606 | end 607 | 608 | for animName, animId in pairs(humRigTypeCustomRBXAnimationIds) do 609 | local rbxAnimInstancesContainer = animateScript:FindFirstChild(animName) 610 | 611 | if rbxAnimInstancesContainer then 612 | for _, animInstance in ipairs(rbxAnimInstancesContainer:GetChildren()) do 613 | local animInstance = animInstance :: Animation 614 | local oldAnimId = getAnimIdNumber(animInstance.AnimationId) 615 | 616 | if type(animId) == "table" then 617 | local animIdTable = animId 618 | local newAnimId = animIdTable[animInstance.Name] 619 | local animIdChanged = newAnimId and newAnimId ~= oldAnimId 620 | 621 | if animIdChanged then 622 | animInstance.AnimationId = ANIM_ID_STR:format(newAnimId) 623 | end 624 | else 625 | local newAnimId = animId 626 | local animIdChanged = newAnimId ~= oldAnimId 627 | 628 | if animIdChanged then 629 | animInstance.AnimationId = ANIM_ID_STR:format(newAnimId) 630 | end 631 | end 632 | end 633 | end 634 | end 635 | end 636 | 637 | function AnimationsClass:_applyCustomRBXAnimationIds(player_or_rig, humanoidRigTypeToCustomRBXAnimationIds) 638 | local rig = getRig(player_or_rig) 639 | local animator = getAnimator(player_or_rig, rig) 640 | 641 | -- These two things (Humanoid + Animate script) are traits of R6/R15 NPCs and 642 | -- player characters, the only rigs this function should be called on. They 643 | -- might not exist for custom rigs and therefore this could produce an 644 | -- infinite yield error. 645 | local hum = rig:WaitForChild("Humanoid") 646 | local animateScript = rig:WaitForChild("Animate") 647 | 648 | local humRigTypeCustomRBXAnimationIds = humanoidRigTypeToCustomRBXAnimationIds[hum.RigType] 649 | 650 | if humRigTypeCustomRBXAnimationIds then 651 | local player = Players:GetPlayerFromCharacter(rig) 652 | 653 | if player then 654 | local clientSidedAnimateScript = animateScript.ClassName == "LocalScript" or animateScript.RunContext == Enum.RunContext.Client 655 | 656 | if RunService:IsClient() then 657 | if clientSidedAnimateScript then 658 | self.ApplyCustomRBXAnimationIdsSignal:Fire(humRigTypeCustomRBXAnimationIds) 659 | else 660 | self.ApplyCustomRBXAnimationIdsSignal:Fire() 661 | end 662 | else 663 | if not clientSidedAnimateScript then 664 | -- Forced to make changes on the server because the client 665 | -- can't make them 666 | self:_editAnimateScriptValues(animator, animateScript, humRigTypeCustomRBXAnimationIds) 667 | self.ApplyCustomRBXAnimationIdsSignal:FireClient(player) 668 | else 669 | -- Tell the client to make the changes 670 | self.ApplyCustomRBXAnimationIdsSignal:FireClient(player, humRigTypeCustomRBXAnimationIds) 671 | end 672 | end 673 | else 674 | -- WARNING: Undefined behavior if the NPC rig is of auto/server 675 | -- network ownership and or has a server "Animate" script and the 676 | -- client calls `Animations:ApplyRigAnimationProfile()` or 677 | -- `Animations:ApplyRigCustomRBXAnimationIds()` on it. 678 | self:_editAnimateScriptValues(animator, animateScript, humRigTypeCustomRBXAnimationIds) 679 | 680 | RunService.Stepped:Wait() -- Without a task.wait() or a RunService.Stepped:Wait() the running animation bugs if the rig is moving when this function is called 681 | 682 | hum:ChangeState(Enum.HumanoidStateType.Landed) -- Hack to force apply the new animations. 683 | end 684 | end 685 | end 686 | 687 | function AnimationsClass:_loadTracksAt(player_or_rig, path) 688 | CustomAssert(path ~= nil, "Path is nil") 689 | 690 | local rig = getRig(player_or_rig) 691 | self:_rigRegisteredAssertion(rig, "unable to load tracks at path [", path or ALL_ANIMS_MARKER, "]") 692 | 693 | local parent = self.PerRig.LoadedTracks[rig] 694 | local instance_or_id_table, parent_id_table, animationName = parent, nil, nil 695 | 696 | if path ~= ALL_ANIMS_MARKER then 697 | if type(path) == "string" then 698 | parent_id_table = ChildFromPath(parent, path:gsub("(%.?[^.]+)$", function(s) 699 | animationName = s:sub(-#s-1) 700 | return "" 701 | end)) 702 | else 703 | animationName = table.remove(path) 704 | parent_id_table = ChildFromPath(parent, path) 705 | table.insert(path, animationName) 706 | end 707 | 708 | instance_or_id_table = parent_id_table and animationName and parent_id_table[animationName] 709 | CustomAssert(instance_or_id_table, "No track or id table found at path [", path, "in", parent, "]") 710 | end 711 | 712 | local s = os.clock() 713 | if self.TimeToLoadPrints then 714 | print("Started loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "]") 715 | end 716 | 717 | -- Wait for the animator to be a descendant of the game before using 718 | -- 'animator:LoadAnimation()' 719 | local animator = getAnimator(rig, rig) 720 | local done = false 721 | task.delay(3, function() 722 | if not done then 723 | warn(`Infinite yield possible - animator for {rig:GetFullName()} is still not descendant of game after 3 seconds`) 724 | end 725 | end) 726 | while true do 727 | if animator:IsDescendantOf(game) then 728 | done = true 729 | break 730 | end 731 | animator.AncestryChanged:Wait() 732 | end 733 | 734 | local function animator_LoadAnimation(animationInstance, animationName, parent_id_table, runtimeProps) 735 | local track = animator:LoadAnimation(animationInstance) 736 | track.Name = animationInstance.Name 737 | 738 | parent_id_table[animationName] = track 739 | 740 | local animatedObjectInfo = self.AnimationInstanceToAnimatedObjectInfo[animationInstance] 741 | if animatedObjectInfo then 742 | self.PerRig.TrackToAnimatedObjectInfo[rig][animationInstance.AnimationId:match("%d+$")] = animatedObjectInfo 743 | end 744 | 745 | if runtimeProps then 746 | if runtimeProps.Looped ~= nil then 747 | track.Looped = runtimeProps.Looped 748 | end 749 | if runtimeProps.Priority ~= nil then 750 | track.Priority = runtimeProps.Priority 751 | end 752 | if runtimeProps.StartSpeed ~= nil then 753 | track:SetAttribute("StartSpeed", runtimeProps.StartSpeed) -- Make it easier to access 754 | end 755 | end 756 | end 757 | 758 | if type(instance_or_id_table) == "userdata" then 759 | if instance_or_id_table.ClassName == "AnimationTrack" then 760 | if self.TimeToLoadPrints then 761 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in 0 seconds (already loaded)") 762 | end 763 | 764 | return RAN_FULL_METHOD.No 765 | else 766 | animator_LoadAnimation(instance_or_id_table, animationName, parent_id_table) 767 | 768 | if self.TimeToLoadPrints then 769 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in ", os.clock() - s, "seconds") 770 | end 771 | end 772 | elseif instance_or_id_table[DESCENDANT_ANIMS_LOADED_MARKER] then 773 | if self.TimeToLoadPrints then 774 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in 0 seconds (already loaded)") 775 | end 776 | 777 | return RAN_FULL_METHOD.No 778 | elseif instance_or_id_table._singleAnimation then 779 | animator_LoadAnimation(instance_or_id_table._singleAnimation, animationName, parent_id_table, instance_or_id_table._runtimeProps) 780 | 781 | if self.TimeToLoadPrints then 782 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in ", os.clock() - s, "seconds") 783 | end 784 | else 785 | local idTable = instance_or_id_table 786 | local loadedATrack = false 787 | 788 | local function loadTracks(animationsTable) 789 | if animationsTable[DESCENDANT_ANIMS_LOADED_MARKER] then 790 | return 791 | end 792 | 793 | animationsTable[DESCENDANT_ANIMS_LOADED_MARKER] = true 794 | 795 | for animationName, animationInstance in pairs(animationsTable) do 796 | local type = type(animationInstance) 797 | 798 | if type == "table" then 799 | if animationInstance._singleAnimation then 800 | loadedATrack = true 801 | animator_LoadAnimation(animationInstance._singleAnimation, animationName, animationsTable, animationInstance._runtimeProps) 802 | else 803 | loadTracks(animationInstance) 804 | end 805 | elseif type == "userdata" and animationInstance.ClassName == "Animation" then 806 | loadedATrack = true 807 | animator_LoadAnimation(animationInstance, animationName, animationsTable) 808 | end 809 | end 810 | end 811 | 812 | loadTracks(idTable) 813 | 814 | self.FinishedLoadingSignal:Fire(rig, path or ALL_ANIMS_MARKER) 815 | 816 | if not loadedATrack then 817 | if self.TimeToLoadPrints then 818 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in 0 seconds (already loaded)") 819 | end 820 | return false 821 | elseif self.TimeToLoadPrints then 822 | print("Finished loading animation tracks for [", rig:GetFullName(), "] at path [", path or ALL_ANIMS_MARKER, "] in ", os.clock() - s, "seconds") 823 | end 824 | end 825 | 826 | return RAN_FULL_METHOD.Yes 827 | end 828 | 829 | function AnimationsClass:_awaitTracksLoadedAt(player_or_rig, path) 830 | CustomAssert(path ~= nil, "Path is nil") 831 | 832 | local rig = getRig(player_or_rig) 833 | self:AwaitRegistered(player_or_rig) 834 | 835 | local parent = self.PerRig.LoadedTracks[rig] 836 | local instance_or_id_table = parent 837 | if path ~= ALL_ANIMS_MARKER then 838 | local ok 839 | ok, instance_or_id_table = pcall(ChildFromPath, parent, path) 840 | CustomAssert(ok and instance_or_id_table, "No track or id table found at path [", path, "in", parent, "]") 841 | end 842 | 843 | if type(instance_or_id_table) == "userdata" then 844 | if instance_or_id_table.ClassName ~= "AnimationTrack" then 845 | Await.Args(nil, self.FinishedLoadingSignal, Await.AllArgumentsCheck(function(loadedRig, loadedPath) 846 | return loadedRig == rig 847 | and (loadedPath == ALL_ANIMS_MARKER or isSubPath(loadedPath, path)) 848 | end)) 849 | return RAN_FULL_METHOD.Yes 850 | end 851 | elseif not instance_or_id_table[DESCENDANT_ANIMS_LOADED_MARKER] then 852 | Await.Args(nil, self.FinishedLoadingSignal) 853 | return RAN_FULL_METHOD.Yes 854 | end 855 | 856 | return RAN_FULL_METHOD.No 857 | end 858 | 859 | function AnimationsClass:_areTracksLoadedAt(player_or_rig, path, parent, retIfNotFound) 860 | CustomAssert(path ~= nil, "Path is nil") 861 | 862 | local rig = getRig(player_or_rig) 863 | self:_rigRegisteredAssertion(rig, "unable to check are tracks loaded at path [", path or ALL_ANIMS_MARKER, "]") 864 | 865 | parent = parent or self.PerRig.LoadedTracks[rig] 866 | local instance_or_id_table = parent 867 | if path ~= ALL_ANIMS_MARKER then 868 | local ok 869 | ok, instance_or_id_table = pcall(ChildFromPath, parent, path) 870 | 871 | if retIfNotFound 872 | and not (ok and instance_or_id_table) 873 | then 874 | return true, nil -- Return that the track is loaded but doesn't exist to prevent errors when there shouldn't be for `_getTrack()` 875 | end 876 | 877 | CustomAssert(ok and instance_or_id_table, "No track or id table found at path [", path, "in", parent, "]") 878 | end 879 | 880 | if type(instance_or_id_table) == "userdata" then 881 | return instance_or_id_table.ClassName == "AnimationTrack", instance_or_id_table 882 | else 883 | return instance_or_id_table[DESCENDANT_ANIMS_LOADED_MARKER], instance_or_id_table 884 | end 885 | end 886 | 887 | function AnimationsClass:_getAnimId(rigType, path) 888 | local newPath = path 889 | if type(path) == "table" then 890 | newPath = table.clone(path) 891 | table.insert(newPath, 1, rigType) 892 | else 893 | newPath = rigType .. '.' .. path 894 | end 895 | 896 | -- `animDescriptor` is either an animation instance or table with runtime 897 | -- props as created when the module runs `_animationIdsToInstances()` 898 | local ok, animDescriptor = pcall(ChildFromPath, AnimationIds, newPath) 899 | CustomAssert(ok and (type(animDescriptor) == "userdata" or type(animDescriptor) == "table" and animDescriptor._singleAnimation), "No animation id found at path [", path, `] in [ AnimationIds.{rigType} ]`) 900 | 901 | local animId 902 | if type(animDescriptor) == "userdata" then 903 | animId = animDescriptor.AnimationId 904 | else 905 | animId = animDescriptor._singleAnimation.AnimationId 906 | end 907 | 908 | return animId 909 | end 910 | 911 | ------------- 912 | -- PUBLIC -- 913 | ------------- 914 | function AnimationsClass:AwaitPreloadAsyncFinished() 915 | if self._preloadAsyncArray then 916 | return self.PreloadAsyncFinishedSignal:Wait() 917 | end 918 | 919 | return RAN_FULL_METHOD.No 920 | end 921 | 922 | function AnimationsClass:GetTrackStartSpeed(player_or_rig: Player | Model, path: {any} | string): number? 923 | self:_initializedAssertion() 924 | 925 | return self:_getTrack(player_or_rig, path):GetAttribute("StartSpeed") 926 | end 927 | 928 | function AnimationsClass:AttachAnimatedObject(player_or_rig: Player | Model, animatedObjectPath: {any} | string) 929 | self:_initializedAssertion() 930 | 931 | self:_attachDetachAnimatedObject("attach", player_or_rig, animatedObjectPath) 932 | end 933 | 934 | function AnimationsClass:DetachAnimatedObject(player_or_rig: Player | Model, animatedObjectPath: {any} | string) 935 | self:_initializedAssertion() 936 | 937 | self:_attachDetachAnimatedObject("detach", player_or_rig, animatedObjectPath) 938 | end 939 | 940 | function AnimationsClass:AreTracksLoadedAt(player_or_rig: Player | Model, path: {any} | string): boolean 941 | self:_initializedAssertion() 942 | 943 | return not not self:_areTracksLoadedAt(player_or_rig, path) 944 | end 945 | 946 | function AnimationsClass:AreAllTracksLoaded(player_or_rig: Player | Model): boolean 947 | self:_initializedAssertion() 948 | 949 | return not not self:_areTracksLoadedAt(player_or_rig, ALL_ANIMS_MARKER) 950 | end 951 | 952 | function AnimationsClass:AwaitTracksLoadedAt(player_or_rig: Player | Model, path: {any} | string): Types.RanFullMethodType 953 | self:_initializedAssertion() 954 | 955 | return self:_awaitTracksLoadedAt(player_or_rig, path) 956 | end 957 | 958 | function AnimationsClass:AwaitAllTracksLoaded(player_or_rig: Player | Model): Types.RanFullMethodType 959 | self:_initializedAssertion() 960 | 961 | return self:_awaitTracksLoadedAt(player_or_rig, ALL_ANIMS_MARKER) 962 | end 963 | 964 | function AnimationsClass:LoadTracksAt(player_or_rig: Player | Model, path: {any} | string): Types.RanFullMethodType 965 | self:_initializedAssertion() 966 | 967 | return self:_loadTracksAt(player_or_rig, path) 968 | end 969 | 970 | function AnimationsClass:LoadAllTracks(player_or_rig: Player | Model): Types.RanFullMethodType 971 | self:_initializedAssertion() 972 | 973 | return self:_loadTracksAt(player_or_rig, ALL_ANIMS_MARKER) 974 | end 975 | 976 | function AnimationsClass:Register(player_or_rig: Player | Model, rigType: string?) 977 | self:_initializedAssertion() 978 | 979 | local rig = getRig(player_or_rig) 980 | if self.PerRig.IsRegistered[rig] then 981 | return 982 | end 983 | 984 | local isPlayer = isPlayer(player_or_rig) 985 | rigType = rigType or isPlayer and "Player" 986 | 987 | local animationIdsToClone = AnimationIds[rigType] 988 | CustomAssert(animationIdsToClone, "No animations found for [", player_or_rig:GetFullName(), "] under rig type [", rigType, "]") 989 | 990 | local connections = {} 991 | self.PerRig.Connections[rig] = connections 992 | 993 | local animatedObjectsCache = AnimatedObjectsCache.new() 994 | self.PerRig.AnimatedObjectsCache[rig] = animatedObjectsCache 995 | 996 | -- Destroy the client-sided animated object clone when the server-sided one 997 | -- gets added. We also destroy the server-sided one before the server does. 998 | -- This makes for 0 latency when the player plays animations on their 999 | -- character. 1000 | if RunService:IsClient() then 1001 | table.insert(connections, rig.ChildAdded:Connect(function(newChild) 1002 | local serverAnimatedObject = newChild 1003 | local serverAnimatedObjectPathType = serverAnimatedObject:GetAttribute(AnimatedObject.SERVER_ANIM_OBJ_AUTO_ATTACH_STR) 1004 | 1005 | if serverAnimatedObjectPathType then 1006 | local serverAnimatedObjectPath = 1007 | if serverAnimatedObjectPathType == "string" then 1008 | serverAnimatedObject.Name 1009 | else 1010 | {serverAnimatedObject.Name} 1011 | 1012 | local clientSidedAnimatedObject = animatedObjectsCache:Get(serverAnimatedObjectPath) 1013 | 1014 | if clientSidedAnimatedObject then 1015 | clientSidedAnimatedObject:TransferToServerAnimatedObject(serverAnimatedObject) 1016 | 1017 | if self.AnimatedObjectsDebugMode then 1018 | print("[ANIM_OBJ_DEBUG] Cache after transfering client sided animated object lifeline to server animated object", animatedObjectsCache.Cache) 1019 | end 1020 | end 1021 | end 1022 | end)) 1023 | end 1024 | 1025 | -- When a track gets played attach an animated object if required 1026 | table.insert(connections, getAnimator(player_or_rig, rig).AnimationPlayed:Connect(function(track) 1027 | if self.AnimatedObjectsDebugMode then 1028 | print(`[ANIM_OBJ_DEBUG] Animation track [ {track.Animation.AnimationId:match("%d.*")} ] played on rig [ {rig:GetFullName()} ]`) 1029 | end 1030 | 1031 | local animatedObjectInfo = self.PerRig.TrackToAnimatedObjectInfo[rig][track.Animation.AnimationId:match("%d+$")] 1032 | 1033 | if animatedObjectInfo then 1034 | local function tryAttach(animatedObjectInfo) 1035 | local isAttached = false 1036 | 1037 | if animatedObjectInfo.AnimatedObjectSettings.AutoAttach then 1038 | isAttached = self:_attachDetachAnimatedObject("attach", player_or_rig, animatedObjectInfo.AnimatedObjectPath, animatedObjectInfo) -- Pass in `animatedObjectInfo` to indicate it was an auto attach 1039 | end 1040 | 1041 | if animatedObjectInfo.AnimatedObjectSettings.AutoDetach then 1042 | track.Stopped:Once(function() 1043 | if self.AnimatedObjectsDebugMode then 1044 | print(`[ANIM_OBJ_DEBUG] Animation track [ {track.Animation.AnimationId:match("%d.*")} ] stopped on rig [ {rig:GetFullName()} ]`) 1045 | end 1046 | 1047 | self:_attachDetachAnimatedObject("detach", player_or_rig, animatedObjectInfo.AnimatedObjectPath) 1048 | end) 1049 | end 1050 | 1051 | return isAttached 1052 | end 1053 | 1054 | if isAnimatedObjectInfoArray(animatedObjectInfo) then 1055 | local animatedObjectInfoArray = animatedObjectInfo 1056 | 1057 | for _, animatedObjectInfo in animatedObjectInfoArray do -- Attaches the first associated animated object 1058 | if tryAttach(animatedObjectInfo) then 1059 | break 1060 | end 1061 | end 1062 | else 1063 | tryAttach(animatedObjectInfo) 1064 | end 1065 | end 1066 | end)) 1067 | 1068 | -- When the rig gets destroyed the animation tracks are no longer loaded 1069 | table.insert(connections, rig.AncestryChanged:Connect(function(_, newParent) 1070 | if newParent == nil then 1071 | for _, conn in ipairs(connections) do 1072 | conn:Disconnect() 1073 | end 1074 | 1075 | clearTracks(self.PerRig.LoadedTracks[rig]) 1076 | 1077 | if not isPlayer then 1078 | self.Aliases[rig] = nil 1079 | end 1080 | 1081 | self.PerRig.IsRegistered[rig] = nil 1082 | self.PerRig.Connections[rig] = nil 1083 | self.PerRig.LoadedTracks[rig] = nil 1084 | self.PerRig.AppliedProfile[rig] = nil 1085 | self.PerRig.AnimatedObjectsCache[rig] = nil 1086 | self.PerRig.TrackToAnimatedObjectInfo[rig] = nil 1087 | end 1088 | end)) 1089 | 1090 | -- Set an attribute for convenience's sake to determine a rig's "type" 1091 | rig:SetAttribute("AnimationsRigType", rigType) 1092 | 1093 | -- Tie the player object to aliases instead of their character with 1094 | -- `_aliasesRegisterPlayer()` 1095 | if isPlayer then 1096 | self:_aliasesRegisterPlayer(player_or_rig) 1097 | else 1098 | self.Aliases[rig] = {} 1099 | end 1100 | 1101 | self.PerRig.TrackToAnimatedObjectInfo[rig] = {} 1102 | self.PerRig.LoadedTracks[rig] = deepClone(animationIdsToClone) 1103 | self.PerRig.IsRegistered[rig] = true 1104 | self.RegisteredRigSignal:Fire(rig) 1105 | end 1106 | 1107 | function AnimationsClass:AwaitRegistered(player_or_rig: Player | Model): Types.RanFullMethodType 1108 | self:_initializedAssertion() 1109 | 1110 | local rig = getRig(player_or_rig) 1111 | 1112 | if not self.PerRig.IsRegistered[rig] then 1113 | local done = false 1114 | task.delay(3, function() 1115 | if not done then 1116 | warn(`Infinite yield possible on {self._moduleName}:AwaitRegistered({player_or_rig:GetFullName()})`) 1117 | end 1118 | end) 1119 | Await.Args(nil, self.RegisteredRigSignal, rig) 1120 | done = true 1121 | return RAN_FULL_METHOD.Yes 1122 | end 1123 | 1124 | return RAN_FULL_METHOD.No 1125 | end 1126 | 1127 | function AnimationsClass:IsRegistered(player_or_rig: Player | Model): boolean 1128 | self:_initializedAssertion() 1129 | 1130 | return not not self.PerRig.IsRegistered[getRig(player_or_rig)] 1131 | end 1132 | 1133 | function AnimationsClass:GetTrack(player_or_rig: Player | Model, path: {any} | string): AnimationTrack? 1134 | self:_initializedAssertion() 1135 | 1136 | return self:_getTrack(player_or_rig, path) 1137 | end 1138 | 1139 | function AnimationsClass:PlayTrack(player_or_rig: Player | Model, path: {any} | string, fadeTime: number?, weight: number?, speed: number?): AnimationTrack 1140 | self:_initializedAssertion() 1141 | 1142 | return self:_playStopTrack("Play", player_or_rig, path, false, fadeTime, weight, speed) 1143 | end 1144 | 1145 | function AnimationsClass:StopTrack(player_or_rig: Player | Model, path: {any} | string, fadeTime: number?): AnimationTrack 1146 | self:_initializedAssertion() 1147 | 1148 | return self:_playStopTrack("Stop", player_or_rig, path, false, fadeTime) 1149 | end 1150 | 1151 | function AnimationsClass:StopTracksOfPriority(player_or_rig: Player | Model, animationPriority: Enum.AnimationPriority, fadeTime: number?): {AnimationTrack?} 1152 | self:_initializedAssertion() 1153 | 1154 | local animator = getAnimator(player_or_rig, getRig(player_or_rig)) 1155 | 1156 | local stoppedTracks = {} 1157 | 1158 | for _, track in ipairs(animator:GetPlayingAnimationTracks()) do 1159 | if track.Priority == animationPriority then 1160 | track:Stop(fadeTime) 1161 | 1162 | table.insert(stoppedTracks, track) 1163 | end 1164 | end 1165 | 1166 | return stoppedTracks 1167 | end 1168 | 1169 | function AnimationsClass:StopPlayingTracks(player_or_rig: Player | Model, fadeTime: number?): {AnimationTrack?} 1170 | self:_initializedAssertion() 1171 | 1172 | local animator = getAnimator(player_or_rig, getRig(player_or_rig)) 1173 | 1174 | local stoppedTracks = {} 1175 | 1176 | for _, track in ipairs(animator:GetPlayingAnimationTracks()) do 1177 | track:Stop(fadeTime) 1178 | 1179 | table.insert(stoppedTracks, track) 1180 | end 1181 | 1182 | return stoppedTracks 1183 | end 1184 | 1185 | function AnimationsClass:FindFirstRigPlayingTrack(rig: Model, path: {any} | string): AnimationTrack? 1186 | self:_initializedAssertion() 1187 | 1188 | local animator = getAnimator(rig, rig) 1189 | local rigType = GetAttributeAsync(rig, "AnimationsRigType") 1190 | local animId = self:_getAnimId(rigType, path) 1191 | 1192 | local playingTracks = animator:GetPlayingAnimationTracks() 1193 | for _, v in playingTracks do 1194 | if v.Animation.AnimationId == animId then 1195 | return v 1196 | end 1197 | end 1198 | 1199 | return nil 1200 | end 1201 | 1202 | function AnimationsClass:GetAnimationIdString(rigType: string, path: {any} | string) 1203 | self:_initializedAssertion() 1204 | 1205 | return self:_getAnimId(rigType, path) 1206 | end 1207 | 1208 | function AnimationsClass:GetTimeOfMarker(animTrack_or_IdString: AnimationTrack | string, markerName: string): number? 1209 | self:_initializedAssertion() 1210 | 1211 | local animId = animTrack_or_IdString 1212 | if type(animTrack_or_IdString) == "userdata" then 1213 | animId = animTrack_or_IdString.Animation.AnimationId 1214 | end 1215 | 1216 | local thisMarkerTimes = allMarkerTimes[animId] 1217 | if thisMarkerTimes == false then -- Indicating that it's currently loading 1218 | -- task.delay(3, function() -- If we were going to yield forever we'd want a warning 1219 | -- if not thisMarkerTimes then 1220 | -- warn(`Infinite yield possible on Animations:GetTimeOfMarker({animTrack}, {markerName})`) 1221 | -- end 1222 | -- end) 1223 | 1224 | local _ 1225 | _, thisMarkerTimes = Await.Args(3, markerTimesLoadedSignal, animId) 1226 | end 1227 | 1228 | return thisMarkerTimes and thisMarkerTimes[markerName] 1229 | end 1230 | 1231 | function AnimationsClass:WaitForRigPlayingTrack(rig: Model, path: {any} | string, timeout: number?): AnimationTrack? 1232 | self:_initializedAssertion() 1233 | 1234 | local animator = getAnimator(rig, rig) 1235 | local rigType = GetAttributeAsync(rig, "AnimationsRigType") 1236 | local animId = self:_getAnimId(rigType, path) 1237 | 1238 | local playingTracks = animator:GetPlayingAnimationTracks() 1239 | for _, v in playingTracks do 1240 | if v.Animation.AnimationId == animId then 1241 | return v 1242 | end 1243 | end 1244 | 1245 | local anim = nil 1246 | 1247 | if not timeout then 1248 | task.delay(3, function() 1249 | if not anim then 1250 | warn(`Infinite yield possible on 'Animations:WaitForRigPlayingTrack({rig}, {path})'`) 1251 | end 1252 | end) 1253 | end 1254 | 1255 | local _ 1256 | _, anim = Await.Args(timeout, animator.AnimationPlayed, function(v) 1257 | return v.Animation.AnimationId == animId 1258 | end) 1259 | 1260 | return anim 1261 | end 1262 | 1263 | function AnimationsClass:GetPlayingTracks(player_or_rig: Player | Model, fadeTime: number?): {AnimationTrack?} 1264 | self:_initializedAssertion() 1265 | 1266 | local animator = getAnimator(player_or_rig, getRig(player_or_rig)) 1267 | 1268 | return animator:GetPlayingAnimationTracks() 1269 | end 1270 | 1271 | function AnimationsClass:GetTrackFromAlias(player_or_rig: Player | Model, alias: any): AnimationTrack? 1272 | self:_initializedAssertion() 1273 | 1274 | return self:_getTrack(player_or_rig, alias, true) 1275 | end 1276 | 1277 | function AnimationsClass:PlayTrackFromAlias(player_or_rig: Player | Model, alias: any, fadeTime: number?, weight: number?, speed: number?): AnimationTrack 1278 | self:_initializedAssertion() 1279 | 1280 | return self:_playStopTrack("Play", player_or_rig, alias, true, fadeTime, weight, speed) 1281 | end 1282 | 1283 | function AnimationsClass:StopTrackFromAlias(player_or_rig: Player | Model, alias: any, fadeTime: number?): AnimationTrack 1284 | self:_initializedAssertion() 1285 | 1286 | return self:_playStopTrack("Stop", player_or_rig, alias, true, fadeTime) 1287 | end 1288 | 1289 | function AnimationsClass:SetTrackAlias(player_or_rig: Player | Model, alias: any, path: {any} | string) 1290 | self:_initializedAssertion() 1291 | 1292 | self:_setTrackAlias(player_or_rig, alias, path) 1293 | end 1294 | 1295 | function AnimationsClass:RemoveTrackAlias(player_or_rig: Player | Model, alias: any) 1296 | self:_initializedAssertion() 1297 | 1298 | self:_removeTrackAlias(player_or_rig, alias) 1299 | end 1300 | 1301 | function AnimationsClass:ApplyCustomRBXAnimationIds(player_or_rig: Player | Model, humanoidRigTypeToCustomRBXAnimationIds: Types.HumanoidRigTypeToCustomRBXAnimationIdsType) 1302 | self:_initializedAssertion() 1303 | 1304 | self:_applyCustomRBXAnimationIds(player_or_rig, humanoidRigTypeToCustomRBXAnimationIds) 1305 | end 1306 | 1307 | function AnimationsClass:GetAnimationProfile(animationProfileName: string): Types.HumanoidRigTypeToCustomRBXAnimationIdsType? 1308 | self:_initializedAssertion() 1309 | 1310 | local animationProfile = animationProfiles[animationProfileName] 1311 | 1312 | return animationProfile 1313 | end 1314 | 1315 | function AnimationsClass:GetAppliedProfileName(player_or_rig: Player | Model): string? 1316 | self:_initializedAssertion() 1317 | 1318 | return self.PerRig.AppliedProfile[getRig(player_or_rig)] 1319 | end 1320 | 1321 | function AnimationsClass:ApplyAnimationProfile(player_or_rig: Player | Model, animationProfileName: string) 1322 | self:_initializedAssertion() 1323 | 1324 | local animationProfile = animationProfiles[animationProfileName] 1325 | CustomAssert(animationProfile, "No animation profile found at name [", animationProfileName, "]") 1326 | 1327 | local rig = getRig(player_or_rig) 1328 | self.PerRig.AppliedProfile[rig] = animationProfileName 1329 | 1330 | self:_applyCustomRBXAnimationIds(rig, animationProfile) 1331 | end 1332 | 1333 | return AnimationsClass 1334 | --------------------------------------------------------------------------------