The problem with self and metatable

I am creating a coin spawn system, and different types of them. The problem is in the line function createDiamondCoin:SpawnCoins(player) which is nil, so nothing works.

-- Services
local players = game:GetService("Players")
local replicatedStorage = game:GetService("ReplicatedStorage"):WaitForChild("SpawnCoinsSystemV2")

-- Variables
local storageCoins = {} -- Table for storing coins

-- Events
local playLocalSoundTouchedCoinEvent = replicatedStorage:WaitForChild("Events"):WaitForChild("PlayLocalSoundTouchedCoin")

-- Modules
local animationCoinModule = require(replicatedStorage:WaitForChild("AnimationCoinModule"))
local leaderstatsModule = require(replicatedStorage:WaitForChild("LeaderstatsModule"))

-- Custom coin data types
type coinsType = {
	name: string,
	typeCoin: string,
	rarity: number,
	value: number,
}
-- Initialize special type for special coins
type diamondCoins = coinsType & {
	specialAbility: string?
}

-- Function to create coins
function storageCoins:CreateCoins(name: string, typeCoin: string, rarity: number, value: number): coinsType
	
	-- Table with type coinsType
	local coin: coinsType = {
		name = name,
		typeCoin = typeCoin,
		rarity = rarity,
		value = value
	}
	
	-- Set metatable
	setmetatable(coin, { __index = self })
	
	-- Function to spawn coins
	function storageCoins:SpawnCoins(player: Player, specialCoin: string): coinsType
		local character = player.Character or player.CharacterAdded:Wait()
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		
		-- Check for player's humanoid
		if character and humanoid then
			local spawnCoinsPart = workspace:WaitForChild("SpawnCoinsPart"):FindFirstChild("SpawnCoinsPlatform")
			
			local offset = 5 -- Offset for margin
			
			-- Get the part size range for spawning coins
			local minX = spawnCoinsPart.Position.X - spawnCoinsPart.Size.X / 2 + offset
			local maxX = spawnCoinsPart.Position.X + spawnCoinsPart.Size.X / 2 - offset
			local minZ = spawnCoinsPart.Position.Z - spawnCoinsPart.Size.Z / 2 + offset
			local maxZ = spawnCoinsPart.Position.Z + spawnCoinsPart.Size.Z / 2 - offset
			local minY = spawnCoinsPart.Position.Y + spawnCoinsPart.Size.Y / 2
			
			-- Create region
			local region3 = Region3.new(Vector3.new(minX, minY, minZ), Vector3.new(maxX, minY, maxZ))
			
			-- Get the minimum and maximum points of region3
			local min = region3.CFrame.Position - (region3.Size / 2)
			local max = region3.CFrame.Position + (region3.Size / 2)
			
			-- Calculate center and size of Region3
			local center = (min + max) / 2
			local size = max - min
			
			-- Visualize region
			local part = Instance.new("Part", workspace)
			part.Name = "VisualizationPartRegion3"
			part.Position = center
			part.Size = size
			part.Color = Color3.fromRGB(0, 170, 0)
			part.Transparency = .5
			part.Anchored = true
			part.CanCollide = false
			
			print(region3)
			
			-- Loop to spawn coins within region3
			local folder = Instance.new("Folder", workspace)
			folder.Name = "CollectionCoinsFolder"
			
			local currentCoin = 1
			
			-- Overlapping function
			local function overlappingFunction(position: Vector3, orientation: CFrame, ...: MeshPart)
				for _, _coin in ipairs({...}) do
					-- Get position and orientation of the current coin
					local coinPosition = _coin.Position
					local coinOrientation = _coin.CFrame - _coin.Position
					
					-- Calculate distance between coin centers
					local distance = (coinPosition - position).Magnitude
					
					-- Calculate coin radius
					local coinRadius = _coin.Size.Magnitude / 2
					
					-- Check for overlap considering radius
					if distance <= coinRadius then
						local deltaOrientation = orientation:ToObjectSpace(coinOrientation)
						if deltaOrientation.Position.Magnitude <= coinRadius then
							return true
						end
					end
				end
				return false
			end
			
			-- Randomized coin spawn loop
			for _, value: MeshPart in pairs(replicatedStorage:GetChildren()) do
				if value:IsA("MeshPart") and value.Name == "Coin" then
					
					repeat
						-- Function for generating random rotation
						local function randomRotationObject(minY, maxY)
							return math.random() * (maxY - minY) + minY
						end
						-- Create rotation matrix
						local function randomCFrame(minY, maxY)
							return CFrame.Angles(0, randomRotationObject(minY, maxY), 0)
						end
						
						-- Randomize coin positions
						local randomX = math.random(minX, maxX)
						local randomY = math.random(minY, minY)
						local randomZ = math.random(minZ, maxZ)
						
						-- Results of calculations
						local randomPosition = Vector3.new(randomX, randomY + 1, randomZ)
						local randomRotation = randomCFrame(0, math.rad(360))
						
						-- Clone coin
						local newCoin = value:Clone()
						newCoin.Parent = folder
						newCoin.Name = "ClonedCoin" .. currentCoin
						newCoin.Position = randomPosition
						newCoin.Transparency = 0
						newCoin.CanCollide = false
						newCoin.CanQuery = true
						newCoin.CanTouch = true
						newCoin.CFrame *= randomRotation
						
						currentCoin = currentCoin + 1
						print(newCoin.Name)
						
						table.insert(self, newCoin)
						
					until currentCoin or not overlappingFunction(randomPosition, randomRotation, table.unpack(self))
					print("Were created: " .. #self .. " coins")
				end
			end
		else
			warn("The player does not have Humanoid")
		end
		print("Spawning coins for player:", player.Name)
		return self
	end
	return self
end

-- Spawn special type of coins
function storageCoins:DiamondSpawnCoins(): diamondCoins
	local createDiamondCoin = self:CreateCoins("DiamondCoin", "Special", 30, 4)
	print(table.unpack(self)) -- nil
	print(createDiamondCoin) -- index table
	-- Inherit or override SpawnCoins function
	function createDiamondCoin:SpawnCoins(player)
		-- Call base implementation
		if self.__index and self.__index.SpawnCoins then
			self.__index:SpawnCoins(player, "DiamondCoin") -- Call common coin spawning logic
		else
			warn("11111111111")
		end
		
		self.specialAbility = "TestString"
		
		print("Spawn diamonds for player:", player.Name)
	end
	return createDiamondCoin
end

-- Function to handle coin touch event
local function onTouchedCoin(coin: MeshPart, player: Player)
	playLocalSoundTouchedCoinEvent:FireClient(player, true)
end

-- Loop to find coins
for _, index: MeshPart in ipairs(workspace:GetDescendants()) do
	if index:IsA("MeshPart") and string.match(index.Name, "^ClonedCoin%d+$") then
		index.Touched:Connect(function(hit: BasePart)
			if hit.Parent and hit:FindFirstChildOfClass("Humanoid") then
				local player = players:GetPlayerFromCharacter(hit.Parent)
				if player then
					onTouchedCoin(index, player)
				end
			end
		end)
	end
end

-- Event handler
players.PlayerAdded:Connect(function(player)
	local specialCoin = storageCoins:DiamondSpawnCoins()
	specialCoin:SpawnCoins(player)
end)

3 Likes

I have question, you call those functions like that?

local Coin = CoinsModule.new(some data)
Coin:Spawn(player) -- example function

If not then self will don’t work

3 Likes

This is a server script, not a module.

2 Likes

right, but you call module like this?

EDIT: It’s important how you call module, because what i mentioned above is the only way self can work, as it passes table itself onto module script

1 Like

Yes, I use it as you indicated, but the problem as I wrote above is in the function: function storageCoins:DiamondSpawnCoins(), line: function createDiamondCoin:SpawnCoins(player). To be more precise, the error is: attempt to index nil.

2 Likes

why would you use table.unpack ??? it doesn’t work this way, you can print self and you’ll be fine

1 Like

This is just for debugging, it has nothing to do with the problem.

1 Like

I’m confused on what your issue is here, could you please elaborate?

is this unintended to be nil?

1 Like

I explained it. When I call the spawnCoins function, a nil indexing error occurs, I believe the problem is in self. You can check the script in the game yourself.

1 Like

For starters, did you intend to return self from the original SpawnCoins() function, because you set its return type to coinsType but returned self. Which in this case would be storageCoins not following the correct type.

If you mean that you need to return the table instead of self, you are right, but I can’t return the table because a conversion error occurs.

Can you send a screenshot of the exception traceback you’re getting?
It’s a bit hard to debug this issue without additional information.
You can also try setting a breakpoint to trigger when the function is called to step through every line until you encounter your exception.

I just put the code into studio and did not get a conversion error when returning coin from the function, where did this error occur?

Code block:

function storageCoins:DiamondSpawnCoins():diamondCoins
	local createDiamondCoin=self:CreateCoins("DiamondCoin","Special",30,4)
	
	function createDiamondCoin:_SpawnCoins(player)
		--if self.__index and self.__index.SpawnCoins then
		--	self.__index:SpawnCoins(player,"DiamondCoin")
		--else
		--	warn("11111111111")
		--end
		
		self.__index:SpawnCoins(player,"DiamondCoin")
		
		self.specialAbility="TestString"
		
		print("Spawn diamonds for player:", player.Name)
	end
	return createDiamondCoin
end

Error: ServerScriptService.CoinsSystemV2.SpawnCoinsSystemScriptV2:179: attempt to index nil with ‘SpawnCoins’.

Show me where you did it. (I am writing the message in brackets due to restrictions)

You never returned the coinType value from storage coins:CreateCoins() instead you returned storageCoins which doesn’t have a __index k/v.

function storageCoins:CreateCoins(name: string, typeCoin: string, rarity: number, value: number): coinsType

	-- Table with type coinsType
	local coin: coinsType = {
		name = name,
		typeCoin = typeCoin,
		rarity = rarity,
		value = value
	}

	-- Set metatable
	setmetatable(coin, { __index = self })

	-- Function to spawn coins
	function storageCoins:SpawnCoins(player: Player, specialCoin: string): coinsType
		print("og spawn coin")
		return coin
	end
	return coin
end

I removed the unneeded part of this function for the debugging, but as you can see it returns coin

I don’t understand why it works for you. When I try to return a table, I immediately get a syntax error. Could the error be because I’m using types?

изображение

This could be because of type checking. Hovering over the underlined code should display the reason for the warning. Also the yellow outlines do not indicate a syntax error. It’s purely a warning provided by the code editor.

Correct me if that’s not what you were asking.