LegacyShell WikiLegacyShell Wiki
Back to LegacyShell
Home
Wiki
Plugins
Docs
Back to LegacyShell
Home
Wiki
Plugins
Docs
  • Documentation

    • Getting Started

      • What is LegacyShell?
      • Speed Setup
      • Requirements
      • Installation
      • First Run
      • Config Files
      • Making an Account
      • Troubleshooting (Getting Started)
    • Running a Server

      • Architecture Overview
      • The Database
      • Users and Ranks
      • Adding Game Servers
      • Client Mirrors
      • Perpetual
      • Backups
      • Rate Limiting
      • Moderation
      • Closed Mode
      • Deployment
      • Troubleshooting (Running a Server)
      • Hosting for Someone Else's Instance
    • Content Creation

      • Maps
      • Dealing with Babylon Models
      • Map Blocks
      • Items and Skins
      • Hats and Stamps
      • Sounds
      • Gamemodes
      • Seasonal Events
    • Plugin Development

      • Quickstart
      • I Want To...
      • Anatomy of a Plugin
      • Lifecycle
      • Dependencies
      • Events (Concept)
      • Event Reference

        • services: events
        • game: events — shared logic (src/shell/)
        • game: events — main-thread server process
        • game: events — per-connection client object
        • game: events — room lifecycle & tick loop
        • game: events — in-browser gameplay
        • client: events — client server & build pipeline
      • Commands
      • Client-Side Code
      • Static Assets
      • Content Packs
      • Networking
      • Workers and State
      • Prediction and Authority
      • Recipes

        • Recipe: Killstreaks
        • Recipe: New Pickup Item
        • Recipe: New Gamemode
        • Recipe: Custom Weapon
        • Recipe: UI Modification
        • Recipe: Discord Integration
        • Recipe: Replacing Core Behaviour
        • Recipe: Persistent Plugin Storage
        • Recipe: Rewarding Players with Currency
        • Recipe: Custom Per-Player Data
        • Recipe: Custom Theme
      • Publishing
      • Pitfalls
      • Modifiers
      • Sound and Apollo
    • Codebase Reference

      • Repo Layout
      • Shared Shell Layer
      • Server-Only Markers
      • The ss Object
      • Build Pipeline
      • Stamps and Babylons
      • Game Loop
      • Rooms and Workers
      • Wire Protocol
      • Generated

        • Wire Protocol Opcodes
        • Enums & Lookup Tables
        • Database Schema
        • Config Reference
        • Slash Command Reference
      • Services Internals
      • Catalog and Items
      • Permissions Internals
      • Physics and Collision
      • Known Quirks
      • Codebase Anecdotes
      • Development Timeline
    • Contributing

      • Documentation Style Guide
      • Generators
      • For AI Agents

Modifiers

Audience: Plugin authors building gamemodes, moderators using in-game change commands · Prereqs: Events (concept)

Canonical source: src/shell/gametypes.js (defaultOptions), src/shell/player.js:176-193 (setDefaultModifiers), src/shell/permissions.js:37-333 (the change-category commands)

Modifiers are the per-team numeric multipliers that make up most of what a gamemode actually is mechanically - speed, gravity, damage, and a dozen others. They're set two ways: baked into a GameType's options at boot (the gamemode-authoring path), or changed live in a room via slash commands (the moderator path, /gravity, /speed, etc. - see the generated slash command reference for the full list and permission levels). This page covers the mechanism underneath both.

Shape: one array of three, per modifier, per team

Every modifier in defaultOptions is a 3-element array, indexed by team:

// src/shell/gametypes.js:20-24 - real code
speedModifier: [
    1, //ffa
    1, //team1
    1, //team2
],

