Files
watrbx-game-engine/RobloxTest/Scripts/Wander.lua
T
2025-09-18 17:55:52 -04:00

228 lines
7.1 KiB
Lua

-- Our wanderer will always choose a point on a circle of this radius.
local WanderCircleRadius = 4.0
-- Number of seconds to wait before we start checking if there's ground in front of us.
local GroundCheckDelay = 0
-- Downwards stud distance we should check to see if there's ground at our intended destination
local GroundCheckDistance = 50
-- Information related to random spawns.
local SpawnChance = 0.0005 -- 0.05% chance to spawn elsewhere
-- Some helpful variables related to the player.
local Player = Game.Players.LocalPlayer
while Player.Character == nil do wait() end
local Character = Player.Character
local Humanoid = Character:WaitForChild("Humanoid")
local Torso = Humanoid.Torso
local PlayerID = string.gsub(Player.Name, "Player", "")
local Terrain = Game.Workspace.Terrain
-- Create a randomtable with precomputed random numbers.
-- We can't control the fact other people can call PRNG;
-- so, this table creates a reproducibly random set.
local function initrand(seed)
randomstats = {}
local randomtable = {}
math.randomseed(seed)
for i = 1,0xFFFF+1 do
randomtable[i] = math.random()
end
randomstats.seed = seed
randomstats.randomtable = randomtable
randomstats.randomindex = 0
randomstats.timescalled = 0
end
-- Choose the next precomputed random value and update our randomstats.
-- Cycle the random values once we reach the end.
local function random()
randomstats.timescalled = randomstats.timescalled + 1
randomstats.randomindex = 1 + randomstats.randomindex % #randomstats.randomtable
return randomstats.randomtable[randomstats.randomindex]
end
-- Given a CurrentIndex and a Table, calculate the next valid index
-- in the Table using a circular indexing mechanism.
local function NextIndex(CurrentIndex, Table)
return 1 + (CurrentIndex % #Table)
end
local function SetupTeleportLocations()
TeleportLocations = {}
TeleportLocationIdx = 0
local Radius = 350
local i = 1
local j = 1
-- Create 10 valid teleport locations in a circle.
while i <= 10 and j <= 1000 do
local Angle = random()
local x = math.cos(Angle) * Radius
local z = math.sin(Angle) * Radius
local Origin = Vector3.new(x, 500, z)
local ray = Ray.new(Origin, Vector3.new(0, -500, 0))
local part, endPoint = Game.Workspace:FindPartOnRay(ray)
if part ~= nil then
TeleportLocations[i] = endPoint + Vector3.new(0, 3.0, 0)
i = i + 1
end
j = j + 1
end
if #TeleportLocations < 1 then
TeleportLocations[1] = Torso.Position
end
end
local function TryTeleport(ForceTeleport)
if ForceTeleport or random() <= SpawnChance then
TeleportLocationIdx = NextIndex(TeleportLocationIdx, TeleportLocations)
return TeleportLocations[TeleportLocationIdx]
end
return nil
end
-- project a circle at the player's location and find a random point on that circle
-- return the theta and the target location
--
-- theta can be passed back into this function if you want another target in
-- the same direction as before
local function Wander(ChangeDirection, WanderTheta)
if ChangeDirection then
WanderTheta = random() * 2 * math.pi
end
local circleLocation = Torso.Position
local rotateX = WanderCircleRadius * math.cos(WanderTheta)
local rotateY = WanderCircleRadius * math.sin(WanderTheta)
local circleOffset = Vector3.new(rotateX, 0, rotateY)
local WanderTarget = circleLocation + circleOffset
return WanderTheta, WanderTarget
end
-- cast a downward ray directly beneath where we expect to walk
-- to see if there's any ground present
local function GroundInFront(WanderTarget, StartTime)
if tick() - StartTime < GroundCheckDelay then
return true
end
local ray = Ray.new(WanderTarget, Vector3.new(0, -1, 0) * GroundCheckDistance)
local part, endPoint = Game.Workspace:FindPartOnRay(ray)
return part ~= nil
end
local function IsTargetBlocked(WanderTarget)
local CharSize = Character:GetModelSize()
local UnitVecMult = 5
-- for several rays directed in front of us, see if we can find anything
for rad = -math.pi / 2.0, math.pi / 2.0, math.pi / 4.0 do
for yDelta = -CharSize.y / 2.0, CharSize.y / 2.0 + 0.1, 0.1 do
local direction = (WanderTarget - Torso.Position).unit
direction = direction + Vector3.new(math.cos(rad), 0, math.sin(rad))
local origin = Torso.Position + Vector3.new(0, yDelta, 0)
local ray = Ray.new(origin, UnitVecMult * direction)
local part, endPoint = Game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character, Terrain})
if part ~= nil then
return true
end
end
end
return false
end
initrand(PlayerID * 1000)
local ScriptStartDelay = 5
print(string.format("Starting Player%d in %d seconds...", PlayerID, ScriptStartDelay))
wait(ScriptStartDelay)
SetupTeleportLocations()
print("Start walking...")
local WanderTheta, WanderTarget = Wander(true, 0.0)
local StartTime = tick()
local PrintHeading = true
while not _G.EndGame do
-- First, try to teleport somewhere, based on a statistical chance.
local TeleportLocation = TryTeleport()
if TeleportLocation then
local msg = string.format("Teleporting to location (%f, %f, %f)",
TeleportLocation.x, TeleportLocation.y, TeleportLocation.z)
print(msg)
Torso.CFrame = CFrame.new(TeleportLocation.x, TeleportLocation.y, TeleportLocation.z)
print("Waiting 3 seconds before moving again.")
wait(3)
-- Otherwise, find a heading and march towards it.
else
WanderTheta, WanderTarget = Wander(false, WanderTheta)
local PrintedBlockedMessage = false
local BlockedCount = 0
-- If we get blocked, try to find a new heading and go for that. If we don't find
-- a new heading after a few tries, teleport to a new location. If we got blocked
-- because no ground was in front of us, mark it as an error.
while IsTargetBlocked(WanderTarget) or not GroundInFront(WanderTarget, StartTime) do
if not PrintedBlockedMessage then
if not GroundInFront(WanderTarget, StartTime) then
-- throw some sort of error here
-- and print our seed and random number iteration number
-- along with CFrame information
local pos = WanderTarget
local rs = randomstats
local error = string.format("Ground expected at target (%f, %f, %f), "..
"randomstats: {seed=%d, randomindex=%d, timescalled=%d}",
pos.x, pos.y, pos.z,
rs.seed, rs.randomindex, rs.timescalled)
if _G.TestingError == nil then
_G.TestingError = error
end
print(error)
end
print("Finding new heading...")
PrintedBlockedMessage = true
end
BlockedCount = BlockedCount + 1
if 20 == BlockedCount then
local TeleportLocation = TryTeleport(true)
local msg = string.format("Still blocked. Teleporting to location (%f, %f, %f)",
TeleportLocation.x, TeleportLocation.y, TeleportLocation.z)
print(msg)
Torso.CFrame = CFrame.new(TeleportLocation.x, TeleportLocation.y, TeleportLocation.z)
PrintHeading = false
break
end
PrintHeading = true
WanderTheta, WanderTarget = Wander(true, WanderTheta)
end
if PrintHeading then
PrintHeading = false
local Pos = Torso.Position
print(string.format("Heading is %.3f away from (%f, %f, %f)", WanderTheta, Pos.x, Pos.y, Pos.z))
end
Humanoid:MoveTo(WanderTarget, Terrain)
wait()
end
end
print("Caught _G.EndGame.")