local W=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local w=game:GetService("\087\111\114\107\115\112\097\099\101")local Z=W:WaitForChild("\099\111\109\109\111\110")local d=Z:WaitForChild("\111\098\106\101\099\116")local a=Z:WaitForChild("\117\116\105\108")local p=require(a:WaitForChild("\115\111\108\118\101\073\075"))local o=require(d:WaitForChild("\076\101\103"))local c=Vector3.new(0,-1,0)local h={w:WaitForChild("\115\116\097\116\105\099\119\111\114\108\100")}local E={[0]=150,[1]=100,[2]=100,[3]=100;[4]=200;[5]=200;[6]=200,[7]=300,[8]=300,[9]=1000;[10]=1000}local g={}local function H(W,w,Z,d)return(W:lerp(w,d)):lerp(Z,d)end function g.new(W)local w=W:WaitForChild("\085\112\112\101\114\082\105\103\104\116\076\101\103")local Z=W:WaitForChild("\085\112\112\101\114\076\101\102\116\076\101\103")local d=W:WaitForChild("\076\111\119\101\114\082\105\103\104\116\076\101\103")local a=W:WaitForChild("\076\111\119\101\114\076\101\102\116\076\101\103")local p=w:WaitForChild("\082\105\103\104\116\072\105\112")local c=Z:WaitForChild("\076\101\102\116\072\105\112")local h=d:WaitForChild("\082\105\103\104\116\075\110\101\101")local E=a:WaitForChild("\076\101\102\116\075\110\101\101")local H=setmetatable({Rig=W;Root=W:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116"),Humanoid=W:FindFirstChild("\072\117\109\097\110\111\105\100");LastStepped=nil,Legs={Right=o.new(W,p,h),Left=o.new(W,c,E)}},{__index=g})H.Legs.Right:setOtherLeg(H.Legs.Left)H.Legs.Left:setOtherLeg(H.Legs.Right)return H end function g.destroy(W) end local function X(W,w,Z)W.StepStartTick=tick()W.StepStart=W.FootPos W.FootTarget=w W.StepTime=Z W.Planted=false end function g.walkStep(w,W)local Z=w.Legs.Left local d=w.Legs.Right local a=w.Rig local p=w.Root local o=w.Humanoid local E=((W*2)/o.WalkSpeed)*o.HipHeight local g=Z.FootTarget:lerp(d.FootTarget,.5)local H=((p.CFrame*Z.Hip.C0).p+E)+Vector3.new(0,3,0)local b=((p.CFrame*d.Hip.C0).p+E)+Vector3.new(0,3,0)local q=Ray.new(H,(c*o.HipHeight)*3)local S=Ray.new(b,(c*o.HipHeight)*3)local r,v,T=workspace:FindPartOnRayWithWhitelist(q,h)local t,Q,N=workspace:FindPartOnRayWithWhitelist(S,h)local j=(a.PrimaryPart.Position*Vector3.new(1,0,1)-v*Vector3.new(1,0,1)).Magnitude/W.Magnitude local s=(a.PrimaryPart.Position*Vector3.new(1,0,1)-Q*Vector3.new(1,0,1)).Magnitude/W.Magnitude j=math.clamp(j,0,W.Magnitude*o.HipHeight+.3)s=math.clamp(s,0,W.Magnitude*o.HipHeight+.3)local J=p.CFrame*CFrame.new(0,0,-1)local L=W.Unit:Dot((g-J.p).Unit)if W.Magnitude<1 then if(Z.FootTarget-v).Magnitude>.7 then X(Z,v,.2*(o.HipHeight/5))end if(d.FootTarget-Q).Magnitude>.7 then X(d,Q,.2*(o.HipHeight/5))end elseif not w.LeftForward then if d.Planted and(Z.Planted and math.abs(L)>=0)then X(Z,v,s/2)w.LeftForward=true end else if Z.Planted and(d.Planted and math.abs(L)>=0)then X(d,Q,j/2)w.LeftForward=false end end end function g.step(d,W,Z)local a=d.Legs.Left local o=d.Legs.Right local c=d.Rig if not c then return end local h=d.Root local g=c:FindFirstChild("\084\111\114\115\111")if not h then return end if not g then return end local X=((UserSettings()):GetService("\085\115\101\114\071\097\109\101\083\101\116\116\105\110\103\115")).SavedQualityLevel.Value local b=E[X]or 100 local q=(h.Position-w.CurrentCamera.CFrame.p).Magnitude if q>b then return end local S=d.Humanoid local r=h.Velocity*Vector3.new(1,0,1)local v=r.Magnitude local T=S.FloorMaterial~=Enum.Material.Air if T then d:walkStep(r)local W=tick()-a.StepStartTick local w=tick()-o.StepStartTick if W<a.StepTime then local w=a.StepStart:Lerp(a.FootTarget,.5)a.FootPos=H(a.StepStart,w+Vector3.new(0,S.HipHeight*2,0),a.FootTarget,W/a.StepTime)else a.FootPos=a.FootTarget a.Planted=true end if w<o.StepTime then local W=o.StepStart:Lerp(o.FootTarget,.5)o.FootPos=H(o.StepStart,W+Vector3.new(0,S.HipHeight*2,0),o.FootTarget,w/o.StepTime)else o.FootPos=o.FootTarget o.Planted=true end else a.FootPos=a.FootPos:Lerp((g.CFrame*CFrame.new(-0.5,-2.5,.6)).p,.7)o.FootPos=o.FootPos:Lerp((g.CFrame*CFrame.new(.5,-2.5,.6)).p,.7)a.StepStartTick=tick()o.StepStartTick=tick()a.StepStart=a.FootPos o.StepStart=o.FootPos end local t=g.CFrame*a.OrigHipC0 local Q,N,j=p(t,a.FootPos,c.UpperLeftLeg.Size.Y-.3,c.LowerLeftLeg.Size.Y)a.Hip.C0=g.CFrame:toObjectSpace(Q)*CFrame.Angles(N,0,0)a.Knee.C0=a.OrigKneeC0*CFrame.Angles(j,0,0)local s=g.CFrame*o.OrigHipC0 local J,L,k=p(s,o.FootPos,c.UpperRightLeg.Size.Y-.3,c.LowerRightLeg.Size.Y)o.Hip.C0=g.CFrame:toObjectSpace(J)*CFrame.Angles(L,0,0)o.Knee.C0=o.OrigKneeC0*CFrame.Angles(k,0,0)local K=CFrame.fromAxisAngle(Vector3.new(0,0,1),h.RotVelocity.Y/8)K=K*CFrame.fromAxisAngle(Vector3.new(1,0,0),v/(S.WalkSpeed*4))local M=math.sin((tick()*5)%(math.pi*2))if T then K=K*CFrame.fromAxisAngle(Vector3.new(0,1,0),M*(v/(S.WalkSpeed*6)))end K=K*CFrame.new(0,((M*M)*-0.33)*((v+3)/S.WalkSpeed),0)if g:FindFirstChild("\082\111\111\116")then g.Root.C0=g.Root.C0:lerp(K,.08)end end return g
-- Todo: Refactor E V E R Y T H A N G

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local common = ReplicatedStorage:WaitForChild("common")
local object = common:WaitForChild("object")
local util = common:WaitForChild("util")

local solveIK = require(util:WaitForChild("solveIK"))
local Leg = require(object:WaitForChild("Leg"))

local DOWN = Vector3.new(0,-1,0)

local WHITELIST = {
	Workspace:WaitForChild("staticworld")
}

local QUALITY_DISTS = {
	[0] = 150,
	[1] = 100,
	[2] = 100,
	[3] = 100,
	[4] = 200,
	[5] = 200,
	[6] = 200,
	[7] = 300,
	[8] = 300,
	[9] = 1000,
	[10] = 1000,
}

local LegAnimator = {}

-- TODO: Move this somewhere else.
local function bezierSolve(V1,V2,V3,a)
	return V1:lerp(V2,a):lerp(V3,a)
end

function LegAnimator.new(rig)

	local UpperRightLeg = rig:WaitForChild("UpperRightLeg")
	local UpperLeftLeg = rig:WaitForChild("UpperLeftLeg")
	local LowerRightLeg = rig:WaitForChild("LowerRightLeg")
	local LowerLeftLeg = rig:WaitForChild("LowerLeftLeg")

	local RightHip = UpperRightLeg:WaitForChild("RightHip")
	local LeftHip = UpperLeftLeg:WaitForChild("LeftHip")

	local RightKnee = LowerRightLeg:WaitForChild("RightKnee")
	local LeftKnee = LowerLeftLeg:WaitForChild("LeftKnee")

	local self = setmetatable({

		Rig = rig,
		Root = rig:FindFirstChild("HumanoidRootPart"),
		Humanoid = rig:FindFirstChild("Humanoid"),

		LastStepped = nil,

		Legs = {
			Right = Leg.new(rig,RightHip,RightKnee),
			Left = Leg.new(rig,LeftHip,LeftKnee),
		},

	}, {__index = LegAnimator})

	self.Legs.Right:setOtherLeg(self.Legs.Left)
	self.Legs.Left:setOtherLeg(self.Legs.Right)

	return self
end

function LegAnimator:destroy()
end

local function setupFootTween(foot,target,stepTime)
	foot.StepStartTick = tick()
	foot.StepStart = foot.FootPos
	foot.FootTarget = target
	foot.StepTime = stepTime
	foot.Planted = false
end

function LegAnimator:walkStep(XZVel)

	local LeftLeg = self.Legs.Left
	local RightLeg = self.Legs.Right

	local Rig = self.Rig
	local Root = self.Root
	local Humanoid = self.Humanoid

	local StepVec = (((XZVel*2)/Humanoid.WalkSpeed) * Humanoid.HipHeight)
	local AvgFootPosition = LeftLeg.FootTarget:lerp(RightLeg.FootTarget,0.5)
	local LeftFootRayOrigin = (Root.CFrame * LeftLeg.Hip.C0).p + StepVec + Vector3.new(0,3,0)
	local RightFootRayOrigin = (Root.CFrame * RightLeg.Hip.C0).p + StepVec + Vector3.new(0,3,0)

	local LeftFootRay = Ray.new(LeftFootRayOrigin,DOWN*Humanoid.HipHeight*3)
	local RightFootRay = Ray.new(RightFootRayOrigin,DOWN*Humanoid.HipHeight*3)

	local _,LeftRayPos,_ = workspace:FindPartOnRayWithWhitelist(LeftFootRay,WHITELIST)
	local _,RightRayPos,_ = workspace:FindPartOnRayWithWhitelist(RightFootRay,WHITELIST)

	local TimeToReachLeftRayPos = (Rig.PrimaryPart.Position * Vector3.new(1,0,1) - LeftRayPos * Vector3.new(1,0,1)).Magnitude / XZVel.Magnitude
	local TimeToReachRightRayPos = (Rig.PrimaryPart.Position * Vector3.new(1,0,1) - RightRayPos * Vector3.new(1,0,1)).Magnitude / XZVel.Magnitude

	TimeToReachLeftRayPos = math.clamp(TimeToReachLeftRayPos,0,XZVel.Magnitude*Humanoid.HipHeight + 0.3)
	TimeToReachRightRayPos = math.clamp(TimeToReachRightRayPos,0,XZVel.Magnitude*Humanoid.HipHeight + 0.3)

	local Infront = Root.CFrame * CFrame.new(0,0,-1)
	local AvgAngle = XZVel.Unit:Dot((AvgFootPosition - (Infront.p)).Unit)

	if (XZVel.Magnitude < 1) then
		if (LeftLeg.FootTarget - LeftRayPos).Magnitude > 0.7 then
			setupFootTween(LeftLeg,LeftRayPos,0.2*(Humanoid.HipHeight/5))
		end
		if (RightLeg.FootTarget - RightRayPos).Magnitude > 0.7 then
			setupFootTween(RightLeg,RightRayPos,0.2*(Humanoid.HipHeight/5))
		end
	elseif not self.LeftForward then
		if (RightLeg.Planted and LeftLeg.Planted and math.abs(AvgAngle) >= 0) then
			setupFootTween(LeftLeg,LeftRayPos,TimeToReachRightRayPos/2)
			self.LeftForward = true
		end
	else
		if (LeftLeg.Planted and RightLeg.Planted and math.abs(AvgAngle) >= 0) then
			setupFootTween(RightLeg,RightRayPos,TimeToReachLeftRayPos/2)
			self.LeftForward = false
		end
	end
end

function LegAnimator:step(et,dt)
	-- Do the stuff!
	local LeftLeg = self.Legs.Left
	local RightLeg = self.Legs.Right

	local Rig = self.Rig
	if not Rig then return end -- gross
	local Root = self.Root
	local Torso = Rig:FindFirstChild("Torso")
	if not Root then return end
	if not Torso then return end

	local quality = UserSettings():GetService("UserGameSettings").SavedQualityLevel.Value
	local thresholdDist = QUALITY_DISTS[quality] or 100
	local dist = (Root.Position - Workspace.CurrentCamera.CFrame.p).Magnitude
	if dist > thresholdDist then
		return
	end

	local Humanoid = self.Humanoid

	local XZVel = (Root.Velocity*Vector3.new(1,0,1))
	local XZSpeed = XZVel.Magnitude

	local IsGrounded = (Humanoid.FloorMaterial ~= Enum.Material.Air)

	if IsGrounded then
		self:walkStep(XZVel)
		-- Tween feet to desired positions
		local LeftLegT = tick() - LeftLeg.StepStartTick
		local RightLegT = tick() - RightLeg.StepStartTick
		if LeftLegT < LeftLeg.StepTime then
			local MidPoint = LeftLeg.StepStart:Lerp(LeftLeg.FootTarget,0.5)
			LeftLeg.FootPos = bezierSolve(LeftLeg.StepStart,MidPoint + Vector3.new(0,Humanoid.HipHeight*2,0),LeftLeg.FootTarget,LeftLegT/LeftLeg.StepTime)
		else
			LeftLeg.FootPos = LeftLeg.FootTarget
			LeftLeg.Planted = true
		end

		if RightLegT < RightLeg.StepTime then
			local MidPoint = RightLeg.StepStart:Lerp(RightLeg.FootTarget,0.5)
			RightLeg.FootPos = bezierSolve(RightLeg.StepStart,MidPoint + Vector3.new(0,Humanoid.HipHeight*2,0),RightLeg.FootTarget,RightLegT/RightLeg.StepTime)
		else
			RightLeg.FootPos = RightLeg.FootTarget
			RightLeg.Planted = true
		end
	else
		LeftLeg.FootPos = LeftLeg.FootPos:Lerp((Torso.CFrame * CFrame.new(-0.5,-2.5,0.6)).p,0.7)
		RightLeg.FootPos = RightLeg.FootPos:Lerp((Torso.CFrame * CFrame.new(0.5,-2.5,0.6)).p,0.7)

		LeftLeg.StepStartTick = tick()
		RightLeg.StepStartTick = tick()

		LeftLeg.StepStart = LeftLeg.FootPos
		RightLeg.StepStart = RightLeg.FootPos
	end

	local LeftHipCF = Torso.CFrame * LeftLeg.OrigHipC0
	local LeftHipPlane, LeftHipAngle, LeftKneeAngle = solveIK(
		LeftHipCF, LeftLeg.FootPos,
		Rig.UpperLeftLeg.Size.Y-0.3, Rig.LowerLeftLeg.Size.Y
	)

	LeftLeg.Hip.C0 = Torso.CFrame:toObjectSpace(LeftHipPlane) * CFrame.Angles(LeftHipAngle, 0, 0)
	LeftLeg.Knee.C0 = LeftLeg.OrigKneeC0 * CFrame.Angles(LeftKneeAngle, 0, 0)

	local RightHipCF = Torso.CFrame * RightLeg.OrigHipC0
	local RightHipPlane, RightHipAngle, RightKneeAngle = solveIK(
		RightHipCF, RightLeg.FootPos,
		Rig.UpperRightLeg.Size.Y-0.3, Rig.LowerRightLeg.Size.Y
	)

	RightLeg.Hip.C0 = Torso.CFrame:toObjectSpace(RightHipPlane) * CFrame.Angles(RightHipAngle, 0, 0)
	RightLeg.Knee.C0 = RightLeg.OrigKneeC0 * CFrame.Angles(RightKneeAngle, 0, 0)


	local TargetC0CF = CFrame.fromAxisAngle(Vector3.new(0,0,1),Root.RotVelocity.Y/8)
	TargetC0CF = TargetC0CF * CFrame.fromAxisAngle(Vector3.new(1,0,0),XZSpeed/(Humanoid.WalkSpeed*4))
	local SinVal = math.sin((tick()*5)%(math.pi*2))
	if IsGrounded then
	TargetC0CF = TargetC0CF * CFrame.fromAxisAngle(Vector3.new(0,1,0),SinVal*(XZSpeed/(Humanoid.WalkSpeed*6)))
	end
	TargetC0CF = TargetC0CF * CFrame.new(0,SinVal*SinVal*-0.33*((XZSpeed+3)/Humanoid.WalkSpeed),0)
	if Torso:FindFirstChild("Root") then
		Torso.Root.C0 = Torso.Root.C0:lerp(TargetC0CF,0.08)
	end
end

return LegAnimator