Index 0 is used for FFA (everyone's on "team 0" in a non-team gamemode), 1 and 2 are the two real teams in a Teams-enabled gamemode. A room's live values live at room.gameOptions[key][team], seeded from defaultOptions and overridden per-GameType (see Recipe: New Gamemode for a worked example that overrides resistanceModifier this way).

A player's own effective modifiers are copied out of gameOptions onto the player object once, in setDefaultModifiers:

// src/shell/player.js:176-193 - real code (abridged)
setDefaultModifiers(init) {
    this.changeModifiers({
        gravityModifier: this.gameOptions.gravityModifier[this.team],
        speedModifier: this.gameOptions.speedModifier[this.team],
        // ...one line per modifier, same pattern
    }, init);
};

This is why changing a team's modifier live doesn't retroactively need every player object rebuilt - changeModifiers (below) pushes the new value onto already-joined players directly.

The full catalog and exactly what each one does

Traced to its actual usage, not just its name:

ModifierDefaultEffectWhere
scale1Multiplies outgoing damage dealt and divides incoming damage taken - a de facto "player size" stat, not purely cosmetic.bullets.js:115, player.js:1140
speedModifier1Multiplies horizontal movement input.player.js:535-536
gravityModifier1Multiplies downward acceleration.player.js:529
regenModifier1Multiplies passive HP regen rate.player.js:367
damageModifier1Multiplies damage dealt by this player's shots/explosions (attacker-side).bullets.js:52,115
resistanceModifier1Divides damage taken by this player (defender-side) - the modifier Recipe: New Gamemode sets to 0.01 for a one-hit-kill mode.player.js:1140
jumpBoostModifier1Multiplies jump velocity.player.js:771
knockbackModifier0Multiplies self-knockback taken from incoming damage - off by default, unlike every other combat modifier.player.js:1123
physicsSpeedModifier1Multiplies the delta-time used for this player's own physics step and view bobble - a personal slow-motion/fast-forward dial.player.js:240,360
bulletSpeedModifier1Multiplies bullet travel speed.bullets.js:128
reloadSpeedModifier1Divides reload time (higher = faster reload).player.js:1003,1005
weaponSettleModifier1Multiplies the delta-time used for weapon sway/settle animation.player.js:896
grenadeThrowModifier1Multiplies throw power.player.js:1019
grenadeTimerModifier1Divides the rate a grenade's fuse counts down (higher = longer fuse before detonation).bullets.js:366
grenadeBounceModifier1Multiplies bounce impulse off surfaces.bullets.js:380

Two more team-indexed arrays exist alongside the modifiers but aren't named *Modifier: lifesteal (defaults 0) and scale's sibling itemsEnabled/teamSwitchMaximumDifference are plain defaultOptions entries, not per-modifier multipliers - see gametypes.js directly if you need those.

Setting modifiers from a gamemode plugin

At GameTypesInit time, before any room exists - this is baked into every room of that gamemode from creation:

// pattern from Recipe: New Gamemode - real code
this.plugins.on('game:GameTypesInit', ({ GameTypes }) => {
    GameTypes.push({
        shortName: "Sudden Death",
        longName: "Sudden Death",
        codeName: "suddendeath",
        mapPool: "FFA",
        options: {
            resistanceModifier: [0.01, 0.01, 0.01],   // [ffa, team1, team2]
        },
    });
});

options only needs to list what differs from defaultOptions - everything else deep-merges in automatically (see Recipe: New Gamemode for the full mechanism).

Setting modifiers live, at runtime

This is what every change-category slash command does, and it's available to your own commands too via setGameOptionInMentions (server-side, authoritative) paired with changeModifiers (client-side, predicted) - see Prediction and Authority for why both halves exist:

// pattern from src/shell/permissions.js:37-56 - real code, abridged
executeClient: ({ player, opts, mentions }) => {
    forEachMentionInMentions(mentions, (player) => {
        player.changeModifiers({ gravityModifier: opts });
    });
},
executeServer: ({ player, opts, mentions, mentionsLiteral }) => {
    setGameOptionInMentions(player, mentions, mentionsLiteral, "gravityModifier", opts);
}

setGameOptionInMentions (permissions.js:963) understands three scope literals, checked against the command's @mention:

MentionEffect
@aSets the value for all three team slots at once - the modifier applies room-wide regardless of team.
@tSets only the sender's own team's slot.
@oSets only the opposing team's slot - the asymmetric case (e.g. handicapping one side).

Any other mention resolves to specific players via forEachMentionInMentions and calls changeModifiers on each one directly, bypassing gameOptions entirely - a per-player override that doesn't touch the team-wide default, and so doesn't persist for players who join after the command runs (setDefaultModifiers would just re-seed them from the unchanged gameOptions).

Common Issues

I set a modifier via GameTypesInit but it's not taking effect. Check you're mutating options, not defaultOptions directly - editing defaultOptions in place changes the value for every gamemode, including the built-in ones, since they all deep-merge from the same object.

A per-player override I applied with a specific @mention disappeared after they respawned or reconnected. Expected - a targeted-player override lives only on that player object, not in room.gameOptions. Use @a/@t/@o instead if you want the change to persist for anyone who joins or respawns afterward.

knockbackModifier seems to do nothing by default. It's the one modifier that defaults to 0, not 1 - knockback-on-hit is opt-in, not a baseline effect scaled by 1.

Next: Recipe: New Gamemode for a complete worked example, or Commands for building your own change-style command from scratch.


This page was drafted with AI assistance and reviewed for accuracy. If something looks wrong, please open a PR or flag it.

Edit this page on GitHub
Prev
Pitfalls
Next
Sound and Apollo