mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
228 lines
7.1 KiB
Lua
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.")
|