Pinkstack/roblox-luau-corpus-text huggingface dataset:
--[[



]]



local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Http = game:GetService("HttpService")

local AvatarEditor = ReplicatedStorage.AvatarEditor
local Promise = require(AvatarEditor.Shared.Util.Promise)
local Colors = require(AvatarEditor.Shared.Colors)

local player = Players.LocalPlayer

local ids = nil
local skintone = nil
local scales = nil

local idProperties = {
	"BackAccessory",
	"FaceAccessory",
	"FrontAccessory",
	"HairAccessory",
	"HatAccessory",
	"NeckAccessory",
	"ShouldersAccessory",
	"WaistAccessory",
	"Face",
	"Head",
	"LeftArm",
	"LeftLeg",
	"RightArm",
	"RightLeg",
	"Torso",
	"ClimbAnimation",
	"FallAnimation",
	"IdleAnimation",
	"JumpAnimation",
	"RunAnimation",
	"SwimAnimation",
	"WalkAnimation",
	"Pants",
	"Shirt",
}

local scaleProperties = {
	"BodyTypeScale",
	--"DepthScale",
	"HeadScale",
	"HeightScale",
	"ProportionScale",
	"WidthScale",
}

local module = {}
module.UID = Http:GenerateGUID(false)


local function getHumanoid()
	local character = player.Character
	local humanoid = character and character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		return humanoid
	end
end


local function getHumanoidDescription()
	return Promise.new(function(resolve, reject)
		local humanoid = getHumanoid()
		if not humanoid then
			return Promise.reject("no humanoid")
		end
		
		local description = humanoid:GetAppliedDescription()
		if description then
			resolve(description)
		else
			reject("description not found")
		end
	end)
	:andThen(function(description)
		ids = {}
		for i, v in ipairs(idProperties) do
			local values = string.split(description[v], ",")
			for j, k in ipairs(values) do
				if k == "" or k == "0" then
					continue
				end
				ids[tonumber(k)] = true
			end
		end
		scales = {}
		for i, v in ipairs(scaleProperties) do
			scales[v] = description[v]
		end
		skintone = description.HeadColor
	end)
	:catch(warn)
	:finally(function()
		if ids == nil then
			ids = {}
		end
	end)
end


function module:Get()
	if ids == nil then
		getHumanoidDescription():await()
	end
	return ids
end


function module:GetTone()
	if skintone == nil then
		getHumanoidDescription():await()
	end
	return skintone
end


function module:GetScale()
	if scales == nil then
		getHumanoidDescription():await()
	end
	return scales
end


function module:Update()
	getHumanoidDescription():await()
	self.UID = Http:GenerateGUID(false)
end

return module
--// # key, ManOff


mouse.KeyUp:connect(function(key)
	if key=="h" then 
		veh.Lightbar.middle.Man:Stop()
		veh.Lightbar.middle.Wail.Volume = 3
		veh.Lightbar.middle.Yelp.Volume = 3
		veh.Lightbar.middle.Priority.Volume = 3
		script.Parent.Parent.Sirens.Man.BackgroundColor3 = Color3.fromRGB(62, 62, 62)
		veh.Lightbar.MAN.Transparency = 1
	end
end)
--[[ Derived classes are required to override and implement all of the following functions ]]

--
function BaseOcclusion:GetOcclusionMode()
	-- Must be overridden in derived classes to return an Enum.DevCameraOcclusionMode value
	warn("BaseOcclusion GetOcclusionMode must be overridden by derived classes")
	return nil
end

function BaseOcclusion:Enable(enabled)
	warn("BaseOcclusion Enable must be overridden by derived classes")
end

function BaseOcclusion:Update(dt, desiredCameraCFrame, desiredCameraFocus)
	warn("BaseOcclusion Update must be overridden by derived classes")
	return desiredCameraCFrame, desiredCameraFocus
end

return BaseOcclusion
-- Create ParticleEffect for specified part (being destroyed)	


