Pinkstack/roblox-luau-corpus-text huggingface dataset:
-- Variables (best not to touch these!)


local button = script.Parent.Parent
local car = script.Parent.Parent.Parent.Car.Value
local sound = script.Parent.Start
local st = script.Parent.Stall
st.Parent = car.DriveSeat
sound.Parent = car.DriveSeat -- What brick the start sound is playing from.
button.MouseButton1Click:connect(function() -- Event when the button is clicked
	if script.Parent.Parent.Text == "Engine: Off" then -- If the text says it's off then..
		sound:Play() -- Startup sound plays..
		wait(1.1) -- I don't know what to put for this
		script.Parent.Parent.Parent.IsOn.Value = true -- The car is is on, or in other words, start up the car.
		button.Text = "Engine: On"  -- You don't really need this, but I would keep it.
	else -- If it's on then when you click the button,
		st:play()
		script.Parent.Parent.Parent.IsOn.Value = false -- The car is turned off.
		button.Text = "Engine: Off"
	end -- Don't touch this.
end) -- And don't touch this either.
-----


R = false
UIS.InputBegan:connect(function(i,GP) if not GP then if i.KeyCode == Enum.KeyCode.C then
if R == false then
script.Parent.Right.ImageColor3 = Color3.fromRGB(142, 255, 169)
R = true
else
script.Parent.Right.ImageColor3 = Color3.fromRGB(255,255,255)
R = false
end
end end end)
--[[ Local Constants ]]

--
local UNIT_Z = Vector3.new(0,0,1)
local X1_Y0_Z1 = Vector3.new(1,0,1)	--Note: not a unit vector, used for projecting onto XZ plane

local DEFAULT_DISTANCE = 12.5	-- Studs
local PORTRAIT_DEFAULT_DISTANCE = 25		-- Studs
local FIRST_PERSON_DISTANCE_THRESHOLD = 1.0 -- Below this value, snap into first person
--// If youre here to add your team go to the module and add it in there, just tryna to clean this up alittle


local WhiteListedTeams = require(game.ReplicatedStorage.MotionDetectorsWhitelist.StandartPermission)

Detector.Touched:Connect(
	function(boop)
		if boop.Parent:FindFirstChild("Humanoid") then
			local char = boop.Parent
			local Player = game:GetService("Players"):GetPlayerFromCharacter(char)
			if WhiteListedTeams[Player.Team] ==nil then
				if not isTouched then  
					isTouched = true 

					wait(0.1)
					script.Parent.Alarm:Play()
					Light.Enabled = true
					L1.BrickColor = BrickColor.new ("Persimmon")
					L12.BrickColor = BrickColor.new ("Persimmon")
					L2.BrickColor = BrickColor.new ("Persimmon")
					L22.BrickColor = BrickColor.new ("Persimmon")
					wait(5)
					L1.BrickColor = BrickColor.new ("Pastel yellow")
					L12.BrickColor = BrickColor.new ("Pastel yellow")
					L2.BrickColor = BrickColor.new ("Pastel yellow")
					L22.BrickColor = BrickColor.new ("Pastel yellow")
					Light.Enabled = false
					wait(RESET_SECONDS)
					isTouched = false 
				end
			end
		end
	end
)
--------LEFT DOOR --------


game.Workspace.doorleft.l11.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l23.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l32.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l41.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l53.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l62.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
game.Workspace.doorleft.l71.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)

game.Workspace.doorleft.pillar.BrickColor = BrickColor.new(game.Workspace.Lighting.cyan.Value)
------------------
--SPAWNING--
------------------



miked=script.Parent

itlh=miked.Torso:findFirstChild("Left Hip")
itlh.Part0=miked.Torso
itlh.Part1=miked:findFirstChild("Left Leg")
itlh.C0=CFrame.new(-1, -1, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0)

itrh=miked.Torso:findFirstChild("Right Hip")
itrh.Part0=miked.Torso
itrh.Part1=miked:findFirstChild("Right Leg")
itrh.C0=CFrame.new(1, -1, 0, 0, 0, 1, 0, 1, 0, -1, 0, 0)

itls=miked.Torso:findFirstChild("Left Shoulder")
itls.Part1=miked.Torso
itls.C0=CFrame.new(2, 0.5, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0)
itls.Part0=miked:findFirstChild("Left Arm")

