Skip to main content

Inventory System

The inventory system manages player item storage with 28 slots (OSRS standard), stackable item support, drag-and-drop operations, OSRS-style context menus, and database persistence.
Inventory code lives in packages/shared/src/systems/shared/character/InventorySystem.ts and packages/client/src/game/systems/InventoryActionDispatcher.ts.

Core Constants


Inventory Structure

Each player has a PlayerInventory containing:

Money Pouch System

Hyperscape uses an RS3-style money pouch for protected coin storage:

Architecture

  • Money Pouch (characters.coins): Protected storage, doesn’t use inventory slots
  • Physical Coins (inventory with itemId='coins'): Stackable item, uses inventory slot

Coin Pouch Withdrawal

Players can withdraw coins from the money pouch to inventory:

Security Features

UI Components

Accessibility Features:
  • role="button" for screen readers
  • tabIndex={0} for keyboard focus
  • Enter/Space key activation
  • Descriptive aria-label

Error Handling

Testing: The coin pouch system has 32 comprehensive unit tests covering input validation, insufficient coins, inventory full, stack overflow, new stack creation, existing stack updates, and atomicity simulation.

Key Operations

Adding Items

Dropping Items

Pickup Items

Performance Update (PR #888): Item and coin pickup is now visually instant. The ground entity is removed before the database write, eliminating the 500ms+ delay players previously experienced.
Key Changes:
  • addItem() now accepts silent flag to skip DB persist and event emission
  • Ground entity removed before database write (instant visual feedback)
  • Inventory update emitted immediately after ground entity removal
  • Database persist happens afterward (still guaranteed before lock release)
  • No item loss or duplication risk (pickup lock held throughout)
  • Coin pickup uses same pattern with skipPersist flag on addCoins()
Performance Impact:
  • Before: 500ms+ database round-trip blocked visual feedback
  • After: Ground item vanishes instantly, DB persist happens in background
  • Security: DB persist still awaited (not fire-and-forget)

Transaction Locking

For atomic operations like bank deposits, store purchases, and trades, the inventory system supports transaction locks:

Database Persistence

Inventories are persisted to the database via the DatabaseSystem:

Item Consumption

Food and Healing

Players can consume food items to restore health with OSRS-accurate mechanics:
OSRS-Accurate Behavior: Food is consumed even at full health, and the eat delay applies regardless. Attack delay is only added if the player is already on attack cooldown.

Eat Delay Manager

The EatDelayManager tracks per-player eating cooldowns:

Combat Integration

When eating during combat, attack delay is added to prevent instant attacks:

Context Menus

Inventory items support OSRS-style context menus with manifest-driven actions.

Manifest-Driven Actions

Items can define explicit inventoryActions in their manifest:
The first action becomes the left-click default. If not specified, the system falls back to type-based detection.

Action Types

Item Helpers

The item-helpers.ts module provides type detection utilities for OSRS-accurate inventory actions:
Testing: The item-helpers module has 510 lines of comprehensive unit tests covering all type detection functions and edge cases.

Context Menu Colors

Context menus use OSRS-accurate color coding with centralized constants:
Example Usage:
All interaction handlers (NPCInteractionHandler, MobInteractionHandler, ItemInteractionHandler, etc.) now use these centralized constants instead of hardcoded values.

Inventory Events


UI Features

Hover Tooltips

Inventory and equipment items show tooltips on hover:
Tooltip Features:
  • Follows mouse cursor
  • Shows item stats and bonuses
  • Displays level requirements
  • Includes examine text
  • Auto-hides on mouse leave

Click-to-Unequip

Equipment slots support left-click to unequip (OSRS-style):
Unequip Behavior:
  • Left-click equipped item to unequip
  • Item moves to first available inventory slot
  • If inventory full, shows “Your inventory is full” message
  • Right-click still shows context menu with “Remove” option
This matches OSRS behavior where left-clicking equipment unequips it directly.

UI Integration

The client displays the inventory via InventoryPanel.tsx:
Features:
  • 28-slot grid layout
  • Drag-and-drop item movement
  • OSRS-style right-click context menus with manifest-driven actions and colored labels
  • Left-click primary actions (Eat, Wield, Use, etc.) using getPrimaryAction()
  • Shift-click to drop (OSRS-style instant drop)
  • Invalid target feedback: “Nothing interesting happens.” when using item on invalid target
  • Stack quantity display with OSRS-style formatting
  • Coin pouch separate display
  • Cancel option always shown last in context menus
  • Performance optimizations: useMemo and useCallback for render efficiency

Context Menus

Manifest-Driven Actions

Items define their actions in items.json:

Action Ordering

Actions are ordered by priority (first action is left-click default):

InventoryActionDispatcher

The InventoryActionDispatcher is the single source of truth for handling inventory actions. It eliminates duplication between context menu selections and left-click primary actions:
Testing: The dispatcher has 333 lines of comprehensive unit tests covering all action types, error handling, and edge cases.


Equipment Interaction Optimizations

Stackable Equipment Merge (PR #887)

When equipping stackable items (like arrows) that match what’s already equipped, the system now merges quantities directly instead of unequipping and re-equipping:
Benefits:
  • Eliminates brief inventory flash showing combined arrow count before equip
  • Reduces network packets and database writes
  • Improves responsiveness when equipping arrows
  • Maintains transaction safety (lock still held throughout)
Example Scenario:
  1. Player has 50 bronze arrows equipped
  2. Player equips 100 bronze arrows from inventory
  3. Old behavior: Unequip 50 → inventory shows 150 → equip 150 (visible flash)
  4. New behavior: Directly merge to 150 equipped (no flash)