Skip to main content

TierDataProvider

Provides OSRS-accurate tier-based level requirements for equipment and tools. Single source of truth loaded from tier-requirements.json manifest. Location: packages/shared/src/data/TierDataProvider.ts

Overview

TierDataProvider eliminates redundant requirement definitions by mapping metal tiers (bronze, iron, steel, etc.) to skill requirements. Instead of manually specifying requirements for every item, you just set the tier property.

Before (Manual Requirements)

{
  "id": "steel_sword",
  "name": "Steel Sword",
  "requirements": {
    "skills": {
      "attack": 5
    }
  }
}

After (Tier-Based)

{
  "id": "steel_sword",
  "name": "Steel Sword",
  "tier": "steel",
  "equipSlot": "weapon",
  "attackType": "MELEE"
  // requirements auto-derived from tier
}

Initialization

import { TierDataProvider, loadTierRequirements } from '@hyperscape/shared';

// Load tier requirements (called by DataManager)
const manifest = await fetch('/assets/manifests/tier-requirements.json');
loadTierRequirements(await manifest.json());

// Check if loaded
if (TierDataProvider.isLoaded()) {
  console.log("Tier data ready");
}

Methods

getRequirements

Get requirements for an item based on its tier.
getRequirements(item: TierableItem): TierRequirements | null
Parameters:
interface TierableItem {
  id: string;
  type: string;
  tier?: string;
  equipSlot?: string;
  attackType?: string;
  requirements?: {
    level?: number;
    skills?: Record<string, number>;
  };
  tool?: {
    skill: "woodcutting" | "mining" | "fishing";
    priority: number;
    rollTicks?: number;
  };
}
Returns:
interface TierRequirements {
  attack?: number;
  defence?: number;
  ranged?: number;
  magic?: number;
  woodcutting?: number;
  mining?: number;
  fishing?: number;
}
Example:
const item = {
  id: "steel_sword",
  type: "weapon",
  tier: "steel",
  equipSlot: "weapon",
  attackType: "MELEE"
};

const requirements = TierDataProvider.getRequirements(item);
// Returns: { attack: 5 }

getTierData

Get raw tier data for a specific category and tier.
getTierData(
  category: "melee" | "tools" | "ranged" | "magic",
  tier: string
): MeleeTierData | ToolTierData | RangedTierData | MagicTierData | null
Example:
const steelMelee = TierDataProvider.getTierData("melee", "steel");
// Returns: { attack: 5, defence: 5 }

const steelTools = TierDataProvider.getTierData("tools", "steel");
// Returns: { attack: 5, woodcutting: 6, mining: 6 }

getAvailableTiers

Get all available tiers for a category.
getAvailableTiers(category: "melee" | "tools" | "ranged" | "magic"): string[]
Example:
const meleeTiers = TierDataProvider.getAvailableTiers("melee");
// Returns: ["bronze", "iron", "steel", "black", "mithril", "adamant", "rune", "dragon"]

isLoaded

Check if tier data is loaded.
isLoaded(): boolean

reset

Reset tier data (for testing).
reset(): void

Tier Categories

Melee Equipment

Weapons and armor that use Attack/Defence requirements.
interface MeleeTierData {
  attack: number;
  defence: number;
}
Tiers:
TierAttackDefence
Bronze11
Iron11
Steel55
Black1010
Mithril2020
Adamant3030
Rune4040
Dragon6060

Tools

Hatchets and pickaxes that use Attack + skill requirements.
interface ToolTierData {
  attack: number;
  woodcutting: number;
  mining: number;
}
Tiers:
TierAttackWoodcuttingMining
Bronze111
Iron111
Steel566
Black101111
Mithril202121
Adamant303131
Rune404141
Dragon606161
Fishing tools don’t use the tier system - they have explicit requirements in the item definition.

Ranged Equipment

Bows and ranged armor that use Ranged/Defence requirements.
interface RangedTierData {
  ranged: number;
  defence: number;
}
Tiers:
TierRangedDefence
Leather11
Hardleather1010
Studded2020
Green D’hide4040
Blue D’hide5050
Red D’hide6060
Black D’hide7070

Magic Equipment

Staffs and robes that use Magic/Defence requirements.
interface MagicTierData {
  magic: number;
  defence?: number;
}
Tiers:
TierMagicDefence
Basic1-
Wizard1010
Mystic4020
Infinity5025
Ancestral7565

Requirement Precedence

TierDataProvider follows this order when determining requirements:
  1. Explicit requirements (item.requirements.skills) - Highest priority
  2. Tier-derived requirements (from tier-requirements.json)
  3. null (no requirements)
This allows special items (Barrows, quest rewards) to override tier-based requirements.

Manifest Structure

File: packages/server/world/assets/manifests/tier-requirements.json
{
  "melee": {
    "bronze": { "attack": 1, "defence": 1 },
    "iron": { "attack": 1, "defence": 1 },
    "steel": { "attack": 5, "defence": 5 },
    "mithril": { "attack": 20, "defence": 20 },
    "adamant": { "attack": 30, "defence": 30 },
    "rune": { "attack": 40, "defence": 40 },
    "dragon": { "attack": 60, "defence": 60 }
  },
  "tools": {
    "bronze": { "attack": 1, "woodcutting": 1, "mining": 1 },
    "iron": { "attack": 1, "woodcutting": 1, "mining": 1 },
    "steel": { "attack": 5, "woodcutting": 6, "mining": 6 },
    "mithril": { "attack": 20, "woodcutting": 21, "mining": 21 },
    "adamant": { "attack": 30, "woodcutting": 31, "mining": 31 },
    "rune": { "attack": 40, "woodcutting": 41, "mining": 41 },
    "dragon": { "attack": 60, "woodcutting": 61, "mining": 61 }
  },
  "ranged": {
    "leather": { "ranged": 1, "defence": 1 },
    "hardleather": { "ranged": 10, "defence": 10 },
    "studded": { "ranged": 20, "defence": 20 },
    "green_dhide": { "ranged": 40, "defence": 40 },
    "red_dhide": { "ranged": 60, "defence": 60 },
    "black_dhide": { "ranged": 70, "defence": 70 }
  },
  "magic": {
    "basic": { "magic": 1 },
    "wizard": { "magic": 10, "defence": 10 },
    "mystic": { "magic": 40, "defence": 20 },
    "infinity": { "magic": 50, "defence": 25 }
  }
}

Integration with DataManager

DataManager uses TierDataProvider during item normalization:
// From DataManager.normalizeItem()
if (!requirements && item.tier && TierDataProvider.isLoaded()) {
  const tierableItem: TierableItem = {
    id: item.id,
    type: item.type,
    tier: item.tier,
    equipSlot: equipSlot || undefined,
    attackType: attackType || undefined,
    tool: item.tool,
  };
  
  const derived = TierDataProvider.getRequirements(tierableItem);
  if (derived) {
    // Calculate level as max of all skill requirements
    const level = Math.max(1, ...Object.values(derived).filter(
      (v): v is number => typeof v === "number"
    ));
    
    requirements = {
      level,
      skills: derived,
    };
  }
}
This automatically populates item.requirements from the tier system.