Skip to main content

Item Data Structure

Items are defined in JSON manifests and loaded at runtime. Items are now organized into separate files by type for better maintainability.
Item data is managed in packages/shared/src/data/items.ts and loaded from world/assets/manifests/items/ directory.

Data Loading

Items are NOT hardcoded. The ITEMS map is populated at runtime from multiple manifest files:
// From items.ts
export const ITEMS: Map<string, Item> = new Map();

// Populated by DataManager from JSON files
DataManager.loadItems(); // Reads all files in world/assets/manifests/items/

Manifest Organization

Items are split into separate files by category:
manifests/items/
├── weapons.json      # Combat weapons (swords, axes, etc.)
├── armor.json        # Armor pieces (helmets, bodies, legs, boots, gloves, shields, capes, amulets, rings)
├── tools.json        # Skilling tools (hatchets, pickaxes, fishing rods)
├── resources.json    # Gathered materials (ores, logs, bars, raw fish)
├── food.json         # Cooked consumables
└── misc.json         # Currency, burnt food, junk items

Directory-Based Loading

Items are organized by category in packages/server/world/assets/manifests/items/:
  • weapons.json - Swords, axes, bows, etc.
  • armor.json - Helmets, bodies, legs, boots, gloves, shields, capes, amulets, rings
  • tools.json - Hatchets, pickaxes, fishing rods, hammer, tinderbox
  • resources.json - Ores, bars, logs, raw fish
  • food.json - Cooked food, raw food, burnt food
  • misc.json - Coins, junk items, quest items
Atomic Loading: All 6 category files must exist or the system falls back to legacy items.json. Duplicate Detection: The loader validates that no item ID appears in multiple category files.

Item Schema

Each item has the following structure:
interface Item {
  id: string;                    // Unique identifier (e.g., "bronze_sword")
  name: string;                  // Display name (e.g., "Bronze Sword")
  type: ItemType;                // Category
  tier?: string;                 // Equipment tier (bronze, steel, mithril, etc.)
  rarity: ItemRarity;            // Common, Uncommon, Rare, etc.
  modelPath?: string;            // Path to GLB model (for ground/inventory display)
  equippedModelPath?: string;    // Path to aligned GLB model (for equipped display)

  // Inventory properties
  stackable: boolean;            // Can stack in inventory
  tradeable: boolean;            // Can be traded/sold
  value: number;                 // Base value in coins

  // Equipment properties (for weapons/armor)
  requirements?: ItemRequirement;
  bonuses?: ItemStats;           // Renamed from stats
  attackType?: AttackType;
  weaponType?: WeaponType;
  equipSlot?: EquipmentSlotName; // Where item equips
  attackSpeed?: number;          // In milliseconds
  range?: number;                // Attack range
  equipSlot?: string;            // Equipment slot (weapon, shield, head, body, etc.)

  // Tool properties (for gathering tools)
  tool?: {
    skill: "woodcutting" | "mining" | "fishing";
    priority: number;            // Higher = better tool
    rollTicks?: number;          // Mining: ticks between roll attempts
  };

  // OSRS-accurate inventory actions
  inventoryActions?: string[];   // Context menu actions (e.g., ["Eat", "Use", "Drop"])

  // Tool properties (for gathering tools)
  tool?: {
    skill: "woodcutting" | "mining" | "fishing";
    priority: number;            // Higher priority tools used first
    rollTicks?: number;          // Ticks between gather attempts
  };

  // Processing properties (embedded recipe data)
  cooking?: CookingRecipeData;
  firemaking?: FiremakingRecipeData;
  smelting?: SmeltingRecipeData;
  smithing?: SmithingRecipeData;

  // Noted item support
  isNoted?: boolean;
  baseItemId?: string;           // For noted items
  notedItemId?: string;          // For base items

  // Processing properties
  cooking?: CookingData;         // For raw food items
  firemaking?: FiremakingData;   // For logs
  smelting?: SmeltingData;       // For bars
  smithing?: SmithingData;       // For smithable items
}

Tier-Based Requirements

Items with a tier property automatically derive level requirements from tier-requirements.json:
{
  "id": "steel_sword",
  "name": "Steel Sword",
  "type": "weapon",
  "tier": "steel",
  "equipSlot": "weapon",
  "attackType": "MELEE"
  // requirements auto-derived: { attack: 5 }
}
The TierDataProvider maps tiers to skill requirements, eliminating redundant requirement definitions.