local function createParticles(parentPart)
	local particles = Instance.new("ParticleEmitter")
	particles.Name = "Particles"
	particles.Parent = parentPart
	particles.Texture = "rbxassetid://3127279684" -- Points icon
	particles.Size = NumberSequence.new(.75)
	particles.Acceleration = Vector3.new(0, -15, 0)
	particles.Lifetime = NumberRange.new(0.5, 1.5)
	particles.Speed = NumberRange.new(15)
	particles.SpreadAngle = Vector2.new(30, 30)
	particles.Rate = 20
	
	local particleThread = coroutine.create(function()
		wait(0.75)
		particles.Enabled = false
		DebrisService:AddItem(particles, 0.75)
	end)
	coroutine.resume(particleThread)
	
	return particles
end

local function getHittableWhitelist()
	local map = mapManager.getMap()
	if not map then 
		return false
	end
	
	local highPoints = map:FindFirstChild("HighPoints")
	local mediumPoints = map:FindFirstChild("MediumPoints")
	local lowPoints = map:FindFirstChild("LowPoints")
	
	return {highPoints, mediumPoints, lowPoints}
end

local function getHit(player)
	-- Destroy parts touching the player's right hand if they are in the map and not a prop (and award points)
	local humanoidRootPart = player.Character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end
	
	local origin = humanoidRootPart.Position + PUNCH_HEIGHT_OFFSET -- Lower the origin
	local aimDirection = humanoidRootPart.CFrame.lookVector
	
	-- create a ray aiming the same way as player
	local ray = Ray.new(origin, aimDirection * PUNCH_RANGE)

	-- Create a whitelist for the map folders
	local hittableWhitelist = getHittableWhitelist()
	if not hittableWhitelist then return end
	
	-- raycast		
	local hit, position = workspace:FindPartOnRayWithWhitelist(ray, hittableWhitelist, true)
	
	return hit
end

local function handleHit(hit, player)
	local model = getModel(hit)
	if model and isModelDestroyed(hit, destroyedModels) then
		return
	end
		
	-- Get topmost ancestor (for cases where touched part is part of a model)
	local topAncestor = getTopAncestor(hit)

	-- Points type matching name of ancestor folder
	local pointsType = topAncestor.Parent.Name
	
	-- Add member to debris
	DebrisService:AddItem(topAncestor, 0.01)
			
	-- Add model to destroy model table to track before Debris does garbage
	-- collection and removes it
	table.insert(destroyedModels, topAncestor)
			
	-- Make part/model non-collidable
	if topAncestor:IsA("Model") then
		breakModel(topAncestor)
	else
		topAncestor.CanCollide = false
	end
	
	createSmoke(topAncestor)
	local debris = createDebris(topAncestor)
	createParticles(debris)

	-- Award points
	leaderboardManager:incrementStat(player, "Points", gameSettings.pointValues[pointsType])
end
--[[ Utility Functions ]]

--
local function IsFinite(num: number)
	return num == num and num ~= 1/0 and num ~= -1/0
end

local function IsFiniteVector3(vec3)
	return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end

local movementUpdateEvent = Instance.new("BindableEvent")
movementUpdateEvent.Name = "MovementUpdate"
movementUpdateEvent.Parent = script

coroutine.wrap(function()
	local PathDisplayModule = script.Parent:WaitForChild("PathDisplay")
	if PathDisplayModule then
		PathDisplay = require(PathDisplayModule)
	end
end)()
--[[
	Returns the item transparency binding for a single item.

	Used for coordinating slide + fade animations: image should fade to the inactive state as we are sliding.
]]


modules.getItemTransparency = function(self)
	return function(renderedIndex, index, mid)
		-- Avoid the same image being shown as selected when they are shown on the same page
		local isSelected = self.state.selectedIndex == index and renderedIndex == mid

		-- Offset from the current selected index
		local offset = mid - renderedIndex

		return self.position:map(function(position)
			if not self.isAnimating then
				return isSelected and TRANSPARENCY_SELECTED or TRANSPARENCY_NOT_SELECTED
			end

			local progress = 0
			if self.target:getValue() ~= 0 then
				progress = position / self.target:getValue()
			end

			local res
			if isSelected then
				-- Current selected (we should fade this out since we only set state AFTER the position
				-- animation completes)
				res = progress * TRANSPARENCY_NOT_SELECTED
			elseif self.formatTargetIndex(index, offset) == self.targetIndex then
				-- Target rendered index that we want to get to
				res = TRANSPARENCY_NOT_SELECTED * (1 - progress)
			else
				-- Every other image
				res = TRANSPARENCY_NOT_SELECTED
			end

			return res
		end)
	end
