コンテンツへスキップ
動作中

Tail Wag (server)

POPooPoo324 閲覧数Universal2026年7月22日
共有
Tail Wag (server)

スクリプトコード

Lua
--tungsten88474 (hat tool) and mxsynry (fix) on discord

local env = (getgenv and getgenv()) or _G
env.__TailAnim = env.__TailAnim or {}

local TA = env.__TailAnim

if type(TA._cleanup) == "function" then
	pcall(TA._cleanup)
end

TA.Enabled = true
TA.Speed = 10.0
TA.Amplitude = 35

-- Optional:
-- Set this to the exact accessory name when automatic detection fails.
-- Example:
-- TA.AccessoryName = "White Fox Tail"
TA.AccessoryName = TA.AccessoryName or nil

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")

local localPlayer = Players.LocalPlayer

if not localPlayer then
	error("TailAnim: LocalPlayer is unavailable")
end

local characterConnection = nil
local childConnection = nil
local renderConnection = nil

local currentCharacter = nil
local currentAccessory = nil
local realHandle = nil

local fakeAccessory = nil
local fakeHandle = nil
local fakeJoint = nil
local fakeBaseC0 = nil

local savedJoint = nil
local savedJointParent = nil

local animationTime = 0
local currentYaw = 0
local lastSearchTime = 0
local bindGeneration = 0

local function disconnect(connection)
	if connection then
		pcall(function()
			connection:Disconnect()
		end)
	end
end

local function lower(value)
	return string.lower(tostring(value or ""))
end

local function containsPlain(value, searchText)
	return string.find(
		lower(value),
		lower(searchText),
		1,
		true
	) ~= nil
end

local function isHairRelatedName(value)
	local name = lower(value)

	return string.find(name, "ponytail", 1, true) ~= nil
		or string.find(name, "pony tail", 1, true) ~= nil
		or string.find(name, "pigtail", 1, true) ~= nil
		or string.find(name, "pig tail", 1, true) ~= nil
		or string.find(name, "hair", 1, true) ~= nil
		or string.find(name, "braid", 1, true) ~= nil
		or string.find(name, "bangs", 1, true) ~= nil
		or string.find(name, "hairstyle", 1, true) ~= nil
end

local function hasTailWord(value)
	local text = lower(value)

	if text == "" or isHairRelatedName(text) then
		return false
	end

	-- Match "tail" as a separate word.
	if string.match(text, "%f[%a]tail%f[%A]") then
		return true
	end

	-- Common separators used in accessory names.
	if string.find(text, "_tail", 1, true)
		or string.find(text, "tail_", 1, true)
		or string.find(text, "-tail", 1, true)
		or string.find(text, "tail-", 1, true)
	then
		return true
	end

	return false
end

local function getHandle(accessory)
	if not accessory or not accessory:IsA("Accessory") then
		return nil
	end

	local handle = accessory:FindFirstChild("Handle")

	if handle and handle:IsA("BasePart") then
		return handle
	end

	return nil
end

local function getAccessoryJoint(accessory)
	local handle = getHandle(accessory)

	if not handle then
		return nil
	end

	local accessoryWeld = handle:FindFirstChild("AccessoryWeld")

	if accessoryWeld and (
		accessoryWeld:IsA("Weld")
		or accessoryWeld:IsA("Motor6D")
	) then
		return accessoryWeld
	end

	for _, object in ipairs(handle:GetChildren()) do
		if object:IsA("Motor6D") or object:IsA("Weld") then
			return object
		end
	end

	return nil
end

local function getAccessoryType(accessory)
	local success, accessoryType = pcall(function()
		return accessory.AccessoryType
	end)

	if success then
		return accessoryType
	end

	return nil
end

local function hasHairAttachment(accessory)
	local handle = getHandle(accessory)

	if not handle then
		return false
	end

	for _, object in ipairs(handle:GetDescendants()) do
		if object:IsA("Attachment") then
			local attachmentName = lower(object.Name)

			if string.find(
				attachmentName,
				"hairattachment",
				1,
				true
			) then
				return true
			end
		end
	end

	return false
end

local function hasTailAttachment(accessory)
	local handle = getHandle(accessory)

	if not handle then
		return false
	end

	for _, object in ipairs(handle:GetDescendants()) do
		if object:IsA("Attachment") then
			if hasTailWord(object.Name) then
				return true
			end
		end
	end

	return false
end

