Self is "nil" in some areas of script

local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")
local HTTPService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local PhysicsService = game:GetService("PhysicsService")

local UiHandler = require(script.Parent.Parent:WaitForChild("Modules"):WaitForChild("UIHandler"))
local trove = require(ReplicatedStorage:WaitForChild("UtilityModules"):WaitForChild("Trove"))
local GoodSignal = require(ReplicatedStorage:WaitForChild("UtilityModules"):WaitForChild("GoodSignal"))

local placeTowerRemote = ReplicatedStorage:WaitForChild("PlayerRemotes"):WaitForChild("PlaceTower")

local TowerHandler = {}

TowerHandler.__newindex = function()
	error("Unable to obtain tower!")
end

local isClient = RunService:IsClient()
if not isClient then return end

---------
local player = Players.LocalPlayer
local x, y, z 
local towerBeingPlace = false
local towerRaycast = nil
local placeHolderTower
local rotationAngle = 0
local angleCFrame = nil
---------

local function mouseRaycast(tower)
	if not placeHolderTower then print("NOT FOUND") return end
	local raycastParamsTower = RaycastParams.new()
	raycastParamsTower.FilterType = Enum.RaycastFilterType.Exclude
	raycastParamsTower.FilterDescendantsInstances = {player.Character, tower}

	local mousePosition = UserInputService:GetMouseLocation()
	local mouseRay = workspace.CurrentCamera:ViewportPointToRay(mousePosition.X, mousePosition.Y)
	local raycastResult = workspace:Raycast(mouseRay.Origin, mouseRay.Direction * 1000, raycastParamsTower)

	if raycastResult then
		return raycastResult
	else
		return nil
	end
end

function TowerHandler.InitializePlacement(player: Player, tower: Model)
	if towerBeingPlace then print("Already being placed!") end
	local self = {}
	self.Tower = tower:Clone()
	self.Player = player
	self.TowerField = tower.TowerField
	self.towerPrimaryPart = self.Tower.PrimaryPart
	self.UIPart = tower.UIDisplay
	self.Tower.Parent = game.Workspace
	self.__trove = trove.new()
	self.Tower:SetAttribute("UniqueID", player.UserId)

	placeHolderTower = tower
	towerBeingPlace = true

	self.TowerConnection = RunService.Heartbeat:Connect(function(deltaTime)
		local raycastResult = mouseRaycast(self.Tower)
		if raycastResult and raycastResult.Position and towerBeingPlace then
			x, y, z = raycastResult.Position.X, raycastResult.Position.Y + tower.Humanoid.HipHeight + (tower.PrimaryPart.Size.Y / 0.65), raycastResult.Position.Z

			local positionCFrame = CFrame.new(x, y, z)

			local rotationCFrame = CFrame.Angles(0, math.rad(rotationAngle), 0)
			angleCFrame = angleCFrame and angleCFrame:Lerp(rotationCFrame, 0.15) or rotationCFrame

			local finalCFrame = positionCFrame * angleCFrame

			self.Tower:PivotTo(finalCFrame)
			towerRaycast = finalCFrame  
		elseif not towerBeingPlace then
			self.TowerConnection:Disconnect()
			self.TowerConnection = nil
			self.Tower:Destroy()
			towerRaycast = nil
		end
	end)

	TowerHandler:InitializeProperties(self)

	return setmetatable(self, TowerHandler)
end


function TowerHandler:InitializeProperties(main)
	UiHandler.InitializeGUI(player, main.UIPart, main.UIPart.SurfaceGui)
	local properties = require(main.Tower:WaitForChild("Properties"))

	if properties then
		properties(main.Tower)
	end
end

function TowerHandler:InitializeRotation()
	rotationAngle = (rotationAngle - 90) % 360
end


function TowerHandler:InitializeClick()
	print(self.Tower)
	placeTowerRemote:FireServer(towerRaycast, placeHolderTower)
end

function TowerHandler:End()
	towerBeingPlace = false
	rotationAngle = 0
	angleCFrame = nil
	placeHolderTower = nil
	setmetatable(self, nil)
end	

return TowerHandler

self.Tower is printing nil? I’m not sure why. Self is considered nil outside of the TowerHandler.InitializePlacement constructor.

image

The problem is there because the self variable is not retained across different function calls unless you’re explicitly passing the instance around.

1 Like

What would be the solution? I have tried the .__index metamethod on the TowerHandler table but it didn’t work.

local TowerHandler = {}
TowerHandler.__index = TowerHandler

function TowerHandler.InitializePlacement(player: Player, tower: Model)
    if towerBeingPlace then print("Already being placed!") end

    local self = setmetatable({}, TowerHandler)

    self.Tower = tower:Clone()
    self.Player = player
    self.TowerField = tower.TowerField
    self.towerPrimaryPart = self.Tower.PrimaryPart
    self.UIPart = tower.UIDisplay
    self.Tower.Parent = game.Workspace
    self.__trove = trove.new()
    self.Tower:SetAttribute("UniqueID", player.UserId)

    placeHolderTower = tower
    towerBeingPlace = true

    self.TowerConnection = RunService.Heartbeat:Connect(function(deltaTime)
        local raycastResult = mouseRaycast(self.Tower)
        if raycastResult and raycastResult.Position and towerBeingPlace then
            x, y, z = raycastResult.Position.X, raycastResult.Position.Y + tower.Humanoid.HipHeight + (tower.PrimaryPart.Size.Y / 0.65), raycastResult.Position.Z

            local positionCFrame = CFrame.new(x, y, z)
            local rotationCFrame = CFrame.Angles(0, math.rad(rotationAngle), 0)
            angleCFrame = angleCFrame and angleCFrame:Lerp(rotationCFrame, 0.15) or rotationCFrame

            local finalCFrame = positionCFrame * angleCFrame

            self.Tower:PivotTo(finalCFrame)
            towerRaycast = finalCFrame  
        elseif not towerBeingPlace then
            self.TowerConnection:Disconnect()
            self.TowerConnection = nil
            self.Tower:Destroy()
            towerRaycast = nil
        end
    end)

    self:InitializeProperties()  -- Call with self
    return self
end

function TowerHandler:InitializeProperties()
    UiHandler.InitializeGUI(self.Player, self.UIPart, self.UIPart.SurfaceGui)
    local properties = require(self.Tower:WaitForChild("Properties"))

    if properties then
        properties(self.Tower)
    end
end

function TowerHandler:InitializeRotation()
    rotationAngle = (rotationAngle - 90) % 360
end

function TowerHandler:InitializeClick()
    print(self.Tower)
    placeTowerRemote:FireServer(towerRaycast, placeHolderTower)
end

function TowerHandler:End()
    towerBeingPlace = false
    rotationAngle = 0
    angleCFrame = nil
    placeHolderTower = nil
    setmetatable(self, nil)
end    

return TowerHandler

Can we see where you are calling InitializeClick() from? Are you calling from the object itself or the class?

From the looks of it, your class definition seems fine. @PowFPS1’s code does exactly the same thing as what you’re doing; it’s just a preference of styles. You’re passing in the object before the metatable has been assigned as a parameter called main while Pow just assigns the metatable at the beginning and self calls the method.

This means that the issue is most likely due to how you’re calling the click function. Make sure the table that you’re calling it on is the object itself not simply the class.

1 Like

Thank you i fixed it i just needed to pass the object

2 Likes