Problems with my tree system

I have a tree system that is only half working. Problems include:

  1. Can only pick up one type of collectible.
  2. On generation, all the tree collectibles are respawned.
  3. Having multiple trees will spawn collectibles exponentially.

I have been trying for hours to figure out ways to fix these bugs but with no luck.

Server:

local ReplicatedStorage = game:GetService"ReplicatedStorage"
local ServerScriptService = game:GetService"ServerScriptService"

local Knit = require(ReplicatedStorage.Packages.Knit)
local Component = require(ReplicatedStorage.Packages.Component)
local Trove = require(ReplicatedStorage.Packages.Trove)
local Collectables = require(ServerScriptService.Source.Modules.Collectables)

local Generate = ReplicatedStorage:WaitForChild "Remotes".Tree.Generate
local RemoveObject = ReplicatedStorage:WaitForChild "Remotes".Tree.RemoveObject

local Tree = Component.new {
    Tag = "Tree"
}

function Tree:Construct()
    self._Trove = Trove.new()
    self.Random = Random.new()

    self.PlayersCooldown = {}
    self.ItemData = {}
    self.CooldownTime = 1

     for _, Player in pairs(game.Players:GetPlayers()) do
        self.ItemData[Player.UserId] = {}
    end

    game.Players.PlayerAdded:Connect(function(Player)
        self.ItemData[Player.UserId] = {}
	end)
	
	game.Players.PlayerRemoving:Connect(function(Player)
		self.ItemData[Player.UserId] = nil
		self.PlayersCooldown[Player.UserId] = nil
	end)
end

function Tree:RollCollectable()
    local Counter = 0

    for Index, _ in Collectables do
        Counter += Collectables[Index][2]
    end

    local Chosen = self.Random:NextNumber(0, Counter)

    for Index, _ in Collectables do
        Counter -= Collectables[Index][2]
        if Chosen > Counter then
            return Collectables[Index][1]
        end
    end
end

function Tree:Start()
    local Prompt = self.Instance.PrimaryPart.Prompt.ShakePrompt
    local DataService = Knit.GetService("DataService")

     self._Trove:Add(Prompt.Triggered:Connect(function(Player)

        if not self.PlayersCooldown[Player.UserId] then
            self.PlayersCooldown[Player.UserId] = os.time() - self.CooldownTime
        end

        if os.time() - self.PlayersCooldown[Player.UserId] >= self.CooldownTime then
            local RandomAmount = self.Random:NextInteger(1, 3)
        
            for _ = 1, RandomAmount do
                local Type = self:RollCollectable()

                if not self.ItemData[Player.UserId][Type] then
                    self.ItemData[Player.UserId][Type] = 0
                    self.ItemData[Player.UserId][Type] += 1
                else
                    self.ItemData[Player.UserId][Type] += 1
                end
            end
            
            print(self.ItemData) -- {[Player.UserId] = {Apple = 2, Orange = 1}}
            Generate:FireClient(Player, self.ItemData[Player.UserId], self.Instance)

            self.PlayersCooldown[Player.UserId] = os.time()
        end
    end))

    RemoveObject.OnServerInvoke = function(Player: Player, InstanceName: string)
        for Index, Data in self.ItemData[Player.UserId] do
            print(self.ItemData[Player.UserId])
            print(Index, Data, InstanceName) -- Apple, 2, Orange
            if Index == InstanceName then
                Data -= 1

                local Profile = DataService:Get(Player)

                if not Profile.Data.Inventory[InstanceName] then
                    Profile.Data.Inventory[InstanceName] = 0
                else
                    Profile.Data.Inventory[InstanceName] += 1
                end

                return true
            else
                return false
            end
        end
    end
end

function Tree:Stop()
    self._Trove:Clean()
end

return Tree

Client:

local ReplicatedStorage = game:GetService"ReplicatedStorage"

local Component = require(ReplicatedStorage.Packages.Component)
local Trove = require(ReplicatedStorage.Packages.Trove)

local Generate = ReplicatedStorage:WaitForChild "Remotes".Tree.Generate
local RemoveObject = ReplicatedStorage:WaitForChild "Remotes".Tree.RemoveObject

local TreeClient = Component.new {
    Tag = "Collectable",
    Ancestors = {ReplicatedStorage, workspace}
}

function TreeClient:Construct()
    self._Trove = Trove.new()
end

function TreeClient:Start()
    self._Trove:Add(self.Instance)

    self._Trove:Connect(Generate.OnClientEvent, function(ItemData: {[string]: number}, Tree)
        for _, _ in Tree.Spawned:GetChildren() do
            if not self.Instance.Parent then
                return
            end

            if self.Instance.Parent.Parent == Tree then
                self._Trove:Remove(self.Instance)
            end
        end

        for Name, Amount in ItemData do
            if self.Instance.Name == Name then
                for _ = 1, Amount do
                    local ClonedObject = self.Instance:Clone()
    
                    ClonedObject:PivotTo(Tree.PrimaryPart.CFrame * CFrame.new(math.random(1, 3), 3, math.random(1, 3)))
                    ClonedObject.Parent = Tree.Spawned
                end    
            end 
        end
    end)

    if self.Instance.PrimaryPart then
        self._Trove:Add(self.Instance.PrimaryPart.CollectPrompt.Triggered:Connect(function()
            local IsToRemove = RemoveObject:InvokeServer(self.Instance.Name)
    
            if IsToRemove then
                self._Trove:Remove(self.Instance)
            end
        end))
    else
        return
    end
end

function TreeClient:Stop()
    self._Trove:Clean()
end

return TreeClient

