-- use a remote function for setup to avoid async
remotes.setup.OnServerEvent:Connect(function(player, isR15, weapon)
	local weapon = weapon:Clone();
	
	local character = player.Character;
	local humanoid = character:WaitForChild("Humanoid");
	
	local joint = Instance.new("Motor6D");
	joint.Part0 = character:WaitForChild(isR15 and "RightHand" or "Right Arm");
	joint.Part1 = weapon:WaitForChild("Handle");
	joint.Parent = weapon.Handle;
	
	weapon.Parent = character;
	weapons[player] = require(weapon:WaitForChild("settings"))(isR15);
	
	local tilt = spring.new(0, 0, 0, 10, 1);
	local recoil = spring.new(Vector2.new(), Vector2.new(), Vector2.new(), 20, 1);
	
	springs[player] = {};
	springs[player].tilt = tilt;
	springs[player].recoil = recoil;
	
	-- update the character according to their rig type
	local tiltFunc, recoilFunc;
	if (isR15) then
		local neck = character:WaitForChild("Head"):WaitForChild("Neck");
		local waist = character:WaitForChild("UpperTorso"):WaitForChild("Waist");
		local rShoulder = character:WaitForChild("RightUpperArm"):WaitForChild("RightShoulder");
		local lShoulder = character:WaitForChild("LeftUpperArm"):WaitForChild("LeftShoulder");
		
		local neckC0 = neck.C0;
		local waistC0 = waist.C0;
		local rShoulderC0 = rShoulder.C0;
		local lShoulderC0 = lShoulder.C0;
		
		function tiltFunc(theta)
			neck.C0 = neckC0 * CFrame.fromEulerAnglesYXZ(theta, 0, 0);
			waist.C0 = waistC0 * CFrame.fromEulerAnglesYXZ(theta, 0, 0);
			rShoulder.C0 = rShoulderC0 * CFrame.fromEulerAnglesYXZ(theta, 0, 0);
			lShoulder.C0 = lShoulderC0 * CFrame.fromEulerAnglesYXZ(theta, 0, 0);
		end
		
		function recoilFunc(x, y)
			rShoulder.C0 = rShoulder.C0 * CFrame.new(0, 0, x);
			lShoulder.C0 = lShoulder.C0 * CFrame.new(0, 0, y);
		end
	else
		local torso = character:WaitForChild("Torso");
		local hrp = character:WaitForChild("HumanoidRootPart");
		
		local neck = torso:WaitForChild("Neck");
		local root = hrp:WaitForChild("RootJoint");
		local rShoulder = torso:WaitForChild("Right Shoulder");
		local lShoulder = torso:WaitForChild("Left Shoulder");
		local rHip = torso:WaitForChild("Right Hip");
		local lHip = torso:WaitForChild("Left Hip");
		
		local neckC0 = neck.C0;
		local rootC0 = root.C0;
		local rShoulderC0 = rShoulder.C0;
		local lShoulderC0 = lShoulder.C0;
		local rHipC0 = rHip.C0;
		local lHipC0 = lHip.C0;
		
		-- rotating the R6 joints is so arbitrary
		function tiltFunc(theta)
			neck.C0 = neckC0 * CFrame.fromEulerAnglesYXZ(-theta, 0, 0);
			root.C0 = rootC0 * CFrame.new(0, 0, -1) * CFrame.fromEulerAnglesYXZ(-theta, 0, 0) * CFrame.new(0, 0, 1);
			rShoulder.C0 = rShoulderC0 * CFrame.fromEulerAnglesYXZ(0, 0, theta);
			lShoulder.C0 = lShoulderC0 * CFrame.fromEulerAnglesYXZ(0, 0, -theta);
			rHip.C0 = rHipC0 * CFrame.fromEulerAnglesYXZ(0, 0, -theta);
			lHip.C0 = lHipC0 * CFrame.fromEulerAnglesYXZ(0, 0, theta);
		end
		
		function recoilFunc(x, y)
			rShoulder.C0 = rShoulder.C0 * CFrame.new(-x, 0, 0);
			lShoulder.C0 = lShoulder.C0 * CFrame.new(y, 0, 0);
		end
	end
	
	local heartBeat = runService.Heartbeat:Connect(function(dt)
		tilt:update(dt);
		recoil:update(dt);
		
		tiltFunc(states[humanoid:GetState()] and 0 or tilt.p / 2);
		recoilFunc(recoil.p.x, recoil.p.y);
	end)
	
	-- disconnect on death
	local died; died = player.Character.Humanoid.Died:Connect(function()
		heartBeat:Disconnect();
		died:Disconnect();
	end)
	
	-- store this for later
	cleanup[player] = {
		tiltFunc;
		heartBeat;
		died;
		weapon;
		joint;
		recoilFunc;
	}
end)

remotes.cleanup.OnServerEvent:Connect(function(player)
	local clean = cleanup[player];
	if (clean) then
		clean[2]:Disconnect();
		clean[1](0);
		clean[3]:Disconnect();
		clean[4]:Destroy();
		clean[5]:Destroy();
		clean[6](0, 0);
	end
end)