A high-performance, type-safe networking library for Roblox using binary buffers for efficient data transmission.
Add to your wally.toml:
[dependencies]
knightremotes = "vq9o/knightremotes@2.0.0"Then run:
wally installServer:
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
KnightRemotes:init()
-- Create a reliable, one-way event
KnightRemotes:new("PlayerDamaged", true, false)
-- Connect callback
KnightRemotes:Connect("PlayerDamaged", function(player, damage)
print(player.Name .. " took " .. damage .. " damage")
end)
-- Fire to specific player
local player = game.Players:GetPlayerByUserId(12345)
KnightRemotes:Fire("PlayerDamaged", player, 25)
-- Fire to all players
KnightRemotes:FireAll("PlayerDamaged", 50)Client:
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
KnightRemotes:init()
-- Register the same event
KnightRemotes:new("PlayerDamaged", true, false)
-- Fire to server
KnightRemotes:Fire("PlayerDamaged", 10)Server:
KnightRemotes:init()
-- Create a reliable, two-way remote
KnightRemotes:new("GetPlayerData", true, true)
-- Connect callback that returns data
KnightRemotes:Connect("GetPlayerData", function(player, dataType)
if dataType == "coins" then
return true, player.leaderstats.Coins.Value
else
return false, "Invalid data type"
end
end)Client:
KnightRemotes:init()
KnightRemotes:new("GetPlayerData", true, true)
-- Invoke and wait for response
local success, coins = KnightRemotes:Fire("GetPlayerData", "coins")
if success then
print("You have " .. coins .. " coins")
else
warn("Error: " .. coins)
endInitializes the networking system. Must be called before using any other methods.
KnightRemotes:init()Creates a new remote event or function.
KnightRemotes:new(
RemoteName: string,
Reliable: boolean?, -- Default: true
TwoWay: boolean? -- Default: false
)Parameters:
RemoteName- Unique identifier for this remoteReliable- Whether to use reliable transmission (guaranteed delivery)TwoWay- Whether this is a request/response pattern (must be reliable)
Example:
-- Reliable one-way event
KnightRemotes:new("ChatMessage", true, false)
-- Unreliable one-way event (good for high-frequency updates)
KnightRemotes:new("PlayerPosition", false, false)
-- Two-way remote function
KnightRemotes:new("PurchaseItem", true, true)Connects a callback to handle incoming events.
local connection = KnightRemotes:Connect(
RemoteName: string,
Callback: function
) -> ConnectionReturns: Connection object with Disconnect() method
Server Callback Signature:
function(player: Player, ...args) -> ...returnsClient Callback Signature:
function(...args) -> ...returnsExample:
local conn = KnightRemotes:Connect("OnDamage", function(player, amount)
-- Handle damage
end)
-- Later, disconnect
conn:Disconnect()Fires an event or invokes a two-way remote.
Server Usage:
KnightRemotes:Fire(RemoteName: string, player: Player, ...args)Client Usage:
KnightRemotes:Fire(RemoteName: string, ...args) -> ...resultsFor two-way remotes, this yields and returns the response.
(Server Only) Fires an event to all players.
KnightRemotes:FireAll(RemoteName: string, ...args)Example:
-- Notify all players of a global event
KnightRemotes:FireAll("ServerShutdown", "Server restarting in 5 minutes")For familiarity with standard Roblox RemoteEvents/RemoteFunctions:
FireClient()- Alias forFire()(server)FireServer()- Alias forFire()(client)FireAllClients()- Alias forFireAll()InvokeClient()- Alias forFire()for two-way remotesInvokeServer()- Alias forFire()for two-way remotes
Middleware allows you to intercept and modify network packets before they're processed. Perfect for validation, logging, rate limiting, and more.
KnightRemotes:UseMiddleware(function(
player: Player?, -- nil on client
eventName: string,
args: {any}
) -> (shouldContinue: boolean, modifiedArgs: {any}?)
end)Parameters:
player- The player who sent the packet (nil on client)eventName- Name of the remote being calledargs- Arguments passed to the remote
Returns:
shouldContinue- Whether to continue processing (false = drop packet)modifiedArgs- Optional modified arguments to use instead
local rateLimits = {}
local MAX_REQUESTS_PER_SECOND = 10
KnightRemotes:UseMiddleware(function(player, eventName, args)
if not player then return true end -- Skip on client
local key = player.UserId .. "_" .. eventName
local now = os.clock()
if not rateLimits[key] then
rateLimits[key] = { count = 0, resetTime = now + 1 }
end
local limit = rateLimits[key]
if now > limit.resetTime then
limit.count = 1
limit.resetTime = now + 1
return true
end
limit.count += 1
if limit.count > MAX_REQUESTS_PER_SECOND then
warn(player.Name .. " is being rate limited on " .. eventName)
return false -- Drop the packet
end
return true
end)KnightRemotes:UseMiddleware(function(player, eventName, args)
local timestamp = os.date("%Y-%m-%d %H:%M:%S")
local playerName = player and player.Name or "CLIENT"
print(string.format(
"[%s] %s called '%s' with %d args",
timestamp,
playerName,
eventName,
#args
))
return true -- Continue processing
end)KnightRemotes:UseMiddleware(function(player, eventName, args)
if eventName == "ChatMessage" then
local message = args[1]
-- Check message length
if type(message) ~= "string" or #message > 200 then
warn("Invalid chat message from " .. player.Name)
return false
end
-- Filter profanity
local filtered = game:GetService("TextService"):FilterStringAsync(
message,
player.UserId
)
-- Modify the arguments
return true, { filtered:GetNonChatStringForBroadcastAsync() }
end
return true
end)KnightRemotes:UseMiddleware(function(player, eventName, args)
if eventName == "PurchaseItem" then
local itemId = args[1]
local price = args[2]
-- Validate item exists
local itemData = ReplicatedStorage.Items:FindFirstChild(itemId)
if not itemData then
warn(player.Name .. " tried to purchase non-existent item")
return false
end
-- Validate price (never trust client)
local actualPrice = itemData.Price.Value
if price ~= actualPrice then
warn(player.Name .. " sent incorrect price for " .. itemId)
-- Force the correct price
return true, { itemId, actualPrice }
end
end
return true
end)local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
KnightRemotes:init()
-- Setup remotes
KnightRemotes:new("PlayerJoined", true, false)
KnightRemotes:new("GetInventory", true, true)
KnightRemotes:new("EquipItem", true, true)
KnightRemotes:new("UpdatePosition", false, false) -- Unreliable for performance
-- Rate limiting middleware
local requestCounts = {}
KnightRemotes:UseMiddleware(function(player, eventName, args)
if not player then return true end
local key = player.UserId
requestCounts[key] = (requestCounts[key] or 0) + 1
if requestCounts[key] > 100 then
player:Kick("Too many requests")
return false
end
return true
end)
-- Reset rate limits every second
task.spawn(function()
while true do
task.wait(1)
requestCounts = {}
end
end)
-- Handle inventory requests
KnightRemotes:Connect("GetInventory", function(player)
local inventory = player:FindFirstChild("Inventory")
if inventory then
return true, inventory:GetChildren()
else
return false, "Inventory not found"
end
end)
-- Handle item equipping
KnightRemotes:Connect("EquipItem", function(player, itemId)
local inventory = player:FindFirstChild("Inventory")
local item = inventory and inventory:FindFirstChild(itemId)
if item then
-- Equip logic here
return true, "Item equipped"
else
return false, "Item not in inventory"
end
end)
-- Broadcast player positions to nearby players
KnightRemotes:Connect("UpdatePosition", function(player, position)
-- Get nearby players
local nearbyPlayers = {}
for _, otherPlayer in pairs(game.Players:GetPlayers()) do
if otherPlayer ~= player then
local char = otherPlayer.Character
local otherPos = char and char:GetPrimaryPartCFrame().Position
if otherPos and (otherPos - position).Magnitude < 100 then
table.insert(nearbyPlayers, otherPlayer)
end
end
end
-- Send position update to nearby players only
for _, nearbyPlayer in pairs(nearbyPlayers) do
KnightRemotes:Fire("PlayerMoved", nearbyPlayer, player.Name, position)
end
end)local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
local player = Players.LocalPlayer
KnightRemotes:init()
-- Setup remotes
KnightRemotes:new("PlayerJoined", true, false)
KnightRemotes:new("GetInventory", true, true)
KnightRemotes:new("EquipItem", true, true)
KnightRemotes:new("UpdatePosition", false, false)
KnightRemotes:new("PlayerMoved", false, false)
-- Handle other players moving
local otherPlayerPositions = {}
KnightRemotes:Connect("PlayerMoved", function(playerName, position)
otherPlayerPositions[playerName] = position
-- Update visual representation
end)
-- Send position updates (unreliable for performance)
RunService.Heartbeat:Connect(function()
local character = player.Character
if character then
local hrp = character:FindFirstChild("HumanoidRootPart")
if hrp then
KnightRemotes:Fire("UpdatePosition", hrp.Position)
end
end
end)
-- Request inventory
local function refreshInventory()
local success, inventory = KnightRemotes:Fire("GetInventory")
if success then
print("Inventory:", inventory)
-- Update UI
else
warn("Failed to get inventory:", inventory)
end
end
-- Equip item
local function equipItem(itemId)
local success, message = KnightRemotes:Fire("EquipItem", itemId)
if success then
print(message)
else
warn("Failed to equip:", message)
end
endCall :init() before using any networking features:
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
KnightRemotes:init()Use Reliable (true) for:
- Critical game logic
- Transactions (purchases, trades)
- Chat messages
- Important state changes
Use Unreliable (false) for:
- High-frequency updates (position, rotation)
- Cosmetic effects
- Non-critical audio/visual updates
When you need confirmation or data back:
-- Two-way remote for purchases
KnightRemotes:new("BuyItem", true, true)
local success, result = KnightRemotes:Fire("BuyItem", "Sword123")
if success then
print("Purchased successfully!")
else
warn("Purchase failed:", result)
endProtect against spam and exploits:
-- Use middleware for rate limiting
KnightRemotes:UseMiddleware(rateLimitingMiddleware)Always validate on the server:
KnightRemotes:Connect("PurchaseItem", function(player, itemId, clientPrice)
-- NEVER trust client price
local actualPrice = getItemPrice(itemId)
if player.Coins.Value >= actualPrice then
player.Coins.Value -= actualPrice
giveItem(player, itemId)
return true
else
return false, "Insufficient funds"
end
end)Centralize validation, logging, and anti-exploit measures:
-- Single middleware for all validation
KnightRemotes:UseMiddleware(function(player, eventName, args)
-- Log all requests
logRequest(player, eventName, args)
-- Rate limit
if isRateLimited(player) then
return false
end
-- Anti-exploit checks
if isSuspiciousActivity(player, eventName, args) then
flagPlayer(player)
return false
end
return true
end)Clean up connections you no longer need:
local connection = KnightRemotes:Connect("TempEvent", callback)
-- Later...
connection:Disconnect()Two-way remotes can error - handle them:
local success, result = pcall(function()
return KnightRemotes:Fire("GetData")
end)
if success then
-- Process result
else
warn("Request failed:", result)
end- Batch Updates: Instead of sending multiple individual updates, batch them together
- Use Unreliable for High-Frequency: Position/rotation updates don't need guaranteed delivery
- Limit Arguments: Fewer arguments = smaller packets
- Optimize Data Types: Use integers when possible (more compact than floats)
- Middleware Performance: Keep middleware functions lightweight
KnightRemotes is built with --!strict mode and full type annotations. You get IntelliSense and type checking:
--!strict
local KnightRemotes = require(ReplicatedStorage.Packages.knightremotes)
-- Type-safe callbacks
KnightRemotes:Connect("Damage", function(player: Player, amount: number)
-- TypeScript-like autocomplete and checking
player.Character.Humanoid.Health -= amount
end)If you're coming from standard RemoteEvents/RemoteFunctions:
| Old | New |
|---|---|
remoteEvent = Instance.new("RemoteEvent") |
KnightRemotes:new("EventName") |
remoteEvent:FireClient(player, ...) |
KnightRemotes:Fire("EventName", player, ...) |
remoteEvent:FireAllClients(...) |
KnightRemotes:FireAll("EventName", ...) |
remoteEvent.OnServerEvent:Connect(...) |
KnightRemotes:Connect("EventName", ...) |
remoteFunction:InvokeServer(...) |
KnightRemotes:Fire("EventName", ...) (with TwoWay=true) |
Default timeout is 60 seconds. Check:
- Server callback is connected
- No errors in server callback
- Network connectivity
You can't create unreliable two-way remotes. Change to reliable = true.
Check your middleware return values - make sure to return true to allow packets through.
Need help? Open an issue on GitHub