# data[].attributes.attachment_link.readable_text
local ChatService = game:GetService("Chat")
local head = script.Parent.Head
local prompt = head.ProximityPrompt

local chatList = {"Hello, I'm Sensei Matt!", "Hi-Yah!"}

prompt.Triggered:Connect(function()
	ChatService:Chat(head, chatList[math.random(1, #chatList)])
end)

# data[].attributes.body
As shown in the camp.

# data[].attributes.attachment_link.readable_text
local waypoints = game.Workspace.Waypoints 

local npc = script.Parent:WaitForChild("Humanoid") 

  

while true do 

for waypoint = 1, #waypoints:GetChildren() - 1 do 

npc:MoveTo(waypoints[waypoint].Position) 

npc.MoveToFinished:Wait() 

end 

end 

# data[].attributes.body
Not shown in the camp, but has more intelligent pathfinding. Will jump over obstacles and find the quickest path to the destination.

# data[].attributes.attachment_link.readable_text
local waypointsFolder = game.Workspace.Waypoints
local npc = script.Parent:WaitForChild("Humanoid")
local body = script.Parent:FindFirstChild("HumanoidRootPart") 
local ps = game:GetService("PathfindingService")

local path = ps:CreatePath()

while true do
	for waypoint = 1, #waypointsFolder:GetChildren() do
		path:ComputeAsync(body.Position, 
			waypointsFolder[waypoint].Position)
		
		local waypoints = path:GetWaypoints()
		
		for i, wp in pairs(waypoints) do
			npc:MoveTo(wp.Position)
			if wp.Action == Enum.PathWaypointAction.Jump then
				npc:ChangeState(Enum.HumanoidStateType.Jumping)
			end
			
			npc.MoveToFinished:Wait()
		end
	end
end

# data[].attributes.body
Final door script as shown in the camp.

# data[].attributes.attachment_link.readable_text
local door = script.Parent 

local location = door.Parent.Placeholder 

  

local state = "closed" 

  

local ts = game:GetService("TweenService") 

local info = TweenInfo.new() 

  

local close = ts:Create(door, info, {CFrame = door.CFrame}) 

local open = ts:Create(door, info, {CFrame = location.CFrame}) 

  

for i, part in door.Parent:GetDescendants() do 

if part:IsA("ProximityPrompt") then 

part.Triggered:Connect(function() 

door.trigger:Play() 

if state == "open" then 

close:Play() 

door.close:Play() 

else 

open:Play() 

door.open:Play() 

end 

part.Enabled = false 

end) 

end 

end 

  

close.Completed:Connect(function() 

for i, part in door.Parent:GetDescendants() do 

if part:IsA("ProximityPrompt") then 

part.Enabled = true 

state = "closed" 

end 

end 

end) 

  

open.Completed:Connect(function() 

for i, part in door.Parent:GetDescendants() do 

if part:IsA("ProximityPrompt") then 

part.Enabled = true 

state = "open" 

end 

end 

end) 

# data[].attributes.body
Not shown in the camp. NPC follows and attacks the player.

# data[].attributes.attachment_link.readable_text
local PathfindingService = game:GetService("PathfindingService")

local npc = script.Parent
local hum = npc:WaitForChild("Humanoid")
local hrp = npc:WaitForChild("HumanoidRootPart")
--hrp:SetNetworkOwner(nil)

local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {npc}

local lastPos

local RANGE = 60
local DAMAGE = 30

local function canSeeTarget(target)
	local orgin = hrp.Position
	local direction = (target.HumanoidRootPart.Position - hrp.Position).Unit * RANGE
	local ray = workspace:Raycast(orgin, direction, rayParams)

	if ray and ray.Instance then
		if ray.Instance:IsDescendantOf(target) then
			return true
		else
			return false
		end
	else
		return false
	end
end

local function findTarget()
	local players = game.Players:GetPlayers()
	local maxDistance = RANGE
	local nearestTarget

	for i, player in pairs(players) do
		if player.Character then
			local target = player.Character
			local distance = (hrp.Position - target.HumanoidRootPart.Position).Magnitude

			if distance < maxDistance and canSeeTarget(target) then
				nearestTarget = target
				maxDistance = distance
			end
		end
	end

	return nearestTarget
end

local function getPath(destination)
	local path = PathfindingService:CreatePath()

	path:ComputeAsync(hrp.Position, destination.Position)

	return path	
end

local function attack(target)
	local distance = (hrp.Position - target.HumanoidRootPart.Position).Magnitude
	local debounce = false

	if distance > 5 then
		hum:MoveTo(target.HumanoidRootPart.Position)
	else
		if debounce == false then
			debounce = true

			target.Humanoid.Health -= DAMAGE
			task.wait(0.5)
			debounce = false
		end
	end
end

local function walkTo(destination)
	local path = getPath(destination)

	if path.Status == Enum.PathStatus.Success then
		for i, waypoint in pairs(path:GetWaypoints()) do
			path.Blocked:Connect(function()
				path:Destroy()
			end)

			local target = findTarget()

			if target and target.Humanoid.Health > 0 then
				lastPos = target.HumanoidRootPart.Position
				attack(target)
				break
			else

				if lastPos then
					hum:MoveTo(lastPos)
					hum.MoveToFinished:Wait()
					lastPos = nil
					break
				else
					hum:MoveTo(waypoint.Position)
					hum.MoveToFinished:Wait()
				end
			end
		end
	else
		return
	end
end

local function patrol()
	local waypoints = workspace.Waypoints:GetChildren()
	local randomNum = math.random(1, #waypoints)
	walkTo(waypoints[randomNum])
end

while task.wait(0.2) do
	patrol()
end

# data[].attributes.body
Drag and drop into the Roblox workspace.

# data[].attributes.body
Double click the file to open. Requires Roblox Studio to be on the device.

# data[].attributes.headline
NPC Example (better pathfinding, extra code)

# data[].attributes.subject
NPC Example (better pathfinding, extra code)

# data[].attributes.body
Drag and drop into the Roblox workspace.

# data[].attributes.body
Drag and drop into the Roblox workspace.

# data[].attributes.body
The door without sounds. If this script does not work, check the hierarchy of objects in the workspace and cross reference with the slides. Ensure that objects are named correctly such as "Placeholder" with the correct capitalization. Ensure that proximity prompts are named "ProximityPrompt" and are inside of the buttons.

# data[].attributes.attachment_link.readable_text
local door = script.Parent
local location = door.Parent.Placeholder

local state = "closed"

local ts = game:GetService("TweenService")
local info = TweenInfo.new()

local close = ts:Create(door, info, {CFrame = door.CFrame})
local open = ts:Create(door, info, {CFrame = location.CFrame})

for i,part in door.Parent:GetDescendants() do
	if part:IsA("ProximityPrompt") then
		part.Triggered:Connect(function()
			if state == "open" then
				close:Play()
			else
				open:Play()
			end
			part.Enabled = false
		end)
	end
end

close.Completed:Connect(function()
	for i,part in door.Parent:GetDescendants() do
		if part:IsA("ProximityPrompt") then
			part.Enabled = true
			state = "closed"
		end
	end
end)

open.Completed:Connect(function()
	for i,part in door.Parent:GetDescendants() do
		if part:IsA("ProximityPrompt") then
			part.Enabled = true
			state = "open"
		end
	end
end)