Bucket's (Quick) Portfolio

About Me

Hello. I am writing this as a scripter. The point of this post is to document some things I have done in the past.
I have been scripting in Roblox for about 5 years. Pretty much all of the projects I have created came from “I wonder..” or “Would this work?”
Not everything I do works well, most of it just works once, then stops when showing to others. :C But I find you can learn a lot more about building a rocket when it crashes and burns than if nothing goes wrong.
It was been good fun, and look forward to doing more, either myself or with others.

Showcase

Here are some of the projects I have worked on. They are all solo things. The few group projects I was a part of were never documented. (which might have something to do with the lack of organization in those projects…)

I’ll post an image, some information, and a code sample from the project.
(If snooping around, feel free to take any ideas you want from the code :slight_smile: )

Before They Come

A tower defense game in which the player would collect resources on a randomly generated tile-world to build defenses to protect their orb. The project was titled “Before They Come” and was only ever a prototype, but it was fun to make.

For a code example, this is the main control script for the project.
Can you tell it was a personal project?

local Players = game:GetService("Players")
local repStorage = game:GetService("ReplicatedStorage")
local serStorage = game:GetService("ServerStorage")

local eventFolder = repStorage:WaitForChild("Events")
local itemPickup = eventFolder:WaitForChild("ItemPickup")
local toolUse = eventFolder:WaitForChild("ToolUse")
local buildRequest = eventFolder:WaitForChild("BuildRequest")
local soundChange = eventFolder.SoundChange

local items = serStorage:WaitForChild("Items")

local objectStats = require(repStorage:WaitForChild("Modules"):WaitForChild("ObjectStats"))
local harvestType = require(repStorage.Modules:WaitForChild("HarvestTypes"))
local buildingStats = require(repStorage.Modules.BuildingStats)
local gameControl = require(repStorage.Modules.GameControl)
local guardStats = require(repStorage.Modules.GuardStats)

local Buildings = repStorage:WaitForChild("Buildings")

--Configs for stuff
local pickupDis = 10
local toolHitDis = 10

--_______ITEM PICKUP CONTROL_______↓
local function FindTarget(target)
	local parts = workspace:GetChildren()
	for _, v in next, parts do
		if v == target then
			return true
		end--end if v==target
	end--end for loop
end--end function

itemPickup.OnServerEvent:Connect(function(player,target)
	--first find out if the player is close enough to get the item
	local disToPart = (player.Character.HumanoidRootPart.Position - target.Position).magnitude--get the distance from player to item
	--print(target.Name)
	if disToPart <= pickupDis then
		local find = FindTarget(target)
			if find == true then--we want to make sure the item is still in the game
			--print("Close enough")
			--Now we get all the stuff from the player that we need
			local playerGui = player.PlayerGui
			local inventoryGui = playerGui.InventoryGui
			local itemFrameHolder = inventoryGui.MainFrame.ItemFrame
			local itemFrame = itemFrameHolder:FindFirstChild(target.Name)--this is badly named, but it is the items panel
		
			local inventory = player:FindFirstChild("Inventory")--the inventory where the item will be stored
			local item = inventory:FindFirstChild(target.Name)
			item.Value = item.Value + 1--give the player the item in their inventroy
		
			itemFrame.Amount.Text = tostring(item.Value)
			
			--the following lines are to remind me of my failuers
			--local amount = itemFrame:FindFirstChild("Amount") 
			--amount.Text = tostring(item.Value)
		
			target:Destroy()--remove the item so that they cannot get more
			--print("Item has been collected")--this is for testing
		end
	end
end)
--_______END OF PICKUP CONTROL________↑

--_____TOOL USE (AXE AND PICKAXE)________↓
toolUse.OnServerEvent:Connect(function(player,character,tool)
	local humRootPos = character.HumanoidRootPart.Position
	for _, part in next, game.Workspace:GetChildren() do
		if part:FindFirstChild("Health", true) then--loop though everything! Might be a bad idea
			local health = part:FindFirstChild("Health",true)
			if health:IsA("IntValue") then--make sure the health is not from a character
				local distance = (health.Parent.Position - character.HumanoidRootPart.Position).magnitude
				if distance <= toolHitDis then
					local object = health.Parent--getting the parent of health, which is a part
					local objectModel = object.Parent--getting the object parent
					--print(object)
					--Now we can do stuff to damage it or harvest it
					local hType = harvestType[object.Name].htype
					
					if hType == "wood" then--Find out what damage will be done, and the tool will do the damage
						local damage = objectStats[tool.Name].woodDamage
						health.Value = health.Value - damage
						--print(health.Value)
					elseif hType == "stone" then
						local damage = objectStats[tool.Name].stoneDamage
						health.Value = health.Value - damage
						--print(health.Value)
					end
					
					--If health is 0 or lower	
					if health.Value <= 0 then
						--time to harvest
						local drop = harvestType[object.Name].yield--what item to get
						local amount = harvestType[object.Name].amount--how much to get
						local neededItem = items:FindFirstChild(tostring(drop))
						--print(neededItem)
						
						repeat
							local dropClone = neededItem:Clone()
							dropClone.Position = object.Position
							dropClone.Parent = workspace
							amount = amount - 1
							
						until amount <= 0
						
						if objectModel:FindFirstChild("Leaf") then--leaf spawning because it won't work properly for some reason
							local leaf = objectModel.Leaf
							local leafAmount = harvestType[tostring(leaf)].amount
							local neededItem = items:FindFirstChild(tostring(leaf))
							repeat
								local dropClone = neededItem:Clone()
								dropClone.Position = object.Position
								dropClone.Parent = workspace
								leafAmount = leafAmount - 1

							until leafAmount <= 0
						end
						
						objectModel:Destroy()
					end
					
				end					
			end--end if health:IsA
		end--end if part:FindFirstChild
	end--end for loop
end)
--________END OF TOOL USE (AXE AND PICKAXE)_____↑

--_________PLAYER BUILDING CONTROL_________↓
local function HammerCheck(player)--checking if the player has the hammer tool
	if player.Character:FindFirstChild("Hammer") then--
		return true
	else
		return false
	end--end if player.Character.Hammer then
end--end funton HammerCheck

buildRequest.OnServerEvent:Connect(function(player,requestedBuilding,cframe,pos)--this function works somehow
	local hasHammer = HammerCheck(player)--tell server to check for tool "Hammer"
	
	if hasHammer == true then
			local playerInventory = player.Inventory--the players requested inventory
			local playerGui = player.PlayerGui
			local inventoryGui = playerGui.InventoryGui
			local itemFrame = inventoryGui.MainFrame.ItemFrame
			local mouse = player:GetMouse()
			local neededBuilding = Buildings:FindFirstChild(tostring(requestedBuilding))
			local buildStats = buildingStats[tostring(neededBuilding)]
			local buildRecipe = buildingStats[tostring(neededBuilding)].Recipe

			local canBuild = true
			--Find we want to find out if the player has enough material
			for item, number in pairs(buildRecipe) do
				if playerInventory:FindFirstChild(item).Value - number >= 0 then
					--print("all good")
				else
					canBuild = false
				end--end if playerInventory
			end--end for loop

			if canBuild == true then--at this point, we do the building stuff
				--take the item from playerInv


				--time to do the stuff to set up the building
				local neededBuildingClone = neededBuilding:Clone()
				neededBuildingClone:MoveTo(pos)  
				neededBuildingClone:SetPrimaryPartCFrame(cframe)
				neededBuildingClone.Parent = workspace--put the building into play

				for item,number in pairs(buildRecipe) do
					local itemInInv = playerInventory:FindFirstChild(item)
					itemInInv.Value = itemInInv.Value - number
					--Update player GUI
					local itemHolder = itemFrame:FindFirstChild(tostring(item))--find the item frame we need
					itemHolder.Amount.Text = tostring(itemInInv.Value)--update the number

				end--end for itenm,number #2
			end--end if canBuild == true
	end--end is hasHammer == true
end)--end function
--____________END OF PLAYER BUILDING CONTROL_______↑

--________WORLD TIME________________↓
local timeStamps = serStorage.TimeStamps

