From 0f4e5f55cb40132ec95ad24a2b3596c1d9a5bcfb Mon Sep 17 00:00:00 2001
From: Zoe <104020300+Antikyth@users.noreply.github.com>
Date: Mon, 17 Mar 2025 02:34:57 +1300
Subject: [PATCH] Add `FabricEntityLootTableProvider` for datagen (#4459)

* Add FabricEntityLootTableProvider

* Add ConditionEntityLootTableGenerator

* Apply spotless

* Add datagen test for entity loot tables

* Add test dependency on object builder API

Removes DFU error:
No data fixer registered for fabric-data-gen-api-v1-testmod:simple_entity

* Remove no longer necessary EntityLootTableGenerator#register invoker

* Fix checkstyle

---------

Co-authored-by: modmuss50 <modmuss50@gmail.com>
---
 fabric-data-generation-api-v1/build.gradle    |   4 +
 .../loot/FabricEntityLootTableGenerator.java  |  36 +++++
 .../FabricEntityLootTableProvider.java        | 128 ++++++++++++++++++
 .../v1/provider/FabricLootTableProvider.java  |   4 +-
 .../ConditionEntityLootTableGenerator.java    |  50 +++++++
 .../EntityLootTableGeneratorAccessor.java     |  29 ++++
 .../loot/EntityLootTableGeneratorMixin.java   |  27 ++++
 ...abric-data-generation-api-v1.accesswidener |   3 +
 .../fabric-data-generation-api-v1.mixins.json |   4 +-
 .../src/main/resources/fabric.mod.json        |   3 +-
 .../loot_table/entities/simple_entity.json    |  29 ++++
 .../datagen/DataGeneratorTestContent.java     |  16 +++
 .../datagen/DataGeneratorTestEntrypoint.java  |  22 +++
 .../template.accesswidener                    |   3 +
 14 files changed, 355 insertions(+), 3 deletions(-)
 create mode 100644 fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/loot/FabricEntityLootTableGenerator.java
 create mode 100644 fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricEntityLootTableProvider.java
 create mode 100644 fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/ConditionEntityLootTableGenerator.java
 create mode 100644 fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorAccessor.java
 create mode 100644 fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorMixin.java
 create mode 100644 fabric-data-generation-api-v1/src/testmod/generated/data/fabric-data-gen-api-v1-testmod/loot_table/entities/simple_entity.json

diff --git a/fabric-data-generation-api-v1/build.gradle b/fabric-data-generation-api-v1/build.gradle
index 83b2c7945..2102e6d89 100644
--- a/fabric-data-generation-api-v1/build.gradle
+++ b/fabric-data-generation-api-v1/build.gradle
@@ -11,6 +11,10 @@ moduleDependencies(project, [
 	'fabric-recipe-api-v1'
 ])
 
+testDependencies(project, [
+    ':fabric-object-builder-api-v1'
+])
+
 dependencies {
 }
 
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/loot/FabricEntityLootTableGenerator.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/loot/FabricEntityLootTableGenerator.java
new file mode 100644
index 000000000..47609952d
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/loot/FabricEntityLootTableGenerator.java
@@ -0,0 +1,36 @@
+/*
+ * 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.datagen.v1.loot;
+
+import com.google.common.base.Preconditions;
+
+import net.minecraft.data.loottable.EntityLootTableGenerator;
+
+import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
+import net.fabricmc.fabric.impl.datagen.loot.ConditionEntityLootTableGenerator;
+
+public interface FabricEntityLootTableGenerator {
+	/**
+	 * {@return a new generator that applies the specified conditions to any loot table it receives, and then forwards
+	 * the loot tables to this generator}.
+	 */
+	default EntityLootTableGenerator withConditions(ResourceCondition... conditions) {
+		Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
+
+		return new ConditionEntityLootTableGenerator((EntityLootTableGenerator) this, conditions);
+	}
+}
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricEntityLootTableProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricEntityLootTableProvider.java
new file mode 100644
index 000000000..5f100b5fe
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricEntityLootTableProvider.java
@@ -0,0 +1,128 @@
+/*
+ * 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.datagen.v1.provider;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+
+import com.google.common.collect.Sets;
+import org.jetbrains.annotations.NotNull;
+
+import net.minecraft.data.DataWriter;
+import net.minecraft.data.loottable.EntityLootTableGenerator;
+import net.minecraft.data.loottable.vanilla.VanillaEntityLootTableGenerator;
+import net.minecraft.entity.EntityType;
+import net.minecraft.loot.LootTable;
+import net.minecraft.loot.context.LootContextTypes;
+import net.minecraft.registry.Registries;
+import net.minecraft.registry.RegistryKey;
+import net.minecraft.registry.RegistryWrapper;
+import net.minecraft.resource.featuretoggle.FeatureFlags;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
+import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
+import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
+import net.fabricmc.fabric.impl.datagen.loot.FabricLootTableProviderImpl;
+
+/**
+ * Extend this class and implement {@link FabricEntityLootTableProvider#generate()}.
+ *
+ * <p>Register an instance of this class with {@link FabricDataGenerator.Pack#addProvider} in a
+ * {@link DataGeneratorEntrypoint}.
+ */
+public abstract class FabricEntityLootTableProvider extends EntityLootTableGenerator implements FabricLootTableProvider {
+	private final FabricDataOutput output;
+	private final Set<Identifier> excludedFromStrictValidation = new HashSet<>();
+	private final CompletableFuture<RegistryWrapper.WrapperLookup> registryLookupFuture;
+
+	protected FabricEntityLootTableProvider(FabricDataOutput output, @NotNull CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
+		super(FeatureFlags.FEATURE_MANAGER.getFeatureSet(), registryLookup.join());
+
+		this.output = output;
+		this.registryLookupFuture = registryLookup;
+	}
+
+	/**
+	 * Implement this method to add entity drops.
+	 *
+	 * <p>Use the {@link EntityLootTableGenerator#register} methods to generate entity drops.
+	 *
+	 * <p>See {@link VanillaEntityLootTableGenerator#generate()} for examples of vanilla entity loot tables.
+	 */
+	@Override
+	public abstract void generate();
+
+	/**
+	 * Disable strict validation for the given entity type.
+	 */
+	public void excludeFromStrictValidation(EntityType<?> entityType) {
+		this.excludedFromStrictValidation.add(Registries.ENTITY_TYPE.getId(entityType));
+	}
+
+	@Override
+	public void accept(BiConsumer<RegistryKey<LootTable>, LootTable.Builder> biConsumer) {
+		this.generate();
+
+		for (Map<RegistryKey<LootTable>, LootTable.Builder> tables : this.lootTables.values()) {
+			// Register each of this particular entity type's loot tables
+			for (Map.Entry<RegistryKey<LootTable>, LootTable.Builder> entry : tables.entrySet()) {
+				biConsumer.accept(entry.getKey(), entry.getValue());
+			}
+		}
+
+		if (this.output.isStrictValidationEnabled()) {
+			Set<Identifier> missing = Sets.newHashSet();
+
+			// Find any entity types from this mod that are missing their main loot table
+			for (Identifier entityTypeId : Registries.ENTITY_TYPE.getIds()) {
+				if (entityTypeId.getNamespace().equals(this.output.getModId())) {
+					EntityType<?> entityType = Registries.ENTITY_TYPE.get(entityTypeId);
+
+					entityType.getLootTableKey().ifPresent(mainLootTableKey -> {
+						if (mainLootTableKey.getValue().getNamespace().equals(this.output.getModId())) {
+							Map<RegistryKey<LootTable>, LootTable.Builder> tables = this.lootTables.get(entityType);
+
+							if (tables == null || !tables.containsKey(mainLootTableKey)) {
+								missing.add(entityTypeId);
+							}
+						}
+					});
+				}
+			}
+
+			missing.removeAll(this.excludedFromStrictValidation);
+
+			if (!missing.isEmpty()) {
+				throw new IllegalStateException("Missing loot table(s) for %s".formatted(missing));
+			}
+		}
+	}
+
+	@Override
+	public CompletableFuture<?> run(DataWriter writer) {
+		return FabricLootTableProviderImpl.run(writer, this, LootContextTypes.ENTITY, this.output, this.registryLookupFuture);
+	}
+
+	@Override
+	public String getName() {
+		return "Entity Loot Tables";
+	}
+}
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLootTableProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLootTableProvider.java
index 898df2262..610e5a91f 100644
--- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLootTableProvider.java
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLootTableProvider.java
@@ -27,6 +27,7 @@ import net.minecraft.loot.LootTable;
 import net.minecraft.registry.RegistryKey;
 
 import net.fabricmc.fabric.api.datagen.v1.loot.FabricBlockLootTableGenerator;
