From f0555ce0eb2fae2ac87059770616e402514dd1fa Mon Sep 17 00:00:00 2001 From: Juuxel <6596629+Juuxel@users.noreply.github.com> Date: Wed, 20 Jul 2022 18:22:47 +0300 Subject: [PATCH] [ci skip] Add loot table modification event (#287) * Add loot table modification event Closes #42. It's a simple wrapper around the platform events. * Add param for builtin loot tables Co-authored-by: shedaniel --- .../event/events/common/LootEvent.java | 98 +++++++++++++++++++ .../event/fabric/EventHandlerImpl.java | 4 + .../LootTableModificationContextImpl.java | 42 ++++++++ .../event/forge/EventHandlerImplCommon.java | 6 ++ .../LootTableModificationContextImpl.java | 37 +++++++ .../java/dev/architectury/test/TestMod.java | 2 + .../dev/architectury/test/loot/TestLoot.java | 39 ++++++++ 7 files changed, 228 insertions(+) create mode 100644 common/src/main/java/dev/architectury/event/events/common/LootEvent.java create mode 100644 fabric/src/main/java/dev/architectury/event/fabric/LootTableModificationContextImpl.java create mode 100644 forge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java create mode 100644 testmod-common/src/main/java/dev/architectury/test/loot/TestLoot.java diff --git a/common/src/main/java/dev/architectury/event/events/common/LootEvent.java b/common/src/main/java/dev/architectury/event/events/common/LootEvent.java new file mode 100644 index 00000000..dedf9277 --- /dev/null +++ b/common/src/main/java/dev/architectury/event/events/common/LootEvent.java @@ -0,0 +1,98 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.event.events.common; + +import dev.architectury.event.Event; +import dev.architectury.event.EventFactory; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTables; +import org.jetbrains.annotations.ApiStatus; + +/** + * Events related to loot tables and loot generation. + */ +public interface LootEvent { + /** + * An event to modify loot tables when they are loaded. + * This can be used to add new drops via new loot pools to existing loot tables + * without replacing the entire table. + * + *

Built-in loot tables

+ *

{@linkplain ModifyLootTable The event interface} includes a {@code builtin} parameter. + * If it's {@code true}, the loot table is built-in to vanilla or a mod. + * Otherwise, it's from a user data pack. The parameter can be used to only modify built-in loot tables + * and let user-provided loot tables act as full "overwrites". + * + *

This event only runs for built-in loot tables on Forge due to the limitations of + * {@code LootTableLoadEvent}. + * + *

Example: adding diamonds as a drop for dirt

+ *
{@code
+     * LootEvent.MODIFY_LOOT_TABLE.register((lootTables, id, context, builtin) -> {
+     *     // Check that the loot table is dirt and built-in
+     *     if (builtin && Blocks.DIRT.getLootTable().equals(id)) {
+     *         // Create a loot pool with a single item entry of Items.DIAMOND
+     *         LootPool.Builder pool = LootPool.lootPool().add(LootItem.lootTableItem(Items.DIAMOND));
+     *         context.addPool(pool);
+     *     }
+     * });
+     * }
+ * + * @see ModifyLootTable#modifyLootTable(LootTables, ResourceLocation, LootTableModificationContext, boolean) + */ + Event MODIFY_LOOT_TABLE = EventFactory.createLoop(); + + @FunctionalInterface + interface ModifyLootTable { + /** + * Modifies a loot table. + * + * @param lootTables the {@link LootTables} instance containing all loot tables + * @param id the loot table ID + * @param context the context used to modify the loot table + * @param builtin if {@code true}, the loot table is built-in; + * if {@code false}, it is from a user data pack + */ + void modifyLootTable(LootTables lootTables, ResourceLocation id, LootTableModificationContext context, boolean builtin); + } + + /** + * A platform-specific bridge for modifying a specific loot table. + */ + @ApiStatus.NonExtendable + interface LootTableModificationContext { + /** + * Adds a pool to the loot table. + * + * @param pool the pool to add + */ + void addPool(LootPool pool); + + /** + * Adds a pool to the loot table. + * + * @param pool the pool to add + */ + default void addPool(LootPool.Builder pool) { + addPool(pool.build()); + } + } +} diff --git a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java index 4ed7d0a7..5a47869b 100644 --- a/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java +++ b/fabric/src/main/java/dev/architectury/event/fabric/EventHandlerImpl.java @@ -26,6 +26,7 @@ import dev.architectury.event.events.client.ClientTooltipEvent; import dev.architectury.event.events.common.CommandRegistrationEvent; import dev.architectury.event.events.common.InteractionEvent; import dev.architectury.event.events.common.LifecycleEvent; +import dev.architectury.event.events.common.LootEvent; import dev.architectury.event.events.common.TickEvent; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; @@ -40,6 +41,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseItemCallback; +import net.fabricmc.fabric.api.loot.v2.LootTableEvents; public class EventHandlerImpl { @Environment(EnvType.CLIENT) @@ -75,6 +77,8 @@ public class EventHandlerImpl { UseItemCallback.EVENT.register((player, world, hand) -> InteractionEvent.RIGHT_CLICK_ITEM.invoker().click(player, hand).asMinecraft()); UseBlockCallback.EVENT.register((player, world, hand, hitResult) -> InteractionEvent.RIGHT_CLICK_BLOCK.invoker().click(player, hand, hitResult.getBlockPos(), hitResult.getDirection()).asMinecraft()); AttackBlockCallback.EVENT.register((player, world, hand, pos, face) -> InteractionEvent.LEFT_CLICK_BLOCK.invoker().click(player, hand, pos, face).asMinecraft()); + + LootTableEvents.MODIFY.register((resourceManager, lootManager, id, tableBuilder, source) -> LootEvent.MODIFY_LOOT_TABLE.invoker().modifyLootTable(lootManager, id, new LootTableModificationContextImpl(tableBuilder), source.isBuiltin())); } @Environment(EnvType.SERVER) diff --git a/fabric/src/main/java/dev/architectury/event/fabric/LootTableModificationContextImpl.java b/fabric/src/main/java/dev/architectury/event/fabric/LootTableModificationContextImpl.java new file mode 100644 index 00000000..3d00da9a --- /dev/null +++ b/fabric/src/main/java/dev/architectury/event/fabric/LootTableModificationContextImpl.java @@ -0,0 +1,42 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.event.fabric; + +import dev.architectury.event.events.common.LootEvent; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; + +final class LootTableModificationContextImpl implements LootEvent.LootTableModificationContext { + private final LootTable.Builder tableBuilder; + + LootTableModificationContextImpl(LootTable.Builder tableBuilder) { + this.tableBuilder = tableBuilder; + } + + @Override + public void addPool(LootPool pool) { + tableBuilder.pool(pool); + } + + @Override + public void addPool(LootPool.Builder pool) { + tableBuilder.withPool(pool); + } +} diff --git a/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java b/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java index e447f668..d4f553a8 100644 --- a/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java +++ b/forge/src/main/java/dev/architectury/event/forge/EventHandlerImplCommon.java @@ -31,6 +31,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; import net.minecraftforge.event.CommandEvent; +import net.minecraftforge.event.LootTableLoadEvent; import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.ServerChatEvent; import net.minecraftforge.event.TickEvent.LevelTickEvent; @@ -415,6 +416,11 @@ public class EventHandlerImplCommon { ChunkEvent.LOAD_DATA.invoker().load(event.getChunk(), level instanceof ServerLevel ? (ServerLevel) level : null, event.getData()); } + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(LootTableLoadEvent event) { + LootEvent.MODIFY_LOOT_TABLE.invoker().modifyLootTable(event.getLootTableManager(), event.getName(), new LootTableModificationContextImpl(event.getTable()), true); + } + public interface LevelEventAttachment { LevelAccessor architectury$getAttachedLevel(); diff --git a/forge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java b/forge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java new file mode 100644 index 00000000..b8eff2ea --- /dev/null +++ b/forge/src/main/java/dev/architectury/event/forge/LootTableModificationContextImpl.java @@ -0,0 +1,37 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.event.forge; + +import dev.architectury.event.events.common.LootEvent; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; + +final class LootTableModificationContextImpl implements LootEvent.LootTableModificationContext { + private final LootTable table; + + LootTableModificationContextImpl(LootTable table) { + this.table = table; + } + + @Override + public void addPool(LootPool pool) { + table.addPool(pool); + } +} diff --git a/testmod-common/src/main/java/dev/architectury/test/TestMod.java b/testmod-common/src/main/java/dev/architectury/test/TestMod.java index a960bdeb..009bcf22 100644 --- a/testmod-common/src/main/java/dev/architectury/test/TestMod.java +++ b/testmod-common/src/main/java/dev/architectury/test/TestMod.java @@ -28,6 +28,7 @@ import dev.architectury.test.entity.TestEntity; import dev.architectury.test.events.DebugEvents; import dev.architectury.test.gamerule.TestGameRules; import dev.architectury.test.item.TestBlockInteractions; +import dev.architectury.test.loot.TestLoot; import dev.architectury.test.networking.TestModNet; import dev.architectury.test.particle.TestParticles; import dev.architectury.test.registry.TestRegistries; @@ -56,6 +57,7 @@ public class TestMod { TestParticles.initialize(); TestModNet.initialize(); TestBlockInteractions.init(); + TestLoot.init(); TestWorldGeneration.initialize(); EnvExecutor.runInEnv(Env.CLIENT, () -> TestMod.Client::initializeClient); } diff --git a/testmod-common/src/main/java/dev/architectury/test/loot/TestLoot.java b/testmod-common/src/main/java/dev/architectury/test/loot/TestLoot.java new file mode 100644 index 00000000..9ae36a74 --- /dev/null +++ b/testmod-common/src/main/java/dev/architectury/test/loot/TestLoot.java @@ -0,0 +1,39 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021, 2022 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.test.loot; + +import dev.architectury.event.events.common.LootEvent; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.entries.LootItem; + +public class TestLoot { + public static void init() { + LootEvent.MODIFY_LOOT_TABLE.register((lootTables, id, context, builtin) -> { + // Check that the loot table is dirt and built-in + if (builtin && Blocks.DIRT.getLootTable().equals(id)) { + // Create a loot pool with a single item entry of Items.DIAMOND + LootPool.Builder pool = LootPool.lootPool().add(LootItem.lootTableItem(Items.DIAMOND)); + context.addPool(pool); + } + }); + } +}