Roblox/luau_corpus huggingface dataset:
--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local DynamicThumbstick = setmetatable({}, BaseCharacterController)
DynamicThumbstick.__index = DynamicThumbstick

function DynamicThumbstick.new()
	local self = setmetatable(BaseCharacterController.new(), DynamicThumbstick)

	self.moveTouchObject = nil
	self.moveTouchLockedIn = false
	self.moveTouchFirstChanged = false
	self.moveTouchStartPosition = nil

	self.startImage = nil
	self.endImage = nil
	self.middleImages = {}

	self.startImageFadeTween = nil
	self.endImageFadeTween = nil
	self.middleImageFadeTweens = {}

	self.isFirstTouch = true

	self.thumbstickFrame = nil

	self.onRenderSteppedConn = nil

	self.fadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
	self.fadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
	self.hasFadedBackgroundInPortrait = false
	self.hasFadedBackgroundInLandscape = false

	self.tweenInAlphaStart = nil
	self.tweenOutAlphaStart = nil

	return self
end
--s.Pitch = 0.7
--s.Pitch = 0.7

while true do
while s.Pitch<1 do
s.Pitch=s.Pitch+0.010
s:Play()
if s.Pitch>1 then
	s.Pitch=1
end
wait(0.001)
end
--[=[
	@param value any
	Set the top-level value of the property, but does not override
	any per-player data (e.g. set with `SetFor` or `SetFilter`).
	Any player without custom-set data will receive this new data.

	This is useful if certain players have specific values that
	should not be changed, but all other players should receive
	the same new value.

	```lua
	-- Using just 'Set' overrides per-player data:
	remoteProperty:SetFor(somePlayer, "CustomData")
	remoteProperty:Set("Data")
	print(remoteProperty:GetFor(somePlayer)) --> "Data"

	-- Using 'SetTop' does not override:
	remoteProperty:SetFor(somePlayer, "CustomData")
	remoteProperty:SetTop("Data")
	print(remoteProperty:GetFor(somePlayer)) --> "CustomData"
	```
]=]
function RemoteProperty:SetTop(value: any)
	self._value = value
	for _,player in ipairs(Players:GetPlayers()) do
		if self._perPlayer[player] == nil then
			self._rs:Fire(player, value)
		end
	end
end
--[[
	Adds to the velocity of the internal springs, on top of the existing
	velocity of this Spring. This doesn't affect position.

	If the type doesn't match the current type of the spring, an error will be
	thrown.
]]
function class:addVelocity(deltaValue)
	local deltaType = typeof(deltaValue)
	if deltaType ~= self._currentType then
		parseError("springTypeMismatch", nil, deltaType, self._currentType)
	end

	local springDeltas = unpackType(deltaValue, deltaType)

	for index, delta in ipairs(springDeltas) do
		self._springVelocities[index] += delta
	end

	SpringScheduler.add(self)
end
-- Connect change events
for _, i in pairs(script.Parent.Parent:GetChildren()) do
	if i:FindFirstChild("Close") then
		i:GetPropertyChangedSignal("Visible"):connect(update)
	end
end
--[ LOCALS ]--

local AFKStatus = {}
local AFKEvent = game.ReplicatedStorage:FindFirstChild("AFK")
-- Debris
local debris = ReplicatedStorage.Debris
local debrisParts = debris:GetChildren()

local destroyedModels = {}
--Tell if a seat is flipped
local function isFlipped(Seat)
	local UpVector = Seat.CFrame.upVector
	local Angle = math.deg(math.acos(UpVector:Dot(Vector3.new(0, 1, 0))))
	return Angle >= MIN_FLIP_ANGLE
end
--[[ The Module ]]--
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
local ClassicCamera = setmetatable({}, BaseCamera)
ClassicCamera.__index = ClassicCamera

function ClassicCamera.new()
	local self = setmetatable(BaseCamera.new(), ClassicCamera)

	self.isFollowCamera = false
	self.isCameraToggle = false
	self.lastUpdate = tick()
	self.cameraToggleSpring = Util.Spring.new(5, 0)

	return self
end