+import net.fabricmc.fabric.api.datagen.v1.loot.FabricEntityLootTableGenerator;
 import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
 import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
 
@@ -42,7 +43,8 @@ public interface FabricLootTableProvider extends LootTableGenerator, DataProvide
 	/**
 	 * Return a new exporter that applies the specified conditions to any loot table it receives.
 	 *
-	 * <p>For block loot tables, use {@link FabricBlockLootTableGenerator#withConditions} instead.
+	 * <p>For block and entity loot tables, use {@link FabricBlockLootTableGenerator#withConditions} or
+	 * {@link FabricEntityLootTableGenerator#withConditions} instead, respectively.
 	 */
 	default BiConsumer<RegistryKey<LootTable>, LootTable.Builder> withConditions(BiConsumer<RegistryKey<LootTable>, LootTable.Builder> exporter, ResourceCondition... conditions) {
 		Preconditions.checkArgument(conditions.length > 0, "Must add at least one condition.");
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/ConditionEntityLootTableGenerator.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/ConditionEntityLootTableGenerator.java
new file mode 100644
index 000000000..ad1b0abc0
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/impl/datagen/loot/ConditionEntityLootTableGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.impl.datagen.loot;
+
+import net.minecraft.data.loottable.EntityLootTableGenerator;
+import net.minecraft.entity.EntityType;
+import net.minecraft.loot.LootTable;
+import net.minecraft.registry.RegistryKey;
+import net.minecraft.resource.featuretoggle.FeatureFlags;
+
+import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
+import net.fabricmc.fabric.impl.datagen.FabricDataGenHelper;
+import net.fabricmc.fabric.mixin.datagen.loot.EntityLootTableGeneratorAccessor;
+
+public class ConditionEntityLootTableGenerator extends EntityLootTableGenerator {
+	private final EntityLootTableGenerator parent;
+	private final ResourceCondition[] conditions;
+
+	public ConditionEntityLootTableGenerator(EntityLootTableGenerator parent, ResourceCondition[] conditions) {
+		super(FeatureFlags.FEATURE_MANAGER.getFeatureSet(), ((EntityLootTableGeneratorAccessor) parent).getRegistries());
+
+		this.parent = parent;
+		this.conditions = conditions;
+	}
+
+	@Override
+	public void generate() {
+		throw new UnsupportedOperationException("generate() should not be called.");
+	}
+
+	@Override
+	public void register(EntityType<?> entityType, RegistryKey<LootTable> tableKey, LootTable.Builder lootTable) {
+		FabricDataGenHelper.addConditions(lootTable, this.conditions);
+		this.parent.register(entityType, tableKey, lootTable);
+	}
+}
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorAccessor.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorAccessor.java
new file mode 100644
index 000000000..9a0901646
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorAccessor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.datagen.loot;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import net.minecraft.data.loottable.EntityLootTableGenerator;
+import net.minecraft.registry.RegistryWrapper;
+
+@Mixin(EntityLootTableGenerator.class)
+public interface EntityLootTableGeneratorAccessor {
+	@Accessor()
+	RegistryWrapper.WrapperLookup getRegistries();
+}
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorMixin.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorMixin.java
new file mode 100644
index 000000000..8fbeaa61b
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/mixin/datagen/loot/EntityLootTableGeneratorMixin.java
@@ -0,0 +1,27 @@
+/*
+ * 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.datagen.loot;
+
+import org.spongepowered.asm.mixin.Mixin;
+
+import net.minecraft.data.loottable.EntityLootTableGenerator;
+
+import net.fabricmc.fabric.api.datagen.v1.loot.FabricEntityLootTableGenerator;
+
+@Mixin(EntityLootTableGenerator.class)
+public class EntityLootTableGeneratorMixin implements FabricEntityLootTableGenerator {
+}
diff --git a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener
index 4fea74e6f..4445d1db1 100644
--- a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener
+++ b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.accesswidener
@@ -16,6 +16,9 @@ transitive-extendable method net/minecraft/data/tag/TagProvider$ProvidedTagBuild
 accessible field net/minecraft/data/tag/TagProvider tagBuilders Ljava/util/Map;
 
 accessible field net/minecraft/data/loottable/BlockLootTableGenerator lootTables Ljava/util/Map;
+accessible field net/minecraft/data/loottable/EntityLootTableGenerator lootTables Ljava/util/Map;
+transitive-accessible method net/minecraft/data/loottable/EntityLootTableGenerator register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/loot/LootTable$Builder;)V
+transitive-accessible method net/minecraft/data/loottable/EntityLootTableGenerator register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/loot/LootTable$Builder;)V
 
 extendable method net/minecraft/registry/tag/TagEntry <init> (Lnet/minecraft/util/Identifier;ZZ)V
 accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier;
diff --git a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.mixins.json b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.mixins.json
index 0c65f720e..93e301db0 100644
--- a/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.mixins.json
+++ b/fabric-data-generation-api-v1/src/main/resources/fabric-data-generation-api-v1.mixins.json
@@ -9,7 +9,9 @@
     "TagBuilderMixin",
     "TagProviderMixin",
     "loot.BlockLootTableGeneratorAccessor",
-    "loot.BlockLootTableGeneratorMixin"
+    "loot.BlockLootTableGeneratorMixin",
+    "loot.EntityLootTableGeneratorAccessor",
+    "loot.EntityLootTableGeneratorMixin"
   ],
   "server": [
     "server.MainMixin"
diff --git a/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json b/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json
index 008c45e30..14e3ad31e 100644
--- a/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json
+++ b/fabric-data-generation-api-v1/src/main/resources/fabric.mod.json
@@ -31,7 +31,8 @@
   "custom": {
     "fabric-api:module-lifecycle": "stable",
     "loom:injected_interfaces": {
-      "net/minecraft/class_7788": ["net/fabricmc/fabric/api/datagen/v1/loot/FabricBlockLootTableGenerator"]
+      "net/minecraft/class_7788": ["net/fabricmc/fabric/api/datagen/v1/loot/FabricBlockLootTableGenerator"],
+      "net/minecraft/class_7789": ["net/fabricmc/fabric/api/datagen/v1/loot/FabricEntityLootTableGenerator"]
     }
   }
 }
diff --git a/fabric-data-generation-api-v1/src/testmod/generated/data/fabric-data-gen-api-v1-testmod/loot_table/entities/simple_entity.json b/fabric-data-generation-api-v1/src/testmod/generated/data/fabric-data-gen-api-v1-testmod/loot_table/entities/simple_entity.json
new file mode 100644
index 000000000..930a04a24
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/testmod/generated/data/fabric-data-gen-api-v1-testmod/loot_table/entities/simple_entity.json
@@ -0,0 +1,29 @@
+{
+  "fabric:load_conditions": [
+    {
+      "condition": "fabric:not",
+      "value": {
+        "condition": "fabric:not",
+        "value": {
+          "condition": "fabric:true"
+        }
+      }
+    },
+    {
+      "condition": "fabric:true"
+    }
+  ],
+  "type": "minecraft:entity",
+  "pools": [
+    {
+      "bonus_rolls": 0.0,
+      "entries": [
+        {
+          "type": "minecraft:item",
+          "name": "fabric-data-gen-api-v1-testmod:simple_block"
+        }
+      ],
+      "rolls": 1.0
+    }
+  ]
+}
\ No newline at end of file
diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
index bd24be8a8..0ae8b9c63 100644
--- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
+++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
@@ -18,10 +18,14 @@ package net.fabricmc.fabric.test.datagen;
 
 import com.mojang.serialization.Codec;
 import com.mojang.serialization.codecs.RecordCodecBuilder;
+import org.jetbrains.annotations.NotNull;
 
 import net.minecraft.block.AbstractBlock;
 import net.minecraft.block.Block;
 import net.minecraft.block.Blocks;
+import net.minecraft.entity.Entity;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.SpawnGroup;
 import net.minecraft.item.BlockItem;
 import net.minecraft.item.Item;
 import net.minecraft.item.ItemGroup;
@@ -48,6 +52,9 @@ public class DataGeneratorTestContent implements ModInitializer {
 	public static Block BLOCK_WITH_VANILLA_LOOT_TABLE;
 	public static Block BLOCK_THAT_DROPS_NOTHING;
 
+	public static EntityType<?> SIMPLE_ENTITY_TYPE;
+	public static EntityType<?> ENTITY_TYPE_WITHOUT_LOOT_TABLE;
+
 	public static final RegistryKey<ItemGroup> SIMPLE_ITEM_GROUP = RegistryKey.of(RegistryKeys.ITEM_GROUP, Identifier.of(MOD_ID, "simple"));
 
 	public static final RegistryKey<Registry<TestDatagenObject>> TEST_DATAGEN_DYNAMIC_REGISTRY_KEY =
@@ -72,6 +79,9 @@ public class DataGeneratorTestContent implements ModInitializer {
 		BLOCK_WITH_VANILLA_LOOT_TABLE = createBlock("block_with_vanilla_loot_table", false, AbstractBlock.Settings.create().lootTable(Blocks.STONE.getLootTableKey()));
 		BLOCK_THAT_DROPS_NOTHING = createBlock("block_that_drops_nothing", false, AbstractBlock.Settings.create().dropsNothing());
 
+		SIMPLE_ENTITY_TYPE = createEntityType("simple_entity", EntityType.Builder.create(SpawnGroup.MISC));
+		ENTITY_TYPE_WITHOUT_LOOT_TABLE = createEntityType("entity_without_loot_table", EntityType.Builder.create(SpawnGroup.MISC));
+
 		ItemGroupEvents.modifyEntriesEvent(SIMPLE_ITEM_GROUP).register(entries -> entries.add(SIMPLE_BLOCK));
 
 		Registry.register(Registries.ITEM_GROUP, SIMPLE_ITEM_GROUP, FabricItemGroup.builder()
@@ -94,6 +104,12 @@ public class DataGeneratorTestContent implements ModInitializer {
 		return block;
 	}
 
+	private static <E extends Entity> EntityType<E> createEntityType(String name, EntityType.@NotNull Builder<E> builder) {
+		RegistryKey<EntityType<?>> key = RegistryKey.of(RegistryKeys.ENTITY_TYPE, Identifier.of(MOD_ID, name));
+
+		return Registry.register(Registries.ENTITY_TYPE, key, builder.build(key));
+	}
+
 	public record TestDatagenObject(String value) {
 		public static final Codec<TestDatagenObject> CODEC = RecordCodecBuilder.create(instance -> instance.group(
 				Codec.STRING.fieldOf("value").forGetter(TestDatagenObject::value)
diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
index fc852df56..2f86a4aa3 100644
--- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
+++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
@@ -18,8 +18,10 @@ package net.fabricmc.fabric.test.datagen;
 
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.BLOCK_WITHOUT_ITEM;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.BLOCK_WITHOUT_LOOT_TABLE;
+import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.ENTITY_TYPE_WITHOUT_LOOT_TABLE;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.MOD_ID;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_BLOCK;
+import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_ENTITY_TYPE;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_ITEM_GROUP;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.TEST_DATAGEN_DYNAMIC_REGISTRY_KEY;
 import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.TEST_DYNAMIC_REGISTRY_EXTRA_ITEM_KEY;
@@ -88,6 +90,7 @@ import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider;
+import net.fabricmc.fabric.api.datagen.v1.provider.FabricEntityLootTableProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
 import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
@@ -113,6 +116,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
 		pack.addProvider(TestRecipeProvider::new);
 		pack.addProvider(TestAdvancementProvider::new);
 		pack.addProvider(TestBlockLootTableProvider::new);
+		pack.addProvider(TestEntityLootTableProvider::new);
 		pack.addProvider(TestBarterLootTableProvider::new);
 		pack.addProvider(ExistingEnglishLangProvider::new);
 		pack.addProvider(JapaneseLangProvider::new);
@@ -395,6 +399,24 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
 		}
 	}
 
+	public static class TestEntityLootTableProvider extends FabricEntityLootTableProvider {
+		private TestEntityLootTableProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
+			super(output, registryLookup);
+		}
+
+		@Override
+		public void generate() {
+			this.withConditions(ALWAYS_LOADED)
+					.withConditions(ResourceConditions.not(NEVER_LOADED))
+					.register(
+							SIMPLE_ENTITY_TYPE,
+							LootTable.builder().pool(LootPool.builder().with(ItemEntry.builder(SIMPLE_BLOCK.asItem())))
+					);
+
+			this.excludeFromStrictValidation(ENTITY_TYPE_WITHOUT_LOOT_TABLE);
+		}
+	}
+
 	private static class TestBarterLootTableProvider extends SimpleFabricLootTableProvider {
 		private TestBarterLootTableProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
 			super(output, registryLookup, LootContextTypes.BARTER);
diff --git a/fabric-data-generation-api-v1/template.accesswidener b/fabric-data-generation-api-v1/template.accesswidener
index 578f3e7e0..12d41fea5 100644
--- a/fabric-data-generation-api-v1/template.accesswidener
+++ b/fabric-data-generation-api-v1/template.accesswidener
@@ -11,6 +11,9 @@ transitive-extendable method net/minecraft/data/tag/TagProvider$ProvidedTagBuild
 accessible field net/minecraft/data/tag/TagProvider tagBuilders Ljava/util/Map;
 
 accessible field net/minecraft/data/loottable/BlockLootTableGenerator lootTables Ljava/util/Map;
+accessible field net/minecraft/data/loottable/EntityLootTableGenerator lootTables Ljava/util/Map;
+transitive-accessible method net/minecraft/data/loottable/EntityLootTableGenerator register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/loot/LootTable$Builder;)V
+transitive-accessible method net/minecraft/data/loottable/EntityLootTableGenerator register (Lnet/minecraft/entity/EntityType;Lnet/minecraft/registry/RegistryKey;Lnet/minecraft/loot/LootTable$Builder;)V
 
 extendable method net/minecraft/registry/tag/TagEntry <init> (Lnet/minecraft/util/Identifier;ZZ)V
 accessible field net/minecraft/registry/tag/TagEntry id Lnet/minecraft/util/Identifier;