package carpetextra.mixins; import carpetextra.CarpetExtraSettings; import net.minecraft.block.AbstractRailBlock; import net.minecraft.block.BlockState; import net.minecraft.block.entity.BlockEntity; import net.minecraft.block.entity.Hopper; import net.minecraft.block.entity.HopperBlockEntity; import net.minecraft.block.enums.RailShape; import net.minecraft.entity.EntityType; import net.minecraft.entity.vehicle.HopperMinecartEntity; import net.minecraft.entity.vehicle.StorageMinecartEntity; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SidedInventory; import net.minecraft.item.ItemStack; import net.minecraft.tag.BlockTags; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; import java.util.stream.IntStream; import static net.minecraft.block.entity.HopperBlockEntity.transfer; @Mixin(HopperMinecartEntity.class) public abstract class HopperMinecartEntity_transferItemsOutFeatureMixin extends StorageMinecartEntity implements Hopper { @Shadow @Final @Mutable private BlockPos currentBlockPos; @Shadow public abstract boolean canOperate(); public HopperMinecartEntity_transferItemsOutFeatureMixin(EntityType<? extends HopperMinecartEntity> entityType_1, World world_1) { super(entityType_1, world_1); this.currentBlockPos = BlockPos.ORIGIN; } @Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/vehicle/HopperMinecartEntity;canOperate()Z")) private boolean operate(HopperMinecartEntity hopperMinecartEntity){ boolean workDone = false; if(CarpetExtraSettings.hopperMinecartItemTransfer) workDone = this.insert(); workDone |= this.canOperate(); return workDone; } private static final Vec3d upwardVec = new Vec3d(0,1,0).normalize().multiply(-1); private static final Vec3d ascending_east_offset = new Vec3d(-1,1, 0).normalize().multiply(-1); private static final Vec3d ascending_west_offset = new Vec3d( 1,1, 0).normalize().multiply(-1); private static final Vec3d ascending_north_offset= new Vec3d( 0,1, 1).normalize().multiply(-1); private static final Vec3d ascending_south_offset= new Vec3d( 0,1,-1).normalize().multiply(-1); private Vec3d getBlockBelowCartOffset(){ BlockState blockState_1 = this.world.getBlockState(new BlockPos(MathHelper.floor(this.getX()), MathHelper.floor(this.getY()), MathHelper.floor(this.getZ()))); if (blockState_1.matches(BlockTags.RAILS)) { RailShape railShape = (RailShape)blockState_1.get(((AbstractRailBlock)blockState_1.getBlock()).getShapeProperty()); switch (railShape){ case ASCENDING_EAST: return ascending_east_offset; case ASCENDING_WEST: return ascending_west_offset; case ASCENDING_NORTH: return ascending_north_offset; case ASCENDING_SOUTH: return ascending_south_offset; default: return upwardVec; } } return upwardVec; } private Direction outputDirection = Direction.DOWN; private Inventory getOutputInventory() { Vec3d offsetToInventory = getBlockBelowCartOffset(); //The visual rotation point of the minecart is roughly 0.5 above its feet (determined visually ingame) //Search 0.5 Blocks below the feet for an inventory Inventory inv = HopperBlockEntity.getInventoryAt(this.world, this.getX() + offsetToInventory.x, this.getY() + 0.5 + offsetToInventory.y, this.getZ() + offsetToInventory.z); //There is probably a way nicer way to determine the access side of the target inventory if(inv instanceof BlockEntity){ BlockPos pos = ((BlockEntity) inv).getPos(); if(pos.getY() < MathHelper.floor(this.getY())) outputDirection = Direction.DOWN; else if(pos.getX() > MathHelper.floor(this.getX())) outputDirection = Direction.EAST; else if(pos.getX() < MathHelper.floor(this.getX())) outputDirection = Direction.WEST; else if(pos.getZ() > MathHelper.floor(this.getZ())) outputDirection = Direction.SOUTH; else if(pos.getZ() < MathHelper.floor(this.getZ())) outputDirection = Direction.NORTH; else outputDirection = Direction.DOWN; }else outputDirection = Direction.DOWN; return inv; } private Direction getLastOutputDirection() { return outputDirection; } //copied from HopperBlockEntity, (code originally taken from 1.14.4 pre 6) private boolean insert(){ if(!this.isInvEmpty()){ Inventory inventory_1 = this.getOutputInventory(); if (inventory_1 == null) { return false; } else { Direction direction_1 = getLastOutputDirection().getOpposite(); if (this.isInventoryFull(inventory_1, direction_1)) { return false; } else { for(int int_1 = 0; int_1 < this.getInvSize(); ++int_1) { if (!this.getInvStack(int_1).isEmpty()) { ItemStack itemStack_1 = this.getInvStack(int_1).copy(); ItemStack itemStack_2 = transfer(this, inventory_1, this.takeInvStack(int_1, 1), direction_1); if (itemStack_2.isEmpty()) { inventory_1.markDirty(); return true; } this.setInvStack(int_1, itemStack_1); } } return false; } } } return false; } //Copied from HopperBlockEntity as it is private there private boolean isInventoryFull(Inventory inventory_1, Direction direction_1) { return getAvailableSlots(inventory_1, direction_1).allMatch((int_1) -> { ItemStack itemStack_1 = inventory_1.getInvStack(int_1); return itemStack_1.getCount() >= itemStack_1.getMaxCount(); }); } //Copied from HopperBlockEntity as it is private there private static IntStream getAvailableSlots(Inventory inventory_1, Direction direction_1) { return inventory_1 instanceof SidedInventory ? IntStream.of(((SidedInventory)inventory_1).getInvAvailableSlots(direction_1)) : IntStream.range(0, inventory_1.getInvSize()); } }