How to fix Player Flinging from NPCs

Hello!

I am trying to make a game similar to Decaying Winter, and something that I cannot seem to get right are the NPCs. So many games have NPCs that follow the player around to attack them, but for some reason mine always fling the player. I’ve tried setting parts to massless, creating a custom hitbox, changing Collisions in the CollisionGroup Editor, but nothing I try seems to work.

Here is the video for a demonstration to what I am trying to avoid:

I still need the NPCs to be able to collide with each other and the player, so if anyone has any suggestions, it’d be much appreciated.

Here is my NPC code down below, if it’ll help at all:

local Lib = require(game.ReplicatedStorage.Library)
local MyHumanoid:Humanoid = script.Parent
local MyCharacterModel:Model = MyHumanoid.Parent
local PathfindingService = game:GetService("PathfindingService")
local MyEnums = require(game.ReplicatedStorage.ModuleScripts.Enums)
local RunService = game:GetService('RunService')

-- first, generate all the necessary values
local NpcConfigFolder = MyHumanoid:FindFirstChild("NpcValues") or Lib.ObjectFuncs.ShorthandNew("Configuration",MyHumanoid,"NpcValues")
local MyBehaviorState:StringValue = MyHumanoid.States:FindFirstChild("BehaviorState") or Lib.ObjectFuncs.ShorthandNew("StringValue",MyHumanoid.States,"MyBehviorState",{["Value"] = "Idle"})
local SeeDistance:NumberValue = MyHumanoid.NpcValues.SeeDistance
local FeelDistance:NumberValue = MyHumanoid.NpcValues.FeelDistance
local ArmState:StringValue = MyHumanoid.States.ArmState
local LegState:StringValue = MyHumanoid.States.LegState
local MyTargetHumanoid:ObjectValue = MyBehaviorState.TargetHumanoid
local HumanoidsOfInterest:{[Humanoid]: boolean} = {} -- if there are more than 1 enemies inside the AggroRange, they'll show up here

-- Now, do the more Runtime specific variables
local TargetLastKnownLocation:Vector3 = Vector3.zero
local CurrentTargetBodyParts:{Part|MeshPart} = {}
local CurrentMovementThreadId = 0 -- used to stop more than one pathfinding thread from running at once.

-- Aggro state has a couple values:
-- Idle       (basically perfectly still)
-- Idle Wandering     (Passive, not found target, just wandering to a nearby randomly chosen location)
-- Investigating      (Heard gunshot, or Flare trap activated, now moving to the source of the disturbance)
-- Searching      (target found, then lost, now moving towards their last known location)
-- AggressivePath      (target found, now actively moving towards their location. However, complex pathing required)
-- AggressiveDirect    (target found, now actively moving towards their location. Simple path, just a straight line)