local function hasWaistOrBackAttachment(accessory)
	local handle = getHandle(accessory)

	if not handle then
		return false
	end

	for _, object in ipairs(handle:GetDescendants()) do
		if object:IsA("Attachment") then
			local attachmentName = lower(object.Name)

			if string.find(
				attachmentName,
				"waistbackattachment",
				1,
				true
			)
				or string.find(
					attachmentName,
					"waistcenterattachment",
					1,
					true
				)
				or string.find(
					attachmentName,
					"bodybackattachment",
					1,
					true
				)
			then
				return true
			end
		end
	end

	return false
end

local function hasTailMarkerInDescendants(accessory)
	local handle = getHandle(accessory)

	if not handle then
		return false
	end

	for _, object in ipairs(handle:GetDescendants()) do
		if hasTailWord(object.Name) then
			return true
		end

		if object:IsA("SpecialMesh") then
			local meshId = ""
			local textureId = ""

			pcall(function()
				meshId = object.MeshId
			end)

			pcall(function()
				textureId = object.TextureId
			end)

			if hasTailWord(meshId) or hasTailWord(textureId) then
				return true
			end
		end
	end

	return false
end

local function matchesConfiguredAccessory(accessory)
	if type(TA.AccessoryName) ~= "string"
		or TA.AccessoryName == ""
	then
		return false
	end

	return lower(accessory.Name) == lower(TA.AccessoryName)
end

local function isTailAccessory(accessory)
	if not accessory or not accessory:IsA("Accessory") then
		return false
	end

	if accessory:GetAttribute("__TailAnimFake") == true then
		return false
	end

	if not getHandle(accessory) then
		return false
	end

	-- An explicitly configured accessory name takes priority.
	if matchesConfiguredAccessory(accessory) then
		return true
	end

	local accessoryType = getAccessoryType(accessory)

	-- Never select an accessory Roblox identifies as hair.
	if accessoryType == Enum.AccessoryType.Hair then
		return false
	end

	-- Reject hair attachments even if the accessory type is incorrect.
	if hasHairAttachment(accessory) then
		return false
	end

	-- Reject common hairstyle names containing the word "tail."
	if isHairRelatedName(accessory.Name) then
		return false
	end

	local nameHasTail = hasTailWord(accessory.Name)
	local attachmentHasTail = hasTailAttachment(accessory)
	local descendantsHaveTail =
		hasTailMarkerInDescendants(accessory)

	if not nameHasTail
		and not attachmentHasTail
		and not descendantsHaveTail
	then
		return false
	end

	-- A tail normally uses a waist or back attachment.
	if hasWaistOrBackAttachment(accessory) then
		return true
	end

	-- Accept clearly named tail accessories unless they are hair.
	if nameHasTail then
		return true
	end

	return attachmentHasTail or descendantsHaveTail
end

local function configureInvisibleAccessory(accessory)
	for _, object in ipairs(accessory:GetDescendants()) do
		if object:IsA("BasePart") then
			object.Transparency = 1
			object.CanCollide = false
			object.CanTouch = false
			object.CanQuery = false
			object.Massless = true

			pcall(function()
				object.CastShadow = false
			end)
		end
	end
end

local function configureRealHandle(handle)
	handle.Anchored = false
	handle.CanCollide = false
	handle.CanTouch = false
	handle.CanQuery = false
	handle.Massless = true
end

local function destroyFakeAccessory()
	if fakeAccessory then
		pcall(function()
			fakeAccessory:Destroy()
		end)
	end

	fakeAccessory = nil
	fakeHandle = nil
	fakeJoint = nil
	fakeBaseC0 = nil
end

local function restoreOriginalJoint()
	if not savedJoint then
		return
	end

	local joint = savedJoint

	savedJoint = nil

	if currentAccessory
		and currentAccessory.Parent
		and realHandle
		and realHandle.Parent
		and not getAccessoryJoint(currentAccessory)
	then
		local restored = pcall(function()
			joint.Parent = savedJointParent or realHandle
		end)

		if restored then
			savedJointParent = nil
			return
		end
	end

	pcall(function()
		joint:Destroy()
	end)

	savedJointParent = nil
end

local function releaseCurrentAccessory(restoreOriginal)
	destroyFakeAccessory()

	if restoreOriginal then
		restoreOriginalJoint()
	elseif savedJoint then
		pcall(function()
			savedJoint:Destroy()
		end)

		savedJoint = nil
		savedJointParent = nil
	end

	currentAccessory = nil
	realHandle = nil

	animationTime = 0
	currentYaw = 0
