Hello, everyone! I’ve tried absolutely everything in power to solve this problem, but I’m left at a complete loss at what could be causing multiple unrelated systems to break in my game “Ultimate Camp Tycoon: RE-CAMPED!” Game Link
These are the features that break:
- The GUI “Open Backpack” image button will stop working, preventing the player from opening or closing it again. (Including the shortcut button)
- The Tools that allow you to collect resources from the world will stop working, preventing players from earning resources. (Core gameplay)
- The Proximity Prompt buttons for purchasing tycoon objects will stop showing what the button requires. (Gui Frame that Tweens in and out from top left of the screen)
These are some facts that I have gathered regarding what is happening:
- When one of these things breaks, all 3 of them do. This leads me to believe that they are connected and caused by a single problem.
- Some players never come into contact with this bug. They are able to play the game completely normally, as intended. (This is also my experience when I test it)
- There are no thrown errors or warnings regarding these scripts. Nothing script-wise seems broken, even when I check the developer console in live servers with players who have the bug. (Client and server)
- These 3 things are abstracted from each other, and the only thing connecting them is potentially just the replicated values for the player’s saved resources. However, it can still happen even when the saved data is perfectly fine and loaded, which makes me think that is not the problem.
- There is no particular cause or player action that triggers the bug to appear. I’ve received reports from a whole spectrum of players about what they were doing beforehand.
- There is no particular time this occurs. Some players load into the server with the bug, and others can get it after playing normally for over an hour.
Tools Client side:
resourceCollider.Touched:Connect(function(hit)
if hit.Name == "Stone" or hit.Name == "Wood" or hit.Name == "Metal" or hit.Name == "Gem" or hit.Name == "Cloth" then
--if we haven't collected with this activation
if toolMadeContact == false then
toolMadeContact = true
ToolHandler.PlayerResourceHit(player, hit, tool)
end
end
end)
tool.Activated:Connect(function()
if activateDebounce == false then
activateDebounce = true
toolCollecting = true
while toolCollecting do
tool.ManualActivationOnly = true
toolMadeContact = false
AnimationHandler.PlayAnimation(player, "Swing")
task.wait(RepGlobalConstants.ToolCooldownTime)
toolMadeContact = true
tool.ManualActivationOnly = false
end
activateDebounce = false
end
end)
ToolHandler.PlayerResourceHit = function(player, hit, tool)
local resourceCollider = tool:FindFirstChild("ResourceCollider")
if resourceCollider then
local damage = resourceCollider:GetAttribute(hit.Name .. "Damage")
if damage ~= nil then
--correct tool and damage
if damage > 0 then
local material = hit.Parent
if material ~= nil then
AudioHandler.PlaySFX(hit.Name .. "Hit")
--Check the material health
local materialFullHealth = material:GetAttribute("FullHealth")
local materialHealth = material:GetAttribute("Health")
--do damage to the material
if materialHealth then
materialHealth = materialHealth - damage
local reward = damage
local overrides = RepGlobalConstants.VFXoverrides
overrides.Time = 0.5
PlayClientVFXEvent:FireServer(hit.Name .. "Hit", hit, overrides)
--check if the material is destroyed
if materialHealth <= 0 then
local destroyed = material:GetAttribute("Destroyed")
if not destroyed then
--set destroyed
material:SetAttribute("Destroyed", true)
--the material is destroyed, get the bonus reward
local bonusMaterial = material:GetAttribute("Reward")
--collect and only reward the amount of health left
reward = (bonusMaterial + (damage + materialHealth))
--create a parent part
local particleParent = Instance.new("Part")
particleParent.Anchored = true
particleParent.Position = hit.Position
particleParent.Transparency = 1
particleParent.CanCollide = false
particleParent.Parent = material
task.wait()
--send VFX to client
local overrides2 = RepGlobalConstants.VFXoverrides
overrides2.Time = 2
overrides2.Texture = RepGlobalConstants.VFXTextures[hit.Name]
PlayClientVFXEvent:FireServer("ResourceCompleted", particleParent, overrides2)
else
return 0
end
else
--set the new tree health
material:SetAttribute("Health", materialHealth)
end
--send this data to the remote event for health bar display
activatedHealthBarEvent:FireServer(material.Name, materialFullHealth, materialHealth)
toolHitRemoteEvent:FireServer(hit.Name, reward, material, material:GetAttribute("Destroyed"))
end
else
warn("Material is nil in Reward Handler")
end
--upgrade tool
elseif damage == 0 then
errorFeedbackEvent:FireServer(RepGlobalConstants.ErrorFeedbackMessages.UpgradeTool)
tooltipFeedbackEvent:FireServer(RepGlobalConstants.ToolTipFeedbackMessages.UpgradeTool)
elseif damage == -1 then
errorFeedbackEvent:FireServer(RepGlobalConstants.ErrorFeedbackMessages.WrongTool)
end
else
warn("Damage value not found in Tool Handler")
end
else
warn("Resource Collider not found in Tool Handler")
end
end
Tools Server side (after the remote event):
RewardHandler.HarvestResourceAmount = function(player, resourceName, reward)
local playerData = ProfileDataHandler:GetPlayerData(player)
if playerData ~= nil then
local resources = playerData["Resources"]
local playerStats = playerData["AllStats"]
local playerUpgrades = playerData["UpgradesPurchased"]
--get rep Folder
local repPlayerFolder = repPlayerData:WaitForChild(player.Name)
local passesFolder = repPlayerFolder:WaitForChild("GamePasses")
local pass = passesFolder:WaitForChild("2XResources")
--check for token increase
local newReward = reward + playerUpgrades["ResourcesEarned"]
--this is harvested, check 2X resources.
if pass.Value == true then
newReward = newReward * 2
end
--set new resources
if resourceName ~= nil then
if resources[resourceName] ~= nil then
resources[resourceName] = resources[resourceName] + newReward
ProfileDataHandler:SetPlayerData(player, "Resources", resources)
--track the resources in the players stats
playerStats["ResourcesCollected"] = playerStats["ResourcesCollected"] + newReward
--playerStats["ResourcesCollected"] = 999900
--check for the badge
if playerStats["ResourcesCollected"] > 1000000 then
BadgeHandler.AwardBadge(player, BadgeHandler.badgeIDs.HarvestHero)
end
ProfileDataHandler:SetPlayerData(player, "AllStats", playerStats)
--track the resource quests
if resourceName == "Wood" then
CheckQuestProgress(player, "WoodCollected", newReward)
CheckQuestProgress(player, "WoodCollected2", newReward)
elseif resourceName == "Stone" then
CheckQuestProgress(player, "StoneCollected", newReward)
CheckQuestProgress(player, "StoneCollected2", newReward)
elseif resourceName == "Metal" then
CheckQuestProgress(player, "MetalCollected", newReward)
CheckQuestProgress(player, "MetalCollected2", newReward)
elseif resourceName == "Cloth" then
--check if this reward is a bonus reward
if reward >= 60 then
CheckQuestProgress(player, "TentsDestroyed", 1)
end
end
else
warn("RESOURCES TABLE WAS NIL FOR " .. resourceName)
end
else
warn("RESOURCE NAME WAS NIL!")
end
else
warn("PLAYER DATA WAS NIL IN REWARD HANDLER")
end
end
Backpack:
--BACKPACK BUTTON
local function ActivateBackpackButton()
if backpackGui.Enabled == true then
OvertakeGUI(mainGui)
else
OvertakeGUI(backpackGui, mainGui)
end
end
backpackButton.Activated:Connect(function()
ActivateBackpackButton()
end)
--function that overrides all current GUIs to be disabled in favor of new one
local function OvertakeGUI(guiToEnable, extraGUI)
--check for backpack SFX and visuals
if guiToEnable == backpackGui or extraGUI == backpackGui then
AudioHandler.PlaySFX("BackpackOpened")
backpackButton.Image = "rbxassetid://11437829560"
backpackButton.PressedImage = "rbxassetid://11437829681"
backpackButton.HoverImage = "rbxassetid://11437829681"
elseif backpackGui.Enabled == true then
AudioHandler.PlaySFX("BackpackClosed")
backpackButton.Image = "rbxassetid://11437829681"
backpackButton.PressedImage = "rbxassetid://11437829560"
backpackButton.HoverImage = "rbxassetid://11437829560"
end
for i, v in pairs(containedScreenGuis) do
v.Enabled = false
end
guiToEnable.Enabled = true
if extraGUI then
extraGUI.Enabled = true
end
if UserInputService.GamepadEnabled then
if extraGUI == nil and guiToEnable == mainGui then
GamePadService:DisableGamepadCursor()
else
local mainFrame = guiToEnable:FindFirstChildWhichIsA("Frame")
if mainFrame then
GamePadService:EnableGamepadCursor(mainFrame)
else
warn("No frame available for gamepad cursor")
end
end
end
if guiToEnable == mainGui then
blur.Enabled = false
else
blur.Enabled = true
end
end
Proximity Prompts:
--when a prompt is displayed to this user
ProximityPromptService.PromptShown:Connect(function(prompt)
AudioHandler.PlaySFX("ButtonShrink")
if prompt.Parent ~= nil then
local buttonName = prompt.Parent.Name
if buttonName == "Button" then
TweenButton(prompt, true)
ToggleGui(prompt)
promptingButton = true
end
else
warn("PROXIMITY PROMPT PARENT WAS NIL, MEANING IT WAS DESTROYED ")
end
end)
--when a prompt is no longer being displayed to this user
ProximityPromptService.PromptHidden:Connect(function(prompt)
if prompt.Parent ~= nil then
local buttonName = prompt.Parent.Name
AudioHandler.PlaySFX("ButtonGrow")
--if this was a regular button
if buttonName == "Button" then
TweenButton(prompt, false)
promptingButton = false
end
end
end)
local function ToggleGui(prompt)
--look for a billboard Gui to display
local buttonHead = prompt.Parent
local buttonActivated = buttonHead.Parent
--for each cost frame
for i, v in pairs(costFrames) do
--check that this is a frame
if v:IsA("Frame") then
--check if this frame is needed
local thisAttribute = buttonActivated:GetAttribute(v.Name)
if thisAttribute ~= nil then
local amount = v:WaitForChild("Amount")
--change amount value
amount.Text = RepGlobalConstants.ShortNumber(thisAttribute)
--check color
local playerResourceVal = playerCostTracking[v.Name].Value
local amount = v:WaitForChild("Amount")
if playerResourceVal >= thisAttribute then
amount.TextColor3 = goodResourcesColor
else
amount.TextColor3 = badResourceColor
--check for tooltip
if v.Name == "Gem" then
tooltipFeedbackEvent:FireServer(RepGlobalConstants.ToolTipFeedbackMessages.NeedGems)
end
end
--tween in the cost for this resource
tweenCostsIn[v.Name]:Play()
--tween in the display for this resource
if v.Name ~= "Price" then
tweenIns[v.Name]:Play()
tweenTimers[v.Name] = RepGlobalConstants.ResourceFrameFadeTime
end
else
--tween out the cost frame
tweenCostsOut[v.Name]:Play()
end
end
end
end
--set up a fading display loop
while true do
if not promptingButton then
--tween out all the cost frames
for i, v in pairs(tweenCostsOut) do
v:Play()
end
--for each frame, tick down the tween timers
for i, v in pairs(resourceTrackingFrames) do
--if we are still displaying, tick
if tweenTimers[v.Name] > 0 then
tweenTimers[v.Name] = tweenTimers[v.Name] - 1
--we are done displaying, turn off and tween
else
tweenOuts[v.Name]:Play()
end
end
end
task.wait(0.5)
end
I’ve tried finding forum posts about similar problems, fixing random Roblox errors in the error-report, rewriting the tools to use “GetPartsInPart()” rather than touched events, changing the resource nodes (trees, boulders) to be collected client-side, adding a backpack shortcut with ContextActionService, investigating players with the bug in live servers, and everything in-between.
If you have any ideas, suggestions, thoughts, or theories, I’m open to them. I can provide additional details, code, information, and explanations on anything in the codebase. (I wrote all of it except some modules like ProfileService and Weather systems.)
Thank you for reading!