Ideas on how to make a Server Event Listener

Your current script is very vulnerable. You are relying on the client for crucial game data (which is very bad), and you are doing no validation of data server side. Never trust the client for anything.

If you are using a tool for this, you can detect an activation of the tool and find the closest field the player is in. You could add attributes, names, etc. to each field of different flower types and raycast downwards from the player when you suspect a field change, or just use a .Touched event for a BasePart.

As for the events themselves, I would recommend making your own class similar to RBXScriptSignal for event changing things. It’d be better than bindables.

local CustomEvent = {}
CustomEvent.__index = CustomEvent

--current events
CustomEvent.Events = {}

--class constructor
function CustomEvent.new(name: string): Event
    local self = setmetatable({}, CustomEvent)
    --set data about the event
    self.Callbacks = {}
    self.Fired = false
    
    --add to the module of events
    CustomEvent.Events[name] = self

    --return the new event
    return self
end

--waiting for a signal to exist
function CustomEvent:WaitForEvent(name: string): Event?
    local Signal
    local attempt = 0

    repeat
        Signal = self.Events[name]
        attempt += 1
        task.wait(1)

        if attempt == 5 then
            warn("Yielded for signal 5+ seconds")
        end
    until
        Signal or attempt == 60

    return Signal
end

--replicating RBXScriptSignal's :Connect function
function CustomEvent:Connect(callback: (any?) -> (nil)): nil --the given callback should not return a value
    --add an asynchronous version of the given callback to be run
    table.insert(self.Callbacks, callback)
end

--to fire your custom event
function CustomEvent:Fire(...:any?): nil -- '...' is data to be sent to the connected callbacks
    local extraData = {...} --get extra data into a table
    --run each callback
    for _, callback: (any?) -> (nil) in next, self.Callbacks, nil do
        --an asynchronous version of the callback was added so we can just run it normally
        task.spawn(callback, table.unpack(extraData))
    end
end

--type for custom event
--I didn't include inherited things that won't be used
export type Event = {
    ["Connect"]: (self: Event, (any?) -> (nil),
    ["Fire"]: (self: Event, any?) -> (nil),
    ["Callbacks"]: {(any?) -> (nil)}
    ["Fired"]: boolean
}

return CustomEvent






An example use for your game:

local players = game:GetService("Players")
local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])

--create a new event for when a player enters a new field
local FieldEvent = Event.new("PlayerFieldChanged")

local fields = {}

--now, every time a player changes field, you can store it
--example use in a player's raycast using suggestions I mentioned earlier:
--'fields' holds the current field of each player

local function checkNewField(field:string, hit:BasePart)
    local player = players:GetPlayerFromCharacter(hit.Parent)
    if player and fields[player.Name] ~= field then
        fields[player.Name] = field
        FieldEvent:Fire(player, field)
    end
end

for _, field in next, fields, nil do
    field.Touched:Connect(function(hit)
        checkNewField(field.Name, hit)
    end)
end

Another script can receive this:

local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])

local function onFieldChanged(player: Player, newField: string)
    print(`@{player.Name} entered field: {newField}`)
end

event.Events.PlayerFieldChanged:Connect(onFieldChanged)










Another example use for listening for a player collecting pollen:

local players = game:GetService("Players")
local Event = require(game:GetService("ServerScriptService")["FinishWithYourPathHere"])

local CollectEvent = Event.new("PlayerCollectPollen")

players.PlayerAdded:Connect(function(player: Player)
    local char = player.Character or player.CharacterAdded:Wait()
    local backpack = player.Backpack

    local tool = backpack:WaitForChild("ToolName", 5) or char:WaitForChild("ToolName", 5)
    tool.Activated:Connect(function()
        CollectEvent:Fire(player) --custom event saves a lot of scripts holding memory references to the tool
    end)
end)
Event.Events.PlayerCollectPollen:Connect(function(player: Player)
    print("Player @"..player.Name.." requested to collect pollen.")
end)

This structure could be applied to the things you mentioned you wanted to do.

1 Like

Wow, i didn’t know i could make my own script signals and events.
Could u provide some in-depth tutorial for this so i can learn more?
Thanks for your reply.

1 Like

Sure, what parts do you need to know more about? After making this module, you should be able to treat each custom event like an RBXScriptSignal, provided you add the other methods to it.

Basically everything, i feel like especially the passing of arguments/callbacks i feel like im not the most keen on, and these Methods u just mentioned.
Im very new to this sort of thing with making Signals/Events.

