Help on creating a good item system

im making a game in which the main point is getting items from mario kart type boxes while dodging events that happen, rn im working on the item system


issue is idk how to properly structure the code on them, currently the way the system works is a folder which every item has that includes:

  • a module script that handles client through functions like module.click, which is connected to a local script on the player
  • a server script
  • a event

however this system is kinda fragile, it can break whenever the player dies, drops the item or anything

is there a better way of handling this? like handling scripts outside of the item, maybe even use module script on the server aswell

How is your script breaking exactly? Are there any errors thrown by your scripts? If your items are possibly getting deleted when you die, I think it’s breaking because the script will stop as soon as it’s destroyed. To solve this, I would create a callExternally utility, which would be a module that returns a function which takes a function along with a variable number of arguments. The module contains a BindableEvent and a server Script that it will pass it’s arguments to, then the script inside of the module would call the function. This should prevent anything from breaking, since the function is fired from the external server script and not from the item itself. I believe Roblox’s new FPS template includes something like this, specifically, bindToInstanceDestroyed. With all this in mind, I would write this utility like so:

-- In your module script "callExternally"
-- The bindable event to allow communicating to the external server script.
local CallEvent = script:WaitForChild("CallEvent")

return function(func, ...)
  task.defer(function()
    -- Send the function along with it's arguments to the server script through the CallEvent.
    CallEvent:Fire(func, ...)
  end)
end

-- In your server script "ServerCallHandler" within "callExternally"
local Root = script.Parent
local CallEvent = Root:WaitForChild("CallEvent")

-- Recieve the function along with it's arguments, then call it from within the server script.
CallEvent.Event:Connect(function(func, ...)
  func(...)
end)

I hope this solves your problems.

what exactly does task.defer do? my issue is like, on the server part mostly, i need stuff to not break upon player’s death or item changing parents

to make things more clear heres some snippets of my code
on the client:

function module.click(anims)
	
	local result = sharedItem:processDebounce(item, "click")
	if result == false then return end
	
	if configuration.currentPlayer then
		
		highlight:Destroy()
		
		if configuration.cooldown then return end
		configuration.cooldown = true

		event:FireServer(configuration.currentPlayer) 

		task.wait(1)
		configuration.cooldown = false
		
	end
end

and on the server:

local function activateAbility(currentPlayer, hit)

	if player and hit then
		
		local status = items:setActive(player, item, "click", true)
		if status == false then
			return
		end
		
		task.delay(0.1, function()
			items:setActive(player, item, "click", false)
		end)
		
		local vfx = item.BodyAttach.Effect:Clone()
		local sfx = item.BodyAttach.Use:Clone()
		sfx.Parent = hit.Torso
		vfx.Parent = hit.Torso
		vfx.Enabled = true
		events.Cosmetic:FireAllClients("Sound", sfx)
		task.delay(sfx.TimeLength, function()
			sfx:Destroy()
			vfx:Destroy()
		end)
		
		modifier:cast(hit, "AI Generated", player)

		task.delay(0.1, function()
			item.Parent = serverScriptService.Cache
		end)

	end

end

items:setActive detects and handles cooldowns btw, decided to put it all in one function, any ways lets suppose this instead had like a connection, and the player died while the connection was still running, it would just break, i wanted to fix that

task.defer() simply fires the given function on the next frame. In the FPS template, it is used so that “the script is able to initialize first”.

Is your server script inside the player’s item? Where are your scripts located exactly within your game?

oh wait thats kinda useful, anyways the script is located inside the tool yes

So, the server script is inside the tool. Another question is what are you trying to bind a connection to, the tool, the owner, or another (possible opponent) player?

like, theres another item which i didnt send the script because it was too big, which set a modifier which stuns the player (modifers are a system in my game that can do any stat effects), and the way they work is it requires a module for each modifier that run a function or whatever, but lets suppose the modifier is called, and the player holding the item dies, the modifier will just stop and not run anymore, so when the stun is supposed to end, it doesnt

So, in other words, you are trying to effect another player which is not the tool owner. Are the player’s status effects (stun) stored within the character or the player? If the status effects (like stun) are stored in the player instance and not the player’s character, I would move the status effect from the player to the player’s character. This would make sure that all the player’s status effects are cleared once the player dies.

the status effects are stored on the targets character yes, but the issue is like, if the player dies the other pretty much gets stun locked, due to the stun effect never clearing, before i changed the runcontext to server just unequipping the tool was enough to stun lock the target

What could be happening is that the modifier is being called within the tool’s owner. Once the tool gets removed from the player, then the modifier gets disabled. What you could do instead is use a BindableEvent on the server to cast the modifier instead of using a ModuleScript. This would ensure that the modifier runs on an external script instead of running within the player’s tool, so the modifier will never get disabled upon the player’s death or upon the tool being unequipped.

oh wait yeah that should work, ill try tomorrow and mark it as a solution if it works, thanks

1 Like