diff --git a/lib/SharedEvent.luau b/lib/SharedEvent.luau new file mode 100644 index 0000000..029c408 --- /dev/null +++ b/lib/SharedEvent.luau @@ -0,0 +1,217 @@ +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") + +local Identifier = require(script.Parent.Identifier) +local Spawn = require(script.Parent.Parent.Spawn) +local Signal = require(script.Parent.Parent.Signal) + +local Net = require(script.Parent.Net) + +type SharedBaseEvent = { + Id: string, + Reliable: boolean, + + FireClient: (self: SharedBaseEvent, Player: Player, T...) -> (), + FireAllClients: (self: SharedBaseEvent, T...) -> (), + FireAllClientsExcept: (self: SharedBaseEvent, Player: Player, T...) -> (), + FireClients: (self: SharedBaseEvent, Players: { Player }, T...) -> (), + + FireServer: (self: SharedBaseEvent, T...) -> (), +} + +export type SharedCallEvent = SharedBaseEvent & { + CallMode: "Call", + + Listener: ((...any) -> ())?, + + SetServerListener: (self: SharedCallEvent, Listener: (Player: Player, T...) -> ()) -> (), + SetClientListener: (self: SharedCallEvent, Listener: (T...) -> ()) -> (), +} + +export type SharedSignalEvent = SharedBaseEvent & { + CallMode: "Signal", + + Signal: Signal.Signal<...any>, + + OnServer: (self: SharedSignalEvent, Listener: (Player: Player, T...) -> ()) -> () -> (), + OnClient: (self: SharedSignalEvent, Listener: (T...) -> ()) -> () -> (), +} + +export type SharedEvent = SharedCallEvent | SharedSignalEvent + +local function FireClient(self: SharedBaseEvent, Player: Player, ...) + assert(RunService:IsServer(), "FireClient can only be called from the server") + + if self.Reliable then + Net.Server.SendReliableEvent(Player, self.Id, table.pack(...)) + else + Net.Server.SendUnreliableEvent(Player, self.Id, table.pack(...)) + end +end + +local function FireAllClients(self: SharedBaseEvent, ...) + assert(RunService:IsServer(), "FireAllClients can only be called from the server") + + local Args = table.pack(...) + + if self.Reliable then + for _, Player in Players:GetPlayers() do + Net.Server.SendReliableEvent(Player, self.Id, Args) + end + else + for _, Player in Players:GetPlayers() do + Net.Server.SendUnreliableEvent(Player, self.Id, Args) + end + end +end + +local function FireAllClientsExcept(self: SharedBaseEvent, Player: Player, ...) + assert(RunService:IsServer(), "FireAllClientsExcept can only be called from the server") + + local Args = table.pack(...) + + if self.Reliable then + for _, OtherPlayer in Players:GetPlayers() do + if OtherPlayer ~= Player then + Net.Server.SendReliableEvent(OtherPlayer, self.Id, Args) + end + end + else + for _, OtherPlayer in Players:GetPlayers() do + if OtherPlayer ~= Player then + Net.Server.SendUnreliableEvent(OtherPlayer, self.Id, Args) + end + end + end +end + +local function FireClients(self: SharedBaseEvent, Players: { Player }, ...) + assert(RunService:IsServer(), "FireClients can only be called from the server") + + local Args = table.pack(...) + + if self.Reliable then + for _, Player in Players do + Net.Server.SendReliableEvent(Player, self.Id, Args) + end + else + for _, Player in Players do + Net.Server.SendUnreliableEvent(Player, self.Id, Args) + end + end +end + +local function FireServer(self: SharedBaseEvent, ...) + assert(RunService:IsClient(), "FireServer can only be called from the client") + + if self.Reliable then + Net.Client.SendReliableEvent(self.Id, table.pack(...)) + else + Net.Client.SendUnreliableEvent(self.Id, table.pack(...)) + end +end + +local function SetServerListener(self: SharedCallEvent, Listener: (Player: Player, T...) -> ()) + assert(RunService:IsServer(), "SetServerListener can only be called from the server") + + self.Listener = Listener :: any +end + +local function SetClientListener(self: SharedCallEvent, Listener: (T...) -> ()) + assert(RunService:IsClient(), "SetClientListener can only be called from the client") + + self.Listener = Listener :: any +end + +local function OnServer(self: SharedSignalEvent, Listener: (Player: Player, T...) -> ()): () -> () + assert(RunService:IsServer(), "OnServer can only be called from the server") + + return self.Signal:Connect(Listener :: any) +end + +local function OnClient(self: SharedSignalEvent, Listener: (T...) -> ()): () -> () + assert(RunService:IsClient(), "OnClient can only be called from the client") + + return self.Signal:Connect(Listener :: any) +end + +type SharedEventOptions = { + Name: string, + Unreliable: boolean?, + + Validate: (...unknown) -> T..., +} + +type ConstructorType = + ((Options: SharedEventOptions & { CallMode: "Call"? }) -> SharedCallEvent) + & ((Options: SharedEventOptions & { CallMode: "Signal" }) -> SharedSignalEvent) + +local SharedEvent: ConstructorType = function(Options: any) + local self = { + Id = Identifier.Shared(Options.Name):Await(), + Reliable = not Options.Unreliable, + + FireClient = FireClient, + FireAllClients = FireAllClients, + FireAllClientsExcept = FireAllClientsExcept, + FireClients = FireClients, + + FireServer = FireServer, + } + + if Options.CallMode == "Call" then + self.CallMode = "Call" + + self.SetServerListener = SetServerListener + self.SetClientListener = SetClientListener + + self.Listener = nil :: (...any) -> ()? + + if RunService:IsServer() then + Net.Server.SetListener(self.Id, function(Player, Args) + Spawn(function(Player: Player, ...: any) + if self.Listener and pcall(Options.Validate, ...) then + self.Listener(Player, ...) + end + end, Player, table.unpack(Args)) + end) + else + Net.Client.SetListener(self.Id, function(Args) + Spawn(function(...: any) + if self.Listener and pcall(Options.Validate, ...) then + self.Listener(...) + end + end, table.unpack(Args)) + end) + end + elseif Options.CallMode == "Signal" then + self.CallMode = "Signal" + + self.Signal = Signal() + + self.OnServer = OnServer + self.OnClient = OnClient + + if RunService:IsServer() then + Net.Server.SetListener(self.Id, function(Player, Args) + Spawn(function(Player: Player, ...: any) + if pcall(Options.Validate, ...) then + self.Signal:Fire(Player, ...) + end + end, Player, table.unpack(Args)) + end) + else + Net.Client.SetListener(self.Id, function(Args) + Spawn(function(...: any) + if pcall(Options.Validate, ...) then + self.Signal:Fire(...) + end + end, table.unpack(Args)) + end) + end + end + + return self +end :: any + +return SharedEvent diff --git a/wally.toml b/wally.toml index 22cd0e8..1c6a6d0 100644 --- a/wally.toml +++ b/wally.toml @@ -16,5 +16,6 @@ include = [ [dependencies] Future = "red-blox/future@^1.0.0" +Signal = "red-blox/signal@^2.0.0" Spawn = "red-blox/spawn@^1.0.0" Guard = "red-blox/guard@^1.0.0"