mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Make turtle tools a little more flexible
Turtle tools now accept two additional JSON fields - allowEnchantments: Whether items with enchantments (or any non-standard NBT) can be equipped. - consumesDurability: Whether durability will be consumed. This can be "never" (the current and default behaviour), "always", and "when_enchanted". Closes #1501.
This commit is contained in:
		| @@ -0,0 +1,48 @@ | |||||||
|  | // SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers | ||||||
|  | // | ||||||
|  | // SPDX-License-Identifier: MPL-2.0 | ||||||
|  | 
 | ||||||
|  | package dan200.computercraft.api.turtle; | ||||||
|  | 
 | ||||||
|  | import net.minecraft.util.StringRepresentable; | ||||||
|  | import net.minecraft.world.entity.EquipmentSlot; | ||||||
|  | import net.minecraft.world.item.ItemStack; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Indicates if an equipped turtle item will consume durability. | ||||||
|  |  * | ||||||
|  |  * @see TurtleUpgradeDataProvider.ToolBuilder#consumesDurability(TurtleToolDurability) | ||||||
|  |  */ | ||||||
|  | public enum TurtleToolDurability implements StringRepresentable { | ||||||
|  |     /** | ||||||
|  |      * The equipped tool always consumes durability when using. | ||||||
|  |      */ | ||||||
|  |     ALWAYS("always"), | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The equipped tool consumes durability if it is {@linkplain ItemStack#isEnchanted() enchanted} or has | ||||||
|  |      * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}. | ||||||
|  |      */ | ||||||
|  |     WHEN_ENCHANTED("when_enchanted"), | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The equipped tool never consumes durability. Tools which have been damaged cannot be used as upgrades. | ||||||
|  |      */ | ||||||
|  |     NEVER("never"); | ||||||
|  | 
 | ||||||
|  |     private final String serialisedName; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * The codec which may be used for serialising/deserialising {@link TurtleToolDurability}s. | ||||||
|  |      */ | ||||||
|  |     public static final StringRepresentable.EnumCodec<TurtleToolDurability> CODEC = StringRepresentable.fromEnum(TurtleToolDurability::values); | ||||||
|  | 
 | ||||||
|  |     TurtleToolDurability(String serialisedName) { | ||||||
|  |         this.serialisedName = serialisedName; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String getSerializedName() { | ||||||
|  |         return serialisedName; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -13,8 +13,10 @@ import net.minecraft.data.DataGenerator; | |||||||
| import net.minecraft.data.PackOutput; | import net.minecraft.data.PackOutput; | ||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| import net.minecraft.tags.TagKey; | import net.minecraft.tags.TagKey; | ||||||
|  | import net.minecraft.world.entity.EquipmentSlot; | ||||||
| import net.minecraft.world.entity.ai.attributes.Attributes; | import net.minecraft.world.entity.ai.attributes.Attributes; | ||||||
| import net.minecraft.world.item.Item; | import net.minecraft.world.item.Item; | ||||||
|  | import net.minecraft.world.item.ItemStack; | ||||||
| import net.minecraft.world.level.block.Block; | import net.minecraft.world.level.block.Block; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
| @@ -61,6 +63,8 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur | |||||||
|         private @Nullable Item craftingItem; |         private @Nullable Item craftingItem; | ||||||
|         private @Nullable Float damageMultiplier = null; |         private @Nullable Float damageMultiplier = null; | ||||||
|         private @Nullable TagKey<Block> breakable; |         private @Nullable TagKey<Block> breakable; | ||||||
|  |         private boolean allowEnchantments = false; | ||||||
|  |         private TurtleToolDurability consumesDurability = TurtleToolDurability.NEVER; | ||||||
| 
 | 
 | ||||||
|         ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) { |         ToolBuilder(ResourceLocation id, TurtleUpgradeSerialiser<?> serialiser, Item toolItem) { | ||||||
|             this.id = id; |             this.id = id; | ||||||
| @@ -104,6 +108,28 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur | |||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         /** | ||||||
|  |          * Indicate that this upgrade allows items which have been {@linkplain ItemStack#isEnchanted() enchanted} or have | ||||||
|  |          * {@linkplain ItemStack#getAttributeModifiers(EquipmentSlot) custom attribute modifiers}. | ||||||
|  |          * | ||||||
|  |          * @return The tool builder, for further use. | ||||||
|  |          */ | ||||||
|  |         public ToolBuilder allowEnchantments() { | ||||||
|  |             allowEnchantments = true; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Set when the tool will consume durability. | ||||||
|  |          * | ||||||
|  |          * @param durability The durability predicate. | ||||||
|  |          * @return The tool builder, for further use. | ||||||
|  |          */ | ||||||
|  |         public ToolBuilder consumesDurability(TurtleToolDurability durability) { | ||||||
|  |             consumesDurability = durability; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         /** |         /** | ||||||
|          * Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks |          * Provide a list of breakable blocks. If not given, the tool can break all blocks. If given, only blocks | ||||||
|          * in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can |          * in this tag, those in {@link ComputerCraftTags.Blocks#TURTLE_ALWAYS_BREAKABLE} and "insta-mine" ones can | ||||||
| @@ -132,6 +158,10 @@ public abstract class TurtleUpgradeDataProvider extends UpgradeDataProvider<ITur | |||||||
|                 } |                 } | ||||||
|                 if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier); |                 if (damageMultiplier != null) s.addProperty("damageMultiplier", damageMultiplier); | ||||||
|                 if (breakable != null) s.addProperty("breakable", breakable.location().toString()); |                 if (breakable != null) s.addProperty("breakable", breakable.location().toString()); | ||||||
|  |                 if (allowEnchantments) s.addProperty("allowEnchantments", true); | ||||||
|  |                 if (consumesDurability != TurtleToolDurability.NEVER) { | ||||||
|  |                     s.addProperty("consumesDurability", consumesDurability.getSerializedName()); | ||||||
|  |                 } | ||||||
|             })); |             })); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ import dan200.computercraft.api.turtle.TurtleCommand; | |||||||
| import dan200.computercraft.api.turtle.TurtleSide; | import dan200.computercraft.api.turtle.TurtleSide; | ||||||
| import dan200.computercraft.api.upgrades.UpgradeData; | import dan200.computercraft.api.upgrades.UpgradeData; | ||||||
| import dan200.computercraft.core.computer.ComputerSide; | import dan200.computercraft.core.computer.ComputerSide; | ||||||
| import dan200.computercraft.core.util.Colour; |  | ||||||
| import dan200.computercraft.impl.TurtleUpgrades; | import dan200.computercraft.impl.TurtleUpgrades; | ||||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | import dan200.computercraft.shared.computer.core.ServerComputer; | ||||||
| @@ -35,7 +34,6 @@ import net.minecraft.tags.FluidTags; | |||||||
| import net.minecraft.world.Container; | import net.minecraft.world.Container; | ||||||
| import net.minecraft.world.entity.Entity; | import net.minecraft.world.entity.Entity; | ||||||
| import net.minecraft.world.entity.MoverType; | import net.minecraft.world.entity.MoverType; | ||||||
| import net.minecraft.world.item.DyeColor; |  | ||||||
| import net.minecraft.world.item.ItemStack; | import net.minecraft.world.item.ItemStack; | ||||||
| import net.minecraft.world.level.Level; | import net.minecraft.world.level.Level; | ||||||
| import net.minecraft.world.level.material.PushReaction; | import net.minecraft.world.level.material.PushReaction; | ||||||
| @@ -465,23 +463,6 @@ public class TurtleBrain implements TurtleAccessInternal { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public @Nullable DyeColor getDyeColour() { |  | ||||||
|         if (colourHex == -1) return null; |  | ||||||
|         var colour = Colour.fromHex(colourHex); |  | ||||||
|         return colour == null ? null : DyeColor.byId(15 - colour.ordinal()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public void setDyeColour(@Nullable DyeColor dyeColour) { |  | ||||||
|         var newColour = -1; |  | ||||||
|         if (dyeColour != null) { |  | ||||||
|             newColour = Colour.values()[15 - dyeColour.getId()].getHex(); |  | ||||||
|         } |  | ||||||
|         if (colourHex != newColour) { |  | ||||||
|             colourHex = newColour; |  | ||||||
|             BlockEntityHelpers.updateBlock(owner); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public void setColour(int colour) { |     public void setColour(int colour) { | ||||||
|         if (colour >= 0 && colour <= 0xFFFFFF) { |         if (colour >= 0 && colour <= 0xFFFFFF) { | ||||||
|   | |||||||
| @@ -74,19 +74,7 @@ public class TurtlePlaceCommand implements TurtleCommand { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static boolean deployCopiedItem( |     public static boolean deploy( | ||||||
|         ItemStack stack, ITurtleAccess turtle, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage |  | ||||||
|     ) { |  | ||||||
|         // Create a fake player, and orient it appropriately |  | ||||||
|         var playerPosition = turtle.getPosition().relative(direction); |  | ||||||
|         var turtlePlayer = TurtlePlayer.getWithPosition(turtle, playerPosition, direction); |  | ||||||
|         turtlePlayer.loadInventory(stack); |  | ||||||
|         var result = deploy(stack, turtle, turtlePlayer, direction, extraArguments, outErrorMessage); |  | ||||||
|         turtlePlayer.player().getInventory().clearContent(); |  | ||||||
|         return result; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static boolean deploy( |  | ||||||
|         ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, |         ItemStack stack, ITurtleAccess turtle, TurtlePlayer turtlePlayer, Direction direction, | ||||||
|         @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage |         @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage | ||||||
|     ) { |     ) { | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class UpgradeContainer implements Container { | |||||||
| 
 | 
 | ||||||
|     private ItemStack setUpgradeStack(int slot, @Nullable UpgradeData<ITurtleUpgrade> upgrade) { |     private ItemStack setUpgradeStack(int slot, @Nullable UpgradeData<ITurtleUpgrade> upgrade) { | ||||||
|         var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getUpgradeItem(); |         var stack = upgrade == null ? ItemStack.EMPTY : upgrade.getUpgradeItem(); | ||||||
|         lastUpgrade.set(slot, upgrade); |         lastUpgrade.set(slot, UpgradeData.copyOf(upgrade)); | ||||||
|         lastStack.set(slot, stack); |         lastStack.set(slot, stack); | ||||||
|         return stack; |         return stack; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.shared.turtle.upgrades; | |||||||
| 
 | 
 | ||||||
| import dan200.computercraft.api.ComputerCraftTags; | import dan200.computercraft.api.ComputerCraftTags; | ||||||
| import dan200.computercraft.api.turtle.*; | import dan200.computercraft.api.turtle.*; | ||||||
|  | import dan200.computercraft.core.util.Nullability; | ||||||
| import dan200.computercraft.shared.platform.PlatformHelper; | import dan200.computercraft.shared.platform.PlatformHelper; | ||||||
| import dan200.computercraft.shared.turtle.TurtleUtil; | import dan200.computercraft.shared.turtle.TurtleUtil; | ||||||
| import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; | import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand; | ||||||
| @@ -17,14 +18,19 @@ import net.minecraft.core.Direction; | |||||||
| import net.minecraft.nbt.CompoundTag; | import net.minecraft.nbt.CompoundTag; | ||||||
| import net.minecraft.resources.ResourceLocation; | import net.minecraft.resources.ResourceLocation; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
|  | import net.minecraft.server.level.ServerPlayer; | ||||||
| import net.minecraft.tags.TagKey; | import net.minecraft.tags.TagKey; | ||||||
|  | import net.minecraft.world.InteractionHand; | ||||||
| import net.minecraft.world.InteractionResult; | import net.minecraft.world.InteractionResult; | ||||||
| import net.minecraft.world.entity.Entity; | import net.minecraft.world.entity.Entity; | ||||||
|  | import net.minecraft.world.entity.LivingEntity; | ||||||
|  | import net.minecraft.world.entity.MobType; | ||||||
| import net.minecraft.world.entity.ai.attributes.Attributes; | import net.minecraft.world.entity.ai.attributes.Attributes; | ||||||
| import net.minecraft.world.entity.decoration.ArmorStand; | import net.minecraft.world.entity.decoration.ArmorStand; | ||||||
| import net.minecraft.world.entity.player.Player; | import net.minecraft.world.entity.player.Player; | ||||||
| import net.minecraft.world.item.Item; | import net.minecraft.world.item.Item; | ||||||
| import net.minecraft.world.item.ItemStack; | import net.minecraft.world.item.ItemStack; | ||||||
|  | import net.minecraft.world.item.enchantment.EnchantmentHelper; | ||||||
| import net.minecraft.world.level.BlockGetter; | import net.minecraft.world.level.BlockGetter; | ||||||
| import net.minecraft.world.level.Level; | import net.minecraft.world.level.Level; | ||||||
| import net.minecraft.world.level.block.Block; | import net.minecraft.world.level.block.Block; | ||||||
| @@ -33,6 +39,8 @@ import net.minecraft.world.level.block.state.BlockState; | |||||||
| import net.minecraft.world.phys.EntityHitResult; | import net.minecraft.world.phys.EntityHitResult; | ||||||
| 
 | 
 | ||||||
| import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||||
|  | import java.util.Objects; | ||||||
|  | import java.util.function.Function; | ||||||
| 
 | 
 | ||||||
| import static net.minecraft.nbt.Tag.TAG_COMPOUND; | import static net.minecraft.nbt.Tag.TAG_COMPOUND; | ||||||
| import static net.minecraft.nbt.Tag.TAG_LIST; | import static net.minecraft.nbt.Tag.TAG_LIST; | ||||||
| @@ -43,29 +51,37 @@ public class TurtleTool extends AbstractTurtleUpgrade { | |||||||
| 
 | 
 | ||||||
|     final ItemStack item; |     final ItemStack item; | ||||||
|     final float damageMulitiplier; |     final float damageMulitiplier; | ||||||
|     @Nullable |     final boolean allowsEnchantments; | ||||||
|     final TagKey<Block> breakable; |     final TurtleToolDurability consumesDurability; | ||||||
|  |     final @Nullable TagKey<Block> breakable; | ||||||
| 
 | 
 | ||||||
|     public TurtleTool(ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, @Nullable TagKey<Block> breakable) { |     public TurtleTool( | ||||||
|  |         ResourceLocation id, String adjective, Item craftItem, ItemStack toolItem, float damageMulitiplier, | ||||||
|  |         boolean allowsEnchantments, TurtleToolDurability consumesDurability, @Nullable TagKey<Block> breakable | ||||||
|  |     ) { | ||||||
|         super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem)); |         super(id, TurtleUpgradeType.TOOL, adjective, new ItemStack(craftItem)); | ||||||
|         item = toolItem; |         item = toolItem; | ||||||
|         this.damageMulitiplier = damageMulitiplier; |         this.damageMulitiplier = damageMulitiplier; | ||||||
|  |         this.allowsEnchantments = allowsEnchantments; | ||||||
|  |         this.consumesDurability = consumesDurability; | ||||||
|         this.breakable = breakable; |         this.breakable = breakable; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean isItemSuitable(ItemStack stack) { |     public boolean isItemSuitable(ItemStack stack) { | ||||||
|         var tag = stack.getTag(); |         if (consumesDurability == TurtleToolDurability.NEVER && stack.isDamaged()) return false; | ||||||
|         if (tag == null || tag.isEmpty()) return true; |         if (!allowsEnchantments && isEnchanted(stack)) return false; | ||||||
| 
 |         return true; | ||||||
|         // Check we've not got anything vaguely interesting on the item. We allow other mods to add their |  | ||||||
|         // own NBT, with the understanding such details will be lost to the mist of time. |  | ||||||
|         if (stack.isDamaged() || stack.isEnchanted()) return false; |  | ||||||
|         if (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) { |  | ||||||
|             return false; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         return true; |     private static boolean isEnchanted(ItemStack stack) { | ||||||
|  |         return !stack.isEmpty() && isEnchanted(stack.getTag()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static boolean isEnchanted(@Nullable CompoundTag tag) { | ||||||
|  |         if (tag == null || tag.isEmpty()) return false; | ||||||
|  |         return (tag.contains(ItemStack.TAG_ENCH, TAG_LIST) && !tag.getList(ItemStack.TAG_ENCH, TAG_COMPOUND).isEmpty()) | ||||||
|  |                || (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -83,11 +99,64 @@ public class TurtleTool extends AbstractTurtleUpgrade { | |||||||
|         return item; |         return item; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private ItemStack getToolStack(ITurtleAccess turtle, TurtleSide side) { | ||||||
|  |         var item = getCraftingItem(); | ||||||
|  |         var tag = turtle.getUpgradeNBTData(side); | ||||||
|  |         if (!tag.isEmpty()) item.setTag(tag); | ||||||
|  |         return item.copy(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void setToolStack(ITurtleAccess turtle, TurtleSide side, ItemStack stack) { | ||||||
|  |         var tag = turtle.getUpgradeNBTData(side); | ||||||
|  | 
 | ||||||
|  |         var useDurability = switch (consumesDurability) { | ||||||
|  |             case NEVER -> false; | ||||||
|  |             case WHEN_ENCHANTED -> isEnchanted(tag); | ||||||
|  |             case ALWAYS -> true; | ||||||
|  |         }; | ||||||
|  |         if (!useDurability) return; | ||||||
|  | 
 | ||||||
|  |         // If the tool has broken, remove the upgrade! | ||||||
|  |         if (stack.isEmpty()) { | ||||||
|  |             turtle.setUpgradeWithData(side, null); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // If the tool has changed, no clue what's going on. | ||||||
|  |         if (stack.getItem() != item.getItem()) return; | ||||||
|  | 
 | ||||||
|  |         var itemTag = stack.getTag(); | ||||||
|  | 
 | ||||||
|  |         // Early return if the item hasn't changed to avoid redundant syncs with the client. | ||||||
|  |         if ((itemTag == null && tag.isEmpty()) || Objects.equals(itemTag, tag)) return; | ||||||
|  | 
 | ||||||
|  |         if (itemTag == null) { | ||||||
|  |             tag.getAllKeys().clear(); | ||||||
|  |         } else { | ||||||
|  |             for (var key : itemTag.getAllKeys()) tag.put(key, Nullability.assertNonNull(itemTag.get(key))); | ||||||
|  |             tag.getAllKeys().removeIf(x -> !itemTag.contains(x)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         turtle.updateUpgradeNBTData(side); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private <T> T withEquippedItem(ITurtleAccess turtle, TurtleSide side, Direction direction, Function<TurtlePlayer, T> action) { | ||||||
|  |         var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtle.getPosition(), direction); | ||||||
|  |         turtlePlayer.loadInventory(getToolStack(turtle, side)); | ||||||
|  | 
 | ||||||
|  |         var result = action.apply(turtlePlayer); | ||||||
|  | 
 | ||||||
|  |         setToolStack(turtle, side, turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND)); | ||||||
|  |         turtlePlayer.player().getInventory().clearContent(); | ||||||
|  | 
 | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) { |     public TurtleCommandResult useTool(ITurtleAccess turtle, TurtleSide side, TurtleVerb verb, Direction direction) { | ||||||
|         return switch (verb) { |         return switch (verb) { | ||||||
|             case ATTACK -> attack(turtle, direction); |             case ATTACK -> attack(turtle, side, direction); | ||||||
|             case DIG -> dig(turtle, direction); |             case DIG -> dig(turtle, side, direction); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -102,16 +171,14 @@ public class TurtleTool extends AbstractTurtleUpgrade { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Attack an entity. This is a <em>very</em> cut down version of {@link Player#attack(Entity)}, which doesn't handle |      * Attack an entity. | ||||||
|      * enchantments, knockback, etc... Unfortunately we can't call attack directly as damage calculations are rather |  | ||||||
|      * different (and we don't want to play sounds/particles). |  | ||||||
|      * |      * | ||||||
|      * @param turtle    The current turtle. |      * @param turtle    The current turtle. | ||||||
|  |      * @param side      The side the tool is on. | ||||||
|      * @param direction The direction we're attacking in. |      * @param direction The direction we're attacking in. | ||||||
|      * @return Whether an attack occurred. |      * @return Whether an attack occurred. | ||||||
|      * @see Player#attack(Entity) |  | ||||||
|      */ |      */ | ||||||
|     private TurtleCommandResult attack(ITurtleAccess turtle, Direction direction) { |     private TurtleCommandResult attack(ITurtleAccess turtle, TurtleSide side, Direction direction) { | ||||||
|         // Create a fake player, and orient it appropriately |         // Create a fake player, and orient it appropriately | ||||||
|         var world = turtle.getLevel(); |         var world = turtle.getLevel(); | ||||||
|         var position = turtle.getPosition(); |         var position = turtle.getPosition(); | ||||||
| @@ -123,10 +190,11 @@ public class TurtleTool extends AbstractTurtleUpgrade { | |||||||
|         var turtlePos = player.position(); |         var turtlePos = player.position(); | ||||||
|         var rayDir = player.getViewVector(1.0f); |         var rayDir = player.getViewVector(1.0f); | ||||||
|         var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null); |         var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null); | ||||||
|  |         var attacked = false; | ||||||
|         if (hit instanceof EntityHitResult entityHit) { |         if (hit instanceof EntityHitResult entityHit) { | ||||||
|             // Load up the turtle's inventory |             // Load up the turtle's inventory | ||||||
|             var stackCopy = item.copy(); |             var stack = getToolStack(turtle, side); | ||||||
|             turtlePlayer.loadInventory(stackCopy); |             turtlePlayer.loadInventory(stack); | ||||||
| 
 | 
 | ||||||
|             var hitEntity = entityHit.getEntity(); |             var hitEntity = entityHit.getEntity(); | ||||||
| 
 | 
 | ||||||
| @@ -134,62 +202,120 @@ public class TurtleTool extends AbstractTurtleUpgrade { | |||||||
|             DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle)); |             DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle)); | ||||||
| 
 | 
 | ||||||
|             // Attack the entity |             // Attack the entity | ||||||
|             var attacked = false; |  | ||||||
|             var result = PlatformHelper.get().canAttackEntity(player, hitEntity); |             var result = PlatformHelper.get().canAttackEntity(player, hitEntity); | ||||||
|             if (result.consumesAction()) { |             if (result.consumesAction()) { | ||||||
|                 attacked = true; |                 attacked = true; | ||||||
|             } else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) { |             } else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) { | ||||||
|                 var damage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier; |                 attacked = attack(player, direction, hitEntity); | ||||||
|                 if (damage > 0.0f) { |  | ||||||
|                     var source = player.damageSources().playerAttack(player); |  | ||||||
|                     if (hitEntity instanceof ArmorStand) { |  | ||||||
|                         // Special case for armor stands: attack twice to guarantee destroy |  | ||||||
|                         hitEntity.hurt(source, damage); |  | ||||||
|                         if (hitEntity.isAlive()) hitEntity.hurt(source, damage); |  | ||||||
|                         attacked = true; |  | ||||||
|                     } else { |  | ||||||
|                         if (hitEntity.hurt(source, damage)) attacked = true; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Stop claiming drops |             // Stop claiming drops | ||||||
|             TurtleUtil.stopConsuming(turtle); |             TurtleUtil.stopConsuming(turtle); | ||||||
| 
 | 
 | ||||||
|             // Put everything we collected into the turtles inventory, then return |             // Put everything we collected into the turtles inventory. | ||||||
|  |             setToolStack(turtle, side, player.getItemInHand(InteractionHand.MAIN_HAND)); | ||||||
|             player.getInventory().clearContent(); |             player.getInventory().clearContent(); | ||||||
|             if (attacked) return TurtleCommandResult.success(); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return TurtleCommandResult.failure("Nothing to attack here"); |         return attacked ? TurtleCommandResult.success() : TurtleCommandResult.failure("Nothing to attack here"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private TurtleCommandResult dig(ITurtleAccess turtle, Direction direction) { |     /** | ||||||
|         if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) { |      * Attack an entity. This is a copy of {@link Player#attack(Entity)}, with some unwanted features removed (sweeping | ||||||
|             return TurtleCommandResult.success(); |      * edge). This is a little limited. | ||||||
|  |      * <p> | ||||||
|  |      * Ideally we'd use attack directly (if other mods mixin to that method, we won't support their features). | ||||||
|  |      * Unfortunately,that doesn't give us any feedback to whether the attack occurred or not (and we don't want to play | ||||||
|  |      * sounds/particles). | ||||||
|  |      * | ||||||
|  |      * @param player    The fake player doing the attacking. | ||||||
|  |      * @param direction The direction the turtle is attacking. | ||||||
|  |      * @param entity    The entity to attack. | ||||||
|  |      * @return Whether we attacked or not. | ||||||
|  |      * @see Player#attack(Entity) | ||||||
|  |      */ | ||||||
|  |     private boolean attack(ServerPlayer player, Direction direction, Entity entity) { | ||||||
|  |         var baseDamage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier; | ||||||
|  |         var bonusDamage = EnchantmentHelper.getDamageBonus( | ||||||
|  |             player.getItemInHand(InteractionHand.MAIN_HAND), entity instanceof LivingEntity target ? target.getMobType() : MobType.UNDEFINED | ||||||
|  |         ); | ||||||
|  |         var damage = baseDamage + bonusDamage; | ||||||
|  |         if (damage <= 0) return false; | ||||||
|  | 
 | ||||||
|  |         var knockBack = EnchantmentHelper.getKnockbackBonus(player); | ||||||
|  | 
 | ||||||
|  |         // We follow the logic in Player.attack of setting the entity on fire before attacking, so it's burning when it | ||||||
|  |         // (possibly) dies. | ||||||
|  |         var fireAspect = EnchantmentHelper.getFireAspect(player); | ||||||
|  |         var onFire = false; | ||||||
|  |         if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) { | ||||||
|  |             onFire = true; | ||||||
|  |             target.setSecondsOnFire(1); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         var source = player.damageSources().playerAttack(player); | ||||||
|  |         if (!entity.hurt(source, damage)) { | ||||||
|  |             // If we failed to damage the entity, undo us setting the entity on fire. | ||||||
|  |             if (onFire) entity.clearFire(); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Special case for armor stands: attack twice to guarantee destroy | ||||||
|  |         if (entity.isAlive() && entity instanceof ArmorStand) entity.hurt(source, damage); | ||||||
|  | 
 | ||||||
|  |         // Apply knockback | ||||||
|  |         if (knockBack > 0) { | ||||||
|  |             if (entity instanceof LivingEntity target) { | ||||||
|  |                 target.knockback(knockBack * 0.5, -direction.getStepX(), -direction.getStepZ()); | ||||||
|  |             } else { | ||||||
|  |                 entity.push(direction.getStepX() * knockBack * 0.5, 0.1, direction.getStepZ() * knockBack * 0.5); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Apply remaining enchantments | ||||||
|  |         if (entity instanceof LivingEntity target) EnchantmentHelper.doPostHurtEffects(target, player); | ||||||
|  |         EnchantmentHelper.doPostDamageEffects(player, entity); | ||||||
|  | 
 | ||||||
|  |         // Damage the original item stack. | ||||||
|  |         if (entity instanceof LivingEntity target) { | ||||||
|  |             player.getItemInHand(InteractionHand.MAIN_HAND).hurtEnemy(target, player); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Apply fire aspect | ||||||
|  |         if (entity instanceof LivingEntity target && fireAspect > 0 && !target.isOnFire()) { | ||||||
|  |             target.setSecondsOnFire(4 * fireAspect); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private TurtleCommandResult dig(ITurtleAccess turtle, TurtleSide side, Direction direction) { | ||||||
|         var level = (ServerLevel) turtle.getLevel(); |         var level = (ServerLevel) turtle.getLevel(); | ||||||
|         var turtlePosition = turtle.getPosition(); |  | ||||||
| 
 | 
 | ||||||
|         var blockPosition = turtlePosition.relative(direction); |         var blockPosition = turtle.getPosition().relative(direction); | ||||||
|         if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) { |         if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) { | ||||||
|             return TurtleCommandResult.failure("Nothing to dig here"); |             return TurtleCommandResult.failure("Nothing to dig here"); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction); |         return withEquippedItem(turtle, side, direction, turtlePlayer -> { | ||||||
|         turtlePlayer.loadInventory(item.copy()); |             var stack = turtlePlayer.player().getItemInHand(InteractionHand.MAIN_HAND); | ||||||
|  | 
 | ||||||
|  |             // Right-click the block when using a shovel/hoe. | ||||||
|  |             if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) { | ||||||
|  |                 return TurtleCommandResult.success(); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // Check if we can break the block |             // Check if we can break the block | ||||||
|             var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); |             var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); | ||||||
|             if (!breakable.isSuccess()) return breakable; |             if (!breakable.isSuccess()) return breakable; | ||||||
| 
 | 
 | ||||||
|  |             // And break it! | ||||||
|             DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); |             DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); | ||||||
|             var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); |             var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); | ||||||
|             TurtleUtil.stopConsuming(turtle); |             TurtleUtil.stopConsuming(turtle); | ||||||
| 
 | 
 | ||||||
|         // Check spawn protection |  | ||||||
|             return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block"); |             return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block"); | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { |     private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) { | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| package dan200.computercraft.shared.turtle.upgrades; | package dan200.computercraft.shared.turtle.upgrades; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
|  | import dan200.computercraft.api.turtle.TurtleToolDurability; | ||||||
| import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; | import dan200.computercraft.api.turtle.TurtleUpgradeSerialiser; | ||||||
| import dan200.computercraft.api.upgrades.UpgradeBase; | import dan200.computercraft.api.upgrades.UpgradeBase; | ||||||
| import dan200.computercraft.shared.platform.RegistryWrappers; | import dan200.computercraft.shared.platform.RegistryWrappers; | ||||||
| @@ -28,6 +29,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl | |||||||
|         var toolItem = GsonHelper.getAsItem(object, "item"); |         var toolItem = GsonHelper.getAsItem(object, "item"); | ||||||
|         var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem); |         var craftingItem = GsonHelper.getAsItem(object, "craftingItem", toolItem); | ||||||
|         var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f); |         var damageMultiplier = GsonHelper.getAsFloat(object, "damageMultiplier", 3.0f); | ||||||
|  |         var allowsEnchantments = GsonHelper.getAsBoolean(object, "allowsEnchantments", false); | ||||||
|  |         var consumesDurability = TurtleToolDurability.CODEC.byName(GsonHelper.getAsString(object, "consumesDurability", null), TurtleToolDurability.NEVER); | ||||||
| 
 | 
 | ||||||
|         TagKey<Block> breakable = null; |         TagKey<Block> breakable = null; | ||||||
|         if (object.has("breakable")) { |         if (object.has("breakable")) { | ||||||
| @@ -35,7 +38,7 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl | |||||||
|             breakable = TagKey.create(Registries.BLOCK, tag); |             breakable = TagKey.create(Registries.BLOCK, tag); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, breakable); |         return new TurtleTool(id, adjective, craftingItem, new ItemStack(toolItem), damageMultiplier, allowsEnchantments, consumesDurability, breakable); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -46,9 +49,11 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl | |||||||
|         // damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly |         // damageMultiplier and breakable aren't used by the client, but we need to construct the upgrade exactly | ||||||
|         // as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade! |         // as otherwise syncing on an SP world will overwrite the (shared) upgrade registry with an invalid upgrade! | ||||||
|         var damageMultiplier = buffer.readFloat(); |         var damageMultiplier = buffer.readFloat(); | ||||||
|  |         var allowsEnchantments = buffer.readBoolean(); | ||||||
|  |         var consumesDurability = buffer.readEnum(TurtleToolDurability.class); | ||||||
| 
 | 
 | ||||||
|         var breakable = buffer.readBoolean() ? TagKey.create(Registries.BLOCK, buffer.readResourceLocation()) : null; |         var breakable = buffer.readBoolean() ? TagKey.create(Registries.BLOCK, buffer.readResourceLocation()) : null; | ||||||
|         return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, breakable); |         return new TurtleTool(id, adjective, craftingItem, toolItem, damageMultiplier, allowsEnchantments, consumesDurability, breakable); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
| @@ -57,6 +62,8 @@ public final class TurtleToolSerialiser implements TurtleUpgradeSerialiser<Turtl | |||||||
|         RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem()); |         RegistryWrappers.writeId(buffer, RegistryWrappers.ITEMS, upgrade.getCraftingItem().getItem()); | ||||||
|         buffer.writeItem(upgrade.item); |         buffer.writeItem(upgrade.item); | ||||||
|         buffer.writeFloat(upgrade.damageMulitiplier); |         buffer.writeFloat(upgrade.damageMulitiplier); | ||||||
|  |         buffer.writeBoolean(upgrade.allowsEnchantments); | ||||||
|  |         buffer.writeEnum(upgrade.consumesDurability); | ||||||
|         buffer.writeBoolean(upgrade.breakable != null); |         buffer.writeBoolean(upgrade.breakable != null); | ||||||
|         if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location()); |         if (upgrade.breakable != null) buffer.writeResourceLocation(upgrade.breakable.location()); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.shared.platform; | |||||||
| 
 | 
 | ||||||
| import com.mojang.authlib.GameProfile; | import com.mojang.authlib.GameProfile; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
|  | import net.minecraft.server.level.ServerPlayer; | ||||||
| import net.minecraft.world.damagesource.DamageSource; | import net.minecraft.world.damagesource.DamageSource; | ||||||
| import net.minecraft.world.entity.EntityDimensions; | import net.minecraft.world.entity.EntityDimensions; | ||||||
| import net.minecraft.world.entity.Pose; | import net.minecraft.world.entity.Pose; | ||||||
| @@ -39,4 +40,9 @@ public final class FakePlayer extends net.fabricmc.fabric.api.entity.FakePlayer | |||||||
|     public double getBlockReach() { |     public double getBlockReach() { | ||||||
|         return MAX_REACH; |         return MAX_REACH; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean broadcastToPlayer(ServerPlayer player) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ package dan200.computercraft.shared.platform; | |||||||
| 
 | 
 | ||||||
| import com.mojang.authlib.GameProfile; | import com.mojang.authlib.GameProfile; | ||||||
| import net.minecraft.server.level.ServerLevel; | import net.minecraft.server.level.ServerLevel; | ||||||
|  | import net.minecraft.server.level.ServerPlayer; | ||||||
| import net.minecraft.world.MenuProvider; | import net.minecraft.world.MenuProvider; | ||||||
| import net.minecraft.world.entity.Entity; | import net.minecraft.world.entity.Entity; | ||||||
| import net.minecraft.world.entity.EntityDimensions; | import net.minecraft.world.entity.EntityDimensions; | ||||||
| @@ -57,4 +58,9 @@ class FakePlayerExt extends FakePlayer { | |||||||
|     public double getEntityReach() { |     public double getEntityReach() { | ||||||
|         return MAX_REACH; |         return MAX_REACH; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean broadcastToPlayer(ServerPlayer player) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates