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

Recipe: New Pickup Item

Audience: Plugin authors · Prereqs: Content Packs

Canonical source: src/shell/items.js, plugins_default/healthpackitem/shared.js (the reference pattern this recipe follows)

A pickup item (like ammo or grenades - or healthpackitem's health pack) is different from a shop/catalog item (see Content Packs): it's an in-world object players walk over to collect, defined in src/shell/items.js's AllItems array, not a database row.

The hook: game:AllItems

// plugins_default/healthpackitem/shared.js - real code from this codebase
this.plugins.on('game:AllItems', this.AllItems.bind(this));

AllItems(data) {
    data.AllItems.push({
        codeName: "HEALTH",
        mesh: "healthpack.alt",
        name: "Health Pack",
        actor: data.ItemActor,
        poolSize: 50,
        collect: function (player, applyToWeaponIdx) {
            if (player.hp === 100) return false;
            if (isServer) player.heal(50);
            return true;
        }
    });
},

Push a new entry onto data.AllItems from a game:AllItems listener, and it becomes a real, spawnable pickup with an assigned id and a ItemTypes lookup entry, indistinguishable from the built-in AMMO/GRENADE items.

A complete example

// plugins/adrenalineshot/index.js
import log from 'puppylog';
import { isServer } from '#constants';

export const PluginMeta = {
    identifier: "adrenalineshot",
    name: 'Adrenaline Shot',
    author: 'you',
    version: '1.0.0',
    descriptionShort: 'A pickup that grants a temporary speed boost.',
    descriptionLong: 'Adds a pickup item that grants a 5-second speed boost on collection.',
    legacyShellVersion: 598,
};

export class Plugin {
    constructor(plugins, thisDir) {
        this.plugins = plugins;
        this.thisDir = thisDir;

        if (plugins.type !== "game") {
            log.orange(`${PluginMeta.identifier} won't run on this server type.`);
            return;
        };

        this.plugins.on('game:AllItems', this.onAllItems.bind(this));
    };

    onAllItems(data) {
        data.AllItems.push({
            codeName: "ADRENALINE",
            mesh: "grenadeItem", // reusing an existing mesh for this example - ship your own via Content Packs for real use
            name: "Adrenaline Shot",
            actor: data.ItemActor,
            poolSize: 20,
            collect: function (player, applyToWeaponIdx) {
                if (isServer) {
                    const previousSpeed = player.modifiers.speedModifier;
                    player.changeModifiers({ speedModifier: previousSpeed * 1.5 });
                    clearTimeout(player._adrenalineTimeout);
                    player._adrenalineTimeout = setTimeout(() => {
                        player.changeModifiers({ speedModifier: previousSpeed });
                    }, 5000);
                };
                return true;
            }
        });
    };
};

We validated this exact plugin against a real (isolated, scratch) game server: ItemTypes.ADRENALINE is present, correctly indexed, and usable.

Fields, for reference

FieldMeaning
codeNameUnique string key - becomes the ItemTypes lookup key.
meshThe model name to render, from items.babylon (see Dealing with Babylon Models) - ship your own via Content Packs for a real plugin rather than reusing an existing one as this example does.
actorThe client-side visual actor class - ItemActor (spins slowly) is the default, or your own subclass. Available on the event payload as data.ItemActor.
poolSizeHow many concurrent instances of this item can exist in a room at once (see Pool in Codebase Reference).
collect(player, applyToWeaponIdx)Called when a player walks over it. Return false to leave it in place (not collected - e.g. player already at full HP/ammo), true to consume it.

Common Issues

A model-not-found error / the item is invisible. You're referencing a mesh name that doesn't exist in any loaded .babylon file - either reuse an existing name (as this recipe does, for teaching purposes) or ship your own model per Content Packs.


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
Recipe: Killstreaks
Next
Recipe: New Gamemode