The first issue is in the server script:

 RemoveObject.OnServerInvoke = function(Player: Player, InstanceName: string)
        for Index, Data in self.ItemData[Player.UserId] do
            print(self.ItemData[Player.UserId])
            print(Index, Data, InstanceName) -- Apple, 2, Orange
            if Index == InstanceName then
                Data -= 1

                local Profile = DataService:Get(Player)

                if not Profile.Data.Inventory[InstanceName] then
                    Profile.Data.Inventory[InstanceName] = 0
                else
                    Profile.Data.Inventory[InstanceName] += 1
                end

                return true
            else
                return false
            end
        end
    end

The commented portion says enough, but for some reason, the index will always be the same depending on which collectible I get first.

The second problem lies in the client, on generation, the ItemData will not account for the already spawned collectibles in the Spawned folder in the tree. I tried subtracting the amount spawned by the current ItemData, however, nothing seemed to work and I do not know the best way of doing it.

I honestly have no idea what is wrong for the third problem to occur.

Any help is helpful, thank you.

Sounds like event stacking in your on client event in tree client since you have an on client event for a single remote instance.

Usually when scripts execute more than once unexpectedly it’s due to event stacking.

local remote = script.Parent:WaitForChild("RemoteEvent")

remote.OnClientEvent:Connect(function(...)
	print("On client event")
end)

remote.OnClientEvent:Connect(function(...)
	print("On client event")
end)
remote.OnClientEvent:Connect(function(...)
	print("On client event")
end)

image

1 Like

It doesn’t seem to be the problem.

function TreeClient:Construct()
    self._Trove = Trove.new()

    self.RemoteCooldown = {}
    self.CooldownTime = 1
end

function TreeClient:Start()
    self._Trove:Add(self.Instance)

    self._Trove:Connect(Generate.OnClientEvent, function(ItemData: {[string]: number}, Tree)
        if not self.RemoteCooldown[Knit.Player.UserId] then
            self.RemoteCooldown[Knit.Player.UserId] = os.time() - self.CooldownTime
        end

        if os.time() - self.RemoteCooldown[Knit.Player.UserId] >= self.CooldownTime then
            for _, _ in Tree.Spawned:GetChildren() do
                if not self.Instance.Parent then
                    return
                end
    
                if self.Instance.Parent.Parent == Tree then
                    self._Trove:Remove(self.Instance)
                end
            end
    
            for Name, Amount in ItemData do
                if self.Instance.Name == Name then
                    print(Name, Amount)
                    for _ = 1, Amount do
                        local ClonedObject = self.Instance:Clone()
        
                        ClonedObject:PivotTo(Tree.PrimaryPart.CFrame * CFrame.new(math.random(1, 3), 3, math.random(1, 3)))
                        ClonedObject.Parent = Tree.Spawned
                    end
                end 
            end
        end
    end)

    if self.Instance.PrimaryPart then
        self._Trove:Add(self.Instance.PrimaryPart.CollectPrompt.Triggered:Connect(function()
            local IsToRemove = RemoveObject:InvokeServer(self.Instance.Name)
    
            if IsToRemove then
                self._Trove:Remove(self.Instance)
            end
        end))
    else
        return
    end
end

It’s still multiplying it, but this time I debugged a little bit more to become a little bit more confused:

https://i.gyazo.com/6787a07f8323767f837c11f04d739f81

https://i.gyazo.com/fd1f593a2e504572804ba021b426d071

Hello, again! :smiley:

It seems like you’re facing several issues with your code. Let’s go through them one by one:

  1. Can only pick up one type of collectible. This issue could be due to how you’re handling the collection of collectibles in your RemoveObject.OnServerInvoke function. You’re returning false as soon as a match isn’t found for the InstanceName, which means only the first collectible in the ItemData list is being considered. To fix this, you should only return false after checking all the collectibles:
RemoveObject.OnServerInvoke = function(Player: Player, InstanceName: string)
    for Index, Data in pairs(self.ItemData[Player.UserId]) do
        print(self.ItemData[Player.UserId])
        print(Index, Data, InstanceName) -- Apple, 2, Orange
        if Index == InstanceName then
            Data -= 1

            local Profile = DataService:Get(Player)

            if not Profile.Data.Inventory[InstanceName] then
                Profile.Data.Inventory[InstanceName] = 0
            end
            Profile.Data.Inventory[InstanceName] += 1

            return true
        end
    end
    return false
end`Preformatted text`

  1. On generation, all the tree collectibles are respawned. It looks like you’re not correctly handling the spawned collectibles. When you’re generating new collectibles, you should first remove the previously spawned collectibles in the Generate.OnClientEvent event. Here’s a possible way to do this:
self._Trove:Connect(Generate.OnClientEvent, function(ItemData: {[string]: number}, Tree)
    for _, child in ipairs(Tree.Spawned:GetChildren()) do
        if child:IsA('Model') and child.Name == self.Instance.Name then
            child:Destroy()
        end
    end

    -- Your existing code follows here...
end)

  1. Having multiple trees will spawn collectibles exponentially. This problem could be occurring because each tree is independently spawning collectibles. This means that the total number of collectibles being spawned is the sum of the collectibles from each tree. You should modify your collectible spawning code to take into account the total number of trees and distribute the collectibles evenly among them. This would require a more significant change in your code, and the specific implementation depends on how you want to handle this situation.

Please try the above solutions for the first two problems and let me know if they work. Then we can look at the third problem in more detail.

Looks like we meet again!

  1. That was the problem and it is now fixed.
  2. That also works!
  3. I was thinking, could it be caused by cloning self.Instance instead of the actual collectibles from the folder in ReplicatedStorage? I tried it before, but it didn’t work, however, I could have been doing it wrong.