'Hampter' Spawner: Code Improvement Suggestions

So I’m a new developer, currently with 3 months of experience. Recently I made this:

Basically it randomly spawns ‘hampters’ based on rarity. Just a proof of concept to test my skills, Idk if imma make something out of it.

Here’s the code:

Module1

--Type Declaration
export type HampterTemplate = {
	Name: string,
	Type: string,
	Price: number,
	Size: NumberRange,
	Mass: number,
	LifeSpan: number,
	Color: Color3,
	Transparency: number,
	Reflectance: number,
	Material: Enum.Material,
	Rarity: Rarity,
	MeshId: Content,
	TextureID: Content?,
	Special: typeof(function(hampter: MeshPart, ...: any) end)?,
	Variants: {
		Main: {
			Rarity: Rarity
		},
		[string]: {
			Rarity: Rarity,
			Modification: HampterTemplate
		}
	}?
}

export type RarityLevel = {
	Label: string,
	Threshold: number,
	Color: Color3
}

export type Rarity = {
	RarityLevel: RarityLevel,
	Probability: number,
}

--Main Module
local hampterLib = {}

--Supplemental Functions
local function defineRarityLevel(probability: number): RarityLevel
	local currRarityLvl: RarityLevel
	for _, rarityLvl in hampterLib.Rarity_Levels :: {RarityLevel} do
		if not currRarityLvl or (probability >= currRarityLvl.Threshold and currRarityLvl.Threshold < rarityLvl.Threshold) then
			currRarityLvl = rarityLvl
		end
	end
	return currRarityLvl
end

hampterLib.Default_Assets = {
	MeshId = Content.fromUri("rbxassetid://6648328063"),
	TextureID = Content.fromUri("rbxassetid://6648330426")
}
hampterLib.Size_Standards = {
	Micro = NumberRange.new(0.15, 0.15),
	Tiny = NumberRange.new(0.16, 0.20),
	Very_Small = NumberRange.new(0.21, 0.30),
	Small = NumberRange.new(0.31, 0.65),
	Below_Average = NumberRange.new(0.66, 1.5),
	Average = NumberRange.new(1.51, 2.80),
	Above_Average = NumberRange.new(2.81, 5.5),
	Large = NumberRange.new(5.51, 10),
	Very_Large = NumberRange.new(10.01, 16.75),
	Giant = NumberRange.new(16.76, 26.75),
	Huge = NumberRange.new(26.76, 41.25),
}
hampterLib.Hampter_Templates = {
	Non_Event = {
		Regular_Hampter = {
			Name = "Regular Hampter",
			Price = 0.5,
			Size = hampterLib.Size_Standards.Average,
			Mass = 0.15,
			Color = Color3.fromRGB(255, 255, 255),
			LifeSpan = 60,
			Transparency = 0,
			Reflectance = 0,
			Material = Enum.Material.Plastic,
			Rarity = 900,
			MeshId = hampterLib.Default_Assets.MeshId,
			TextureID = hampterLib.Default_Assets.TextureID,
			Variants = {
				Main = {
					Rarity = 700
				},
				Bigger = {
					Rarity = 100,
					Modification = {Size = hampterLib.Size_Standards.Above_Average}
				},
				Smaller = {
					Rarity = 100,
					Modification = {Size = hampterLib.Size_Standards.Below_Average}
				},
				Even_Bigger = {
					Rarity = 50,
					Modification = {Size = hampterLib.Size_Standards.Large}
				},
				Even_Smaller = {
					Rarity = 50,
					Modification = {Size = hampterLib.Size_Standards.Small}
				}
			}
		} :: HampterTemplate,
		--[[
		Bigger_Hampter = {
			Name = "Bigger Hampter",
			Price = 0.5,
			Size = hampterLib.Size_Standards.Above_Average,
			Mass = 0.15,
			Color = Color3.fromRGB(255, 255, 255),
			LifeSpan = 60,
			Transparency = 0,
			Reflectance = 0,
			Material = Enum.Material.Plastic,
			Rarity = 90,
			MeshId = hampterLib.Default_Assets.MeshId,
			TextureID = hampterLib.Default_Assets.TextureID
		} :: HampterTemplate,
		Even_Bigger_Hampter = {
			Name = "Even Bigger Hampter",
			Price = 0.5,
			Size = hampterLib.Size_Standards.Large,
			Mass = 0.15,
			Color = Color3.fromRGB(255, 255, 255),
			LifeSpan = 60,
			Transparency = 0,
			Reflectance = 0,
			Material = Enum.Material.Plastic,
			Rarity = 9,
			MeshId = hampterLib.Default_Assets.MeshId,
			TextureID = hampterLib.Default_Assets.TextureID
		} :: HampterTemplate,
		Even_Biggerer_Hampter = {
			Name = "Even Biggerer Hampter",
			Price = 0.5,
			Size = hampterLib.Size_Standards.Very_Large,
			Mass = 0.15,
			Color = Color3.fromRGB(255, 255, 255),
			LifeSpan = 60,
			Transparency = 0,
			Reflectance = 0,
			Material = Enum.Material.Plastic,
			Rarity = 1,
			MeshId = hampterLib.Default_Assets.MeshId,
			TextureID = hampterLib.Default_Assets.TextureID
		} :: HampterTemplate
		]]
	}
}
hampterLib.Rarity_Levels = {
	Common = {Label = "Common", Threshold = 50, Color = Color3.fromRGB(0, 255, 0)} :: RarityLevel,
	Uncommon = {Label = "Uncommon", Threshold = 5, Color = Color3.fromRGB(0, 85, 0)} :: RarityLevel,
	Rare = {Label = "Rare", Threshold = 1, Color = Color3.fromRGB(0, 255, 255)} :: RarityLevel,
	Very_Rare = {Label = "Very Rare", Threshold = 0.1, Color = Color3.fromRGB(0, 85, 127)} :: RarityLevel,
	Legendary = {Label = "Legendary", Threshold = 0.01, Color = Color3.fromRGB(255, 255, 0)} :: RarityLevel,
	Mythical = {Label = "Mythical", Threshold = 0.001, Color = Color3.fromRGB(255, 255, 255)} :: RarityLevel,
	Unique = {Label = "Unique", Threshold = 0, Color = Color3.fromRGB(0, 0, 0)} :: RarityLevel
}