end

return modules
--//Script\\--


function whileTouching()
	script.Parent.AutomaticF.Disabled = false
end

function letGo()
	script.Parent.Lighted.Disabled = true
	script.Parent.LightedOff.Disabled = false
	wait(1)
	--wait(1.4)
	script.Parent.AutoFlush.Disabled = false
	script.Disabled = true
end
-- Written By Kip Turner, Copyright Roblox 2014



local newClickToMove = script:FindFirstChild("NewClickToMove")
if newClickToMove then
	local newClickToMoveFlagSuccess, newClickToMoveFlagEnabled = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserUseNewClickToMove")
	end)
	local useNewClickToMove = newClickToMoveFlagSuccess and newClickToMoveFlagEnabled
	if useNewClickToMove then
		return require(newClickToMove)
	end
end

local UIS = game:GetService("UserInputService")
local PathfindingService = game:GetService("PathfindingService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local DebrisService = game:GetService('Debris')
local ReplicatedStorage = game:GetService('ReplicatedStorage')

local CameraScript = script.Parent
local ClassicCameraModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('ClassicCamera'))

local Player = PlayerService.localPlayer
local MyMouse = Player:GetMouse()


local DirectPathEnabled = false
local SHOW_PATH = false

local Y_VECTOR3 = Vector3.new(0, 1, 0)
local XZ_VECTOR3 = Vector3.new(1, 0, 1)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local ZERO_VECTOR2 = Vector2.new(0, 0)

local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local GetPartsTouchingExtents = workspace.FindPartsInRegion3

local math_min = math.min
local math_max = math.max
local math_pi = math.pi
local math_floor = math.floor
local math_abs = math.abs
local math_deg = math.deg
local math_acos = math.acos
local math_sin = math.sin
local math_atan2 = math.atan2

local Vector3_new = Vector3.new
local Vector2_new = Vector2.new
local CFrame_new = CFrame.new

local CurrentSeatPart = nil
local DrivingTo = nil
--[[Engine]]


	--Torque Curve
	Tune.Horsepower		= 210		--	[TORQUE CURVE VISUAL]
	Tune.IdleRPM		= 700		--		https://www.desmos.com/calculator/2uo3hqwdhf
	Tune.PeakRPM		= 6000		--		Use sliders to manipulate values
	Tune.Redline		= 6700		--		Copy and paste slider values into the respective tune values
	Tune.EqPoint		= 5500
	Tune.PeakSharpness	= 7.5
	Tune.CurveMult		= 0.16
	
	--Incline Compensation
	Tune.InclineComp	= 1.7			-- Torque compensation multiplier for inclines (applies gradient from 0-90 degrees)
	
	--Misc
	Tune.RevAccel		= 100		-- RPM acceleration when clutch is off
	Tune.RevDecay		= 75		-- RPM decay when clutch is off
	Tune.RevBounce		= 500		-- RPM kickback from redline
	Tune.IdleThrottle	= 3			-- Percent throttle at idle
	Tune.ClutchTol		= 500		-- Clutch engagement threshold (higher = faster response)
-- <Region> CreateRegionFromPart(<BasePart> Part)


	--Creates Region from BasePart
	--Returns <Region>
local function CreateRegionFromPart(Part)
	local Center = Part.Position
	local Size = Part.Size
	
	local X, Y, Z = Part.CFrame:toEulerAnglesXYZ()
	local Rotation = Vector3.new(X, Y, Z)

	return CreateRegion(Center, Rotation, Size)
end
--- Registers the default commands, with an optional filter function or array of groups.


function Registry:RegisterDefaultCommands (arrayOrFunc)
	local isArray = type(arrayOrFunc) == "table"

	if isArray then
		arrayOrFunc = Util.MakeDictionary(arrayOrFunc)
	end

	self:RegisterCommandsIn(self.Cmdr.DefaultCommandsFolder, isArray and function (command)
		return arrayOrFunc[command.Group] or false
	end or arrayOrFunc)