end

local function removeOldFakeAccessories(character)
	if not character then
		return
	end

	for _, child in ipairs(character:GetChildren()) do
		if child:IsA("Accessory")
			and child:GetAttribute("__TailAnimFake") == true
		then
			pcall(function()
				child:Destroy()
			end)
		end
	end
end

local function attachTail(accessory)
	if not accessory
		or not accessory.Parent
		or not accessory:IsA("Accessory")
	then
		return false
	end

	if not isTailAccessory(accessory) then
		return false
	end

	local handle = getHandle(accessory)
	local originalJoint = getAccessoryJoint(accessory)

	if not handle or not originalJoint then
		return false
	end

	releaseCurrentAccessory(true)

	local previousArchivable = accessory.Archivable
	accessory.Archivable = true

	local cloneSuccess, clonedAccessory = pcall(function()
		return accessory:Clone()
	end)

	accessory.Archivable = previousArchivable

	if not cloneSuccess or not clonedAccessory then
		warn(
			"TailAnim: unable to clone accessory "
				.. tostring(accessory.Name)
		)

		return false
	end

	clonedAccessory.Name =
		"__TailAnimFake_" .. tostring(accessory.Name)

	clonedAccessory:SetAttribute("__TailAnimFake", true)

	configureInvisibleAccessory(clonedAccessory)

	clonedAccessory.Parent = accessory.Parent

	local clonedHandle = getHandle(clonedAccessory)
	local clonedJoint = getAccessoryJoint(clonedAccessory)

	if not clonedHandle or not clonedJoint then
		clonedAccessory:Destroy()

		warn(
			"TailAnim: cloned tail has no valid handle or joint"
		)

		return false
	end

	local jointClone = nil

	local jointCloneSuccess = pcall(function()
		originalJoint.Archivable = true
		jointClone = originalJoint:Clone()
	end)

	if not jointCloneSuccess or not jointClone then
		clonedAccessory:Destroy()

		warn(
			"TailAnim: unable to preserve the original joint"
		)

		return false
	end

	currentAccessory = accessory
	realHandle = handle

	fakeAccessory = clonedAccessory
	fakeHandle = clonedHandle
	fakeJoint = clonedJoint
	fakeBaseC0 = clonedJoint.C0

	savedJoint = jointClone
	savedJointParent = originalJoint.Parent

	configureRealHandle(realHandle)

	local destroyed = pcall(function()
		originalJoint:Destroy()
	end)

	if not destroyed then
		releaseCurrentAccessory(false)
		return false
	end

	animationTime = 0
	currentYaw = 0

	return true
end

local function findBestTail(character)
	if not character or not character.Parent then
		return nil
	end

	local configuredMatch = nil
	local namedMatch = nil
	local otherMatch = nil

	for _, child in ipairs(character:GetChildren()) do
		if child:IsA("Accessory")
			and child:GetAttribute("__TailAnimFake") ~= true
		then
			if matchesConfiguredAccessory(child) then
				configuredMatch = child
				break
			end

			if isTailAccessory(child) then
				if hasTailWord(child.Name) then
					namedMatch = namedMatch or child
				else
					otherMatch = otherMatch or child
				end
			end
		end
	end

	return configuredMatch or namedMatch or otherMatch
end

local function findAndAttachTail(character)
	local accessory = findBestTail(character)

	if not accessory then
		return false
	end

	return attachTail(accessory)
end

local function bindCharacter(character)
	bindGeneration += 1

	local generation = bindGeneration

	disconnect(childConnection)

	childConnection = nil
	TA._childConn = nil

	releaseCurrentAccessory(true)
	removeOldFakeAccessories(character)

	currentCharacter = character
	lastSearchTime = 0

	-- Give Roblox a moment to add all character accessories.
	task.delay(0.35, function()
		if generation ~= bindGeneration then
			return
		end

		if character ~= localPlayer.Character then
			return
		end

		findAndAttachTail(character)
	end)

	childConnection = character.ChildAdded:Connect(function(child)
		if not child:IsA("Accessory") then
			return
		end

		if child:GetAttribute("__TailAnimFake") == true then
			return
		end

		task.delay(0.2, function()
			if generation ~= bindGeneration then
				return
			end

			if child.Parent ~= character then
				return
			end

			if isTailAccessory(child) then
				attachTail(child)
			end
		end)
	end)

	TA._childConn = childConnection