local globalTime = 0--250
local function TimeCheck()--this function forwards global time and also changes the time values when needed
	globalTime = globalTime + 1  --add 1 second to the global time
	--print(globalTime)
	for i,v in pairs(gameControl) do--for #, time value in pairs do
		--print(i)
		for _,value in pairs(v) do
			local sTime = v.sTime--the start time of the time value
			local eTime = v.eTime--the end time of the time value
			local active = v.active
			
			
			
			
			if globalTime > sTime then
				if globalTime < eTime then--checking if the time slot is correct for the current time
					--print(i)
					--print(tostring(i).. " is ".. tostring((timeStamps:FindFirstChild(tostring(i)).Value)))
					timeStamps:FindFirstChild(tostring(i)).Value = true--setting the value to true
					
				else--else 	if globalTime < eTime then--checking if the time slot is correct for the current time
					timeStamps:FindFirstChild(tostring(i)).Value = false--setting the value to false
				end--end if globalTime < etime
			else--else if globaltime > sTime
				timeStamps:FindFirstChild(tostring(i)).Value = false--setting the value to false
			end--end if global time > sTime
			
			if timeStamps:FindFirstChild(tostring(i)).Value == true then
				local soundId = gameControl[tostring(i)].soundId
				soundChange:FireAllClients(soundId)
				
			end--end 	if timeStamps:FindFirstChild(tostring(i)).Value == true then
			
		end--end for _, value in pairs(v) do
	end--end for i,v in pairs(gameControl) do
end--end function TimeCheck()


--_____END OF TIME CONTROL___________↑

--_______GUARD SPAWNING__________↓
local maxSpawn = 30 --how many guards can be in the game at once
local currentSpawn = script.CurrentSpawn.Value--how many guards are in the game ATM
local spawners = game.Workspace.BaseWorld.Spawners:GetChildren()
local guardFolder = serStorage:WaitForChild("Guards")
local spawnTime = 0

local controlScript = repStorage:WaitForChild("Scripts").GuardScript

local function CurrentSpawn()--Counts the number of guards in the workspace
	currentSpawn = 0
	for _, model in next, game.Workspace:GetChildren() do
		--print(model)
		if model:IsA("Model") then--we only are going to target models
			if guardStats[model.Name] then--we only want to target guards, not the player
				--print(model)
				currentSpawn = currentSpawn + 1
				--print("Count")
			end--end if humRoot and model.Humanoid.Health > 0 then
		end--end if guardStats[model.Name] then
	end--end if model:IsA("Model") then
	wait(.1)
	return currentSpawn
end--end function CurrentSpawn