end
-------- OMG HAX



r = game:service("RunService")


local damage = 5


local slash_damage = 12


sword = script.Parent.Handle
Tool = script.Parent


local SlashSound = Instance.new("Sound")
SlashSound.SoundId = "rbxasset://sounds\\swordslash.wav"
SlashSound.Parent = sword
SlashSound.Volume = .7


local UnsheathSound = Instance.new("Sound")
UnsheathSound.SoundId = "rbxasset://sounds\\unsheath.wav"
UnsheathSound.Parent = sword
UnsheathSound.Volume = 1


function blow(hit)
	local humanoid = hit.Parent:findFirstChild("Humanoid")
	local vCharacter = Tool.Parent
	local vPlayer = game.Players:playerFromCharacter(vCharacter)
	local hum = vCharacter:findFirstChild("Humanoid") -- non-nil if tool held by a character
	if humanoid~=nil and humanoid ~= hum and hum ~= nil then
		-- final check, make sure sword is in-hand

		local right_arm = vCharacter:FindFirstChild("Right Arm")
		if (right_arm ~= nil) then
			local joint = right_arm:FindFirstChild("RightGrip")
			if (joint ~= nil and (joint.Part0 == sword or joint.Part1 == sword)) then
				tagHumanoid(humanoid, vPlayer)
				humanoid:TakeDamage(damage)
				wait(1)
				untagHumanoid(humanoid)
			end
		end


	end
end


function tagHumanoid(humanoid, player)
	local creator_tag = Instance.new("ObjectValue")
	creator_tag.Value = player
	creator_tag.Name = "creator"
	creator_tag.Parent = humanoid
end

function untagHumanoid(humanoid)
	if humanoid ~= nil then
		local tag = humanoid:findFirstChild("creator")
		if tag ~= nil then
			tag.Parent = nil
		end
	end
end


function attack()
	damage = slash_damage
	SlashSound:play()
	local anim = Instance.new("StringValue")
	anim.Name = "toolanim"
	anim.Value = "Slash"
	anim.Parent = Tool
end


function swordUp()
	Tool.GripForward = Vector3.new(-1,0,0)
	Tool.GripRight = Vector3.new(0,1,0)
	Tool.GripUp = Vector3.new(0,0,1)
end

function swordOut()
	Tool.GripForward = Vector3.new(0,0,1)
	Tool.GripRight = Vector3.new(0,-1,0)
	Tool.GripUp = Vector3.new(-1,0,0)
end



Tool.Enabled = true

function onActivated()

	if not Tool.Enabled then
		return
	end

	Tool.Enabled = false

	local character = Tool.Parent;
	local humanoid = character.Humanoid
	if humanoid == nil then
		print("Humanoid not found")
		return 
	end

	

	attack()

	wait(.5)

	Tool.Enabled = true
end


function onEquipped()
	UnsheathSound:play()
end


script.Parent.Activated:connect(onActivated)
script.Parent.Equipped:connect(onEquipped)


connection = sword.Touched:connect(blow)
--when verifying friendships, we just check both the caller and the target's friend lists to see if the IDs are found in both lists



local friendlists = {} --Key: person's username. Value: table of online friends' user IDs

function CheckFriendship(caller,target)
	if caller == nil or target == nil or friendlists[caller.Name] == nil or friendlists[target.Name] == nil then return false end
	local f1 = false
	local f2 = false
	for _,v in pairs(friendlists[caller.Name]) do
		if v == target.userId then
			f1 = true
			break
		end
	end
	for _,v in pairs(friendlists[target.Name]) do
		if v == caller.userId then
			f2 = true
			break
		end
	end
	if f1 == true and f2 == true then
		return true
	else
		return false
	end
end

game.ReplicatedStorage.RemoteFunctions.SendFriendsOnlineData.OnServerEvent:connect(function(plr,tbl) --client should send a table of all friend userIDs
	if type(tbl) == "table" and #tbl <= 200 then
		for _,v in pairs(tbl) do
			if type(v) ~= "number" then return end
		end
		friendlists[plr.Name] = tbl
	end
end)