itrs=miked.Torso:findFirstChild("Right Shoulder")
itrs.Part1=miked.Torso
itrs.C0=CFrame.new(-2, 0.5, 0, 0, 0, 1, 0, 1, 0, -1, 0, 0)
itrs.Part0=miked:findFirstChild("Right Arm")

miked.Head:makeJoints()
-- / Functions / --


EventModule.ActivateEvent = function()
	local RandomPlayer = PlayingTeam:GetPlayers()[math.random(1, #PlayingTeam:GetPlayers())]

	if RandomPlayer then
		local Character = RandomPlayer.Character or RandomPlayer.CharacterAdded:Wait()
		
		if Character then
			local Humanoid = Character:WaitForChild("Humanoid")
			
			if Humanoid then	
				local HumanoidDescription = Humanoid:GetAppliedDescription()
				
				HumanoidDescription.HeadScale = 3
				
				Humanoid:ApplyDescription(HumanoidDescription)
				
				local Head = Character:WaitForChild("Head")
				
				if Head then
					Head.Massless = true
					Head.CanCollide = false
				end

			end
		end
	end
end
--[=[
	Cleans up all tasks and removes them as entries from the Maid.

	:::note
	Signals that are already connected are always disconnected first. After that
	any signals added during a cleaning phase will be disconnected at random times.
	:::

	:::tip
	DoCleaning() may be recursively invoked. This allows the you to ensure that
	tasks or other tasks. Each task will be executed once.

	However, adding tasks while cleaning is not generally a good idea, as if you add a
	function that adds itself, this will loop indefinitely.
	:::
]=]


function Maid:DoCleaning()
	local tasks = self._tasks

	-- Disconnect all events first as we know this is safe
	for index, job in pairs(tasks) do
		if typeof(job) == "RBXScriptConnection" then
			tasks[index] = nil
			job:Disconnect()
		end
	end

	-- Clear out tasks table completely, even if clean up tasks add more tasks to the maid
	local index, job = next(tasks)
	while job ~= nil do
		tasks[index] = nil
		if type(job) == "function" then
			job()
		elseif type(job) == "thread" then
			task.cancel(job)
		elseif typeof(job) == "RBXScriptConnection" then
			job:Disconnect()
		elseif job.Destroy then
			job:Destroy()
		end
		index, job = next(tasks)
	end
end
--game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, false)



currentCamera.FieldOfView = customMenuFieldOfView
currentCamera.CameraType = Enum.CameraType.Scriptable
currentCamera.CFrame = spawnItems.CameraPart.CFrame
gameVersion.Text = configuration.GameVersionInfo.Value.." "..game.PlaceVersion
versionOpen:Play()
blurOpen:Play()
fade.Visible = true
Sounds.MenuMusic.Playing = configuration.MenuMusic.Value
menuMusicOn:Play()
wait(longLength)
fadeOpen:Play()
wait(longLength * 0.5)
buttonsOpen:Play()
titleOpen:Play()
--[[Engine]]


	--Torque Curve
	Tune.Horsepower		= 553		--	[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		= 150		-- 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)
--[[
	Package link auto-generated by Rotriever
]]


local PackageIndex = script.Parent.Parent.Parent._Index

local Package = require(PackageIndex["LuauPolyfill-2fca3173-0.3.4"]["LuauPolyfill"])

export type Array<T> = Package.Array<T> 
export type AssertionError = Package.AssertionError
export type Error = Package.Error
export type Map<T, V> = Package.Map<T, V> 
export type Object = Package.Object
export type PromiseLike<T> = Package.PromiseLike<T> 
export type Promise<T> = Package.Promise<T> 
export type Set<T> = Package.Set<T> 
export type Symbol = Package.Symbol
export type Timeout = Package.Timeout
export type Interval = Package.Interval
export type WeakMap<T, V> = Package.WeakMap<T, V> 


return Package
----------------------------------------------------------------------------------------------------
--------------------=[ OUTROS ]=--------------------------------------------------------------------
----------------------------------------------------------------------------------------------------


	,FastReload = true					--- Automatically operates the bolt on reload if needed
	,SlideLock = true
	,MoveBolt = false
	,BoltLock = false
	,CanBreachDoor = false
	,CanBreak = true					--- Weapon can jam?
	,JamChance = 800					--- This old piece of brick doesn't work fine >;c
	,IncludeChamberedBullet = true		--- Include the chambered bullet on next reload
	,Chambered = true					--- Start with the gun chambered?
	,LauncherReady = false				--- Start with the GL ready?
	,CanCheckMag = true					--- You can check the magazine
	,ArcadeMode = false					--- You can see the bullets left in magazine
	,RainbowMode = false				--- Operation: Party Time xD
	,ModoTreino = false					--- Surrender enemies instead of killing them
	,GunSize = 1
	,GunFOVReduction = 5

	,BoltExtend = Vector3.new(0, 0, 0.225)
	,SlideExtend = Vector3.new(0, 0, 0.225)
--[[ Controls State Management ]]

--
local onControlsChanged = nil
if IsTouchDevice then
	createTouchGuiContainer()
	onControlsChanged = function()
		local newModuleToEnable = nil
		if not IsUserChoice then
			if DevMovementMode == Enum.DevTouchMovementMode.Thumbstick then
				newModuleToEnable = ControlModules.Thumbstick
				isJumpEnabled = true
			elseif DevMovementMode == Enum.DevTouchMovementMode.Thumbpad then
				newModuleToEnable = ControlModules.Thumbpad
				isJumpEnabled = true
			elseif DevMovementMode == Enum.DevTouchMovementMode.DPad then
				newModuleToEnable = ControlModules.DPad
			elseif DevMovementMode == Enum.DevTouchMovementMode.ClickToMove then
				-- Managed by CameraScript
				newModuleToEnable = nil
			elseif DevMovementMode == Enum.DevTouchMovementMode.Scriptable then
				newModuleToEnable = nil
			end
		else
			if UserMovementMode == Enum.TouchMovementMode.Default or UserMovementMode == Enum.TouchMovementMode.Thumbstick then
				newModuleToEnable = ControlModules.Thumbstick
				isJumpEnabled = true
			elseif UserMovementMode == Enum.TouchMovementMode.Thumbpad then
				newModuleToEnable = ControlModules.Thumbpad
				isJumpEnabled = true
			elseif UserMovementMode == Enum.TouchMovementMode.DPad then
				newModuleToEnable = ControlModules.DPad
			elseif UserMovementMode == Enum.TouchMovementMode.ClickToMove then
				-- Managed by CameraScript
				newModuleToEnable = nil
			end
		end
		setClickToMove()
		if newModuleToEnable ~= CurrentControlModule then
			if CurrentControlModule then
				CurrentControlModule:Disable()
			end
			setJumpModule(isJumpEnabled)
			CurrentControlModule = newModuleToEnable
			if CurrentControlModule and not IsModalEnabled then
				CurrentControlModule:Enable()
				if isJumpEnabled then TouchJumpModule:Enable() end
			end
		end
	end
elseif UserInputService.KeyboardEnabled then
	onControlsChanged = function()
		-- NOTE: Click to move still uses keyboard. Leaving cases in case this ever changes.
		local newModuleToEnable = nil
		if not IsUserChoice then
			if DevMovementMode == Enum.DevComputerMovementMode.KeyboardMouse then
				newModuleToEnable = ControlModules.Keyboard
			elseif DevMovementMode == Enum.DevComputerMovementMode.ClickToMove then
				-- Managed by CameraScript
				newModuleToEnable = ControlModules.Keyboard
			end 
		else
			if UserMovementMode == Enum.ComputerMovementMode.KeyboardMouse or UserMovementMode == Enum.ComputerMovementMode.Default then
				newModuleToEnable = ControlModules.Keyboard
			elseif UserMovementMode == Enum.ComputerMovementMode.ClickToMove then
				-- Managed by CameraScript
				newModuleToEnable = ControlModules.Keyboard
			end
		end
		if newModuleToEnable ~= CurrentControlModule then
			if CurrentControlModule then
				CurrentControlModule:Disable()
			end
			CurrentControlModule = newModuleToEnable
			if CurrentControlModule then
				CurrentControlModule:Enable()
			end
		end
	end
elseif UserInputService.GamepadEnabled then
	onControlsChanged = function()
		-- nil for now, probably needs some stuff later
	end
end
--		print("2!!!",hum.Health)


		if hum.Health<1 then return end
	end
	
	if string.upper(Type) == "AOE" then
		-- AoE
		explosion = Instance.new("Explosion")
		explosion.Position = bullet.Position
		explosion.ExplosionType = ("NoCraters")
		-- 		find instigator tag
		--local creator = script.Parent:findFirstChild("creator")
		--if creator ~= nil then
		--	explosion.Hit:connect(function(part, distance)  onPlayerBlownUp(part, distance, creator) end)
		--end
		explosion.Parent = game.Workspace		
	else -- SINGLE
		local hum = hit.Parent:FindFirstChild("Humanoid")
	    if hum~= nil then
	        hum:TakeDamage(damage)
	    end
	end
	
	connection:disconnect()
	wait(.1)
	bullet:remove()
end
-- TODO: Optimize for vertical faces


function BitOps.getConnectivityForFace(connectivity, face)
	local result = 0

	local bitIndexes = BitOps.CONNECTIVITY_INDEXES_FOR_FACE[tostring(BitOps.normalizeVec3(face))]
	for _, index in ipairs(bitIndexes) do
		result = bit32.lshift(result, 1)
		result = bit32.bor(result, bit32.extract(connectivity, index))
	end

	return result
end

function BitOps.getVerticalFaceFromOccupancy(occupancy)
	local horizontalFace
	for _, face in pairs(BitOps.FACES) do
		horizontalFace = face.Y ~= 0
		if not horizontalFace and occupancy == BitOps.OCCUPANCY_FACE_MASK[tostring(face)] then
			return face
		end
	end

	return false
end

function BitOps.applyFaceOccupancy(targetOccupancy, targetFace, appliedOccupancy, appliedFace)
	local appliedOccupancyFaceBit = bit32.extract(appliedOccupancy, BitOps.OCCUPANCY_INDEX_FOR_FACE[tostring(BitOps.normalizeVec3(appliedFace))])
	if appliedOccupancyFaceBit == 1 then
		targetOccupancy =
			bit32.replace(targetOccupancy, appliedOccupancyFaceBit, BitOps.OCCUPANCY_INDEX_FOR_FACE[tostring(BitOps.normalizeVec3(targetFace))])
	end

	return targetOccupancy
end

function BitOps.removeFaceOccupancy(targetOccupancy, targetFace, appliedOccupancy, appliedFace)
	local appliedOccupancyFaceBit = bit32.extract(appliedOccupancy, BitOps.OCCUPANCY_INDEX_FOR_FACE[tostring(BitOps.normalizeVec3(appliedFace))])
	if appliedOccupancyFaceBit == 1 then
		targetOccupancy =
		bit32.replace(targetOccupancy, 0, BitOps.OCCUPANCY_INDEX_FOR_FACE[tostring(BitOps.normalizeVec3(targetFace))])
	end

	return targetOccupancy
end

function BitOps.applyFaceConnectivity(targetConnectivity, targetFace, appliedConnectivity, appliedFace)
	local appliedConnectivityFaceBits = BitOps.getConnectivityForFace(appliedConnectivity, appliedFace)
	local targetConnectivityFaceBits = BitOps.getConnectivityForFace(targetConnectivity, targetFace)
	local newTargetConnectivityFaceBits = bit32.bor(targetConnectivityFaceBits, appliedConnectivityFaceBits)

	targetConnectivity = BitOps.setFaceConnectivity(targetConnectivity, targetFace, newTargetConnectivityFaceBits)

	return targetConnectivity
end
--Remove with FFlagPlayerScriptsBindAtPriority


function BaseCamera:OnKeyDown(input, processed)
	if not self.hasGameLoaded and VRService.VREnabled then
		return
	end

	if processed then
		return
	end

	if self.distanceChangeEnabled then
		if input.KeyCode == Enum.KeyCode.I then
			self:SetCameraToSubjectDistance( self.currentSubjectDistance - 5 )
		elseif input.KeyCode == Enum.KeyCode.O then
			self:SetCameraToSubjectDistance( self.currentSubjectDistance + 5 )
		end
	end

	if self.panBeginLook == nil and self.keyPanEnabled then
		if input.KeyCode == Enum.KeyCode.Left then
			self.turningLeft = true
		elseif input.KeyCode == Enum.KeyCode.Right then
			self.turningRight = true
		elseif input.KeyCode == Enum.KeyCode.Comma then
			local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), -math.pi*0.1875, math.pi*0.25)
			if angle ~= 0 then
				self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
				self.lastUserPanCamera = tick()
				self.lastCameraTransform = nil
			end
		elseif input.KeyCode == Enum.KeyCode.Period then
			local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), math.pi*0.1875, math.pi*0.25)
			if angle ~= 0 then
				self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
				self.lastUserPanCamera = tick()
				self.lastCameraTransform = nil
			end
		elseif input.KeyCode == Enum.KeyCode.PageUp then
			self.rotateInput = self.rotateInput + Vector2.new(0,math.rad(15))
			self.lastCameraTransform = nil
		elseif input.KeyCode == Enum.KeyCode.PageDown then
			self.rotateInput = self.rotateInput + Vector2.new(0,math.rad(-15))
			self.lastCameraTransform = nil
		end
	end
