Skills & Progression
Hyperscape features a skill-based progression system where skills level independently through use. The XP formula and combat level calculation are authentic recreations of Old School RuneScape mechanics.
Skills are managed by SkillsSystem in packages/shared/src/systems/shared/character/SkillsSystem.ts.
Available Skills
Combat Skills
| Skill | Description | Training Methods |
|---|
| Attack | Melee accuracy | Combat (Accurate style) |
| Strength | Melee max hit | Combat (Aggressive style) |
| Defense | Damage reduction | Combat (Defensive style) |
| Constitution | Maximum HP | All combat (always granted) |
| Ranged | Ranged combat | Bow/crossbow attacks |
| Magic | Spellcasting | Casting combat spells |
| Prayer | Combat bonuses, protection | Burying bones |
Gathering Skills
| Skill | Description | Training Methods |
|---|
| Woodcutting | Chop trees | Using hatchet on trees |
| Mining | Mine ore | Using pickaxe on rocks |
| Fishing | Catch fish | Using fishing rod at spots |
Support Skills
| Skill | Description | Training Methods |
|---|
| Agility | Movement and shortcuts | Agility courses (future) |
Artisan Skills
| Skill | Description | Training Methods |
|---|
| Firemaking | Light fires | Using tinderbox on logs |
| Cooking | Cook food | Using raw food on fires/ranges |
| Smithing | Smelt bars and smith equipment | Smelting ores at furnaces, smithing bars at anvils |
| Crafting | Create leather armor, dragonhide, jewelry, cut gems | Using needle/chisel/furnace on materials |
| Fletching | Create bows and arrows | Using knife on logs, item-on-item for arrows/stringing |
| Runecrafting | Convert essence into runes | Clicking altars with essence in inventory |
Smithing is a two-step process: first smelt ores into bars at a furnace, then smith bars into equipment at an anvil. Smithing now supports outputQuantity for multi-output recipes (e.g., 15 arrowtips per bar).
Crafting uses thread (5 uses per item) for leather/dragonhide armor. Tanning converts hides to leather at tanner NPCs for a coin fee. Crafting features auto-select when only one recipe matches the input item.
Fletching produces multiple items per action (e.g., 15 arrow shafts per log). Quantity selection refers to actions, not output items. Fletching UI groups recipes by category for easier navigation.
Runecrafting is instant (no tick delay). Multi-rune multipliers grant additional runes per essence at higher levels (e.g., 2x air runes at level 11). Each altar is tied to a specific rune type and features mystical particle effects.
Prayer Training
Prayer is trained by burying bones dropped from combat:
| Bone Type | XP | Source |
|---|
| Bones | 4.5 | Most monsters |
| Big Bones | 15 | Larger monsters |
| Dragon Bones | 72 | Dragons |
Bury Mechanics:
- Right-click bones in inventory → “Bury”
- 2-tick (1.2 second) delay between burials
- XP granted immediately on successful bury
- Bones consumed after burying
See Prayer System for complete prayer mechanics, drain formulas, and altar usage.
The XP table uses the authentic RuneScape formula. Level 92 represents the halfway point to 99 in total XP.
// From SkillsSystem.ts
private generateXPTable(): void {
this.xpTable = [0, 0]; // Levels 0 and 1
for (let level = 2; level <= 99; level++) {
const xp = Math.floor(level - 1 + 300 * Math.pow(2, (level - 1) / 7)) / 4;
this.xpTable.push(Math.floor(this.xpTable[level - 1] + xp));
}
}
XP Requirements by Level
| Level | Total XP | XP to Next |
|---|
| 1 | 0 | 83 |
| 10 | 1,154 | 229 |
| 20 | 4,470 | 899 |
| 30 | 13,363 | 2,673 |
| 40 | 37,224 | 7,195 |
| 50 | 101,333 | 18,389 |
| 60 | 273,742 | 46,236 |
| 70 | 737,627 | 115,590 |
| 80 | 1,986,068 | 287,883 |
| 90 | 5,346,332 | 716,427 |
| 92 | 6,517,253 | ~50% of 99 XP |
| 99 | 13,034,431 | MAX |
XP Cap: Maximum XP per skill is 200,000,000 (200M). This matches OSRS.
Level Calculations
Get Level from XP
public getLevelForXP(xp: number): number {
for (let level = 99; level >= 1; level--) {
if (xp >= this.xpTable[level]) {
return level;
}
}
return 1;
}
XP to Next Level
public getXPToNextLevel(skill: SkillData): number {
if (skill.level >= 99) return 0;
const nextLevelXP = this.getXPForLevel(skill.level + 1);
return nextLevelXP - skill.xp;
}
Progress Percentage
public getXPProgress(skill: SkillData): number {
if (skill.level >= 99) return 100;
const currentLevelXP = this.getXPForLevel(skill.level);
const nextLevelXP = this.getXPForLevel(skill.level + 1);
const progressXP = skill.xp - currentLevelXP;
const requiredXP = nextLevelXP - currentLevelXP;
return (progressXP / requiredXP) * 100;
}
Combat Level
Combat level is calculated from combat skills using the OSRS formula. Magic and Prayer are now included in the calculation.
// From SkillsSystem.ts
public getCombatLevel(stats: StatsComponent): number {
const defenseLevel = stats.defense?.level ?? 1;
const hitpointsLevel = stats.constitution?.level ?? 10;
const prayerLevel = stats.prayer?.level ?? 1;
const attackLevel = stats.attack?.level ?? 1;
const strengthLevel = stats.strength?.level ?? 1;
const rangedLevel = stats.ranged?.level ?? 1;
const magicLevel = stats.magic?.level ?? 1;
// Base = 0.25 × (Defense + Hitpoints + floor(Prayer / 2))
const base = 0.25 * (defenseLevel + hitpointsLevel + Math.floor(prayerLevel / 2));
// Melee = 0.325 × (Attack + Strength)
const melee = 0.325 * (attackLevel + strengthLevel);
// Ranged = 0.325 × floor(Ranged × 1.5)
const rangedCalc = 0.325 * Math.floor(rangedLevel * 1.5);
// Magic = 0.325 × floor(Magic × 1.5)
const magicCalc = 0.325 * Math.floor(magicLevel * 1.5);
// Combat Level = floor(Base + max(Melee, Ranged, Magic))
return Math.floor(base + Math.max(melee, rangedCalc, magicCalc));
}
Magic & Prayer Persistence: Magic and Prayer skills now persist to the database. Previously, these skills were not saved between sessions.
Combat Level Examples
| Stats | Combat Level |
|---|
| All 1s | 3 |
| 40 Atk/Str, 1 everything else | 32 |
| 60 Atk/Str/Def, 60 HP | 66 |
| 99 all combat | 126 |
Total Level
Total level is the sum of all skill levels.
public getTotalLevel(stats: StatsComponent): number {
let total = 0;
const skills = [
"attack", "strength", "defense", "constitution", "ranged", "magic", "prayer",
"woodcutting", "mining", "fishing", "firemaking", "cooking", "smithing",
"agility", "crafting", "fletching", "runecrafting"
];
for (const skill of skills) {
const skillData = stats[skill];
total += skillData?.level ?? 1;
}
return total;
}
Maximum Total Level
With 17 skills at level 99: 1,683 total level
New Skills Added: Crafting, Fletching, and Runecrafting are now fully implemented with OSRS-accurate mechanics, recipes, and XP formulas. All three skills persist to the database and integrate with the skill guide panel.
XP Sources
Combat XP
| Action | XP Per Damage |
|---|
| Main skill (style-dependent) | 4.0 |
| Constitution (always) | 1.33 |
| Controlled (per skill) | 1.33 |
Gathering XP
Gathering resources is now defined in manifests/gathering/ with OSRS-accurate XP values:
Woodcutting (gathering/woodcutting.json):
| Tree | Level | XP |
|---|
| Normal | 1 | 25 |
| Oak | 15 | 37.5 |
| Willow | 30 | 67.5 |
| Teak | 35 | 85 |
| Maple | 45 | 100 |
| Mahogany | 50 | 125 |
| Yew | 60 | 175 |
| Magic | 75 | 250 |
Mining (gathering/mining.json):
| Ore | Level | XP | Success Rate (L1 → L99) |
|---|
| Copper/Tin | 1 | 17.5 | 39.5% → 100% (at L62) |
| Iron | 15 | 35 | 52% (at L15) → 100% (at L63) |
| Coal | 30 | 50 | 16.4% (at L30) → 39.5% (at L99) |
| Mithril | 55 | 80 | 11.7% (at L55) → 19.9% (at L99) |
| Adamantite | 70 | 95 | 7.4% (at L70) → 10.2% (at L99) |
| Runite | 85 | 125 | 6.64% (at L85) → 7.42% (at L97+) |
Mining rocks always deplete after yielding one ore (OSRS-accurate). Success rate depends only on Mining level, not pickaxe tier. Pickaxe tier affects roll frequency (speed), not success chance.
Pickaxe Speed Bonuses
Dragon and Crystal pickaxes have a chance for bonus speed:
| Pickaxe | Base Speed | Bonus Chance | Bonus Speed | Average Speed |
|---|
| Bronze-Rune | 3-8 ticks | - | - | 3-8 ticks |
| Dragon | 3 ticks | 1/6 (16.7%) | 2 ticks | 2.83 ticks |
| Crystal | 3 ticks | 1/4 (25%) | 2 ticks | 2.75 ticks |
The bonus speed roll is server-authoritative to prevent client/server desyncs.
Mining Mechanics: Rocks always deplete after yielding one ore (100% depletion chance). Success rates are OSRS-accurate and depend only on Mining level, not pickaxe tier. Pickaxe tier affects roll frequency (speed) only. The bonus speed roll for Dragon/Crystal pickaxes is server-authoritative to prevent client/server desyncs.
Model Scale Normalization
Mining rocks (and all resources) now have normalized model scales to prevent “squished” or “stretched” appearance:
// From ModelCache.ts - normalizeScales()
// Detects and resets non-uniform scales in GLTF models
// Some 3D modeling tools export models with non-uniform scales on internal nodes
// (e.g., scale (2, 0.5, 1)) which causes distortion
scene.traverse((node) => {
const s = node.scale;
const isNonUniform =
Math.abs(s.x - s.y) > 0.001 ||
Math.abs(s.y - s.z) > 0.001 ||
Math.abs(s.x - s.z) > 0.001;
if (isNonUniform) {
node.scale.set(1, 1, 1); // Reset to identity
node.updateMatrix();
}
});
This normalization runs once when a model is first loaded, before caching. All clones will have correct proportions.
Fishing (gathering/fishing.json):
| Fish | Level | XP |
|---|
| Shrimp | 1 | 10 |
| Sardine | 5 | 20 |
| Herring | 10 | 30 |
| Trout | 20 | 50 |
| Pike | 25 | 60 |
| Salmon | 30 | 70 |
| Lobster | 40 | 90 |
| Swordfish | 50 | 100 |
| Shark | 76 | 110 |
Artisan XP
Processing recipes are defined in manifests/recipes/ with OSRS-accurate XP values:
Crafting (recipes/crafting.json):
| Item | Level | Materials | XP |
|---|
| Leather gloves | 1 | 1 leather + thread | 13.8 |
| Leather boots | 7 | 1 leather + thread | 16.3 |
| Leather body | 14 | 1 leather + thread | 25 |
| Coif | 18 | 1 leather + thread | 37 |
| Green d’hide vambraces | 57 | 1 green dragon leather + thread | 62 |
| Green d’hide body | 63 | 3 green dragon leather + thread | 186 |
| Gold ring | 5 | 1 gold bar + ring mould | 15 |
| Sapphire ring | 20 | 1 gold bar + sapphire + ring mould | 40 |
| Sapphire (cut) | 20 | 1 uncut sapphire + chisel | 50 |
| Emerald (cut) | 27 | 1 uncut emerald + chisel | 67.5 |
| Ruby (cut) | 34 | 1 uncut ruby + chisel | 85 |
| Diamond (cut) | 43 | 1 uncut diamond + chisel | 107.5 |
Thread has 5 uses before being consumed. Tanning converts hides to leather for a coin fee (no XP).
Fletching (recipes/fletching.json):
| Item | Level | Materials | XP | Output Qty |
|---|
| Arrow shaft | 1 | 1 logs + knife | 5 | 15 |
| Headless arrow | 1 | 15 arrow shafts + 15 feathers | 1 | 15 |
| Bronze arrow | 1 | 15 headless arrows + 15 bronze arrowtips | 1.3 | 15 |
| Iron arrow | 15 | 15 headless arrows + 15 iron arrowtips | 2.5 | 15 |
| Steel arrow | 30 | 15 headless arrows + 15 steel arrowtips | 5 | 15 |
| Shortbow (u) | 5 | 1 logs + knife | 5 | 1 |
| Shortbow | 5 | 1 shortbow (u) + 1 bowstring | 5 | 1 |
| Oak shortbow (u) | 20 | 1 oak logs + knife | 16.5 | 1 |
| Willow shortbow (u) | 35 | 1 willow logs + knife | 33.3 | 1 |
| Yew longbow (u) | 70 | 1 yew logs + knife | 75 | 1 |
| Yew longbow | 70 | 1 yew longbow (u) + 1 bowstring | 75 | 1 |
Fletching produces multiple items per action for arrows/shafts (15 per action). Arrowtips are created via Smithing (15 per bar).
Runecrafting (recipes/runecrafting.json):
| Rune | Level | XP/Essence | Multi-Rune Levels |
|---|
| Air | 1 | 5 | 11, 22, 33, 44, 55, 66, 77, 88, 99 |
| Mind | 2 | 5.5 | 14, 28, 42, 56, 70, 84, 98 |
| Water | 5 | 6 | 19, 38, 57, 76, 95 |
| Earth | 9 | 6.5 | 26, 52, 78 |
| Fire | 14 | 7 | 35, 70 |
| Body | 20 | 7.5 | 46, 92 |
| Cosmic | 27 | 8 | 59 |
| Chaos | 35 | 8.5 | 74 |
| Nature | 44 | 9 | - |
| Law | 54 | 9.5 | - |
| Death | 65 | 10 | - |
Runecrafting is instant (no tick delay). Multi-rune levels grant +1 rune per essence. Example: At level 22, you get 3 air runes per essence (base 1 + level 11 threshold + level 22 threshold).
Smelting (recipes/smelting.json):
| Bar | Level | Ores Required | XP | Success Rate |
|---|
| Bronze | 1 | 1 Copper + 1 Tin | 6.2 | 100% |
| Iron | 15 | 1 Iron | 12.5 | 50% |
| Steel | 30 | 1 Iron + 2 Coal | 17.5 | 100% |
| Mithril | 50 | 1 Mithril + 4 Coal | 30 | 100% |
| Adamant | 70 | 1 Adamantite + 6 Coal | 37.5 | 100% |
| Rune | 85 | 1 Runite + 8 Coal | 50 | 100% |
Iron ore has a 50% success rate when smelting - failed attempts consume the ore but grant no bar or XP.
Smithing (recipes/smithing.json):
Smithing requires a hammer in inventory and access to an anvil. Recipes are organized by category:
Weapons:
| Item | Level | Bars | XP |
|---|
| Bronze dagger | 1 | 1 | 12.5 |
| Bronze sword | 4 | 1 | 12.5 |
| Bronze scimitar | 5 | 2 | 25 |
| Iron sword | 19 | 1 | 25 |
| Steel sword | 34 | 1 | 37.5 |
| Mithril sword | 54 | 1 | 50 |
| Adamant sword | 74 | 1 | 62.5 |
| Rune sword | 89 | 1 | 75 |
Armor:
| Item | Level | Bars | XP |
|---|
| Bronze platebody | 18 | 5 | 62.5 |
| Iron platebody | 33 | 5 | 125 |
| Steel platebody | 48 | 5 | 187.5 |
| Mithril platebody | 68 | 5 | 250 |
Tools:
| Item | Level | Bars | XP |
|---|
| Bronze hatchet | 1 | 1 | 12.5 |
| Bronze pickaxe | 1 | 1 | 12.5 |
| Iron hatchet | 16 | 1 | 25 |
| Steel hatchet | 31 | 1 | 37.5 |
Smithing recipes support “Make X” functionality - players can smith multiple items in one session. The system remembers the last custom quantity entered.
Cooking (recipes/cooking.json):
| Food | Level | XP | Heals |
|---|
| Shrimp | 1 | 30 | 3 HP |
| Sardine | 1 | 40 | 3 HP |
| Herring | 5 | 50 | 5 HP |
| Trout | 15 | 70 | 7 HP |
| Pike | 20 | 80 | 8 HP |
| Salmon | 25 | 90 | 9 HP |
| Lobster | 40 | 120 | 12 HP |
| Swordfish | 45 | 140 | 14 HP |
| Shark | 80 | 210 | 20 HP |
Food Consumption Mechanics
Food consumption follows OSRS-accurate timing and combat integration:
Eating Delay:
- 3-tick (1.8s) cooldown between eating foods
- Managed by
EatDelayManager (per-player tracking)
- Food consumed only after validation passes
- Message shown even at full health: “You eat the shrimp.”
Combat Integration:
- Eating during combat adds 3-tick delay to attack cooldown
- OSRS rule: Only adds delay if weapon is already on cooldown
- If weapon is ready to attack, eating does NOT add delay
- Prevents eating from being used to delay attacks strategically
Server-Authoritative Flow:
Client → useItem packet → Server validates → INVENTORY_USE event →
InventorySystem validates item exists → ITEM_USED → PlayerSystem validates eat delay →
Consume item + heal + attack delay
Security Features:
- Rate limiting (3 requests/sec)
- Input validation (slot bounds, item ID mismatch detection)
- Heal amount capped at 99 HP (prevents manifest exploits)
- Server-side eat delay enforcement
Food is consumed even at full health (OSRS-accurate). The eat delay and attack delay still apply.
Firemaking (recipes/firemaking.json):
| Logs | Level | XP |
|---|
| Normal | 1 | 40 |
| Oak | 15 | 60 |
| Willow | 30 | 90 |
| Teak | 35 | 105 |
| Maple | 45 | 135 |
| Mahogany | 50 | 157.5 |
| Yew | 60 | 202.5 |
| Magic | 75 | 303.8 |
Firemaking Mechanics
OSRS-Accurate: Players must stand still while lighting fires. Movement during the lighting animation cancels the action.
Lighting Process:
- Player clicks logs with tinderbox (or tinderbox on logs)
- Player movement is stopped automatically
- Lighting animation plays (3 seconds)
- Fire spawns at the position where lighting began
- Logs are consumed, XP is granted
Movement Cancellation:
// From ProcessingSystem.ts
// Stop player movement before lighting fire (OSRS: player stands still to light)
this.tileMovementManager.stopPlayer(player.id);
// Cancel firemaking when the player moves during the lighting animation
private cancelFiremaking(playerId: string, action: ProcessingAction): void {
this.activeProcessing.delete(playerId);
this.releaseAction(action);
this.resetPlayerEmote(playerId);
this.emitTypedEvent(EventType.FIRE_LIGHTING_CANCELLED, { playerId });
this.emitTypedEvent(EventType.UI_MESSAGE, {
playerId,
message: "You move and stop trying to light the fire.",
type: "info",
});
}
Movement Detection:
The system monitors player position during the 3-second lighting animation:
// Movement threshold: 0.5 world units (~half a tile)
private static readonly FIREMAKING_MOVE_THRESHOLD_SQ = 0.25;
// Check for movement in update loop
const dx = player.node.position.x - action.startPosition.x;
const dz = player.node.position.z - action.startPosition.z;
const distSq = dx * dx + dz * dz;
if (distSq > ProcessingSystem.FIREMAKING_MOVE_THRESHOLD_SQ) {
this.cancelFiremaking(playerId, action);
}
Fire Placement:
Fires spawn at the cached start position, not the player’s current position:
// Cache player start position for movement detection and fire placement
const startPosition = {
x: player.node.position.x,
y: player.node.position.y,
z: player.node.position.z,
};
action.startPosition = startPosition;
// Fire spawns where lighting began, not where player ended up
this.completeFiremaking(playerId, processingAction, startPosition);
Fire Lifecycle:
- Fires burn for a limited time (configurable per log type)
- When fires extinguish, they spawn ashes as a ground item
- Ashes despawn after 2 minutes
- Fire models use bounding box snapping to sit flush on terrain
Level Requirements
Skills gate access to equipment and activities. Requirements are now centralized in tier-requirements.json and skill-unlocks.json.
Tier-Based Equipment Requirements
Equipment now uses a tier system that automatically looks up requirements:
Melee Weapons & Armor (Attack/Defence):
| Tier | Level |
|---|
| Bronze/Iron | 1 |
| Steel | 5 |
| Black | 10 |
| Mithril | 20 |
| Adamant | 30 |
| Rune | 40 |
| Dragon | 60 |
Tools (Woodcutting/Mining):
| 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 |
Gathering Resource Requirements
Defined in gathering/*.json:
Woodcutting:
| Tree | Level |
|---|
| Normal | 1 |
| Oak | 15 |
| Willow | 30 |
| Teak | 35 |
| Maple | 45 |
| Mahogany | 50 |
| Yew | 60 |
| Magic | 75 |
Mining:
| Ore | Level |
|---|
| Copper/Tin | 1 |
| Iron | 15 |
| Coal | 30 |
| Mithril | 55 |
| Adamantite | 70 |
| Runite | 85 |
Mining Success Rates (OSRS-Accurate)
Mining success rates depend only on Mining level, not pickaxe tier. Pickaxe tier affects roll frequency (speed) only.
The formula uses linear interpolation between low and high values:
P(Level) = (1 + floor(low × (99 - L) / 98 + high × (L - 1) / 98 + 0.5)) / 256
| Ore | Success at Min Level | Success at L99 | Notes |
|---|
| Copper/Tin | ~39.5% (L1) | 100% (L62+) | Beginner-friendly |
| Iron | ~52% (L15) | 100% (L63+) | Fast training |
| Coal | ~16.4% (L30) | ~39.5% (L99) | Slow but valuable |
| Mithril | ~11.7% (L55) | ~19.9% (L99) | Very slow |
| Adamantite | ~7.4% (L70) | ~10.2% (L99) | Rare |
| Runite | ~6.64% (L85) | ~7.42% (L97+) | Extremely rare |
Rock Depletion: Mining rocks ALWAYS deplete after yielding one ore (100% chance). This matches OSRS behavior. The only exception would be Mining gloves (not currently implemented).
Pickaxe Speed Bonuses
Pickaxe tier determines roll frequency (ticks between attempts):
| Pickaxe | Roll Ticks | Speed | Special Bonus |
|---|
| Bronze | 8 | Slowest | None |
| Iron | 7 | Slow | None |
| Steel | 6 | Medium | None |
| Mithril | 5 | Fast | None |
| Adamant | 4 | Faster | None |
| Rune | 3 | Very fast | None |
| Dragon | 3 | Very fast | 1/6 chance for 2-tick roll (avg 2.83) |
| Crystal | 3 | Very fast | 1/4 chance for 2-tick roll (avg 2.75) |
Dragon/Crystal Pickaxe Bonus: These pickaxes have a chance to trigger a faster roll. The server rolls this randomly and applies it deterministically to prevent client/server desyncs.
Fishing:
| Fish | Level |
|---|
| Shrimp | 1 |
| Sardine | 5 |
| Herring | 10 |
| Trout | 20 |
| Pike | 25 |
| Salmon | 30 |
| Lobster | 40 |
| Swordfish | 50 |
| Shark | 76 |
Processing Recipe Requirements
Defined in recipes/*.json:
Smelting: Levels 1-85 (Bronze to Rune bars)
Smithing: Levels 1-91 (Bronze to Rune equipment)
Cooking: Levels 1-80 (Shrimp to Shark)
Firemaking: Levels 1-75 (Normal to Magic logs)
Smithing Requirements
| Item | Smithing Level | Bars Required |
|---|
| Bronze Bar | 1 | 1 copper ore + 1 tin ore |
| Iron Bar | 15 | 1 iron ore (50% success) |
| Steel Bar | 30 | 1 iron ore + 2 coal |
| Mithril Bar | 50 | 1 mithril ore + 4 coal |
| Adamant Bar | 70 | 1 adamantite ore + 6 coal |
| Rune Bar | 85 | 1 runite ore + 8 coal |
| Bronze Equipment | 1 | 1-5 bronze bars |
| Iron Equipment | 15 | 1-5 iron bars |
| Steel Equipment | 30 | 2-5 steel bars |
| Mithril Equipment | 50 | 2-5 mithril bars |
| Adamant Equipment | 70 | 3-5 adamant bars |
| Rune Equipment | 85 | 3-5 rune bars |
Anti-Exploit Protections
Resource Spam Click Prevention
The gathering system prevents duplicate harvesting when players spam-click resources:
// From ResourceSystem.ts
const existingSession = this.activeGathering.get(playerId);
if (existingSession) {
// If already gathering this EXACT resource, silently ignore (prevents duplicate rewards)
if (existingSession.resourceId === resource.id) {
return;
}
// If switching to a DIFFERENT resource, cancel the old session first
this.cancelGatheringForPlayer(playerId, "switch_resource");
}
Protection Features:
- Duplicate gather requests for the same resource are silently ignored
- Prevents timer resets and duplicate drops
- Allows switching to different resources (cancels old session first)
- Guard clause executes after resource resolution for accurate comparison
This fix prevents the spam-click exploit where players could reset gathering timers by rapidly clicking the same resource.
Noted items (bank notes) cannot be used as skilling tools:
// From ToolUtils.ts
export function itemMatchesToolCategory(
itemId: string,
category: string,
): boolean {
// Noted items are bank notes - cannot be used as tools
if (isNotedItemId(itemId)) {
return false;
}
// ... rest of validation
}
Affected Tools:
- Pickaxes (mining)
- Hatchets/axes (woodcutting)
- Fishing rods, nets, harpoons (fishing)
Noted items must be un-noted at a bank before they can be equipped or used for skilling.
Skill Events
The system emits events for UI updates and logging:
| Event | Data | Description |
|---|
SKILLS_XP_GAINED | playerId, skill, amount | XP added to skill |
SKILLS_LEVEL_UP | entityId, skill, oldLevel, newLevel | Level increased |
SKILLS_UPDATED | playerId, skills | Skills state changed |
SKILLS_MILESTONE | entityId, skill, milestone | Milestone reached |
COMBAT_LEVEL_CHANGED | entityId, oldLevel, newLevel | Combat level changed |
TOTAL_LEVEL_CHANGED | entityId, oldLevel, newLevel | Total level changed |
Milestones
Special milestones trigger events and messages:
const commonMilestones = [
{ level: 50, name: "Halfway", message: "Halfway to mastery!" },
{ level: 92, name: "Half XP", message: "Halfway to 99 in XP!" },
{ level: 99, name: "Mastery", message: "Skill mastered!" },
];
// Attack-specific milestones
const attackMilestones = [
{ level: 40, name: "Rune Weapons", message: "You can now wield rune weapons!" },
{ level: 60, name: "Dragon Weapons", message: "You can now wield dragon weapons!" },
];
API Reference
Grant XP
// Grant XP via event system (recommended)
skillsSystem.grantXP(entityId, "woodcutting", 25);
// Direct internal grant (used by external handlers)
skillsSystem.addXPInternal(entityId, skill, amount);
Check Requirements
// Check if entity meets skill requirements
const canEquip = skillsSystem.meetsRequirements(entity, {
attack: 40,
defense: 40,
});
Set Level (Admin)
// Directly set skill level (for admin commands)
skillsSystem.setSkillLevel(entityId, "attack", 99);
Reset Skill
// Reset skill to level 1
skillsSystem.resetSkill(entityId, "attack");
Skill Unlocks
The skill-unlocks.json manifest documents what players unlock at each level. This is used for UI display and progression tracking.
Example unlocks:
- Attack 40: Rune weapons
- Attack 60: Dragon weapons
- Woodcutting 15: Oak trees
- Mining 30: Coal
- Cooking 40: Lobster
- Smithing 50: Mithril bar
See manifests/skill-unlocks.json for the complete list of unlocks for all skills.
Manifest-Driven Design
All skill-related data is now defined in JSON manifests:
tier-requirements.json - Equipment level requirements by tier
skill-unlocks.json - What unlocks at each level
gathering/*.json - Resource nodes, yields, and XP
recipes/*.json - Processing recipes and XP
This allows easy content updates without code changes and enables community modding.