diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/villager/VillagerProfessionBuilder.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/villager/VillagerProfessionBuilder.java
new file mode 100644
index 000000000..38a3111f2
--- /dev/null
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/villager/VillagerProfessionBuilder.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.object.builder.v1.villager;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableSet;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.Blocks;
+import net.minecraft.client.render.entity.feature.VillagerResourceMetadata;
+import net.minecraft.item.Item;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.util.Identifier;
+import net.minecraft.village.TradeOffers;
+import net.minecraft.village.VillagerProfession;
+import net.minecraft.world.poi.PointOfInterestType;
+
+import net.fabricmc.fabric.mixin.object.builder.VillagerProfessionAccessor;
+
+/**
+ * Allows for the creation of new {@link VillagerProfession}s.
+ *
+ *
The texture for the villagers are located at assets/IDENTIFIER_NAMESPACE/textures/entity/villager/profession/IDENTIFIER_PATH.png
+ *
+ *
A corresponding IDENTIFIER_PATH.mcmeta
file exits in the same directory to define properties such as the {@link VillagerResourceMetadata.HatType HatType} this profession would use.
+ *
+ *
Note this does not register any trades to these villagers. To register trades, add a new entry with your profession as the key to {@link TradeOffers#PROFESSION_TO_LEVELED_TRADE}.
+ */
+public final class VillagerProfessionBuilder {
+ private final ImmutableSet.Builder- gatherableItemsBuilder = ImmutableSet.builder();
+ private final ImmutableSet.Builder secondaryJobSiteBlockBuilder = ImmutableSet.builder();
+ private Identifier identifier;
+ private PointOfInterestType pointOfInterestType;
+ /* @Nullable */
+ private SoundEvent workSoundEvent;
+
+ private VillagerProfessionBuilder() {
+ }
+
+ /**
+ * Creates a builder instance to allow for creation of a {@link VillagerProfession}.
+ * @return A new builder.
+ */
+ static VillagerProfessionBuilder create() {
+ return new VillagerProfessionBuilder();
+ }
+
+ /**
+ * The Identifier used to identify this villager profession.
+ *
+ * @param id The identifier to assign to this profession.
+ * @return this builder
+ */
+ public VillagerProfessionBuilder id(Identifier id) {
+ this.identifier = id;
+ return this;
+ }
+
+ /**
+ * The {@link PointOfInterestType} the Villager of this profession will search for when finding a workstation.
+ *
+ * @param type The {@link PointOfInterestType} the Villager will attempt to find.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder workstation(PointOfInterestType type) {
+ this.pointOfInterestType = type;
+ return this;
+ }
+
+ /**
+ * Items that a Villager may harvest in this profession.
+ *
+ *
In Vanilla, this is used by the farmer to define what type of crops the farmer can harvest.
+ *
+ * @param items Items harvestable by this profession.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder harvestableItems(Item... items) {
+ this.gatherableItemsBuilder.add(items);
+ return this;
+ }
+
+ /**
+ * Items that a Villager may harvest in this profession.
+ *
+ *
In Vanilla, this is used by the farmer to define what type of crops the farmer can harvest.
+ *
+ * @param items Items harvestable by this profession.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder harvestableItems(Iterable- items) {
+ this.gatherableItemsBuilder.addAll(items);
+ return this;
+ }
+
+ /**
+ * A collection of blocks blocks which may suffice as a secondary job site for a Villager.
+ *
+ *
In Vanilla, this is used by the {@link VillagerProfession#FARMER Farmer} to stay near {@link Blocks#FARMLAND Farmland} when at it's job site.
+ *
+ * @param blocks Collection of secondary job site blocks.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder secondaryJobSites(Block... blocks) {
+ this.secondaryJobSiteBlockBuilder.add(blocks);
+ return this;
+ }
+
+ /**
+ * A collection of blocks blocks which may suffice as a secondary job site for a Villager.
+ *
+ *
In Vanilla, this is used by the {@link VillagerProfession#FARMER Farmer} to stay near {@link Blocks#FARMLAND Farmland} when at it's job site.
+ *
+ * @param blocks Collection of secondary job site blocks.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder secondaryJobSites(Iterable blocks) {
+ this.secondaryJobSiteBlockBuilder.addAll(blocks);
+ return this;
+ }
+
+ /**
+ * Provides the sound made when a Villager works.
+ *
+ * @param workSoundEvent The {@link SoundEvent} to be played.
+ * @return this builder.
+ */
+ public VillagerProfessionBuilder workSound(/* @Nullable */ SoundEvent workSoundEvent) {
+ this.workSoundEvent = workSoundEvent;
+ return this;
+ }
+
+ /**
+ * Creates the {@link VillagerProfession}.
+ *
+ * @return a new {@link VillagerProfession}.
+ * @throws IllegalStateException if the builder is missing an {@link Identifier id} and {@link PointOfInterestType workstation}.
+ */
+ public VillagerProfession build() {
+ checkState(this.identifier != null, "An Identifier is required to build a new VillagerProfession.");
+ checkState(this.pointOfInterestType != null, "A PointOfInterestType is required to build a new VillagerProfession.");
+ return VillagerProfessionAccessor.create(this.identifier.toString(), this.pointOfInterestType, this.gatherableItemsBuilder.build(), this.secondaryJobSiteBlockBuilder.build(), this.workSoundEvent);
+ }
+}
diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/world/poi/PointOfInterestHelper.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/world/poi/PointOfInterestHelper.java
new file mode 100644
index 000000000..b8cf5516b
--- /dev/null
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/api/object/builder/v1/world/poi/PointOfInterestHelper.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.object.builder.v1.world.poi;
+
+import java.util.Set;
+import java.util.function.Predicate;
+
+import com.google.common.collect.ImmutableSet;
+
+import net.minecraft.block.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.util.Identifier;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.world.poi.PointOfInterest;
+import net.minecraft.world.poi.PointOfInterestType;
+
+import net.fabricmc.fabric.mixin.object.builder.PointOfInterestTypeAccessor;
+
+/**
+ * This class provides utilities to create a {@link PointOfInterestType}.
+ *
+ * A point of interest is typically used by villagers to specify their workstation blocks, meeting zones and homes.
+ * Points of interest are also used by bees to specify where their bee hive is and nether portals to find existing portals.
+ */
+public final class PointOfInterestHelper {
+ private PointOfInterestHelper() {
+ }
+
+ /**
+ * Creates and registers a {@link PointOfInterestType}.
+ *
+ * @param id The id of this {@link PointOfInterestType}.
+ * @param ticketCount the amount of tickets.
+ * @param searchDistance the search distance.
+ * @param blocks all the blocks where a {@link PointOfInterest} of this type will be present.
+ * @return a new {@link PointOfInterestType}.
+ */
+ public static PointOfInterestType register(Identifier id, int ticketCount, int searchDistance, Block... blocks) {
+ final ImmutableSet.Builder builder = ImmutableSet.builder();
+
+ for (Block block : blocks) {
+ builder.addAll(block.getStateManager().getStates());
+ }
+
+ return register(id, ticketCount, searchDistance, builder.build());
+ }
+
+ /**
+ * Creates and registers a {@link PointOfInterestType}.
+ *
+ * @param id The id of this {@link PointOfInterestType}.
+ * @param ticketCount the amount of tickets.
+ * @param completionCondition a {@link Predicate} which determines if two {@link PointOfInterestType}s are the same.
+ * @param searchDistance the search distance.
+ * @param blocks all blocks where a {@link PointOfInterest} of this type will be present
+ * @return a new {@link PointOfInterestType}.
+ */
+ public static PointOfInterestType register(Identifier id, int ticketCount, Predicate completionCondition, int searchDistance, Block... blocks) {
+ final ImmutableSet.Builder builder = ImmutableSet.builder();
+
+ for (Block block : blocks) {
+ builder.addAll(block.getStateManager().getStates());
+ }
+
+ return register(id, ticketCount, completionCondition, searchDistance, builder.build());
+ }
+
+ /**
+ * Creates and registers a {@link PointOfInterestType}.
+ *
+ * @param id the id of this {@link PointOfInterestType}.
+ * @param ticketCount the amount of tickets.
+ * @param searchDistance the search distance.
+ * @param blocks all {@link BlockState block states} where a {@link PointOfInterest} of this type will be present
+ * @return a new {@link PointOfInterestType}.
+ */
+ public static PointOfInterestType register(Identifier id, int ticketCount, int searchDistance, Iterable blocks) {
+ final ImmutableSet.Builder builder = ImmutableSet.builder();
+
+ return register(id, ticketCount, searchDistance, builder.addAll(blocks).build());
+ }
+
+ /**
+ * Creates and registers a {@link PointOfInterestType}.
+ *
+ * @param id the id of this {@link PointOfInterestType}.
+ * @param ticketCount the amount of tickets.
+ * @param typePredicate a {@link Predicate} which determines if two {@link PointOfInterestType}s are the same.
+ * @param searchDistance the search distance.
+ * @param states all {@link BlockState block states} where a {@link PointOfInterest} of this type will be present
+ * @return a new {@link PointOfInterestType}.
+ */
+ public static PointOfInterestType register(Identifier id, int ticketCount, Predicate typePredicate, int searchDistance, Iterable states) {
+ final ImmutableSet.Builder builder = ImmutableSet.builder();
+
+ return register(id, ticketCount, typePredicate, searchDistance, builder.addAll(states).build());
+ }
+
+ // INTERNAL METHODS
+
+ private static PointOfInterestType register(Identifier id, int ticketCount, int searchDistance, Set states) {
+ return Registry.register(Registry.POINT_OF_INTEREST_TYPE, id, PointOfInterestTypeAccessor.callSetup(
+ PointOfInterestTypeAccessor.callCreate(id.toString(), states, ticketCount, searchDistance)));
+ }
+
+ private static PointOfInterestType register(Identifier id, int ticketCount, Predicate typePredicate, int searchDistance, Set states) {
+ return Registry.register(Registry.POINT_OF_INTEREST_TYPE, id, PointOfInterestTypeAccessor.callSetup(
+ PointOfInterestTypeAccessor.callCreate(id.toString(), states, ticketCount, typePredicate, searchDistance)));
+ }
+}
diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PointOfInterestTypeAccessor.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PointOfInterestTypeAccessor.java
new file mode 100644
index 000000000..c3c9df89d
--- /dev/null
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/PointOfInterestTypeAccessor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.object.builder;
+
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.world.poi.PointOfInterestType;
+
+@Mixin(PointOfInterestType.class)
+public interface PointOfInterestTypeAccessor {
+ @Invoker("")
+ static PointOfInterestType callCreate(String id, Set blockStates, int ticketCount, Predicate typePredicate, int searchDistance) {
+ throw new AssertionError("Untransformed Accessor!");
+ }
+
+ @Invoker("")
+ static PointOfInterestType callCreate(String id, Set blockStates, int ticketCount, int searchDistance) {
+ throw new AssertionError("Untransformed Accessor!");
+ }
+
+ @Invoker("setup")
+ static PointOfInterestType callSetup(PointOfInterestType pointOfInterestType) {
+ throw new AssertionError("Untransformed Accessor!");
+ }
+}
diff --git a/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/VillagerProfessionAccessor.java b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/VillagerProfessionAccessor.java
new file mode 100644
index 000000000..46bfb6c2a
--- /dev/null
+++ b/fabric-object-builder-api-v1/src/main/java/net/fabricmc/fabric/mixin/object/builder/VillagerProfessionAccessor.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.object.builder;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Invoker;
+import com.google.common.collect.ImmutableSet;
+
+import net.minecraft.block.Block;
+import net.minecraft.item.Item;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.village.VillagerProfession;
+import net.minecraft.world.poi.PointOfInterestType;
+
+@Mixin(VillagerProfession.class)
+public interface VillagerProfessionAccessor {
+ @Invoker("")
+ static VillagerProfession create(String id, PointOfInterestType type, ImmutableSet- gatherableItems, ImmutableSet secondaryJobSites, /* @Nullable */ SoundEvent soundEvent) {
+ throw new AssertionError("Untransformed accessor!");
+ }
+}
diff --git a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json
index 0378b7bdc..866ca7b76 100644
--- a/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json
+++ b/fabric-object-builder-api-v1/src/main/resources/fabric-object-builder-v1.mixins.json
@@ -9,7 +9,9 @@
"DefaultAttributeRegistryAccessor",
"DefaultAttributeRegistryMixin",
"MaterialBuilderAccessor",
- "MixinBlock"
+ "MixinBlock",
+ "PointOfInterestTypeAccessor",
+ "VillagerProfessionAccessor"
],
"client": [
"ModelPredicateProviderRegistryAccessor",