Item Types

type ItemType =
  | "weapon"      // Swords, axes, bows → weapons.json
  | "armor"       // Helmets, bodies, legs, boots, gloves, shields, capes, amulets, rings → armor.json
  | "tool"        // Hatchets, pickaxes, fishing rods → tools.json
  | "consumable"  // Food, potions → food.json
  | "resource"    // Logs, ore, fish, bars → resources.json
  | "currency"    // Coins → misc.json
  | "junk"        // Burnt food → misc.json
  | "misc";       // Other items → misc.json

Tier-Based Equipment System

Items now use a centralized tier system defined in tier-requirements.json. Instead of hardcoding level requirements in each item, equipment references a tier:
{
  "id": "bronze_sword",
  "name": "Bronze Sword",
  "type": "weapon",
  "tier": "bronze",  // References tier-requirements.json
  // ... other properties
}
The system automatically looks up requirements from tier-requirements.json:

Melee Tiers

TierAttackDefence
bronze/iron11
steel55
black1010
mithril2020
adamant3030
rune4040
dragon6060

Tool Tiers

TierAttackWoodcuttingMining
bronze/iron111
steel566
mithril202121
adamant303131
rune404141
dragon606161
This centralized approach ensures OSRS-accurate requirements and makes it easy to add new tiers without modifying individual items.

Item Rarity

type ItemRarity =
  | "common"
  | "uncommon"
  | "rare"
  | "epic"
  | "legendary";

Equipment Stats

For weapons and armor:
interface ItemStats {
  attack?: number;       // Attack bonus
  strength?: number;     // Strength bonus (max hit)
  defense?: number;      // Defense bonus
  ranged?: number;       // Ranged attack bonus
  rangedStrength?: number; // Ranged damage bonus
  prayer?: number;       // Prayer bonus
}

Skill Requirements

Items can require skill levels to use:
interface ItemRequirement {
  attack?: number;
  strength?: number;
  defense?: number;
  ranged?: number;
  woodcutting?: number;
  mining?: number;
  fishing?: number;
}

Weapon Types

type WeaponType =
  | "sword"
  | "scimitar"
  | "longsword"
  | "dagger"
  | "axe"
  | "mace"
  | "warhammer"
  | "2h"
  | "bow"
  | "crossbow"
  | "staff";

type AttackType = "melee" | "ranged" | "magic";

Attack Speed

Weapons have different attack speeds:
Weapon TypeSpeed (ms)Speed (ticks)
Dagger18003
Scimitar18003
Sword24004
Longsword24004
Battleaxe30005
2H Sword36006
Shortbow18003
Longbow30005

Helper Functions

Item Type Detection

The item-helpers.ts module provides utilities for detecting item types:
// From item-helpers.ts
export function isFood(item: Item | null): boolean {
  if (!item) return false;
  return (
    item.type === "consumable" &&
    typeof item.healAmount === "number" &&
    item.healAmount > 0 &&
    !item.id.includes("potion")
  );
}

export function isPotion(item: Item | null): boolean {
  if (!item) return false;
  return item.type === "consumable" && item.id.includes("potion");
}

export function isBone(item: Item | null): boolean {
  if (!item) return false;
  return item.id === "bones" || item.id.endsWith("_bones");
}

export function isWeapon(item: Item | null): boolean {
  if (!item) return false;
  return (
    item.equipSlot === "weapon" ||
    item.equipSlot === "2h" ||
    item.is2h === true ||
    item.weaponType != null
  );
}

export function isShield(item: Item | null): boolean {
  if (!item) return false;
  return item.equipSlot === "shield";
}

export function usesWield(item: Item | null): boolean {
  return isWeapon(item) || isShield(item);
}

export function usesWear(item: Item | null): boolean {
  if (!item) return false;
  if (!item.equipable && !item.equipSlot) return false;
  return !usesWield(item);
}

export function isNotedItem(item: Item | null): boolean {
  if (!item) return false;
  return item.isNoted === true || item.id.endsWith("_noted");
}

Primary Action Detection

