Crystals appearing in random spots but not in buildings - how would i do this?

for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
		crystal.Touched:Connect(crystalTouched)
	end
end

This means the error is in this section. By the looks of it you havent put any checks to check whether the object named Crystal is actually a BasePart. Objects such as Folders and Models don’t have a .Touched event.

Edit: Sorry, I assumed you had meant this for loop, not the other one.

This for loop, not the one @Quackers337 just said.

which part in the for loop does it get up to

2 Likes

It doesn’t get past this print statement:

for i, gem in pairs(hit:GetTouchingParts()) do
	print(gem.Name)

well actually it doesn’t print anything.

So I guess hit:GetTouchingParts() is nil

1 Like

before print(gem.Name), add print(not not hit.GetTouchingParts).
see if it prints true or false.

If it’s true, that means there’s no touching parts. If it’s false, it means the function doesn’t exist.

1 Like

Part:GetTouchingParts() does not return parts that are only touching, it only returns a list of the parts intersecting with Part.

Parts that are adjacent but not intersecting are not considered touching.

https://developer.roblox.com/en-us/api-reference/function/BasePart/GetTouchingParts

You can also just do print(#hit:GetTouchingParts()) before the for loop. If the resultant number is 0, then no parts are touching and the for loop will not run.

Well I thought that would be how you could find the part the player touched…

part.Hit:Connect() has automatically passes the part the object hit as the first argument.

local debounce = false
part.Hit:Connect(function(partthatithit)
    if debounce then return end
    debounce = true

    -- rest of the code
    debounce = false
end)
1 Like

Does that mean the crystal, or the character?

if the part is the crystal, then hit is an instance descended from the character.

-- Take the crystal that was touched as an argument
local function crystalTouched(crystal, hit)
	if not deb then
		if hit.Parent:FindFirstChild("Humanoid") then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local crystals = plr:WaitForChild('leaderstats').Crystals
			
			crystals.Value = crystals.Value + (1 * plr:WaitForChild("Gamepasses").CrystalsMultiplier.Value)
			
			deb = true
			
                        -- We can use the crystal that was touched since we already know which crystal it is
			crystal.Parent = crystalFolder
			--wait(math.random(10,20))
			wait(1) -- for testing purposes
			crystal.Parent = workspace
			setRandomPosition(crystal)
			deb = false
		end
	end
end

for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
                -- Using an anonymous function here to pass the crystal that was touched
		crystal.Touched:Connect(function(hit) crystalTouched(crystal, hit) end)
	end
end

I would use an anonymous function to pass the crystal that was touched as well as the part that touched it.

2 Likes

This is slightly unrelated but I prefer using debounces like this:

function debounce(func)
    local running = false
    return function(...)
        if not running then
            running = true
            func(...)
            running = false
        end
    end
end

This function was pulled from the bottom of roblox’s article on debounce and it makes .Touched connections look much cleaner. This is how you use it:

part.Touched:Connect(debounce(function(hitPart)
    -- now you don't need anything related to debounce in here
end)) -- don't forget the extra ) from the debounce()
1 Like

uhh…

local rs = game.ReplicatedStorage
local crystalFolder = rs.Crystals

local deb = false
local RNG = Random.new()
local bases = {
    {Object = workspace.Ground.Grass, Weight = 5};
    {Object = workspace.Path, Weight = 2};
}

local function random(choices)
	local weightSum = 0
	for i = 1, #choices do
		weightSum = weightSum + choices[i].Weight
	end

	local rand = RNG:NextInteger(0, weightSum)
	for i = 1, #choices do
		if rand <= choices[i].Weight then
			return choices[i].Object
		end
		rand = rand - choices[i].Weight
	end
end

local function setRandomPosition(item)
	local Base = random(bases)

	item.CFrame = CFrame.new(
		RNG:NextInteger((-Base.Size.X / 2), (Base.Size.X / 2)),
		Base.Position.Y + (Base.Size.Y / 2) + (item.Size.Y / 2),
		RNG:NextInteger((-Base.Size.Z / 2), (Base.Size.Z / 2))
	) * CFrame.Angles(0, math.rad(RNG:NextInteger(0, 360)), 0)
end

local function crystalTouched(crystal, hit)
	if not deb then
		if hit.Parent:FindFirstChild("Humanoid") then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local crystals = plr:WaitForChild('leaderstats').Crystals
			
			crystals.Value = crystals.Value + (1 * plr:WaitForChild("Gamepasses").CrystalsMultiplier.Value)
			
			deb = true
			
			crystal.Parent = crystalFolder
			print("Yay")
			--wait(math.random(8,20))
			wait(1)
			print("Waited")
			crystal.Parent = workspace
			setRandomPosition(crystal)
			deb = false
		end
	end
end

for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
		crystal.Touched:Connect(function(hit)
			crystalTouched(crystal, hit)
		end)
	end
end

I got print statements in the output but then I think the bases disappeared.

I also got this error again! (yay)

attempt to call a nil value

in between the two print statements.

local rs = game.ReplicatedStorage
local crystalFolder = rs.Crystals

local deb = false
local RNG = Random.new()
local bases = {
    {Object = workspace.Ground.Grass, Weight = 5};
    {Object = workspace.Path, Weight = 2};
}

local function random(choices)
	local weightSum = 0
	for i = 1, #choices do
		weightSum = weightSum + choices[i].Weight
	end

	local rand = RNG:NextInteger(0, weightSum)
	for i = 1, #choices do
		if rand <= choices[i].Weight then
			return choices[i].Object
		end
		rand = rand - choices[i].Weight
	end
end

local function setRandomPosition(item)
	local Base = random(bases)

	item.CFrame = CFrame.new(
		RNG:NextInteger((-Base.Size.X / 2), (Base.Size.X / 2)),
		Base.Position.Y + (Base.Size.Y / 2) + (item.Size.Y / 2),
		RNG:NextInteger((-Base.Size.Z / 2), (Base.Size.Z / 2))
	) * CFrame.Angles(0, math.rad(RNG:NextInteger(0, 360)), 0)
end

local function crystalTouched(crystal, hit)
	if not deb then
                deb = true
		if hit.Parent:FindFirstChild("Humanoid") then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local crystals = plr:WaitForChild('leaderstats').Crystals
			
			crystals.Value = crystals.Value + (1 * plr:WaitForChild("Gamepasses").CrystalsMultiplier.Value)
			
			crystal.Parent = crystalFolder
			print("Yay")
			--wait(math.random(8,20))
			wait(1)
			print("Waited")
			crystal.Parent = workspace
			setRandomPosition(crystal)
			deb = false
		end
	end
end

for i, crystal in pairs(workspace:GetChildren()) do
	if crystal.Name == "Crystal" then
		crystal.Touched:Connect(function(hit)
			crystalTouched(crystal, hit)
		end)
	end
end

Try this.
I moved the deb = true to the top of the if statement. There will be a fraction of a second of lag while assigning the variables because you used :WaitForChild() and :GetPlayerFromCharacter(). During this time, a new .Touched even could have fired on the same crystal, and since deb is not set to true yet, the function executes.

1 Like

Ok it worked but the bases are still disappearing along with the other crystals.

The bases should just be transparent CanCollide = false rectangles that mark the areas where the crystals can spawn. You’re not supposed to be able to see them.

1 Like

ohhhh… that’s why.

If the problem is fixed you can mark the answer that fixed it for you as the solution. It will help others when they view the thread by allowing them to jump straight to the answer rather than read through every reply.

1 Like

One problem still:
The grass keeps moving as well as crystals that were not touched:

local crystalFolder = game.ServerStorage.Crystals

local deb = false
local RNG = Random.new()
local bases = {
    {Object = workspace.Base, Weight = 3};
    {Object = workspace.Base2, Weight = 4};
}

local function random(choices)
	local weightSum = 0
	for i = 1, #choices do
		weightSum = weightSum + choices[i].Weight
	end

	local rand = RNG:NextInteger(0, weightSum)
	for i = 1, #choices do
		if rand <= choices[i].Weight then
			return choices[i].Object
		end
		rand = rand - choices[i].Weight
	end
end

local function setRandomPosition(item)
	local Base = random(bases)

	item.CFrame = CFrame.new(
		RNG:NextInteger((-Base.Size.X / 2), (Base.Size.X / 2)),
		Base.Position.Y + (Base.Size.Y / 2) + (item.Size.Y / 2),
		RNG:NextInteger((-Base.Size.Z / 2), (Base.Size.Z / 2))
	) * CFrame.Angles(0, math.rad(RNG:NextInteger(0, 360)), 0)
end

local function crystalTouched(crystal, hit)
	if not deb then
		deb = true
		if hit.Parent:FindFirstChild("Humanoid") then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local crystals = plr:WaitForChild('leaderstats').Crystals
			
			crystals.Value = crystals.Value + (1 * plr:WaitForChild("Gamepasses").CrystalsMultiplier.Value)
			
			crystal.Parent = crystalFolder
			print("Yay")
			--wait(math.random(8,20))
			wait(1)
			print("Waited")
			crystal.Parent = workspace
			setRandomPosition(crystal)
			deb = false
		else
			deb = false
		end
	end
end


script.Parent.Touched:Connect(function(hit)
	crystalTouched(script.Parent, hit)
end)

Note: I moved the script into every crystal.

It works again after I deleted some welds. (Idk how that does anything but whatever maybe I’m dumb)

One thing though: some crystals appear in the tycoons which could be bad.