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