1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-29 21:02: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:
Jonathan Coates
2023-07-03 22:10:11 +01:00
parent 943a9406b1
commit a91ac6f214
9 changed files with 281 additions and 89 deletions

View File

@@ -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;
}
}

View File

@@ -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());
}
})); }));
} }
} }

View File

@@ -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) {

View File

@@ -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
) { ) {

View File

@@ -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;
} }

View File

@@ -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,31 +51,39 @@ 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;
// 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; 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
public CompoundTag getUpgradeData(ItemStack stack) { public CompoundTag getUpgradeData(ItemStack stack) {
// Just use the current item's tag. // Just use the current item's tag.
@@ -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 level = (ServerLevel) turtle.getLevel(); var source = player.damageSources().playerAttack(player);
var turtlePosition = turtle.getPosition(); 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;
}
var blockPosition = turtlePosition.relative(direction); // 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 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);
// Check if we can break the block // Right-click the block when using a shovel/hoe.
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer); if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deploy(stack, turtle, turtlePlayer, direction, null, null)) {
if (!breakable.isSuccess()) return breakable; return TurtleCommandResult.success();
}
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle)); // Check if we can break the block
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition); var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
TurtleUtil.stopConsuming(turtle); if (!breakable.isSuccess()) return breakable;
// Check spawn protection // And break it!
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block"); DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
TurtleUtil.stopConsuming(turtle);
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) {

View File

@@ -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());
} }

View File

@@ -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;
}
} }

View File

@@ -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;
}
} }