Skip to content

Trader Profile

A trader profile defines the complete trading behavior for a custom NPC trader, including trade pools, trade counts, refresh logic, alignment requirements, and wandering behavior.

Trader profiles are fully data-driven and configured using JSON files. Each profile describes what a trader buys, what they sell, how many trades they offer, and how those trades refresh over time.

Trades are selected from weighted pools and may offer either fixed or randomized items and prices, allowing for highly configurable and replayable trader behavior.

Trader Profiles are defined and configured using JSON files located within a data pack under:
- data/<namespace>/trader_profiles/<profile>.json
- Where the namespace defines what mod is adding it and profile is the profile's id.

Trader Profile JSON format

  • The root object
    • id: namespace:path (required)
      • Unique identifier for this trader profile.
      • An invalid or missing `id` value will cause the profile to fail loading
      • Must match the file location of this entry. Located in data/<namespace>/trader_profiles/<path>.json
    • tradingAlignmentRequired: int (required)
      • Minimum alignment required to trade with this NPC.
    • minimumTradesOffered: int (required)
      • The minimum number of trades this trader will offer.
      • If `minimumTradesOffered + maximumAdditionalTradesOffered > 9`, minimumTradesOffered will be forcibly reset to 3.
    • maximumAdditionalTradesOffered: int (required)
      • Additional trades added randomly beyond the minimum.
      • If `minimumTradesOffered + maximumAdditionalTradesOffered > 9`, maximumAdditionalTradesOffered will be forcibly reset to 6.
    • shouldRefreshTrades: true|false (required)
      • If true, the trader will refresh their trade list after a threshold is reached.
      • Typicaly set to true.
    • refreshTradesAtValue: int (required)
      • Total trade value required before trades refresh.
      • Typicaly set to 5000 coins.
    • lockTicksAfterRefresh: int (required)
      • Cooldown (in ticks) after refresh before new trades become available.
      • Typicaly set to 6000 ticks, or 5 minutes.
    • isTypicallyWandering: true|false (default: false)
      • Indicates if this trader is usually wandering, used internally for trader validation and error correcting.
    • buyFromPlayer: A list of trade pool items
      • Defines what items the trader buys from the player. See below.
    • sellToPlayer: A list of trade pool items
      • Defines what items the trader sells to the player. See below.

Trade pool items

Each entry in buyFromPlayer or sellToPlayer represents a weighted trade option.

  • A trade pool item
    • weight: int (default: 1)
      • Controls how likely this trade is to be selected.
    • maxTransferQuantity: int (default: 200)
      • Maximum quantity that can be traded.
    • shouldTradePersist: true|false (default: true)
      • If false, the trade is removed after use and not ticked back.
      • Used for one-time available trades, like with smith scrolls.
    • One of below (required):
      • stack: Single item definition
      • stacks: Multiple item definitions
      • Either `stack` or`stacks` must be defined — never both!

Item supplier formats

Single Stack Supplier

Defines a trade that always uses the same item and price.

  • stack
    • item: namespace:item (required)
    • averageCost: int (default: 1000)
    • count: int (default: 1)
    • nbt: string (optional)

Multi Stack Supplier

Defines a trade that randomly selects from multiple possible items.

  • stacks: List of stack entries
    • Each entry follows the same format as a single stack
    • One entry is chosen randomly per trade

Example trader profile

data/lotrextended/trader_profiles/bree_brewer.json
{
  "id": "lotrextended:bree_brewer",
  "tradingAlignmentRequired": 0,
  "minimumTradesOffered": 3,
  "maximumAdditionalTradesOffered": 6,
  "shouldRefreshTrades": true,
  "refreshTradesAtValue": 5000,
  "lockTicksAfterRefresh": 6000,
  "buyFromPlayer": [
    {
      "weight": 50,
      "stack": {
        "item": "minecraft:wheat",
        "averageCost": 1,
        "count": 2
      }
    },
    {
      "weight": 50,
      "stack": {
        "item": "minecraft:apple",
        "averageCost": 1
      }
    },
    {
      "weight": 50,
      "stack": {
        "item": "minecraft:potato",
        "averageCost": 1,
        "count": 2
      }
    }
  ],
  "sellToPlayer": [
    {
      "weight": 50,
      "stack": {
        "item": "lotr:apple_juice",
        "averageCost": 6
      }
    },
    {
      "weight": 50,
      "stack": {
        "item": "lotr:sweet_berry_juice",
        "averageCost": 5
      }
    },
    {
      "weight": 50,
      "stacks": [
        {
          "item": "lotr:ale",
          "averageCost": 8,
          "nbt": "{vessel:{potency:\"light\"}}"
        },
        {
          "item": "lotr:ale",
          "averageCost": 10,
          "nbt": "{vessel:{potency:\"moderate\"}}"
        },
        {
          "item": "lotr:ale",
          "averageCost": 12,
          "nbt": "{vessel:{potency:\"strong\"}}"
        }
      ]
    },
    {
      "weight": 50,
      "stack": {
        "item": "lotr:ceramic_mug",
        "averageCost": 2
      }
    }
  ]
}