But i feel like once i learn this it will be very useful, since i can make my own Events instead of using roblox’s own Events which i believe has some drawbacks if im not mistaken.

This system uses an Object-Oriented structure. This means each signal is called an object, and they all inherit methods from the class. We use a class constructor (indicated here by .new) to create this object. In each function, self refers to the signal itself. It’s defined through colon notation. Here’s some more information.

When we use :Connect with this class, we are passing our callback function which is to be run whenever the event fires. We use coroutine.wrap to allow each callback to be run asynchronously when the event fires. This means they can be run at the same time as one another.

When we use :Fire, we are passing information that we want to be sent to each callback function. Each callback will receive this data and do as it will with the data. ... is used to store this data and is then gathered into a table using local extraData = {...}. It then iterates over each callback that has been connected to it, setting it to run with the extra data.

I’ve only explained the functions I included, please let me know if you need more detail and on what parts.

Roblox’s events are very helpful and very good for a lot of things - I think your own events and Roblox’s should go hand-in-hand.

Why you can modify variables in functions passed and why the functions themselves actually run

Let me give you a statement that will probably make you go, “what?”
A variable does not store a value.

In fact, a variable stores the memory location of the value. A variable is a pointer to a location in memory that can change whilst the program is running.

When we pass a function to :Connect, we are in fact passing the memory location of a function. This means we can run it with the correct code.

So, when we reference variables within these functions, we are referencing the same location in memory, hence why if we changed it in one place, it would change in all the others, even if it is under a different variable name.

Hope this helps!

Thank you for the extra info, could we talk via discord if i need any help on this sort of topic?
Another thing how will i fire this, just like a Remote Event or Function?
And does this go Client → Server → Client? or vice versa?

I made this to mimic bindables, I personally think it’s a lot simpler than bindables and it takes up less memory. So, use it on the same side of the client-server boundary. In terms of firing, you could fire it from any script that has the reference to the event.

local Event = require(path_to_module)

--create a new signal
local Signal = Event.new("Test1")

--we can fire it from here...
Signal:Fire()

--or, in a seperate script...
local Signal = Event.Events.Test1
Signal:Fire()

--either way, it will run any connected callbacks.

Interesting, thank you!
Any issues i have ill post them here.
Heres my discord if u wish to talk via there:
peter.3_

1 Like

Just like to add that I’ve edited my solution to add another method for waiting for a signal to be created.

--Script 1
task.wait(1)
local Signal = Event.new("Test1")
--Script 2
local Signal = Event:WaitForEvent("Test1")

Signal:Connect(function()
    print("Signal fired")
end)

It’s helpful, hope it helps you!

Why wait thought? Is it so there’s no clogging up?

It’s an easier way to wait for events; I noticed the event takes a little bit of time to be indexable, because of the time it takes to create the event. It was a very short amount of time but it required about 10 lines of code to fix. I put those 10 -ish lines of code into the class and made a method for it.

hm ok, imma start looking into the code now was working on a project.

So i have some questions:

-- Class Constructor

function CustomEvent.new(name: string): Event
	local self = {}
	setmetatable(self, CustomEvent)
	
	self.Callbacks = {} -->> Any Functions/Arguments to be executed/passed!
	self.Fired = false -->> Tells us if this Event has fired.

	--Add to the Events Table.
	CustomEvent.Events[name] = self

	--Return the self/New Event.
	return self
end

In the “.new” Function mentioned above, it says that this function will return an “Event” Type but the values you set for it is only 2 of those values?
for example if i set the script mode (i think thats the name) to --!strict
It shows me this:

image

Now i think its good(correct me if im wrong) to set these variables and connect everything together.
But why did u not set them here directly in the class constructor? :sweat_smile:

Another thing is:

Callbacks are Functions/Arguments to be executed/passed right? Example:

Event.Events.PlayerFieldChanged:Connect(onFieldChanged) -->> This is a callback

Or if im not mistaken the above was for Function like the normal :Connect.

And below is the arguments like in FireServer/Client() for example:

FieldEvent:Fire(player, field) -->> Fires Event with given Arguments.

I believe its separate from the two, but please correct me if im wrong.

Another thing that i actually realized was that the Signal here is an Event not boolean:

local Signal = Event:WaitForEvent("PlayerFieldChanged")

Signal:Connect(function(...)
	print("I got fired!!!")
	print("Arguments: ", ...)
end)

I thought this was a boolean(Also because of the name being Signal not Event) at first because of how this was structured:
But now i understand it by just setting the types of some variables like Signal to Event.
Also makes sure script doesn’t warn when using --!strict