function ClassicCamera:GetCameraToggleOffset(dt: number)
	if self.isCameraToggle then
		local zoom = self.currentSubjectDistance

		if CameraInput.getTogglePan() then
			self.cameraToggleSpring.goal = math.clamp(Util.map(zoom, 0.5, self.FIRST_PERSON_DISTANCE_THRESHOLD, 0, 1), 0, 1)
		else
			self.cameraToggleSpring.goal = 0
		end

		local distanceOffset: number = math.clamp(Util.map(zoom, 0.5, 64, 0, 1), 0, 1) + 1
		return Vector3.new(0, self.cameraToggleSpring:step(dt)*distanceOffset, 0)
	end

	return Vector3.new()
end
--[[
game:GetService("RunService").Stepped:Connect(function()
	camera.CFrame = CFrame.new(player.Character.HumanoidRootPart.Position) * CFrame.new(0, 0, 30)
end)
--]]
local function onUpdate()
	if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
camera.CFrame = CFrame.new(player.Character.HumanoidRootPart.Position) * CFrame.new(0, 0, 30)
	end
end

RunService:BindToRenderStep("Control", Enum.RenderPriority.Input.Value, onUpdate)
--[=[
	@return deltaScreenPosition: Vector2

	:::info Only When Mouse Locked
	Getting the mouse delta is only intended for when the mouse is locked. If the
	mouse is _not_ locked, this will return a zero Vector2. The mouse can be locked
	using the `mouse:Lock()` and `mouse:LockCenter()` method.
]=]
function Mouse:GetDelta()
	return UserInputService:GetMouseDelta()
end
-- ANIMATOR

export type RawAnimationIndex = {
	id: string,
	weight: number
}

export type AnimationIndex = {
	animation: Animation,
	animationTrack: AnimationTrack,
	weight: number
}

export type AnimationList = Array<AnimationIndex>

export type AnimationTable = Map<string, AnimationList>

export type AnimatorInstanceType = AnimatorType & {
	StateFunctions: {},
	Humanoid: Humanoid,
	Character: CharacterType,
	CoreAnimations: AnimationTable,
	Tracks: Map<string, AnimationTrack>,
	CoreAnimationTrack: AnimationTrack?,
	CoreAnimation: Animation?,
	Connections: {},
	State: string,
	Speed: number,
}

export type AnimatorType = {
	Humanoid
}

export type AnimatorOptionsType = {
	priority: Enum.AnimationPriority?,
	speed: number?,
	transition: number?,
	weight: number,
}
--// # key, Yelp
mouse.KeyDown:connect(function(key)
	if key=="t" then 
		if veh.Lightbar.middle.Yelp.IsPlaying == true then
		veh.Lightbar.middle.Wail:Stop()
		veh.Lightbar.middle.Yelp:Stop()
		veh.Lightbar.middle.Priority:Stop()
		script.Parent.Parent.Sirens.Yelp.BackgroundColor3 = Color3.fromRGB(62, 62, 62)
		else
		veh.Lightbar.middle.Wail:Stop()
		veh.Lightbar.middle.Yelp:Play()
		veh.Lightbar.middle.Priority:Stop()
		script.Parent.Parent.Sirens.Yelp.BackgroundColor3 = Color3.fromRGB(215, 135, 110)
		script.Parent.Parent.Sirens.Wail.BackgroundColor3 = Color3.fromRGB(62, 62, 62)
		script.Parent.Parent.Sirens.Priority.BackgroundColor3 = Color3.fromRGB(62, 62, 62)
		end
	end
end)
------------------------------------------------------------------------
-- creates and sets a nil object
-- * used only in luaK:exp2RK()
------------------------------------------------------------------------
function luaK:nilK(fs)
  local k, v = {}, {}  -- TValue
  self:setnilvalue(v)
  -- cannot use nil as key; instead use table itself to represent nil
  self:sethvalue(k, fs.h)
  return self:addk(fs, k, v)