end
--[[Here are some suggested ID's for you, Note these are mostly club/Dubstep songs
	1.145060711
	2.146180801
	3.152357361
	4.152357361	
	5.145579822
	6.148492408
	7.142720946
	8.142372565
	9.152779074
	10.145915908
	11.138855854
--]]
--Purchase and receive gamepasses


main.marketplaceService.PromptGamePassPurchaseFinished:Connect(function(player,assetId,isPurchased)
	local pdata = main.pd[player]
	if isPurchased and pdata then
		local validGamepass = main.permissions.gamepasses[tostring(assetId)]
		if validGamepass and not main:GetModule("cf"):FindValue(pdata.Gamepasses, assetId) then
			main:GetModule("PlayerData"):InsertStat(player, "Gamepasses", assetId)
			main:GetModule("cf"):RankPlayerSimple(player, validGamepass.Rank)
			if not main:GetModule("cf"):FindValue(main.products, assetId) then
				main:GetModule("cf"):FormatAndFireNotice(player, "UnlockRank", main:GetModule("cf"):GetRankName(validGamepass.Rank))
			end
			main:GetModule("cf"):CheckAndRankToDonor(player, pdata, assetId)
		end
	end
end)




return module
-- ProfileVersionQuery object:



local ProfileVersionQuery = {
	--[[
		_profile_store = profile_store,
		_profile_key = profile_key,
		_sort_direction = sort_direction,
		_min_date = min_date,
		_max_date = max_date,

		_query_pages = pages, -- [DataStoreVersionPages]
		_query_index = index, -- [number]
		_query_failure = false,

		_is_query_yielded = false,
		_query_queue = {},
	--]]
}
ProfileVersionQuery.__index = ProfileVersionQuery