// Get primary action for left-click
export function getPrimaryAction(
  item: Item | null,
  isNoted: boolean,
): PrimaryActionType {
  if (isNoted) return "use";

  // Check manifest first
  const manifestAction = getPrimaryActionFromManifest(item);
  if (manifestAction) return manifestAction;

  // Fallback to heuristic detection
  if (isFood(item)) return "eat";
  if (isPotion(item)) return "drink";
  if (isBone(item)) return "bury";
  if (usesWield(item)) return "wield";
  if (usesWear(item)) return "wear";

  return "use";
}

Get Item by ID

export function getItem(itemId: string): Item | null {
  return ITEMS.get(itemId) || null;
}

Get Items by Type

export function getItemsByType(type: ItemType): Item[] {
  return Array.from(ITEMS.values()).filter((item) => item.type === type);
}

// Convenience functions
export function getWeapons(): Item[] {
  return getItemsByType("weapon");
}

export function getArmor(): Item[] {
  return getItemsByType("armor");
}

export function getTools(): Item[] {
  return getItemsByType("tool");
}

export function getConsumables(): Item[] {
  return getItemsByType("consumable");
}

export function getResources(): Item[] {
  return getItemsByType("resource");
}

Get Items by Skill Requirement

export function getItemsBySkill(skill: string): Item[] {
  return Array.from(ITEMS.values()).filter(
    (item) => item.requirements && item.requirements[skill as keyof ItemRequirement]
  );
}

Get Items by Level Requirement

export function getItemsByLevel(level: number): Item[] {
  return Array.from(ITEMS.values()).filter((item) => {
    if (!item.requirements) return true;
    return Object.values(item.requirements).every((req) =>
      typeof req === "number" ? req <= level : true
    );
  });
}

Shop Items

Items available in general stores:
export const SHOP_ITEMS = [
  "bronze_hatchet",
  "fishing_rod",
  "tinderbox",
  "arrows",
];

Noted Items

Items can have “noted” variants for efficient storage:
// Check if item can be noted
export function canBeNoted(itemId: string): boolean {
  const item = ITEMS.get(itemId);
  return item?.stackable === false && item?.notedItemId !== undefined;
}

// Get base item from noted item
export function getBaseItem(itemId: string): Item | null {
  const item = ITEMS.get(itemId);
  if (!item) return null;

  if (item.isNoted && item.baseItemId) {
    return ITEMS.get(item.baseItemId) || null;
  }
  return item;
}

// Get noted variant of item
export function getNotedItem(itemId: string): Item | null {
  const item = ITEMS.get(itemId);
  if (!item || item.isNoted) return null;

  if (item.notedItemId) {
    return ITEMS.get(item.notedItemId) || null;
  }
  return null;
}

// Check if item ID is a noted variant
export function isNotedItemId(itemId: string): boolean {
  return itemId.endsWith("_noted");
}

// Get base item ID from noted ID
export function getBaseItemId(itemId: string): string {
  if (isNotedItemId(itemId)) {
    return itemId.replace(/_noted$/, "");
  }
  return itemId;
}

Example Item Definitions

Weapon (weapons.json)

{
  "id": "bronze_sword",
  "name": "Bronze Sword",
  "type": "weapon",
  "tier": "bronze",
  "value": 100,
  "weight": 2,
  "equipSlot": "weapon",
  "weaponType": "SWORD",
  "attackType": "MELEE",
  "attackSpeed": 4,
  "attackRange": 1,
  "description": "A basic sword made of bronze",
  "examine": "A basic sword made of bronze",
  "tradeable": true,
  "rarity": "common",
  "modelPath": "asset://models/sword-bronze/sword-bronze.glb",
  "equippedModelPath": "asset://models/sword-steel/sword-steel-aligned.glb",
  "iconPath": "asset://models/sword-bronze/concept-art.png",
  "bonuses": {
    "attack": 4,
    "strength": 3,
    "defense": 0,
    "ranged": 0
  }
}

Tool (tools.json)