end
--[[Engine]]
	--Torque Curve
	Tune.Horsepower		= 10		--	[TORQUE CURVE VISUAL]
	Tune.IdleRPM		= 800		--		https://www.desmos.com/calculator/2uo3hqwdhf
	Tune.PeakRPM		= 3000		--		Use sliders to manipulate values
	Tune.Redline		= 6000		--		Copy and paste slider values into the respective tune values
	Tune.EqPoint		= 6000
	Tune.PeakSharpness	= 2
	Tune.CurveMult		= .2
	
	--Incline Compensation
	Tune.InclineComp	= 1.5		-- Torque compensation multiplier for inclines (applies gradient from 0-90 degrees)
	
	--Misc
	Tune.RevAccel		= 450		-- RPM acceleration when clutch is off
	Tune.RevDecay		= 150		-- RPM decay when clutch is off
	Tune.RevBounce		= 500		-- RPM kickback from redline
	Tune.IdleThrottle	= 0			-- Percent throttle at idle
	Tune.ClutchTol		= 500		-- Clutch engagement threshold (higher = faster response, lower = more stable RPM)
--[[Weight and CG]]
	Tune.Weight			= 1240		-- Total weight (in pounds)
	Tune.WeightBSize	= {			-- Size of weight brick (dimmensions in studs ; larger = more stable)
	 --[[Width]]		6		,
	 --[[Height]]		3.5		,
	 --[[Length]]		14		}
	Tune.WeightDist		= 50		-- Weight distribution (0 - on rear wheels, 100 - on front wheels, can be <0 or >100)
	Tune.CGHeight		= .8		-- Center of gravity height (studs relative to median of all wheels)
	Tune.WBVisible		= false		-- Makes the weight brick visible
	
	--Unsprung Weight
	Tune.FWheelDensity	= .1		-- Front Wheel Density
	Tune.RWheelDensity	= .1		-- Rear Wheel Density
	Tune.FWLgcyDensity	= 1			-- Front Wheel Density [PGS OFF]
	Tune.RWLgcyDensity	= 1			-- Rear Wheel Density [PGS OFF]
	
	Tune.AxleSize		= 2			-- Size of structural members (larger = more stable/carry more weight)
	Tune.AxleDensity	= .1		-- Density of structural members
