--!native


local UserInput = game:GetService("UserInputService")
local ModelLoader = require(game.ReplicatedStorage.ModelLoader)
local WeldModule = require(game.ReplicatedStorage.Weld)

function KeysToStrings(Objects)
	local Temp = {}

	for i,v in pairs(Objects) do
		table.insert(Temp, v.KeyCode.Name)
	end

	return Temp
end

function AreKeysDown(tbl)
	task.synchronize()
	local Count = 0

	for i,Key in pairs(tbl) do
		if UserInput:IsKeyDown(Enum.KeyCode[Key]) then
			Count+=1
		end
	end
	
	task.desynchronize()

	if Count > 0 then
		return true
	else
		return false
	end
end

function V3Clamp(vector, min, max)
	return Vector3.new(math.clamp(vector.X, min, max), math.clamp(vector.Y, min, max), math.clamp(vector.Z, min, max))
end

local Players = {}

function MakeCharacter()
	local Character = {}

	local Head = ModelLoader.LoadFromName("Cube")
	Head.Size = Vector3.new(2,1,1)
	Head:SetColor(255,255,0)
	Head.Name = "Head"
	Head.CanCollide = false
	Head.Anchored = false
	Character.Head = Head

	local Root = ModelLoader.LoadFromName("Cube")
	Root.Size = Vector3.new(2,2,1)
	Root:SetColor(165,165,165)
	Root.Rendered = true
	Root.CFrame = CFrame.new(0,3,0)
	Root.Name = "HumanoidRootPart"
	Root.PhysicsMover.Offset = Vector3.new(0,-2,0)
	Root.Anchored = false
	Character.HumanoidRootPart = Root

	local Torso = ModelLoader.LoadFromName("Cube")
	Torso.Size = Vector3.new(2,2,1)
	Torso:SetColor(0,165,255)
	Torso.Name = "Torso"
	Torso.CanCollide = false
	Torso.Anchored = false
	Character.Torso = Torso

	local RightArm = ModelLoader.LoadFromName("Cube")
	RightArm.Size = Vector3.new(1,2,1)
	RightArm:SetColor(255,255,0)
	RightArm.Name = "Right Arm"
	RightArm.CanCollide = false
	RightArm.Anchored = false
	Character["Right Arm"] = RightArm

	local LeftArm = ModelLoader.LoadFromName("Cube")
	LeftArm.Size = Vector3.new(1,2,1)
	LeftArm:SetColor(255,255,0)
	LeftArm.Name = "Left Arm"
	LeftArm.CanCollide = false
	LeftArm.Anchored = false
	Character["Left Arm"] = LeftArm

	local RightLeg = ModelLoader.LoadFromName("Cube")
	RightLeg.Size = Vector3.new(1,2,1)
	RightLeg:SetColor(0,165,0)
	RightLeg.Name = "Right Leg"
	RightLeg.CanCollide = false
	RightLeg.Anchored = false
	Character["Right Leg"] = RightLeg

	local LeftLeg = ModelLoader.LoadFromName("Cube")
	LeftLeg.Size = Vector3.new(1,2,1)
	LeftLeg:SetColor(0,165,0)
	LeftLeg.Name = "Left Leg"
	LeftLeg.CanCollide = false
	LeftLeg.Anchored = false
	Character["Left Leg"] = LeftLeg

	Character.PrimaryPart = Character.HumanoidRootPart
	
	local RootJoint = WeldModule.new("RootJoint", Root, Torso, CFrame.new(0,0,0), CFrame.new(0,0,0))
	local Neck = WeldModule.new("Neck", Torso, Head, CFrame.new(0, 1, 0), CFrame.new(0, -0.5, 0))
	local RightShoulder = WeldModule.new("RightShoulder", Torso, RightArm, CFrame.new(1, 0.5, 0), CFrame.new(-0.5, 0.5, 0))
	local LeftShoulder = WeldModule.new("LeftShoulder", Torso, LeftArm, CFrame.new(-1, 0.5, 0), CFrame.new(0.5, 0.5, 0))
	local RightHip = WeldModule.new("RightHip", Torso, RightLeg, CFrame.new(1, -1, 0), CFrame.new(0.5, 1, 0))
	local LeftHip = WeldModule.new("LeftHip", Torso, LeftLeg, CFrame.new(-1, -1, 0), CFrame.new(-0.5, 1, 0))
	
	Character.HumanoidRootPart.RootJoint = RootJoint
	Character.Torso.Neck = Neck
	Character.Torso["Right Shoulder"] = RightShoulder
	Character.Torso["Left Shoulder"] = LeftShoulder
	Character.Torso["Right Hip"] = RightHip
	Character.Torso["Left Hip"] = LeftHip

	Character.BaseWelds = WeldModule:GetBaseWelds()

	return Character