for typeName, hampterTypes in hampterLib.Hampter_Templates do
	local totalWeight = 0
	for n = 1, 2 do
		for _, hampter in hampterTypes :: {HampterTemplate} do
			if n == 1 then
				hampter.Type = typeName
				totalWeight += hampter.Rarity
				
				if hampter.Variants then
					local totalWeight = 0
					for n = 1, 2 do
						for _, variant in hampter.Variants do
							if n == 1 then
								totalWeight += variant.Rarity
							else
								local weight = variant.Rarity
								variant.Rarity = {}
								variant.Rarity.Probability = math.round(weight / totalWeight * 100 * 1000) / 1000
								variant.Rarity.RarityLevel = defineRarityLevel(variant.Rarity.Probability)
							end
						end
					end
				end
			else
				local weight = hampter.Rarity
				hampter.Rarity = {}
				hampter.Rarity.Probability = math.round(weight / totalWeight * 100 * 1000) / 1000
				hampter.Rarity.RarityLevel = defineRarityLevel(hampter.Rarity.Probability)
			end
		end --Yea yea this looks horrible ik
	end
end -- 8 end chain new record woo!

print("HampterLib initialised!")

return hampterLib

Module2

--Services
local AS = game:GetService("AssetService")
local TweenService = game:GetService("TweenService")

--Modules
local HampterLib = require(script.Parent)

--Settings
local ACTIVE_KEYS = {
	HampterLib.Hampter_Templates.Non_Event
}

--Supplemental Functions
local function cloneTable<T>(original: {T}): {T}
	local clone = {}
	for i, v in original do
		if typeof(v) ~= "table" then
			clone[i] = v
		else
			clone[i] = cloneTable(v)
		end
	end
	return clone
end

--MetaTable
local MT = {}

--Type Declarations
export type Hampter = typeof(setmetatable({} :: {
	Name: string,
	Type: string,
	Variant: {
		Name: string,
		Rarity: HampterLib.Rarity
	}?,
	Price: number,
	Size: number,
	Mass: number,
	LifeSpan: number,
	Color: Color3,
	Transparency: number,
	Reflectance: number,
	Material: Enum.Material,
	Rarity: HampterLib.RarityLevel,
	MeshId: Content,
	TextureID: Content?,
	Special: typeof(function(hampter: MeshPart) end)?
}, MT))

--Main Module
local hampterFuncs = {}

--Constructors