--[[Wheel Alignment]]
	--[Don't physically apply alignment to wheels]
	--[Values are in degrees]
	Tune.FCamber		= 2
	Tune.RCamber		= 2.5
	Tune.FCaster		= 0
	Tune.FToe			= 0
	Tune.RToe			= 0
--sort by keys
newTable.xsort = function(tbl, sort)
	local keys = newTable.keys(tbl)
	
	table.sort(keys, sort)
	
	return function()
		if #keys == 0 then
			return nil
		end
		
		local nextValue = table.remove(keys, 1)
		
		return nextValue, tbl[nextValue]
	end
end
--edit the function below to return true when you want this response/prompt to be valid
--player is the player speaking to the dialogue, and dialogueFolder is the object containing the dialogue data
return function(player, dialogueFolder)
	local plrData = require(game.ReplicatedStorage.Source.Modules.Util):GetPlayerData(player)
	return plrData.Classes.Base.Smasher.NoHumor.Value == true
end
--Unequip a title for a player
function functions.UnequipTitle(plr: Player)

	local plrStats = plr.leaderstats
	local plrEquippedTitle = plrStats.EquippedTitle

	if plrEquippedTitle.Value ~= "" then

		local char = plr.Character
		if char.HumanoidRootPart:FindFirstChild("TITLE BILLBOARD GUI") then
			char.HumanoidRootPart["TITLE BILLBOARD GUI"]:Destroy()
		end

		plrEquippedTitle.Value = ""
	end
end
------------------------------------------------------------------------
--
-- * used in luaK:infix()
------------------------------------------------------------------------
function luaK:goiffalse(fs, e)
  local pc  -- pc of last jump
  self:dischargevars(fs, e)
  local k = e.k
  if k == "VNIL" or k == "VFALSE"then
    pc = self.NO_JUMP  -- always false; do nothing
  elseif k == "VTRUE" then
    pc = self:jump(fs)  -- always jump
  elseif k == "VJMP" then
    pc = e.info
  else
    pc = self:jumponcond(fs, e, true)
  end
  e.t = self:concat(fs, e.t, pc)  -- insert last jump in `t' list
  self:patchtohere(fs, e.f)
  e.f = self.NO_JUMP
end
------------------------------------------------------------
local pos1 = Danger.Position.X
local pos2 = Danger.Position.Y
local pos3 = Danger.Position.Z
--[[[Default Controls]]
	--Peripheral Deadzones
	Tune.Peripherals = {
		MSteerWidth				= 67		,	-- Mouse steering control width	(0 - 100% of screen width)
		MSteerDZone				= 10		,	-- Mouse steering deadzone (0 - 100%)
		
		ControlLDZone			= 5			,	-- Controller steering L-deadzone (0 - 100%)
		ControlRDZone			= 5			,	-- Controller steering R-deadzone (0 - 100%)
	}
	
	--Control Mapping
	Tune.Controls = {
		
	--Keyboard Controls
		--Mode Toggles
		ToggleTCS				=  Enum.KeyCode.ButtonR2				,
		ToggleABS				=  Enum.KeyCode.ButtonR2						,
		ToggleTransMode			= Enum.KeyCode.M					,
		ToggleMouseDrive		= Enum.KeyCode.ButtonR2				,
		
		--Primary Controls
		Throttle				= Enum.KeyCode.Up					,
		Brake					= Enum.KeyCode.Down					,
		SteerLeft				= Enum.KeyCode.Left					,
		SteerRight				= Enum.KeyCode.Right				,
		
		--Secondary Controls
		Throttle2				= Enum.KeyCode.W					,
		Brake2					= Enum.KeyCode.S					,
		SteerLeft2				= Enum.KeyCode.A					,
		SteerRight2				= Enum.KeyCode.D					,
		
		--Manual Transmission
		ShiftUp					= Enum.KeyCode.E					,
		ShiftDown				= Enum.KeyCode.Q					,
		Clutch					= Enum.KeyCode.LeftShift			,
		
		--Handbrake
		PBrake					= Enum.KeyCode.P					,
		
	--Mouse Controls
		MouseThrottle			= Enum.UserInputType.MouseButton1	,
		MouseBrake				= Enum.UserInputType.MouseButton2	,
		MouseClutch				= Enum.KeyCode.W					,
		MouseShiftUp			= Enum.KeyCode.E					,
		MouseShiftDown			= Enum.KeyCode.Q					,
		MousePBrake				= Enum.KeyCode.LeftShift			,
		
	--Controller Mapping
		ContlrThrottle			= Enum.KeyCode.ButtonR2				,
		ContlrBrake				= Enum.KeyCode.ButtonL2				,
		ContlrSteer				= Enum.KeyCode.Thumbstick1			,
		ContlrShiftUp			= Enum.KeyCode.ButtonY				,
		ContlrShiftDown			= Enum.KeyCode.ButtonX				,
		ContlrClutch			= Enum.KeyCode.ButtonR1				,
		ContlrPBrake			= Enum.KeyCode.ButtonL1				,
		ContlrToggleTMode		= Enum.KeyCode.DPadUp				,
		ContlrToggleTCS			= Enum.KeyCode.DPadDown				,
		ContlrToggleABS			= Enum.KeyCode.DPadRight			,
	}
--------LEFT DOOR --------

game.Workspace.doorleft.l11.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l22.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l33.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l41.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l52.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l63.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l71.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)

game.Workspace.doorleft.l43.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l61.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l53.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l72.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l62.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l51.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l21.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l31.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l12.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l13.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l32.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l23.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l42.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)
game.Workspace.doorleft.l73.BrickColor = BrickColor.new(game.Workspace.Lighting.defaultlights.Value)

game.Workspace.doorleft.pillar.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.Lighting.flashcurrent.Value = "12"
--		if _Tune.Config == "FWD" or _Tune.Config == "AWD" then for i,v in pairs(car.Wheels:GetChildren()) do if v.Name=="FL" or v.Name=="FR" or v.Name=="F" then table.insert(Drive,v) end end end
	
	--Power Rear Wheels
		if _Tune.Config == "RWD" or _Tune.Config == "AWD" then for i,v in pairs(car.Wheels:GetChildren()) do if v.Name=="RL" or v.Name=="RR" or v.Name=="R" then table.insert(Drive,v) end end end
	
	--Determine Wheel Size
	local wDia = 0 for i,v in pairs(Drive) do if v.Size.x>wDia then wDia = v.Size.x end end
	
	--Pre-Toggled PBrake
	for i,v in pairs(car.Wheels:GetChildren()) do if math.abs(v["#AV"].maxTorque.Magnitude-PBrakeForce)<1 then _PBrake=true end end
-- the toggleables
local walkSpeed = 16
local aggroRange = 60
local stateData = {
["state"] = "Idle",
["targ"] = nil,
["lastOrder"] = 0,
}

local animController = char:WaitForChild("AnimationController")

local preAnims = {
--returns the wielding player of this tool
function getPlayer()
	local char = Tool.Parent
	return game:GetService("Players"):GetPlayerFromCharacter(char)
end
--[[Rear]]--
Tune.RTireProfile	= 1			-- Tire profile, aggressive or smooth
Tune.RProfileHeight	= .45		-- Profile height, conforming to tire

Tune.RTireCompound	= 0			-- The more compounds you have, the harder your tire will get towards the middle, sacrificing grip for wear
Tune.RTireFriction	= 3		-- Your tire's friction in the best conditions.
----- Private Functions -----



local function ProcessReceipt(receipt_info)
	local ProductId = receipt_info.ProductId

	local player = Players:GetPlayerByUserId(receipt_info.PlayerId)

	if player == nil then
		return Enum.ProductPurchaseDecision.NotProcessedYet
	end

	local profile = PlayerData.Profiles[player]
	if not profile then return end

	local ProductToBuy, amount = EggConfig.GetEggFromProduct(ProductId)
	
	local pets = HatchBind:Invoke(player, ProductToBuy, amount)
	
	task.wait(1.5)
	
	Remotes.Eggs.EggProductPurchased:FireClient(player, ProductToBuy, pets)
	
end
--Door Sound
--Local Setting(Do not touch)
SP = script.Parent.Parent
Data = SP.Data
Floor = Data.Floor
Motor = Data.Motor
Car   = SP.Car
Door   = Data.Door
Direction = Data.Direction
TCarG = Car:GetChildren()
Floors = SP.Floors
TFloorsG = Floors:GetChildren()
TotalFloors = 0
--Hand
local RHL = character.RightHand:FindFirstChild("RightWristRigAttachment")
local RHR = character.LeftHand:FindFirstChild("LeftWristRigAttachment")
---SG Is  Here---
---Please Dont Change Anything Can Dont Work If You Change--

local repStorage = game:GetService("ReplicatedStorage")
local remote = repStorage:FindFirstChild("Rebirth")

remote.OnServerEvent:Connect(function(plr, x)
	if plr.leaderstats.Coins.Value >= 100 * (x.Value + 1) then
		x.Value = x.Value + 1
		plr.leaderstats.Coins.Value =  0
		plr.leaderstats.Clicks.Value =  0		
	end
end)
--[[Wheel Alignment]]
	--[Don't physically apply alignment to wheels]
	--[Values are in degrees]
	Tune.FCamber		= -0.2
	Tune.RCamber		= 0
	Tune.FToe			= 0
	Tune.RToe			= 0
-- New raycast parameters.
local CastParams = RaycastParams.new()
CastParams.IgnoreWater = true
CastParams.FilterType = Enum.RaycastFilterType.Blacklist
CastParams.FilterDescendantsInstances = {}
--[=[
	Folds an array of values or promises into a single value. The array is traversed sequentially.

	The reducer function can return a promise or value directly. Each iteration receives the resolved value from the previous, and the first receives your defined initial value.

	The folding will stop at the first rejection encountered.
	```lua
	local basket = {"blueberry", "melon", "pear", "melon"}
	Promise.fold(basket, function(cost, fruit)
		if fruit == "blueberry" then
			return cost -- blueberries are free!
		else
			-- call a function that returns a promise with the fruit price
			return fetchPrice(fruit):andThen(function(fruitCost)
				return cost + fruitCost
			end)
		end
	end, 0)
	```

	@since v3.1.0
	@param list {T | Promise<T>}
	@param reducer (accumulator: U, value: T, index: number) -> U | Promise<U>
	@param initialValue U
]=]
function Promise.fold(list, reducer, initialValue)
	assert(type(list) == "table", "Bad argument #1 to Promise.fold: must be a table")
	assert(isCallable(reducer), "Bad argument #2 to Promise.fold: must be a function")

	local accumulator = Promise.resolve(initialValue)
	return Promise.each(list, function(resolvedElement, i)
		accumulator = accumulator:andThen(function(previousValueResolved)
			return reducer(previousValueResolved, resolvedElement, i)
		end)
	end):andThen(function()
		return accumulator
	end)
end
--[[
	Assigns a new spot on a canvas for the current player.

	Complex logic for multiple cases, but we broke these down into a table form in Confluence.
	Refer to the tech spec for more information!

	https://confluence.rbx.com/display/SOCIAL/Surface+Art+Tech+Spec#SurfaceArtTechSpec-spot-assigning-logic

	Parameters:
	- canvas (Canvas): current Canvas object that player wants to apply an art on
]]
function PlayerSpots:assignSpot(player, canvas)
	-- Removes previous spot that has no art placed. This happens if player gets a spot but cancels before placing an art
	self:_removeUnusedSpots(player)

	local playerHasQuota = #self.placedSpots < self.quota
	local canvasHasSpot = not canvas:isFull()
	local playerHasArtOnSurface = self:_hasArtOnCanvas(canvas)

	if canvasHasSpot and playerHasQuota then
		return self:_assignRandomSpot(canvas)
	elseif canvasHasSpot and not playerHasQuota then
		self:_removeOldestArt()

		return self:_assignRandomSpot(canvas)
	elseif not canvasHasSpot and playerHasArtOnSurface then
		self:_removeOldestArtFromCanvas(canvas)

		return self:_assignRandomSpot(canvas)
	elseif canvasHasSpot and not playerHasQuota and not playerHasArtOnSurface then
		self:_removeOldestArtFromOtherCanvas(canvas)

		return self:_assignRandomSpot(canvas)
	elseif not canvasHasSpot and not playerHasArtOnSurface then
		return false, constants.Errors.SpotNotAvailable
	end
end
-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:
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 GetValueObject(name, defaultValue)
	local valueObj = script:FindFirstChild(name)
	if valueObj then
		return valueObj.Value
	end
	return defaultValue
end

local function LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
	local valueObj = script:FindFirstChild(name)
	
	if valueObj and valueObj:isA(valueType) then
		-- Value object exists and is the correct type, use its value
		externalProperties[name] = valueObj.Value
	elseif externalProperties[name] ~= nil then
		-- Create missing (or replace incorrectly-typed) valueObject with default value
		valueObj = Instance.new(valueType)
		valueObj.Name = name
		valueObj.Parent = script
		valueObj.Value = externalProperties[name]
	else
		print("externalProperties table has no entry for ",name)
		return
	end
	
	if updateFunction then
		if changedSignalConnections[name] then
			changedSignalConnections[name]:disconnect()
		end
		changedSignalConnections[name] = valueObj.Changed:connect(function(newValue)
			externalProperties[name] = newValue
			updateFunction()
		end)
	end
end

local function SetAndBoundsCheckAzimuthValues()
	minAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(externalProperties["CWAzimuthTravel"]))
	maxAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(externalProperties["CCWAzimuthTravel"]))
	useAzimuthLimits = externalProperties["UseAzimuthLimits"]
	if useAzimuthLimits then
		curAzimuthRad = math.max(curAzimuthRad, minAzimuthAbsoluteRad)
		curAzimuthRad = math.min(curAzimuthRad, maxAzimuthAbsoluteRad)
	end
end

local function SetAndBoundsCheckElevationValues()
	-- These degree values are the direct user input values. It is deliberate that they are
	-- ranged checked only against the extremes, and not against each other. Any time one
	-- is changed, both of the internal values in radians are recalculated. This allows for
	-- A developer to change the values in any order and for the end results to be that the
	-- internal values adjust to match intent as best as possible.
	local minElevationDeg = math.max(externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
	local maxElevationDeg = math.min(externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
	
	-- Set internal values in radians
	minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
	maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
	curElevationRad = math.max(curElevationRad, minElevationRad)
	curElevationRad = math.min(curElevationRad, maxElevationRad)
end

local function SetAndBoundsCheckDistanceValues()
	minDistance = externalProperties["MinDistance"]
	maxDistance = externalProperties["MaxDistance"]
	curDistance = math.max(curDistance, minDistance)
	curDistance = math.min(curDistance, maxDistance)
end
-- ONLY SUPPORTS ORDINAL TABLES (ARRAYS). Takes the range of entries in this table in the range [start, finish] and returns that range as a table.
Table.range = function (tbl, start, finish)
	return table.move(tbl, start, finish, 1, table.create(finish - start + 1))
end