Skip to main content

Stagger

GTA V / Euphoria style balance loss for R15 characters. Call Stagger.Begin on the server and the character lurches in a direction, stutter-stepping to get their feet back under them while the arms come out and the torso swings with the momentum. Little by little the steps catch up, and the character either recovers and stands normally again, or runs out of balance and ragdolls, staying down for a while (or until you call Stagger.Recover) before standing back up.

It is built on the joints Roblox already gives every character with the Avatar Joint Upgrade: AnimationConstraint for animation and BallSocketConstraint for the limits. No joint is ever replaced or added. While staggering the upper body is physically simulated with a soft AngularStrength (a "muscle"), so it lags behind the procedural pose and reacts to the movement the way NaturalMotion's characters do, and when the character falls every joint goes limp and the ball sockets take over.

Networking. The server is the authority: it decides when a stagger starts and ends, switches the joints (which replicate), and broadcasts state with Packet. The balance model runs on whoever owns the character's physics and the outcome is reported back: the player's own client for a player, and for an NPC the nearest player's client (within Stagger.DriverRange), which is given network ownership of the body for the duration so the stagger and the ragdoll are simulated at full rate instead of replicated from the server. With nobody near, the server drives the NPC. Controllers are managed by a Binder, so every staggering character carries the Staggering tag for the duration.

Require the module once on the server and once on every client (a shared ModuleScript under ReplicatedStorage.Packages, which Wally gives you). Requiring it is all the setup there is.

-- Server
local Stagger = require(ReplicatedStorage.Packages.Stagger)

explosion.Touched:Connect(function(hit)
	local character = hit.Parent
	if character:FindFirstChildOfClass("Humanoid") then
		local away = (character:GetPivot().Position - explosion.Position)
		Stagger.Begin(character, away, { Preset = "Heavy", Intensity = 1.5 })
	end
end)

Stagger.StateChanged:Connect(function(character, state)
	if state == Stagger.State.Ragdoll then
		print(character.Name, "hit the floor")
	end
end)
-- Client: nothing to call, just make sure it is required somewhere.
require(ReplicatedStorage.Packages.Stagger)

Requires StarterPlayer.AvatarJointUpgrade to be on (the default for new experiences). Characters still rigged with Motor6D are skipped with a warning.

Types​

BeginOptions​

interface BeginOptions {
Preset: string?--

Name of a preset from Stagger.Presets or Stagger.RegisterPreset. Default "Default".

Intensity: number?--

Scales the shove: 1 is the preset as tuned, 0.5 a nudge, 2 a truck. Default 1.

Seed: number?--

Seed for the balance model's randomness (0 to 65535). Random when omitted.

}

Properties​

State​

This item is read only and cannot be modified. Read Only
Stagger.State: {
Idle: number,
Staggering: number,
Ragdoll: number,
Recovering: number
}

The states a character moves through. Idle (0) is not staggering. Staggering (1) is stutter-stepping to regain balance. Ragdoll (2) is on the floor, fully simulated. Recovering (3) is standing back up (or the short settle after recovering on their feet) before returning to Idle.

StateNames​

This item is read only and cannot be modified. Read Only
Stagger.StateNames: {[number]: string}

Stagger.StateNames[Stagger.State.Ragdoll] == "Ragdoll".

TAG​

This item is read only and cannot be modified. Read Only
Stagger.TAG: string

"Staggering": the CollectionService tag every character carries from Stagger.Begin (or Stagger.Ragdoll) until it is back to Idle.

ATTRIBUTE​

This item is read only and cannot be modified. Read Only
Stagger.ATTRIBUTE: string

"StaggerState": the server writes the numeric state to this attribute on the character so other systems (and non-scripted things like UI) can read it.

DriverRange​

Stagger.DriverRange: number

How close (studs) a player has to be to an NPC to be handed its stagger to drive and simulate. Keep it well inside your streaming radius, since that player needs the NPC streamed in. Beyond it the server drives. Default 128.

Presets​

This item is read only and cannot be modified. Read Only
Stagger.Presets: {[string]: Config}

The built-in presets (Default, Light, Heavy, Drunk) plus anything you registered. See Config for every field.

StateChanged​

This item is read only and cannot be modified. Read Only
Stagger.StateChanged: Signal<(
number,
number
)>

Fires on every peer when a character changes state: (character, newState, oldState).

Fell​

This item is read only and cannot be modified. Read Only
Stagger.Fell: Signal<Model>

Fires on every peer when a character hits the floor (enters Ragdoll).

Recovered​

This item is read only and cannot be modified. Read Only
Stagger.Recovered: Signal<Model>