Tools include a tool object specifying the skill and priority. The tier system automatically derives level requirements:
{
  "id": "bronze_hatchet",
  "name": "Bronze Hatchet",
  "type": "tool",
  "tier": "bronze",
  "tool": {
    "skill": "woodcutting",
    "priority": 1
  },
  "value": 50,
  "weight": 1,
  "equipSlot": "weapon",
  "weaponType": "AXE",
  "attackType": "MELEE",
  "attackSpeed": 5,
  "attackRange": 1,
  "description": "A basic hatchet for chopping trees",
  "examine": "A basic hatchet for chopping trees",
  "tradeable": true,
  "rarity": "common",
  "modelPath": "asset://models/hatchet-bronze/hatchet-bronze.glb",
  "iconPath": "asset://models/hatchet-bronze/concept-art.png",
  "bonuses": {
    "attack": 4,
    "strength": 3,
    "defense": 0,
    "ranged": 0
  }
}
The tier: "bronze" automatically gives this tool attack: 1 and woodcutting: 1 requirements from tier-requirements.json. Tools with equipSlot: "weapon" can be equipped and used for combat.

Resource (resources.json)

{
  "id": "copper_ore",
  "name": "Copper Ore",
  "type": "resource",
  "stackable": false,
  "maxStackSize": 100,
  "value": 5,
  "weight": 2,
  "description": "Copper ore that can be smelted into a bronze bar",
  "examine": "Ore containing copper. Can be combined with tin to make bronze.",
  "tradeable": true,
  "rarity": "common",
  "modelPath": null,
  "iconPath": "asset://icons/ore-copper.png"
}

Consumable (food.json)

Consumables can include inventoryActions to define OSRS-style context menu actions:
{
  "id": "shrimp",
  "name": "Shrimp",
  "type": "consumable",
  "stackable": false,
  "value": 10,
  "weight": 0.2,
  "description": "Some nicely cooked shrimp",
  "examine": "Some nicely cooked shrimp.",
  "tradeable": true,
  "rarity": "common",
  "modelPath": null,
  "iconPath": "asset://icons/shrimp.png",
  "healAmount": 3,
  "inventoryActions": ["Eat", "Use", "Drop", "Examine"]
}
The first action in inventoryActions becomes the left-click default. If not specified, the system uses heuristic detection based on item properties (food → Eat, potions → Drink, etc.).

Context Menu Color Coding

Context menus use OSRS-accurate color coding for entity names:
// From GameConstants.ts
export const CONTEXT_MENU_COLORS = {
  ITEM: "#ff9040",    // Orange for item names
  NPC: "#ffff00",     // Yellow for NPC names
  OBJECT: "#00ffff",  // Cyan for scenery/objects
  PLAYER: "#ffffff",  // White for player names
} as const;
Examples:
  • “Eat Shrimp” (orange)
  • “Attack Goblin” (yellow)
  • “Mine Copper rocks” (cyan)
  • “Trade Shop keeper” (yellow)

Currency (misc.json)

{
  "id": "coins",
  "name": "Coins",
  "type": "currency",
  "stackable": true,
  "maxStackSize": 2147483647,
  "value": 1,
  "weight": 0,
  "description": "The universal currency of Hyperia",
  "examine": "Gold coins used as currency throughout the realm",
  "tradeable": true,
  "rarity": "always",
  "modelPath": null,
  "iconPath": "asset://icons/coins.png"
}

Adding New Items

1

Choose the right manifest file

  • Weapons → items/weapons.json
  • Armor → items/armor.json
  • Tools → items/tools.json
  • Resources (ores, logs, bars, raw fish) → items/resources.json
  • Food → items/food.json
  • Currency, junk → items/misc.json
2

Add entry with proper structure

Follow the examples above. For tiered equipment, specify the tier field instead of hardcoding requirements.
3

Create 3D Model (optional)

Generate model in 3D Asset Forge if needed
4

Restart Server

Server must restart to reload manifests
DO NOT add item data directly to items.ts. Keep all content in JSON manifests for data-driven design.

Tool Priority System

Tools use a priority system to determine which tool to use when multiple are available:
{
  "tool": {
    "skill": "woodcutting",
    "priority": 1  // Higher = better tool
  }
}
For example:
  • Bronze hatchet: priority 1
  • Iron hatchet: priority 2
  • Steel hatchet: priority 3
  • Rune hatchet: priority 6
The system automatically selects the highest priority tool the player has equipped or in inventory.

Inventory Actions System