local function GuardSpawn()--Spawns guards in.
	spawnTime = spawnTime + 1
	local defendTime = serStorage.TimeStamps.Defend.Value
	
	while defendTime == true do
		local guardAmount =  CurrentSpawn()
		
		if guardAmount < maxSpawn then--if there are not max guards, pleae go on
		--	print(currentSpawn)
			spawners = game.Workspace.BaseWorld.Spawners:GetChildren()
			local randomNum = math.random(1,#spawners)--make a random number
			local randomSpawn = spawners[randomNum]--pick the spawner the guards will spawn from
			local randomSpawn = spawners[randomNum]

			local throwGuards = guardFolder:GetChildren()--lol the guards are throwing
			local guardNmber = nil
			local guardToSpawn = nil--which guard has been selected to spawn
			
			repeat--Getting guars to spawn into the game, but we also want to make sure we spawn the right guards at the right time
				guardNmber = math.random(1,# throwGuards)
				guardToSpawn = throwGuards[guardNmber]--getting the type of guard we want to spawn
				wait(.1)
			until guardStats[tostring(guardToSpawn)].mTime < spawnTime --repeat untill we can spawn in guards that furfill the time stamp
			
			local spawnAmount = math.random(guardStats[tostring(guardToSpawn)].minSpawn, guardStats[tostring(guardToSpawn)].maxSpawn)
			repeat
				spawnAmount = spawnAmount - 1
				local guardClone = guardFolder:FindFirstChild(tostring(guardToSpawn)):Clone()
				
				local scriptClone = controlScript:Clone()--putting the guard control sctip into the guard
				scriptClone.Parent = guardClone
				scriptClone.Disabled = false--Give it LIFE
				
				guardClone.Parent = workspace
				guardClone:MoveTo(randomSpawn.Position)

				wait(.1)
			until spawnAmount == 0 
					
		end--end if guardStats < maxSpawn then
	end--end while globalTime["Defend"].active == true do
end--end function GuardSpawn

timeStamps.Defend.Changed:Connect(function()
	if timeStamps.Defend.Value == true then--find out if it is time to call guard spawn
		GuardSpawn()
	end
end)--end timeStamps.Defend.Changed:Connect(function()

--________END OF GUARD SPAWNING___________↑


--____TOOL GIVE AND REMOVE_________↓
local tools = repStorage:WaitForChild("Tools")
--local Axe = tools.Axe
--local Pickaxe = tools.Pickaxe
--local Hammer = tools.Hammer

for _, timeStamp in next, timeStamps:GetChildren() do
	--if timeStamp.Name ~= "Defend" then--we don't want to do anything for defend, this is taken care of somewhere else
		timeStamp.Changed:Connect(function()
			if timeStamp.Value == true then
			local neededTools = gameControl[tostring(timeStamp)].Tools
			
			for _, player in next, Players:GetChildren() do--do this for every player Multiplayer confirmed?
				local char = player.Character
				local pBackpack = player.Backpack
				print("Hello")
				--Clear the player backpack real quick
				for _, pTool in next, pBackpack:GetChildren() do--clearing the player backpack
					print(pTool)
					if pTool:IsA("Tool") then
						pTool:Destroy()
					end--end if pTool:IsA("Tool") then
				end--end for _, pTool in next, pBackpack:GetChildren() do
				
				for _, part in next, char:GetChildren() do--clear tools from the character 
					if part:IsA("Tool") then
						part:Destroy()--destroy the tool
					end--end if part:IsA("Tool") then
				end--end for _, part in next, char:GetChildren()
				
				for tool, _ in pairs(neededTools) do--loop through every tool needed
					local stringTool = tostring(tool)
					local toolToClone = tools:FindFirstChild(stringTool)
					
					if toolToClone ~= nil then--make sure this time stamp has a tool 
						local toolClone = tools:FindFirstChild(stringTool):Clone()
						toolClone.Parent = pBackpack--put the tools each players backpack
					end--end if toolToClone ~= nil
					
					--toolToGet.Parent = pBackpack
					
				end--end for _, tool in pairs(neededTools) do
			end--end for_,player in next, Players:GetChildren() do
			
			end--end if timeStamp.Value == true then
		end)--end tomeStamp.Changed:Connect(function()
		
	--end--end if v.Name ~= "Defend" then
end--end for _, v in next, timeStamps:GetChildren() do


--_____END OF TOOL GIVE AND REMOVE_____↑



--_____LOSING FUNCTION____↓
local objectV = game.Workspace:WaitForChild("ObjectOfValue",10)--wait up to 10 seconds for ObjectV
local gameLose = false

local function LoseGame()
	
end--end function LoseGame

objectV.Touched:Connect(	function(part)-- if objectV was touched
	local partParent = part.Parent
	if gameLose == false then
		if guardStats[tostring(partParent)] then
			print("You Lose")
			gameLose = true
			
			--Do the stuff that happeneds whne you lose
			for _, player in next, Players:GetChildren() do
				if player then
					player:Kick("You have lost the game, good try though	")--kick the player
				end--end if player then
			end--end for _, player in next, Players:GetChildren()			
			
		end--end if guardStats[tostring(partParent)] then
	end-- end if gameLose == false then
end)--end objectV.Touched:Connect(	function(part)

--____END LOSING FUNCTION___↑


while true do
	print(globalTime)
	TimeCheck()
	wait(1)
	
	if globalTime >= 360 then--end game
		for _, player in next, Players:GetChildren() do
			if player then
				player:Kick("You have won. Sorry there is nothing after,.")
			end
		end
	end--end game
	
end

Yea, this one is not a good example for code readability. This project was only ever to see if I could do it.

NPC Controlling Test


This was a test for creating units that a player could command. Although the actual gameplay was never completed beyond making the NPCS follow commands, I still was pleased with what I learned from it.
I don’t have any good code to show for this one.
It is pretty much some remote events for a player to tell their units “Move up” or to purchase more units.

Survival Game Project


This project was designed to be a game where you hit stuff to build stuff. It did allow you to hit stuff and was also fun to make. The building side never went though, but it the lessons I learned from this one were very valuable.
I would also like to ask for forgiveness for the GUI. It was thrown together in 10 minuets…

The code in this one is also not interesting. I’ll still post something…
Here is an example of something you should never do:
Server-side GUI control

equipTool.OnServerEvent:Connect(function(player,toolSlot,slotNum)
	local plrData = player.PlayerData
	local plrTrack = plrData.Tracker
	local equippedSlot = plrTrack.EquippedToolSlot
	
	local plrGui = player.PlayerGui
	local hotbarFrame = plrGui.HUDGui.ToolsBar
	
	local slotKeys = {}--for num keys	
	
	local findSlot --For clicking on the slot
	if toolSlot then
		findSlot = hotbarFrame:FindFirstChild(toolSlot.Name)
		if toolSlot and findSlot and findSlot.Name ~= "Slot" then
			--Check if tool slot is already selected
			local numSave
			for num, slot in pairs(hotbarFrame:GetChildren()) do
				if slot:IsA("ImageButton") then
					table.insert(slotKeys,num,tostring(slot))
				end
			end
			--Track which number slot is being used
			for num, slot in pairs(slotKeys) do
				if tostring(slot) == toolSlot.Name then
					--set equippted slot
					equippedSlot.Value = num - 1 --the first number is not a slot
					numSave = equippedSlot.Value
					functions.EquipTool(nil,player)
				else
					functions.EquipTool(toolSlot,player)
					break
				end
			end
					
			--Update Equipped slot
			for num, slot in pairs(slotKeys) do
				if tostring(slot) == toolSlot.Name then
					--set equippted slot
					equippedSlot.Value = num - 1 --the first number is not a slot
					break
				end
			end
			
		end
		
	elseif slotNum ~= nil then--for pressing a number key
		--decode which slot it is
		--getting all the slots
		for _, slot in pairs(hotbarFrame:GetChildren()) do
			if slot:IsA("ImageButton") then
				table.insert(slotKeys,slot)
			end
		end

		--get which tool slot is called
		local toolSlot = slotKeys[slotNum]
		--check if tool slot is already selected
		if slotNum == equippedSlot.Value then --or toolSlot.Name == "Slot"
			--Unequip tool by sending nil
			functions.EquipTool(nil,player)
			equippedSlot.Value = 0 --reset slot value (otherwise you cannot drawl tool again)
		else
			if toolSlot.Name ~= "Slot" then--check if slot is not empty
				equippedSlot.Value = slotNum
				functions.EquipTool(toolSlot,player)	
			end
		end
	end--end if toolSlot then/elseif slotNum ~= nil then
	
	--check if toolSlot is a child of player hotbar
end)

(Yes, this was one of the first projects I made)

Tank Game


This comes from a long term project to make component style vehicles. The first thing I made with it was a tank. Why? Because it’s cool.
Working on this ranged from very fun to very unfun. I find OOP code to be your worst best friend.

This is also a good time to show a code example. Here is the VehicleController class written for the vehicle.
Note: Some of the sections of this code are incomplete. It appears as if some changes did not get saved. This was painful for me to find out.
Always spam ctrl+s

--!nonstrict
--set to strict to see crimes
--[[
Ground Vehicle Controller

Makes a platform with wheels

Pointer: Fancy name of ObjectValue because it is a pointer but for Instances lolz
]]

----_DEPENDENCIES_-----
--Service
local Players = game:GetService("Players")
----_END_DEPENDENCIES_----



----_CLASS_----
local wheelVehicleController = {}
wheelVehicleController.interface = {}
wheelVehicleController.schema = {}
wheelVehicleController.metatable = {__index = wheelVehicleController.schema}
wheelVehicleController.staticFunctions = {}
----_END_CLASS_----


----_VARS_----
--Config
local config = {
	FOLDER_NAMES = {
		WHEEL_FOLDER_NAME = "Wheels",
		SCRIPTS_FOLDER_NAME = "ControlScripts",
		SOUNDS_FOLDER_NAME = "Sounds",
		MAIN_ASSEMBLY_FOLDER_NAME = "MainAssembly"
	},
	--Expected names of important MainAssembly pointers
	MAIN_ASSEMBLY_POINTER = {
		CONTROL_SEAT = "ControlSeat",
		CHASSIS = "Chassis",
		MASS = "Mass"
	},
	--Expected names of important scripts
	SCRIPT_NAMES = {
		LOCAL_CONTROL_SCRIPT = "control_local"
	}
}
---_END_VARS_----

----_STATIC_FUNCTIONS_----
--Attempts to find the wheel folder 
function wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle : Model, folder_name : string) : Folder?
	--Find instance with matching name
	local requiredFolder = vehicle:FindFirstChild(folder_name)
	if not requiredFolder then return nil end--Could not find instance

	--Make sure instance is a folder (It could be anything, but it gotta be a folder because um... yes)
	if not requiredFolder:IsA("Folder") then return nil end

	return requiredFolder

end

--Function to search through folder of object values and return the value of the object with the corrosponding name
function wheelVehicleController.staticFunctions.get_instance_from_pointer_folder(pointer_folder : Folder, instance_name : string) : Instance?
	--Attempt to find pointer (object value) in folder by name
	local result = pointer_folder:FindFirstChild(instance_name)

	if not result then
		error( `Could not find {instance_name} in folder {pointer_folder.Name} (Required Instance missing!`)
	end


	--Make sure the result is an ObjectValue
	if not result:IsA("ObjectValue") then
		error(`{instance_name} in folder {pointer_folder} is not an ObjectValue. (Required Instance could not be found or is missing!`)
	end

	--If value is nil, there is potentially a problem
	if not result.Value then
		warn(`ObjectValue {instance_name} found in folder {pointer_folder}, but the value is nil. This may cause an error. (Make sure the value is set!`)
	end

	--All checks passed. Return object
	return result.Value

end

--Returns array of wheels from vehicile. (Will error if it cannot find the wheels)
function wheelVehicleController.staticFunctions.get_wheels(vehicle) : {Part} --TODO: wheelArray refrence is a possible memory leak!!!! (Check this)
	--Attempt to find wheel folder from vehicle
	local wheelFolder = wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle, config.FOLDER_NAMES.WHEEL_FOLDER_NAME)
	assert(wheelFolder, `Could not find Wheel folder. (Expected name is {config.FOLDER_NAMES.WHEEL_FOLDER_NAME})`)

	--Make array of wheels
	local wheelArray = {}
	for _, wheel in ipairs(wheelFolder:GetChildren()) do
		if not wheel:IsA("Part") then continue end --Filter out non-part instances
		table.insert(wheelArray,wheel)
	end

	return wheelArray
end

--Returns array of main assembly Instances
function wheelVehicleController.staticFunctions.get_main_assembly(vehicle : Model) : {ControlSeat : VehicleSeat, Chassis : Part, Mass : Part}

	--Get MainAssembly folder
	local assemblyFolder = wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle, config.FOLDER_NAMES.MAIN_ASSEMBLY_FOLDER_NAME)
	if not assemblyFolder then 
		error(`Could not find MainAssembly folder. (Expected name is {config.FOLDER_NAMES.MAIN_ASSEMBLY_FOLDER_NAME})`)
	end

	--Get the required instances from the folder
	local controlSeat = wheelVehicleController.staticFunctions.get_instance_from_pointer_folder(assemblyFolder, config.MAIN_ASSEMBLY_POINTER.CONTROL_SEAT)
	local chassis = wheelVehicleController.staticFunctions.get_instance_from_pointer_folder(assemblyFolder, config.MAIN_ASSEMBLY_POINTER.CHASSIS)
	local mass = wheelVehicleController.staticFunctions.get_instance_from_pointer_folder(assemblyFolder, config.MAIN_ASSEMBLY_POINTER.MASS)

	--_Check status of required instances_ (WARNING: LARGE IF BLOCK CHAIN!!!)--
	if not controlSeat then 
		error("Could not find control VehicleSeat! (Make sure the ObjectValue for the control seat in MainAssembly is not nil")
	elseif not controlSeat:IsA("VehicleSeat") then
		error("ControlSeat ObjectValue does not point to a VehicleSeat!")
	end
	if not chassis then 
		error("Could not find vehicle Chassis! (Make sure the ObjectValue for the Chassis in MainAssembly is not nil")
	elseif not chassis:IsA("Part") then
		error("ControlSeat ObjectValue does not point to a Part!")
	end
	if not mass then 
		error("Could not find vehicle Mass! (Make sure the ObjectValue for the Mass in MainAssembly is not nil")
	elseif not mass:IsA("Part") then
		error("ControlSeat ObjectValue does not point to a Part!")
	end
	--_↑_--

	return {ControlSeat = controlSeat, Chassis = chassis, Mass = mass}
end

--Returns the LocalControl script for the vehicle
function wheelVehicleController.staticFunctions.get_control_scripts(vehicle : Model) : {LocalControl : LocalScript}
	--Get the ControlScripts folder
	local scriptsFolder = wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle, config.FOLDER_NAMES.SCRIPTS_FOLDER_NAME)
	if not scriptsFolder then 
		error(`Could not find MainAssembly folder. (Expected name is {config.FOLDER_NAMES.MAIN_ASSEMBLY_FOLDER_NAME})`)
	end

	--Get required instance from the folder
	local localControl = scriptsFolder:FindFirstChild(config.SCRIPT_NAMES.LOCAL_CONTROL_SCRIPT)

	--_Check status of required instance_↓_ 
	if not localControl then
		error("Could not find local vehicle control script in scripts folder!")
	elseif not localControl:IsA("LocalScript") then
		error("LocalControl script is not a LocalScript!")
	end
	--_↑_--

	--All checks passed
	return {LocalControl = localControl}
end
--To-Be-Written (TBW)
--function wheelVehicleController.staticFunctions.get_sounds(vehicle) : {}

--end

--Handle when the seat occupant changes
function wheelVehicleController.staticFunctions.on_seated(self : class)
	--[[
	1: Check for seat occupant.
		Seat occupant: take route A
		No seat occupant: take route B
		
		Route A:
		Get the humanoid occupant of the seat
		Assume the parent of the Humanoid is a character
		Attempt to get the player who owns the character. (If cannot get player, remove character from seat)
		Call function which returns a clone of the local control script (the local control script has a refrence to the vehicle)
		
	]]

	local seatOccupant : Humanoid? = self._controlSeat.Occupant

	if self._controlSeat.Occupant then
		--Path A: Set up player to control vehicle

		local humOccupant : Humanoid = self._controlSeat.Occupant

		--Get the occupant's player
		local plrOccupant = Players:GetPlayerFromCharacter(humOccupant.Parent)

		if not plrOccupant then
			warn("Failed to get player from the occupant. Removing character from seat")
			humOccupant.Jump = true
			humOccupant.Sit = false

			--_Clear_local_control_scripts
			wheelVehicleController.staticFunctions.clear_local_control_scripts(self)
			return
		end

		--_Give_player_ownership_of_vehicle_↓_--
		self._controlSeat:SetNetworkOwner(plrOccupant)
		self._currentPlayer = plrOccupant
		--_↑_--

		--_Give_player_local_control_script_↓_
		local localControlClone = wheelVehicleController.staticFunctions.get_local_control_script_clone(self)
		table.insert(self._activeLocalControlScripts, localControlClone)

		localControlClone.Parent = humOccupant.Parent
		localControlClone.Enabled = true--Local control should start disabled

		--_↑_
	else
		--Path B: Remove previous player from vehicle control


		--Clear local control scripts that may still exist
		wheelVehicleController.staticFunctions.clear_local_control_scripts(self)
	end

end

--Function to get a local control script clone, which is set up to allow for the player to control the vehicle
function wheelVehicleController.staticFunctions.get_local_control_script_clone(self : class) : LocalScript?
	--Make sure the local control script was not deleted
	if not self._controlScripts.LocalControl then 
		warn(`LocalControlScript for {self._vehicle} has gone missing!`)
		return nil
	end

	--Make a clone of the local control script
	local localControlClone = self._controlScripts.LocalControl:Clone()

	--Put in a pointer back to the vehicle for the local control script
	local objVal = Instance.new("ObjectValue")
	objVal.Name = "VehicleRefrence"
	objVal.Value = self._vehicle
	objVal.Parent = localControlClone

	return localControlClone
end

--Function to delete all local control scripts the object has created
function wheelVehicleController.staticFunctions.clear_local_control_scripts(self : class)
	for i,v : Instance in ipairs(self._activeLocalControlScripts) do
		v:Destroy()
	end
	table.clear(self._activeLocalControlScripts)
end
----_END_STATIC_FUNCTIONS_----

----_INTERFACE_----
function wheelVehicleController.interface.new(vehicle : Model)
	--_Getting/Checking for folders_↓_--
	local controlScriptsFolder = wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle, config.FOLDER_NAMES.SCRIPTS_FOLDER_NAME)

	local soundsFolder = wheelVehicleController.staticFunctions.get_folder_from_vehicle(vehicle, config.FOLDER_NAMES.SOUNDS_FOLDER_NAME)
	local vehicleWheels = wheelVehicleController.staticFunctions.get_wheels(vehicle)--This will always either return or error


	assert(soundsFolder, `Could not find Sounds folder. (Expected name is {config.FOLDER_NAMES.SOUNDS_FOLDER_NAME})`)
	assert(controlScriptsFolder, `Could not find Control Scripts folder. (Expected name is {config.FOLDER_NAMES.SCRIPTS_FOLDER_NAME})`)
	--_↑_--+

	local self = setmetatable({}, wheelVehicleController.metatable)
	self._config = {}
	self._activeLocalControlScripts = {} :: {LocalScript}--Put local control scripts created by the controller to clean then up when needed
	self._controlScripts = wheelVehicleController.staticFunctions.get_control_scripts(vehicle)
	self._mainAssembly = wheelVehicleController.staticFunctions.get_main_assembly(vehicle)
	self._currentPlayer = nil :: Player | nil
	self._controlSeat = self._mainAssembly.ControlSeat
	self._vehicle = vehicle
	self._wheels = vehicleWheels

	--_Internal_Connections_↓--
	self._internalConnections = {
		--VehicleSeat.Changed handler. (Used for checking if a character is sitting in the seat)
		self._controlSeat.Changed:Connect(function(changed : string)
			if changed ~= "Occupant" then return end

			--Handle character sitting in seat/getting up
			wheelVehicleController.staticFunctions.on_seated(self)

		end)
	}
	--_↑_

	return self
