Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/NeonD00m/feces/llms.txt

Use this file to discover all available pages before exploring further.

When you mark an entity for replication, you control which players receive its data by setting the value of the feces.replicated component. feces calls resolvePlayers() internally every time it builds a delta, expanding whatever value you set into a concrete list of Player instances. There are four supported filter modes, and they all flow through the same single function.

Broadcast (nil)

The simplest mode. Call world:add without a value. Because there is no value on the component, resolvePlayers receives nil and falls back to Players:GetPlayers() — every player currently in the server gets the data.
local entity = world:entity()
world:set(entity, Transform, CFrame.new(0, 5, 0))

-- Replicate all components to every player in the server
world:add(entity, feces.replicated)
The player list is resolved at the moment feces:delta() runs, not when you call world:add. If a player joins between the two calls they will already be included in the broadcast.

Single Player or String

Pass a Player instance (or a plain string) as the component value. resolvePlayers detects the Instance or string type and wraps it in a single-element table so the rest of the pipeline treats it identically to any other list.
local entity = world:entity()
world:set(entity, Inventory, { sword = true })

-- Only Player1 receives this entity's data
world:set(entity, feces.replicated, Player1)

List of Players

Pass a table of Player instances. resolvePlayers returns it directly — no copying occurs.
local party = { Player1, Player2, Player3 }

local entity = world:entity()
world:set(entity, QuestState, currentQuest)

-- Only the party members receive this entity's data
world:set(entity, feces.replicated, party)

Filter Function

Pass a function that accepts a Player and returns a boolean. resolvePlayers calls it once for every player from Players:GetPlayers() and collects those for which it returns true.
local entity = world:entity()
world:set(entity, SecretData, hiddenValue)

-- Everyone except Player1 receives this entity's data
world:set(entity, feces.replicated, function(player)
    return player ~= Player1
end)
Use a filter function when the recipient set is dynamic — for example, line-of-sight or team membership checks that change frequently. Because the function is re-evaluated on every delta() call, the recipient set automatically adjusts without you touching the component again.

Component-Level Targeting with Pairs

All four modes above apply to the entity as a whole. If you need per-component targeting — for example, sending Transform to everyone but PrivateStats only to the owning player — use jecs.pair(feces.replicated, Component) instead of feces.replicated directly.
local entity = world:entity()
world:set(entity, Transform, CFrame.new(0, 5, 0))
world:set(entity, PrivateStats, { health = 100, coins = 500 })

-- Broadcast Transform to all players
world:add(entity, jecs.pair(feces.replicated, Transform))

-- Send PrivateStats only to the owning player
world:set(entity, jecs.pair(feces.replicated, PrivateStats), ownerPlayer)
The filter value on a pair follows the exact same four modes (nil → broadcast, Player → single, table → list, function → predicate).
During delta(), feces checks for a plain replicated tag first. If the entity has replicated set, that filter applies to all components that don’t have their own pair. If only a per-component pair exists (no plain replicated), the pair’s filter is used for that specific component.

How resolvePlayers Works

Below is the full source of resolvePlayers so the behavior is unambiguous:
local Players = game and game:GetService("Players")

local function resolvePlayers(filter)
    if type(filter) == "function" then
        -- Call the predicate for every current player
        local out = {}
        for _, player in (if game then Players:GetPlayers() else { "-1" }) do
            local result = filter(player)
            if result then
                table.insert(out, player)
            end
        end
        return out
    elseif type(filter) == "table" then
        -- Use the table directly as the player list
        return filter
    elseif typeof(filter) == "Instance" or typeof(filter) == "string" then
        -- Wrap a single player (or string) in a table
        return { filter }
    else
        -- nil or any other value → broadcast to all current players
        return if game then Players:GetPlayers() else { "-1" }
    end
end
The "-1" string fallback in each branch is used by feces’s own test suite, which runs outside of a live Roblox game instance where Players is unavailable. In production your filters will always receive real Player objects.

Build docs developers (and LLMs) love