local plr = game:GetService("Players").LocalPlayer function RunCustomAnimation(Char) if Char:WaitForChild("Animate") ~= nil then Char.Animate.Disabled = true end Char:WaitForChild("Humanoid") for i,v in next, Char.Humanoid:GetPlayingAnimationTracks() do v:Stop() end --fake script local script = Char.Animate local Character = Char local Humanoid = Character:WaitForChild("Humanoid") local pose = "Standing" local UserGameSettings = UserSettings():GetService("UserGameSettings") local userNoUpdateOnLoopSuccess, userNoUpdateOnLoopValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNoUpdateOnLoop") end) local userNoUpdateOnLoop = userNoUpdateOnLoopSuccess and userNoUpdateOnLoopValue local AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent") local HumanoidHipHeight = 2 local humanoidSpeed = 0 -- speed most recently sent to us from onRunning() local cachedRunningSpeed = 0 -- The most recent speed used to compute blends. Tiny variations from cachedRunningSpeed will not cause animation updates. local cachedLocalDirection = {x=0.0, y=0.0} -- unit 2D object space direction of motion local smallButNotZero = 0.0001 -- We want weights to be small but not so small the animation stops local runBlendtime = 0.2 local lastLookVector = Vector3.new(0.0, 0.0, 0.0) -- used to track whether rootPart orientation is changing. local lastBlendTime = 0 -- The last time we blended velocities local WALK_SPEED = 6.4 local RUN_SPEED = 12.8 local EMOTE_TRANSITION_TIME = 0.1 local currentAnim = "" local currentAnimInstance = nil local currentAnimTrack = nil local currentAnimKeyframeHandler = nil local currentAnimSpeed = 1.0 local PreloadedAnims = {} local animTable = {} local animNames = { idle = { { id = "http://www.roblox.com/asset/?id=12521158637", weight = 9 }, { id = "http://www.roblox.com/asset/?id=12521162526", weight = 1 }, }, walk = { { id = "http://www.roblox.com/asset/?id=12518152696", weight = 10 } }, run = { { id = "http://www.roblox.com/asset/?id=12518152696", weight = 10 } }, jump = { { id = "http://www.roblox.com/asset/?id=12520880485", weight = 10 } }, fall = { { id = "http://www.roblox.com/asset/?id=12520972571", weight = 10 } }, climb = { { id = "http://www.roblox.com/asset/?id=12520982150", weight = 10 } }, sit = { { id = "http://www.roblox.com/asset/?id=12520993168", weight = 10 } }, toolnone = { { id = "http://www.roblox.com/asset/?id=12520996634", weight = 10 } }, toolslash = { { id = "http://www.roblox.com/asset/?id=12520999032", weight = 10 } }, toollunge = { { id = "http://www.roblox.com/asset/?id=12521002003", weight = 10 } }, wave = { { id = "http://www.roblox.com/asset/?id=12521004586", weight = 10 } }, point = { { id = "http://www.roblox.com/asset/?id=12521007694", weight = 10 } }, dance = { { id = "http://www.roblox.com/asset/?id=12521009666", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521151637", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521015053", weight = 10 } }, dance2 = { { id = "http://www.roblox.com/asset/?id=12521169800", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521173533", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521027874", weight = 10 } }, dance3 = { { id = "http://www.roblox.com/asset/?id=12521178362", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521181508", weight = 10 }, { id = "http://www.roblox.com/asset/?id=12521184133", weight = 10 } }, laugh = { { id = "http://www.roblox.com/asset/?id=12521018724", weight = 10 } }, cheer = { { id = "http://www.roblox.com/asset/?id=12521021991", weight = 10 } }, } local strafingLocomotionMap = {} local fallbackLocomotionMap = {} local locomotionMap = strafingLocomotionMap -- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote local emoteNames = { wave = false, point = false, dance = true, dance2 = true, dance3 = true, laugh = false, cheer = false} math.randomseed(tick()) function findExistingAnimationInSet(set, anim) if set == nil or anim == nil then return 0 end for idx = 1, set.count, 1 do if set[idx].anim.AnimationId == anim.AnimationId then return idx end end return 0 end function configureAnimationSet(name, fileList) if (animTable[name] ~= nil) then for _, connection in pairs(animTable[name].connections) do connection:disconnect() end end animTable[name] = {} animTable[name].count = 0 animTable[name].totalWeight = 0 animTable[name].connections = {} -- uncomment this section to allow players to load with their -- own (non-classic) animations --[[ local config = script:FindFirstChild(name) if (config ~= nil) then table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end)) table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end)) local idx = 0 for _, childPart in pairs(config:GetChildren()) do if (childPart:IsA("Animation")) then local newWeight = 1 local weightObject = childPart:FindFirstChild("Weight") if (weightObject ~= nil) then newWeight = weightObject.Value end animTable[name].count = animTable[name].count + 1 idx = animTable[name].count animTable[name][idx] = {} animTable[name][idx].anim = childPart animTable[name][idx].weight = newWeight animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end)) table.insert(animTable[name].connections, childPart.ChildAdded:connect(function(property) configureAnimationSet(name, fileList) end)) table.insert(animTable[name].connections, childPart.ChildRemoved:connect(function(property) configureAnimationSet(name, fileList) end)) local lv = childPart:GetAttribute("LinearVelocity") if lv then strafingLocomotionMap[name] = {lv=lv, speed = lv.Magnitude} end if name == "run" or name == "walk" then if lv then fallbackLocomotionMap[name] = strafingLocomotionMap[name] else local speed = name == "run" and RUN_SPEED or WALK_SPEED fallbackLocomotionMap[name] = {lv=Vector2.new(0.0, speed), speed = speed} locomotionMap = fallbackLocomotionMap -- If you don't have a linear velocity with your run or walk, you can't blend/strafe --warn("Strafe blending disabled. No linear velocity information for "..'"'.."walk"..'"'.." and "..'"'.."run"..'"'..".") end end end end end ]] -- if you uncomment the above section, comment out this "if"-block if name == "run" or name == "walk" then local speed = name == "run" and RUN_SPEED or WALK_SPEED fallbackLocomotionMap[name] = {lv=Vector2.new(0.0, speed), speed = speed} locomotionMap = fallbackLocomotionMap -- If you don't have a linear velocity with your run or walk, you can't blend/strafe --warn("Strafe blending disabled. No linear velocity information for "..'"'.."walk"..'"'.." and "..'"'.."run"..'"'..".") end -- fallback to defaults if (animTable[name].count <= 0) then for idx, anim in pairs(fileList) do animTable[name][idx] = {} animTable[name][idx].anim = Instance.new("Animation") animTable[name][idx].anim.Name = name animTable[name][idx].anim.AnimationId = anim.id animTable[name][idx].weight = anim.weight animTable[name].count = animTable[name].count + 1 animTable[name].totalWeight = animTable[name].totalWeight + anim.weight end end -- preload anims for i, animType in pairs(animTable) do for idx = 1, animType.count, 1 do if PreloadedAnims[animType[idx].anim.AnimationId] == nil then Humanoid:LoadAnimation(animType[idx].anim) PreloadedAnims[animType[idx].anim.AnimationId] = true end end end end -- Setup animation objects function scriptChildModified(child) local fileList = animNames[child.Name] if (fileList ~= nil) then configureAnimationSet(child.Name, fileList) else if child:isA("StringValue") then animNames[child.Name] = {} configureAnimationSet(child.Name, animNames[child.Name]) end end end script.ChildAdded:connect(scriptChildModified) script.ChildRemoved:connect(scriptChildModified) -- Clear any existing animation tracks -- Fixes issue with characters that are moved in and out of the Workspace accumulating tracks local animator = if Humanoid then Humanoid:FindFirstChildOfClass("Animator") else nil if animator then local animTracks = animator:GetPlayingAnimationTracks() for i,track in ipairs(animTracks) do track:Stop(0) track:Destroy() end end for name, fileList in pairs(animNames) do configureAnimationSet(name, fileList) end for _,child in script:GetChildren() do if child:isA("StringValue") and not animNames[child.name] then animNames[child.Name] = {} configureAnimationSet(child.Name, animNames[child.Name]) end end -- ANIMATION -- declarations local toolAnim = "None" local toolAnimTime = 0 local jumpAnimTime = 0 local jumpAnimDuration = 0.31 local toolTransitionTime = 0.1 local fallTransitionTime = 0.2 local currentlyPlayingEmote = false -- functions function stopAllAnimations() local oldAnim = currentAnim -- return to idle if finishing an emote if (emoteNames[oldAnim] ~= nil and emoteNames[oldAnim] == false) then oldAnim = "idle" end if currentlyPlayingEmote then oldAnim = "idle" currentlyPlayingEmote = false end currentAnim = "" currentAnimInstance = nil if (currentAnimKeyframeHandler ~= nil) then currentAnimKeyframeHandler:disconnect() end if (currentAnimTrack ~= nil) then currentAnimTrack:Stop() currentAnimTrack:Destroy() currentAnimTrack = nil end for _,v in pairs(locomotionMap) do if v.track then v.track:Stop() v.track:Destroy() v.track = nil end end return oldAnim end function getHeightScale() if Humanoid then if not Humanoid.AutomaticScalingEnabled then return 1 end local scale = Humanoid.HipHeight / HumanoidHipHeight if AnimationSpeedDampeningObject == nil then AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent") end if AnimationSpeedDampeningObject ~= nil then scale = 1 + (Humanoid.HipHeight - HumanoidHipHeight) * AnimationSpeedDampeningObject.Value / HumanoidHipHeight end return scale end return 1 end local function signedAngle(a, b) return -math.atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y) end local angleWeight = 2.0 local function get2DWeight(px, p1, p2, sx, s1, s2) local avgLength = 0.5 * (s1 + s2) local p_1 = {x = (sx - s1)/avgLength, y = (angleWeight * signedAngle(p1, px))} local p12 = {x = (s2 - s1)/avgLength, y = (angleWeight * signedAngle(p1, p2))} local denom = smallButNotZero + (p12.x*p12.x + p12.y*p12.y) local numer = p_1.x * p12.x + p_1.y * p12.y local r = math.clamp(1.0 - numer/denom, 0.0, 1.0) return r end local function blend2D(targetVelo, targetSpeed) local h = {} local sum = 0.0 for n,v1 in pairs(locomotionMap) do if targetVelo.x * v1.lv.x < 0.0 or targetVelo.y * v1.lv.y < 0 then -- Require same quadrant as target h[n] = 0.0 continue end h[n] = math.huge for j,v2 in pairs(locomotionMap) do if targetVelo.x * v2.lv.x < 0.0 or targetVelo.y * v2.lv.y < 0 then -- Require same quadrant as target continue end h[n] = math.min(h[n], get2DWeight(targetVelo, v1.lv, v2.lv, targetSpeed, v1.speed, v2.speed)) end sum += h[n] end --truncates below 10% contribution local sum2 = 0.0 local weightedVeloX = 0 local weightedVeloY = 0 for n,v in pairs(locomotionMap) do if (h[n] / sum > 0.1) then sum2 += h[n] weightedVeloX += h[n] * v.lv.x weightedVeloY += h[n] * v.lv.y else h[n] = 0.0 end end local animSpeed local weightedSpeedSquared = weightedVeloX * weightedVeloX + weightedVeloY * weightedVeloY if weightedSpeedSquared > smallButNotZero then animSpeed = math.sqrt(targetSpeed * targetSpeed / weightedSpeedSquared) else animSpeed = 0 end animSpeed = animSpeed / getHeightScale() local groupTimePosition = 0 for n,v in pairs(locomotionMap) do if v.track.IsPlaying then groupTimePosition = v.track.TimePosition break end end for n,v in pairs(locomotionMap) do -- if not loco if h[n] > 0.0 then if not v.track.IsPlaying then v.track:Play(runBlendtime) v.track.TimePosition = groupTimePosition end local weight = math.max(smallButNotZero, h[n] / sum2) v.track:AdjustWeight(weight, runBlendtime) v.track:AdjustSpeed(animSpeed) else v.track:Stop(runBlendtime) end end end local function getWalkDirection() local walkToPoint = Humanoid.WalkToPoint local walkToPart = Humanoid.WalkToPart if Humanoid.MoveDirection ~= Vector3.zero then return Humanoid.MoveDirection elseif walkToPart or walkToPoint ~= Vector3.zero then local destination if walkToPart then destination = walkToPart.CFrame:PointToWorldSpace(walkToPoint) else destination = walkToPoint end local moveVector = Vector3.zero if Humanoid.RootPart then moveVector = destination - Humanoid.RootPart.CFrame.Position moveVector = Vector3.new(moveVector.x, 0.0, moveVector.z) local mag = moveVector.Magnitude if mag > 0.01 then moveVector /= mag end end return moveVector else return Humanoid.MoveDirection end end local function updateVelocity(currentTime) local tempDir if locomotionMap == strafingLocomotionMap then local moveDirection = getWalkDirection() if not Humanoid.RootPart then return end local cframe = Humanoid.RootPart.CFrame if math.abs(cframe.UpVector.Y) < smallButNotZero or pose ~= "Running" or humanoidSpeed < 0.001 then -- We are horizontal! Do something (turn off locomotion) for n,v in pairs(locomotionMap) do if v.track then v.track:AdjustWeight(smallButNotZero, runBlendtime) end end return end local lookat = cframe.LookVector local direction = Vector3.new(lookat.X, 0.0, lookat.Z) direction = direction / direction.Magnitude --sensible upVector means this is non-zero. local ly = moveDirection:Dot(direction) if ly <= 0.0 and ly > -0.05 then ly = smallButNotZero -- break quadrant ties in favor of forward-friendly strafes end local lx = direction.X*moveDirection.Z - direction.Z*moveDirection.X local tempDir = Vector2.new(lx, ly) -- root space moveDirection local delta = Vector2.new(tempDir.x-cachedLocalDirection.x, tempDir.y-cachedLocalDirection.y) -- Time check serves the purpose of the old keyframeReached sync check, as it syncs anim timePosition if delta:Dot(delta) > 0.001 or math.abs(humanoidSpeed - cachedRunningSpeed) > 0.01 or currentTime - lastBlendTime > 1 then cachedLocalDirection = tempDir cachedRunningSpeed = humanoidSpeed lastBlendTime = currentTime blend2D(cachedLocalDirection, cachedRunningSpeed) end else if math.abs(humanoidSpeed - cachedRunningSpeed) > 0.01 or currentTime - lastBlendTime > 1 then cachedRunningSpeed = humanoidSpeed lastBlendTime = currentTime blend2D(Vector2.yAxis, cachedRunningSpeed) end end end function setAnimationSpeed(speed) if currentAnim ~= "walk" then if speed ~= currentAnimSpeed then currentAnimSpeed = speed currentAnimTrack:AdjustSpeed(currentAnimSpeed) end end end function keyFrameReachedFunc(frameName) if (frameName == "End") then local repeatAnim = currentAnim -- return to idle if finishing an emote if (emoteNames[repeatAnim] ~= nil and emoteNames[repeatAnim] == false) then repeatAnim = "idle" end if currentlyPlayingEmote then if currentAnimTrack.Looped then -- Allow the emote to loop return end repeatAnim = "idle" currentlyPlayingEmote = false end local animSpeed = currentAnimSpeed playAnimation(repeatAnim, 0.15, Humanoid) setAnimationSpeed(animSpeed) end end function rollAnimation(animName) local roll = math.random(1, animTable[animName].totalWeight) local origRoll = roll local idx = 1 while (roll > animTable[animName][idx].weight) do roll = roll - animTable[animName][idx].weight idx = idx + 1 end return idx end local maxVeloX, minVeloX, maxVeloY, minVeloY local function destroyRunAnimations() for _,v in pairs(strafingLocomotionMap) do if v.track then v.track:Stop() v.track:Destroy() v.track = nil end end for _,v in pairs(fallbackLocomotionMap) do if v.track then v.track:Stop() v.track:Destroy() v.track = nil end end cachedRunningSpeed = 0 end local function resetVelocityBounds(velo) minVeloX = 0 maxVeloX = 0 minVeloY = 0 maxVeloY = 0 end local function updateVelocityBounds(velo) if velo then if velo.x > maxVeloX then maxVeloX = velo.x end if velo.y > maxVeloY then maxVeloY = velo.y end if velo.x < minVeloX then minVeloX = velo.x end if velo.y < minVeloY then minVeloY = velo.y end end end local function checkVelocityBounds(velo) if maxVeloX == 0 or minVeloX == 0 or maxVeloY == 0 or minVeloY == 0 then if locomotionMap == strafingLocomotionMap then warn("Strafe blending disabled. Not all quadrants of motion represented.") end locomotionMap = fallbackLocomotionMap else locomotionMap = strafingLocomotionMap end end local function setupWalkAnimation(anim, animName, transitionTime, humanoid) resetVelocityBounds() -- check to see if we need to blend a walk/run animation for n,v in pairs(locomotionMap) do v.track = humanoid:LoadAnimation(animTable[n][1].anim) v.track.Priority = Enum.AnimationPriority.Core updateVelocityBounds(v.lv) end checkVelocityBounds() end local function switchToAnim(anim, animName, transitionTime, humanoid) -- switch animation if (anim ~= currentAnimInstance) then if (currentAnimTrack ~= nil) then currentAnimTrack:Stop(transitionTime) currentAnimTrack:Destroy() end if (currentAnimKeyframeHandler ~= nil) then currentAnimKeyframeHandler:disconnect() end currentAnimSpeed = 1.0 currentAnim = animName currentAnimInstance = anim -- nil in the case of locomotion if animName == "walk" then setupWalkAnimation(anim, animName, transitionTime, humanoid) else destroyRunAnimations() -- load it to the humanoid; get AnimationTrack currentAnimTrack = humanoid:LoadAnimation(anim) currentAnimTrack.Priority = Enum.AnimationPriority.Core currentAnimTrack:Play(transitionTime) -- set up keyframe name triggers currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc) end end end function playAnimation(animName, transitionTime, humanoid) local idx = rollAnimation(animName) local anim = animTable[animName][idx].anim switchToAnim(anim, animName, transitionTime, humanoid) currentlyPlayingEmote = false