391 lines
16 KiB
Java
391 lines
16 KiB
Java
/*
|
|
* Ex Deorum
|
|
* Copyright (c) 2024 thedarkcolour
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package thedarkcolour.exdeorum.item;
|
|
|
|
import com.mojang.blaze3d.vertex.PoseStack;
|
|
import com.mojang.math.Axis;
|
|
import net.minecraft.ChatFormatting;
|
|
import net.minecraft.client.player.LocalPlayer;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.core.particles.ParticleTypes;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.server.level.ServerLevel;
|
|
import net.minecraft.tags.BlockTags;
|
|
import net.minecraft.util.Mth;
|
|
import net.minecraft.world.InteractionHand;
|
|
import net.minecraft.world.InteractionResult;
|
|
import net.minecraft.world.entity.HumanoidArm;
|
|
import net.minecraft.world.entity.LivingEntity;
|
|
import net.minecraft.world.entity.ai.attributes.Attributes;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemStack;
|
|
import net.minecraft.world.item.TooltipFlag;
|
|
import net.minecraft.world.item.ItemUseAnimation;
|
|
import net.minecraft.world.level.ClipContext;
|
|
import net.minecraft.world.level.Level;
|
|
import net.minecraft.world.level.block.Blocks;
|
|
import net.minecraft.world.level.block.BucketPickup;
|
|
import net.minecraft.world.level.block.FarmlandBlock;
|
|
import net.minecraft.world.level.block.LevelEvent;
|
|
import net.minecraft.world.level.block.SugarCaneBlock;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.level.material.Fluids;
|
|
import net.minecraft.world.phys.BlockHitResult;
|
|
import net.minecraft.world.phys.HitResult;
|
|
import net.neoforged.neoforge.capabilities.Capabilities;
|
|
import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions;
|
|
import net.neoforged.neoforge.common.util.FakePlayer;
|
|
import net.neoforged.neoforge.fluids.FluidStack;
|
|
import net.neoforged.neoforge.fluids.capability.IFluidHandler;
|
|
import net.neoforged.neoforge.fluids.capability.templates.FluidHandlerItemStack;
|
|
import thedarkcolour.exdeorum.blockentity.BarrelBlockEntity;
|
|
import thedarkcolour.exdeorum.data.TranslationKeys;
|
|
import thedarkcolour.exdeorum.registry.EDataComponents;
|
|
import thedarkcolour.exdeorum.registry.ESounds;
|
|
import thedarkcolour.exdeorum.tag.EBlockTags;
|
|
|
|
import java.util.List;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Supplier;
|
|
|
|
public class WateringCanItem extends Item {
|
|
private static final int WATERING_INTERVAL = 4;
|
|
private static final int STARTUP_TIME = 10;
|
|
// only used on the clientside
|
|
private static boolean isWatering = false;
|
|
|
|
private final int capacity;
|
|
private final boolean renewing;
|
|
private final boolean usableInMachines;
|
|
|
|
public WateringCanItem(int capacity, Properties properties) {
|
|
super(properties);
|
|
|
|
this.capacity = capacity;
|
|
this.renewing = capacity >= 4000;
|
|
this.usableInMachines = false;
|
|
}
|
|
|
|
protected WateringCanItem(boolean usableInMachines, Properties properties) {
|
|
super(properties);
|
|
|
|
this.capacity = 4000;
|
|
this.renewing = true;
|
|
this.usableInMachines = usableInMachines;
|
|
}
|
|
|
|
public static ItemStack getFull(Supplier<? extends Item> wateringCan) {
|
|
var stack = new ItemStack(wateringCan.get());
|
|
var fluidHandler = stack.getCapability(Capabilities.FluidHandler.ITEM);
|
|
if (fluidHandler != null) {
|
|
fluidHandler.fill(new FluidStack(Fluids.WATER, Integer.MAX_VALUE), IFluidHandler.FluidAction.EXECUTE);
|
|
}
|
|
return stack;
|
|
}
|
|
|
|
@Override
|
|
public boolean isBarVisible(ItemStack stack) {
|
|
if (this.renewing) {
|
|
var fluidHandler = stack.getCapability(Capabilities.FluidHandler.ITEM);
|
|
return fluidHandler == null || fluidHandler.getFluidInTank(0).getAmount() < this.capacity;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getBarColor(ItemStack pStack) {
|
|
return 0x3F76E4;
|
|
}
|
|
|
|
@Override
|
|
public int getBarWidth(ItemStack stack) {
|
|
var fluidHandler = stack.getCapability(Capabilities.FluidHandler.ITEM);
|
|
if (fluidHandler != null) {
|
|
return Math.round((float) fluidHandler.getFluidInTank(0).getAmount() * 13f / (float) this.capacity);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getUseDuration(ItemStack stack, LivingEntity entity) {
|
|
return 72000;
|
|
}
|
|
|
|
@Override
|
|
public ItemUseAnimation getUseAnimation(ItemStack stack) {
|
|
return ItemUseAnimation.NONE;
|
|
}
|
|
|
|
@Override
|
|
public void appendHoverText(ItemStack stack, TooltipContext context, List<Component> tooltip, TooltipFlag pIsAdvanced) {
|
|
var fluidHandler = stack.getCapability(Capabilities.FluidHandler.ITEM);
|
|
if (fluidHandler != null) {
|
|
// use the block name which is guaranteed to have a vanilla translation
|
|
tooltip.add(Component.translatable("block.minecraft.water").append(Component.translatable(TranslationKeys.FRACTION_DISPLAY, fluidHandler.getFluidInTank(0).getAmount(), this.capacity)).withStyle(ChatFormatting.GRAY));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public InteractionResult use(Level level, Player player, InteractionHand hand) {
|
|
var itemInHand = player.getItemInHand(hand);
|
|
var fluidHandler = itemInHand.getCapability(Capabilities.FluidHandler.ITEM);
|
|
if (fluidHandler != null) {
|
|
if (fluidHandler.getFluidInTank(0).getAmount() < this.capacity) {
|
|
var hitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.SOURCE_ONLY);
|
|
|
|
if (hitResult.getType() == HitResult.Type.BLOCK) {
|
|
var pos = hitResult.getBlockPos();
|
|
var state = level.getBlockState(pos);
|
|
|
|
if (state.getFluidState().getType() == Fluids.WATER && state.getBlock() instanceof BucketPickup pickup) {
|
|
if (!level.isClientSide) {
|
|
fluidHandler.fill(new FluidStack(Fluids.WATER, 1000), IFluidHandler.FluidAction.EXECUTE);
|
|
pickup.pickupBlock(player, level, pos, state);
|
|
pickup.getPickupSound(state).ifPresent(sound -> player.playSound(sound, 1.0F, 1.0F));
|
|
}
|
|
|
|
return level.isClientSide ? InteractionResult.SUCCESS : InteractionResult.SUCCESS_SERVER;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fluidHandler.getFluidInTank(0).isEmpty()) {
|
|
var realPlayer = !(player instanceof FakePlayer);
|
|
|
|
if (realPlayer) {
|
|
player.startUsingItem(hand);
|
|
} else if (this.usableInMachines) {
|
|
onUseTick(level, player, itemInHand, 72000);
|
|
}
|
|
|
|
return InteractionResult.CONSUME;
|
|
}
|
|
}
|
|
return InteractionResult.PASS;
|
|
}
|
|
|
|
@Override
|
|
public void onUseTick(Level level, LivingEntity living, ItemStack stack, int remainingTicks) {
|
|
var useTicks = 72000 - remainingTicks;
|
|
|
|
if (useTicks >= STARTUP_TIME || living instanceof FakePlayer) {
|
|
var fluidHandler = stack.getCapability(Capabilities.FluidHandler.ITEM);
|
|
if (fluidHandler != null) {
|
|
if (!fluidHandler.getFluidInTank(0).isEmpty()) {
|
|
// do watering can
|
|
var reachDist = living.getAttributeValue(Attributes.BLOCK_INTERACTION_RANGE);
|
|
var hit = living.pick(reachDist, 0, true);
|
|
|
|
if (hit instanceof BlockHitResult blockHit && blockHit.getType() == HitResult.Type.BLOCK) {
|
|
var pos = blockHit.getBlockPos();
|
|
var state = level.getBlockState(pos);
|
|
|
|
if (!level.isClientSide) {
|
|
if (useTicks % WATERING_INTERVAL == 0) {
|
|
tryWatering((ServerLevel) level, pos, state);
|
|
|
|
if (!this.renewing || fluidHandler.getFluidInTank(0).getAmount() != this.capacity) {
|
|
if (!(living instanceof Player player && player.getAbilities().instabuild)) {
|
|
((FluidHandler) fluidHandler).drain();
|
|
}
|
|
}
|
|
}
|
|
if (useTicks % 2 == 0) {
|
|
waterParticles(level, pos, state);
|
|
}
|
|
if ((useTicks - STARTUP_TIME) % 20 == 0) {
|
|
level.playSound(null, pos, ESounds.WATERING_CAN_USE.get(), living.getSoundSource(), this.getClass() == WideWateringCanItem.class ? 0.6f : 0.3f, 1.5f);
|
|
}
|
|
} else {
|
|
isWatering = true;
|
|
}
|
|
} else {
|
|
isWatering = false;
|
|
}
|
|
} else {
|
|
living.stopUsingItem();
|
|
isWatering = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void releaseUsing(ItemStack stack, Level level, LivingEntity living, int timeCharged) {
|
|
if (timeCharged > STARTUP_TIME) {
|
|
level.playLocalSound(living.getX(), living.getY(), living.getZ(), ESounds.WATERING_CAN_STOP.get(), living.getSoundSource(), 0.6f, 0.7f, false);
|
|
}
|
|
}
|
|
|
|
protected void tryWatering(ServerLevel level, BlockPos pos, BlockState state) {
|
|
if (state.is(EBlockTags.WATERING_CAN_TICKABLE)) {
|
|
if (state.is(BlockTags.SAPLINGS)) {
|
|
if (level.random.nextInt(3) == 0) {
|
|
state.randomTick(level, pos, level.random);
|
|
level.levelEvent(LevelEvent.PARTICLES_AND_SOUND_PLANT_GROWTH, pos, 0);
|
|
}
|
|
} else if (state.getBlock() instanceof SugarCaneBlock block) {
|
|
var cursor = pos.mutable();
|
|
while (level.isInWorldBounds(cursor.move(0, 1, 0)) && level.getBlockState(cursor).getBlock() == block) {
|
|
// just keep looping, cursor is moved up each check
|
|
}
|
|
// randomTick only works on the top sugarcane block
|
|
var topState = level.getBlockState(cursor.move(0, -1, 0));
|
|
topState.randomTick(level, cursor, level.random);
|
|
} else {
|
|
state.randomTick(level, pos, level.random);
|
|
}
|
|
} else {
|
|
if (BarrelBlockEntity.isHotFluid(state.getFluidState().getFluidType())) {
|
|
level.levelEvent(LevelEvent.LAVA_FIZZ, pos, 0);
|
|
} else if (state.getBlock() instanceof FarmlandBlock) {
|
|
hydrateFarmland(level, pos, state);
|
|
}
|
|
}
|
|
var below = pos.below();
|
|
var belowState = level.getBlockState(below);
|
|
if (belowState.getBlock() == Blocks.FARMLAND) {
|
|
hydrateFarmland(level, below, belowState);
|
|
}
|
|
}
|
|
|
|
private static void hydrateFarmland(ServerLevel level, BlockPos pos, BlockState state) {
|
|
var randomPos = pos.offset(level.random.nextIntBetweenInclusive(-1, 1), 0, level.random.nextIntBetweenInclusive(-1, 1));
|
|
|
|
if (randomPos != pos) {
|
|
pos = randomPos;
|
|
state = level.getBlockState(pos);
|
|
|
|
if (state.getBlock() != Blocks.FARMLAND) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (state.getValue(FarmlandBlock.MOISTURE) < 7) {
|
|
level.setBlockAndUpdate(pos, state.setValue(FarmlandBlock.MOISTURE, 7));
|
|
}
|
|
}
|
|
|
|
protected void waterParticles(Level level, BlockPos pos, BlockState state) {
|
|
if (level instanceof ServerLevel serverLevel) {
|
|
double x = pos.getX() + 0.5 + level.random.nextGaussian() / 8f;
|
|
double y = pos.getY();
|
|
double z = pos.getZ() + 0.5 + level.random.nextGaussian() / 8f;
|
|
var collisionShape = state.getCollisionShape(level, pos);
|
|
if (!collisionShape.isEmpty()) {
|
|
y += collisionShape.max(Direction.Axis.Y);
|
|
}
|
|
for (int i = -1; i <= 1; i++) {
|
|
for (int j = -1; j <= 1; j++) {
|
|
if (level.random.nextBoolean()) {
|
|
serverLevel.sendParticles(ParticleTypes.RAIN, x + i * 0.33, y, z + j * 0.33, 2, 0, 0, 0, 0.2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void initializeClient(Consumer<IClientItemExtensions> consumer) {
|
|
consumer.accept(ClientExtensions.INSTANCE);
|
|
}
|
|
|
|
public static class FluidHandler extends FluidHandlerItemStack {
|
|
public FluidHandler(ItemStack container) {
|
|
super(EDataComponents.WATERING_CAN, container, determineCapacityFromItem(container.getItem()));
|
|
}
|
|
|
|
private static int determineCapacityFromItem(Item item) {
|
|
if (item instanceof WateringCanItem wateringCan) {
|
|
return wateringCan.capacity;
|
|
} else {
|
|
throw new IllegalArgumentException("Invalid watering can");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean canFillFluidType(FluidStack fluid) {
|
|
return fluid.getFluid() == Fluids.WATER;
|
|
}
|
|
|
|
@Override
|
|
public boolean canDrainFluidType(FluidStack fluid) {
|
|
return false;
|
|
}
|
|
|
|
public void drain() {
|
|
var contained = getFluid();
|
|
var drainAmount = Math.min(contained.getAmount(), 1);
|
|
|
|
var drained = contained.copy();
|
|
drained.setAmount(drainAmount);
|
|
|
|
contained.shrink(drainAmount);
|
|
|
|
if (contained.isEmpty()) {
|
|
setContainerToEmpty();
|
|
} else {
|
|
setFluid(contained);
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum ClientExtensions implements IClientItemExtensions {
|
|
INSTANCE;
|
|
|
|
@Override
|
|
public boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, HumanoidArm arm, ItemStack itemInHand, float partialTick, float equipProcess, float swingProcess) {
|
|
if (player.isUsingItem()) {
|
|
var useTicks = 72000 - 1 - player.getUseItemRemainingTicks();
|
|
var step = useTicks + partialTick;
|
|
var startProgress = easeOutCubic(Math.min(step, STARTUP_TIME) / STARTUP_TIME);
|
|
|
|
poseStack.translate(-0.2 * startProgress, -0.2 * startProgress, 0);
|
|
|
|
if (startProgress == 1.0f && isWatering) {
|
|
var sin = Mth.sin(0.35f * (step - 10f));
|
|
poseStack.rotateAround(Axis.XP.rotationDegrees(10 * sin), 0f, 0f, -0.2f);
|
|
}
|
|
|
|
var rotate = Mth.lerp(startProgress, 0, Mth.DEG_TO_RAD);
|
|
|
|
poseStack.rotateAround(Axis.ZP.rotation(rotate * 15f), -0.75f, 0f, 0);
|
|
|
|
int i = arm == HumanoidArm.RIGHT ? 1 : -1;
|
|
poseStack.translate((float) i * 0.56F, -0.52F + (player.isUsingItem() ? 0 : equipProcess) * -0.6F, -0.72F);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
// https://easings.net/#easeOutCubic
|
|
private static float easeOutCubic(float progress) {
|
|
var opposite = 1 - progress;
|
|
return 1 - opposite * opposite * opposite;
|
|
}
|
|
}
|
|
}
|