end

function Players.new(Camera, Speed)
	local Player = {}

	Player.Character = MakeCharacter()
	Player.CurrentCamera = Camera
	Player.CameraSubject = Player.Character.PrimaryPart
	Player.CameraOffset = Vector3.new(0,5,7)
	Player.CameraRot = Vector3.new(0,0,0)
	Player.Speed = Speed
	Player.Walking = false
	Player.Jump = true
	Player.OnGround = true
	Player.Animation = "Idle"
	Player.Sine = 0

	function Player:PlayAnimation(Name)
		local RightShoulder = Player.Character.Torso["Right Shoulder"]
		local LeftShoulder = Player.Character.Torso["Left Shoulder"]
		local RightHip = Player.Character.Torso["Right Hip"]
		local LeftHip = Player.Character.Torso["Left Hip"]

		if Name == "Walk" then
			task.spawn(function()
				if Player.Animation ~= Name then
					Player.Animation = Name

					while Player.Animation == Name do
						RightShoulder.C0 = Player.Character.BaseWelds.RightShoulder.C0 * CFrame.Angles(math.rad(math.sin(Player.Sine/6)*40), 0, 0)
						LeftShoulder.C0 = Player.Character.BaseWelds.LeftShoulder.C0 * CFrame.Angles(-math.rad(math.sin(Player.Sine/6)*40), 0, 0)
						RightHip.C0 = Player.Character.BaseWelds.RightHip.C0 * CFrame.Angles(-math.rad(math.sin(Player.Sine/6)*40), 0, 0)
						LeftHip.C0 = Player.Character.BaseWelds.LeftHip.C0 * CFrame.Angles(math.rad(math.sin(Player.Sine/6)*40), 0, 0)

						Player.Sine+=1
						task.wait()
					end

					Player.Animation = "Idle"
				end
			end)
		elseif Name == "Idle" then
			Player.Animation = "Idle"

			RightShoulder.C0 = Player.Character.BaseWelds.RightShoulder.C0
			LeftShoulder.C0 = Player.Character.BaseWelds.LeftShoulder.C0
			RightHip.C0 = Player.Character.BaseWelds.RightHip.C0
			LeftHip.C0 = Player.Character.BaseWelds.LeftHip.C0

			Player.Sine = 0
		elseif Name == "Jump" then
			Player.Animation = "Jump"
			
			RightShoulder.C0 = Player.Character.BaseWelds.RightShoulder.C0 * CFrame.Angles(math.rad(180), 0, 0)
			LeftShoulder.C0 = Player.Character.BaseWelds.LeftShoulder.C0 * CFrame.Angles(math.rad(180), 0, 0)
			RightHip.C0 = Player.Character.BaseWelds.RightHip.C0
			LeftHip.C0 = Player.Character.BaseWelds.LeftHip.C0
		end
	end

	function Player:Movement()
		task.synchronize()
		local PressedKeys = KeysToStrings(UserInput:GetKeysPressed())
		local MouseButton = UserInput:GetMouseButtonsPressed()
		task.desynchronize()

		local WalkKeys = {"W", "A", "S", "D"}
		local CameraKeys = {"Left", "Right", "Up", "Down"}
		
		for i,Key in pairs(WalkKeys) do
			task.synchronize()
			if UserInput:IsKeyDown(Enum.KeyCode[Key]) then
				if Key == "W" then
					Player.Character.PrimaryPart.CFrame = CFrame.new(Player.Character.PrimaryPart.Position + (Player.Character.PrimaryPart.Velocity)/Player.Character.PrimaryPart.Mass) * CFrame.Angles(0, math.rad(Player.CameraRot.Y), 0) --* CFrame.new(0, 0, -Speed)
					Player.Character.PrimaryPart.Velocity = Vector3.new(0, Player.Character.PrimaryPart.Velocity.Y, 0)
					Player.Character.PrimaryPart.Velocity += Player.Character.PrimaryPart.CFrame.LookVector*30
				elseif Key == "S" then
					Player.Character.PrimaryPart.CFrame = CFrame.new(Player.Character.PrimaryPart.Position + (Player.Character.PrimaryPart.Velocity)/Player.Character.PrimaryPart.Mass) * CFrame.Angles(0, math.rad(180 + Player.CameraRot.Y), 0)
					Player.Character.PrimaryPart.Velocity = Vector3.new(0, Player.Character.PrimaryPart.Velocity.Y, 0)
					Player.Character.PrimaryPart.Velocity += Player.Character.PrimaryPart.CFrame.LookVector*30
				elseif Key == "A" then
					Player.Character.PrimaryPart.CFrame = CFrame.new(Player.Character.PrimaryPart.Position + (Player.Character.PrimaryPart.Velocity)/Player.Character.PrimaryPart.Mass) * CFrame.Angles(0, math.rad(90 + Player.CameraRot.Y), 0)
					Player.Character.PrimaryPart.Velocity = Vector3.new(0, Player.Character.PrimaryPart.Velocity.Y, 0)
					Player.Character.PrimaryPart.Velocity += Player.Character.PrimaryPart.CFrame.LookVector*30
				elseif Key == "D" then
					Player.Character.PrimaryPart.CFrame = CFrame.new(Player.Character.PrimaryPart.Position + (Player.Character.PrimaryPart.Velocity)/Player.Character.PrimaryPart.Mass) * CFrame.Angles(0, math.rad(-90 + Player.CameraRot.Y), 0)
					Player.Character.PrimaryPart.Velocity = Vector3.new(0, Player.Character.PrimaryPart.Velocity.Y, 0)
					Player.Character.PrimaryPart.Velocity += Player.Character.PrimaryPart.CFrame.LookVector*30
				end

				Player.Walking = true
				
				if Player.Animation ~= "Jump" then
					Player:PlayAnimation("Walk")
				end
			end
			task.desynchronize()
		end
		
		task.synchronize()
		if (not AreKeysDown(WalkKeys)) and (Player.Walking == true) then
			Player.Walking = false
			Player.Character.PrimaryPart.Velocity = Vector3.new(0, Player.Character.PrimaryPart.Velocity.Y, 0)
			Player:PlayAnimation("Idle")
		end
		task.desynchronize()

		for i,Key in pairs(CameraKeys) do
			task.synchronize()
			if UserInput:IsKeyDown(Enum.KeyCode[Key]) then
				if Key == "Right" then
					Player.CameraRot -= Vector3.new(0, 2, 0)
				elseif Key == "Left" then
					Player.CameraRot += Vector3.new(0, 2, 0)
				elseif Key == "Up" then
					Player.CameraRot += Vector3.new(2, 0, 0)
				elseif Key == "Down" then
					Player.CameraRot -= Vector3.new(2, 0, 0)
				end
				
				if Player.CameraRot.X > 125 then
					Player.CameraRot = Vector3.new(125, Player.CameraRot.Y, 0)
				elseif Player.CameraRot.X < -50 then
					Player.CameraRot = Vector3.new(-50, Player.CameraRot.Y, 0)
				end
			end
			task.desynchronize()
		end
		
		if Player.Character.PrimaryPart.PhysicsMover.CollidedNormal == Vector3.new(0,1,0) then
			Player.OnGround = true
			
			if Player.Animation ~= "Walk" then
				Player:PlayAnimation("Idle")
			end
		else
			Player.OnGround = false
			Player:PlayAnimation("Jump")
		end
		
		task.synchronize()
		if UserInput:IsKeyDown(Enum.KeyCode.Space) then
			if Player.OnGround == true and Player.Jump == true then
				Player.Jump = false
				Player.Character.PrimaryPart.Velocity += Vector3.new(0, 50, 0)
				
				task.spawn(function()
					task.wait(0.1)
					Player.Jump = true
				end)
			end
		end
		task.desynchronize()
	end

	function Player:Update()
		Player.CurrentCamera.CFrame = CFrame.new((CFrame.new(Player.CameraSubject.Position) * CFrame.Angles(0, math.rad(Player.CameraRot.Y), 0) * CFrame.Angles(math.rad(Player.CameraRot.X), 0, 0) * CFrame.new(Player.CameraOffset)).Position, Player.CameraSubject.Position)
		Player.CurrentCamera:Update(false)
		Player:Movement()
		WeldModule:Update()
	end

	return Player
end

return Players