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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions src/main/java/knightminer/simplytea/core/Registration.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
});
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/knightminer/simplytea/core/config/Teapot.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
Expand All @@ -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();
}

Expand All @@ -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();
}
}
99 changes: 99 additions & 0 deletions src/main/java/knightminer/simplytea/fluid/FluidTeapotWrapper.java
Original file line number Diff line number Diff line change
@@ -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<IFluidHandlerItem> 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 <T> @NotNull LazyOptional<T> getCapability(@NotNull Capability<T> capability, @Nullable Direction side) {
return ForgeCapabilities.FLUID_HANDLER_ITEM.orEmpty(capability, holder);
}
}
85 changes: 51 additions & 34 deletions src/main/java/knightminer/simplytea/item/TeapotItem.java
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -33,40 +40,44 @@ public TeapotItem(Properties props) {
public InteractionResultHolder<ItemStack> 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<SoundEvent> 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work if you make the config lower than 1000 for the teapot capacity? If not we need to special case that here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the way FluidUtil is handling it, it should be treated as a successful pickup as long as the item's fluid handler returns a non-zero filled amount.

The item fluid handler (FluidTeapotWrapper) uses the configured size. It will either "fill" 0 (fail) or teapotCapacity (success).

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
Expand All @@ -82,4 +93,10 @@ public InteractionResult interactLivingEntity(ItemStack stack, Player player, Li
}
return InteractionResult.PASS;
}
}

@Nullable
@Override
public ICapabilityProvider initCapabilities(ItemStack stack, @Nullable CompoundTag nbt) {
return new FluidTeapotWrapper(stack);
}
}