function CustomEvent:WaitForEvent(EventName: string): Event
	local Signal: Event
	local attempt = 0 --Times waited for a Signal.

	repeat
		Signal = self.Events[EventName] -->> Signal is equal to the Event given.
		attempt += 1
		task.wait(1)

		if attempt == 5 then -->> If we have waited for a Signal for more than 5 seconds then warn.
			warn("Yielded for Event: "..EventName.."5+".."seconds")
		end
	until
	Signal or attempt == 60

	return Signal
end

So to add to this post i was thinking of making my own Bindable Functions code for this Script you made, so i could return arguments and things like that
now i wanna try and make it with as minimal help as possible but i still want some guidance/path that i could take to make such Features.

And also maybe in the future implement this into Server-Client if needed! (Maybe not the best idea since Remote Events exist)

But overall i learned a lot here and i wanna try and make my own version and get some explanations back from you!
Thanks for reading.
I will provide a Place copy with some of my comments if u wanna see my thought process(and things i didn’t include in this post) when reading through your code and some things i added to it:

PLACE:
CustomEvents Place.rbxl (56.9 KB)

1 Like

Another thing, what is next???

for _, field in next, PlayerFields, nil do -->> what is next???? for _, field in next??? so it jumps up an index without getting the current one?
	field.Touched:Connect(function(hit) --When Field is touched check Field.
		checkNewField(field.Name, hit)
	end)
end
1 Like


I only included items that were relevant to the event class, so using strict mode would indeed create that message.

Do you mean the functions of the class? I can expand on the type if you want me to below.



When using the :Connect method, you pass a callback to be run when the event is fired. However, like you showed in your snippet, any parameters you include in :Fire will be passed to the callback functions when executing. You don’t have to use ... if you know what parameters you are expecting.

Making your own bindable function would be harder, but it’s still possible. I think you would need some sort of middle-ground function.

function Bindable:Invoke(...: any?): any?
    --we would only have one callback, just like normal BindableFunctions
    local results = table.pack(self.callback(table.unpack({...})))
    return table.unpack(results)
end

next is just another iterator function. You can use it to start iterating from a certain point, but I don’t.

--next is the iterator we are going to use
--yourTable is the table to be passed to next
--nil indicates to next that we want to start iterating at the first index within the table
for index, value in next, yourTable, nil do
end

Please ask if you have any more questions, I’m not quite sure I fully understood your post.

1 Like

Hi thanks for the reply, so when i said functions of the class i just meant to mention them so in !strict mode the script wouldn’t get mad and just look a bit better and more organized but i feel like i can do those ill learn a bit more too, and what is table.pack and table.unpack and why would it be good for making a BindableFunction?

And when u meant “a callback to be run” and that the parameters i include in :Fire will be sent to the callbacks which are Functions i don’t need to use “…” it’s just so i don’t have to type them out on the explanation.

But so interesting and confusing at first since the name of Callbacks aren’t functions or parameters/arguments.

And another thing before i try to attempt to make my own BindableFunctions i just need to make the receiving end return something back to the function that called :Invoke right?
And some sort of new receiving method instead of just :WaitForEvent etc.

And what do you mean by only one callback on your :Invoke?

i tried fixing the types problem but i couldn’t fix it, tried making some functions or reference to the functions inside the table.
You know how?

table.pack creates a table from all the given parameters, and table.unpack will “unpack” a table.

So, for example:

local function testFunc(...: any?): nil
    --we don't know what's contained in ...
    --let's gather it into a table
    local extraData = {...}

    --[[now we need to run our callback
    we use table.unpack so each variable in our table gets distributed to a variable in the parameters
    so if it takes 3 parameters and our table has 3 elements each of the parameters receives one of the elements

    but we also need to store the results. We don't know how many results we are going to get,
    so we can store them in a table using table.pack and assign it to results
    ]]

    local results = table.pack(
        self.callback(
            table.unpack(extraData)
        )
    )
end

These functions are useful for within a bindable as it allows for one generic method. We don’t know how many parameters we are going to receive, or how many we are going to get. We could just do return callback(table.unpack(extraData)), but that’s harder to read and understand. Therefore, we send all the parameters needed for the callback to to the callback, and return all the parameters the callback returns.

We only use one callback for this, just like a BindableFunction. It wouldn’t work very well otherwise.

What do you mean by this part? Do you mean I don’t put the brackets after the function name? If I did that, it would run the function and pass the return result of the function, instead of the function itself.

1 Like

I think you need to include all the fields of the custom event type in strict mode. I didn’t because I didn’t make it for strict mode.