Fires on every peer when a character is done: back to Idle, on their feet.

Binder​

This item is read only and cannot be modified. Read Only
Stagger.Binder: Binder<Controller>

The Binder that owns the controllers, for InstanceBound / InstanceUnbound or GetInstances(). It is manual-only: do not Bind through it yourself.

Functions​

Begin​

This item only works when running on the server. Server
Stagger.Begin(
character: Model,--

An R15 character on AnimationConstraints (player or NPC).

direction: Vector3?,--

World direction of the shove. Defaults to the character's forward.

options: BeginOptions?
) → boolean--

false if the character is not a supported rig, is dead, or is ragdolled.

Starts a stagger. The character lurches along direction (flattened to the ground) and begins stutter-stepping. Whether they recover or fall is up to the balance model, tuned by the preset and Intensity. Calling it on a character that is already staggering hits them again: a fresh lurch from the new direction. Calling it on a ragdolled character does nothing.

Ragdoll​

This item only works when running on the server. Server
Stagger.Ragdoll(
character: Model,
options: BeginOptions?--

Only Preset is used (for the ragdoll tuning) when the character was Idle.

) → boolean--

false if the character is not a supported rig or is dead.

Drops the character straight into the ragdoll, skipping the stagger (or cutting one short). Uses the preset's RagdollDuration to decide when they get up; 0 means they stay down until Stagger.Recover.

Recover​

This item only works when running on the server. Server
Stagger.Recover(character: Model) → ()

Ends the stagger or ragdoll: the character stands up now (Recovering), then returns to Idle after the preset's GetUpDuration. Does nothing for an Idle character.

Stop​

This item only works when running on the server. Server
Stagger.Stop(character: Model) → ()

Cancels everything immediately: joints restored, controls back, straight to Idle with no get-up. Use it when something else takes over the character (a cutscene, a seat, a teleport).

RegisterPreset​

Stagger.RegisterPreset(
name: string,
base: Config,--

A preset to start from, usually Stagger.Presets.Default.

overrides: {[string]: any}?--

Fields to change; see Config.

) → Config--

The frozen preset.

Registers (or replaces) a preset for Stagger.Begin's Preset option. Register the same presets on the server and the client, since the client needs the tuning to run the balance model for its own character. Unknown keys and wrong types raise an error.

Stagger.RegisterPreset("Slippery", Stagger.Presets.Default, { MisstepChance = 0.8, StepMomentumKeep = 0.85 })

GetState​

Stagger.GetState(character: Model) → number--

One of Stagger.State; Idle for anything that is not staggering.

IsStaggering​

Stagger.IsStaggering(character: Model) → boolean--

true in any state other than Idle.

GetController​

Stagger.GetController(character: Model) → Controller?

The live Controller for a character, or nil when it is Idle. On the driving peer its Balance field is the running Balance model, handy for debugging or for feeding extra bumps with Balance:Disturb.

GetCharacters​

Stagger.GetCharacters() → {Model}--

Every character currently not Idle.

IsSupported​

Stagger.IsSupported(character: Instance) → boolean--

Whether the character is an R15 rig on AnimationConstraints.

