Clients/Client2018/content/scripts/PlayerScripts/StarterPlayerScripts_NewStr.../RobloxPlayerScript/ControlScript/ClickToMoveController.lua

1074 lines
30 KiB
Lua

--[[
-- Original By Kip Turner, Copyright Roblox 2014
-- Updated by Garnold to utilize the new PathfindingService API, 2017
-- 2018 PlayerScripts Update - AllYourBlox
--]]
--[[ Roblox Services ]]--
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DebrisService = game:GetService('Debris')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local TweenService = game:GetService("TweenService")
--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local movementKeys = {
[Enum.KeyCode.W] = true;
[Enum.KeyCode.A] = true;
[Enum.KeyCode.S] = true;
[Enum.KeyCode.D] = true;
[Enum.KeyCode.Up] = true;
[Enum.KeyCode.Down] = true;
}
local FFlagUserNavigationFixClickToMoveInterruptionSuccess, FFlagUserNavigationFixClickToMoveInterruptionResult = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNavigationFixClickToMoveInterruption") end)
local FFlagUserNavigationFixClickToMoveInterruption = FFlagUserNavigationFixClickToMoveInterruptionSuccess and FFlagUserNavigationFixClickToMoveInterruptionResult
local Player = Players.LocalPlayer
local PlayerScripts = Player.PlayerScripts
local TouchJump = nil
local SHOW_PATH = true
local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local CurrentSeatPart = nil
local DrivingTo = nil
local XZ_VECTOR3 = Vector3.new(1,0,1)
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local ZERO_VECTOR2 = Vector2.new(0,0)
local BindableEvent_OnFailStateChanged = nil
if UserInputService.TouchEnabled then
-- BindableEvent_OnFailStateChanged = MasterControl:GetClickToMoveFailStateChanged()
end
--------------------------UTIL LIBRARY-------------------------------
local Utility = {}
do
local function ViewSizeX()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if x == 0 then
return 1024
else
if x > y then
return x
else
return y
end
end
end
Utility.ViewSizeX = ViewSizeX
local function ViewSizeY()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if y == 0 then
return 768
else
if x > y then
return y
else
return x
end
end
end
Utility.ViewSizeY = ViewSizeY
local function FindCharacterAncestor(part)
if part then
local humanoid = part:FindFirstChild("Humanoid")
if humanoid then
return part, humanoid
else
return FindCharacterAncestor(part.Parent)
end
end
end
Utility.FindCharacterAncestor = FindCharacterAncestor
local function Raycast(ray, ignoreNonCollidable, ignoreList)
local ignoreList = ignoreList or {}
local hitPart, hitPos, hitNorm, hitMat = RayCastIgnoreList(workspace, ray, ignoreList)
if hitPart then
if ignoreNonCollidable and hitPart.CanCollide == false then
table.insert(ignoreList, hitPart)
return Raycast(ray, ignoreNonCollidable, ignoreList)
end
return hitPart, hitPos, hitNorm, hitMat
end
return nil, nil
end
Utility.Raycast = Raycast
local function AveragePoints(positions)
local avgPos = ZERO_VECTOR2
if #positions > 0 then
for i = 1, #positions do
avgPos = avgPos + positions[i]
end
avgPos = avgPos / #positions
end
return avgPos
end
Utility.AveragePoints = AveragePoints
end
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoidCache[player] = humanoid
end
return humanoid
end
end
end
--------------------------CHARACTER CONTROL-------------------------------
local CurrentIgnoreList
local function GetCharacter()
return Player and Player.Character
end
local function GetTorso()
local humanoid = findPlayerHumanoid(Player)
return humanoid and humanoid.RootPart
end
local function getIgnoreList()
if CurrentIgnoreList then
return CurrentIgnoreList
end
CurrentIgnoreList = {}
table.insert(CurrentIgnoreList, GetCharacter())
return CurrentIgnoreList
end
-----------------------------------PATHER--------------------------------------
local popupAdornee
local function getPopupAdorneePart()
--Handle the case of the adornee part getting deleted (camera changed, maybe)
if popupAdornee and not popupAdornee.Parent then
popupAdornee = nil
end
--If the adornee doesn't exist yet, create it
if not popupAdornee then
popupAdornee = Instance.new("Part")
popupAdornee.Name = "ClickToMovePopupAdornee"
popupAdornee.Transparency = 1
popupAdornee.CanCollide = false
popupAdornee.Anchored = true
popupAdornee.Size = Vector3.new(2, 2, 2)
popupAdornee.CFrame = CFrame.new()
popupAdornee.Parent = workspace.CurrentCamera
end
return popupAdornee
end
local activePopups = {}
local function createNewPopup(popupType)
local newModel = Instance.new("ImageHandleAdornment")
newModel.AlwaysOnTop = false
newModel.Transparency = 1
newModel.Size = ZERO_VECTOR2
newModel.SizeRelativeOffset = ZERO_VECTOR3
newModel.Image = "rbxasset://textures/ui/move.png"
newModel.ZIndex = 20
local radius = 0
if popupType == "DestinationPopup" then
newModel.Color3 = Color3.fromRGB(0, 175, 255)
radius = 1.25
elseif popupType == "DirectWalkPopup" then
newModel.Color3 = Color3.fromRGB(0, 175, 255)
radius = 1.25
elseif popupType == "FailurePopup" then
newModel.Color3 = Color3.fromRGB(255, 100, 100)
radius = 1.25
elseif popupType == "PatherPopup" then
newModel.Color3 = Color3.fromRGB(255, 255, 255)
radius = 1
newModel.ZIndex = 10
end
newModel.Size = Vector2.new(5, 0.1) * radius
local dataStructure = {}
dataStructure.Model = newModel
activePopups[#activePopups + 1] = newModel
function dataStructure:TweenIn()
local tweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)
local tween1 = TweenService:Create(newModel, tweenInfo, { Size = Vector2.new(2,2) * radius })
tween1:Play()
TweenService:Create(newModel, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, false, 0.1), { Transparency = 0, SizeRelativeOffset = Vector3.new(0, radius * 1.5, 0) }):Play()
return tween1
end
function dataStructure:TweenOut()
local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
local tween1 = TweenService:Create(newModel, tweenInfo, { Size = ZERO_VECTOR2 })
tween1:Play()
coroutine.wrap(function()
tween1.Completed:Wait()
for i = 1, #activePopups do
if activePopups[i] == newModel then
table.remove(activePopups, i)
break
end
end
end)()
return tween1
end
function dataStructure:Place(position, dest)
-- place the model at position
if not self.Model.Parent then
local popupAdorneePart = getPopupAdorneePart()
self.Model.Parent = popupAdorneePart
self.Model.Adornee = popupAdorneePart
--Start the 10-stud long ray 2.5 studs above where the tap happened and point straight down to try to find
--the actual ground 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, Player.Character })
self.Model.CFrame = CFrame.new(hitPoint) + Vector3.new(0, -radius,0)
end
end
return dataStructure
end
local function createPopupPath(points, numCircles)
-- creates a path with the provided points, using the path and number of circles provided
local popups = {}
local stopTraversing = false
local function killPopup(i)
-- kill all popups before and at i
for iter, v in pairs(popups) do
if iter <= i then
local tween = v:TweenOut()
spawn(function()
tween.Completed:Wait()
v.Model:Destroy()
end)
popups[iter] = nil
end
end
end
local function stopFunction()
stopTraversing = true
killPopup(#points)
end
spawn(function()
for i = 1, #points do
if stopTraversing then
break
end
local includeWaypoint = i % numCircles == 0
and i < #points
and (points[#points].Position - points[i].Position).magnitude > 4
if includeWaypoint then
local popup = createNewPopup("PatherPopup")
popups[i] = popup
local nextPopup = points[i+1]
popup:Place(points[i].Position, nextPopup and nextPopup.Position or points[#points].Position)
local tween = popup:TweenIn()
wait(0.2)
end
end
end)
return stopFunction, killPopup
end
local function Pather(character, endPoint, surfaceNormal)
local this = {}
this.Cancelled = false
this.Started = false
this.Finished = Instance.new("BindableEvent")
this.PathFailed = Instance.new("BindableEvent")
this.PathComputing = false
this.PathComputed = false
this.TargetPoint = endPoint
this.TargetSurfaceNormal = surfaceNormal
this.DiedConn = nil
this.SeatedConn = nil
this.MoveToConn = nil
this.CurrentPoint = 0
function this:Cleanup()
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
if this.MoveToConn then
this.MoveToConn:Disconnect()
this.MoveToConn = nil
end
if this.DiedConn then
this.DiedConn:Disconnect()
this.DiedConn = nil
end
if this.SeatedConn then
this.SeatedConn:Disconnect()
this.SeatedConn = nil
end
this.humanoid = nil
end
function this:Cancel()
this.Cancelled = true
this:Cleanup()
end
function this:OnPathInterrupted()
-- Stop moving
this.Cancelled = true
this:OnPointReached(false)
end
function this:ComputePath()
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
local success = false
if torso then
if this.PathComputed or this.PathComputing then return end
this.PathComputing = true
success = pcall(function()
this.pathResult = PathfindingService:FindPathAsync(torso.CFrame.p, this.TargetPoint)
end)
this.pointList = this.pathResult and this.pathResult:GetWaypoints()
this.PathComputing = false
this.PathComputed = this.pathResult and this.pathResult.Status == Enum.PathStatus.Success or false
end
return true
end
function this:IsValidPath()
if not this.pathResult then
this:ComputePath()
end
return this.pathResult.Status == Enum.PathStatus.Success
end
function this:OnPointReached(reached)
if reached and not this.Cancelled then
this.CurrentPoint = this.CurrentPoint + 1
if this.CurrentPoint > #this.pointList then
-- End of path reached
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
this.Finished:Fire()
this:Cleanup()
else
-- If next action == Jump, but the humanoid
-- is still jumping from a previous action
-- wait until it gets to the ground
if this.CurrentPoint + 1 <= #this.pointList then
local nextAction = this.pointList[this.CurrentPoint + 1].Action
if nextAction == Enum.PathWaypointAction.Jump then
local currentState = this.humanoid:GetState()
if currentState == Enum.HumanoidStateType.FallingDown or
currentState == Enum.HumanoidStateType.Freefall or
currentState == Enum.HumanoidStateType.Jumping then
this.humanoid.FreeFalling:Wait()
-- Give time to the humanoid's state to change
-- Otherwise, the jump flag in Humanoid
-- will be reset by the state change
wait(0.1)
end
end
end
-- Move to the next point
if this.setPointFunc then
this.setPointFunc(this.CurrentPoint)
end
local nextWaypoint = this.pointList[this.CurrentPoint]
if nextWaypoint.Action == Enum.PathWaypointAction.Jump then
this.humanoid.Jump = true
end
this.humanoid:MoveTo(nextWaypoint.Position)
end
else
this.PathFailed:Fire()
this:Cleanup()
end
end
function this:Start()
if CurrentSeatPart then
return
end
this.humanoid = findPlayerHumanoid(Player)
if FFlagUserNavigationFixClickToMoveInterruption and not this.humanoid then
this.PathFailed:Fire()
return
end
if this.Started then return end
this.Started = true
if SHOW_PATH then
-- choose whichever one Mike likes best
this.stopTraverseFunc, this.setPointFunc = createPopupPath(this.pointList, 4)
end
if #this.pointList > 0 then
if FFlagUserNavigationFixClickToMoveInterruption then
this.SeatedConn = this.humanoid.Seated:Connect(function(reached) this:OnPathInterrupted() end)
this.DiedConn = this.humanoid.Died:Connect(function(reached) this:OnPathInterrupted() end)
end
this.MoveToConn = this.humanoid.MoveToFinished:Connect(function(reached) this:OnPointReached(reached) end)
this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
this:OnPointReached(true) -- Move to first point
else
this.PathFailed:Fire()
if this.stopTraverseFunc then
this.stopTraverseFunc()
end
end
end
this:ComputePath()
if not this.PathComputed then
-- set the end point towards the camera and raycasted towards the ground in case we hit a wall
local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
local ray = Ray.new(offsetPoint, Vector3.new(0,-1,0)*50)
local newHitPart, newHitPos = RayCastIgnoreList(workspace, ray, getIgnoreList())
if newHitPart then
this.TargetPoint = newHitPos
end
-- try again
this:ComputePath()
end
return this
end
-------------------------------------------------------------------------
local function IsInBottomLeft(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function IsInBottomRight(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function CheckAlive(character)
local humanoid = findPlayerHumanoid(Player)
return humanoid ~= nil and humanoid.Health > 0
end
local function GetEquippedTool(character)
if character ~= nil then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
return child
end
end
end
end
local ExistingPather = nil
local ExistingIndicator = nil
local PathCompleteListener = nil
local PathFailedListener = nil
local function CleanupPath()
DrivingTo = nil
if ExistingPather then
ExistingPather:Cancel()
end
if PathCompleteListener then
PathCompleteListener:Disconnect()
PathCompleteListener = nil
end
if PathFailedListener then
PathFailedListener:Disconnect()
PathFailedListener = nil
end
if ExistingIndicator then
local obj = ExistingIndicator
local tween = obj:TweenOut()
local tweenCompleteEvent = nil
tweenCompleteEvent = tween.Completed:connect(function()
tweenCompleteEvent:Disconnect()
obj.Model:Destroy()
end)
ExistingIndicator = nil
end
end
local function getExtentsSize(Parts)
local maxX,maxY,maxZ = -math.huge,-math.huge,-math.huge
local minX,minY,minZ = math.huge,math.huge,math.huge
for i = 1, #Parts do
maxX,maxY,maxZ = math.max(maxX, Parts[i].Position.X), math.max(maxY, Parts[i].Position.Y), math.max(maxZ, Parts[i].Position.Z)
minX,minY,minZ = math.min(minX, Parts[i].Position.X), math.min(minY, Parts[i].Position.Y), math.min(minZ, Parts[i].Position.Z)
end
return Region3.new(Vector3.new(minX, minY, minZ), Vector3.new(maxX, maxY, maxZ))
end
local function inExtents(Extents, Position)
if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
return false
end
if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
return false
end
--ignoring Y for now
return true
end
local function showQuickPopupAsync(position, popupType)
local popup = createNewPopup(popupType)
popup:Place(position, Vector3.new(0,position.y,0))
local tweenIn = popup:TweenIn()
tweenIn.Completed:Wait()
local tweenOut = popup:TweenOut()
tweenOut.Completed:Wait()
popup.Model:Destroy()
popup = nil
end
local FailCount = 0
local function OnTap(tapPositions, goToPoint)
-- Good to remember if this is the latest tap event
local camera = workspace.CurrentCamera
local character = Player.Character
if not CheckAlive(character) then return end
-- This is a path tap position
if #tapPositions == 1 or goToPoint then
if camera then
local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
-- inivisicam stuff
local initIgnore = getIgnoreList()
local invisicamParts = {} --InvisicamModule and InvisicamModule:GetObscuredParts() or {}
local ignoreTab = {}
-- add to the ignore list
for i, v in pairs(invisicamParts) do
ignoreTab[#ignoreTab+1] = i
end
for i = 1, #initIgnore do
ignoreTab[#ignoreTab+1] = initIgnore[i]
end
--
local myHumanoid = findPlayerHumanoid(Player)
local hitPart, hitPt, hitNormal, hitMat = Utility.Raycast(ray, true, ignoreTab)
local hitChar, hitHumanoid = Utility.FindCharacterAncestor(hitPart)
local torso = GetTorso()
local startPos = torso.CFrame.p
if goToPoint then
hitPt = goToPoint
hitChar = nil
end
if hitChar and hitHumanoid and hitHumanoid.RootPart and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
CleanupPath()
if myHumanoid then
myHumanoid:MoveTo(hitPt)
end
-- Do shoot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
elseif hitPt and character and not CurrentSeatPart then
local thisPather = Pather(character, hitPt, hitNormal)
if thisPather:IsValidPath() then
FailCount = 0
thisPather:Start()
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(false)
end
CleanupPath()
local destinationPopup = createNewPopup("DestinationPopup")
destinationPopup:Place(hitPt, Vector3.new(0,hitPt.y,0))
local failurePopup = createNewPopup("FailurePopup")
local currentTween = destinationPopup:TweenIn()
ExistingPather = thisPather
ExistingIndicator = destinationPopup
PathCompleteListener = thisPather.Finished.Event:Connect(function()
if destinationPopup then
if ExistingIndicator == destinationPopup then
ExistingIndicator = nil
end
local tween = destinationPopup:TweenOut()
local tweenCompleteEvent = nil
tweenCompleteEvent = tween.Completed:Connect(function()
tweenCompleteEvent:Disconnect()
destinationPopup.Model:Destroy()
destinationPopup = nil
end)
end
if hitChar then
local humanoid = findPlayerHumanoid(Player)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
if humanoid then
humanoid:MoveTo(hitPt)
end
end
end)
PathFailedListener = thisPather.PathFailed.Event:Connect(function()
if FFlagUserNavigationFixClickToMoveInterruption then
CleanupPath()
end
if failurePopup then
failurePopup:Place(hitPt, Vector3.new(0,hitPt.y,0))
local failTweenIn = failurePopup:TweenIn()
failTweenIn.Completed:Wait()
local failTweenOut = failurePopup:TweenOut()
failTweenOut.Completed:Wait()
failurePopup.Model:Destroy()
failurePopup = nil
end
end)
else
if hitPt then
-- Feedback here for when we don't have a good path
local foundDirectPath = false
if (hitPt-startPos).Magnitude < 25 and (startPos.y-hitPt.y > -3) then
-- move directly here
if myHumanoid then
if myHumanoid.Sit then
myHumanoid.Jump = true
end
myHumanoid:MoveTo(hitPt)
foundDirectPath = true
end
end
coroutine.wrap(showQuickPopupAsync)(hitPt, foundDirectPath and "DirectWalkPopup" or "FailurePopup")
end
end
elseif hitPt and character and CurrentSeatPart then
local destinationPopup = createNewPopup("DestinationPopup")
ExistingIndicator = destinationPopup
destinationPopup:Place(hitPt, Vector3.new(0,hitPt.y,0))
destinationPopup:TweenIn()
DrivingTo = hitPt
local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
while wait() do
if CurrentSeatPart and ExistingIndicator == destinationPopup then
local ExtentsSize = getExtentsSize(ConnectedParts)
if inExtents(ExtentsSize, hitPt) then
local popup = destinationPopup
spawn(function()
local tweenOut = popup:TweenOut()
tweenOut.Completed:Wait()
popup.Model:Destroy()
end)
destinationPopup = nil
DrivingTo = nil
break
end
else
if CurrentSeatPart == nil and destinationPopup == ExistingIndicator then
DrivingTo = nil
OnTap(tapPositions, hitPt)
end
local popup = destinationPopup
spawn(function()
local tweenOut = popup:TweenOut()
tweenOut.Completed:Wait()
popup.Model:Destroy()
end)
destinationPopup = nil
break
end
end
end
end
elseif #tapPositions >= 2 then
if camera then
-- Do shoot
local avgPoint = Utility.AveragePoints(tapPositions)
local unitRay = camera:ScreenPointToRay(avgPoint.x, avgPoint.y)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
end
end
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function findAngleBetweenXZVectors(vec2, vec1)
return math.atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
local function DisconnectEvent(event)
if event then
event:Disconnect()
end
end
--[[ The ClickToMove Controller Class ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local ClickToMove = setmetatable({}, BaseCharacterController)
ClickToMove.__index = ClickToMove
function ClickToMove.new()
print("Instantiating Keyboard Controller")
local self = setmetatable(BaseCharacterController.new(), ClickToMove)
self.fingerTouches = {}
self.numUnsunkTouches = 0
-- PC simulation
self.mouse1Down = tick()
self.mouse1DownPos = Vector2.new()
self.mouse2DownTime = tick()
self.mouse2DownPos = Vector2.new()
self.mouse2UpTime = tick()
self.tapConn = nil
self.inputBeganConn = nil
self.inputChangedConn = nil
self.inputEndedConn = nil
self.humanoidDiedConn = nil
self.characterChildAddedConn = nil
self.onCharacterAddedConn = nil
self.characterChildRemovedConn = nil
self.renderSteppedConn = nil
self.humanoidSeatedConn = nil
self.running = false
return self
end
function ClickToMove:DisconnectEvents()
DisconnectEvent(self.tapConn)
DisconnectEvent(self.inputBeganConn)
DisconnectEvent(self.inputChangedConn)
DisconnectEvent(self.inputEndedConn)
DisconnectEvent(self.humanoidDiedConn)
DisconnectEvent(self.characterChildAddedConn)
DisconnectEvent(self.onCharacterAddedConn)
DisconnectEvent(self.renderSteppedConn)
DisconnectEvent(self.characterChildRemovedConn)
-- TODO: Resolve with ControlScript handling of seating for vehicles
DisconnectEvent(self.humanoidSeatedConn)
pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
end
function ClickToMove:OnTouchBegan(input, processed)
if self.fingerTouches[input] == nil and not processed then
self.numUnsunkTouches = self.numUnsunkTouches + 1
end
self.fingerTouches[input] = processed
end
function ClickToMove:OnTouchChanged(input, processed)
if self.fingerTouches[input] == nil then
self.fingerTouches[input] = processed
if not processed then
self.numUnsunkTouches = self.numUnsunkTouches + 1
end
end
end
function ClickToMove:OnTouchEnded(input, processed)
if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
self.numUnsunkTouches = self.numUnsunkTouches - 1
end
self.fingerTouches[input] = nil
end
function ClickToMove:OnCharacterAdded(character)
self:DisconnectEvents()
self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchBegan(input, processed)
-- Give back controls when they tap both sticks
local wasInBottomLeft = IsInBottomLeft(input.Position)
local wasInBottomRight = IsInBottomRight(input.Position)
if wasInBottomRight or wasInBottomLeft then
for otherInput, _ in pairs(self.fingerTouches) do
if otherInput ~= input then
local otherInputInLeft = IsInBottomLeft(otherInput.Position)
local otherInputInRight = IsInBottomRight(otherInput.Position)
if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
return
end
end
end
end
end
-- Cancel path when you use the keyboard controls.
if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
CleanupPath()
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
self.mouse1DownTime = tick()
self.mouse1DownPos = input.Position
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
self.mouse2DownTime = tick()
self.mouse2DownPos = input.Position
end
end)
self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchChanged(input, processed)
end
end)
self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
self:OnTouchEnded(input, processed)
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
self.mouse2UpTime = tick()
local currPos = input.Position
if self.mouse2UpTime - self.mouse2DownTime < 0.25 and (currPos - self.mouse2DownPos).magnitude < 5 then
local positions = {currPos}
OnTap(positions)
end
end
end)
self.tapConn = UserInputService.TouchTap:Connect(function(touchPositions, processed)
if not processed then
OnTap(touchPositions)
end
end)
local function computeThrottle(dist)
if dist > .2 then
return 0.5+(dist^2)/2
else
return 0
end
end
local lastSteer = 0
--kP = how much the steering corrects for the current error in driving angle
--kD = how much the steering corrects for how quickly the error in driving angle is changing
local kP = 1
local kD = 0.5
local function getThrottleAndSteer(object, point)
local throttle, steer = 0, 0
local oCF = object.CFrame
local relativePosition = oCF:pointToObjectSpace(point)
local relativeZDirection = -relativePosition.z
local relativeDistance = relativePosition.magnitude
-- throttle quadratically increases from 0-1 as distance from the selected point goes from 0-50, after 50, throttle is 1.
-- this allows shorter distance travel to have more fine-tuned control.
throttle = computeThrottle(math.min(1,relativeDistance/50))*math.sign(relativeZDirection)
local steerAngle = -math.atan2(-relativePosition.x, -relativePosition.z)
steer = steerAngle/(math.pi/4)
local steerDelta = steer - lastSteer
lastSteer = steer
local pdSteer = kP * steer + kD * steer
return throttle, pdSteer
end
local function Update()
if CurrentSeatPart then
if DrivingTo then
local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
CurrentSeatPart.ThrottleFloat = throttle
CurrentSeatPart.SteerFloat = steer
else
CurrentSeatPart.ThrottleFloat = 0
CurrentSeatPart.SteerFloat = 0
end
end
local cameraPos = workspace.CurrentCamera.CFrame.p
for i = 1, #activePopups do
local popup = activePopups[i]
popup.CFrame = CFrame.new(popup.CFrame.p, cameraPos)
end
end
RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update)
-- TODO: Resolve with control script seating functionality
-- local function onSeated(child, active, currentSeatPart)
-- if active then
-- if TouchJump and UserInputService.TouchEnabled then
-- TouchJump:Enable()
-- end
-- if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
-- CurrentSeatPart = currentSeatPart
-- end
-- else
-- CurrentSeatPart = nil
-- if TouchJump and UserInputService.TouchEnabled then
-- TouchJump:Disable()
-- end
-- end
-- end
local function OnCharacterChildAdded(child)
if UserInputService.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = true
end
end
if child:IsA('Humanoid') then
DisconnectEvent(self.humanoidDiedConn)
self.humanoidDiedConn = child.Died:Connect(function()
if ExistingIndicator then
DebrisService:AddItem(ExistingIndicator.Model, 1)
end
end)
-- self.humanoidSeatedConn = child.Seated:Connect(function(active, seat) onSeated(child, active, seat) end)
-- if child.SeatPart then
-- onSeated(child, true, child.SeatPart)
-- end
end
end
self.characterChildAddedConn = character.ChildAdded:Connect(function(child)
OnCharacterChildAdded(child)
end)
self.characterChildRemovedConn = character.ChildRemoved:Connect(function(child)
if UserInputService.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end)
for _, child in pairs(character:GetChildren()) do
OnCharacterChildAdded(child)
end
end
function ClickToMove:Start()
self:Enable(true)
end
function ClickToMove:Stop()
self:Enable(false)
end
function ClickToMove:Enable(enable)
if enable then
if not self.running then
if Player.Character then -- retro-listen
self:OnCharacterAdded(Player.Character)
end
self.onCharacterAddedConn = Player.CharacterAdded:Connect(function(char)
self:OnCharacterAdded(char)
end)
self.running = true
end
else
if self.running then
self:DisconnectEvents()
CleanupPath()
-- Restore tool activation on shutdown
if UserInputService.TouchEnabled then
local character = Player.Character
if character then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end
end
DrivingTo = nil
self.running = false
end
end
self.enabled = enable
end
return ClickToMove