function ProfileVersionQuery:_MoveQueue()
	while #self._query_queue > 0 do
		local queue_entry = table.remove(self._query_queue, 1)
		task.spawn(queue_entry)
		if self._is_query_yielded == true then
			break
		end
	end
end

function ProfileVersionQuery:NextAsync(_is_stacking) --> [Profile] or nil

	if self._profile_store == nil then
		return nil
	end

	local profile
	local is_finished = false

	local function query_job()

		if self._query_failure == true then
			is_finished = true
			return
		end

		-- First "next" call loads version pages:

		if self._query_pages == nil then

			self._is_query_yielded = true
			task.spawn(function()
				profile = self:NextAsync(true)
				is_finished = true
			end)
			
			local list_success, error_message = pcall(function()
				self._query_pages = self._profile_store._global_data_store:ListVersionsAsync(
					self._profile_key,
					self._sort_direction,
					self._min_date,
					self._max_date
				)
				self._query_index = 0
			end)

			if list_success == false or self._query_pages == nil then
				warn("[ProfileService]: Version query fail - " .. tostring(error_message))
				self._query_failure = true
			end

			self._is_query_yielded = false
			self:_MoveQueue()

			return

		end

		local current_page = self._query_pages:GetCurrentPage()
		local next_item = current_page[self._query_index + 1]

		-- No more entries:
		
		if self._query_pages.IsFinished == true and next_item == nil then
			is_finished = true
			return
		end

		-- Load next page when this page is over:

		if next_item == nil then

			self._is_query_yielded = true
			task.spawn(function()
				profile = self:NextAsync(true)
				is_finished = true
			end)

			local success = pcall(function()
				self._query_pages:AdvanceToNextPageAsync()
				self._query_index = 0
			end)

			if success == false or #self._query_pages:GetCurrentPage() == 0 then
				self._query_failure = true
			end

			self._is_query_yielded = false
			self:_MoveQueue()

			return

		end

		-- Next page item:

		self._query_index += 1
		profile = self._profile_store:ViewProfileAsync(self._profile_key, next_item.Version)
		is_finished = true

	end

	if self._is_query_yielded == false then
		query_job()
	else
		if _is_stacking == true then
			table.insert(self._query_queue, 1, query_job)
		else
			table.insert(self._query_queue, query_job)
		end
	end

	while is_finished == false do
		task.wait()
	end

	return profile

