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
| Tier | Attack | Defence |
|---|
| bronze/iron | 1 | 1 |
| steel | 5 | 5 |
| black | 10 | 10 |
| mithril | 20 | 20 |
| adamant | 30 | 30 |
| rune | 40 | 40 |
| dragon | 60 | 60 |
| Tier | Attack | Woodcutting | Mining |
|---|
| bronze/iron | 1 | 1 | 1 |
| steel | 5 | 6 | 6 |
| mithril | 20 | 21 | 21 |
| adamant | 30 | 31 | 31 |
| rune | 40 | 41 | 41 |
| dragon | 60 | 61 | 61 |
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 Type | Speed (ms) | Speed (ticks) |
|---|
| Dagger | 1800 | 3 |
| Scimitar | 1800 | 3 |
| Sword | 2400 | 4 |
| Longsword | 2400 | 4 |
| Battleaxe | 3000 | 5 |
| 2H Sword | 3600 | 6 |
| Shortbow | 1800 | 3 |
| Longbow | 3000 | 5 |
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
}
}
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 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
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
Add entry with proper structure
Follow the examples above. For tiered equipment, specify the tier field instead of hardcoding requirements.
Create 3D Model (optional)
Generate model in 3D Asset Forge if needed
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.
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 Type | Actions |
|---|
| 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:
| Slot | Item Types | Examples |
|---|
weapon | Swords, axes, bows, staffs | Bronze sword, Steel hatchet, Willow bow |
shield | Shields, defenders | Bronze kiteshield, Rune kiteshield |
helmet | Helmets, hats, hoods | Bronze full helm, Wizard hat, Leather cowl, Coif |
body | Platebodies, chainbodies, robes | Bronze platebody, Leather body, Wizard robe top, Studded body |
legs | Platelegs, chainlegs, skirts | Bronze platelegs, Leather chaps, Wizard robe bottom |
boots | Boots | Leather boots, Bronze boots, Iron boots, Steel boots, Mithril boots, Adamant boots, Rune boots, Wizard boots |
gloves | Gloves, bracers, vambraces | Leather gloves, Bronze gloves, Iron gloves, Steel gloves, Mithril gloves, Adamant gloves, Rune gloves, Leather vambraces, Green d’hide vambraces, Mystic gloves |
cape | Capes, cloaks | Cape, Black cape, Obsidian cape |
ring | Rings | Gold ring, Warrior ring, Berserker ring, Archer’s ring, Seer’s ring |
amulet | Amulets, necklaces | Amulet of accuracy, Amulet of strength, Amulet of power, Amulet of glory, Amulet of fury |
ammo | Arrows, bolts, runes | Bronze 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.