function CheckForClosestVisibleEnemy():Humanoid
	
	
	--if #HumanoidsOfInterest == 0 then return nil end
	
	-- first, purge the Humanoids from the list that don't exist anymore
	--remove those who don't exist, are dead, or are too far away
	local OrderedHumanoidList:{Humanoid} = {}
	for i,v in pairs(HumanoidsOfInterest) do
		if v ~= true then HumanoidsOfInterest[i] = nil end
		if i then
			if i.Health <= 0 then -- is dead
				HumanoidsOfInterest[i] = nil
			else
				if (i.Parent.PrimaryPart.Position - MyCharacterModel.PrimaryPart.Position).Magnitude > SeeDistance.Value then
					-- is too far away
					HumanoidsOfInterest[i] = nil
				else
					-- a valid humanoid to want to investigate
					OrderedHumanoidList[#OrderedHumanoidList + 1] = i
				end
			end
		else -- doesn't exist
			HumanoidsOfInterest[i] = nil
		end
	end
	
	-- then, sort through the remaining humanoids
	-- table.sort moves smallest to first in line, so default to smaller < larger
	table.sort(OrderedHumanoidList,function(a:Humanoid,b:Humanoid)
		return (MyCharacterModel.PrimaryPart.Position - a.Parent.PrimaryPart.Position).Magnitude < (MyCharacterModel.PrimaryPart.Position - a.Parent.PrimaryPart.Position).Magnitude
	end)
	
	-- now, raycast to see if the target is actually visible
	local MyBodyParts = Lib.ObjectFuncs.FilterTable(MyCharacterModel:GetDescendants(),{"Part","MeshPart","Union"})
	for i,v in pairs(OrderedHumanoidList) do
		local TargetPart:Part = v.Parent.PrimaryPart
		
		local NewRaycast = Lib.ObjectFuncs.ShorthandRaycast(MyCharacterModel.PrimaryPart.Position,(TargetPart.Position - MyCharacterModel.PrimaryPart.Position).Unit * SeeDistance.Value,MyBodyParts,MyEnums.CollisionGroups.Eyesight)
		if NewRaycast.Instance then
			-- actually hit something, otherwise the enemy is too far, but.. they should have been filtered out so... idk
			if NewRaycast.Instance.Parent == TargetPart.Parent then
				-- first thing hit was the target, meaning they are in sight! Otherwise, loop to next item
				return v
			end
		end
		
	end
	
	-- none found, so just return nothin'
	return nil
end

function CreateAggroBoundary()
	local NewPart:Part = Lib.ObjectFuncs.ShorthandNew("Part",MyCharacterModel,"SightAggroSphere",{
		["Size"] = Vector3.new(SeeDistance.Value,SeeDistance.Value,SeeDistance.Value);
		["Shape"] = Enum.PartType.Ball;
		["CanCollide"] = false;
		["CanTouch"] = true;
		["CollisionGroup"] = Lib.Enums.CollisionGroups.Eyesight;
		["Transparency"] = 0.7;
		["Anchored"] = false;
		["Position"] = MyCharacterModel.PrimaryPart.Position;
	})
	local NewWeld:Motor6D= Lib.ObjectFuncs.ShorthandNew("Motor6D",NewPart,"AggroSphereWeld",{
		["Part0"] = MyCharacterModel.PrimaryPart;
		["Part1"] = NewPart;
		--["C1"] = CFrame.new(0,0,-SeeDistance.Value);
	})

	return NewPart
end

function SetupAi(AggroSphere:Part)
	-- first, add all the current CharacterParts to the BlacklistedParts list
	local BlacklistedParts:{Part|MeshPart} = {} --Lib.ObjectFuncs.FilterTable(MyCharacterModel:GetDescendants(),{"Part","MeshPart","Union"})
	for i,v in pairs(Lib.ObjectFuncs.FilterTable(MyCharacterModel:GetDescendants(),{"Part","MeshPart","Union"})) do BlacklistedParts[v] = true end
	-- setup the Touched event
	AggroSphere.Touched:Connect(function(OtherPart)
		-- first, see if this part is already blacklisted
		
		if BlacklistedParts[OtherPart] then return end
		-- Second, now that this is a new part, see if they are of a different faction
		local OtherHum:Humanoid = OtherPart.Parent:FindFirstChild("Humanoid")
		if not OtherHum then BlacklistedParts[OtherPart] = true; return end
		if HumanoidsOfInterest[OtherPart] then return end -- already investigating this humanoid
		
		if Lib.ObjectFuncs.SafelyGetPropertyFromChild(OtherHum,"Faction","Value") == MyHumanoid.Faction.Value then
			BlacklistedParts[OtherPart] = true; return
		end
		
		
		-- now, check to see if this body part is a part of the humanoid I am already hunting
		if CurrentTargetBodyParts[OtherPart] then return end

		-- ok, we have determined that this is an enemy humanoid that we are NOT currently targeting
		-- Later, add in a check to see if I already am Aggroed and have a target I'm going towards. If I do,
		-- then ask if this new enemy is closer than I am. If not, well, aggro onto this new person, then
		if OtherHum.Health <= 0 then return end
		
		-- otherwise, this is a valid enemy. Add it to the Investigation List
		HumanoidsOfInterest[OtherHum] = true
		
		
		-- Ok so, we DEFINITELY have our target. Now, we need to begin moving towards them
		-- first, add all parts to CurrentTargetBodyParts
		CurrentTargetBodyParts = {}
		for i,v in pairs(Lib.ObjectFuncs.FilterTable(OtherHum.Parent:GetDescendants(),{"Part","MeshPart","Union"})) do CurrentTargetBodyParts[v] = true end
		
	end)
end


function StartNewMovementThread(SkipDirectMoveToEnemy:boolean)
	CurrentMovementThreadId += 1
	local MyThreadId = tonumber(CurrentMovementThreadId)
	MyBehaviorState.Value = Lib.Enums.EnemyBehaviorState.Aggressive_Direct
	-- if at any point, CurrentMovementThreadId ~= MyThreadId, stop this function thread at once
	
	print("Starting new Thread! " ..tostring(MyThreadId))
	
	local MyTargetPart:Part = MyTargetHumanoid.Value.Parent.PrimaryPart
	
	local CurrPath = PathfindingService:CreatePath({})
	local MyTargetPart:Part = MyTargetHumanoid.Value.Parent.PrimaryPart
	
	while CurrentMovementThreadId == MyThreadId do
		-- start moving towards them
		
		-- start by just moving towards them using hum:MoveTo(Pos,Part)
		-- and repeat that every 3 seconds, asking if the enemy can see them still. If not,
		-- then move them to the enemy's last known location using a path.
		-- Every time they reach a new waypoint, find out if the ClosestVisibleEnemy has changed
		
		-- So, loop just began, so we know that the ClosestVisibleEnemy is MyTargetHumanoid
		-- first, just begin walking to them, until we lose sight of them
		if not SkipDirectMoveToEnemy then
			local ClosestEnemy = MyTargetHumanoid.Value
			repeat
				-- do this every 0.1 seconds for 1.5 seconds
				local MiniChaseStartTime = time()
				while time() - MiniChaseStartTime <= 1.5 do
					-- moves towards the enemy, backed up by 1 unit, + their velocity by 1 unit
					MyHumanoid:MoveTo(MyTargetPart.Position - (MyTargetPart.Position - MyCharacterModel.PrimaryPart.Position).Unit)
					--MyHumanoid:MoveTo(MyTargetPart.Position - (MyTargetPart.Position - MyCharacterModel.PrimaryPart.Position).Unit + (MyTargetPart.AssemblyLinearVelocity).Unit)
					task.wait(0.02)
				end
				
				
				ClosestEnemy = CheckForClosestVisibleEnemy()
			until ClosestEnemy ~= MyTargetHumanoid.Value

			-- now, the closest visible enemy is someone else
			-- first, if the Closest visible enemy is someone else, start a new thread
			if ClosestEnemy ~= nil then
				-- target someone else
				MyTargetHumanoid.Value = ClosestEnemy
				script.StartNewMovementThread:Fire()
				return
			end

			-- current target is lost, and there's no one else, so investigate by going to their last known location
			-- first, make sure their still alive. If they're not, swap to idle
			if Lib.ObjectFuncs.SafelyGetProperty( Lib.ObjectFuncs.SafelyGetProperty(MyTargetHumanoid,"Value",nil),"Health",0) <= 0 then
				-- target is either dead or they don't exist anymore, so swap to idle
				MyHumanoid:MoveTo(MyCharacterModel.PrimaryPart.Position)
				MyBehaviorState.Value = Lib.Enums.EnemyBehaviorState.Idle
				return
			end
		end
		
		-- if we made it this far down, we need to investigate, and our enemy is still alive
		
		MyBehaviorState.Value = Lib.Enums.EnemyBehaviorState.Searching
		TargetLastKnownLocation = MyTargetPart.Position
		
		-- now, begin the complex path towards the last known location
		CurrPath:ComputeAsync(MyCharacterModel.PrimaryPart.Position,TargetLastKnownLocation)
		
		if CurrPath.Status == Enum.PathStatus.Success then
			-- complex path found!
			local Waypoints:{PathWaypoint} = CurrPath:GetWaypoints()
			
			for i=1, #Waypoints,1 do
				local Reached = false
				repeat
					-- in case a new humanoid enemy gets found
					if CurrentMovementThreadId ~= MyThreadId then return end 
					
					MyHumanoid:MoveTo(Waypoints[i].Position)
					Reached = MyHumanoid.MoveToFinished:Wait()
				until Reached
				
				-- waypoint finally reached!
				-- now check to see where the nearest visible enemy is
				-- if it's a different enemy, Create new thread
				-- if it is the same enemy, check to see if they're new position is reachable from last waypoint
					-- if yes, keep going
					-- if no, recaculate path by doing StartNewThread(Yes, please skip Direct Pathing)
				-- if there's no visible enemy, keep following this path
				
				local Closest = CheckForClosestVisibleEnemy()
				if Closest == nil then
					-- Nobody else to switch to, so just keep looping
				elseif Closest ~= MyTargetHumanoid.Value then
					-- change targets
					MyTargetHumanoid.Value = Closest
					script.StartNewMovementThread:Fire()
					return
				elseif Closest == MyTargetHumanoid.Value then
					local MyRay = Lib.ObjectFuncs.ShorthandRaycast(
						Waypoints[#Waypoints].Position,
						(MyTargetPart.Position - Waypoints[#Waypoints].Position).Unit * SeeDistance.Value
						,{MyCharacterModel:GetDescendants()}
						,Lib.Enums.CollisionGroups.Eyesight)
					if MyRay.Instance then
						if MyRay.Instance:IsDescendantOf(MyTargetHumanoid.Parent) then
							-- ray hit enemy, meaning that the path is still usable and we do not need to recalculate
							-- so, just move on to the next waypoint
						else
							-- ray hit something else, like a wall, meaning that the path needs
							-- to be recalculated
							-- so, start a new Thread
							script.StartNewMovementThread:Fire(true)
							return
						end
					else
						-- Nothing hit, meaning Current Target is too far away somehow?
						-- Anyway, I need to recaculate path
						script.StartNewMovementThread:Fire(true)
						return
					end
				end
				
			end
			
		else -- Enemy is unreachable
			MyHumanoid:MoveTo(MyCharacterModel.PrimaryPart.Position)
			MyBehaviorState.Value = Lib.Enums.EnemyBehaviorState.Idle
			return
		end
		
	end
end


-- really great idea.
--[[
	Every time the Enemy gets to a new Waypoint, ask 
	(1) Raycast from MyPos to EnemyHumanoidPos. If it's NOT blocked, update TargetLastKnownLocation
	(2) if I raycast from FinalWaypoint.Pos -> TargetLastKnownLocation, is it blocked?
	(3) If not, continue to the next waypoint and don't recalculate path
	(4) If YES, it IS blocked, recalculate Path

]]

-- If Idle, check every 2 seconds if there is someone inside the AggroSphere. If so,
-- use Raycast from RootPart -> RootPart






-- Now, setup useful connections

local CurrEnemyCheckThread = 0
MyBehaviorState.Changed:Connect(function(newVal)
	if CurrEnemyCheckThread > 1 then return end
	CurrEnemyCheckThread += 1
	-- check for enemies during Idle
	if newVal ~= Lib.Enums.EnemyBehaviorState.Aggressive_Direct and newVal ~= Lib.Enums.EnemyBehaviorState.Aggressive_Path and newVal ~= Lib.Enums.EnemyBehaviorState.Searching then
		while MyBehaviorState.Value ~= Lib.Enums.EnemyBehaviorState.Aggressive_Direct and MyBehaviorState.Value ~= Lib.Enums.EnemyBehaviorState.Aggressive_Path do
			task.wait(2)
			print("checking!")
			if MyBehaviorState.Value ~= Lib.Enums.EnemyBehaviorState.Aggressive_Direct and MyBehaviorState.Value ~= Lib.Enums.EnemyBehaviorState.Aggressive_Path then
				local Closest = CheckForClosestVisibleEnemy()
				print("Closest: " ..tostring(Closest))
				if Closest ~= MyTargetHumanoid.Value and Closest ~= nil then
					MyTargetHumanoid.Value = Closest
					StartNewMovementThread()
				end
			end
		end
	end
	CurrEnemyCheckThread -= 1
end)

script.StartNewMovementThread.Event:Connect(function()
	StartNewMovementThread()
end)

-- now, run the final setup Functions to get the ball rolling
local AggroSphere = CreateAggroBoundary()
SetupAi(AggroSphere)

MyBehaviorState.Value = MyEnums.EnemyBehaviorState.Idle

I have come to realize something - even when the npc is stationary, my character is unable to move them due to some way the AggroSphere is setup under CreateAggroSphere(). For some reason, the Motor6D (formerly a Weld) locks the NPC to the Aggro sphere, even when I swap Part0 and Part1. This is most perplexing, and any help would be greatly appreciated, as I do not know why this keeps happening.