end
--watch the video to know where to put it


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

local button = script.Parent

local debounce = false

button.MouseButton1Click:Connect(function()
	local rebirths = game.Players.LocalPlayer.leaderstats:FindFirstChild("Rebirths")
	if not debounce then
		debounce = true
		local playerPoint = rebirths
		remote:FireServer(playerPoint)
		wait(0.001)
		debounce = false
	end
end)

repeat
	local rebirths = game.Players.LocalPlayer.leaderstats:FindFirstChild("Rebirths")
	local textButton = script.Parent
	textButton.Text = "".. tostring(1000000 * (rebirths.Value + 1)) .." Cash "
	wait(0.001)
until script.Parent == nil
--[[
INSTRUCTIONS: Place both teleporter1a and teleporter1b in the same directory
(e.g. both in workspace directly, or both directly in the same group or model)
Otherwise it wont work. Once youve done that, jump on the teleporter to teleport to the other.
If you want more than one set of teleporters I will be adding more types in the future.

Send me requests and ideas - miked.
--]]
--!strict


local RunService = game:GetService("RunService")

local PostSimulation = RunService.Heartbeat

local OPT_INTERVAL_ATTRIBUTE = "Interval"
local OPT_INTERVAL = 3
local OPT_SWITCH_ATTRIBUTE = "Switch"
local OPT_SWITCH = false
local OPT_DEFAULT_MATERIAL = Enum.Material.SmoothPlastic
local OPT_SWITCH_MATERIAL = Enum.Material.ForceField