Show raw api
{
    "functions": [
        {
            "name": "Begin",
            "desc": "Starts a stagger. The character lurches along `direction` (flattened to the ground) and\nbegins stutter-stepping. Whether they recover or fall is up to the balance model, tuned by\nthe preset and `Intensity`. Calling it on a character that is already staggering hits them\nagain: a fresh lurch from the new direction. Calling it on a ragdolled character does nothing.",
            "params": [
                {
                    "name": "character",
                    "desc": "An R15 character on AnimationConstraints (player or NPC).",
                    "lua_type": "Model"
                },
                {
                    "name": "direction",
                    "desc": "World direction of the shove. Defaults to the character's forward.",
                    "lua_type": "Vector3?"
                },
                {
                    "name": "options",
                    "desc": "",
                    "lua_type": "BeginOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "`false` if the character is not a supported rig, is dead, or is ragdolled.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 474,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Ragdoll",
            "desc": "Drops the character straight into the ragdoll, skipping the stagger (or cutting one short).\nUses the preset's `RagdollDuration` to decide when they get up; `0` means they stay down\nuntil [Stagger.Recover].",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                },
                {
                    "name": "options",
                    "desc": "Only `Preset` is used (for the ragdoll tuning) when the character was Idle.",
                    "lua_type": "BeginOptions?"
                }
            ],
            "returns": [
                {
                    "desc": "`false` if the character is not a supported rig or is dead.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 513,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Recover",
            "desc": "Ends the stagger or ragdoll: the character stands up now (`Recovering`), then returns to\n`Idle` after the preset's `GetUpDuration`. Does nothing for an Idle character.",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "returns": [],
            "function_type": "static",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 533,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Stop",
            "desc": "Cancels everything immediately: joints restored, controls back, straight to `Idle` with no\nget-up. Use it when something else takes over the character (a cutscene, a seat, a teleport).",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "returns": [],
            "function_type": "static",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 551,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "RegisterPreset",
            "desc": "Registers (or replaces) a preset for [Stagger.Begin]'s `Preset` option. Register the same\npresets on the server and the client, since the client needs the tuning to run the balance\nmodel for its own character. Unknown keys and wrong types raise an error.\n\n```lua\nStagger.RegisterPreset(\"Slippery\", Stagger.Presets.Default, { MisstepChance = 0.8, StepMomentumKeep = 0.85 })\n```",
            "params": [
                {
                    "name": "name",
                    "desc": "",
                    "lua_type": "string"
                },
                {
                    "name": "base",
                    "desc": "A preset to start from, usually `Stagger.Presets.Default`.",
                    "lua_type": "Config"
                },
                {
                    "name": "overrides",
                    "desc": "Fields to change; see [Config].",
                    "lua_type": "{ [string]: any }?"
                }
            ],
            "returns": [
                {
                    "desc": "The frozen preset.",
                    "lua_type": "Config"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 574,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "GetState",
            "desc": "",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "returns": [
                {
                    "desc": "One of [Stagger.State]; `Idle` for anything that is not staggering.",
                    "lua_type": "number"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 586,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "IsStaggering",
            "desc": "",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "returns": [
                {
                    "desc": "`true` in any state other than `Idle`.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 597,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "GetController",
            "desc": "The live [Controller] for a character, or `nil` when it is Idle. On the driving peer its\n`Balance` field is the running [Balance] model, handy for debugging or for feeding extra\nbumps with `Balance:Disturb`.",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Model"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Controller?"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 611,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "GetCharacters",
            "desc": "",
            "params": [],
            "returns": [
                {
                    "desc": "Every character currently not Idle.",
                    "lua_type": "{ Model }"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 620,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "IsSupported",
            "desc": "",
            "params": [
                {
                    "name": "character",
                    "desc": "",
                    "lua_type": "Instance"
                }
            ],
            "returns": [
                {
                    "desc": "Whether the character is an R15 rig on AnimationConstraints.",
                    "lua_type": "boolean"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 630,
                "path": "packages/src/Stagger/init.luau"
            }
        }
    ],
    "properties": [
        {
            "name": "State",
            "desc": "The states a character moves through. `Idle` (0) is not staggering. `Staggering` (1) is\nstutter-stepping to regain balance. `Ragdoll` (2) is on the floor, fully simulated.\n`Recovering` (3) is standing back up (or the short settle after recovering on their feet)\nbefore returning to `Idle`.",
            "lua_type": "{ Idle: number, Staggering: number, Ragdoll: number, Recovering: number }",
            "readonly": true,
            "source": {
                "line": 105,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "StateNames",
            "desc": "`Stagger.StateNames[Stagger.State.Ragdoll] == \"Ragdoll\"`.",
            "lua_type": "{ [number]: string }",
            "readonly": true,
            "source": {
                "line": 113,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "TAG",
            "desc": "`\"Staggering\"`: the CollectionService tag every character carries from [Stagger.Begin] (or\n[Stagger.Ragdoll]) until it is back to Idle.",
            "lua_type": "string",
            "readonly": true,
            "source": {
                "line": 122,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "ATTRIBUTE",
            "desc": "`\"StaggerState\"`: the server writes the numeric state to this attribute on the character so\nother systems (and non-scripted things like UI) can read it.",
            "lua_type": "string",
            "readonly": true,
            "source": {
                "line": 131,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "DriverRange",
            "desc": "How close (studs) a player has to be to an NPC to be handed its stagger to drive and\nsimulate. Keep it well inside your streaming radius, since that player needs the NPC\nstreamed in. Beyond it the server drives. Default `128`.",
            "lua_type": "number",
            "source": {
                "line": 140,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Presets",
            "desc": "The built-in presets (`Default`, `Light`, `Heavy`, `Drunk`) plus anything you registered.\nSee [Config] for every field.",
            "lua_type": "{ [string]: Config }",
            "readonly": true,
            "source": {
                "line": 149,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "StateChanged",
            "desc": "Fires on every peer when a character changes state: `(character, newState, oldState)`.",
            "lua_type": "Signal<(Model, number, number)>",
            "readonly": true,
            "source": {
                "line": 157,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Fell",
            "desc": "Fires on every peer when a character hits the floor (enters `Ragdoll`).",
            "lua_type": "Signal<Model>",
            "readonly": true,
            "source": {
                "line": 165,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Recovered",
            "desc": "Fires on every peer when a character is done: back to `Idle`, on their feet.",
            "lua_type": "Signal<Model>",
            "readonly": true,
            "source": {
                "line": 173,
                "path": "packages/src/Stagger/init.luau"
            }
        },
        {
            "name": "Binder",
            "desc": "The Binder that owns the controllers, for `InstanceBound` / `InstanceUnbound` or\n`GetInstances()`. It is manual-only: do not `Bind` through it yourself.",
            "lua_type": "Binder<Controller>",
            "readonly": true,
            "source": {
                "line": 182,
                "path": "packages/src/Stagger/init.luau"
            }
        }
    ],
    "types": [
        {
            "name": "BeginOptions",
            "desc": "",
            "fields": [
                {
                    "name": "Preset",
                    "lua_type": "string?",
                    "desc": "Name of a preset from [Stagger.Presets] or [Stagger.RegisterPreset]. Default `\"Default\"`."
                },
                {
                    "name": "Intensity",
                    "lua_type": "number?",
                    "desc": "Scales the shove: 1 is the preset as tuned, 0.5 a nudge, 2 a truck. Default `1`."
                },
                {
                    "name": "Seed",
                    "lua_type": "number?",
                    "desc": "Seed for the balance model's randomness (0 to 65535). Random when omitted."
                }
            ],
            "source": {
                "line": 86,
                "path": "packages/src/Stagger/init.luau"
            }
        }
    ],
    "name": "Stagger",
    "desc": "GTA V / Euphoria style balance loss for R15 characters. Call [Stagger.Begin] on the server and\nthe character lurches in a direction, stutter-stepping to get their feet back under them\nwhile the arms come out and the torso swings with the momentum. Little by little the steps\ncatch up, and the character either **recovers** and stands normally again, or runs out of\nbalance and **ragdolls**, staying down for a while (or until you call [Stagger.Recover])\nbefore standing back up.\n\nIt is built on the joints Roblox already gives every character with the Avatar Joint\nUpgrade: `AnimationConstraint` for animation and `BallSocketConstraint` for the limits. No\njoint is ever replaced or added. While staggering the upper body is physically simulated with\na soft `AngularStrength` (a \"muscle\"), so it lags behind the procedural pose and reacts to the\nmovement the way NaturalMotion's characters do, and when the character falls every joint goes\nlimp and the ball sockets take over.\n\n**Networking.** The server is the authority: it decides when a stagger starts and ends,\nswitches the joints (which replicate), and broadcasts state with\n[Packet](/docs/packages/packet). The balance model runs on whoever owns the character's\nphysics and the outcome is reported back: the player's own client for a player, and for an\nNPC the nearest player's client (within [Stagger.DriverRange]), which is given network\nownership of the body for the duration so the stagger and the ragdoll are simulated at full\nrate instead of replicated from the server. With nobody near, the server drives the NPC.\nControllers are managed by a [Binder](/docs/packages/binder), so every staggering character\ncarries the `Staggering` tag for the duration.\n\nRequire the module once on the server and once on every client (a shared `ModuleScript` under\n`ReplicatedStorage.Packages`, which Wally gives you). Requiring it is all the setup there is.\n\n```lua\n-- Server\nlocal Stagger = require(ReplicatedStorage.Packages.Stagger)\n\nexplosion.Touched:Connect(function(hit)\n\tlocal character = hit.Parent\n\tif character:FindFirstChildOfClass(\"Humanoid\") then\n\t\tlocal away = (character:GetPivot().Position - explosion.Position)\n\t\tStagger.Begin(character, away, { Preset = \"Heavy\", Intensity = 1.5 })\n\tend\nend)\n\nStagger.StateChanged:Connect(function(character, state)\n\tif state == Stagger.State.Ragdoll then\n\t\tprint(character.Name, \"hit the floor\")\n\tend\nend)\n```\n\n```lua\n-- Client: nothing to call, just make sure it is required somewhere.\nrequire(ReplicatedStorage.Packages.Stagger)\n```\n\nRequires **StarterPlayer.AvatarJointUpgrade** to be on (the default for new experiences).\nCharacters still rigged with `Motor6D` are skipped with a warning.",
    "source": {
        "line": 59,
        "path": "packages/src/Stagger/init.luau"
    }
}