game.ServerStorage.RemoteFunctions.PlayersAreFriends.OnInvoke = function(caller,target)
	return CheckFriendship(caller,target)
end


game.Players.PlayerRemoving:connect(function(plr)
	friendlists[plr.Name] = nil
end)
--[=[
	@param optionB Option
	@return Option
	Returns `optionB` if the calling option has a value,
	otherwise returns None.

	```lua
	local optionA = Option.Some(32)
	local optionB = Option.Some(64)
	local opt = optionA:And(optionB)
	-- opt == optionB

	local optionA = Option.None
	local optionB = Option.Some(64)
	local opt = optionA:And(optionB)
	-- opt == Option.None
	```
]=]


function Option:And(optionB)
	if self:IsSome() then
		return optionB
	else
		return Option.None
	end
end
--[[
	Contains the logic to run a test plan and gather test results from it.

	TestRunner accepts a TestPlan object, executes the planned tests, and
	produces a TestResults object. While the tests are running, the system's
	state is contained inside a TestSession object.
]]



local Expectation = require(script.Parent.Expectation)
local TestEnum = require(script.Parent.TestEnum)
local TestSession = require(script.Parent.TestSession)
local LifecycleHooks = require(script.Parent.LifecycleHooks)

local RUNNING_GLOBAL = "__TESTEZ_RUNNING_TEST__"

local TestRunner = {
	environment = {}
}

function TestRunner.environment.expect(...)
	return Expectation.new(...)
end
------------------------------------------------------------------------
-- parse a break statement
-- * used in statements()
------------------------------------------------------------------------


function luaY:breakstat(ls)
  -- stat -> BREAK
  local fs = ls.fs
  local bl = fs.bl
  local upval = false
  while bl and not bl.isbreakable do
    if bl.upval then upval = true end
    bl = bl.previous
  end
  if not bl then
    luaX:syntaxerror(ls, "no loop to break")
  end
  if upval then
    luaK:codeABC(fs, "OP_CLOSE", bl.nactvar, 0, 0)
  end
  bl.breaklist = luaK:concat(fs, bl.breaklist, luaK:jump(fs))
