-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A library for allowing the server and other players to know when a given player loads in
- Loading branch information
1 parent
0464e0e
commit 7cafee3
Showing
3 changed files
with
204 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
tags = [ "Players", "Loaded", "RemoteEvent" ] | ||
version = "1.0.0" | ||
|
||
[runtime] | ||
main = "roblox" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
### No docs site, but doc comments are provided as I'm going to be switching to moonwave at some point soon (hopefully). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
|
||
-- player loaded | ||
-- module for handling callbacks to the player loaded remote | ||
|
||
local ReplicatedStorage = game:GetService("ReplicatedStorage") | ||
local ReplicatedFirst = game:GetService("ReplicatedFirst") | ||
local RunService = game:GetService("RunService") | ||
local Players = game:GetService("Players") | ||
local character = require(ReplicatedFirst:WaitForChild("packages"):WaitForChild("character")) | ||
|
||
type PlayerLoadedCallback = (player: Player & { Character: character.Character }) -> () | ||
|
||
local CALLBACKS_WAITING_FOR_NEXT_PLAYER = {} :: { { PlayerLoadedCallback } } | ||
local PLAYERS_LOADED = table.create(Players.MaxPlayers) :: { Player } | ||
local THREADS_WAITING_FOR_PLAYERS = {} :: { [Player]: { thread } } | ||
local THREADS_WAITING_FOR_NEXT_PLAYER = {} :: { thread } | ||
local CALLBACKS = {} :: { { PlayerLoadedCallback } } | ||
local REMOTE_NAME = "PLAYER LOADED" | ||
local LOADED_COUNT = 0 | ||
|
||
local function CREATE_DISCONNECT<V>(t:{ V }, v: V): () -> () | ||
return function() | ||
local index = table.find(t, v) | ||
|
||
if index then | ||
if index ~= 1 then | ||
local len = #t | ||
t[index] = t[len] | ||
t[len] = nil | ||
else | ||
t[1] = nil | ||
end | ||
end | ||
end | ||
end | ||
|
||
local player_loaded = {} | ||
|
||
--[[ | ||
Calls the provided callback everytime a player loads in, | ||
unless disconnected using the returned disconnect function. | ||
]] | ||
function player_loaded.on(f: PlayerLoadedCallback, run_for_existing: true?): () -> () | ||
if run_for_existing then | ||
for _, player in PLAYERS_LOADED do | ||
task.defer(f, player :: any) | ||
end | ||
end | ||
|
||
local callback_info = table.freeze({ f }) | ||
table.insert(CALLBACKS, callback_info) | ||
|
||
return CREATE_DISCONNECT(CALLBACKS, callback_info) | ||
end | ||
|
||
--[[ | ||
Yields the current thread until the provided player loads in, | ||
or if the player leaves the game it'll then resume the current thread | ||
with nil instead of the player. | ||
]] | ||
function player_loaded.wait_for_player(player: Player): Player? | ||
if not table.find(PLAYERS_LOADED, player) then | ||
local threads = THREADS_WAITING_FOR_PLAYERS[player] | ||
|
||
if threads then | ||
table.insert(threads, coroutine.running()) | ||
else | ||
THREADS_WAITING_FOR_PLAYERS[player] = { coroutine.running() } | ||
end | ||
return coroutine.yield() | ||
else | ||
return player | ||
end | ||
end | ||
|
||
--[[ | ||
Calls the provided callback with the next player that loads in, | ||
unless disconnected using the returned disconnect function. | ||
]] | ||
function player_loaded.once(f: PlayerLoadedCallback): () -> () | ||
local callback_info = table.freeze({ f }) | ||
table.insert(CALLBACKS_WAITING_FOR_NEXT_PLAYER, callback_info) | ||
|
||
return CREATE_DISCONNECT( | ||
CALLBACKS_WAITING_FOR_NEXT_PLAYER, | ||
callback_info | ||
) | ||
end | ||
|
||
--[[ | ||
Yields the current thread, and resumes it with the next player that loads in | ||
]] | ||
function player_loaded.wait(): Player | ||
table.insert(THREADS_WAITING_FOR_NEXT_PLAYER, coroutine.running()) | ||
return coroutine.yield() | ||
end | ||
|
||
do | ||
|
||
local function run_for_player(player: Player) | ||
local threads = THREADS_WAITING_FOR_PLAYERS[player] | ||
|
||
LOADED_COUNT += 1 | ||
PLAYERS_LOADED[LOADED_COUNT] = player | ||
|
||
if threads then | ||
THREADS_WAITING_FOR_PLAYERS[player] = nil | ||
|
||
for _, thread in threads do | ||
task.defer(thread, player) | ||
end | ||
end | ||
|
||
for _, callback in CALLBACKS_WAITING_FOR_NEXT_PLAYER do | ||
task.defer(callback[1], player :: any) | ||
end | ||
|
||
for _, thread in THREADS_WAITING_FOR_NEXT_PLAYER do | ||
task.defer(thread, player) | ||
end | ||
|
||
for _, callback in CALLBACKS do | ||
task.defer(callback[1], player :: any) | ||
end | ||
|
||
table.clear(CALLBACKS_WAITING_FOR_NEXT_PLAYER) | ||
table.clear(THREADS_WAITING_FOR_NEXT_PLAYER) | ||
end | ||
|
||
if RunService:IsServer() then | ||
local remote = Instance.new("RemoteEvent") | ||
local fire_client = remote.FireClient | ||
remote.Name = "PLAYER LOADED" | ||
remote.Parent = ReplicatedStorage | ||
|
||
remote.OnServerEvent:Connect(function(loaded_player) | ||
if not table.find(PLAYERS_LOADED, loaded_player) then | ||
fire_client(remote, loaded_player, PLAYERS_LOADED) | ||
run_for_player(loaded_player) | ||
|
||
for _, player in PLAYERS_LOADED do | ||
fire_client(remote, player, loaded_player) | ||
end | ||
end | ||
end) | ||
else | ||
local remote: RemoteEvent = ReplicatedStorage:WaitForChild(REMOTE_NAME) :: any | ||
|
||
remote.OnClientEvent:Connect(function(player_or_players: Player | { Player }) | ||
if type(player_or_players) == "table" then | ||
for _, player in player_or_players do | ||
if not table.find(PLAYERS_LOADED, player) then | ||
run_for_player(player) | ||
end | ||
end | ||
else | ||
run_for_player(player_or_players) | ||
end | ||
end) | ||
|
||
task.defer(function() | ||
if not game:IsLoaded() then | ||
game.Loaded:Wait() | ||
end | ||
|
||
remote:FireServer() | ||
run_for_player(Players.LocalPlayer) | ||
end) | ||
end | ||
|
||
Players.PlayerRemoving:Connect(function(player) | ||
local threads = THREADS_WAITING_FOR_PLAYERS[player] | ||
local index = table.find(PLAYERS_LOADED, player) | ||
|
||
if index then | ||
if index ~= 1 then | ||
local len = #PLAYERS_LOADED | ||
PLAYERS_LOADED[index] = PLAYERS_LOADED[len] | ||
PLAYERS_LOADED[len] = nil | ||
else | ||
PLAYERS_LOADED[1] = nil | ||
end | ||
|
||
LOADED_COUNT -= 1 | ||
end | ||
|
||
if threads then | ||
THREADS_WAITING_FOR_PLAYERS[player] = nil | ||
|
||
for _, thread in threads do | ||
task.defer(thread, nil) | ||
end | ||
end | ||
end) | ||
|
||
end | ||
|
||
return table.freeze(player_loaded) |