Items can define explicit context menu actions using the inventoryActions array. This is the OSRS-accurate approach where actions are stored per-item in the manifest.
{
  "id": "bronze_sword",
  "inventoryActions": ["Wield", "Use", "Drop", "Examine"]
}
Key Features:
  • First action becomes the left-click default
  • Actions appear in context menu in the order specified
  • “Cancel” is always added automatically as the last option
  • Manifest-defined actions take priority over heuristic detection

Common Action Patterns

Item TypeActions
Food["Eat", "Use", "Drop", "Examine"]
Potions["Drink", "Use", "Drop", "Examine"]
Weapons["Wield", "Use", "Drop", "Examine"]
Armor["Wear", "Use", "Drop", "Examine"]
Bones["Bury", "Use", "Drop", "Examine"]
Generic["Use", "Drop", "Examine"]

Action Handlers

The following actions have built-in handlers in InventoryActionDispatcher:
  • Eat: Sends useItem packet → server validates eat delay (3 ticks) → consumes food → heals player
  • Drink: Sends useItem packet → server validates → applies potion effects
  • Wield: Sends equipItem network message (weapons/shields)
  • Wear: Sends equipItem network message (armor)
  • Bury: Sends buryBones network message
  • Use: Enters targeting mode for item-on-item/item-on-object interactions
  • Drop: Calls world.network.dropItem()
  • Examine: Shows examine text in chat and toast
  • Cancel: Closes context menu (always added automatically)

Heuristic Fallback

If inventoryActions is not specified, the system uses type detection helpers from item-helpers.ts:
// From packages/shared/src/utils/item-helpers.ts
export function getPrimaryAction(item: Item | null, isNoted: boolean): PrimaryActionType {
  if (isNoted) return "use";
  
  const manifestAction = getPrimaryActionFromManifest(item);
  if (manifestAction) return manifestAction;
  
  // Fallback to heuristic detection
  if (isFood(item)) return "eat";
  if (isPotion(item)) return "drink";
  if (isBone(item)) return "bury";
  if (usesWield(item)) return "wield";
  if (usesWear(item)) return "wear";
  
  return "use";
}
Detection Rules:
  • Food: type: "consumable" + healAmount > 0 + not potion
  • Potions: type: "consumable" + id.includes("potion")
  • Weapons: equipSlot: "weapon" or equipSlot: "2h" or weaponType defined
  • Shields: equipSlot: "shield"
  • Armor: equipable: true + not weapon/shield
  • Bones: id === "bones" or id.endsWith("_bones")
  • Noted Items: Always use “use” action (cannot eat/equip noted items)
Manifest-defined inventoryActions take priority over heuristic detection. This allows custom actions for special items.

Equipment Slots

Items equip to specific slots:
SlotItem TypesExamples
weaponSwords, axes, bows, staffsBronze sword, Steel hatchet, Willow bow
shieldShields, defendersBronze kiteshield, Rune kiteshield
helmetHelmets, hats, hoodsBronze full helm, Wizard hat, Leather cowl, Coif
bodyPlatebodies, chainbodies, robesBronze platebody, Leather body, Wizard robe top, Studded body
legsPlatelegs, chainlegs, skirtsBronze platelegs, Leather chaps, Wizard robe bottom
bootsBootsLeather boots, Bronze boots, Iron boots, Steel boots, Mithril boots, Adamant boots, Rune boots, Wizard boots
glovesGloves, bracers, vambracesLeather gloves, Bronze gloves, Iron gloves, Steel gloves, Mithril gloves, Adamant gloves, Rune gloves, Leather vambraces, Green d’hide vambraces, Mystic gloves
capeCapes, cloaksCape, Black cape, Obsidian cape
ringRingsGold ring, Warrior ring, Berserker ring, Archer’s ring, Seer’s ring
amuletAmulets, necklacesAmulet of accuracy, Amulet of strength, Amulet of power, Amulet of glory, Amulet of fury
ammoArrows, bolts, runesBronze arrows, Iron arrows
The game now includes full armor sets across all equipment slots. Melee armor (bronze through rune) includes helmets, platebodies, platelegs, boots, gloves, and shields. Ranged armor includes leather and dragonhide pieces. Magic armor includes wizard and mystic robes with boots and gloves.