diff --git a/src/main/java/knightminer/simplytea/core/Registration.java b/src/main/java/knightminer/simplytea/core/Registration.java index 6963485..959a33e 100644 --- a/src/main/java/knightminer/simplytea/core/Registration.java +++ b/src/main/java/knightminer/simplytea/core/Registration.java @@ -35,13 +35,18 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.stats.Stats; import net.minecraft.world.InteractionResult; import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.BlockItem; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemUtils; import net.minecraft.world.item.crafting.RecipeSerializer; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; @@ -53,8 +58,10 @@ import net.minecraft.world.level.block.LayeredCauldronBlock; import net.minecraft.world.level.block.SaplingBlock; import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.NoteBlockInstrument; import net.minecraft.world.level.block.state.properties.WoodType; +import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import net.minecraft.world.level.levelgen.feature.Feature; import net.minecraft.world.level.levelgen.feature.configurations.NoneFeatureConfiguration; @@ -265,9 +272,21 @@ static void registerMisc(final FMLCommonSetupEvent event) { RestfulEffect.addConflict(caffeinated); RestfulEffect.addConflict(invigorated); + // Lowers the cauldron by a variable amound depending on configured teapot size + // Based on GLASS_BOTTLE interaction and LayeredCauldronBlock.lowerFillLevel() CauldronInteraction.WATER.put(teapot, (state, level, pos, player, hand, stack) -> { - if (Config.SERVER.teapot.fillFromCauldron()) { - return CauldronInteraction.fillBucket(state, level, pos, player, hand, stack, new ItemStack(teapot_water), s -> s.getValue(LayeredCauldronBlock.LEVEL) == 3, SoundEvents.BUCKET_FILL); + // always consume at least one cauldron level, even for tiny teapots + int teapotLevels = Math.max(1, Math.round(Config.SERVER.teapot.teapotCapacity() / 333.0f)); + if (Config.SERVER.teapot.fillFromCauldron() && state.getValue(LayeredCauldronBlock.LEVEL) >= teapotLevels && !level.isClientSide()) { + player.setItemInHand(hand, ItemUtils.createFilledResult(stack, player, new ItemStack(teapot_water))); + player.awardStat(Stats.USE_CAULDRON); + player.awardStat(Stats.ITEM_USED.get(teapot_water)); + int cauldronLevel = state.getValue(LayeredCauldronBlock.LEVEL) - teapotLevels; + BlockState cauldronstate = cauldronLevel == 0 ? Blocks.CAULDRON.defaultBlockState() : state.setValue(LayeredCauldronBlock.LEVEL, Integer.valueOf(cauldronLevel)); + level.setBlockAndUpdate(pos, cauldronstate); + level.gameEvent(GameEvent.BLOCK_CHANGE, pos, GameEvent.Context.of(cauldronstate)); + level.playSound((Player)null, pos, SoundEvents.BUCKET_FILL, SoundSource.BLOCKS, 1.0F, 1.0F); + level.gameEvent((Entity)null, GameEvent.FLUID_PICKUP, pos); } return InteractionResult.PASS; }); diff --git a/src/main/java/knightminer/simplytea/core/config/Teapot.java b/src/main/java/knightminer/simplytea/core/config/Teapot.java index 8420934..11b7244 100644 --- a/src/main/java/knightminer/simplytea/core/config/Teapot.java +++ b/src/main/java/knightminer/simplytea/core/config/Teapot.java @@ -2,11 +2,14 @@ import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec.BooleanValue; +import net.minecraftforge.common.ForgeConfigSpec.IntValue; +import net.minecraftforge.fluids.FluidType; public class Teapot { private BooleanValue infinite_water; private BooleanValue fill_from_cauldron; private BooleanValue milk_cow; + private IntValue teapot_capacity; public Teapot(ForgeConfigSpec.Builder builder) { builder.comment("Options related to filling the teapot").push("teapot"); infinite_water = builder.comment("If true, the teapot will not consume water source blocks when filling. It will still consume water from tank and cauldrons.") @@ -18,6 +21,9 @@ public Teapot(ForgeConfigSpec.Builder builder) { milk_cow = builder.comment("If true, cows can be milked using a teapot to fill it with milk") .translation("simplytea.config.teapot.milk_cow") .define("milk_cow", true); + teapot_capacity = builder.comment("Amount of fluid consumed when filling a teapot from a tank") + .translation("simplytea.config.teapot.teapot_capacity") + .defineInRange("teapot_capacity", FluidType.BUCKET_VOLUME, 1, FluidType.BUCKET_VOLUME * 256); builder.pop(); } @@ -35,4 +41,9 @@ public boolean fillFromCauldron() { public boolean canMilkCows() { return milk_cow.get(); } + + /** Amount of fluid a teapot can contain */ + public int teapotCapacity() { + return teapot_capacity.get(); + } } diff --git a/src/main/java/knightminer/simplytea/fluid/FluidTeapotWrapper.java b/src/main/java/knightminer/simplytea/fluid/FluidTeapotWrapper.java new file mode 100644 index 0000000..bd32bd0 --- /dev/null +++ b/src/main/java/knightminer/simplytea/fluid/FluidTeapotWrapper.java @@ -0,0 +1,99 @@ +package knightminer.simplytea.fluid; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import knightminer.simplytea.core.Config; +import knightminer.simplytea.core.Registration; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; +import net.minecraftforge.common.Tags; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; + +public class FluidTeapotWrapper implements IFluidHandlerItem, ICapabilityProvider { + private final LazyOptional holder = LazyOptional.of(() -> this); + + @NotNull + protected ItemStack container; + protected FluidStack fluid = FluidStack.EMPTY; + + public FluidTeapotWrapper(ItemStack container) { + this.container = container; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public @NotNull FluidStack getFluidInTank(int tank) { + return FluidStack.EMPTY; + } + + @Override + public int getTankCapacity(int tank) { + return Config.SERVER.teapot.teapotCapacity(); + } + + public static boolean isWater(Fluid fluid) { + // Many modded fluids use the WATER tag to give the fluids entity interaction + // Only accept Fluids.WATER and not any other fluid tagged as WATER + return fluid.isSame(Fluids.WATER) || fluid.isSame(Fluids.FLOWING_WATER); + } + + public static boolean isMilk(Fluid fluid) { + // Be more flexible with MILK because if it's tagged as MILK then it should really be milk + return fluid.is(Tags.Fluids.MILK) || fluid.getBucket() == Items.MILK_BUCKET; + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack fluid) { + return isWater(fluid.getFluid()) || isMilk(fluid.getFluid()); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (container.getCount() != 1 || !isFluidValid(0, resource) || resource.getAmount() < Config.SERVER.teapot.teapotCapacity()) { + return 0; + } + + if (action.execute()) { + if (isWater(resource.getFluid())) { + this.container = new ItemStack(Registration.teapot_water); + } else if (isMilk(resource.getFluid())) { + this.container = new ItemStack(Registration.teapot_milk); + } + } + + return Config.SERVER.teapot.teapotCapacity(); + } + + @Override + public @NotNull FluidStack drain(FluidStack resource, FluidAction action) { + return FluidStack.EMPTY; + } + + @Override + public @NotNull FluidStack drain(int maxDrain, FluidAction action) { + return FluidStack.EMPTY; + } + + @Override + public @NotNull ItemStack getContainer() { + return container; + } + + @Override + public @NotNull LazyOptional getCapability(@NotNull Capability capability, @Nullable Direction side) { + return ForgeCapabilities.FLUID_HANDLER_ITEM.orEmpty(capability, holder); + } +} diff --git a/src/main/java/knightminer/simplytea/item/TeapotItem.java b/src/main/java/knightminer/simplytea/item/TeapotItem.java index fecfb36..c87cf86 100644 --- a/src/main/java/knightminer/simplytea/item/TeapotItem.java +++ b/src/main/java/knightminer/simplytea/item/TeapotItem.java @@ -1,28 +1,35 @@ package knightminer.simplytea.item; +import org.jetbrains.annotations.Nullable; + import knightminer.simplytea.core.Config; import knightminer.simplytea.core.Registration; +import knightminer.simplytea.fluid.FluidTeapotWrapper; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; -import net.minecraft.tags.FluidTags; import net.minecraft.world.InteractionHand; import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResultHolder; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.animal.Cow; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemUtils; import net.minecraft.world.level.ClipContext; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.BucketPickup; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.Fluids; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.HitResult.Type; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraftforge.fluids.FluidActionResult; +import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.fluids.capability.IFluidHandler; + +import java.util.Optional; public class TeapotItem extends TooltipItem { public TeapotItem(Properties props) { @@ -33,40 +40,44 @@ public TeapotItem(Properties props) { public InteractionResultHolder use(Level world, Player player, InteractionHand hand) { ItemStack stack = player.getItemInHand(hand); BlockHitResult rayTrace = getPlayerPOVHitResult(world, player, ClipContext.Fluid.SOURCE_ONLY); - if (rayTrace.getType() == Type.BLOCK) { - BlockPos pos = rayTrace.getBlockPos(); - BlockState state = world.getBlockState(pos); - - // we use name for lookup to prevent default fluid conflicts - Fluid fluid = state.getFluidState().getType(); - if(fluid != Fluids.EMPTY) { - // try for water or milk using the config lists - Item item = null; - if (fluid.is(FluidTags.WATER)) { - item = Registration.teapot_water; - } // TODO: milk when mods make a standard - - // if either one is found, update the stack - if(item != null) { - // water is considered infinite unless disabled in the config - if(!Config.SERVER.teapot.infiniteWater()) { - Direction side = rayTrace.getDirection(); - // unable to modify the block, fail - if (!world.mayInteract(player, pos) || !player.mayUseItemAt(pos.relative(side), side, stack) || !(state.getBlock() instanceof BucketPickup)) { - return new InteractionResultHolder<>(InteractionResult.FAIL, stack); - } - ((BucketPickup)state.getBlock()).pickupBlock(world, pos, state); - } + if (rayTrace.getType() != Type.BLOCK) { + return InteractionResultHolder.pass(stack); + } + + BlockPos pos = rayTrace.getBlockPos(); + Direction side = rayTrace.getDirection(); + BlockState state = world.getBlockState(pos); - stack = ItemUtils.createFilledResult(stack, player, new ItemStack(item)); + if (!world.mayInteract(player, pos) || !player.mayUseItemAt(pos.relative(side), side, stack)) { + return InteractionResultHolder.fail(stack); + } - // TODO: fluid sound based on fluid - player.playSound(SoundEvents.BUCKET_FILL, 1.0f, 1.0f); - return new InteractionResultHolder<>(InteractionResult.SUCCESS, stack); + ItemStack filledStack = ItemStack.EMPTY; + if (state.getBlock() instanceof BucketPickup bucketPickup) { + // special case for infinite water + if (FluidTeapotWrapper.isWater(state.getFluidState().getType()) && Config.SERVER.teapot.infiniteWater()) { + filledStack = new ItemStack(Registration.teapot_water); + Optional sound = bucketPickup.getPickupSound(state); + if (sound.isPresent()) { + player.playSound(sound.get(), 1.0f, 1.0f); + } + } + + // use teapot like a bucket to get fluid, should work in most cases + if (filledStack.isEmpty()) { + FluidActionResult actionResult = FluidUtil.tryPickUpFluid(stack, player, world, pos, side); + if (actionResult.isSuccess()) { + filledStack = actionResult.getResult(); } } } - return new InteractionResultHolder<>(InteractionResult.FAIL, stack); + + if (!filledStack.isEmpty()) { + ItemStack filledResult = ItemUtils.createFilledResult(stack, player, filledStack); + return InteractionResultHolder.sidedSuccess(filledResult, world.isClientSide()); + } + + return InteractionResultHolder.fail(stack); } @Override @@ -82,4 +93,10 @@ public InteractionResult interactLivingEntity(ItemStack stack, Player player, Li } return InteractionResult.PASS; } -} \ No newline at end of file + + @Nullable + @Override + public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt) { + return new FluidTeapotWrapper(stack); + } +}