Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fabric-data-generation-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ moduleDependencies(project, [

testDependencies(project, [
':fabric-item-group-api-v1',
':fabric-loot-api-v3',
':fabric-object-builder-api-v1'
])

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:apple"
}
],
"rolls": 1.0
}
],
"target": {
"type": "fabric:require_all",
"children": [
{
"type": "fabric:source",
"sources": "any_builtin"
},
{
"type": "fabric:loot_table_id",
"loot_tables": [
"minecraft:blocks/acacia_button"
]
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"functions": [
{
"add": false,
"count": 10.0,
"function": "minecraft:set_count"
}
],
"pools": [
{
"bonus_rolls": 0.0,
"conditions": [
{
"condition": "minecraft:survives_explosion"
}
],
"entries": [
{
"type": "minecraft:item",
"name": "minecraft:golden_apple"
}
],
"rolls": 1.0
}
],
"target": {
"type": "fabric:require_any",
"children": [
{
"type": "fabric:loot_table_id",
"loot_tables": [
"minecraft:blocks/anvil"
]
},
{
"type": "fabric:require_all",
"children": [
{
"type": "fabric:loot_table_id",
"loot_tables": [
"minecraft:entities/allay"
]
},
{
"type": "fabric:source",
"sources": [
"data_pack"
]
}
]
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@
import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.LootTable;
import net.minecraft.world.level.storage.loot.entries.LootItem;
import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import net.minecraft.world.level.storage.loot.predicates.ExplosionCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition;
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
Expand All @@ -96,6 +98,9 @@
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.SimpleFabricLootTableProvider;
import net.fabricmc.fabric.api.loot.v3.LootModifier;
import net.fabricmc.fabric.api.loot.v3.LootModifierTarget;
import net.fabricmc.fabric.api.loot.v3.LootTableSource;
import net.fabricmc.fabric.api.recipe.v1.ingredient.DefaultCustomIngredients;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
Expand Down Expand Up @@ -123,6 +128,7 @@ public void onInitializeDataGenerator(FabricDataGenerator dataGenerator) {
pack.addProvider(JapaneseLangProvider::new);
pack.addProvider(TestDynamicRegistryProvider::new);
pack.addProvider(TestPredicateProvider::new);
pack.addProvider(TestLootModifierProvider::new);
pack.addProvider(TestCustomCodecProvider::new);

TestBlockTagProvider blockTagProvider = pack.addProvider(TestBlockTagProvider::new);
Expand Down Expand Up @@ -496,6 +502,44 @@ public String getName() {
}
}

private static class TestLootModifierProvider extends FabricCodecDataProvider<LootModifier> {
private TestLootModifierProvider(FabricDataOutput dataOutput, CompletableFuture<HolderLookup.Provider> registriesFuture) {
super(dataOutput, registriesFuture, PackOutput.Target.DATA_PACK, LootModifier.DATA_DIRECTORY, LootModifier.CODEC);
}

@Override
protected void configure(BiConsumer<Identifier, LootModifier> provider, HolderLookup.Provider lookup) {
LootModifier modifierA = LootModifier.builder()
.target(LootModifierTarget.all(LootModifierTarget.builtinSource(), LootModifierTarget.lootTable(net.minecraft.world.level.block.Blocks.ACACIA_BUTTON.getLootTable().orElseThrow())))
.pools(LootPool.lootPool()
.add(LootItem.lootTableItem(Items.APPLE))
.when(ExplosionCondition.survivesExplosion())
.build())
.build();
LootModifier modifierB = LootModifier.builder()
.target(LootModifierTarget.any(
LootModifierTarget.lootTable(net.minecraft.world.level.block.Blocks.ANVIL),
LootModifierTarget.all(
LootModifierTarget.lootTable(EntityType.ALLAY),
LootModifierTarget.source(LootTableSource.DATA_PACK)
)
))
.pools(LootPool.lootPool()
.add(LootItem.lootTableItem(Items.GOLDEN_APPLE))
.when(ExplosionCondition.survivesExplosion()))
.functions(SetItemCountFunction.setCount(ConstantValue.exactly(10)))
.build();

provider.accept(Identifier.fromNamespaceAndPath(MOD_ID, "modifier_a"), modifierA);
provider.accept(Identifier.fromNamespaceAndPath(MOD_ID, "modifier_b"), modifierB);
}

@Override
public String getName() {
return "Loot Modifiers";
}
}

private static class TestCustomCodecProvider extends FabricCodecDataProvider<TestCustomCodecProvider.Entry> {
private TestCustomCodecProvider(FabricDataOutput dataOutput, CompletableFuture<HolderLookup.Provider> registriesFuture) {
super(dataOutput, registriesFuture, PackOutput.Target.DATA_PACK, "biome_entry", Entry.CODEC);
Expand Down
1 change: 1 addition & 0 deletions fabric-loot-api-v3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ version = getSubprojectVersion(project)

moduleDependencies(project, [
'fabric-api-base',
'fabric-registry-sync-v0',
'fabric-resource-loader-v0'
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package net.fabricmc.fabric.api.loot.v3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import com.mojang.serialization.Codec;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.Nullable;

import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;

import net.fabricmc.fabric.impl.loot.LootModifierImpl;

/**
* A data-driven modifier for loot tables.
* Loot modifiers can add new {@linkplain LootPool pools} and {@linkplain LootItemFunction functions}
* to loot tables without overriding loot table data files.
*
* <p>Loot modifiers can be defined in the {@link #DATA_DIRECTORY data/[namespace]/fabric/loot_modifier}
* directory as JSON files with the following format: TODO add format
* {@snippet lang=json :
* "something"
* }
* You can also generate loot modifier files using {@link net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider
* FabricCodecDataProvider}.
* New modifiers can be created with a {@linkplain #builder() builder}.
*
* <p>Loot modifiers determine which loot tables they modify by using a {@link LootModifierTarget}.
* You can use them to target specific loot tables or only builtin loot tables, just like with {@link LootTableEvents#MODIFY}.
*/
@ApiStatus.NonExtendable
public interface LootModifier {
/**
* The data directory for loot modifiers.
*/
String DATA_DIRECTORY = "fabric/loot_modifier";

/**
* The loot modifier codec.
*/
Codec<LootModifier> CODEC = LootModifierImpl.CODEC;

/**
* {@return the target of this loot modifier}
*/
LootModifierTarget target();

/**
* {@return the pools added by this loot modifier}
*/
List<LootPool> pools();

/**
* {@return the functions added by this loot modifier}
*/
List<LootItemFunction> functions();

/**
* {@return a new loot modifier builder}
*/
static Builder builder() {
return new Builder();
}

/**
* A builder for {@link LootModifier}.
*/
final class Builder {
private @Nullable LootModifierTarget target;
private final List<LootPool> pools = new ArrayList<>();
private final List<LootItemFunction> functions = new ArrayList<>();

/**
* Sets the loot modifier target.
*
* @param target the target
* @return this builder
*/
public Builder target(LootModifierTarget target) {
Objects.requireNonNull(target, "Loot modifier target cannot be null");
this.target = target;
return this;
}

/**
* Adds pools to this builder.
*
* @param pools the pools to add
* @return this builder
*/
public Builder pools(LootPool.Builder... pools) {
return pools(Arrays.stream(pools).map(LootPool.Builder::build).toList());
}

/**
* Adds pools to this builder.
*
* @param pools the pools to add
* @return this builder
*/
public Builder pools(LootPool... pools) {
return pools(Arrays.asList(pools));
}

/**
* Adds pools to this builder.
*
* @param pools the pools to add
* @return this builder
*/
public Builder pools(Collection<? extends LootPool> pools) {
this.pools.addAll(pools);
return this;
}

/**
* Adds functions to this builder.
*
* @param functions the functions to add
* @return this builder
*/
public Builder functions(LootItemFunction.Builder... functions) {
return functions(Arrays.stream(functions).map(LootItemFunction.Builder::build).toList());
}

/**
* Adds functions to this builder.
*
* @param functions the functions to add
* @return this builder
*/
public Builder functions(LootItemFunction... functions) {
return functions(Arrays.asList(functions));
}

/**
* Adds functions to this builder.
*
* @param functions the functions to add
* @return this builder
*/
public Builder functions(Collection<? extends LootItemFunction> functions) {
this.functions.addAll(functions);
return this;
}

// TODO: test datagenning

/**
* Builds a loot modifier from this builder.
*
* @return the created modifier
* @throws NullPointerException if the target hasn't been set
*/
public LootModifier build() {
Objects.requireNonNull(target, "Loot modifier target not set");
return new LootModifierImpl(target, pools, functions);
}
}
}
Loading
Loading