end

function TA:SetEnabled(value)
	self.Enabled = not not value
end

function TA:SetSpeed(value)
	local numericValue = tonumber(value)

	if numericValue then
		self.Speed = math.max(numericValue, 0)
	end
end

function TA:SetAmplitude(value)
	local numericValue = tonumber(value)

	if numericValue then
		self.Amplitude = math.clamp(
			numericValue,
			0,
			180
		)
	end
end

function TA:SetAccessoryName(name)
	if name == nil or tostring(name) == "" then
		self.AccessoryName = nil
	else
		self.AccessoryName = tostring(name)
	end

	local character = localPlayer.Character

	if character then
		releaseCurrentAccessory(true)

		task.defer(function()
			findAndAttachTail(character)
		end)
	end
end

function TA:GetSelectedAccessory()
	return currentAccessory
end

function TA:Stop()
	self.Enabled = false

	if type(self._cleanup) == "function" then
		self._cleanup()
	end
end

characterConnection =
	localPlayer.CharacterAdded:Connect(function(character)
		bindCharacter(character)
	end)

TA._charConn = characterConnection

if localPlayer.Character then
	bindCharacter(localPlayer.Character)
end

renderConnection =
	RunService.RenderStepped:Connect(function(deltaTime)
		local character = localPlayer.Character

		if not character then
			return
		end

		local tailIsValid =
			currentAccessory
			and currentAccessory.Parent == character
			and realHandle
			and realHandle.Parent
			and fakeAccessory
			and fakeAccessory.Parent == character
			and fakeHandle
			and fakeHandle.Parent
			and fakeJoint
			and fakeJoint.Parent
			and fakeBaseC0

		if not tailIsValid then
			local now = os.clock()

			if now - lastSearchTime >= 1 then
				lastSearchTime = now
				findAndAttachTail(character)
			end

			return
		end

		local targetYaw = 0

		if TA.Enabled then
			animationTime +=
				deltaTime * (tonumber(TA.Speed) or 10)

			targetYaw =
				math.sin(animationTime)
				* math.rad(
					tonumber(TA.Amplitude) or 35
				)
		end

		local smoothing =
			1 - math.exp(-deltaTime * 18)

		currentYaw +=
			(targetYaw - currentYaw) * smoothing

		fakeJoint.C0 =
			fakeBaseC0
			* CFrame.Angles(0, currentYaw, 0)

		realHandle.CFrame = fakeHandle.CFrame

		pcall(function()
			realHandle.AssemblyLinearVelocity =
				Vector3.new(0, 0, 30)

			realHandle.AssemblyAngularVelocity =
				Vector3.new(0, 0, 0)
		end)
	end)

TA._conn = renderConnection

TA._cleanup = function()
	bindGeneration += 1

	disconnect(renderConnection)
	disconnect(characterConnection)
	disconnect(childConnection)

	renderConnection = nil
	characterConnection = nil
	childConnection = nil

	TA._conn = nil
	TA._charConn = nil
	TA._childConn = nil

	releaseCurrentAccessory(true)

	currentCharacter = nil
end

説明

AI is used to make guide for you in the script ---------------------------------------------- **Moony2HP (@tungsten88474)** i found it on scriptblox and i just made it server **XII (@syniyrz)** how did you even make it server 😭 **Moony2HP (@tungsten88474)** skidding, i look at a tool hat hold script or whatever that let you hold ur hats as tools saw that it destroyed the weld and used cframe and i kinda ported that over **XII (@syniyrz)** ouuuu creative **Moony2HP (@tungsten88474)** yeah

コメント (0)

会話に参加するにはログインしてください

  • 最初のコメントを投稿しましょう。

よくある質問

Tail Wag (server)スクリプトはどう使いますか?
上のスクリプトコードをコピーし、RobloxでUniversalに参加して、エグゼキューターに貼り付けて実行してください。スクリプトが動くとすぐに機能が有効になります。
Tail Wag (server)はまだ動作しますか?
はい。Tail Wag (server)は現在「動作中」とマークされています(最終更新: 2026年9月1日)。Robloxのアップデートで動かなくなった場合は、更新版をお待ちください。
Tail Wag (server)に対応するエグゼキューターは?
このスクリプトはほとんどの人気Robloxエグゼキューターで動作します。お使いのデバイス向けの信頼できる無料または有料エグゼキューターを見つけるには、エグゼキューターページをご覧ください。