local Obstacle = {}

Obstacle.__index = Obstacle

function Obstacle.new(instance: Instance)
	local self = setmetatable({}, Obstacle)
	
	self._events = {}
	self._materialCache = {}
	self._lastTick = tick()
	
	self._instance = instance
	self._interval = instance:GetAttribute(OPT_INTERVAL_ATTRIBUTE) or OPT_INTERVAL
	self._switch = instance:GetAttribute(OPT_SWITCH_ATTRIBUTE) or OPT_SWITCH
	
	self:CachePartMaterials()
	
	table.insert(self._events, PostSimulation:Connect(function()
		local now = tick()
		
		if now - self._lastTick < self._interval then
			return
		end
		
		self._lastTick = now
		self:ToggleState()
	end))
	
	self:UpdateParts()
	
	return self
end

function Obstacle:GetAllParts(): {BasePart}
	local parts = {}
	
	if self._instance:IsA("BasePart") then
		table.insert(parts, self._instance::BasePart)
	end
	
	for _, child: BasePart in ipairs(self._instance:GetDescendants()) do
		if child:IsA("BasePart") then
			table.insert(parts, child)
		end
	end
	
	return parts
end

function Obstacle:CachePartMaterials()
	for _, part: BasePart in ipairs(self:GetAllParts()) do
		self._materialCache[part] = part.Material
	end
end

function Obstacle:GetSwitchState(): boolean
	return self._instance:GetAttribute(OPT_SWITCH_ATTRIBUTE) == true
end

function Obstacle:ToggleState()
	self._instance:SetAttribute(OPT_SWITCH_ATTRIBUTE, not self:GetSwitchState())
	self:UpdateParts()
end

function Obstacle:UpdateParts()
	for _, part in ipairs(self:GetAllParts()) do
		self:SetPartMaterial(part)
	end
end

function Obstacle:SetPartMaterial(part: BasePart)
	local switch = self:GetSwitchState()
	
	part.CanCollide = switch
	part.Material = switch and (self._materialCache[part] or OPT_DEFAULT_MATERIAL) or 
		OPT_SWITCH_MATERIAL
end

function Obstacle:Destroy()
	for _, event: RBXScriptConnection in ipairs(self._events) do
		event:Disconnect()
	end
end

return Obstacle