diff --git a/src/main/java/gregtech/api/items/toolitem/MiningToolItem.java b/src/main/java/gregtech/api/items/toolitem/MiningToolItem.java new file mode 100644 index 0000000..7126b55 --- /dev/null +++ b/src/main/java/gregtech/api/items/toolitem/MiningToolItem.java @@ -0,0 +1,29 @@ +package gregtech.api.items.toolitem; + +import gregtech.api.unification.material.Material; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.enchantment.Enchantment; +import net.minecraft.enchantment.EnchantmentTarget; +import net.minecraft.tag.Tag; + +public class MiningToolItem extends ToolItem { + Tag effectiveBlocks; + + public MiningToolItem(ToolItemSettings settings, ToolItemType toolItemType, Material material, + Tag effectiveBlocks) { + super(settings, toolItemType, material); + this.effectiveBlocks = effectiveBlocks; + } + + @Override + public boolean canApplyEnchantment(Enchantment enchantment) { + return enchantment.type == EnchantmentTarget.DIGGER || + enchantment.type.isAcceptableItem(this); + } + + @Override + protected boolean isCorrectToolForBlock(BlockState state) { + return state.isIn(this.effectiveBlocks); + } +} diff --git a/src/main/java/gregtech/api/items/toolitem/ToolItem.java b/src/main/java/gregtech/api/items/toolitem/ToolItem.java index ca37705..bf51516 100644 --- a/src/main/java/gregtech/api/items/toolitem/ToolItem.java +++ b/src/main/java/gregtech/api/items/toolitem/ToolItem.java @@ -55,11 +55,11 @@ public abstract class ToolItem extends GTItem implements CustomDamageItem, Custo private final float attackDamageMultiplier; protected final int damagePerSpecialAction; - private final int damagePerBlockBreak; + protected final int damagePerBlockBreak; private final int damagePerEntityAttack; private final int damagePerCraft; - private final long energyPerDurabilityPoint; + protected final long energyPerDurabilityPoint; private final int itemDamageChance; private final String translationKey; @@ -100,6 +100,10 @@ public Material getMaterial() { return material; } + public int getDamagePerBlockBreak(){ + return damagePerBlockBreak; + } + @Override protected String getOrCreateTranslationKey() { return translationKey; @@ -175,8 +179,7 @@ public int getMiningLevel() { @Override public final boolean isSuitableFor(BlockState state) { - int miningLevel = getMiningLevel(); - return MiningLevelHelper.checkHarvestLevelRequirements(state, miningLevel) && + return MiningLevelHelper.checkHarvestLevelRequirements(state, getMiningLevel()) && isCorrectToolForBlock(state); } @@ -261,15 +264,10 @@ public boolean postMine(ItemStack stack, World world, BlockState state, BlockPos @Override public Pair attemptDamageItem(ItemStack itemStack, int damage, @Nullable LivingEntity entity, Simulation simulate) { - Ref stackRef = new Ref<>(itemStack.copy()); - LimitedConsumer excess = LimitedConsumer.rejecting(); Random random = new Random(); - if (entity instanceof PlayerEntity playerEntity) { - excess = LimitedConsumer.fromConsumer(PlayerInvUtil.createPlayerInsertable(playerEntity)); - } - - ElectricItem electricItem = GTAttributes.ELECTRIC_ITEM.getFirstOrNull(stackRef, excess); + Ref stackRef = new Ref<>(itemStack.copy()); + ElectricItem electricItem = getElectricItem(stackRef, entity); if (electricItem != null) { long energyToUse = energyPerDurabilityPoint * damage; @@ -299,4 +297,14 @@ public Pair attemptDamageItem(ItemStack itemStack, } return Pair.of(ItemDamageResult.DAMAGED, stackRef.obj); } + + @Nullable + public ElectricItem getElectricItem(Ref stackRef, @Nullable LivingEntity entity) { + LimitedConsumer excess = LimitedConsumer.rejecting(); + + if (entity instanceof PlayerEntity playerEntity) { + excess = LimitedConsumer.fromConsumer(PlayerInvUtil.createPlayerInsertable(playerEntity)); + } + return GTAttributes.ELECTRIC_ITEM.getFirstOrNull(stackRef, excess); + } } diff --git a/src/main/java/gregtech/api/util/TaskScheduler.java b/src/main/java/gregtech/api/util/TaskScheduler.java new file mode 100644 index 0000000..83e4993 --- /dev/null +++ b/src/main/java/gregtech/api/util/TaskScheduler.java @@ -0,0 +1,37 @@ +package gregtech.api.util; + +import gregtech.api.util.function.Task; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerWorldEvents; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.world.World; + +import java.util.*; + +public class TaskScheduler { + private static Map> tasksPerWorld = new HashMap<>(); + + public static void scheduleTask(World world, Task task) { + if(!world.isClient) { + throw new IllegalArgumentException("Attempt to schedule task on client world!"); + } + List taskList = tasksPerWorld.computeIfAbsent(world, k -> new ArrayList<>()); + taskList.add(task); + } + + static { + ServerWorldEvents.UNLOAD.register((MinecraftServer server, ServerWorld world)->{ + if(world.isClient()) { + tasksPerWorld.remove(world); + } + }); + + ServerTickEvents.START_WORLD_TICK.register((ServerWorld world) -> { + if(world.isClient()) { + List taskList = tasksPerWorld.getOrDefault(world, Collections.emptyList()); + taskList.removeIf(task -> !task.run()); + } + }); + } +} diff --git a/src/main/java/gregtech/api/util/function/Task.java b/src/main/java/gregtech/api/util/function/Task.java new file mode 100644 index 0000000..ebbc58d --- /dev/null +++ b/src/main/java/gregtech/api/util/function/Task.java @@ -0,0 +1,7 @@ +package gregtech.api.util.function; + +public interface Task { + + boolean run(); + +} diff --git a/src/main/java/gregtech/common/tools/AxeItem.java b/src/main/java/gregtech/common/tools/AxeItem.java index 55c957b..123afa1 100644 --- a/src/main/java/gregtech/common/tools/AxeItem.java +++ b/src/main/java/gregtech/common/tools/AxeItem.java @@ -2,21 +2,27 @@ import alexiil.mc.lib.attributes.Simulation; import gregtech.api.capability.item.CustomDamageItem; +import gregtech.api.items.toolitem.MiningToolItem; import gregtech.api.items.toolitem.ToolItemSettings; import gregtech.api.items.toolitem.ToolItemType; import gregtech.api.unification.material.Material; +import gregtech.api.util.TaskScheduler; import gregtech.mixin.accessor.AxeItemAccessor; import net.minecraft.block.Block; import net.minecraft.block.BlockState; import net.minecraft.block.Oxidizable; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.HoneycombItem; import net.minecraft.item.ItemStack; import net.minecraft.item.ItemUsageContext; import net.minecraft.item.Items; +import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; +import net.minecraft.tag.BlockTags; import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import net.minecraft.world.WorldEvents; @@ -27,7 +33,7 @@ public class AxeItem extends MiningToolItem { public AxeItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { - super(settings, toolItemType, material); + super(settings, toolItemType, material, BlockTags.AXE_MINEABLE); } private static Optional tryConvertBlockAndPlayEffects(ItemUsageContext context) { @@ -93,4 +99,26 @@ public ActionResult useOnBlock(ItemUsageContext context) { } return ActionResult.PASS; } + + public static boolean applyTimberAxe(ItemStack itemStack, World world, BlockState state, BlockPos blockPos, LivingEntity player) { + if(TreeChopTask.isLogBlock(state) == 1) { + if(!world.isClient()) { + ServerPlayerEntity playerMP = (ServerPlayerEntity) player; + if (playerMP.getItemCooldownManager().isCoolingDown(itemStack.getItem())) + return false; + TreeChopTask treeChopTask = new TreeChopTask(blockPos, world, playerMP, itemStack); + TaskScheduler.scheduleTask(world, treeChopTask); + } + return true; + } + return false; + } + + + @Override + public boolean postMine(ItemStack stack, World world, BlockState state, BlockPos pos, LivingEntity miner) { + return miner.isSneaking() ? + CustomDamageItem.damageItem(miner, Hand.MAIN_HAND, damagePerBlockBreak, Simulation.ACTION) : + applyTimberAxe(stack, world, state, pos, miner); + } } diff --git a/src/main/java/gregtech/common/tools/BranchCutterItem.java b/src/main/java/gregtech/common/tools/BranchCutterItem.java new file mode 100644 index 0000000..d0ce37d --- /dev/null +++ b/src/main/java/gregtech/common/tools/BranchCutterItem.java @@ -0,0 +1,14 @@ +package gregtech.common.tools; + +import gregtech.api.items.toolitem.MiningToolItem; +import gregtech.api.items.toolitem.ToolItemSettings; +import gregtech.api.items.toolitem.ToolItemType; +import gregtech.api.unification.material.Material; +import net.minecraft.tag.BlockTags; + +public class BranchCutterItem extends MiningToolItem { + // TODO: Do either 100% sapling drop without forestry, capitator-like leaves cutting or remove entirely + public BranchCutterItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { + super(settings, toolItemType, material, BlockTags.LEAVES); + } +} diff --git a/src/main/java/gregtech/common/tools/HoeItem.java b/src/main/java/gregtech/common/tools/HoeItem.java index e28bb0f..7cb315f 100644 --- a/src/main/java/gregtech/common/tools/HoeItem.java +++ b/src/main/java/gregtech/common/tools/HoeItem.java @@ -3,6 +3,7 @@ import alexiil.mc.lib.attributes.Simulation; import com.mojang.datafixers.util.Pair; import gregtech.api.capability.item.CustomDamageItem; +import gregtech.api.items.toolitem.MiningToolItem; import gregtech.api.items.toolitem.ToolItemSettings; import gregtech.api.items.toolitem.ToolItemType; import gregtech.api.unification.material.Material; @@ -13,6 +14,7 @@ import net.minecraft.item.Items; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; +import net.minecraft.tag.BlockTags; import net.minecraft.util.ActionResult; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; @@ -23,7 +25,7 @@ public class HoeItem extends MiningToolItem { public HoeItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { - super(settings, toolItemType, material); + super(settings, toolItemType, material, BlockTags.HOE_MINEABLE); } @Override diff --git a/src/main/java/gregtech/common/tools/PickaxeItem.java b/src/main/java/gregtech/common/tools/PickaxeItem.java new file mode 100644 index 0000000..0a4a85f --- /dev/null +++ b/src/main/java/gregtech/common/tools/PickaxeItem.java @@ -0,0 +1,13 @@ +package gregtech.common.tools; +import gregtech.api.items.toolitem.MiningToolItem; +import gregtech.api.items.toolitem.ToolItemSettings; +import gregtech.api.items.toolitem.ToolItemType; +import gregtech.api.unification.material.Material; +import net.minecraft.tag.BlockTags; + +public class PickaxeItem extends MiningToolItem { + + public PickaxeItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { + super(settings, toolItemType, material, BlockTags.PICKAXE_MINEABLE); + } +} diff --git a/src/main/java/gregtech/common/tools/ShovelItem.java b/src/main/java/gregtech/common/tools/ShovelItem.java index 8b39c3d..4938b0f 100644 --- a/src/main/java/gregtech/common/tools/ShovelItem.java +++ b/src/main/java/gregtech/common/tools/ShovelItem.java @@ -2,6 +2,7 @@ import alexiil.mc.lib.attributes.Simulation; import gregtech.api.capability.item.CustomDamageItem; +import gregtech.api.items.toolitem.MiningToolItem; import gregtech.api.items.toolitem.ToolItemSettings; import gregtech.api.items.toolitem.ToolItemType; import gregtech.api.unification.material.Material; @@ -13,6 +14,7 @@ import net.minecraft.item.ItemUsageContext; import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundEvents; +import net.minecraft.tag.BlockTags; import net.minecraft.util.ActionResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; @@ -25,7 +27,7 @@ public class ShovelItem extends MiningToolItem { public ShovelItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { - super(settings, toolItemType, material); + super(settings, toolItemType, material, BlockTags.SHOVEL_MINEABLE); } private static Optional tryConvertBlockAndPlayEffects(ItemUsageContext context) { diff --git a/src/main/java/gregtech/common/tools/MiningToolItem.java b/src/main/java/gregtech/common/tools/SwordItem.java similarity index 56% rename from src/main/java/gregtech/common/tools/MiningToolItem.java rename to src/main/java/gregtech/common/tools/SwordItem.java index aa9eb13..d0cc8a0 100644 --- a/src/main/java/gregtech/common/tools/MiningToolItem.java +++ b/src/main/java/gregtech/common/tools/SwordItem.java @@ -4,18 +4,24 @@ import gregtech.api.items.toolitem.ToolItemSettings; import gregtech.api.items.toolitem.ToolItemType; import gregtech.api.unification.material.Material; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.EnchantmentTarget; -public class MiningToolItem extends ToolItem { - - public MiningToolItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { +public class SwordItem extends ToolItem { + public SwordItem(ToolItemSettings settings, ToolItemType toolItemType, Material material) { super(settings, toolItemType, material); } @Override public boolean canApplyEnchantment(Enchantment enchantment) { - return enchantment.type == EnchantmentTarget.DIGGER || + return enchantment.type == EnchantmentTarget.WEAPON || enchantment.type.isAcceptableItem(this); } + + @Override + protected boolean isCorrectToolForBlock(BlockState state) { + return state.isOf(Blocks.COBWEB); + } } diff --git a/src/main/java/gregtech/common/tools/TreeChopTask.java b/src/main/java/gregtech/common/tools/TreeChopTask.java new file mode 100644 index 0000000..9ca5e59 --- /dev/null +++ b/src/main/java/gregtech/common/tools/TreeChopTask.java @@ -0,0 +1,208 @@ +package gregtech.common.tools; + +import alexiil.mc.lib.attributes.Simulation; +import gregtech.api.capability.item.CustomDamageItem; +import gregtech.api.items.toolitem.ToolItem; +import gregtech.api.unification.material.Material; +import gregtech.api.util.function.Task; +import net.minecraft.block.*; +import net.minecraft.item.ItemStack; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.tag.BlockTags; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.World; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.*; + +public class TreeChopTask implements Task { + + private static final int MAX_BLOCKS_SEARCH_PER_TICK = 1024; + private static final int MAX_BLOCKS_TO_SEARCH = 8192; + private final Stack> moveStack = new Stack<>(); + private boolean isLastBlockLeaves = false; + private final BlockPos.Mutable currentPos = new BlockPos.Mutable(); + private final BlockPos startBlockPos; + private final Set visitedBlockPos = new HashSet<>(); + private final List woodBlockPos = new ArrayList<>(); + + private boolean finishedSearchingBlocks = false; + private int currentWoodBlockIndex = 0; + private final World world; + private final ServerPlayerEntity player; + private final ItemStack itemStack; + + public TreeChopTask(BlockPos startPos, World world, ServerPlayerEntity player, ItemStack toolStack) { + this.startBlockPos = startPos.toImmutable(); + this.currentPos.set(startPos); + this.woodBlockPos.add(startPos.toImmutable()); + this.visitedBlockPos.add(startPos.toImmutable()); + this.world = world; + this.itemStack = toolStack.copy(); + this.player = player; + player.getItemCooldownManager().set(itemStack.getItem(), 20); + } + + @Override + public boolean run() { + boolean isPlayerDisconnected = player.isDisconnected(); + boolean isPlayerNear = player.world == world && currentPos.getSquaredDistance(player.getPos().x, currentPos.getY(), player.getPos().z, true) <= 1024; + ItemStack itemInMainHand = this.player.getMainHandStack(); + if (isPlayerDisconnected || !isPlayerNear || itemInMainHand.isEmpty() || !isItemEqual(itemInMainHand)) { + return false; + } + if (world.getTime() % 10 == 0) + player.getItemCooldownManager().set(itemStack.getItem(), 20); + + if(!finishedSearchingBlocks) { + this.finishedSearchingBlocks = !attemptSearchWoodBlocks() || + this.visitedBlockPos.size() >= MAX_BLOCKS_TO_SEARCH; + if(finishedSearchingBlocks) { + this.woodBlockPos.sort(new WoodBlockComparator()); + } + return true; + } + return tryBreakAny((ToolItem) itemStack.getItem()); + } + + private class WoodBlockComparator implements Comparator { + + @Override + public int compare(BlockPos o1, BlockPos o2) { + int a = -Integer.compare(o1.getY(), o2.getY()); + if(a != 0) { + return a; + } + return Integer.compare(distance(o1), distance(o2)); + } + + private int distance(BlockPos pos) { + int diffX = pos.getX() - startBlockPos.getX(); + int diffZ = pos.getZ() - startBlockPos.getZ(); + return diffX * diffX + diffZ * diffZ; + } + } + + private boolean isItemEqual(ItemStack heldItem) { + if (heldItem.getItem() != itemStack.getItem() || !(heldItem.getItem() instanceof ToolItem)) { + return false; + } + Material heldToolMaterial = ((ToolItem) heldItem.getItem()).getMaterial(); + Material toolMaterial = ((ToolItem) itemStack.getItem()).getMaterial(); + return toolMaterial == heldToolMaterial; + } + + private boolean tryBreakAny(ToolItem toolItem) { + if(woodBlockPos.size() > currentWoodBlockIndex) { + BlockPos woodPos = woodBlockPos.get(currentWoodBlockIndex++); + BlockState blockState = this.world.getBlockState(woodPos); + if(isLogBlock(blockState) == 1) { + if (CustomDamageItem.damageItem(this.player, Hand.MAIN_HAND, toolItem.getDamagePerBlockBreak(), Simulation.ACTION)){ + return this.world.breakBlock(woodPos, true); + } + } + } + return false; + } + + private boolean attemptSearchWoodBlocks() { + int blocksSearchedNow = 0; + int validWoodBlocksFound = 0; + main: while (blocksSearchedNow <= MAX_BLOCKS_SEARCH_PER_TICK) { + //try to iterate neighbour blocks + blocksSearchedNow++; + for (MultiFacing facing : MultiFacing.VALUES) { + //move at facing + facing.move(currentPos); + + if(!visitedBlockPos.contains(currentPos)) { + BlockState blockState = this.world.getBlockState(currentPos); + int blockType = isLogBlock(blockState); + boolean currentIsLeavesBlock = isLastBlockLeaves; + if (blockType == 1 || (blockType == 2 && !isLastBlockLeaves)) { + this.isLastBlockLeaves = blockType == 2; + BlockPos immutablePos = currentPos.toImmutable(); + this.visitedBlockPos.add(immutablePos); + if(blockType == 1) { + this.woodBlockPos.add(immutablePos); + } + validWoodBlocksFound++; + moveStack.add(Pair.of(facing.getOpposite(), currentIsLeavesBlock)); + continue main; + } + } + + //move back if it wasn't a tree block + facing.getOpposite().move(currentPos); + } + //we didn't found any matching block in neighbours - move back + if (!moveStack.isEmpty()) { + Pair prevData = moveStack.pop(); + prevData.getLeft().move(currentPos); + this.isLastBlockLeaves = prevData.getRight(); + } else break; + } + return validWoodBlocksFound > 0; + } + + public static int isLogBlock(BlockState blockState) { + if(blockState.isIn(BlockTags.LOGS)) { + return 1; + } else if(blockState.isIn(BlockTags.LEAVES)) { + return 2; + } + return 0; + } + + private enum MultiFacing { + UP(0, new Vec3i(0, 1, 0)), + DOWN(1, new Vec3i(0, -1, 0)), + SOUTH(2, new Vec3i(0, 0, 1)), + NORTH(3, new Vec3i(0, 0, -1)), + EAST(4, new Vec3i(1, 0, 0)), + WEST(5, new Vec3i(-1, 0, 0)), + + SOUTH_DOWN(10, new Vec3i(0, -1, 1)), + NORTH_DOWN(11, new Vec3i(0, -1, -1)), + EAST_DOWN(12, new Vec3i(1, -1, 0)), + WEST_DOWN(13, new Vec3i(-1, -1, 0)), + + SOUTH_UP(6, new Vec3i(0, 1, 1)), + NORTH_UP(7, new Vec3i(0, 1, -1)), + EAST_UP(8, new Vec3i(1, 1, 0)), + WEST_UP(9, new Vec3i(-1, 1, 0)), + + SOUTH_EAST_DOWN(18, new Vec3i(1, -1, 1)), + SOUTH_WEST_DOWN(19, new Vec3i(-1, -1, 1)), + NORTH_EAST_DOWN(20, new Vec3i(-1, -1, 1)), + NORTH_WEST_DOWN(21, new Vec3i(-1, -1, -1)), + + SOUTH_EAST_UP(14, new Vec3i(1, 1, 1)), + SOUTH_WEST_UP(15, new Vec3i(1, 1, -1)), + NORTH_EAST_UP(16, new Vec3i(1, 1, -1)), + NORTH_WEST_UP(17, new Vec3i(-1, 1, -1)); + + + private final int oppositeIndex; + private final Vec3i direction; + private static final MultiFacing[] VALUES = values(); + + MultiFacing(int oppositeIndex, Vec3i direction) { + this.oppositeIndex = oppositeIndex; + this.direction = direction; + } + + public void move(BlockPos.Mutable blockPos) { + blockPos.set(blockPos.getX() + direction.getX(), + blockPos.getY() + direction.getY(), + blockPos.getZ() + direction.getZ()); + } + + public MultiFacing getOpposite() { + return VALUES[oppositeIndex]; + } + } + +} \ No newline at end of file