fabric-loot-api-v3 (#3903)

* fabric-loot-api-v3

* Review fixes

* Fix tests

* More fixes
This commit is contained in:
modmuss 2024-07-07 13:52:31 +01:00 committed by GitHub
parent d57e92a907
commit 3f89f5a5c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 852 additions and 108 deletions

View file

@ -0,0 +1,7 @@
version = getSubprojectVersion(project)
moduleDependencies(project, [
'fabric-api-base',
'fabric-resource-loader-v0',
'fabric-loot-api-v3'
])

View file

@ -0,0 +1,121 @@
/*
* 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.loot.v2;
import java.util.Collection;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.condition.LootCondition;
import net.minecraft.loot.entry.LootPoolEntry;
import net.minecraft.loot.function.LootFunction;
/**
* Convenience extensions to {@link LootPool.Builder}
* for adding pre-built objects or collections.
*
* <p>This interface is automatically injected to {@link LootPool.Builder}.
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder} instead.
*/
@ApiStatus.NonExtendable
@Deprecated
public interface FabricLootPoolBuilder {
/**
* Adds an entry to this builder.
*
* @param entry the added loot entry
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#with(LootPoolEntry)} instead.
*/
@Deprecated
default LootPool.Builder with(LootPoolEntry entry) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Adds entries to this builder.
*
* @param entries the added loot entries
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#with(LootPoolEntry)} instead.
*/
@Deprecated
default LootPool.Builder with(Collection<? extends LootPoolEntry> entries) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Adds a condition to this builder.
*
* @param condition the added condition
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#conditionally(LootCondition)} instead.
*/
@Deprecated
default LootPool.Builder conditionally(LootCondition condition) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Adds conditions to this builder.
*
* @param conditions the added conditions
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#conditionally(LootCondition)} instead.
*/
@Deprecated
default LootPool.Builder conditionally(Collection<? extends LootCondition> conditions) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Applies a function to this builder.
*
* @param function the applied loot function
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#apply(LootFunction)} instead.
*/
@Deprecated
default LootPool.Builder apply(LootFunction function) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Applies loot functions to this builder.
*
* @param functions the applied loot functions
* @return this builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#apply(LootFunction)} instead.
*/
@Deprecated
default LootPool.Builder apply(Collection<? extends LootFunction> functions) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Creates a builder copy of a loot pool.
*
* @param pool the loot pool
* @return the copied builder
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder#copyOf(LootPool)} instead.
*/
@Deprecated
static LootPool.Builder copyOf(LootPool pool) {
return net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder.copyOf(pool);
}
}

View file

@ -0,0 +1,117 @@
/*
* 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.loot.v2;
import java.util.Collection;
import java.util.function.Consumer;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTable;
import net.minecraft.loot.function.LootFunction;
/**
* Convenience extensions to {@link LootTable.Builder}
* for adding pre-built objects or collections and modifying loot pools.
*
* <p>This interface is automatically injected to {@link LootTable.Builder}.
*
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder} instead.
*/
@ApiStatus.NonExtendable
@Deprecated
public interface FabricLootTableBuilder {
/**
* Adds a loot pool to this builder.
*
* @param pool the added pool
* @return this builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#pool(LootPool)} instead.
*/
@Deprecated
default LootTable.Builder pool(LootPool pool) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Applies a loot function to this builder.
*
* @param function the applied function
* @return this builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#apply(LootFunction)} instead.
*/
@Deprecated
default LootTable.Builder apply(LootFunction function) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Adds loot pools to this builder.
*
* @param pools the added pools
* @return this builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#pools(Collection)} instead.
*/
@Deprecated
default LootTable.Builder pools(Collection<? extends LootPool> pools) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Applies loot functions to this builder.
*
* @param functions the applied functions
* @return this builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#apply(Collection)} instead.
*/
@Deprecated
default LootTable.Builder apply(Collection<? extends LootFunction> functions) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Modifies all loot pools already present in this builder.
*
* <p>This method can be used instead of simply adding a new pool
* when you want the loot table to only drop items from one of the loot pool entries
* instead of both.
*
* <p>Calling this method turns all pools into builders and rebuilds them back into loot pools afterwards,
* so it is more efficient to do all transformations with one {@code modifyPools} call.
*
* @param modifier the modifying function
* @return this builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#modifyPools(Consumer)} instead.
*/
@Deprecated
default LootTable.Builder modifyPools(Consumer<? super LootPool.Builder> modifier) {
throw new UnsupportedOperationException("Implemented via mixin");
}
/**
* Creates a builder copy of a loot table.
*
* @param table the loot table
* @return the copied builder
* @deprecated use {@link net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder#copyOf(LootTable)} instead.
*/
@Deprecated
static LootTable.Builder copyOf(LootTable table) {
return net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder.copyOf(table);
}
}

View file

@ -28,7 +28,10 @@ import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events for manipulating loot tables.
*
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.LootTableEvents} instead.
*/
@Deprecated
public final class LootTableEvents {
private LootTableEvents() {
}
@ -36,7 +39,10 @@ public final class LootTableEvents {
/**
* This event can be used to replace loot tables.
* If a loot table is replaced, the iteration will stop for that loot table.
*
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.LootTableEvents#REPLACE} instead.
*/
@Deprecated
public static final Event<Replace> REPLACE = EventFactory.createArrayBacked(Replace.class, listeners -> (key, original, source) -> {
for (Replace listener : listeners) {
@Nullable LootTable replaced = listener.replaceLootTable(key, original, source);
@ -82,7 +88,10 @@ public final class LootTableEvents {
* });
* }
* </pre>
*
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.LootTableEvents#MODIFY} instead.
*/
@Deprecated
public static final Event<Modify> MODIFY = EventFactory.createArrayBacked(Modify.class, listeners -> (key, tableBuilder, source) -> {
for (Modify listener : listeners) {
listener.modifyLootTable(key, tableBuilder, source);
@ -91,13 +100,17 @@ public final class LootTableEvents {
/**
* This event can be used for post-processing after all loot tables have been loaded and modified by Fabric.
*
* @deprecated Please use {@link net.fabricmc.fabric.api.loot.v3.LootTableEvents#ALL_LOADED} instead.
*/
@Deprecated
public static final Event<Loaded> ALL_LOADED = EventFactory.createArrayBacked(Loaded.class, listeners -> (resourceManager, lootManager) -> {
for (Loaded listener : listeners) {
listener.onLootTablesLoaded(resourceManager, lootManager);
}
});
@Deprecated
public interface Replace {
/**
* Replaces loot tables.
@ -111,6 +124,7 @@ public final class LootTableEvents {
LootTable replaceLootTable(RegistryKey<LootTable> key, LootTable original, LootTableSource source);
}
@Deprecated
public interface Modify {
/**
* Called when a loot table is loading to modify loot tables.
@ -122,6 +136,7 @@ public final class LootTableEvents {
void modifyLootTable(RegistryKey<LootTable> key, LootTable.Builder tableBuilder, LootTableSource source);
}
@Deprecated
public interface Loaded {
/**
* Called when all loot tables have been loaded and {@link LootTableEvents#REPLACE} and {@link LootTableEvents#MODIFY} have been invoked.

View file

@ -0,0 +1,65 @@
/*
* 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.loot.v2;
/**
* Describes where a loot table has been loaded from.
* @deprecated Use {@link net.fabricmc.fabric.api.loot.v3.LootTableSource} instead.
*/
@Deprecated
public enum LootTableSource {
/**
* A loot table loaded from the default data pack.
*/
VANILLA(true),
/**
* A loot table loaded from mods' bundled resources.
*
* <p>This includes the additional builtin data packs registered by mods
* with Fabric Resource Loader.
*/
MOD(true),
/**
* A loot table loaded from an external data pack.
*/
DATA_PACK(false),
/**
* A loot table created in {@link LootTableEvents#REPLACE}.
*/
REPLACED(false);
private final boolean builtin;
LootTableSource(boolean builtin) {
this.builtin = builtin;
}
/**
* Returns whether this loot table source is builtin
* and bundled in the vanilla or mod resources.
*
* <p>{@link #VANILLA} and {@link #MOD} are builtin.
*
* @return {@code true} if builtin, {@code false} otherwise
*/
public boolean isBuiltin() {
return builtin;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.loot.v2;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.loot.v3.LootTableEvents;
import net.fabricmc.fabric.api.loot.v3.LootTableSource;
public class LootInitializer implements ModInitializer {
@Override
public void onInitialize() {
// Forward the events to the v2 API.
LootTableEvents.REPLACE.register(((key, original, source, registries) -> net.fabricmc.fabric.api.loot.v2.LootTableEvents.REPLACE.invoker().replaceLootTable(key, original, toV2Source(source))));
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> net.fabricmc.fabric.api.loot.v2.LootTableEvents.MODIFY.invoker().modifyLootTable(key, tableBuilder, toV2Source(source)));
LootTableEvents.ALL_LOADED.register((resourceManager, lootRegistry) -> net.fabricmc.fabric.api.loot.v2.LootTableEvents.ALL_LOADED.invoker().onLootTablesLoaded(resourceManager, lootRegistry));
}
private static net.fabricmc.fabric.api.loot.v2.LootTableSource toV2Source(LootTableSource source) {
return switch (source) {
case VANILLA -> net.fabricmc.fabric.api.loot.v2.LootTableSource.VANILLA;
case MOD -> net.fabricmc.fabric.api.loot.v2.LootTableSource.MOD;
case DATA_PACK -> net.fabricmc.fabric.api.loot.v2.LootTableSource.DATA_PACK;
case REPLACED -> net.fabricmc.fabric.api.loot.v2.LootTableSource.REPLACED;
};
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.loot.v2;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.loot.LootPool;
import net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder;
/**
* The v3 module injects all the necessary methods into the target class.
*/
@Mixin(LootPool.Builder.class)
abstract class LootPoolBuilderMixin implements FabricLootPoolBuilder {
}

View file

@ -0,0 +1,30 @@
/*
* 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.loot.v2;
import org.spongepowered.asm.mixin.Mixin;
import net.minecraft.loot.LootTable;
import net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder;
/**
* The v3 module injects all the necessary methods into the target class.
*/
@Mixin(LootTable.Builder.class)
abstract class LootTableBuilderMixin implements FabricLootTableBuilder {
}

View file

@ -0,0 +1,12 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.loot.v2",
"compatibilityLevel": "JAVA_17",
"mixins": [
"LootPoolBuilderMixin",
"LootTableBuilderMixin"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,35 @@
{
"schemaVersion": 1,
"id": "fabric-loot-api-v2",
"name": "Fabric Loot API (v2)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-loot-api-v2/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabricloader": ">=0.15.11",
"fabric-api-base": "*",
"fabric-resource-loader-v0": "*"
},
"description": "Hooks for manipulating loot tables.",
"entrypoints": {
"main": [
"net.fabricmc.fabric.impl.loot.v2.LootInitializer"
]
},
"mixins": [
"fabric-loot-api-v2.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "deprecated"
}
}

View file

@ -1,84 +0,0 @@
/*
* 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.loot;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.loot.LootDataType;
import net.minecraft.loot.LootTable;
import net.minecraft.registry.MutableRegistry;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.ReloadableRegistries;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder;
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.loot.v2.LootTableSource;
import net.fabricmc.fabric.impl.loot.LootUtil;
/**
* Implements the events from {@link LootTableEvents}.
*/
@Mixin(ReloadableRegistries.class)
abstract class ReloadableRegistriesMixin {
@ModifyArg(method = "method_58286", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/MutableRegistry;add(Lnet/minecraft/registry/RegistryKey;Ljava/lang/Object;Lnet/minecraft/registry/entry/RegistryEntryInfo;)Lnet/minecraft/registry/entry/RegistryEntry$Reference;"), index = 1)
private static Object modifyLootTable(Object value, @Local(argsOnly = true) Identifier id) {
if (!(value instanceof LootTable table)) return value;
if (table == LootTable.EMPTY) {
// This is a special table and cannot be modified.
return value;
}
RegistryKey<LootTable> key = RegistryKey.of(RegistryKeys.LOOT_TABLE, id);
// Populated inside JsonDataLoaderMixin
LootTableSource source = LootUtil.SOURCES.get().getOrDefault(id, LootTableSource.DATA_PACK);
// Invoke the REPLACE event for the current loot table.
LootTable replacement = LootTableEvents.REPLACE.invoker().replaceLootTable(key, table, source);
if (replacement != null) {
// Set the loot table to MODIFY to be the replacement loot table.
// The MODIFY event will also see it as a replaced loot table via the source.
table = replacement;
source = LootTableSource.REPLACED;
}
// Turn the current table into a modifiable builder and invoke the MODIFY event.
LootTable.Builder builder = FabricLootTableBuilder.copyOf(table);
LootTableEvents.MODIFY.invoker().modifyLootTable(key, builder, source);
return builder.build();
}
@SuppressWarnings("unchecked")
@Inject(method = "method_58279", at = @At("RETURN"))
private static void onLootTablesLoaded(LootDataType lootDataType, ResourceManager resourceManager, RegistryOps registryOps, CallbackInfoReturnable<MutableRegistry> cir) {
if (lootDataType != LootDataType.LOOT_TABLES) return;
LootTableEvents.ALL_LOADED.invoker().onLootTablesLoaded(resourceManager, (Registry<LootTable>) cir.getReturnValue());
LootUtil.SOURCES.remove();
}
}

View file

@ -0,0 +1,29 @@
# Fabric Loot API (v3)
This module includes APIs for modifying and creating loot tables.
## [Loot table events](src/main/java/net/fabricmc/fabric/api/loot/v3/LootTableEvents.java)
This class provides two events for modifying loot tables.
`LootTableEvents.REPLACE` runs first and lets you replace loot tables completely.
`LootTableEvents.MODIFY` runs after and lets you modify loot tables, including the ones created in `REPLACE`,
by adding new loot pools or loot functions to them.
### Loot table sources
Both events have access to a [loot table source](src/main/java/net/fabricmc/fabric/api/loot/v3/LootTableSource.java)
that you can use to check where a loot table is loaded from.
For example, you can use this to check if a loot table is from a user data pack and
not modify the user-provided data in your event.
## Enhanced loot table and loot pool builders
`LootTable.Builder` and `LootPool.Builder` implement
injected interfaces ([`FabricLootTableBuilder`](src/main/java/net/fabricmc/fabric/api/loot/v3/FabricLootTableBuilder.java)
and [`FabricLootPoolBuilder`](src/main/java/net/fabricmc/fabric/api/loot/v3/FabricLootPoolBuilder.java))
which have additional methods for dealing with already-built objects and collections of objects.
Those interfaces also have `copyOf` methods for creating copies of existing loot tables/pools as builders.

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v2;
package net.fabricmc.fabric.api.loot.v3;
import java.util.Collection;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v2;
package net.fabricmc.fabric.api.loot.v3;
import java.util.Collection;
import java.util.function.Consumer;

View file

@ -0,0 +1,137 @@
/*
* 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.loot.v3;
import org.jetbrains.annotations.Nullable;
import net.minecraft.loot.LootTable;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.resource.ResourceManager;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events for manipulating loot tables.
*/
public final class LootTableEvents {
private LootTableEvents() {
}
/**
* This event can be used to replace loot tables.
* If a loot table is replaced, the iteration will stop for that loot table.
*/
public static final Event<Replace> REPLACE = EventFactory.createArrayBacked(Replace.class, listeners -> (key, original, source, registries) -> {
for (Replace listener : listeners) {
@Nullable LootTable replaced = listener.replaceLootTable(key, original, source, registries);
if (replaced != null) {
return replaced;
}
}
return null;
});
/**
* This event can be used to modify loot tables.
* The main use case is to add items to vanilla or mod loot tables (e.g. modded seeds to grass).
*
* <p>You can also modify loot tables that are created by {@link #REPLACE}.
* They have the loot table source {@link LootTableSource#REPLACED}.
*
* <h2>Example: adding diamonds to the cobblestone loot table</h2>
* We'll add a new diamond {@linkplain net.minecraft.loot.LootPool loot pool} to the cobblestone loot table
* that will be dropped alongside the original cobblestone loot pool.
*
* <p>If you want only one of the items to drop, you can use
* {@link FabricLootTableBuilder#modifyPools(java.util.function.Consumer)} to add the new item to
* the original loot pool instead.
* {@snippet :
* LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
* // If the loot table is for the cobblestone block and it is not overridden by a user:
* if (Blocks.COBBLESTONE.getLootTableKey() == key && source.isBuiltin()) {
* // Create a new loot pool that will hold the diamonds.
* LootPool.Builder pool = LootPool.builder()
* // Add diamonds...
* .with(ItemEntry.builder(Items.DIAMOND))
* // ...only if the block would survive a potential explosion.
* .conditionally(SurvivesExplosionLootCondition.builder());
*
* // Add the loot pool to the loot table
* tableBuilder.pool(pool);
* }
* });
* }
*/
public static final Event<Modify> MODIFY = EventFactory.createArrayBacked(Modify.class, listeners -> (key, tableBuilder, source, registries) -> {
for (Modify listener : listeners) {
listener.modifyLootTable(key, tableBuilder, source, registries);
}
});
/**
* This event can be used for post-processing after all loot tables have been loaded and modified by Fabric.
*/
public static final Event<Loaded> ALL_LOADED = EventFactory.createArrayBacked(Loaded.class, listeners -> (resourceManager, lootManager) -> {
for (Loaded listener : listeners) {
listener.onLootTablesLoaded(resourceManager, lootManager);
}
});
@FunctionalInterface
public interface Replace {
/**
* Replaces loot tables.
*
* @param key the loot table key
* @param original the original loot table
* @param source the source of the original loot table
* @param registries the registry wrapper lookup
* @return the new loot table, or null if it wasn't replaced
*/
@Nullable
LootTable replaceLootTable(RegistryKey<LootTable> key, LootTable original, LootTableSource source, RegistryWrapper.WrapperLookup registries);
}
@FunctionalInterface
public interface Modify {
/**
* Called when a loot table is loading to modify loot tables.
*
* @param key the loot table key
* @param tableBuilder a builder of the loot table being loaded
* @param source the source of the loot table
* @param registries the registry wrapper lookup
*/
void modifyLootTable(RegistryKey<LootTable> key, LootTable.Builder tableBuilder, LootTableSource source, RegistryWrapper.WrapperLookup registries);
}
@FunctionalInterface
public interface Loaded {
/**
* Called when all loot tables have been loaded and {@link LootTableEvents#REPLACE} and {@link LootTableEvents#MODIFY} have been invoked.
*
* @param resourceManager the server resource manager
* @param lootRegistry the loot registry
*/
void onLootTablesLoaded(ResourceManager resourceManager, Registry<LootTable> lootRegistry);
}
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v2;
package net.fabricmc.fabric.api.loot.v3;
/**
* Describes where a loot table has been loaded from.

View file

@ -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.
*/
/**
* The Fabric Loot API for manipulating and creating loot tables.
*
* <h2>Events</h2>
* {@link net.fabricmc.fabric.api.loot.v3.LootTableEvents} has events to modify existing loot tables,
* or outright replace them with a new loot table.
*
* <p>You can also check where loot tables are coming from in those events with
* {@link net.fabricmc.fabric.api.loot.v3.LootTableSource}. This is useful when you only want to modify
* loot tables from mods or vanilla, but not user-created data packs.
*
* <h2>Extended loot table and pool builders</h2>
* This API has injected interfaces to add useful methods to
* {@linkplain net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder loot table} and
* {@linkplain net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder loot pool} builders.
* They let you add pre-built objects instead of builders, and collections of objects to the builder
* with one method call.
*/
package net.fabricmc.fabric.api.loot.v3;

View file

@ -23,7 +23,7 @@ import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourcePackSource;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.loot.v2.LootTableSource;
import net.fabricmc.fabric.api.loot.v3.LootTableSource;
import net.fabricmc.fabric.impl.resource.loader.BuiltinModResourcePackSource;
import net.fabricmc.fabric.impl.resource.loader.FabricResource;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;

View file

@ -27,8 +27,10 @@ import net.minecraft.loot.entry.LootPoolEntry;
import net.minecraft.loot.function.LootFunction;
import net.minecraft.loot.provider.number.LootNumberProvider;
import net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder;
/**
* Accesses loot pool fields for {@link net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder#copyOf(LootPool)}.
* Accesses loot pool fields for {@link FabricLootPoolBuilder#copyOf(LootPool)}.
* These are normally available in the transitive access widener module.
*/
@Mixin(LootPool.class)

View file

@ -29,7 +29,7 @@ import net.minecraft.loot.condition.LootCondition;
import net.minecraft.loot.entry.LootPoolEntry;
import net.minecraft.loot.function.LootFunction;
import net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder;
import net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder;
/**
* The implementation of the injected interface {@link FabricLootPoolBuilder}.

View file

@ -27,8 +27,10 @@ import net.minecraft.loot.LootTable;
import net.minecraft.loot.function.LootFunction;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder;
/**
* Accesses loot table fields for {@link net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder#copyOf(LootTable)}.
* Accesses loot table fields for {@link FabricLootTableBuilder#copyOf(LootTable)}.
* These are normally available in the transitive access widener module.
*/
@Mixin(LootTable.class)

View file

@ -32,8 +32,8 @@ import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTable;
import net.minecraft.loot.function.LootFunction;
import net.fabricmc.fabric.api.loot.v2.FabricLootPoolBuilder;
import net.fabricmc.fabric.api.loot.v2.FabricLootTableBuilder;
import net.fabricmc.fabric.api.loot.v3.FabricLootPoolBuilder;
import net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder;
/**
* The implementation of the injected interface {@link FabricLootTableBuilder}.

View file

@ -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.mixin.loot;
import java.util.List;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Function;
import com.google.gson.JsonElement;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import com.mojang.serialization.DynamicOps;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Coerce;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.loot.LootDataType;
import net.minecraft.loot.LootTable;
import net.minecraft.registry.CombinedDynamicRegistries;
import net.minecraft.registry.MutableRegistry;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.ReloadableRegistries;
import net.minecraft.registry.ServerDynamicRegistryType;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.loot.v3.FabricLootTableBuilder;
import net.fabricmc.fabric.api.loot.v3.LootTableEvents;
import net.fabricmc.fabric.api.loot.v3.LootTableSource;
import net.fabricmc.fabric.impl.loot.LootUtil;
/**
* Implements the events from {@link LootTableEvents}.
*/
@Mixin(ReloadableRegistries.class)
abstract class ReloadableRegistriesMixin {
/**
* Due to possible cross-thread handling, this uses WeakHashMap instead of ThreadLocal.
*/
@Unique
private static final WeakHashMap<RegistryOps<JsonElement>, RegistryWrapper.WrapperLookup> WRAPPERS = new WeakHashMap<>();
@WrapOperation(method = "reload", at = @At(value = "INVOKE", target = "Lnet/minecraft/registry/ReloadableRegistries$ReloadableWrapperLookup;getOps(Lcom/mojang/serialization/DynamicOps;)Lnet/minecraft/registry/RegistryOps;"))
private static RegistryOps<JsonElement> storeOps(@Coerce RegistryWrapper.WrapperLookup registries, DynamicOps<JsonElement> ops, Operation<RegistryOps<JsonElement>> original) {
RegistryOps<JsonElement> created = original.call(registries, ops);
WRAPPERS.put(created, registries);
return created;
}
@WrapOperation(method = "reload", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;thenApplyAsync(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;"))
private static CompletableFuture<CombinedDynamicRegistries<ServerDynamicRegistryType>> removeOps(CompletableFuture<List<MutableRegistry<?>>> future, Function<? super List<MutableRegistry<?>>, ? extends CombinedDynamicRegistries<ServerDynamicRegistryType>> fn, Executor executor, Operation<CompletableFuture<CombinedDynamicRegistries<ServerDynamicRegistryType>>> original, @Local RegistryOps<JsonElement> ops) {
return original.call(future.thenApply(v -> {
WRAPPERS.remove(ops);
return v;
}), fn, executor);
}
@WrapOperation(method = "method_58278", at = @At(value = "INVOKE", target = "Ljava/util/Optional;ifPresent(Ljava/util/function/Consumer;)V"))
private static <T> void modifyLootTable(Optional<T> optionalTable, Consumer<? super T> action, Operation<Void> original, @Local(argsOnly = true) Identifier id, @Local(argsOnly = true) RegistryOps<JsonElement> ops) {
original.call(optionalTable.map(table -> modifyLootTable(table, id, ops)), action);
}
@Unique
private static <T> T modifyLootTable(T value, Identifier id, RegistryOps<JsonElement> ops) {
if (!(value instanceof LootTable table)) return value;
if (table == LootTable.EMPTY) {
// This is a special table and cannot be modified.
return value;
}
RegistryKey<LootTable> key = RegistryKey.of(RegistryKeys.LOOT_TABLE, id);
// Populated above.
RegistryWrapper.WrapperLookup registries = WRAPPERS.get(ops);
// Populated inside JsonDataLoaderMixin
LootTableSource source = LootUtil.SOURCES.get().getOrDefault(id, LootTableSource.DATA_PACK);
// Invoke the REPLACE event for the current loot table.
LootTable replacement = LootTableEvents.REPLACE.invoker().replaceLootTable(key, table, source, registries);
if (replacement != null) {
// Set the loot table to MODIFY to be the replacement loot table.
// The MODIFY event will also see it as a replaced loot table via the source.
table = replacement;
source = LootTableSource.REPLACED;
}
// Turn the current table into a modifiable builder and invoke the MODIFY event.
LootTable.Builder builder = FabricLootTableBuilder.copyOf(table);
LootTableEvents.MODIFY.invoker().modifyLootTable(key, builder, source, registries);
return (T) builder.build();
}
@SuppressWarnings("unchecked")
@Inject(method = "method_58279", at = @At("RETURN"))
private static <T> void onLootTablesLoaded(LootDataType<T> lootDataType, ResourceManager resourceManager, RegistryOps<JsonElement> registryOps, CallbackInfoReturnable<MutableRegistry<?>> cir) {
if (lootDataType != LootDataType.LOOT_TABLES) return;
LootTableEvents.ALL_LOADED.invoker().onLootTablesLoaded(resourceManager, (Registry<LootTable>) cir.getReturnValue());
LootUtil.SOURCES.remove();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,11 +1,11 @@
{
"schemaVersion": 1,
"id": "fabric-loot-api-v2",
"name": "Fabric Loot API (v2)",
"id": "fabric-loot-api-v3",
"name": "Fabric Loot API (v3)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-loot-api-v2/icon.png",
"icon": "assets/fabric-loot-api-v3/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
@ -22,13 +22,13 @@
},
"description": "Hooks for manipulating loot tables.",
"mixins": [
"fabric-loot-api-v2.mixins.json"
"fabric-loot-api-v3.mixins.json"
],
"custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_52\u0024class_53": ["net/fabricmc/fabric/api/loot/v2/FabricLootTableBuilder"],
"net/minecraft/class_55\u0024class_56": ["net/fabricmc/fabric/api/loot/v2/FabricLootPoolBuilder"]
"net/minecraft/class_52\u0024class_53": ["net/fabricmc/fabric/api/loot/v3/FabricLootTableBuilder"],
"net/minecraft/class_55\u0024class_56": ["net/fabricmc/fabric/api/loot/v3/FabricLootPoolBuilder"]
}
}
}

View file

@ -16,18 +16,27 @@
package net.fabricmc.fabric.test.loot;
import java.util.Optional;
import net.minecraft.block.Blocks;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.Enchantments;
import net.minecraft.entity.EntityType;
import net.minecraft.item.Items;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTable;
import net.minecraft.loot.condition.SurvivesExplosionLootCondition;
import net.minecraft.loot.entry.ItemEntry;
import net.minecraft.loot.function.SetEnchantmentsLootFunction;
import net.minecraft.loot.function.SetNameLootFunction;
import net.minecraft.loot.provider.number.ConstantLootNumberProvider;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.text.Text;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.loot.v2.LootTableEvents;
import net.fabricmc.fabric.api.loot.v2.LootTableSource;
import net.fabricmc.fabric.api.loot.v3.LootTableEvents;
import net.fabricmc.fabric.api.loot.v3.LootTableSource;
public class LootTest implements ModInitializer {
@Override
@ -35,7 +44,7 @@ public class LootTest implements ModInitializer {
// Test loot table load event
// The LootTable.Builder LootPool.Builder methods here should use
// prebuilt entries and pools to test the injected methods.
LootTableEvents.REPLACE.register((key, original, source) -> {
LootTableEvents.REPLACE.register((key, original, source, registries) -> {
if (Blocks.BLACK_WOOL.getLootTableKey() == key) {
if (source != LootTableSource.VANILLA) {
throw new AssertionError("black wool loot table should have LootTableSource.VANILLA, got " + source);
@ -53,7 +62,7 @@ public class LootTest implements ModInitializer {
});
// Test that the event is stopped when the loot table is replaced
LootTableEvents.REPLACE.register((key, original, source) -> {
LootTableEvents.REPLACE.register((key, original, source, registries) -> {
if (Blocks.BLACK_WOOL.getLootTableKey() == key) {
throw new AssertionError("Event should have been stopped from replaced loot table");
}
@ -61,7 +70,7 @@ public class LootTest implements ModInitializer {
return null;
});
LootTableEvents.MODIFY.register((key, tableBuilder, source) -> {
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
if (Blocks.BLACK_WOOL.getLootTableKey() == key && source != LootTableSource.REPLACED) {
throw new AssertionError("black wool loot table should have LootTableSource.REPLACED, got " + source);
}
@ -93,6 +102,18 @@ public class LootTest implements ModInitializer {
}
});
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
if (EntityType.SALMON.getLootTableId() == key) {
Optional<RegistryEntry<Enchantment>> lure = registries.getOptionalWrapper(RegistryKeys.ENCHANTMENT).flatMap(registry -> registry.getOptional(Enchantments.LURE));
lure.ifPresent((lureEnchantment) -> tableBuilder.pool(LootPool.builder().with(
ItemEntry.builder(Items.FISHING_ROD)
).apply(
new SetEnchantmentsLootFunction.Builder().enchantment(lureEnchantment, ConstantLootNumberProvider.create(1))
)));
}
});
LootTableEvents.ALL_LOADED.register((resourceManager, lootRegistry) -> {
LootTable blackWoolTable = lootRegistry.get(Blocks.BLACK_WOOL.getLootTableKey());

View file

@ -1,12 +1,12 @@
{
"schemaVersion": 1,
"id": "fabric-loot-api-v2-testmod",
"name": "Fabric Loot Table API (v2) Test Mod",
"id": "fabric-loot-api-v3-testmod",
"name": "Fabric Loot Table API (v3) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-loot-api-v2": "*"
"fabric-loot-api-v3": "*"
},
"entrypoints": {
"main": [

View file

@ -36,6 +36,7 @@ fabric-key-binding-api-v1-version=1.0.47
fabric-keybindings-v0-version=0.2.45
fabric-lifecycle-events-v1-version=2.3.11
fabric-loot-api-v2-version=3.0.11
fabric-loot-api-v3-version=1.0.0
fabric-message-api-v1-version=6.0.13
fabric-model-loading-api-v1-version=2.0.0
fabric-networking-api-v1-version=4.2.0

View file

@ -37,7 +37,7 @@ include 'fabric-item-api-v1'
include 'fabric-item-group-api-v1'
include 'fabric-key-binding-api-v1'
include 'fabric-lifecycle-events-v1'
include 'fabric-loot-api-v2'
include 'fabric-loot-api-v3'
include 'fabric-message-api-v1'
include 'fabric-model-loading-api-v1'
include 'fabric-networking-api-v1'
@ -62,6 +62,7 @@ include 'deprecated:fabric-command-api-v1'
include 'deprecated:fabric-commands-v0'
include 'deprecated:fabric-convention-tags-v1'
include 'deprecated:fabric-keybindings-v0'
include 'deprecated:fabric-loot-api-v2'
include 'deprecated:fabric-renderer-registries-v1'
include 'deprecated:fabric-rendering-data-attachment-v1'
include 'deprecated:fabric-rendering-v0'