end
--[[[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.T					,
		ToggleABS				= Enum.KeyCode.Y					,
		ToggleTransMode			= Enum.KeyCode.M					,
		ToggleMouseDrive		= Enum.KeyCode.R					,
		
		--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			,
	}
	
return Tune
--Main Control------------------------------------------------------------------------


Red = script.Parent.Red.Lamp
Yellow = script.Parent.Yellow.Lamp
Green = script.Parent.Green.Lamp
DRed = script.Parent.Red.DynamicLight
DYellow = script.Parent.Yellow.DynamicLight
DGreen = script.Parent.Green.DynamicLight
function Active()
	if Signal.Value == 0 then
		Green.Enabled = false
		Yellow.Enabled = false
		Red.Enabled = false
		DGreen.Enabled = false
		DYellow.Enabled = false
		DRed.Enabled = false
		GArrow.Enabled = false
		YArrow.Enabled = false
		FYellow.Enabled = false
		DGArrow.Enabled = false
		DYArrow.Enabled = false
		DFYellow.Enabled = false
	elseif Signal.Value == 1 then
		while Signal.Value == 1 do
		Green.Enabled = true
		Yellow.Enabled = false
		Red.Enabled = false
		DGreen.Enabled = true
		DYellow.Enabled = false
		DRed.Enabled = false
			while Signal.Value == 1 do
			YArrow.Enabled = false
			FYellow.Enabled = true
			GArrow.Enabled = false
			DRed.Enabled = false
			DYellow.Enabled = false
			DFYellow.Enabled = true
			DGArrow.Enabled = false
			wait(.5)
			FYellow.Enabled = false
			DFYellow.Enabled = false
			wait(.5)
			if Signal.Value ~= 1 then break end
			if Turn.Value == 1 then break end
		    if Turn.Value == 2 then break end
		    if Turn.Value == 3 then break end
		    if Turn.Value == 0 then break end
			end
		end
	elseif Signal.Value == 2 then
		Green.Enabled = false
		Yellow.Enabled = true
		Red.Enabled = false
		DGreen.Enabled = false
		DYellow.Enabled = true
		DRed.Enabled = false
		GArrow.Enabled = false
		FYellow.Enabled = false
		YArrow.Enabled = true
		FYellow.Enabled = false
		DGArrow.Enabled = false
		DYArrow.Enabled = true
		elseif Signal.Value == 3 then
		Green.Enabled = false
		Yellow.Enabled = false
		Red.Enabled = true
		DGreen.Enabled = false
		DYellow.Enabled = false
		DRed.Enabled = true
		GArrow.Enabled = false
		FYellow.Enabled = false
		YArrow.Enabled = false
		DGArrow.Enabled = false
		DFYellow.Enabled = false
		DYArrow.Enabled = false
	end
end
Signal.Changed:connect(Active)
--[[
	Called when the goal state changes value, or when the speed or damping has
	changed.
]]


function class:update(): boolean
	local goalValue = self._goalState:get(false)

	-- figure out if this was a goal change or a speed/damping change
	if goalValue == self._goalValue then
		-- speed/damping change
		local damping = unwrap(self._damping)
		if typeof(damping) ~= "number" then
			warn("mistypedSpringDamping", typeof(damping))
		elseif damping < 0 then
			warn("invalidSpringDamping", damping)
		else
			self._currentDamping = damping
		end

		local speed = unwrap(self._speed)
		if typeof(speed) ~= "number" then
			warn("mistypedSpringSpeed", nil, typeof(speed))
		elseif speed < 0 then
			warn("invalidSpringSpeed", nil, speed)
		else
			self._currentSpeed = speed
		end

		return false
	else
		-- goal change - reconfigure spring to target new goal
		self._goalValue = goalValue

		local oldType = self._currentType
		local newType = typeof(goalValue)
		self._currentType = newType

		local springGoals = unpackType(goalValue, newType)
		local numSprings = #springGoals
		self._springGoals = springGoals

		if newType ~= oldType then
			-- if the type changed, snap to the new value and rebuild the
			-- position and velocity tables
			self._currentValue = self._goalValue

			local springPositions = table.create(numSprings, 0)
			local springVelocities = table.create(numSprings, 0)
			for index, springGoal in ipairs(springGoals) do
				springPositions[index] = springGoal
			end
			self._springPositions = springPositions
			self._springVelocities = springVelocities

			-- the spring may have been animating before, so stop that
			SpringScheduler.remove(self)
			return true

			-- otherwise, the type hasn't changed, just the goal...
		elseif numSprings == 0 then
			-- if the type isn't animatable, snap to the new value
			self._currentValue = self._goalValue
			return true

		else
			-- if it's animatable, let it animate to the goal
			SpringScheduler.add(self)
			return false
		end
	end
end

local function Spring<T>(
	goalState,
	speed: number?,
	damping: number?
)
	-- apply defaults for speed and damping
	if not speed then
		speed = 10
	end

	if not damping then
		damping = 1
	end

	local dependencySet = {[goalState] = true}

	if xtypeof(speed) == "State" then
		dependencySet[speed] = true
	end

	if xtypeof(damping) == "State" then
		dependencySet[damping] = true
	end

	local self = setmetatable({
		type = "State",
		kind = "Spring",
		dependencySet = dependencySet,
		-- if we held strong references to the dependents, then they wouldn't be
		-- able to get garbage collected when they fall out of scope
		dependentSet = setmetatable({}, WEAK_KEYS_METATABLE),
		_speed = speed,
		_damping = damping,

		_goalState = goalState,
		_goalValue = nil,

		_currentType = nil,
		_currentValue = nil,
		_currentSpeed = unwrap(speed),
		_currentDamping = unwrap(damping),

		_springPositions = nil,
		_springGoals = nil,
		_springVelocities = nil
	}, CLASS_METATABLE)

	-- add this object to the goal state's dependent set
	goalState.dependentSet[self] = true
	self:update()

	return self
end

return Spring