NPCs

This page documents the tools to help with syncing NPCs online. NPC behaviour is handled in scripts/onlinePlay_npc.lua.

How are NPCs synced?

Every NPC is considered to be "owned" by a specific player. This player is responsible for sending everyone else updates about the NPC. NPCs only get an owner after spawning, and the owner can change at any time.

NPCs are also given "online UIDs". This number is used to identify the NPC, since using its index would not be reliable. Once an NPC has been assigned a UID, it will never change.

Most properties of an NPC - things like its position, speed, extra settings, and much more - will already be synced. However, the data table is not synced. In order to sync properties in the data table, you can use the "extra data" properties of the online NPC handling config.

Online Handling Config

NPCs IDs can have extra code and properties specified for them through the handling config. Most importantly, this allows for proper syncing of data tables. It also allows you to control things like when a player should steal ownership of an NPC from another player, or how often often the NPC should be updated.

The handling config can be used like so:

local onlinePlayNPC = require("scripts/onlinePlay_npc")

-- 751 is the ID of the NPC you want to affect here.
onlinePlayNPC.onlineHandlingConfig[751] = {
    getExtraData = function(v)
        -- The owner of the NPC will run getExtraData.
        -- Its return value will then be received by everyone else in setExtraData.
        local data = v.data
        if not data.initialized then
            return nil
        end

        return {
            state = data.state,
            timer = data.timer,
        }
    end,
    setExtraData = function(v,receivedData)
        -- The data from getExtraData will be used to change the NPC's state.
        -- This is mainly for data table syncing, but it can also be used for any information about the NPC.
        local data = v.data
        if not data.initialized then
            return nil
        end

        data.state = receivedData.state
        data.timer = receivedData.timer
    end,
}

The following fields are available for a handling config:

Field Type Description
getExtraData function A function to encode data about the NPC's state, which can then be passed into setExtraData on the other players' ends. Can return any encodable value.
setExtraData function A function to take in the data from getExtraData and modify the NPC's state accordingly.
shouldStealFunc function Runs every frame, if the NPC is owned by another player. If it returns true, it will try to take ownership of the NPC. If it returns false, then the NPC cannot be stolen at all. If it returns nil, standard logic for stealing NPCs applies.
findSuitableOwnerFunc function Runs when an NPC initially spawns, to decide who should get ownership of it. If it returns a number, that player of that index will be given ownership. If it returns nil, standard logic for NPC ownership applies.
canClaimHarmFunc function Normally, NPC harming is cancelled unless the player owns the NPC or is responsible for the harming. If this function returns true, harming will be allowed regardless of any other factors.
canClaimKillFunc function Similar to canClaimHarmFunc, but for when an NPC dies.
updateFrequency number How long, in seconds, is between each update of the NPC. The default is 1/10 (i.e., 10 updates per second).

The following functions can be used for modifying the handling config per-NPC instead of per-ID. Note that the getExtraData and setExtraData functions cannot be changed through these methods.

Function Returns Description
onlinePlayNPC.getConfig​(​npc (NPC)​) table or nil Returns the handling config used by the NPC, if it has one. Respects per-NPC configs.
onlinePlayNPC.overwriteConfig​(​npc (NPC), newConfig (table)​) nil Completely replaces the handling config of the NPC, ignoring the per-ID config entirely.
onlinePlayNPC.mergeConfig​(​npc (NPC), newConfig (table)​) nil Merges the new config with the NPC's existing config (be it a per-NPC or per-ID one). If conflicts exist, then the new config takes priority.
onlinePlayNPC.resetConfig​(​npc (NPC)​) nil Resets any per-NPC config, restoring the NPC to the per-ID config.

Miscellaneous Functions

Function Returns Description
onlinePlayNPC.getUIDFromNPC​(​npc (NPC)​) number or nil Returns the online UID of the NPC. If the NPC has not yet been assigned one, returns nil.
onlinePlayNPC.getNPCFromUID​(​onlineUID (number)​) NPC or nil Returns the NPC with the given UID, if it exists.
onlinePlayNPC.getOwner​(​npc (NPC)​) number Returns the index of the player that owns the NPC. If no owner has been assigned, returns 0.
onlinePlayNPC.ownsNPC​(​npc (NPC)​) boolean Returns true if the NPC is owned by the player, or false otherwise. Always returns true when not online.
onlinePlayNPC.tryClaimNPC​(​npc (NPC)​) nil Tries to claim ownership of the NPC, if it doesn't already have an owner. Does not guarantee that it will actually be claimed unless the player is the host.
onlinePlayNPC.assignNPCToOwner​(​npc (NPC), playerIdx (number)​) nil Grants ownership of an NPC to a specific player. Can only be run as the host.

NPC Commands

For convenience, there is a version of the commands system specifically for NPCs. It acts almost exactly like the regular commands sytem, but each message is tied to a specific NPC.

local onlinePlay = require("scripts/onlinePlay")
local onlinePlayNPC = require("scripts/onlinePlay_npc")

-- NPC commands are created exactly like regular commands, just with a different function.
-- You should include the name of your NPC in the command name, to make it more unique.
local exampleCommand = onlinePlayNPC.createNPCCommand("myNPCName_example", onlinePlay.IMPORTANCE_MAJOR)

-- onReceive works almost the same, but there is an added argument: the NPC for the command.
function exampleCommand.onReceive(npc,sourcePlayerIdx, foo,bar)
    -- Your code here.
end

-- Command:send is also mostly the same as a regular command, just with an NPC.
exampleCommand:send(npc,0, foo,bar)