What do you want to achieve?
I’m trying to make a candy tool that activates a random candy effect. It’ll get a random ID from a list of candy effects, and do something to the player character or in the game once activated.
All of this works but none of the RunService.Heartbeat events, or as I call them loops, within the function ever run.
What is the issue?
Connections to events within the candy handler are not firing at all, but only when the function is ran inside of a tool. When ran using my custom admin command system, the events run perfectly fine (but I obviously want them to run in a tool.)
The Candy Handler function:
function CandyEffects:HandleEffectAsync(effectId:string, player:Player, character:Model, humanoid:Humanoid)
print("HandleEffectAsync", effectId, player, character, humanoid)
if effectId == "Messy" then
if not character then return end
if not character.Parent then return end
local root = character.PrimaryPart
if not root then return end
local balls = {}
local amount = math.random(13, 24)
for _ = 1, amount do
local ball:Part = ReplicatedStorage.Objects.Modifiers.BouncyBall:Clone()
ball.Position = root.Position + Vector3.new(math.random(-5, 5), math.random(-5, 5), math.random(-5, 5))
ball.Size = Vector3.new(1,1,1) * math.random(2, 5)
ball.CanQuery = false
ball.CollisionGroup = "Debris"
ball.Parent = workspace.Debris
table.insert(balls, ball)
end
RunService.Heartbeat:Wait()
for _, ball in ipairs(balls) do
ball.AssemblyLinearVelocity += Vector3.new(math.random(-250, 250), math.random(-250, 250), math.random(-250, 250))
end
-- ...
elseif effectId == "RazorBlades" then
print("rz1", character, humanoid)
if not character then return end
if not humanoid then return end
print("rz2", character, humanoid)
local connection:RBXScriptConnection
local canTakeDamage = true
local hitTimes = 0
print("made connection")
connection = RunService.Heartbeat:Connect(function()
print("loop")
if not canTakeDamage then return print("cannot take dmg") end
if hitTimes >= 16 then print("did 16 hits") return connection:Disconnect() end
if not character.Parent then print("no character parent") return connection:Disconnect() end
if not humanoid.Parent then print("no humanoid parent") return connection:Disconnect() end
if humanoid.Health <= 0 then print("player died") return connection:Disconnect() end
print("do damage")
humanoid:TakeDamage(1.3477893139510)
hitTimes += 1
canTakeDamage = false
print("do waiting")
task.wait(0.5)
print("stopped waiting")
canTakeDamage = true
end)
elseif effectId == "Train" then
if not player then return end
if not player.Parent then return end
ReplicatedStorage.Events.CandyEffects.Train:FireClient(player)
-- more effects...
end
end
I’ve singled out 3 candy effects in particular:
The Messy effect works, but none of the bouncy balls are given velocity
The RazorBlades effect does not work at all, the rz1, rz2 and made connection log will all be made but none of the code inside the Heartbeat event runs
and The Train effect works perfectly (the event is fired to the client properly)
The tool code:
local ServerScriptService = game:GetService("ServerScriptService")
local CandyEffects = require(ServerScriptService.CandyEffects)
local CandyTool = require(ServerScriptService.ScriptedTools.CandyTool)
local Tool = script.Parent
local Handle = Tool.Candy
local CandyVariant = Tool:GetAttribute("CandyVariation") or "CandyCircleRed"
CandyTool:CreateTool(Tool, Handle, NumberRange.new(0), CandyVariant)
The CandyTool module:
-- Acts like a class to be inherited, since all of the candy tools are exactly the same.
function CandyTool:CreateTool(tool:Tool, handle:BasePart, levelNumberRange:NumberRange, saveId:string?)
local Tool = tool
local Handle = handle
local OwnerPlayer:Player = nil
local OwnerCharacter:Model = nil
local OwnerHumanoid:Humanoid = nil
local Equipped = false
local CanEat = true
local animationMotor:Motor6D = nil
local animationH:AnimationTrack = nil
local animationU:AnimationTrack = nil
-- if the tool wasnt already initialized with a variation, do it with the provided one here
if saveId then
Tool:SetAttribute("CandyVariation", saveId)
end
Tool.Equipped:Connect(function()
local character = Tool:FindFirstAncestorWhichIsA("Model")
if not character then return end
local player = PlayersService:GetPlayerFromCharacter(character)
if not player then return end
local humanoid = character:FindFirstChildWhichIsA("Humanoid")
if not humanoid then return end
local rightHand = character:FindFirstChild("RightHand")
if not rightHand then return end
Equipped = true
OwnerPlayer = player
OwnerCharacter = character
OwnerHumanoid = humanoid
-- this just makes it so it runs the KnownCandyEffect if the same tool is used again
if saveId and module.SavedEffects[saveId] then
Tool:SetAttribute("KnownCandyEffect", module.SavedEffects[saveId])
end
local animator = humanoid:FindFirstChildWhichIsA("Animator")
if not animator then return end
local motor = Instance.new("Motor6D")
motor.Part0 = rightHand
motor.Part1 = Handle
motor.Name = "CandyMotor"
motor.Parent = rightHand
animationMotor = motor
if not (animationH or animationU) then
animationH = animator:LoadAnimation(Tool.CandyHold)
animationU = animator:LoadAnimation(Tool.CandyEat)
end
animationH:Play()
Handle.Equip.SoundGroup = workspace.ScriptResources
Handle.Equip.TimePosition = 0
Handle.Equip:Play()
end)
Tool.Unequipped:Connect(function()
Equipped = false
if animationMotor then animationMotor:Destroy() end
if animationH then animationH:Stop() end
if animationU then animationU:Stop() end
end)
Tool.Activated:Connect(function()
if not CanEat then return end
if not Equipped then return end
CanEat = false
if animationH then animationH:Stop() end
if animationU then animationU:Play() end
Handle.Eat.SoundGroup = workspace.ScriptResources
Handle.Eat.TimePosition = 0.05
Handle.Eat:Play()
task.wait(0.5)
if not Equipped then
CanEat = true
return
end
-- to test this easier i've put RazorBlades by default
local knownEffectId = module.SavedEffects[saveId] or "RazorBlades"
if knownEffectId then
print("Tool", OwnerPlayer, OwnerPlayer.Parent, OwnerCharacter, OwnerCharacter.Parent, OwnerHumanoid, OwnerHumanoid.Parent)
task.spawn(function()
CandyEffects:HandleEffectAsync(knownEffectId, OwnerPlayer, OwnerPlayer.Character, OwnerHumanoid)
end)
CandyEffects:DisplayEffect(knownEffectId, OwnerPlayer)
else
local effect = CandyEffects:HandleAndDisplayRandomEffect(OwnerPlayer, OwnerCharacter, OwnerHumanoid, levelNumberRange)
if saveId then
module.SavedEffects[saveId] = effect.id
end
end
if animationMotor then animationMotor:Destroy() end
if animationH then animationH:Stop() end
if animationU then animationU:Stop() end
Tool:Destroy()
end)
end
And the admin command, where all effects work fully with no problems:
function Command:Invoke(initiator:Player, args:{string})
local targetCandy = tostring(args[1] or "")
local candyEffect = CandyEffects:GetEffectById(targetCandy, true)
if not candyEffect then
ReplicatedStorage.Events.AdminCommands.SendMessage:FireClient(initiator, "That candy effect does not exist. Try removing any symbols or spaces.", "warn")
return
end
local targetPlayers = {initiator}
if args[2] then
targetPlayers = ArgumentParser:ParsePlayerKeyword(initiator, args[2])
end
if #targetPlayers <= 0 then return end
RuntimeManipulator:RevokeGameRewards(initiator)
for _, player in ipairs(targetPlayers) do
local humanoid:Humanoid = nil
if player.Character then
humanoid = player.Character:FindFirstChildWhichIsA("Humanoid")
end
task.spawn(function()
CandyEffects:HandleEffectAsync(candyEffect.id, player, player.Character, humanoid)
end)
ReplicatedStorage.Events.AdminCommands.SendMessage:FireClient(initiator, "Applied " .. candyEffect.name .. " to " .. player.Name, "success")
end
end
Solutions I’ve tried
I’ve tried adding logs in every step before the HandleEffectAsync function is ran, but from the very beginning and the very end, the player, character, humanoid, and their parents are all valid.
I’m not really sure what other solutions I can even try.