From 692cdd061561a19e616b78f72d2d57aac6169e3d Mon Sep 17 00:00:00 2001 From: Choonster TheMage Date: Sun, 10 Nov 2024 03:41:07 +1100 Subject: [PATCH] Replace Pig Spawner capability wth component - Move default spawnPig implementation to IPigSpawner - Convert FinitePigSpawner and InfinitePigSpawner to records - Change ItemDebuggerBlock to log component instead of capability - Remove container listener and network message --- .../pigspawner/IPigSpawnerFinite.java | 30 --- .../pigspawner/IPigSpawnerInteractable.java | 26 --- .../capability/pigspawner/package-info.java | 9 - .../capability/pigspawner/BasePigSpawner.java | 28 --- .../pigspawner/FinitePigSpawner.java | 111 ---------- .../FinitePigSpawnerContainerListener.java | 24 --- .../pigspawner/PigSpawnerCapability.java | 203 ------------------ .../init/ModCapabilityContainerListeners.java | 4 +- .../testmod3/init/ModDataComponents.java | 11 + .../choonster/testmod3/init/ModItems.java | 19 +- .../choonster/testmod3/init/ModNetwork.java | 4 +- .../UpdateMenuPigSpawnerFiniteMessage.java | 61 ------ .../choonster/testmod3/util/RegistryUtil.java | 21 ++ .../testmod3/world/item/PigSpawnerItem.java | 80 +++---- .../pigspawner/FinitePigSpawner.java | 98 +++++++++ .../component}/pigspawner/IPigSpawner.java | 40 +++- .../pigspawner/IPigSpawnerFinite.java | 40 ++++ .../pigspawner/IPigSpawnerInteractable.java | 25 +++ .../pigspawner/InfinitePigSpawner.java | 19 +- .../item/component/pigspawner/PigSpawner.java | 179 +++++++++++++++ .../component/pigspawner/PigSpawnerType.java | 68 ++++++ .../component}/pigspawner/package-info.java | 2 +- .../world/level/block/ItemDebuggerBlock.java | 18 +- .../level/block/PigSpawnerRefillerBlock.java | 20 +- 24 files changed, 549 insertions(+), 591 deletions(-) delete mode 100644 src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerFinite.java delete mode 100644 src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerInteractable.java delete mode 100644 src/main/java/choonster/testmod3/api/capability/pigspawner/package-info.java delete mode 100644 src/main/java/choonster/testmod3/capability/pigspawner/BasePigSpawner.java delete mode 100644 src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawner.java delete mode 100644 src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawnerContainerListener.java delete mode 100644 src/main/java/choonster/testmod3/capability/pigspawner/PigSpawnerCapability.java delete mode 100644 src/main/java/choonster/testmod3/network/capability/UpdateMenuPigSpawnerFiniteMessage.java create mode 100644 src/main/java/choonster/testmod3/world/item/component/pigspawner/FinitePigSpawner.java rename src/main/java/choonster/testmod3/{api/capability => world/item/component}/pigspawner/IPigSpawner.java (59%) create mode 100644 src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerFinite.java create mode 100644 src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerInteractable.java rename src/main/java/choonster/testmod3/{capability => world/item/component}/pigspawner/InfinitePigSpawner.java (52%) create mode 100644 src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawner.java create mode 100644 src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawnerType.java rename src/main/java/choonster/testmod3/{capability => world/item/component}/pigspawner/package-info.java (80%) diff --git a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerFinite.java b/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerFinite.java deleted file mode 100644 index 7f4efd0d..00000000 --- a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerFinite.java +++ /dev/null @@ -1,30 +0,0 @@ -package choonster.testmod3.api.capability.pigspawner; - -/** - * A spawner that can only spawn a finite number of pigs. - * - * @author Choonster - */ -public interface IPigSpawnerFinite extends IPigSpawner { - /** - * Get the current number of pigs that can be spawned. - * - * @return The number of pigs that can be spawned - */ - int getNumPigs(); - - /** - * Get the maximum number of pigs that can be spawned. - * - * @return The maximum number of pigs that can be spawned. - */ - int getMaxNumPigs(); - - /** - * Set the current number of pigs that can be spawned. - * - * @param numPigs The number of pigs that can be spawned - * @throws IllegalArgumentException If {@code numPigs} is greater than {@link #getMaxNumPigs()} - */ - void setNumPigs(final int numPigs); -} diff --git a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerInteractable.java b/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerInteractable.java deleted file mode 100644 index 527fdbfa..00000000 --- a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawnerInteractable.java +++ /dev/null @@ -1,26 +0,0 @@ -package choonster.testmod3.api.capability.pigspawner; - -import net.minecraft.commands.CommandSource; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.Level; - -import org.jetbrains.annotations.Nullable; - -/** - * Something that can interacted with by an {@link IPigSpawner}. - * - * @author Choonster - */ -public interface IPigSpawnerInteractable { - - /** - * Interact with the {@link IPigSpawner}. Only called on the server. - * - * @param pigSpawner The IPigSpawner - * @param world The level - * @param pos The position of this object - * @param iCommandSender The ICommandSender that caused the interaction, if any - * @return {@code true} to prevent the default action of the IPigSpawner - */ - boolean interact(final IPigSpawner pigSpawner, final Level world, final BlockPos pos, @Nullable final CommandSource iCommandSender); -} diff --git a/src/main/java/choonster/testmod3/api/capability/pigspawner/package-info.java b/src/main/java/choonster/testmod3/api/capability/pigspawner/package-info.java deleted file mode 100644 index ed36d72c..00000000 --- a/src/main/java/choonster/testmod3/api/capability/pigspawner/package-info.java +++ /dev/null @@ -1,9 +0,0 @@ -@FieldsAreNonnullByDefault -@MethodsReturnNonnullByDefault -@ParametersAreNonnullByDefault -package choonster.testmod3.api.capability.pigspawner; - -import net.minecraft.FieldsAreNonnullByDefault; -import net.minecraft.MethodsReturnNonnullByDefault; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/BasePigSpawner.java b/src/main/java/choonster/testmod3/capability/pigspawner/BasePigSpawner.java deleted file mode 100644 index e32441ea..00000000 --- a/src/main/java/choonster/testmod3/capability/pigspawner/BasePigSpawner.java +++ /dev/null @@ -1,28 +0,0 @@ -package choonster.testmod3.capability.pigspawner; - -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.animal.Pig; -import net.minecraft.world.level.Level; - -/** - * Base implementation of {@link IPigSpawner}. - *

- * {@link #spawnPig} is implemented here instead of as a default method so other implementations can add their own - * functionality without having to rewrite the pig spawning themselves (you can't call a default method from its override). - * - * @author Choonster - */ -public abstract class BasePigSpawner implements IPigSpawner { - @Override - public boolean spawnPig(final Level level, final double x, final double y, final double z) { - final Pig pig = EntityType.PIG.create(level); - - if (pig == null) { - return false; - } - - pig.setPos(x, y, z); - return level.addFreshEntity(pig); - } -} diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawner.java b/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawner.java deleted file mode 100644 index f5180978..00000000 --- a/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawner.java +++ /dev/null @@ -1,111 +0,0 @@ -package choonster.testmod3.capability.pigspawner; - -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerFinite; -import choonster.testmod3.text.TestMod3Lang; -import choonster.testmod3.util.DebugUtil; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.mojang.logging.LogUtils; -import com.mojang.serialization.Codec; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.world.level.Level; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; - -import java.util.List; - -/** - * A spawner that can only spawn a finite number of pigs. - * - * @author Choonster - */ -public class FinitePigSpawner extends BasePigSpawner implements IPigSpawnerFinite { - private static final Logger LOGGER = LogUtils.getLogger(); - - public static FinitePigSpawner empty(final int maxNumPigs) { - return new FinitePigSpawner(maxNumPigs); - } - - public static Codec codec(final int maxNumPigs) { - return Codec.INT.xmap( - (numPigs) -> new FinitePigSpawner(numPigs, maxNumPigs), - FinitePigSpawner::getNumPigs - ); - } - - /** - * The current number of pigs that can be spawned. - */ - private int numPigs; - - /** - * The maximum number of pigs that can be spawned. - */ - private final int maxNumPigs; - - private FinitePigSpawner(final int maxNumPigs) { - this(0, maxNumPigs); - } - - private FinitePigSpawner(final int numPigs, final int maxNumPigs) { - this.numPigs = numPigs; - this.maxNumPigs = maxNumPigs; - - LOGGER.debug(PigSpawnerCapability.LOG_MARKER, "Creating finite pig spawner: {}", this, DebugUtil.getStackTrace(10)); - } - - @Override - public int getNumPigs() { - return numPigs; - } - - @Override - public int getMaxNumPigs() { - return maxNumPigs; - } - - @Override - public void setNumPigs(final int numPigs) { - Preconditions.checkArgument(numPigs <= getMaxNumPigs(), "Attempted to set numPigs to %s, but maximum is %s", numPigs, getMaxNumPigs()); - this.numPigs = numPigs; - } - - @Override - public boolean canSpawnPig(final Level level, final double x, final double y, final double z) { - return getNumPigs() > 0; - } - - @Override - public boolean spawnPig(final Level level, final double x, final double y, final double z) { - setNumPigs(getNumPigs() - 1); - return super.spawnPig(level, x, y, z); - } - - @Override - public List getTooltipLines() { - return ImmutableList.of(Component.translatable(TestMod3Lang.PIG_SPAWNER_FINITE_DESC.getTranslationKey(), getNumPigs(), getMaxNumPigs())); - } - - @Override - public boolean equals(@Nullable final Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - final var that = (FinitePigSpawner) obj; - - return numPigs == that.numPigs && maxNumPigs == that.maxNumPigs; - } - - @Override - public int hashCode() { - var result = numPigs; - result = 31 * result + maxNumPigs; - return result; - } -} diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawnerContainerListener.java b/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawnerContainerListener.java deleted file mode 100644 index abd6e7a7..00000000 --- a/src/main/java/choonster/testmod3/capability/pigspawner/FinitePigSpawnerContainerListener.java +++ /dev/null @@ -1,24 +0,0 @@ -package choonster.testmod3.capability.pigspawner; - -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerFinite; -import choonster.testmod3.capability.CapabilityContainerListener; -import choonster.testmod3.network.capability.UpdateMenuPigSpawnerFiniteMessage; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.inventory.AbstractContainerMenu; - -/** - * Syncs the {@link IPigSpawnerFinite} capability for items in {@link AbstractContainerMenu}s. - * - * @author Choonster - */ -public class FinitePigSpawnerContainerListener extends CapabilityContainerListener { - public FinitePigSpawnerContainerListener(final ServerPlayer player) { - super(player, PigSpawnerCapability.PIG_SPAWNER_CAPABILITY, PigSpawnerCapability.DEFAULT_FACING); - } - - @Override - protected UpdateMenuPigSpawnerFiniteMessage createUpdateMessage(final int containerID, final int stateID, final int slotNumber, final IPigSpawner pigSpawner) { - return new UpdateMenuPigSpawnerFiniteMessage(PigSpawnerCapability.DEFAULT_FACING, containerID, stateID, slotNumber, pigSpawner); - } -} diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/PigSpawnerCapability.java b/src/main/java/choonster/testmod3/capability/pigspawner/PigSpawnerCapability.java deleted file mode 100644 index e1b3435a..00000000 --- a/src/main/java/choonster/testmod3/capability/pigspawner/PigSpawnerCapability.java +++ /dev/null @@ -1,203 +0,0 @@ -package choonster.testmod3.capability.pigspawner; - -import choonster.testmod3.TestMod3; -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerInteractable; -import choonster.testmod3.capability.CapabilityContainerListenerManager; -import choonster.testmod3.capability.SerializableCapabilityProvider; -import choonster.testmod3.util.ModLogUtils; -import com.mojang.serialization.Codec; -import net.minecraft.ChatFormatting; -import net.minecraft.commands.CommandSource; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.level.Level; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.CapabilityManager; -import net.minecraftforge.common.capabilities.CapabilityToken; -import net.minecraftforge.common.capabilities.ICapabilityProvider; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.event.AttachCapabilitiesEvent; -import net.minecraftforge.event.entity.player.ItemTooltipEvent; -import net.minecraftforge.event.entity.player.PlayerInteractEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Marker; - -/** - * Capability for {@link IPigSpawner}. - * - * @author Choonster - */ -public final class PigSpawnerCapability { - /** - * The {@link Capability} instance. - */ - public static final Capability PIG_SPAWNER_CAPABILITY = CapabilityManager.get(new CapabilityToken<>() { - }); - - /** - * The default {@link Direction} to use for this capability. - */ - public static final Direction DEFAULT_FACING = null; - - /** - * The ID of the capability. - */ - public static final ResourceLocation ID = ResourceLocation.fromNamespaceAndPath(TestMod3.MODID, "pig_spawner"); - - public static final Marker LOG_MARKER = ModLogUtils.getMarker("PIG_SPAWNER"); - - /** - * Register the capability container listener. - */ - public static void registerContainerListener() { - CapabilityContainerListenerManager.registerListenerFactory(FinitePigSpawnerContainerListener::new); - } - - /** - * Get the {@link IPigSpawner} from the specified {@link ItemStack}'s capabilities, if any. - * - * @param itemStack The ItemStack - * @return A lazy optional containing the IPigSpawner, if any - */ - public static LazyOptional getPigSpawner(final ItemStack itemStack) { - return itemStack.getCapability(PIG_SPAWNER_CAPABILITY, DEFAULT_FACING); - } - - /** - * Create a provider for the specified {@link IPigSpawner} instance. - * - * @param pigSpawner The pig spawner - * @param codec The codec for the pig spawner type - * @return The provider - */ - public static ICapabilityProvider createProvider(final T pigSpawner, final Codec codec) { - return new SerializableCapabilityProvider<>(PIG_SPAWNER_CAPABILITY, DEFAULT_FACING, pigSpawner, codec); - } - - /** - * Event handler for the {@link IPigSpawner} capability. - */ - @Mod.EventBusSubscriber(modid = TestMod3.MODID) - private static class EventHandler { - /** - * Attach the {@link IPigSpawner} capability to vanilla items. - * - * @param event The event - */ - @SubscribeEvent - public static void attachCapabilities(final AttachCapabilitiesEvent event) { - if (event.getObject().getItem() == Items.CLAY_BALL) { - final var maxNumPigs = 20; - final var pigSpawner = FinitePigSpawner.empty(maxNumPigs); - final var codec = FinitePigSpawner.codec(maxNumPigs); - - event.addCapability(ID, createProvider(pigSpawner, codec)); - } - } - - /** - * Spawn a pig at the specified position. - *

- * If there's an {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. - * - * @param pigSpawner The pig spawner - * @param level The level - * @param x The x position to spawn the pig at - * @param y The y position to spawn the pig at - * @param z The z position to spawn the pig at - * @param interactable The IPigSpawnerInteractable, if any - * @param interactablePos The position of the IPigSpawnerInteractable - * @param commandSource The command source, if any - */ - private static void trySpawnPig(final IPigSpawner pigSpawner, final Level level, final double x, final double y, final double z, @Nullable final IPigSpawnerInteractable interactable, final BlockPos interactablePos, @Nullable final CommandSource commandSource) { - if (level.isClientSide) { - return; - } - - var shouldSpawnPig = true; - - if (interactable != null) { - shouldSpawnPig = !interactable.interact(pigSpawner, level, interactablePos, commandSource); - } - - if (shouldSpawnPig && pigSpawner.canSpawnPig(level, x, y, z)) { - pigSpawner.spawnPig(level, x, y, z); - } - } - - /** - * Spawn a pig when a player right-clicks a block with an item that has the {@link IPigSpawner} capability. - *

- * If the block implements {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. - * - * @param event The event - */ - @SubscribeEvent - public static void playerInteract(final PlayerInteractEvent.RightClickBlock event) { - final var facing = event.getFace(); - assert facing != null; - - final var spawnPos = event.getPos().relative(facing); - final double x = spawnPos.getX() + 0.5, y = spawnPos.getY() + 0.5, z = spawnPos.getZ() + 0.5; - - final var level = event.getLevel(); - final var block = level.getBlockState(event.getPos()).getBlock(); - final var interactable = block instanceof IPigSpawnerInteractable ? (IPigSpawnerInteractable) block : null; - - final var player = event.getEntity(); - - getPigSpawner(event.getItemStack()) - .ifPresent(pigSpawner -> trySpawnPig(pigSpawner, level, x, y, z, interactable, event.getPos(), player)); - } - - /** - * Spawn a pig when a player right-clicks an entity with an item that has the {@link IPigSpawner} capability. - *

- * If the entity implements {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. - * - * @param event The event - */ - @SubscribeEvent - public static void entityInteract(final PlayerInteractEvent.EntityInteract event) { - final var level = event.getLevel(); - - final var target = event.getTarget(); - final double x = target.getX(), y = target.getY(), z = target.getZ(); - final var interactable = target instanceof IPigSpawnerInteractable ? (IPigSpawnerInteractable) target : null; - - final var hand = event.getHand(); - - getPigSpawner(event.getEntity().getItemInHand(hand)) - .ifPresent(pigSpawner -> trySpawnPig(pigSpawner, level, x, y, z, interactable, target.blockPosition(), event.getEntity())); - } - - /** - * Add the {@link IPigSpawner}'s tooltip lines to the tooltip if the item has the {@link IPigSpawner} capability - * - * @param event The event - */ - @SubscribeEvent - public static void itemTooltip(final ItemTooltipEvent event) { - getPigSpawner(event.getItemStack()).ifPresent(pigSpawner -> { - final var style = Style.EMPTY.withColor(ChatFormatting.LIGHT_PURPLE); - - final var tooltipLines = pigSpawner - .getTooltipLines() - .stream() - .map(textComponent -> textComponent.setStyle(style)) - .toList(); - - event.getToolTip().add(Component.literal("")); - event.getToolTip().addAll(tooltipLines); - }); - } - } -} diff --git a/src/main/java/choonster/testmod3/init/ModCapabilityContainerListeners.java b/src/main/java/choonster/testmod3/init/ModCapabilityContainerListeners.java index 907cad8e..edb0ae21 100644 --- a/src/main/java/choonster/testmod3/init/ModCapabilityContainerListeners.java +++ b/src/main/java/choonster/testmod3/init/ModCapabilityContainerListeners.java @@ -2,7 +2,6 @@ import choonster.testmod3.TestMod3; import choonster.testmod3.capability.fluidhandler.FluidHandlerCapability; -import choonster.testmod3.capability.pigspawner.PigSpawnerCapability; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; @@ -20,9 +19,8 @@ public class ModCapabilityContainerListeners { * @param event The common setup event */ @SubscribeEvent - public static void registerContainerListeners(FMLCommonSetupEvent event) { + public static void registerContainerListeners(final FMLCommonSetupEvent event) { event.enqueueWork(() -> { - PigSpawnerCapability.registerContainerListener(); FluidHandlerCapability.registerContainerListener(); }); } diff --git a/src/main/java/choonster/testmod3/init/ModDataComponents.java b/src/main/java/choonster/testmod3/init/ModDataComponents.java index 3a2c8ee4..7365569c 100644 --- a/src/main/java/choonster/testmod3/init/ModDataComponents.java +++ b/src/main/java/choonster/testmod3/init/ModDataComponents.java @@ -4,6 +4,7 @@ import choonster.testmod3.serialization.VanillaCodecs; import choonster.testmod3.world.item.*; import choonster.testmod3.world.item.component.lastusetime.LastUseTimeProperties; +import choonster.testmod3.world.item.component.pigspawner.IPigSpawner; import com.mojang.serialization.Codec; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.registries.Registries; @@ -113,6 +114,16 @@ public class ModDataComponents { ); + /** + * @see IPigSpawner + */ + public static final RegistryObject> PIG_SPAWNER = register("pig_spawner", + builder -> builder + .persistent(IPigSpawner.CODEC) + .networkSynchronized(IPigSpawner.STREAM_CODEC) + .cacheEncoding() + ); + /** * Registers the {@link DeferredRegister} instance with the mod event bus. *

diff --git a/src/main/java/choonster/testmod3/init/ModItems.java b/src/main/java/choonster/testmod3/init/ModItems.java index 4914b2f2..caf36253 100644 --- a/src/main/java/choonster/testmod3/init/ModItems.java +++ b/src/main/java/choonster/testmod3/init/ModItems.java @@ -1,12 +1,12 @@ package choonster.testmod3.init; import choonster.testmod3.TestMod3; -import choonster.testmod3.capability.pigspawner.FinitePigSpawner; -import choonster.testmod3.capability.pigspawner.InfinitePigSpawner; import choonster.testmod3.world.entity.BlockDetectionArrow; import choonster.testmod3.world.entity.ModArrow; import choonster.testmod3.world.item.*; import choonster.testmod3.world.item.component.lastusetime.LastUseTimeProperties; +import choonster.testmod3.world.item.component.pigspawner.FinitePigSpawner; +import choonster.testmod3.world.item.component.pigspawner.InfinitePigSpawner; import choonster.testmod3.world.item.variantgroup.ItemVariantGroup; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -142,20 +142,21 @@ public class ModItems { // Capabilities are registered and injected in FMLCommonSetupEvent, which is fired after RegistryEvent.Register. // This means that item constructors can't directly reference Capability fields (e.g. CapabilityPigSpawner.PIG_SPAWNER_CAPABILITY). - public static final RegistryObject> PIG_SPAWNER_FINITE = ITEMS.register("pig_spawner_finite", + // TODO: Do custom data components work here? + public static final RegistryObject PIG_SPAWNER_FINITE = ITEMS.register("pig_spawner_finite", () -> { final var maxNumPigs = 20; - return new PigSpawnerItem<>( - () -> FinitePigSpawner.empty(maxNumPigs), - FinitePigSpawner.codec(maxNumPigs), - defaultItemProperties() + return new PigSpawnerItem( + defaultItemProperties().component(ModDataComponents.PIG_SPAWNER.get(), FinitePigSpawner.empty(maxNumPigs)) ); } ); - public static final RegistryObject> PIG_SPAWNER_INFINITE = ITEMS.register("pig_spawner_infinite", - () -> new PigSpawnerItem<>(InfinitePigSpawner::new, InfinitePigSpawner.CODEC, defaultItemProperties()) + public static final RegistryObject PIG_SPAWNER_INFINITE = ITEMS.register("pig_spawner_infinite", + () -> new PigSpawnerItem( + defaultItemProperties().component(ModDataComponents.PIG_SPAWNER.get(), InfinitePigSpawner.INSTANCE) + ) ); public static final RegistryObject CONTINUOUS_BOW = ITEMS.register("continuous_bow", diff --git a/src/main/java/choonster/testmod3/init/ModNetwork.java b/src/main/java/choonster/testmod3/init/ModNetwork.java index 24f16072..02671fca 100644 --- a/src/main/java/choonster/testmod3/init/ModNetwork.java +++ b/src/main/java/choonster/testmod3/init/ModNetwork.java @@ -3,7 +3,6 @@ import choonster.testmod3.TestMod3; import choonster.testmod3.network.*; import choonster.testmod3.network.capability.UpdateMenuFluidTankMessage; -import choonster.testmod3.network.capability.UpdateMenuPigSpawnerFiniteMessage; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.Channel; import net.minecraftforge.network.ChannelBuilder; @@ -13,7 +12,7 @@ public class ModNetwork { public static final ResourceLocation CHANNEL_NAME = ResourceLocation.fromNamespaceAndPath(TestMod3.MODID, "network"); - public static final int NETWORK_VERSION = 4; + public static final int NETWORK_VERSION = 5; public static SimpleChannel getNetworkChannel() { final var channel = ChannelBuilder.named(CHANNEL_NAME) @@ -34,7 +33,6 @@ public static SimpleChannel getNetworkChannel() { .addMain(FluidTankContentsMessage.class, FluidTankContentsMessage.STREAM_CODEC, FluidTankContentsMessage::handle) .addMain(UpdateChunkEnergyValueMessage.class, UpdateChunkEnergyValueMessage.STREAM_CODEC, UpdateChunkEnergyValueMessage::handle) .addMain(UpdateMenuFluidTankMessage.class, UpdateMenuFluidTankMessage.STREAM_CODEC, UpdateMenuFluidTankMessage::handle) - .addMain(UpdateMenuPigSpawnerFiniteMessage.class, UpdateMenuPigSpawnerFiniteMessage.STREAM_CODEC, UpdateMenuPigSpawnerFiniteMessage::handle) .addMain(openClientScreenMessageClass, OpenClientScreenMessage.STREAM_CODEC, OpenClientScreenMessage::handle) .build(); diff --git a/src/main/java/choonster/testmod3/network/capability/UpdateMenuPigSpawnerFiniteMessage.java b/src/main/java/choonster/testmod3/network/capability/UpdateMenuPigSpawnerFiniteMessage.java deleted file mode 100644 index 37bd5d23..00000000 --- a/src/main/java/choonster/testmod3/network/capability/UpdateMenuPigSpawnerFiniteMessage.java +++ /dev/null @@ -1,61 +0,0 @@ -package choonster.testmod3.network.capability; - -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerFinite; -import choonster.testmod3.capability.pigspawner.PigSpawnerCapability; -import net.minecraft.core.Direction; -import net.minecraft.network.RegistryFriendlyByteBuf; -import net.minecraft.network.codec.ByteBufCodecs; -import net.minecraft.network.codec.StreamCodec; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraftforge.event.network.CustomPayloadEvent; -import org.jetbrains.annotations.Nullable; - -/** - * Updates the {@link IPigSpawnerFinite} for a single slot of an {@link AbstractContainerMenu}. - * - * @author Choonster - */ -public record UpdateMenuPigSpawnerFiniteMessage( - UpdateMenuCapabilityData data -) implements UpdateMenuCapabilityData.UpdateMenuCapabilityDataMessage { - public static final StreamCodec STREAM_CODEC = - UpdateMenuCapabilityData.streamCodec( - PigSpawnerCapability.PIG_SPAWNER_CAPABILITY, - ByteBufCodecs.VAR_INT.cast(), - UpdateMenuPigSpawnerFiniteMessage::new - ); - - public UpdateMenuPigSpawnerFiniteMessage( - @Nullable final Direction direction, - final int containerID, - final int stateID, - final int slotNumber, - final IPigSpawner pigSpawner - ) { - this(new UpdateMenuCapabilityData<>( - PigSpawnerCapability.PIG_SPAWNER_CAPABILITY, - direction, containerID, stateID, slotNumber, pigSpawner, - UpdateMenuPigSpawnerFiniteMessage::convertFinitePigSpawnerToNumPigs - )); - } - - public static void handle(final UpdateMenuPigSpawnerFiniteMessage message, final CustomPayloadEvent.Context ctx) { - message.data.handle(UpdateMenuPigSpawnerFiniteMessage::applyNumPigsToFinitePigSpawner); - } - - @Nullable - static Integer convertFinitePigSpawnerToNumPigs(final IPigSpawner pigSpawner) { - if (pigSpawner instanceof IPigSpawnerFinite) { - return ((IPigSpawnerFinite) pigSpawner).getNumPigs(); - } else { - return null; - } - } - - static void applyNumPigsToFinitePigSpawner(final IPigSpawner pigSpawner, final int numPigs) { - if (pigSpawner instanceof IPigSpawnerFinite) { - ((IPigSpawnerFinite) pigSpawner).setNumPigs(numPigs); - } - } -} diff --git a/src/main/java/choonster/testmod3/util/RegistryUtil.java b/src/main/java/choonster/testmod3/util/RegistryUtil.java index d88c430a..8e2d8bf7 100644 --- a/src/main/java/choonster/testmod3/util/RegistryUtil.java +++ b/src/main/java/choonster/testmod3/util/RegistryUtil.java @@ -2,6 +2,9 @@ import choonster.testmod3.TestMod3; import com.google.common.base.Preconditions; +import net.minecraft.core.Registry; +import net.minecraft.core.component.DataComponentType; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.Item; @@ -72,6 +75,17 @@ public static ResourceLocation getKey(final IForgeRegistry registry, fina return Preconditions.checkNotNull(registry.getKey(entry), "%s has no registry key", entry); } + /** + * Gets the key of the registry entry, throwing an exception if it's not set. + * + * @param entry The registry entry + * @return The key + * @throws NullPointerException If the key is null + */ + public static ResourceLocation getKey(final Registry registry, final T entry) { + return Preconditions.checkNotNull(registry.getKey(entry), "%s has no registry key", entry); + } + /** * @see #getKey(IForgeRegistry, Object) */ @@ -106,4 +120,11 @@ public static ResourceLocation getKey(final Fluid fluid) { public static ResourceLocation getKey(final Potion potion) { return getKey(ForgeRegistries.POTIONS, potion); } + + /** + * @see #getKey(Registry, Object) + */ + public static ResourceLocation getKey(final DataComponentType componentType) { + return getKey(BuiltInRegistries.DATA_COMPONENT_TYPE, componentType); + } } diff --git a/src/main/java/choonster/testmod3/world/item/PigSpawnerItem.java b/src/main/java/choonster/testmod3/world/item/PigSpawnerItem.java index 413758de..6789839a 100644 --- a/src/main/java/choonster/testmod3/world/item/PigSpawnerItem.java +++ b/src/main/java/choonster/testmod3/world/item/PigSpawnerItem.java @@ -1,80 +1,52 @@ package choonster.testmod3.world.item; -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerFinite; -import choonster.testmod3.capability.pigspawner.PigSpawnerCapability; -import com.mojang.serialization.Codec; -import net.minecraft.nbt.CompoundTag; +import choonster.testmod3.init.ModDataComponents; +import choonster.testmod3.world.item.component.pigspawner.IPigSpawnerFinite; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; -import net.minecraftforge.common.capabilities.ICapabilityProvider; import org.jetbrains.annotations.Nullable; -import java.util.function.Supplier; - /** * A pig spawner item. * * @author Choonster */ -public class PigSpawnerItem extends Item { - /** - * A factory to create the {@link IPigSpawner} - */ - private final Supplier spawnerFactory; - - /** - * The codec for the {@link IPigSpawner} type - */ - private final Codec spawnerCodec; +public class PigSpawnerItem extends Item { + private static final float BAR_WIDTH = 13.0f; - public PigSpawnerItem(final Supplier spawnerFactory, final Codec spawnerCodec, final Item.Properties properties) { + public PigSpawnerItem(final Item.Properties properties) { super(properties); - this.spawnerFactory = spawnerFactory; - this.spawnerCodec = spawnerCodec; } - @Override - public ICapabilityProvider initCapabilities(final ItemStack stack, @Nullable final CompoundTag nbt) { - return PigSpawnerCapability.createProvider(spawnerFactory.get(), spawnerCodec); - } + @Nullable + private IPigSpawnerFinite getFinitePigSpawner(final ItemStack stack) { + final var pigSpawner = stack.get(ModDataComponents.PIG_SPAWNER.get()); - @Override - public boolean canBeDepleted() { - return true; - } + if (pigSpawner instanceof final IPigSpawnerFinite finitePigSpawner) { + return finitePigSpawner; + } - @Override - public int getMaxDamage(final ItemStack stack) { - return PigSpawnerCapability.getPigSpawner(stack) - .filter(pigSpawner -> pigSpawner instanceof IPigSpawnerFinite) - .map(pigSpawner -> { - final IPigSpawnerFinite pigSpawnerFinite = (IPigSpawnerFinite) pigSpawner; - return pigSpawnerFinite.getMaxNumPigs(); - }) - .orElse(super.getMaxDamage(stack)); + return null; } @Override - public boolean isDamaged(final ItemStack stack) { - return PigSpawnerCapability.getPigSpawner(stack) - .filter(pigSpawner -> pigSpawner instanceof IPigSpawnerFinite) - .map(pigSpawner -> { - final IPigSpawnerFinite pigSpawnerFinite = (IPigSpawnerFinite) pigSpawner; - return pigSpawnerFinite.getNumPigs() < pigSpawnerFinite.getMaxNumPigs(); - }) - .orElse(super.isDamaged(stack)); + public boolean isBarVisible(final ItemStack stack) { + final var finitePigSpawner = getFinitePigSpawner(stack); + + return finitePigSpawner != null && finitePigSpawner.numPigs() < finitePigSpawner.maxNumPigs(); } @Override public int getBarWidth(final ItemStack stack) { - return PigSpawnerCapability.getPigSpawner(stack) - .filter(pigSpawner -> pigSpawner instanceof IPigSpawnerFinite) - .map(pigSpawner -> { - final IPigSpawnerFinite pigSpawnerFinite = (IPigSpawnerFinite) pigSpawner; - final int maxNumPigs = pigSpawnerFinite.getMaxNumPigs(); - return Math.round(13.0f - ((float) (maxNumPigs - pigSpawnerFinite.getNumPigs()) / maxNumPigs) * 13.0f); - }) - .orElse(super.getBarWidth(stack)); + final var finitePigSpawner = getFinitePigSpawner(stack); + + if (finitePigSpawner != null) { + final var maxNumPigs = finitePigSpawner.maxNumPigs(); + final var numPigs = finitePigSpawner.numPigs(); + + return Math.round(BAR_WIDTH - ((float) (maxNumPigs - numPigs) / maxNumPigs) * BAR_WIDTH); + } + + return super.getBarWidth(stack); } } diff --git a/src/main/java/choonster/testmod3/world/item/component/pigspawner/FinitePigSpawner.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/FinitePigSpawner.java new file mode 100644 index 00000000..2660273d --- /dev/null +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/FinitePigSpawner.java @@ -0,0 +1,98 @@ +package choonster.testmod3.world.item.component.pigspawner; + +import choonster.testmod3.text.TestMod3Lang; +import choonster.testmod3.util.DebugUtil; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.mojang.logging.LogUtils; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.level.Level; +import org.slf4j.Logger; + +import java.util.List; + +/** + * A spawner that can only spawn a finite number of pigs. + * + * @param numPigs The current number of pigs that can be spawned. + * @param maxNumPigs The maximum number of pigs that can be spawned. + * @author Choonster + */ +public record FinitePigSpawner(int numPigs, int maxNumPigs) implements IPigSpawnerFinite { + private static final Logger LOGGER = LogUtils.getLogger(); + + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(builder -> + builder.group( + Codec.INT + .fieldOf("num_pigs") + .forGetter(FinitePigSpawner::numPigs), + + Codec.INT + .fieldOf("max_num_pigs") + .forGetter(FinitePigSpawner::maxNumPigs) + ).apply(builder, FinitePigSpawner::new) + ); + + public static final StreamCodec STREAM_CODEC = StreamCodec.composite( + ByteBufCodecs.VAR_INT, + FinitePigSpawner::numPigs, + ByteBufCodecs.VAR_INT, + FinitePigSpawner::maxNumPigs, + FinitePigSpawner::new + ); + + public static FinitePigSpawner empty(final int maxNumPigs) { + return new FinitePigSpawner(0, maxNumPigs); + } + + public FinitePigSpawner(final int numPigs, final int maxNumPigs) { + Preconditions.checkArgument(numPigs <= maxNumPigs, + "Attempted to set numPigs to %s, but maximum is %s", + numPigs, + maxNumPigs() + ); + + this.numPigs = numPigs; + this.maxNumPigs = maxNumPigs; + + LOGGER.debug(PigSpawner.LOG_MARKER, "Creating finite pig spawner: {}", this, DebugUtil.getStackTrace(10)); + } + + @Override + public PigSpawnerType getType() { + return PigSpawnerType.FINITE; + } + + @Override + public FinitePigSpawner withNumPigs(final int numPigs) { + return new FinitePigSpawner(numPigs, maxNumPigs); + } + + @Override + public boolean canSpawnPig(final Level level, final double x, final double y, final double z) { + return numPigs() > 0; + } + + @Override + public IPigSpawnerFinite spawnPig(final Level level, final double x, final double y, final double z) { + final var success = IPigSpawnerFinite.super.spawnPig(level, x, y, z) != null; + + if (!success) { + return null; + } + + return withNumPigs(numPigs() - 1); + } + + @Override + public List getTooltipLines() { + return ImmutableList.of(Component.translatable(TestMod3Lang.PIG_SPAWNER_FINITE_DESC.getTranslationKey(), numPigs(), maxNumPigs())); + } +} diff --git a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawner.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawner.java similarity index 59% rename from src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawner.java rename to src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawner.java index 81f7f44a..2c50858b 100644 --- a/src/main/java/choonster/testmod3/api/capability/pigspawner/IPigSpawner.java +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawner.java @@ -1,10 +1,13 @@ -package choonster.testmod3.api.capability.pigspawner; +package choonster.testmod3.world.item.component.pigspawner; +import com.mojang.serialization.Codec; +import io.netty.buffer.ByteBuf; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.Level; - -import net.minecraftforge.common.capabilities.AutoRegisterCapability; import org.jetbrains.annotations.Nullable; + import java.util.List; /** @@ -12,8 +15,21 @@ * * @author Choonster */ -@AutoRegisterCapability public interface IPigSpawner { + Codec CODEC = PigSpawnerType.CODEC.dispatch(IPigSpawner::getType, PigSpawnerType::getCodec); + + StreamCodec STREAM_CODEC = PigSpawnerType.STREAM_CODEC.dispatch( + IPigSpawner::getType, + PigSpawnerType::getStreamCodec + ); + + /** + * Get the type of this spawner. + * + * @return The type. + */ + PigSpawnerType getType(); + /** * Can a pig be spawned at the specified position? * @@ -32,9 +48,21 @@ public interface IPigSpawner { * @param x The x coordinate * @param y The y coordinate * @param z The z coordinate - * @return Was the pig successfully spawned? + * @return The new pig spawner state if a pig was successfully spawned, otherwise, null. */ - boolean spawnPig(final Level level, final double x, final double y, final double z); + @Nullable + default IPigSpawner spawnPig(final Level level, final double x, final double y, final double z) { + final var pig = EntityType.PIG.create(level); + + if (pig == null) { + return null; + } + + pig.setPos(x, y, z); + + final var success = level.addFreshEntity(pig); + return success ? this : null; + } /** * Get the tooltip lines for this spawner. Can be called on the client or server. diff --git a/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerFinite.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerFinite.java new file mode 100644 index 00000000..273a1753 --- /dev/null +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerFinite.java @@ -0,0 +1,40 @@ +package choonster.testmod3.world.item.component.pigspawner; + +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +/** + * A spawner that can only spawn a finite number of pigs. + * + * @author Choonster + */ +public interface IPigSpawnerFinite extends IPigSpawner { + /** + * Get the current number of pigs that can be spawned. + * + * @return The number of pigs that can be spawned + */ + int numPigs(); + + /** + * Get the maximum number of pigs that can be spawned. + * + * @return The maximum number of pigs that can be spawned. + */ + int maxNumPigs(); + + /** + * Creates a copy of this spawner with a new number of pigs that can be spawned. + * + * @param numPigs The number of pigs that can be spawned + * @return The new spawner state + * @throws IllegalArgumentException If {@code numPigs} is greater than {@link #maxNumPigs()} + */ + IPigSpawnerFinite withNumPigs(final int numPigs); + + @Override + @Nullable + default IPigSpawnerFinite spawnPig(final Level level, final double x, final double y, final double z) { + return (IPigSpawnerFinite) IPigSpawner.super.spawnPig(level, x, y, z); + } +} diff --git a/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerInteractable.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerInteractable.java new file mode 100644 index 00000000..485c1676 --- /dev/null +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/IPigSpawnerInteractable.java @@ -0,0 +1,25 @@ +package choonster.testmod3.world.item.component.pigspawner; + +import net.minecraft.commands.CommandSource; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import org.jetbrains.annotations.Nullable; + +/** + * Something that can interacted with by an {@link IPigSpawner}. + * + * @author Choonster + */ +public interface IPigSpawnerInteractable { + /** + * Interact with the {@link IPigSpawner}. Only called on the server. + * + * @param pigSpawner The IPigSpawner + * @param world The level + * @param pos The position of this object + * @param commandSource The command source that caused the interaction, if any + * @return The new pig spawner state if it's changed and the default action should be prevented, otherwise, null + */ + @Nullable + IPigSpawner interact(final IPigSpawner pigSpawner, final Level world, final BlockPos pos, @Nullable final CommandSource commandSource); +} diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/InfinitePigSpawner.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/InfinitePigSpawner.java similarity index 52% rename from src/main/java/choonster/testmod3/capability/pigspawner/InfinitePigSpawner.java rename to src/main/java/choonster/testmod3/world/item/component/pigspawner/InfinitePigSpawner.java index d691be07..422c5912 100644 --- a/src/main/java/choonster/testmod3/capability/pigspawner/InfinitePigSpawner.java +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/InfinitePigSpawner.java @@ -1,10 +1,12 @@ -package choonster.testmod3.capability.pigspawner; +package choonster.testmod3.world.item.component.pigspawner; import choonster.testmod3.text.TestMod3Lang; import com.google.common.collect.ImmutableList; -import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import io.netty.buffer.ByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.codec.StreamCodec; import net.minecraft.world.level.Level; import java.util.List; @@ -14,10 +16,17 @@ * * @author Choonster */ -public class InfinitePigSpawner extends BasePigSpawner { - private static final InfinitePigSpawner INSTANCE = new InfinitePigSpawner(); +public record InfinitePigSpawner() implements IPigSpawner { + public static final InfinitePigSpawner INSTANCE = new InfinitePigSpawner(); - public static final Codec CODEC = Codec.unit(INSTANCE); + public static final MapCodec CODEC = MapCodec.unit(INSTANCE); + + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(INSTANCE); + + @Override + public PigSpawnerType getType() { + return PigSpawnerType.INFINITE; + } @Override public boolean canSpawnPig(final Level level, final double x, final double y, final double z) { diff --git a/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawner.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawner.java new file mode 100644 index 00000000..47b40b42 --- /dev/null +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawner.java @@ -0,0 +1,179 @@ +package choonster.testmod3.world.item.component.pigspawner; + +import choonster.testmod3.TestMod3; +import choonster.testmod3.init.ModDataComponents; +import choonster.testmod3.util.ModLogUtils; +import net.minecraft.ChatFormatting; +import net.minecraft.commands.CommandSource; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraftforge.event.entity.player.ItemTooltipEvent; +import net.minecraftforge.event.entity.player.PlayerInteractEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Marker; + +/** + * Helper functions for {@link IPigSpawner} components. + * + * @author Choonster + */ +public final class PigSpawner { + public static final Marker LOG_MARKER = ModLogUtils.getMarker("PIG_SPAWNER"); + + /** + * Event handler for the {@link IPigSpawner} capability. + */ + @Mod.EventBusSubscriber(modid = TestMod3.MODID) + private static class EventHandler { + @Nullable + private static IPigSpawner getPigSpawner(final ItemStack stack) { + return stack.get(ModDataComponents.PIG_SPAWNER.get()); + } + + private static void setPigSpawner(final ItemStack stack, final IPigSpawner pigSpawner) { + stack.set(ModDataComponents.PIG_SPAWNER.get(), pigSpawner); + } + + /** + * Attach the {@link IPigSpawner} capability to vanilla items. + * + * @param event The event + */ + // TODO: Attach component to CLAY_BALL +// @SubscribeEvent +// public static void attachCapabilities(final AttachCapabilitiesEvent event) { +// if (event.getObject().getItem() == Items.CLAY_BALL) { +// final var maxNumPigs = 20; +// final var pigSpawner = FinitePigSpawner.empty(maxNumPigs); +// final var codec = FinitePigSpawner.codec(maxNumPigs); +// +// event.addCapability(ID, createProvider(pigSpawner, codec)); +// } +// } + + /** + * Try to spawn a pig at the specified position, if the item has the {@link IPigSpawner} component. + *

+ * If there's an {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. + * + * @param stack The item that optionally has the pig spawner component + * @param level The level + * @param x The x position to spawn the pig at + * @param y The y position to spawn the pig at + * @param z The z position to spawn the pig at + * @param interactable The IPigSpawnerInteractable, if any + * @param interactablePos The position of the IPigSpawnerInteractable + * @param commandSource The command source, if any + */ + private static void trySpawnPig( + final ItemStack stack, + final Level level, + final double x, + final double y, + final double z, + @Nullable final IPigSpawnerInteractable interactable, + final BlockPos interactablePos, + @Nullable final CommandSource commandSource + ) { + if (level.isClientSide) { + return; + } + + final var pigSpawner = getPigSpawner(stack); + + if (pigSpawner == null) { + return; + } + + if (interactable != null) { + final var newPigSpawner = interactable.interact(pigSpawner, level, interactablePos, commandSource); + if (newPigSpawner != null) { + setPigSpawner(stack, newPigSpawner); + return; + } + } + + if (pigSpawner.canSpawnPig(level, x, y, z)) { + final var newPigSpawner = pigSpawner.spawnPig(level, x, y, z); + if (newPigSpawner != null) { + setPigSpawner(stack, newPigSpawner); + } + } + } + + /** + * Spawn a pig when a player right-clicks a block with an item that has the {@link IPigSpawner} component. + *

+ * If the block implements {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. + * + * @param event The event + */ + @SubscribeEvent + public static void playerInteract(final PlayerInteractEvent.RightClickBlock event) { + final var facing = event.getFace(); + assert facing != null; + + final var spawnPos = event.getPos().relative(facing); + final double x = spawnPos.getX() + 0.5, y = spawnPos.getY() + 0.5, z = spawnPos.getZ() + 0.5; + + final var level = event.getLevel(); + final var block = level.getBlockState(event.getPos()).getBlock(); + final var interactable = block instanceof IPigSpawnerInteractable ? (IPigSpawnerInteractable) block : null; + + final var player = event.getEntity(); + + trySpawnPig(event.getItemStack(), level, x, y, z, interactable, event.getPos(), player); + } + + /** + * Spawn a pig when a player right-clicks an entity with an item that has the {@link IPigSpawner} component. + *

+ * If the entity implements {@link IPigSpawnerInteractable}, call {@link IPigSpawnerInteractable#interact} on it. + * + * @param event The event + */ + @SubscribeEvent + public static void entityInteract(final PlayerInteractEvent.EntityInteract event) { + final var level = event.getLevel(); + + final var target = event.getTarget(); + final double x = target.getX(), y = target.getY(), z = target.getZ(); + final var interactable = target instanceof IPigSpawnerInteractable ? (IPigSpawnerInteractable) target : null; + + final var hand = event.getHand(); + final var heldItem = event.getEntity().getItemInHand(hand); + + trySpawnPig(heldItem, level, x, y, z, interactable, target.blockPosition(), event.getEntity()); + } + + /** + * Add the {@link IPigSpawner}'s tooltip lines to the tooltip if the item has the {@link IPigSpawner} component. + * + * @param event The event + */ + @SubscribeEvent + public static void itemTooltip(final ItemTooltipEvent event) { + final var pigSpawner = getPigSpawner(event.getItemStack()); + + if (pigSpawner == null) { + return; + } + + final var style = Style.EMPTY.withColor(ChatFormatting.LIGHT_PURPLE); + + final var tooltipLines = pigSpawner + .getTooltipLines() + .stream() + .map(textComponent -> textComponent.setStyle(style)) + .toList(); + + event.getToolTip().add(Component.literal("")); + event.getToolTip().addAll(tooltipLines); + } + } +} diff --git a/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawnerType.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawnerType.java new file mode 100644 index 00000000..dd0f9d4b --- /dev/null +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/PigSpawnerType.java @@ -0,0 +1,68 @@ +package choonster.testmod3.world.item.component.pigspawner; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.util.ByIdMap; +import net.minecraft.util.StringRepresentable; + +import java.util.function.IntFunction; + +/** + * The type of {@link IPigSpawner}. + * + * @author Choonster + */ +public enum PigSpawnerType implements StringRepresentable { + INFINITE(0, "infinite", InfinitePigSpawner.CODEC, InfinitePigSpawner.STREAM_CODEC), + FINITE(1, "finite", FinitePigSpawner.CODEC, FinitePigSpawner.STREAM_CODEC); + + private static final IntFunction BY_ID = ByIdMap.continuous( + PigSpawnerType::getId, + values(), + ByIdMap.OutOfBoundsStrategy.ZERO + ); + + public static final Codec CODEC = StringRepresentable.fromEnum(PigSpawnerType::values); + + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.idMapper( + BY_ID, + PigSpawnerType::getId + ); + + private final int id; + private final String name; + private final MapCodec codec; + private final StreamCodec streamCodec; + + PigSpawnerType( + final int id, + final String name, + final MapCodec codec, + final StreamCodec streamCodec + ) { + this.id = id; + this.name = name; + this.codec = codec; + this.streamCodec = streamCodec; + } + + public int getId() { + return id; + } + + @Override + public String getSerializedName() { + return name; + } + + public MapCodec getCodec() { + return codec; + } + + public StreamCodec getStreamCodec() { + return streamCodec; + } +} diff --git a/src/main/java/choonster/testmod3/capability/pigspawner/package-info.java b/src/main/java/choonster/testmod3/world/item/component/pigspawner/package-info.java similarity index 80% rename from src/main/java/choonster/testmod3/capability/pigspawner/package-info.java rename to src/main/java/choonster/testmod3/world/item/component/pigspawner/package-info.java index e3c8370b..cfc3b044 100644 --- a/src/main/java/choonster/testmod3/capability/pigspawner/package-info.java +++ b/src/main/java/choonster/testmod3/world/item/component/pigspawner/package-info.java @@ -1,7 +1,7 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -package choonster.testmod3.capability.pigspawner; +package choonster.testmod3.world.item.component.pigspawner; import net.minecraft.FieldsAreNonnullByDefault; import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/choonster/testmod3/world/level/block/ItemDebuggerBlock.java b/src/main/java/choonster/testmod3/world/level/block/ItemDebuggerBlock.java index 2e84d458..0c528acb 100644 --- a/src/main/java/choonster/testmod3/world/level/block/ItemDebuggerBlock.java +++ b/src/main/java/choonster/testmod3/world/level/block/ItemDebuggerBlock.java @@ -1,11 +1,11 @@ package choonster.testmod3.world.level.block; -import choonster.testmod3.capability.pigspawner.PigSpawnerCapability; +import choonster.testmod3.init.ModDataComponents; import choonster.testmod3.util.RegistryUtil; import com.mojang.logging.LogUtils; import com.mojang.serialization.MapCodec; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; +import net.minecraft.core.component.DataComponentType; import net.minecraft.world.InteractionHand; import net.minecraft.world.ItemInteractionResult; import net.minecraft.world.entity.player.Player; @@ -14,10 +14,8 @@ import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.BlockHitResult; -import net.minecraftforge.common.capabilities.Capability; import net.minecraftforge.fluids.FluidUtil; import net.minecraftforge.fml.ModList; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; /** @@ -42,7 +40,7 @@ protected MapCodec codec() { private void logItem(final Level level, final ItemStack stack) { if (!stack.isEmpty()) { LOGGER.info("ItemStack: {}", stack.save(level.registryAccess())); - logCapability(stack, PigSpawnerCapability.PIG_SPAWNER_CAPABILITY, Direction.NORTH); + logComponent(stack, ModDataComponents.PIG_SPAWNER.get()); logFluidHandler(stack); final var key = RegistryUtil.getKey(stack.getItem()); @@ -54,10 +52,12 @@ private void logItem(final Level level, final ItemStack stack) { } } - private void logCapability(final ItemStack stack, final Capability capability, @Nullable final Direction facing) { - stack.getCapability(capability, facing).ifPresent(instance -> - LOGGER.info("Capability: {} - {}", capability.getName(), instance) - ); + private void logComponent(final ItemStack stack, final DataComponentType componentType) { + final var component = stack.get(componentType); + + if (component != null) { + LOGGER.info("Component: {} - {}", RegistryUtil.getKey(componentType), component); + } } private void logFluidHandler(final ItemStack stack) { diff --git a/src/main/java/choonster/testmod3/world/level/block/PigSpawnerRefillerBlock.java b/src/main/java/choonster/testmod3/world/level/block/PigSpawnerRefillerBlock.java index dd2363af..df3c094a 100644 --- a/src/main/java/choonster/testmod3/world/level/block/PigSpawnerRefillerBlock.java +++ b/src/main/java/choonster/testmod3/world/level/block/PigSpawnerRefillerBlock.java @@ -1,9 +1,9 @@ package choonster.testmod3.world.level.block; -import choonster.testmod3.api.capability.pigspawner.IPigSpawner; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerFinite; -import choonster.testmod3.api.capability.pigspawner.IPigSpawnerInteractable; import choonster.testmod3.text.TestMod3Lang; +import choonster.testmod3.world.item.component.pigspawner.IPigSpawner; +import choonster.testmod3.world.item.component.pigspawner.IPigSpawnerFinite; +import choonster.testmod3.world.item.component.pigspawner.IPigSpawnerInteractable; import com.mojang.serialization.MapCodec; import net.minecraft.commands.CommandSource; import net.minecraft.core.BlockPos; @@ -30,15 +30,17 @@ protected MapCodec codec() { } @Override - public boolean interact(final IPigSpawner pigSpawner, final Level world, final BlockPos pos, @Nullable final CommandSource iCommandSender) { - if (pigSpawner instanceof final IPigSpawnerFinite pigSpawnerFinite) { - pigSpawnerFinite.setNumPigs(pigSpawnerFinite.getMaxNumPigs()); + public IPigSpawner interact(final IPigSpawner pigSpawner, final Level world, final BlockPos pos, @Nullable final CommandSource commandSource) { + if (pigSpawner instanceof final IPigSpawnerFinite finitePigSpawner) { + final var newPigSpawner = finitePigSpawner.withNumPigs(finitePigSpawner.maxNumPigs()); - if (iCommandSender != null) { - iCommandSender.sendSystemMessage(Component.translatable(TestMod3Lang.MESSAGE_PIG_SPAWNER_REFILLER_REFILLED.getTranslationKey())); + if (commandSource != null) { + commandSource.sendSystemMessage(Component.translatable(TestMod3Lang.MESSAGE_PIG_SPAWNER_REFILLER_REFILLED.getTranslationKey())); } + + return newPigSpawner; } - return true; + return null; } }