function hampterFuncs.new(hampterTemplate: HampterLib.HampterTemplate): Hampter	
	local hampter = cloneTable(hampterTemplate) :: HampterLib.HampterTemplate
	
	if hampter.Variants then
		local randNum = math.random(1, 100 * 1000) / 1000
		local variantName: string
		local variantRarity: HampterLib.Rarity
		local variantModifications
		repeat
			for name, variant in hampter.Variants do
				local prob = variant.Rarity.Probability
				if prob > randNum and (not variantName or variant.Rarity.Probability > prob or (variant.Rarity.Probability == prob and math.random(0, 1) == 1)) then
					variantName = name
					variantRarity = variant.Rarity
					if variant.Modification then
						variantModifications = variant.Modification
					end
				end
			end
			if not variantName then
				randNum = math.random(1, 100 * 1000) / 1000
			end
			task.wait()
		until variantName
		
		hampter.Variant = {
			Name = variantName,
			Rarity = variantRarity
		}
		
		if variantModifications then
			for prop, val in variantModifications do
				hampter[prop] = val
			end
		end
		
		hampter.Variants = nil
	end
	
	hampter.Size = math.random(hampter.Size.Min * 100, hampter.Size.Max * 100) / 100
	hampter.Mass = math.round(4/3 * math.pi * (hampter.Size / 2) ^ 3 * hampter.Mass * 100) / 100
	hampter.Price = math.round(hampter.Price * (4/3 * math.pi * (hampter.Size / 2) ^ 3) * (1.24 * hampter.Size ^ -0.25) * 100) / 100
	
	return setmetatable(hampter, MT) :: Hampter
end

function hampterFuncs.randomized(): Hampter
	local randType = ACTIVE_KEYS[math.random(1, #ACTIVE_KEYS)]
	
	local randNum = math.random(1, 100 * 1000) / 1000
	local hampter: HampterLib.HampterTemplate
	repeat
		for _, hampterTemp in randType :: {HampterLib.HampterTemplate} do
			local prob = hampterTemp.Rarity.Probability
			if prob > randNum and (not hampter or hampter.Rarity.Probability > prob or (hampter.Rarity.Probability == prob and math.random(0, 1) == 1)) then
				hampter = hampterTemp
			end
		end
		if not hampter then
			randNum = math.random(1, 100 * 1000) / 1000
		end
	until hampter
		
	return hampterFuncs.new(hampter)
end

--Methods
MT.Methods = {}
MT.__index = MT.Methods

function MT.Methods:Create(position: Vector3, parent: Instance?): MeshPart
	local self = self :: Hampter
	local mesh = AS:CreateMeshPartAsync(self.MeshId)
	local click = Instance.new("ClickDetector")
	local TweenFo = TweenInfo.new(2)
	
	mesh.Anchored = true
	mesh.Name = self.Name
	mesh.Size = Vector3.new(self.Size, self.Size, self.Size)
	mesh.Color = self.Color
	mesh.Transparency = 1
	mesh.Reflectance = self.Reflectance
	mesh.Material = self.Material
	if self.TextureID then mesh.TextureID = self.TextureID.Uri end
	mesh.Position = position
	mesh.Parent = parent or nil
	
	--Tween In
	local Sparks = Instance.new("Sparkles")
	Sparks.SparkleColor = self.Color
	Sparks.Parent = mesh
	local TweenIn = TweenService:Create(mesh, TweenFo, {Transparency = self.Transparency})
	TweenIn:Play()
	
	task.wait(2)
	
	mesh.Anchored = false
	Sparks:Destroy()
	click.Parent = mesh
	
	task.spawn(function()
		local isClicked = false
		local eT = os.clock() + self.LifeSpan
		
		if self.Special then task.spawn(self.Special(mesh)) end
		
		click.MouseClick:Once(function()
			isClicked = true
			print(self.Price)
		end)
				
		while os.clock() < eT and not isClicked do task.wait() end 
		
		local Sound = Instance.new("Sound")
		Sound.Parent = mesh
		if not isClicked then
			click:Destroy()
			Sound.SoundId = "rbxassetid://8090403894"
			
			local Smoke = Instance.new("Smoke")	
			Smoke.Color = self.Color
			Smoke.Parent = mesh
			
			while not Sound.IsLoaded do task.wait() end
			
			local TweenOut = TweenService:Create(mesh, TweenFo, {Transparency = 1})
			TweenOut:Play()
			Sound:Play()
			task.wait(2 + 0.5)
		else
			Sound.SoundId = "rbxassetid://8973091827"
			
			while not Sound.IsLoaded do task.wait() end
			
			Sound:Play()
			task.wait(Sound.TimeLength + 0.5)
		end
		
		
		mesh:Destroy()
	end)
	
	return mesh
end

print("HampterFuncs Initialised!")

return hampterFuncs

Here are my main issues/concerns about my code:

  1. Module1 - the variants section seems fishy to me.
  2. Module1 - the crazy for loops which I still don’t know how to deal with.
  3. Module2 - ineffecient randomizer (in both of the constructors).
  4. Both - I feel like the type declarations could also be cleaned up as well.

Overall it works, but I believe there’s somehow a better way to do it. So if you have any insight/tip/anything, feel free to share :slight_smile:.