mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-14 20:20:30 +00:00
Refactor turtle actions for easier multi-loader support
- TurtlePlayer now returns a fake player, rather than extending from Forge's implementation. - Remove "turtle obeys block protection" config option. - Put some of the more complex turtle actions behind the PlatformHelper.
This commit is contained in:
parent
4d50b48ea6
commit
8a7156785d
@ -1,22 +0,0 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
public final class TurtlePermissions {
|
||||
public static boolean isBlockEnterable(Level world, BlockPos pos, Player player) {
|
||||
var server = world.getServer();
|
||||
return server == null || world.isClientSide || (world instanceof ServerLevel serverLevel && !server.isUnderSpawnProtection(serverLevel, pos, player));
|
||||
}
|
||||
|
||||
public static boolean isBlockEditable(Level world, BlockPos pos, Player player) {
|
||||
return isBlockEnterable(world, pos, player);
|
||||
}
|
||||
}
|
@ -32,7 +32,6 @@ public final class Config {
|
||||
public static boolean turtlesNeedFuel = true;
|
||||
public static int turtleFuelLimit = 20000;
|
||||
public static int advancedTurtleFuelLimit = 100000;
|
||||
public static boolean turtlesObeyBlockProtection = true;
|
||||
public static boolean turtlesCanPush = true;
|
||||
|
||||
public static int computerTermWidth = 51;
|
||||
|
@ -63,7 +63,6 @@ public final class ConfigSpec {
|
||||
private static final ConfigValue<Boolean> turtlesNeedFuel;
|
||||
private static final ConfigValue<Integer> turtleFuelLimit;
|
||||
private static final ConfigValue<Integer> advancedTurtleFuelLimit;
|
||||
private static final ConfigValue<Boolean> turtlesObeyBlockProtection;
|
||||
private static final ConfigValue<Boolean> turtlesCanPush;
|
||||
|
||||
private static final ConfigValue<Integer> computerTermWidth;
|
||||
@ -283,12 +282,6 @@ public final class ConfigSpec {
|
||||
.comment("The fuel limit for Advanced Turtles.")
|
||||
.defineInRange("advanced_fuel_limit", Config.advancedTurtleFuelLimit, 0, Integer.MAX_VALUE);
|
||||
|
||||
turtlesObeyBlockProtection = builder
|
||||
.comment("""
|
||||
If set to true, Turtles will be unable to build, dig, or enter protected areas
|
||||
(such as near the server spawn point).""")
|
||||
.define("obey_block_protection", Config.turtlesObeyBlockProtection);
|
||||
|
||||
turtlesCanPush = builder
|
||||
.comment("""
|
||||
If set to true, Turtles will push entities out of the way instead of stopping if
|
||||
@ -400,7 +393,6 @@ public final class ConfigSpec {
|
||||
Config.turtlesNeedFuel = turtlesNeedFuel.get();
|
||||
Config.turtleFuelLimit = turtleFuelLimit.get();
|
||||
Config.advancedTurtleFuelLimit = advancedTurtleFuelLimit.get();
|
||||
Config.turtlesObeyBlockProtection = turtlesObeyBlockProtection.get();
|
||||
Config.turtlesCanPush = turtlesCanPush.get();
|
||||
|
||||
// Terminal size
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of ComputerCraft - http://www.computercraft.info
|
||||
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
||||
* Send enquiries to dratcliffe@gmail.com
|
||||
*/
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.entity.Pose;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.OptionalInt;
|
||||
|
||||
class FakePlayerExt extends FakePlayer {
|
||||
FakePlayerExt(ServerLevel serverLevel, GameProfile profile) {
|
||||
super(serverLevel, profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doTick() {
|
||||
super.doTick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canHarmPlayer(Player other) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OptionalInt openMenu(@Nullable MenuProvider menu) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startRiding(Entity vehicle, boolean force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getStandingEyeHeight(Pose pose, EntityDimensions dimensions) {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.network.NetworkMessage;
|
||||
@ -20,10 +21,11 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.level.ServerPlayerGameMode;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.WorldlyContainer;
|
||||
import net.minecraft.world.*;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
@ -39,6 +41,7 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
|
||||
@ -296,6 +299,15 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
*/
|
||||
boolean onNotifyNeighbour(Level level, BlockPos pos, BlockState block, Direction direction);
|
||||
|
||||
/**
|
||||
* Create a new fake player.
|
||||
*
|
||||
* @param level The level the player should be created in.
|
||||
* @param profile The user this player should mimic.
|
||||
* @return The newly constructed fake player.
|
||||
*/
|
||||
ServerPlayer createFakePlayer(ServerLevel level, GameProfile profile);
|
||||
|
||||
/**
|
||||
* Determine if a player is not a real player.
|
||||
*
|
||||
@ -316,4 +328,55 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
default double getReachDistance(Player player) {
|
||||
return player.isCreative() ? 5 : 4.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this item is a tool and has some secondary usage.
|
||||
* <p>
|
||||
* In practice, this only checks if a tool is a hoe or shovel. We don't want to include things like axes,
|
||||
*
|
||||
* @param stack The stack to check.
|
||||
* @return Whether this tool has a secondary usage.
|
||||
*/
|
||||
boolean hasToolUsage(ItemStack stack);
|
||||
|
||||
/**
|
||||
* Check if an entity can be attacked according to platform-specific events.
|
||||
*
|
||||
* @param player The player who is attacking.
|
||||
* @param entity The entity we're attacking.
|
||||
* @return If this entity can be attacked.
|
||||
* @see Player#attack(Entity)
|
||||
*/
|
||||
InteractionResult canAttackEntity(ServerPlayer player, Entity entity);
|
||||
|
||||
/**
|
||||
* Interact with an entity, for instance feeding cows.
|
||||
* <p>
|
||||
* Implementations should follow Minecraft behaviour - we try {@link Entity#interactAt(Player, Vec3, InteractionHand)}
|
||||
* and then {@link Player#interactOn(Entity, InteractionHand)}. Loader-specific hooks should also be called.
|
||||
*
|
||||
* @param player The player which is interacting with the entity.
|
||||
* @param entity The entity we're interacting with.
|
||||
* @param hitPos The position our ray trace hit the entity. This is a position in-world, unlike
|
||||
* {@link Entity#interactAt(Player, Vec3, InteractionHand)} which is relative to the entity.
|
||||
* @return Whether any interaction occurred.
|
||||
* @see Entity#interactAt(Player, Vec3, InteractionHand)
|
||||
* @see Player#interactOn(Entity, InteractionHand)
|
||||
* @see ServerGamePacketListenerImpl#handleInteract
|
||||
*/
|
||||
boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos);
|
||||
|
||||
/**
|
||||
* Place an item against a block.
|
||||
* <p>
|
||||
* Implementations should largely mirror {@link ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)}
|
||||
* (including any loader-specific modifications), except they should skip the call to {@link BlockState#use(Level, Player, InteractionHand, BlockHitResult)}.
|
||||
*
|
||||
* @param player The player which is placing this item.
|
||||
* @param stack The item to place.
|
||||
* @param hit The collision with the block we're placing against.
|
||||
* @return Whether any interaction occurred.
|
||||
* @see ServerPlayerGameMode#useItemOn(ServerPlayer, Level, ItemStack, InteractionHand, BlockHitResult)
|
||||
*/
|
||||
InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit);
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.network.wired.IWiredElement;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
@ -27,9 +28,8 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.WorldlyContainerHolder;
|
||||
import net.minecraft.world.*;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu;
|
||||
import net.minecraft.world.inventory.CraftingContainer;
|
||||
@ -37,6 +37,7 @@ import net.minecraft.world.inventory.MenuType;
|
||||
import net.minecraft.world.item.CreativeModeTab;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.item.crafting.Ingredient;
|
||||
import net.minecraft.world.item.crafting.Recipe;
|
||||
import net.minecraft.world.level.Level;
|
||||
@ -45,14 +46,17 @@ import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.Tags;
|
||||
import net.minecraftforge.common.ToolActions;
|
||||
import net.minecraftforge.common.capabilities.Capability;
|
||||
import net.minecraftforge.common.capabilities.ForgeCapabilities;
|
||||
import net.minecraftforge.common.extensions.IForgeMenuType;
|
||||
import net.minecraftforge.common.util.NonNullConsumer;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.items.IItemHandlerModifiable;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
@ -271,11 +275,52 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
return !ForgeEventFactory.onNeighborNotify(level, pos, block, EnumSet.of(direction), false).isCanceled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerPlayer createFakePlayer(ServerLevel world, GameProfile profile) {
|
||||
return new FakePlayerExt(world, profile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getReachDistance(Player player) {
|
||||
return player.getReachDistance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasToolUsage(ItemStack stack) {
|
||||
return stack.canPerformAction(ToolActions.SHOVEL_FLATTEN) || stack.canPerformAction(ToolActions.HOE_TILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult canAttackEntity(ServerPlayer player, Entity entity) {
|
||||
return ForgeHooks.onPlayerAttackTarget(player, entity) ? InteractionResult.PASS : InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean interactWithEntity(ServerPlayer player, Entity entity, Vec3 hitPos) {
|
||||
// Our behaviour is slightly different here - we call onInteractEntityAt before the interact methods, while
|
||||
// Forge does the call afterwards (on the server, not on the client).
|
||||
var interactAt = ForgeHooks.onInteractEntityAt(player, entity, hitPos, InteractionHand.MAIN_HAND);
|
||||
if (interactAt == null) {
|
||||
interactAt = entity.interactAt(player, hitPos.subtract(entity.position()), InteractionHand.MAIN_HAND);
|
||||
}
|
||||
|
||||
return interactAt.consumesAction() || player.interactOn(entity, InteractionHand.MAIN_HAND).consumesAction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InteractionResult useOn(ServerPlayer player, ItemStack stack, BlockHitResult hit) {
|
||||
var level = player.level;
|
||||
var pos = hit.getBlockPos();
|
||||
var event = ForgeHooks.onRightClickBlock(player, InteractionHand.MAIN_HAND, pos, hit);
|
||||
if (event.isCanceled()) return event.getCancellationResult();
|
||||
|
||||
var context = new UseOnContext(player, InteractionHand.MAIN_HAND, hit);
|
||||
if (event.getUseItem() == Event.Result.DENY) return InteractionResult.PASS;
|
||||
|
||||
var result = stack.onItemUseFirst(context);
|
||||
return result != InteractionResult.PASS ? result : stack.useOn(context);
|
||||
}
|
||||
|
||||
private record RegistryWrapperImpl<T>(
|
||||
ResourceLocation name, ForgeRegistry<T> registry
|
||||
) implements Registries.RegistryWrapper<T> {
|
||||
|
@ -9,12 +9,11 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleCommand;
|
||||
import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.shapes.Shapes;
|
||||
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||
@ -32,7 +31,7 @@ public class TurtleMoveCommand implements ITurtleCommand {
|
||||
var direction = this.direction.toWorldDir(turtle);
|
||||
|
||||
// Check if we can move
|
||||
var oldWorld = turtle.getLevel();
|
||||
var oldWorld = (ServerLevel) turtle.getLevel();
|
||||
var oldPosition = turtle.getPosition();
|
||||
var newPosition = oldPosition.relative(direction);
|
||||
|
||||
@ -97,14 +96,14 @@ public class TurtleMoveCommand implements ITurtleCommand {
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
|
||||
private static TurtleCommandResult canEnter(TurtlePlayer turtlePlayer, Level world, BlockPos position) {
|
||||
private static TurtleCommandResult canEnter(TurtlePlayer turtlePlayer, ServerLevel world, BlockPos position) {
|
||||
if (world.isOutsideBuildHeight(position)) {
|
||||
return TurtleCommandResult.failure(position.getY() < 0 ? "Too low to move" : "Too high to move");
|
||||
}
|
||||
if (!world.isInWorldBounds(position)) return TurtleCommandResult.failure("Cannot leave the world");
|
||||
|
||||
// Check spawn protection
|
||||
if (Config.turtlesObeyBlockProtection && !TurtlePermissions.isBlockEnterable(world, position, turtlePlayer)) {
|
||||
if (turtlePlayer.isBlockProtected(world, position)) {
|
||||
return TurtleCommandResult.failure("Cannot enter protected area");
|
||||
}
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
*/
|
||||
package dan200.computercraft.shared.turtle.core;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.ITurtleCommand;
|
||||
import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||
import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
@ -18,12 +18,12 @@ import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.game.ServerboundInteractPacket;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.item.*;
|
||||
import net.minecraft.world.item.BlockItem;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.SignItem;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
@ -33,8 +33,6 @@ import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.eventbus.api.Event.Result;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -76,13 +74,15 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean deployCopiedItem(ItemStack stack, ITurtleAccess turtle, Direction direction, @Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage) {
|
||||
public static boolean deployCopiedItem(
|
||||
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.getInventory().clearContent();
|
||||
turtlePlayer.player().getInventory().clearContent();
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
@Nullable Object[] extraArguments, @Nullable ErrorMessage outErrorMessage
|
||||
) {
|
||||
// Deploy on an entity
|
||||
if (deployOnEntity(stack, turtle, turtlePlayer)) return true;
|
||||
if (deployOnEntity(turtle, turtlePlayer)) return true;
|
||||
|
||||
var position = turtle.getPosition();
|
||||
var newPosition = position.relative(direction);
|
||||
@ -107,11 +107,11 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
|| deployOnBlock(stack, turtle, turtlePlayer, position, direction, extraArguments, false, outErrorMessage);
|
||||
}
|
||||
|
||||
private static boolean deployOnEntity(ItemStack stack, final ITurtleAccess turtle, TurtlePlayer turtlePlayer) {
|
||||
private static boolean deployOnEntity(ITurtleAccess turtle, TurtlePlayer turtlePlayer) {
|
||||
// See if there is an entity present
|
||||
final var world = turtle.getLevel();
|
||||
var turtlePos = turtlePlayer.position();
|
||||
var rayDir = turtlePlayer.getViewVector(1.0f);
|
||||
var world = turtle.getLevel();
|
||||
var turtlePos = turtlePlayer.player().position();
|
||||
var rayDir = turtlePlayer.player().getViewVector(1.0f);
|
||||
var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null);
|
||||
if (!(hit instanceof EntityHitResult entityHit)) return false;
|
||||
|
||||
@ -119,49 +119,17 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
var hitEntity = entityHit.getEntity();
|
||||
var hitPos = entityHit.getLocation();
|
||||
|
||||
DropConsumer.set(hitEntity, drop -> InventoryUtil.storeItemsFromOffset(turtlePlayer.getInventory(), drop, 1));
|
||||
|
||||
var placed = doDeployOnEntity(stack, turtlePlayer, hitEntity, hitPos);
|
||||
|
||||
DropConsumer.set(hitEntity, drop -> InventoryUtil.storeItemsFromOffset(turtlePlayer.player().getInventory(), drop, 1));
|
||||
var placed = PlatformHelper.get().interactWithEntity(turtlePlayer.player(), hitEntity, hitPos);
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
return placed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Place a block onto an entity. For instance, feeding cows.
|
||||
*
|
||||
* @param stack The stack we're placing.
|
||||
* @param turtlePlayer The player of the turtle we're placing.
|
||||
* @param hitEntity The entity we're interacting with.
|
||||
* @param hitPos The position our ray trace hit the entity.
|
||||
* @return If this item was deployed.
|
||||
* @see net.minecraft.server.network.ServerGamePacketListenerImpl#handleInteract(ServerboundInteractPacket)
|
||||
* @see net.minecraft.world.entity.player.Player#interactOn(Entity, InteractionHand)
|
||||
*/
|
||||
private static boolean doDeployOnEntity(ItemStack stack, TurtlePlayer turtlePlayer, Entity hitEntity, Vec3 hitPos) {
|
||||
// Placing "onto" a block follows two flows. First we try to interactAt. If that doesn't succeed, then we try to
|
||||
// call the normal interact path. Cancelling an interactAt *does not* cancel a normal interact path.
|
||||
|
||||
var interactAt = ForgeHooks.onInteractEntityAt(turtlePlayer, hitEntity, hitPos, InteractionHand.MAIN_HAND);
|
||||
if (interactAt == null) interactAt = hitEntity.interactAt(turtlePlayer, hitPos, InteractionHand.MAIN_HAND);
|
||||
if (interactAt.consumesAction()) return true;
|
||||
|
||||
var interact = ForgeHooks.onInteractEntity(turtlePlayer, hitEntity, InteractionHand.MAIN_HAND);
|
||||
if (interact != null) return interact.consumesAction();
|
||||
|
||||
if (hitEntity.interact(turtlePlayer, InteractionHand.MAIN_HAND).consumesAction()) return true;
|
||||
if (hitEntity instanceof LivingEntity hitLiving) {
|
||||
return stack.interactLivingEntity(turtlePlayer, hitLiving, InteractionHand.MAIN_HAND).consumesAction();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean canDeployOnBlock(
|
||||
BlockPlaceContext context, ITurtleAccess turtle, TurtlePlayer player, BlockPos position,
|
||||
Direction side, boolean allowReplaceable, @Nullable ErrorMessage outErrorMessage
|
||||
) {
|
||||
var world = turtle.getLevel();
|
||||
var world = (ServerLevel) turtle.getLevel();
|
||||
if (!world.isInWorldBounds(position) || world.isEmptyBlock(position) ||
|
||||
(context.getItemInHand().getItem() instanceof BlockItem && WorldUtil.isLiquidBlock(world, position))) {
|
||||
return false;
|
||||
@ -172,15 +140,13 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
var replaceable = state.canBeReplaced(context);
|
||||
if (!allowReplaceable && replaceable) return false;
|
||||
|
||||
if (Config.turtlesObeyBlockProtection) {
|
||||
// Check spawn protection
|
||||
var editable = replaceable
|
||||
? TurtlePermissions.isBlockEditable(world, position, player)
|
||||
: TurtlePermissions.isBlockEditable(world, position.relative(side), player);
|
||||
if (!editable) {
|
||||
if (outErrorMessage != null) outErrorMessage.message = "Cannot place in protected area";
|
||||
return false;
|
||||
}
|
||||
// Check spawn protection
|
||||
var isProtected = replaceable
|
||||
? player.isBlockProtected(world, position)
|
||||
: player.isBlockProtected(world, position.relative(side));
|
||||
if (isProtected) {
|
||||
if (outErrorMessage != null) outErrorMessage.message = "Cannot place in protected area";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -203,7 +169,7 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
|
||||
// Check if there's something suitable to place onto
|
||||
var hit = new BlockHitResult(new Vec3(hitX, hitY, hitZ), side, position, false);
|
||||
var context = new UseOnContext(turtlePlayer, InteractionHand.MAIN_HAND, hit);
|
||||
var context = new UseOnContext(turtlePlayer.player(), InteractionHand.MAIN_HAND, hit);
|
||||
if (!canDeployOnBlock(new BlockPlaceContext(context), turtle, turtlePlayer, position, side, allowReplace, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
@ -211,7 +177,7 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
var item = stack.getItem();
|
||||
var existingTile = turtle.getLevel().getBlockEntity(position);
|
||||
|
||||
var placed = doDeployOnBlock(stack, turtlePlayer, position, context, hit).consumesAction();
|
||||
var placed = doDeployOnBlock(stack, turtlePlayer, hit).consumesAction();
|
||||
|
||||
// Set text on signs
|
||||
if (placed && item instanceof SignItem && extraArguments != null && extraArguments.length >= 1 && extraArguments[0] instanceof String message) {
|
||||
@ -232,38 +198,19 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
*
|
||||
* @param stack The stack the player is using.
|
||||
* @param turtlePlayer The player which represents the turtle
|
||||
* @param position The block we're deploying against's position.
|
||||
* @param context The context of this place action.
|
||||
* @param hit Where the block we're placing against was clicked.
|
||||
* @return If this item was deployed.
|
||||
* @see net.minecraft.server.level.ServerPlayerGameMode#useItemOn For the original implementation.
|
||||
*/
|
||||
private static InteractionResult doDeployOnBlock(
|
||||
ItemStack stack, TurtlePlayer turtlePlayer, BlockPos position, UseOnContext context, BlockHitResult hit
|
||||
ItemStack stack, TurtlePlayer turtlePlayer, BlockHitResult hit
|
||||
) {
|
||||
var event = ForgeHooks.onRightClickBlock(turtlePlayer, InteractionHand.MAIN_HAND, position, hit);
|
||||
if (event.isCanceled()) return event.getCancellationResult();
|
||||
|
||||
if (event.getUseItem() != Result.DENY) {
|
||||
var result = stack.onItemUseFirst(context);
|
||||
if (result != InteractionResult.PASS) return result;
|
||||
}
|
||||
|
||||
if (event.getUseItem() != Result.DENY) {
|
||||
var result = stack.useOn(context);
|
||||
if (result != InteractionResult.PASS) return result;
|
||||
}
|
||||
var result = PlatformHelper.get().useOn(turtlePlayer.player(), stack, hit);
|
||||
if (result != InteractionResult.PASS) return result;
|
||||
|
||||
// We special case some items which we allow to place "normally". Yes, this is very ugly.
|
||||
var item = stack.getItem();
|
||||
if (item instanceof BucketItem || item instanceof BoatItem || item instanceof PlaceOnWaterBlockItem || item instanceof BottleItem) {
|
||||
var actionResult = ForgeHooks.onItemRightClick(turtlePlayer, InteractionHand.MAIN_HAND);
|
||||
if (actionResult != null && actionResult != InteractionResult.PASS) return actionResult;
|
||||
|
||||
var result = stack.use(context.getLevel(), turtlePlayer, InteractionHand.MAIN_HAND);
|
||||
if (result.getResult().consumesAction() && !ItemStack.matches(stack, result.getObject())) {
|
||||
turtlePlayer.setItemInHand(InteractionHand.MAIN_HAND, result.getObject());
|
||||
return result.getResult();
|
||||
}
|
||||
if (item.getUseDuration(stack) == 0) {
|
||||
return turtlePlayer.player().gameMode.useItem(turtlePlayer.player(), turtlePlayer.player().level, stack, InteractionHand.MAIN_HAND);
|
||||
}
|
||||
|
||||
return InteractionResult.PASS;
|
||||
@ -271,11 +218,11 @@ public class TurtlePlaceCommand implements ITurtleCommand {
|
||||
|
||||
private static void setSignText(Level world, BlockEntity tile, String message) {
|
||||
var signTile = (SignBlockEntity) tile;
|
||||
var split = message.split("\n");
|
||||
var firstLine = split.length <= 2 ? 1 : 0;
|
||||
var split = Splitter.on('\n').splitToList(message);
|
||||
var firstLine = split.size() <= 2 ? 1 : 0;
|
||||
for (var i = 0; i < 4; i++) {
|
||||
if (i >= firstLine && i < firstLine + split.length) {
|
||||
var line = split[i - firstLine];
|
||||
if (i >= firstLine && i < firstLine + split.size()) {
|
||||
var line = split.get(i - firstLine);
|
||||
signTile.setMessage(i, line.length() > 15
|
||||
? Component.literal(line.substring(0, 15))
|
||||
: Component.literal(line)
|
||||
|
@ -7,56 +7,40 @@ package dan200.computercraft.shared.turtle.core;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||
import dan200.computercraft.shared.util.DirectionUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.MenuProvider;
|
||||
import net.minecraft.world.effect.MobEffectInstance;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.EntityDimensions;
|
||||
import net.minecraft.world.entity.Pose;
|
||||
import net.minecraft.world.entity.animal.horse.AbstractHorse;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.block.entity.SignBlockEntity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class TurtlePlayer extends FakePlayer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(TurtlePlayer.class);
|
||||
public final class TurtlePlayer {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(TurtlePlayer.class);
|
||||
|
||||
private static final GameProfile DEFAULT_PROFILE = new GameProfile(
|
||||
UUID.fromString("0d0c4ca0-4ff1-11e4-916c-0800200c9a66"),
|
||||
"[ComputerCraft]"
|
||||
);
|
||||
|
||||
private TurtlePlayer(ServerLevel world, GameProfile name) {
|
||||
super(world, name);
|
||||
private final ServerPlayer player;
|
||||
|
||||
private TurtlePlayer(ServerPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private static TurtlePlayer create(ITurtleAccess turtle) {
|
||||
var world = (ServerLevel) turtle.getLevel();
|
||||
var profile = turtle.getOwningPlayer();
|
||||
|
||||
var player = new TurtlePlayer(world, getProfile(profile));
|
||||
var player = new TurtlePlayer(PlatformHelper.get().createFakePlayer(world, getProfile(profile)));
|
||||
player.setState(turtle);
|
||||
|
||||
if (profile != null && profile.getId() != null) {
|
||||
// Constructing a player overrides the "active player" variable in advancements. As fake players cannot
|
||||
// get advancements, this prevents a normal player who has placed a turtle from getting advancements.
|
||||
// We try to locate the "actual" player and restore them.
|
||||
var actualPlayer = world.getServer().getPlayerList().getPlayer(profile.getId());
|
||||
if (actualPlayer != null) player.getAdvancements().setPlayer(actualPlayer);
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
@ -65,11 +49,11 @@ public final class TurtlePlayer extends FakePlayer {
|
||||
}
|
||||
|
||||
public static TurtlePlayer get(ITurtleAccess access) {
|
||||
if (!(access instanceof TurtleBrain brain)) return create(access);
|
||||
if (!(access instanceof TurtleBrain brain)) throw new IllegalStateException("ITurtleAccess is not a brain");
|
||||
|
||||
var player = brain.cachedPlayer;
|
||||
if (player == null || player.getGameProfile() != getProfile(access.getOwningPlayer())
|
||||
|| player.getCommandSenderWorld() != access.getLevel()) {
|
||||
if (player == null || player.player.getGameProfile() != getProfile(access.getOwningPlayer())
|
||||
|| player.player.getCommandSenderWorld() != access.getLevel()) {
|
||||
player = brain.cachedPlayer = create(brain);
|
||||
} else {
|
||||
player.setState(access);
|
||||
@ -84,18 +68,26 @@ public final class TurtlePlayer extends FakePlayer {
|
||||
return turtlePlayer;
|
||||
}
|
||||
|
||||
public ServerPlayer player() {
|
||||
return player;
|
||||
}
|
||||
|
||||
private void setRotation(float y, float x) {
|
||||
player.setYRot(y);
|
||||
player.setXRot(x);
|
||||
}
|
||||
|
||||
private void setState(ITurtleAccess turtle) {
|
||||
if (containerMenu != inventoryMenu) {
|
||||
LOG.warn("Turtle has open container ({})", containerMenu);
|
||||
doCloseContainer();
|
||||
if (player.containerMenu != player.inventoryMenu) {
|
||||
LOGGER.warn("Turtle has open container ({})", player.containerMenu);
|
||||
player.doCloseContainer();
|
||||
}
|
||||
|
||||
var position = turtle.getPosition();
|
||||
setPosRaw(position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5);
|
||||
player.setPosRaw(position.getX() + 0.5, position.getY() + 0.5, position.getZ() + 0.5);
|
||||
setRotation(turtle.getDirection().toYRot(), 0);
|
||||
|
||||
setRot(turtle.getDirection().toYRot(), 0);
|
||||
|
||||
getInventory().clearContent();
|
||||
player.getInventory().clearContent();
|
||||
}
|
||||
|
||||
public void setPosition(ITurtleAccess turtle, BlockPos position, Direction direction) {
|
||||
@ -111,116 +103,62 @@ public final class TurtlePlayer extends FakePlayer {
|
||||
}
|
||||
|
||||
if (direction.getAxis() != Direction.Axis.Y) {
|
||||
setRot(direction.toYRot(), 0);
|
||||
setRotation(direction.toYRot(), 0);
|
||||
} else {
|
||||
setRot(turtle.getDirection().toYRot(), DirectionUtil.toPitchAngle(direction));
|
||||
setRotation(turtle.getDirection().toYRot(), DirectionUtil.toPitchAngle(direction));
|
||||
}
|
||||
|
||||
setPosRaw(posX, posY, posZ);
|
||||
xo = posX;
|
||||
yo = posY;
|
||||
zo = posZ;
|
||||
xRotO = getXRot();
|
||||
yRotO = getYRot();
|
||||
|
||||
yHeadRot = getYRot();
|
||||
yHeadRotO = yHeadRot;
|
||||
player.setPosRaw(posX, posY, posZ);
|
||||
player.xo = posX;
|
||||
player.yo = posY;
|
||||
player.zo = posZ;
|
||||
player.xRotO = player.getXRot();
|
||||
player.yHeadRotO = player.yHeadRot = player.yRotO = player.getYRot();
|
||||
}
|
||||
|
||||
public void loadInventory(ItemStack stack) {
|
||||
getInventory().clearContent();
|
||||
getInventory().selected = 0;
|
||||
getInventory().setItem(0, stack);
|
||||
player.getInventory().clearContent();
|
||||
player.getInventory().selected = 0;
|
||||
player.getInventory().setItem(0, stack);
|
||||
}
|
||||
|
||||
public void loadInventory(ITurtleAccess turtle) {
|
||||
getInventory().clearContent();
|
||||
|
||||
var inventory = player.getInventory();
|
||||
var turtleInventory = turtle.getInventory();
|
||||
var currentSlot = turtle.getSelectedSlot();
|
||||
var slots = turtle.getInventory().getContainerSize();
|
||||
var slots = turtleInventory.getContainerSize();
|
||||
|
||||
// Load up the fake inventory
|
||||
getInventory().selected = 0;
|
||||
inventory.selected = 0;
|
||||
for (var i = 0; i < slots; i++) {
|
||||
getInventory().setItem(i, turtle.getInventory().getItem((currentSlot + i) % slots));
|
||||
inventory.setItem(i, turtleInventory.getItem((currentSlot + i) % slots));
|
||||
}
|
||||
}
|
||||
|
||||
public void unloadInventory(ITurtleAccess turtle) {
|
||||
if (player.isUsingItem()) player.stopUsingItem();
|
||||
|
||||
var inventory = player.getInventory();
|
||||
var turtleInventory = turtle.getInventory();
|
||||
var currentSlot = turtle.getSelectedSlot();
|
||||
var slots = turtle.getInventory().getContainerSize();
|
||||
var slots = turtleInventory.getContainerSize();
|
||||
|
||||
// Load up the fake inventory
|
||||
getInventory().selected = 0;
|
||||
inventory.selected = 0;
|
||||
for (var i = 0; i < slots; i++) {
|
||||
turtle.getInventory().setItem((currentSlot + i) % slots, getInventory().getItem(i));
|
||||
turtleInventory.setItem((currentSlot + i) % slots, inventory.getItem(i));
|
||||
}
|
||||
|
||||
// Store (or drop) anything else we found
|
||||
var totalSize = getInventory().getContainerSize();
|
||||
var totalSize = inventory.getContainerSize();
|
||||
for (var i = slots; i < totalSize; i++) {
|
||||
TurtleUtil.storeItemOrDrop(turtle, getInventory().getItem(i));
|
||||
TurtleUtil.storeItemOrDrop(turtle, inventory.getItem(i));
|
||||
}
|
||||
|
||||
getInventory().setChanged();
|
||||
inventory.setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 position() {
|
||||
return new Vec3(getX(), getY(), getZ());
|
||||
public boolean isBlockProtected(ServerLevel level, BlockPos pos) {
|
||||
return level.getServer().isUnderSpawnProtection(level, pos, player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getEyeHeight(Pose pose) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getStandingEyeHeight(Pose pose, EntityDimensions size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
//region Code which depends on the connection
|
||||
@Override
|
||||
public OptionalInt openMenu(@Nullable MenuProvider prover) {
|
||||
return OptionalInt.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnterCombat() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeaveCombat() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startRiding(Entity entityIn, boolean force) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopRiding() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openTextEdit(SignBlockEntity signTile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openHorseInventory(AbstractHorse horse, Container inventory) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openItemGui(ItemStack stack, InteractionHand hand) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeContainer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEffectRemoved(MobEffectInstance effect) {
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ public class TurtleInventoryCrafting extends CraftingContainer {
|
||||
// Special case: craft(0) just returns an empty list if crafting was possible
|
||||
if (maxCount == 0) return Collections.emptyList();
|
||||
|
||||
var player = TurtlePlayer.get(turtle);
|
||||
var player = TurtlePlayer.get(turtle).player();
|
||||
|
||||
var results = new ArrayList<ItemStack>();
|
||||
for (var i = 0; i < maxCount && recipe.matches(this, world); i++) {
|
||||
|
@ -7,8 +7,7 @@ package dan200.computercraft.shared.turtle.upgrades;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftTags;
|
||||
import dan200.computercraft.api.turtle.*;
|
||||
import dan200.computercraft.shared.TurtlePermissions;
|
||||
import dan200.computercraft.shared.config.Config;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||
import dan200.computercraft.shared.turtle.core.TurtlePlaceCommand;
|
||||
import dan200.computercraft.shared.turtle.core.TurtlePlayer;
|
||||
@ -17,22 +16,22 @@ import dan200.computercraft.shared.util.WorldUtil;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.tags.TagKey;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.ai.attributes.Attributes;
|
||||
import net.minecraft.world.entity.decoration.ArmorStand;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.BlockGetter;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.GameMasterBlock;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.EntityHitResult;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.ToolActions;
|
||||
import net.minecraftforge.event.entity.player.AttackEntityEvent;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -63,8 +62,7 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
// 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() || stack.hasCustomHoverName()) return false;
|
||||
if (tag.contains("AttributeModifiers", TAG_LIST) &&
|
||||
!tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) {
|
||||
if (tag.contains("AttributeModifiers", TAG_LIST) && !tag.getList("AttributeModifiers", TAG_COMPOUND).isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -79,11 +77,9 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
};
|
||||
}
|
||||
|
||||
protected TurtleCommandResult checkBlockBreakable(BlockState state, Level world, BlockPos pos, TurtlePlayer player) {
|
||||
var block = state.getBlock();
|
||||
if (state.isAir() || block == Blocks.BEDROCK
|
||||
|| state.getDestroyProgress(player, world, pos) <= 0
|
||||
|| !block.canEntityDestroy(state, world, pos, player)) {
|
||||
protected TurtleCommandResult checkBlockBreakable(Level world, BlockPos pos, TurtlePlayer player) {
|
||||
var state = world.getBlockState(pos);
|
||||
if (state.isAir() || state.getBlock() instanceof GameMasterBlock || state.getDestroyProgress(player.player(), world, pos) <= 0) {
|
||||
return UNBREAKABLE;
|
||||
}
|
||||
|
||||
@ -91,6 +87,16 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
? TurtleCommandResult.success() : INEFFECTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attack an entity. This is a <em>very</em> cut down version of {@link Player#attack(Entity)}, which doesn't handle
|
||||
* 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 direction The direction we're attacking in.
|
||||
* @return Whether an attack occurred.
|
||||
* @see Player#attack(Entity)
|
||||
*/
|
||||
private TurtleCommandResult attack(ITurtleAccess turtle, Direction direction) {
|
||||
// Create a fake player, and orient it appropriately
|
||||
var world = turtle.getLevel();
|
||||
@ -99,8 +105,9 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
final var turtlePlayer = TurtlePlayer.getWithPosition(turtle, position, direction);
|
||||
|
||||
// See if there is an entity present
|
||||
var turtlePos = turtlePlayer.position();
|
||||
var rayDir = turtlePlayer.getViewVector(1.0f);
|
||||
var player = turtlePlayer.player();
|
||||
var turtlePos = player.position();
|
||||
var rayDir = player.getViewVector(1.0f);
|
||||
var hit = WorldUtil.clip(world, turtlePos, rayDir, 1.5, null);
|
||||
if (hit instanceof EntityHitResult entityHit) {
|
||||
// Load up the turtle's inventory
|
||||
@ -109,20 +116,18 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
|
||||
var hitEntity = entityHit.getEntity();
|
||||
|
||||
// Fire several events to ensure we have permissions.
|
||||
if (MinecraftForge.EVENT_BUS.post(new AttackEntityEvent(turtlePlayer, hitEntity)) || !hitEntity.isAttackable()) {
|
||||
return TurtleCommandResult.failure("Nothing to attack here");
|
||||
}
|
||||
|
||||
// Start claiming entity drops
|
||||
DropConsumer.set(hitEntity, TurtleUtil.dropConsumer(turtle));
|
||||
|
||||
// Attack the entity
|
||||
var attacked = false;
|
||||
if (!hitEntity.skipAttackInteraction(turtlePlayer)) {
|
||||
var damage = (float) turtlePlayer.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
||||
var result = PlatformHelper.get().canAttackEntity(player, hitEntity);
|
||||
if (result.consumesAction()) {
|
||||
attacked = true;
|
||||
} else if (result == InteractionResult.PASS && hitEntity.isAttackable() && !hitEntity.skipAttackInteraction(player)) {
|
||||
var damage = (float) player.getAttributeValue(Attributes.ATTACK_DAMAGE) * damageMulitiplier;
|
||||
if (damage > 0.0f) {
|
||||
var source = DamageSource.playerAttack(turtlePlayer);
|
||||
var source = DamageSource.playerAttack(player);
|
||||
if (hitEntity instanceof ArmorStand) {
|
||||
// Special case for armor stands: attack twice to guarantee destroy
|
||||
hitEntity.hurt(source, damage);
|
||||
@ -138,78 +143,42 @@ public class TurtleTool extends AbstractTurtleUpgrade {
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
|
||||
// Put everything we collected into the turtles inventory, then return
|
||||
if (attacked) {
|
||||
turtlePlayer.getInventory().clearContent();
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
player.getInventory().clearContent();
|
||||
if (attacked) return TurtleCommandResult.success();
|
||||
}
|
||||
|
||||
return TurtleCommandResult.failure("Nothing to attack here");
|
||||
}
|
||||
|
||||
private TurtleCommandResult dig(ITurtleAccess turtle, Direction direction) {
|
||||
if (item.canPerformAction(ToolActions.SHOVEL_FLATTEN) || item.canPerformAction(ToolActions.HOE_TILL)) {
|
||||
if (TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) {
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
if (PlatformHelper.get().hasToolUsage(item) && TurtlePlaceCommand.deployCopiedItem(item.copy(), turtle, direction, null, null)) {
|
||||
return TurtleCommandResult.success();
|
||||
}
|
||||
|
||||
// Get ready to dig
|
||||
var world = turtle.getLevel();
|
||||
var level = (ServerLevel) turtle.getLevel();
|
||||
var turtlePosition = turtle.getPosition();
|
||||
|
||||
var blockPosition = turtlePosition.relative(direction);
|
||||
if (world.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(world, blockPosition)) {
|
||||
if (level.isEmptyBlock(blockPosition) || WorldUtil.isLiquidBlock(level, blockPosition)) {
|
||||
return TurtleCommandResult.failure("Nothing to dig here");
|
||||
}
|
||||
|
||||
var state = world.getBlockState(blockPosition);
|
||||
var fluidState = world.getFluidState(blockPosition);
|
||||
|
||||
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, turtlePosition, direction);
|
||||
turtlePlayer.loadInventory(item.copy());
|
||||
|
||||
if (Config.turtlesObeyBlockProtection) {
|
||||
// Check spawn protection
|
||||
if (MinecraftForge.EVENT_BUS.post(new BlockEvent.BreakEvent(world, blockPosition, state, turtlePlayer))) {
|
||||
return TurtleCommandResult.failure("Cannot break protected block");
|
||||
}
|
||||
|
||||
if (!TurtlePermissions.isBlockEditable(world, blockPosition, turtlePlayer)) {
|
||||
return TurtleCommandResult.failure("Cannot break protected block");
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we can break the block
|
||||
var breakable = checkBlockBreakable(state, world, blockPosition, turtlePlayer);
|
||||
var breakable = checkBlockBreakable(level, blockPosition, turtlePlayer);
|
||||
if (!breakable.isSuccess()) return breakable;
|
||||
|
||||
// Consume the items the block drops
|
||||
DropConsumer.set(world, blockPosition, TurtleUtil.dropConsumer(turtle));
|
||||
|
||||
var tile = world.getBlockEntity(blockPosition);
|
||||
|
||||
// Much of this logic comes from PlayerInteractionManager#tryHarvestBlock, so it's a good idea
|
||||
// to consult there before making any changes.
|
||||
|
||||
// Play the destruction sound and particles
|
||||
world.levelEvent(2001, blockPosition, Block.getId(state));
|
||||
|
||||
// Destroy the block
|
||||
var canHarvest = state.canHarvestBlock(world, blockPosition, turtlePlayer);
|
||||
var canBreak = state.onDestroyedByPlayer(world, blockPosition, turtlePlayer, canHarvest, fluidState);
|
||||
if (canBreak) state.getBlock().destroy(world, blockPosition, state);
|
||||
if (canHarvest && canBreak) {
|
||||
state.getBlock().playerDestroy(world, turtlePlayer, blockPosition, state, tile, turtlePlayer.getMainHandItem());
|
||||
}
|
||||
|
||||
DropConsumer.set(level, blockPosition, TurtleUtil.dropConsumer(turtle));
|
||||
var broken = !turtlePlayer.isBlockProtected(level, blockPosition) && turtlePlayer.player().gameMode.destroyBlock(blockPosition);
|
||||
TurtleUtil.stopConsuming(turtle);
|
||||
|
||||
return TurtleCommandResult.success();
|
||||
|
||||
// Check spawn protection
|
||||
return broken ? TurtleCommandResult.success() : TurtleCommandResult.failure("Cannot break protected block");
|
||||
}
|
||||
|
||||
protected boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
||||
private static boolean isTriviallyBreakable(BlockGetter reader, BlockPos pos, BlockState state) {
|
||||
return state.is(ComputerCraftTags.Blocks.TURTLE_ALWAYS_BREAKABLE)
|
||||
// Allow breaking any "instabreak" block.
|
||||
|| state.getDestroySpeed(reader, pos) == 0;
|
||||
|
@ -10,6 +10,9 @@ import dan200.computercraft.api.detail.VanillaDetailRegistries
|
||||
import dan200.computercraft.api.lua.ObjectArguments
|
||||
import dan200.computercraft.core.apis.PeripheralAPI
|
||||
import dan200.computercraft.gametest.api.*
|
||||
import dan200.computercraft.gametest.core.TestHooks
|
||||
import dan200.computercraft.mixin.gametest.GameTestHelperAccessor
|
||||
import dan200.computercraft.mixin.gametest.GameTestInfoAccessor
|
||||
import dan200.computercraft.shared.ModRegistry
|
||||
import dan200.computercraft.shared.media.items.ItemPrintout
|
||||
import dan200.computercraft.shared.peripheral.monitor.BlockMonitor
|
||||
@ -396,10 +399,13 @@ class Turtle_Test {
|
||||
*/
|
||||
@GameTest
|
||||
fun Peripheral_change(helper: GameTestHelper) = helper.sequence {
|
||||
val testInfo = (helper as GameTestHelperAccessor).testInfo as GameTestInfoAccessor
|
||||
|
||||
val events = mutableListOf<Pair<String, String>>()
|
||||
thenStartComputer("listen") {
|
||||
while (true) {
|
||||
val event = pullEvent()
|
||||
TestHooks.LOG.info("[{}] Got event {} at tick {}", testInfo, event[0], testInfo.`computercraft$getTick`())
|
||||
if (event[0] == "peripheral" || event[0] == "peripheral_detach") {
|
||||
events.add((event[0] as String) to (event[1] as String))
|
||||
}
|
||||
@ -408,8 +414,9 @@ class Turtle_Test {
|
||||
thenOnComputer("turtle") {
|
||||
turtle.forward().await().assertArrayEquals(true, message = "Moved turtle forward")
|
||||
turtle.back().await().assertArrayEquals(true, message = "Moved turtle forward")
|
||||
TestHooks.LOG.info("[{}] Finished turtle at {}", testInfo, testInfo.`computercraft$getTick`())
|
||||
}
|
||||
thenIdle(2) // Should happen immediately, but computers might be slow.
|
||||
thenIdle(3) // Should happen immediately, but computers might be slow.
|
||||
thenExecute {
|
||||
assertEquals(
|
||||
listOf(
|
||||
|
Loading…
Reference in New Issue
Block a user