Networking¶
Nova keeps the classic server-authoritative model:
- A
Scriptruns on the server and owns the DataModel. - A
LocalScriptruns on a client and receives filtered replication. RemoteEventandRemoteFunctioncross the boundary.- The server decides what is true; the client is a view and an input source.
FilteringEnabled is enabled for modern places automatically. Saved values from
older places are respected, and new places start filtered.
RemoteEvent¶
-- ReplicatedStorage/Remotes/Server/RequestPurchase (RemoteEvent)
local remote = game:GetService("ReplicatedStorage")
:WaitForChild("Remotes")
:WaitForChild("Server")
:WaitForChild("RequestPurchase")
-- Server
remote.OnServerEvent:Connect(function(player, productId)
if typeof(productId) ~= "number" then
return
end
-- Validate: price, ownership, cooldown, distance, state machine, ...
print(player.Name, "requested", productId)
end)
-- Client
remote:FireServer(12345)
FireClient(player, ...) targets one client; FireAllClients(...) targets
everyone. The first argument on the server is always the Player, and it is
supplied by the engine; do not include it when firing from the client.
RemoteFunction¶
RemoteFunction is a synchronous request/response. It yields, so never call it
from a context where yielding is unsafe.
-- ReplicatedStorage/Remotes/GetProfile (RemoteFunction)
-- Server
local getProfile = game:GetService("ReplicatedStorage")
:WaitForChild("Remotes")
:WaitForChild("GetProfile")
getProfile.OnServerInvoke = function(player)
return {
Name = player.Name,
UserId = player.UserId,
}
end
-- Client
local profile = getProfile:InvokeServer()
print(profile.Name)
Use RemoteFunction for small reads. For anything that changes state, prefer
RemoteEvent so the server can validate and replicate asynchronously.
Rules that prevent most bugs¶
- Never trust the client. Validate types, ranges, ownership, cooldowns, and distance on the server.
- Rate-limit. A client can fire a remote as fast as its frame rate. Keep a per-player cooldown for expensive handlers.
- Wait for replication. A client may not have the target instance yet. Use
WaitForChildon both sides. - Do not send Instances you do not need. Send IDs or primitive values, then resolve them on the receiving side.
- Handle disconnects. A player can leave between the request and the
response; check
player.Parentbefore using it. - Keep the server authoritative for movement and currency. Do not let a client report "I bought this" or "I am at this position" without validation.
Player lifecycle¶
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
print("joined", player.UserId, player.Name)
player.CharacterAdded:Connect(function(character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
print(player.Name, "died")
end)
end)
end)
Players.PlayerRemoving:Connect(function(player)
print("left", player.UserId)
end)
Players.CharacterAutoLoads controls automatic character spawning.
Player:LoadCharacter() is available if you disable it and spawn manually.
UnreliableRemoteEvent¶
UnreliableRemoteEvent is not implemented yet. Use RemoteEvent. For high-frequency
cosmetic data, batch updates and send at a fixed rate rather than every frame.
What replicates¶
Property changes on the server replicate to clients. Client-side changes do not replicate back. If a client needs to change shared state, it must ask the server.
Common mistakes:
- A
LocalScriptsetspart.BrickColor, and other players never see it. The server must set it. - A server script changes a GUI that only exists on the client. GUI changes
should happen in a
LocalScript, driven by a remote. - A remote fires before the client has the object. Wait for the object, then fire.