end
----_END_INTERFACE_----

----_METHODS_----
----_END_METHODS_----

----_CLASS_TYPE_----
type class = typeof(wheelVehicleController.interface.new(table.unpack(...)))
----_END_CLASS_TYPE_----

return wheelVehicleController.interface
Tool Crafting

There is no image for this one, as it was only ever a proof of concept for coding. I mainly included this to show a more up-to-date of my programming style nowaways

--Used to determine if the tool_parts passed has all of the required parts for the certain Tool Blueprint
local function hasRequiredParts(blueprint_data : toolTypeData.ToolBlueprint, tool_parts : {toolTypeData.ToolPart}) : boolean
	--[[
	1.Make a list of things needed by the Blueprint
	2.Make a list of things provided by the "tool_parts"
	3.Compare the lists
		If they are equal, required parts present, return true
		If they are not equal, required parts missing, return false
	]]
	
	--_Vars_↓_--
	local blueprintCount = countPartList(blueprint_data._requiredParts)--Count of parts required to create the tool
	local partCount = countPartList(tool_parts) --Count of parts provided to make the tool
	
	--Flag
	local hasParts = true--If the required parts are present. Assumed to be true, we want to disprove that
	--_↑_--
	
	--_Compare_Counts_↓_--
	for partType,amount in pairs(blueprintCount) do
		if partCount[partType] then
			partCount[partType] -= amount
			
			--Check if "tool_parts" has less then required amount of a certain part
			if partCount[partType] ~= 0 then
				hasParts = false
			end
			
		else
			--If we cannot find this required type of part in "tool_parts", then not all required parts present!
			hasParts = false
			
		end
	end
	--_↑_--
	
	return hasParts
	
end

Other

Nowadays, I spend more time with pencil and paper than I do in Roblox Studio. This helps make better developed ideas and code.

The amount of paper everywhere has got to be a fire hazard

Contact

You can contact me here on the Developer Forum for any question. I’ll try to respond as quick as I can.

Well, that’s about all that is interesting.
Thanks for reading! :slight_smile:

1 Like