484 lines
16 KiB
Lua
484 lines
16 KiB
Lua
local ClickToMoveDisplay = {}
|
|
|
|
local FAILURE_ANIMATION_ID = "rbxassetid://2874840706"
|
|
|
|
local TrailDotIcon = "rbxasset://textures/ui/traildot.png"
|
|
local EndWaypointIcon = "rbxasset://textures/ui/waypoint.png"
|
|
|
|
local WaypointsAlwaysOnTop = false
|
|
|
|
local WAYPOINT_INCLUDE_FACTOR = 2
|
|
local LAST_DOT_DISTANCE = 3
|
|
|
|
local WAYPOINT_BILLBOARD_SIZE = UDim2.new(0, 1.68 * 25, 0, 2 * 25)
|
|
|
|
local ENDWAYPOINT_SIZE_OFFSET_MIN = Vector2.new(0, 0.5)
|
|
local ENDWAYPOINT_SIZE_OFFSET_MAX = Vector2.new(0, 1)
|
|
|
|
local FAIL_WAYPOINT_SIZE_OFFSET_CENTER = Vector2.new(0, 0.5)
|
|
local FAIL_WAYPOINT_SIZE_OFFSET_LEFT = Vector2.new(0.1, 0.5)
|
|
local FAIL_WAYPOINT_SIZE_OFFSET_RIGHT = Vector2.new(-0.1, 0.5)
|
|
|
|
local FAILURE_TWEEN_LENGTH = 0.125
|
|
local FAILURE_TWEEN_COUNT = 4
|
|
|
|
local TWEEN_WAYPOINT_THRESHOLD = 5
|
|
|
|
local TRAIL_DOT_PARENT_NAME = "ClickToMoveDisplay"
|
|
|
|
local TrailDotSize = Vector2.new(1.5, 1.5)
|
|
|
|
local TRAIL_DOT_MIN_SCALE = 1
|
|
local TRAIL_DOT_MIN_DISTANCE = 10
|
|
local TRAIL_DOT_MAX_SCALE = 2.5
|
|
local TRAIL_DOT_MAX_DISTANCE = 100
|
|
|
|
local PlayersService = game:GetService("Players")
|
|
local TweenService = game:GetService("TweenService")
|
|
local RunService = game:GetService("RunService")
|
|
local Workspace = game:GetService("Workspace")
|
|
|
|
local LocalPlayer = PlayersService.LocalPlayer
|
|
|
|
local function CreateWaypointTemplates()
|
|
local TrailDotTemplate = Instance.new("Part")
|
|
TrailDotTemplate.Size = Vector3.new(1, 1, 1)
|
|
TrailDotTemplate.Anchored = true
|
|
TrailDotTemplate.CanCollide = false
|
|
TrailDotTemplate.Name = "TrailDot"
|
|
TrailDotTemplate.Transparency = 1
|
|
local TrailDotImage = Instance.new("ImageHandleAdornment")
|
|
TrailDotImage.Name = "TrailDotImage"
|
|
TrailDotImage.Size = TrailDotSize
|
|
TrailDotImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
|
|
TrailDotImage.AlwaysOnTop = WaypointsAlwaysOnTop
|
|
TrailDotImage.Image = TrailDotIcon
|
|
TrailDotImage.Adornee = TrailDotTemplate
|
|
TrailDotImage.Parent = TrailDotTemplate
|
|
|
|
local EndWaypointTemplate = Instance.new("Part")
|
|
EndWaypointTemplate.Size = Vector3.new(2, 2, 2)
|
|
EndWaypointTemplate.Anchored = true
|
|
EndWaypointTemplate.CanCollide = false
|
|
EndWaypointTemplate.Name = "EndWaypoint"
|
|
EndWaypointTemplate.Transparency = 1
|
|
local EndWaypointImage = Instance.new("ImageHandleAdornment")
|
|
EndWaypointImage.Name = "TrailDotImage"
|
|
EndWaypointImage.Size = TrailDotSize
|
|
EndWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
|
|
EndWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
|
|
EndWaypointImage.Image = TrailDotIcon
|
|
EndWaypointImage.Adornee = EndWaypointTemplate
|
|
EndWaypointImage.Parent = EndWaypointTemplate
|
|
local EndWaypointBillboard = Instance.new("BillboardGui")
|
|
EndWaypointBillboard.Name = "EndWaypointBillboard"
|
|
EndWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
|
|
EndWaypointBillboard.LightInfluence = 0
|
|
EndWaypointBillboard.SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MIN
|
|
EndWaypointBillboard.AlwaysOnTop = true
|
|
EndWaypointBillboard.Adornee = EndWaypointTemplate
|
|
EndWaypointBillboard.Parent = EndWaypointTemplate
|
|
local EndWaypointImageLabel = Instance.new("ImageLabel")
|
|
EndWaypointImageLabel.Image = EndWaypointIcon
|
|
EndWaypointImageLabel.BackgroundTransparency = 1
|
|
EndWaypointImageLabel.Size = UDim2.new(1, 0, 1, 0)
|
|
EndWaypointImageLabel.Parent = EndWaypointBillboard
|
|
|
|
|
|
local FailureWaypointTemplate = Instance.new("Part")
|
|
FailureWaypointTemplate.Size = Vector3.new(2, 2, 2)
|
|
FailureWaypointTemplate.Anchored = true
|
|
FailureWaypointTemplate.CanCollide = false
|
|
FailureWaypointTemplate.Name = "FailureWaypoint"
|
|
FailureWaypointTemplate.Transparency = 1
|
|
local FailureWaypointImage = Instance.new("ImageHandleAdornment")
|
|
FailureWaypointImage.Name = "TrailDotImage"
|
|
FailureWaypointImage.Size = TrailDotSize
|
|
FailureWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
|
|
FailureWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
|
|
FailureWaypointImage.Image = TrailDotIcon
|
|
FailureWaypointImage.Adornee = FailureWaypointTemplate
|
|
FailureWaypointImage.Parent = FailureWaypointTemplate
|
|
local FailureWaypointBillboard = Instance.new("BillboardGui")
|
|
FailureWaypointBillboard.Name = "FailureWaypointBillboard"
|
|
FailureWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
|
|
FailureWaypointBillboard.LightInfluence = 0
|
|
FailureWaypointBillboard.SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER
|
|
FailureWaypointBillboard.AlwaysOnTop = true
|
|
FailureWaypointBillboard.Adornee = FailureWaypointTemplate
|
|
FailureWaypointBillboard.Parent = FailureWaypointTemplate
|
|
local FailureWaypointFrame = Instance.new("Frame")
|
|
FailureWaypointFrame.BackgroundTransparency = 1
|
|
FailureWaypointFrame.Size = UDim2.new(0, 0, 0, 0)
|
|
FailureWaypointFrame.Position = UDim2.new(0.5, 0, 1, 0)
|
|
FailureWaypointFrame.Parent = FailureWaypointBillboard
|
|
local FailureWaypointImageLabel = Instance.new("ImageLabel")
|
|
FailureWaypointImageLabel.Image = EndWaypointIcon
|
|
FailureWaypointImageLabel.BackgroundTransparency = 1
|
|
FailureWaypointImageLabel.Position = UDim2.new(
|
|
0, -WAYPOINT_BILLBOARD_SIZE.X.Offset/2, 0, -WAYPOINT_BILLBOARD_SIZE.Y.Offset
|
|
)
|
|
FailureWaypointImageLabel.Size = WAYPOINT_BILLBOARD_SIZE
|
|
FailureWaypointImageLabel.Parent = FailureWaypointFrame
|
|
|
|
return TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate
|
|
end
|
|
|
|
local TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
|
|
|
|
local function getTrailDotParent()
|
|
local camera = Workspace.CurrentCamera
|
|
local trailParent = camera:FindFirstChild(TRAIL_DOT_PARENT_NAME)
|
|
if not trailParent then
|
|
trailParent = Instance.new("Model")
|
|
trailParent.Name = TRAIL_DOT_PARENT_NAME
|
|
trailParent.Parent = camera
|
|
end
|
|
return trailParent
|
|
end
|
|
|
|
local function placePathWaypoint(waypointModel, position)
|
|
local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
|
|
local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
|
|
ray,
|
|
{ Workspace.CurrentCamera, LocalPlayer.Character }
|
|
)
|
|
if hitPart then
|
|
waypointModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
|
|
waypointModel.Parent = getTrailDotParent()
|
|
end
|
|
end
|
|
|
|
local TrailDot = {}
|
|
TrailDot.__index = TrailDot
|
|
|
|
function TrailDot:Destroy()
|
|
self.DisplayModel:Destroy()
|
|
end
|
|
|
|
function TrailDot:NewDisplayModel(position)
|
|
local newDisplayModel = TrailDotTemplate:Clone()
|
|
placePathWaypoint(newDisplayModel, position)
|
|
return newDisplayModel
|
|
end
|
|
|
|
function TrailDot.new(position, closestWaypoint)
|
|
local self = setmetatable({}, TrailDot)
|
|
|
|
self.DisplayModel = self:NewDisplayModel(position)
|
|
self.ClosestWayPoint = closestWaypoint
|
|
|
|
return self
|
|
end
|
|
|
|
local EndWaypoint = {}
|
|
EndWaypoint.__index = EndWaypoint
|
|
|
|
function EndWaypoint:Destroy()
|
|
self.Destroyed = true
|
|
self.Tween:Cancel()
|
|
self.DisplayModel:Destroy()
|
|
end
|
|
|
|
function EndWaypoint:NewDisplayModel(position)
|
|
local newDisplayModel = EndWaypointTemplate:Clone()
|
|
placePathWaypoint(newDisplayModel, position)
|
|
return newDisplayModel
|
|
end
|
|
|
|
function EndWaypoint:CreateTween()
|
|
local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, -1, true)
|
|
local tween = TweenService:Create(
|
|
self.DisplayModel.EndWaypointBillboard,
|
|
tweenInfo,
|
|
{ SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MAX }
|
|
)
|
|
tween:Play()
|
|
return tween
|
|
end
|
|
|
|
function EndWaypoint:TweenInFrom(originalPosition)
|
|
local currentPositon = self.DisplayModel.Position
|
|
local studsOffset = originalPosition - currentPositon
|
|
self.DisplayModel.EndWaypointBillboard.StudsOffset = Vector3.new(0, studsOffset.Y, 0)
|
|
local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
|
|
local tween = TweenService:Create(
|
|
self.DisplayModel.EndWaypointBillboard,
|
|
tweenInfo,
|
|
{ StudsOffset = Vector3.new(0, 0, 0) }
|
|
)
|
|
tween:Play()
|
|
return tween
|
|
end
|
|
|
|
function EndWaypoint.new(position, closestWaypoint, originalPosition)
|
|
local self = setmetatable({}, EndWaypoint)
|
|
|
|
self.DisplayModel = self:NewDisplayModel(position)
|
|
self.Destroyed = false
|
|
if originalPosition and (originalPosition - position).magnitude > TWEEN_WAYPOINT_THRESHOLD then
|
|
self.Tween = self:TweenInFrom(originalPosition)
|
|
coroutine.wrap(function()
|
|
self.Tween.Completed:Wait()
|
|
if not self.Destroyed then
|
|
self.Tween = self:CreateTween()
|
|
end
|
|
end)()
|
|
else
|
|
self.Tween = self:CreateTween()
|
|
end
|
|
self.ClosestWayPoint = closestWaypoint
|
|
|
|
return self
|
|
end
|
|
|
|
local FailureWaypoint = {}
|
|
FailureWaypoint.__index = FailureWaypoint
|
|
|
|
function FailureWaypoint:Hide()
|
|
self.DisplayModel.Parent = nil
|
|
end
|
|
|
|
function FailureWaypoint:Destroy()
|
|
self.DisplayModel:Destroy()
|
|
end
|
|
|
|
function FailureWaypoint:NewDisplayModel(position)
|
|
local newDisplayModel = FailureWaypointTemplate:Clone()
|
|
placePathWaypoint(newDisplayModel, position)
|
|
local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
|
|
local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
|
|
ray, { Workspace.CurrentCamera, LocalPlayer.Character }
|
|
)
|
|
if hitPart then
|
|
newDisplayModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
|
|
newDisplayModel.Parent = getTrailDotParent()
|
|
end
|
|
return newDisplayModel
|
|
end
|
|
|
|
function FailureWaypoint:RunFailureTween()
|
|
wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore starting tweening
|
|
-- Tween out from center
|
|
local tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
|
|
local tweenLeft = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
|
|
{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_LEFT })
|
|
tweenLeft:Play()
|
|
|
|
local tweenLeftRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
|
|
{ Rotation = 10 })
|
|
tweenLeftRoation:Play()
|
|
|
|
tweenLeft.Completed:wait()
|
|
|
|
-- Tween back and forth
|
|
tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
|
|
FAILURE_TWEEN_COUNT - 1, true)
|
|
local tweenSideToSide = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
|
|
{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_RIGHT})
|
|
tweenSideToSide:Play()
|
|
|
|
-- Tween flash dark and roate left and right
|
|
tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
|
|
FAILURE_TWEEN_COUNT - 1, true)
|
|
local tweenFlash = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame.ImageLabel, tweenInfo,
|
|
{ ImageColor3 = Color3.new(0.75, 0.75, 0.75)})
|
|
tweenFlash:Play()
|
|
|
|
local tweenRotate = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
|
|
{ Rotation = -10 })
|
|
tweenRotate:Play()
|
|
|
|
tweenSideToSide.Completed:wait()
|
|
|
|
-- Tween back to center
|
|
tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
|
|
local tweenCenter = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
|
|
{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER })
|
|
tweenCenter:Play()
|
|
|
|
local tweenRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
|
|
{ Rotation = 0 })
|
|
tweenRoation:Play()
|
|
|
|
tweenCenter.Completed:wait()
|
|
|
|
wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore removing
|
|
end
|
|
|
|
function FailureWaypoint.new(position)
|
|
local self = setmetatable({}, FailureWaypoint)
|
|
|
|
self.DisplayModel = self:NewDisplayModel(position)
|
|
|
|
return self
|
|
end
|
|
|
|
local failureAnimation = Instance.new("Animation")
|
|
failureAnimation.AnimationId = FAILURE_ANIMATION_ID
|
|
|
|
local lastHumanoid = nil
|
|
local lastFailureAnimationTrack = nil
|
|
|
|
local function getFailureAnimationTrack(myHumanoid)
|
|
if myHumanoid == lastHumanoid then
|
|
return lastFailureAnimationTrack
|
|
end
|
|
lastFailureAnimationTrack = myHumanoid:LoadAnimation(failureAnimation)
|
|
lastFailureAnimationTrack.Priority = Enum.AnimationPriority.Action
|
|
lastFailureAnimationTrack.Looped = false
|
|
return lastFailureAnimationTrack
|
|
end
|
|
|
|
local function findPlayerHumanoid()
|
|
local character = LocalPlayer.Character
|
|
if character then
|
|
return character:FindFirstChildOfClass("Humanoid")
|
|
end
|
|
end
|
|
|
|
local function createTrailDots(wayPoints, originalEndWaypoint)
|
|
local newTrailDots = {}
|
|
local count = 1
|
|
for i = 1, #wayPoints - 1 do
|
|
local closeToEnd = (wayPoints[i].Position - wayPoints[#wayPoints].Position).magnitude < LAST_DOT_DISTANCE
|
|
local includeWaypoint = i % WAYPOINT_INCLUDE_FACTOR == 0 and not closeToEnd
|
|
if includeWaypoint then
|
|
local trailDot = TrailDot.new(wayPoints[i].Position, i)
|
|
newTrailDots[count] = trailDot
|
|
count = count + 1
|
|
end
|
|
end
|
|
|
|
local newEndWaypoint = EndWaypoint.new(wayPoints[#wayPoints].Position, #wayPoints, originalEndWaypoint)
|
|
table.insert(newTrailDots, newEndWaypoint)
|
|
|
|
local reversedTrailDots = {}
|
|
count = 1
|
|
for i = #newTrailDots, 1, -1 do
|
|
reversedTrailDots[count] = newTrailDots[i]
|
|
count = count + 1
|
|
end
|
|
return reversedTrailDots
|
|
end
|
|
|
|
local function getTrailDotScale(distanceToCamera, defaultSize)
|
|
local rangeLength = TRAIL_DOT_MAX_DISTANCE - TRAIL_DOT_MIN_DISTANCE
|
|
local inRangePoint = math.clamp(distanceToCamera - TRAIL_DOT_MIN_DISTANCE, 0, rangeLength)/rangeLength
|
|
local scale = TRAIL_DOT_MIN_SCALE + (TRAIL_DOT_MAX_SCALE - TRAIL_DOT_MIN_SCALE)*inRangePoint
|
|
return defaultSize * scale
|
|
end
|
|
|
|
local createPathCount = 0
|
|
-- originalEndWaypoint is optional, causes the waypoint to tween from that position.
|
|
function ClickToMoveDisplay.CreatePathDisplay(wayPoints, originalEndWaypoint)
|
|
createPathCount = createPathCount + 1
|
|
local trailDots = createTrailDots(wayPoints, originalEndWaypoint)
|
|
|
|
local function removePathBeforePoint(wayPointNumber)
|
|
-- kill all trailDots before and at wayPointNumber
|
|
for i = #trailDots, 1, -1 do
|
|
local trailDot = trailDots[i]
|
|
if trailDot.ClosestWayPoint <= wayPointNumber then
|
|
trailDot:Destroy()
|
|
trailDots[i] = nil
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local reiszeTrailDotsUpdateName = "ClickToMoveResizeTrail" ..createPathCount
|
|
local function resizeTrailDots()
|
|
if #trailDots == 0 then
|
|
RunService:UnbindFromRenderStep(reiszeTrailDotsUpdateName)
|
|
return
|
|
end
|
|
local cameraPos = Workspace.CurrentCamera.CFrame.p
|
|
for i = 1, #trailDots do
|
|
local trailDotImage = trailDots[i].DisplayModel:FindFirstChild("TrailDotImage")
|
|
if trailDotImage then
|
|
local distanceToCamera = (trailDots[i].DisplayModel.Position - cameraPos).magnitude
|
|
trailDotImage.Size = getTrailDotScale(distanceToCamera, TrailDotSize)
|
|
end
|
|
end
|
|
end
|
|
RunService:BindToRenderStep(reiszeTrailDotsUpdateName, Enum.RenderPriority.Camera.Value - 1, resizeTrailDots)
|
|
|
|
local function removePath()
|
|
removePathBeforePoint(#wayPoints)
|
|
end
|
|
|
|
return removePath, removePathBeforePoint
|
|
end
|
|
|
|
local lastFailureWaypoint = nil
|
|
function ClickToMoveDisplay.DisplayFailureWaypoint(position)
|
|
if lastFailureWaypoint then
|
|
lastFailureWaypoint:Hide()
|
|
end
|
|
local failureWaypoint = FailureWaypoint.new(position)
|
|
lastFailureWaypoint = failureWaypoint
|
|
coroutine.wrap(function()
|
|
failureWaypoint:RunFailureTween()
|
|
failureWaypoint:Destroy()
|
|
failureWaypoint = nil
|
|
end)()
|
|
end
|
|
|
|
function ClickToMoveDisplay.CreateEndWaypoint(position)
|
|
return EndWaypoint.new(position)
|
|
end
|
|
|
|
function ClickToMoveDisplay.PlayFailureAnimation()
|
|
local myHumanoid = findPlayerHumanoid()
|
|
if myHumanoid then
|
|
local animationTrack = getFailureAnimationTrack(myHumanoid)
|
|
animationTrack:Play()
|
|
end
|
|
end
|
|
|
|
function ClickToMoveDisplay.CancelFailureAnimation()
|
|
if lastFailureAnimationTrack ~= nil and lastFailureAnimationTrack.IsPlaying then
|
|
lastFailureAnimationTrack:Stop()
|
|
end
|
|
end
|
|
|
|
function ClickToMoveDisplay.SetWaypointTexture(texture)
|
|
TrailDotIcon = texture
|
|
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
|
|
end
|
|
|
|
function ClickToMoveDisplay.GetWaypointTexture()
|
|
return TrailDotIcon
|
|
end
|
|
|
|
function ClickToMoveDisplay.SetWaypointRadius(radius)
|
|
TrailDotSize = Vector2.new(radius, radius)
|
|
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
|
|
end
|
|
|
|
function ClickToMoveDisplay.GetWaypointRadius()
|
|
return TrailDotSize.X
|
|
end
|
|
|
|
function ClickToMoveDisplay.SetEndWaypointTexture(texture)
|
|
EndWaypointIcon = texture
|
|
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
|
|
end
|
|
|
|
function ClickToMoveDisplay.GetEndWaypointTexture()
|
|
return EndWaypointIcon
|
|
end
|
|
|
|
function ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
|
|
WaypointsAlwaysOnTop = alwaysOnTop
|
|
TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
|
|
end
|
|
|
|
function ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
|
|
return WaypointsAlwaysOnTop
|
|
end
|
|
|
|
return ClickToMoveDisplay
|