NPC Data Structure
NPCs (Non-Player Characters) and mobs are defined in JSON manifests and loaded at runtime. This data-driven approach allows content to be modified without code changes.NPC data is managed in
packages/shared/src/data/npcs.ts and loaded from world/assets/manifests/npcs.json.Data Loading
NPCs are NOT hardcoded. TheALL_NPCS map is populated at runtime:
NPC Data Schema
Each NPC has the following structure:NPC Categories
Aggression Types
NPCs have different aggression behaviors:Aggro Rules
Drop Tables
Each NPC has aDropTable defining loot:
Drop Calculation
Available 3D Models
NPCs use rigged GLB models from/assets/world/forge/:
Helper Functions
Get NPC by ID
Get NPCs by Category
Get NPCs by Biome
Get NPCs by Level Range
Check if NPC Can Drop Item
Combat Level Calculation
NPC combat level is calculated from stats:Spawn Constants
Global spawn settings:Example NPC Definitions
Melee Mob (Default)
Magic Mob
Magic Mobs: The
magic stat determines damage output. The spellId must reference a valid spell from the spell manifest. Mobs have infinite runes and don’t consume resources when casting.Ranged Mob
Ranged Mobs: The
ranged stat determines damage output. The arrowId must reference a valid arrow from the ammunition manifest. Mobs have infinite arrows and don’t consume ammunition when firing.Combat Type Configuration
Attack Type Fields
For Melee Mobs (default):- No special fields required
- Uses
attackandstrengthstats combatRangedefaults to 1 tile- Plays
COMBATorSWORD_SWINGanimation - Immediate hit (0 tick delay)
attackType:"ranged"(required)arrowId: Required (e.g.,"bronze_arrow","iron_arrow","steel_arrow")ranged: Required stat for damage calculationcombatRange: Typically 7-10 tilesattackSpeedTicks: Typically 4 ticks (2.4 seconds)heldWeaponModel: Optional bow GLB for visuals (e.g.,"asset://weapons/shortbow.glb")- Plays
RANGEanimation - Arrow launch delay: 400ms
- Hit delay:
1 + floor((3 + distance) / 6)ticks
attackType:"magic"(required)spellId: Required (e.g.,"wind_strike","fire_bolt","water_strike")magic: Required stat for damage calculationcombatRange: Typically 10 tilesattackSpeedTicks: Typically 5 ticks (3.0 seconds)heldWeaponModel: Optional staff GLB for visuals (e.g.,"asset://weapons/staff.glb")- Plays
SPELL_CASTanimation - Spell launch delay: 600ms
- Hit delay:
1 + floor((1 + distance) / 3)ticks
Held Weapon Models
TheheldWeaponModel field attaches a 3D weapon to the mob’s hand bone:
- Uses Asset Forge attachment metadata (same as player equipment)
- Supports V1 (direct attachment) and V2 (pre-baked matrix) formats
- Weapons are cached and shared across mobs of the same type
- Automatically attaches to VRM
rightHandbone (or custom bone from metadata)
- Static
_weaponCacheinMobVisualManagershares loaded GLB scenes _pendingLoadsmap deduplicates concurrent fetches for the same URL- First mob to load a weapon caches the scene, subsequent mobs clone from cache
- Eliminates duplicate network requests when multiple mobs of the same type spawn
- Cache cleared on world teardown via
MobNPCSpawnerSystem.destroy() - Weapons use
clone(true)to share geometry/material buffers (GPU efficient)
asset://weapons/shortbow.glb- Shortbow (for ranged mobs)asset://weapons/staff.glb- Magic staff (for magic mobs)asset://weapons/sword.glb- Sword (for melee mobs)- Custom weapons from Asset Forge
vrmBoneName: Target bone name (default:"rightHand")version: Metadata format version (1 or 2)relativeMatrix: Pre-baked 4×4 transform matrix (V2 format only)
removeFromParent()detaches weapon from bone- Geometry and materials are NOT disposed (shared across mob instances)
- JavaScript garbage collection handles the rest
Adding New NPCs
1
Add to JSON Manifest
Add entry to
world/assets/manifests/npcs.json2
Choose or Create Model
Use existing model or generate new one in 3D Asset Forge
3
Configure Combat Type
Set
attackType, spellId/arrowId, and heldWeaponModel if using ranged/magic4
Restart Server
Server must restart to reload manifests