---
name: scp-roleplay-server-addons
description: Create, explain, or debug SCP Roleplay custom server addons using the documented sandboxed Lua 5 API. Use for Server Addons, not general Roblox Studio or Luau development.
---

# SCP: Roleplay Server Addons

This skill is for SCP: Roleplay, a game in the Roblox engine. Its Server Addons
use a custom sandbox with specifically exposed APIs, not the full Roblox API.

Build the requested addon against the [Server Addons documentation](https://scproleplay.com/docs/server-addons/).
Read the relevant API entries and examples before writing code. In a checkout of
the website, the source is `docs/server-addons/index.html`. If the documentation
cannot be read, ask for the relevant excerpt instead of inventing an API.

## Establish the addon contract

- Identify the trigger, affected players or map objects, configuration values,
  and whether the script is for a one-off action or ongoing behavior. Ask only
  for missing details that change the implementation.
- Use the user's exact map, interaction, team, and tool names. Put values they
  must customize together near the top of the script; label example values.
- Match each engine-facing operation to a documented function or example.
  Flag unsupported requests or ambiguous signatures before relying on them.

## Respect the sandbox

- The runtime is sandboxed Lua 5 with specific syntax extensions, not a normal
  Roblox Studio script. It supports `+=`, `-=`, `/=`, `*=`, and `..=` compound
  assignment, the `continue` keyword, and basic type annotations on local
  variables, function parameters, and return values, as shown below. This does
  not imply the full Luau feature set or Roblox API is available: do not assume
  Roblox services, Player objects, client UI creation, other Luau-specific operators,
  generics, or type aliases. Reference signatures also describe defaults and
  argument alternatives; do not copy those signatures literally into code.
- `getPlayers()` and `getTeamMembers(team)` return usernames. Pass those strings
  to player functions. `getPlayerHealth(name)` and `getPlayerMaxHealth(name)`
  return `-1` when the player is not spawned; handle that before changing health.
- `toggleRagdoll(player : string, on : bool (default: true))` enables or
  disables ragdoll for the specified player. `on` defaults to `true`.
- Read existing map Instances through `workspace` or `f("Name")`.
  `f(name)` returns the first matching Instance, so use distinct map names and
  check that a lookup succeeded. Insert a new Instance with `f(instance)`, not
  by parenting it directly to Workspace. The parent may need to be inserted
  before children are attached or their data can be discarded. Follow the
  documented custom-tool example for `f(Tool)`, then `Handle.Parent = Tool`.
- Instance and datatype operations shown in the examples, such as
  `Instance.new("Sound")`, `Instance.new("Tool")`, `Instance.new("Part")`,
  `CFrame.new(...)`, and `Color3.new(...)`, do not imply the entire Roblox API
  is exposed. Check other operations individually.
- "Run Once" executions stop after 10 seconds. Do not design a lasting loop
  for that mode. The cross-addon example uses `task.wait(1)`; do not infer that
  other `task` methods are available. Avoid busy loops and repeated listeners.
- Player-facing messages are filtered and require the server host to be
  present. This also applies to commands that broadcast text. Do not bypass
  filtering or promise messages will work without the host.

### Respect the text-setting cooldown

In Server Addons, text users can set is subject to a 1 minute (60-second)
cooldown for Roblox compliance. Plan repeated actions around prepared templates:

- During setup, initialize the necessary text-bearing instances, such as custom
  Tools, as reusable templates with names and other required text already set.
- For each tool grant, clone the prepared template and give the clone to the
  player. Retain the template for subsequent grants. Do not generate a new Tool
  and assign its name on every grant.
- Keep text initialization outside repeated grant, spawn, interaction, and
  command callbacks. Do not rewrite text on each clone. If new or changed text
  is required, respect the cooldown and filtering requirements; cloning is not
  an exemption or a way to bypass filtering.
- Keep the documented map-insertion and parenting rules, filtering, and host
  requirements. Use only supported sandbox operations; do not invent template
  storage services or APIs. If the cloning operation or setup is unclear, ask
  for a working sandbox example instead of assuming the full Roblox API.
- Treat the existing custom-tool creation examples as one-off setup examples,
  not a pattern to run on every grant. Test repeated grants and verify that no
  text-setting step is repeated in that path.
- Do not confuse this text-setting cooldown with `setToolCooldown`, which
  controls tool use, or the separately documented `registerAddonCommand`
  60-second limit after its latest batch. Do not infer shared timer scope,
  reset behavior, or extra exemptions.

### Supported basic type annotations

```lua
local Name: string = "Typed addon"
local Count: number = 6

local function Describe(Label: string, Days: number): string
    return Label .. ": " .. tostring(Days) .. " days"
end

print(Describe(Name, Count))
```

Plain Lua 5 parsers do not support these sandbox extensions. Do not remove
requested supported syntax just to satisfy such a parser, or treat a parser
failure as proof the addon is invalid. Verify extension behavior in the actual
addon runtime and state when that runtime has not been tested.

## Use the event and API contracts

Use `event("eventName", function(data) ... end)`. The callback receives an
envelope with `data.When` and `data.Value`, not a Roblox Player instance.

- `joined`, `left`, `spawned`, and `death`: `data.Value` is the username string.
- `interaction`: `[1]` is the username, `[2]` is the custom interaction name.
- `lightswitch`: `[1]` is the username, `[2]` is the room, `[3]` is the new state.
- `kill`: `[1]` is the killer, `[2]` is the victim.
- `usedtool`, `mousedown`, and `mouseup`: `[1]` is the username, `[2]` is the
  tool, `[3]` is the click position.

Example: heal a spawned player using an existing custom interaction named
`HealingStation` (the map must already provide that interaction):

```lua
local interactionName = "HealingStation"

event("interaction", function(data)
    local player = data.Value[1]
    local interaction = data.Value[2]

    if interaction == interactionName and getPlayerHealth(player) > 0 then
        heal(player)
    end
end)
```

Consult [API Reference](https://scproleplay.com/docs/server-addons/#api) for
functions beyond this example. Important return-value distinctions:

- `getPlayerPosition(name)` returns a CFrame. `getTeam(name)` returns name and
  color; `getTeams()` returns entries in `{ name, color }` order.
- A raycast hit on a player returns their username in `RaycastResult.Instance`.
- `runCommand(cmd)` returns success and, on failure, an error. Check both and
  use the [Server Commands reference](https://scproleplay.com/docs/server-commands/)
  for the command itself.
- Use `signal`, `signalReceived`, and `signalAsync` for documented cross-addon
  communication. Give signals specific names and ensure a responder exists
  before depending on a response from `signalAsync`.
- `http` is documented, but its response shape is not specified here. Do not
  invent response fields. Ask for a known working example when required.
  Do not add external requests, credentials, or player-data transmission unless
  the user requests them; never embed private secrets in a shared addon.

### Addon Commands API

Use `:addoncommands` to open the addon commands menu, which contains buttons
registered by addons:

- `registerAddonCommand("id", "title", callback)` adds a command button.
  The title is filtered and capped at 32 characters. The callback is invoked
  when the command is selected.
- `registerAddonCommand("id", false)` disables the command.
- `registerAddonCommand("id", true)` re-enables the command.
- `registerAddonCommand("id", nil)` removes the command.

By default, only moderators and above can access the panel. Expand this access
with `registerAddonCommand(whitelist)`, where `whitelist` is a table of player
names or user IDs. Do not infer behavior for an empty whitelist. You can
implement your own restrictions and cooldowns in each command's callback.

For compliance reasons, `registerAddonCommand` is unavailable for 60 seconds
after its latest batch. You can still assign multiple commands within the same
execution frame.

Addon commands reset when the map changes, during loading and before addons
are loaded. Register the commands needed for the new map when its addons load.
Use the registration callback to handle command selection. For example,
register commands to turn ragdoll on and off:

```lua
registerAddonCommand("a", "ragdoll on", function(player) toggleRagdoll(player) end)
registerAddonCommand("b", "ragdoll off", function(player) toggleRagdoll(player, false) end)
```

Do not infer additional callback parameters for button selections or return-value
behavior from this example.

### Using addon commands with arguments

Run a registered addon command with `/command <id> [arguments]`. For example,
`/command test 1` calls the `test` command's callback with the player first,
followed by the supplied argument.

Player-supplied argument values are hidden from `print`, `warn`, and `error`
output, including error logs from the callback. This prevents command arguments
from being used to communicate with other players. Use arguments for command
logic, not for displaying or logging player-entered messages.

Existing commands take priority: addon commands are checked only after all
existing commands. If an addon ID matches an existing command, `/command`
uses the existing command instead and does not run the addon command. Do not
infer argument type conversion or quoting rules from the example.

## Deliver and verify

Provide the Lua script, required map/setup values, execution-mode assumptions,
and a short in-game test checklist. Keep the solution within the requested
scope, especially for damage, score changes, team changes, and server commands.

Review every nonstandard call against the documentation. Test on a separate
custom server or a safe copy of the map before using it with live players:
normal trigger, unrelated trigger, repeated activation, missing map objects,
unspawned or departed players, and host absence where relevant. For custom
tools, check creation/parenting order and `setToolCooldown` behavior.

When debugging, obtain the exact error, script, execution mode, and relevant
map names. Check username-versus-Instance mistakes, `data.Value` indexing,
missing objects, the 10-second limit, and host-only messaging first. Distinguish
static review or mocked tests from execution in SCP: Roleplay; never claim
in-game validation without actually observing it.
