Skip to content

Villagers

MehVahdJukaar edited this page Jul 25, 2026 · 1 revision

Villagers

Two separate things that both concern villagers: data driven trades, and AI hooks for adding activities and schedules to their brain.

Data driven trades

Add and remove trades from any villager, vanilla or modded, from a datapack. No code needed.

Getting Started

The file goes in data/[villager mod id]/moonlight/villager_trades/[profession].json. For a vanilla farmer that's data/minecraft/moonlight/villager_trades/farmer.json.

Here is the basic trade type:

{
  "type": "simple",
  "offer": {
    "id": "minecraft:stone",
    "Count": 1
  },
  "price": {
    "id": "minecraft:emerald",
    "Count": 4
  },
  "price_secondary": {
    "id": "minecraft:cobblestone",
    "Count": 1
  },
  "max_trades": 16,
  "price_multiplier": 0.05,
  "xp": 5,
  "level": 2
}

Only type, offer and price are required.

Fields of simple

Field Type Default Description
offer item stack What the villager sells
price item cost What it costs
price_secondary item cost none Optional second cost
max_trades int 16 Uses before it locks
xp int depends on level Villager xp per trade
price_multiplier float 0.05 Demand price scaling
level 1 to 5 1 Profession level needed
loot_function loot function none Applied to the offered stack, so you can roll enchantments or random counts

Other trade types

Biome variant trades (1.21+), a different trade per villager type:

{
  "type": "villager_type_variant",
  "default": {
    "type": "simple"
  },
  "trades_per_type": {
    "minecraft:desert": { "type": "simple" },
    "minecraft:jungle": { "type": "simple" }
  }
}

Removing trades. remove_all_non_data drops every non datapack trade at a level:

{
  "type": "remove_all_non_data",
  "level": 3
}

Overriding another mod's file. Use no_op, which parses to nothing:

{
  "type": "no_op"
}

Custom trade types

Register a codec and the type becomes available to datapacks:

public static void init() {
    ItemListingManager.registerSerializer(MyMod.res("my_trade"), MyTrade.CODEC);
}
public record MyTrade(ItemStack offer, int level) implements ModItemListing {

    public static final MapCodec<MyTrade> CODEC = RecordCodecBuilder.mapCodec(i -> i.group(
            ItemStack.CODEC.fieldOf("offer").forGetter(MyTrade::offer),
            Codec.intRange(1, 5).optionalFieldOf("level", 1).forGetter(MyTrade::level)
    ).apply(i, MyTrade::new));

    @Override
    public MerchantOffer getOffer(Entity entity, RandomSource random) {
        return new MerchantOffer(new ItemCost(Items.EMERALD, 1), offer, 16,
                ModItemListing.defaultXp(true, level), 0.05f);
    }

    @Override
    public MapCodec<? extends ModItemListing> getCodec() {
        return CODEC;
    }

    @Override
    public int getLevel() {
        return level;
    }
}

ItemListingManager.registerSimple(id, listing, level) registers a plain vanilla ItemListing under an id instead, when you don't need any fields.

Example: VillagerTradesExample

AI hooks

Villager brains are hard to extend without stomping on other mods. These hooks let several mods add activities and tasks to the same villager without fighting.

Getting Started

public static void init() {
    VillagerAIHooks.addBrainModification(MyMod::onBrainEvent);
}

private static void onBrainEvent(IVillagerBrainEvent event) {
    // add to an existing activity without clearing what's already in it
    event.addTaskToActivity(Activity.WORK, Pair.of(5, new MyBehavior()));

    // put your own activity in the daily schedule
    event.scheduleActivity(MY_ACTIVITY.get(), 6000, 9000);
}

You can also add sensors, edit the memory map, or replace a whole activity with addOrReplaceActivity (heavier handed: other mods' additions to it are lost).

Warning

Don't touch the brain directly. The point of these methods is that two mods adding to the same activity both end up working.

Custom memories

Register the memory type normally, then tell the villager about it during setup, or it won't be saved:

public static final Supplier<MemoryModuleType<Integer>> CUSTOM_MEMORY =
        RegHelper.registerMemoryModule(MyMod.res("custom_memory"), Codec.INT);

public static void setup() {
    VillagerAIHooks.registerMemory(CUSTOM_MEMORY.get());
}

Activities, schedules and sensors are registered with the usual RegHelper calls.

Example: VillagersAIHooksExample

Clone this wiki locally