1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-04-09 20:26:42 +00:00

Merge branch 'mc-1.20.x' into mc-1.21.x

This commit is contained in:
Jonathan Coates 2025-01-31 21:41:10 +00:00
commit 9bb62b047a
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
31 changed files with 285 additions and 304 deletions

View File

@ -30,3 +30,5 @@ body:
Description of the bug. Please include the following:
- Logs: These will be located in the `logs/` directory of your Minecraft instance. This is always useful, even if it doesn't include errors, so please upload this!
- Detailed reproduction steps: sometimes I can spot a bug pretty easily, but often it's much more obscure. The more information I have to help reproduce it, the quicker it'll get fixed.
![A gif of burning text reading "Upload your logs!!!"](https://tweaked.cc/images/logs.gif)

View File

@ -10,8 +10,9 @@ kotlin.jvm.target.validation.mode=error
neogradle.subsystems.conventions.runs.enabled=false
# Mod properties
isUnstable=true
modVersion=1.114.3
modVersion=1.114.4
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.21.1

View File

@ -31,6 +31,7 @@ import dan200.computercraft.shared.computer.blocks.CommandComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlock;
import dan200.computercraft.shared.computer.blocks.ComputerBlockEntity;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.AbstractComputerItem;
import dan200.computercraft.shared.computer.items.CommandComputerItem;
@ -90,7 +91,6 @@ import dan200.computercraft.shared.turtle.upgrades.TurtleCraftingTable;
import dan200.computercraft.shared.turtle.upgrades.TurtleModem;
import dan200.computercraft.shared.turtle.upgrades.TurtleSpeaker;
import dan200.computercraft.shared.turtle.upgrades.TurtleTool;
import dan200.computercraft.shared.util.ComponentMap;
import dan200.computercraft.shared.util.DataComponentUtil;
import dan200.computercraft.shared.util.NonNegativeId;
import dan200.computercraft.shared.util.StorageCapacity;
@ -615,7 +615,7 @@ public final class ModRegistry {
ComputerCraftAPI.registerAPIFactory(computer -> {
var turtle = computer.getComponent(ComputerComponents.TURTLE);
var metrics = Objects.requireNonNull(computer.getComponent(ComponentMap.METRICS));
var metrics = Objects.requireNonNull(computer.getComponent(ServerComputer.METRICS));
return turtle == null ? null : new TurtleAPI(metrics, (TurtleAccessInternal) turtle);
});

View File

@ -24,15 +24,13 @@ import dan200.computercraft.shared.computer.metrics.basic.AggregatedMetric;
import dan200.computercraft.shared.computer.metrics.basic.BasicComputerMetricsObserver;
import dan200.computercraft.shared.computer.metrics.basic.ComputerMetrics;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.RelativeMovement;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
@ -259,18 +257,11 @@ public final class CommandComputerCraft {
* @return The constant {@code 1}.
*/
private static int view(CommandSourceStack source, ServerComputer computer) throws CommandSyntaxException {
var player = source.getPlayerOrException();
new ComputerContainerData(computer, new ItemStack(ModRegistry.Items.COMPUTER_NORMAL.get())).open(player, new MenuProvider() {
@Override
public Component getDisplayName() {
return Component.translatable("gui.computercraft.view_computer");
}
@Override
public AbstractContainerMenu createMenu(int id, Inventory player, Player entity) {
return new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, player, p -> true, computer);
}
});
PlatformHelper.get().openMenu(
source.getPlayerOrException(), Component.translatable("gui.computercraft.view_computer"),
(id, player, entity) -> new ComputerMenuWithoutInventory(ModRegistry.Menus.COMPUTER.get(), id, player, p -> true, computer),
new ComputerContainerData(computer, new ItemStack(ModRegistry.Items.COMPUTER_NORMAL.get()))
);
return 1;
}

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared.computer.blocks;
import dan200.computercraft.annotations.ForgeOverride;
import dan200.computercraft.shared.common.IBundledRedstoneBlock;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.platform.RegistryEntry;
import dan200.computercraft.shared.util.BlockEntityHelpers;
import net.minecraft.core.BlockPos;
@ -121,7 +122,7 @@ public abstract class AbstractComputerBlock<T extends AbstractComputerBlockEntit
var serverComputer = computer.createServerComputer();
serverComputer.turnOn();
new ComputerContainerData(serverComputer, getItem(computer)).open(player, computer);
PlatformHelper.get().openMenu(player, computer.getName(), computer, new ComputerContainerData(serverComputer, getItem(computer)));
}
return InteractionResult.sidedSuccess(level.isClientSide);
}

View File

@ -28,9 +28,10 @@ import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.Container;
import net.minecraft.world.LockCode;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.Nameable;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.level.block.GameMasterBlock;
import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
@ -40,7 +41,7 @@ import javax.annotation.Nullable;
import java.util.Objects;
import java.util.UUID;
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuProvider {
public abstract class AbstractComputerBlockEntity extends BlockEntity implements Nameable, MenuConstructor {
private static final String NBT_ID = "ComputerId";
private static final String NBT_LABEL = "Label";
private static final String NBT_ON = "On";
@ -344,6 +345,10 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
return label;
}
public final boolean isAdminOnly() {
return getBlockState().getBlock() instanceof GameMasterBlock;
}
public final void setComputerID(int id) {
if (getLevel().isClientSide || computerID == id) return;
@ -454,4 +459,9 @@ public abstract class AbstractComputerBlockEntity extends BlockEntity implements
public Component getDisplayName() {
return Nameable.super.getDisplayName();
}
@Override
public boolean onlyOpCanSetNbt() {
return isAdminOnly();
}
}

View File

@ -11,7 +11,6 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.ComputerAccess;
import dan200.computercraft.core.apis.IAPIEnvironment;
import dan200.computercraft.core.computer.ApiLifecycle;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
@ -26,11 +25,11 @@ import java.util.Map;
final class ComputerSystem extends ComputerAccess implements IComputerSystem, ApiLifecycle {
private final ServerComputer computer;
private final IAPIEnvironment environment;
private final ComponentMap components;
private final Map<ComputerComponent<?>, Object> components;
private boolean active;
ComputerSystem(ServerComputer computer, IAPIEnvironment environment, ComponentMap components) {
ComputerSystem(ServerComputer computer, IAPIEnvironment environment, Map<ComputerComponent<?>, Object> components) {
super(environment);
this.computer = computer;
this.environment = environment;
@ -95,7 +94,8 @@ final class ComputerSystem extends ComputerAccess implements IComputerSystem, Ap
}
@Override
@SuppressWarnings("unchecked")
public <T> @Nullable T getComponent(ComputerComponent<T> component) {
return components.get(component);
return (T) components.get(component);
}
}

View File

@ -26,18 +26,21 @@ import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.ClientNetworkContext;
import dan200.computercraft.shared.network.client.ComputerTerminalClientMessage;
import dan200.computercraft.shared.network.server.ServerNetworking;
import dan200.computercraft.shared.util.ComponentMap;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
public class ServerComputer implements ComputerEnvironment, ComputerEvents.Receiver {
public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
private final UUID instanceUUID = UUID.randomUUID();
private ServerLevel level;
@ -65,14 +68,12 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
storageCapacity = properties.storageCapacity;
var componentBuilder = ComponentMap.builder();
componentBuilder.add(ComponentMap.METRICS, metrics);
properties.addComponent(METRICS, metrics);
if (family == ComputerFamily.COMMAND) {
componentBuilder.add(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() {
properties.addComponent(ComputerComponents.ADMIN_COMPUTER, new AdminComputer() {
});
}
componentBuilder.add(properties.components.build());
var components = componentBuilder.build();
var components = Map.copyOf(properties.components);
computer = new Computer(context.computerContext(), this, terminal, properties.computerID);
computer.setLabel(properties.label);
@ -282,7 +283,7 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
private int terminalWidth = Config.DEFAULT_COMPUTER_TERM_WIDTH;
private int terminalHeight = Config.DEFAULT_COMPUTER_TERM_HEIGHT;
private long storageCapacity = -1;
private final ComponentMap.Builder components = ComponentMap.builder();
private final Map<ComputerComponent<?>, Object> components = new HashMap<>();
private Properties(int computerID, ComputerFamily family) {
this.computerID = computerID;
@ -313,7 +314,8 @@ public class ServerComputer implements ComputerEnvironment, ComputerEvents.Recei
}
public <T> Properties addComponent(ComputerComponent<T> component, T value) {
components.add(component, value);
if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set");
components.put(component, value);
return this;
}
}

View File

@ -8,9 +8,7 @@ import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuType;
@ -20,16 +18,6 @@ import net.minecraft.world.inventory.MenuType;
public interface ContainerData {
void toBytes(RegistryFriendlyByteBuf buf);
/**
* Open a menu for a specific player using this data.
*
* @param player The player to open the menu for.
* @param menu The underlying menu provider.
*/
default void open(Player player, MenuProvider menu) {
PlatformHelper.get().openMenu(player, menu, this);
}
static <C extends AbstractContainerMenu, T extends ContainerData> MenuType<C> toType(StreamCodec<RegistryFriendlyByteBuf, T> codec, Factory<C, T> factory) {
return PlatformHelper.get().createMenuType(codec, factory);
}

View File

@ -18,6 +18,7 @@ import net.minecraft.core.Direction;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
@ -237,7 +238,7 @@ public class MonitorBlockEntity extends BlockEntity {
getLevel().setBlock(getBlockPos(), getBlockState()
.setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
yIndex < height - 1, yIndex > 0,
xIndex > 0, xIndex < width - 1)), 2);
xIndex > 0, xIndex < width - 1)), Block.UPDATE_CLIENTS);
}
// region Sizing and placement stuff

View File

@ -17,6 +17,7 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.level.ServerLevel;
@ -24,10 +25,14 @@ 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.*;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.WorldlyContainer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.DyeColor;
@ -109,10 +114,11 @@ public interface PlatformHelper {
* Open a container using a specific {@link ContainerData}.
*
* @param player The player to open the menu for.
* @param owner The underlying menu provider.
* @param menu The menu data.
* @param title The title for this menu.
* @param menu The underlying menu constructor.
* @param data The menu data.
*/
void openMenu(Player player, MenuProvider owner, ContainerData menu);
void openMenu(Player player, Component title, MenuConstructor menu, ContainerData data);
/**
* Invalidate components on a block enitty.

View File

@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: 2021 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.pocket.inventory;
import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.pocket.items.PocketComputerItem;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.item.ItemStack;
import javax.annotation.Nullable;
import static dan200.computercraft.core.util.Nullability.assertNonNull;
public class PocketComputerMenuProvider implements MenuProvider {
private final ServerComputer computer;
private final Component name;
private final PocketComputerItem item;
private final InteractionHand hand;
private final boolean isTypingOnly;
public PocketComputerMenuProvider(ServerComputer computer, ItemStack stack, PocketComputerItem item, InteractionHand hand, boolean isTypingOnly) {
this.computer = computer;
name = stack.getHoverName();
this.item = item;
this.hand = hand;
this.isTypingOnly = isTypingOnly;
}
@Override
public Component getDisplayName() {
return name;
}
@Nullable
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player entity) {
return new ComputerMenuWithoutInventory(
isTypingOnly ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.COMPUTER.get(), id, inventory,
p -> {
var stack = p.getItemInHand(hand);
return stack.getItem() == item && PocketComputerItem.getServerComputer(assertNonNull(entity.level().getServer()), stack) == computer;
},
computer
);
}
}

View File

@ -17,13 +17,14 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
import dan200.computercraft.shared.computer.core.ServerComputerRegistry;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.inventory.ComputerMenuWithoutInventory;
import dan200.computercraft.shared.computer.items.ServerComputerReference;
import dan200.computercraft.shared.config.ConfigSpec;
import dan200.computercraft.shared.network.container.ComputerContainerData;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.pocket.core.PocketBrain;
import dan200.computercraft.shared.pocket.core.PocketHolder;
import dan200.computercraft.shared.pocket.core.PocketServerComputer;
import dan200.computercraft.shared.pocket.inventory.PocketComputerMenuProvider;
import dan200.computercraft.shared.util.*;
import net.minecraft.ChatFormatting;
import net.minecraft.core.HolderLookup;
@ -147,8 +148,13 @@ public class PocketComputerItem extends Item implements IMedia {
}
if (!stop) {
var isTypingOnly = hand == InteractionHand.OFF_HAND;
new ComputerContainerData(computer, stack).open(player, new PocketComputerMenuProvider(computer, stack, this, hand, isTypingOnly));
PlatformHelper.get().openMenu(
player, stack.getHoverName(),
(id, inventory, entity) -> new ComputerMenuWithoutInventory(
hand == InteractionHand.OFF_HAND ? ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get() : ModRegistry.Menus.COMPUTER.get(),
id, inventory, p -> isServerComputer(computer, p.getItemInHand(hand)), computer
),
new ComputerContainerData(computer, stack));
}
}
return new InteractionResultHolder<>(InteractionResult.sidedSuccess(world.isClientSide), stack);

View File

@ -40,6 +40,7 @@ import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.MoverType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
@ -256,7 +257,7 @@ public class TurtleBrain implements TurtleAccessInternal {
try {
// We use Block.UPDATE_CLIENTS here to ensure that neighbour updates caused in Block.updateNeighbourShapes
// are sent to the client. We want to avoid doing a full block update until the turtle state is copied over.
if (world.setBlock(pos, newState, 2)) {
if (world.setBlock(pos, newState, Block.UPDATE_CLIENTS)) {
var block = world.getBlockState(pos).getBlock();
if (block == oldBlock.getBlock()) {
var newTile = world.getBlockEntity(pos);
@ -669,7 +670,7 @@ public class TurtleBrain implements TurtleAccessInternal {
}
var aabb = new AABB(minX, minY, minZ, maxX, maxY, maxZ);
var list = world.getEntitiesOfClass(Entity.class, aabb, TurtleBrain::canPush);
var list = world.getEntities((Entity) null, aabb, TurtleBrain::canPush);
if (!list.isEmpty()) {
double pushStep = 1.0f / ANIM_DURATION;
var pushStepX = moveDir.getStepX() * pushStep;

View File

@ -22,11 +22,10 @@ public class TurtleDetectCommand implements TurtleCommand {
var direction = this.direction.toWorldDir(turtle);
// Check if thing in front is air or not
var world = turtle.getLevel();
var oldPosition = turtle.getPosition();
var newPosition = oldPosition.relative(direction);
var level = turtle.getLevel();
var pos = turtle.getPosition().relative(direction);
return !WorldUtil.isLiquidBlock(world, newPosition) && !world.isEmptyBlock(newPosition)
return !WorldUtil.isEmptyBlock(level.getBlockState(pos))
? TurtleCommandResult.success()
: TurtleCommandResult.failure();
}

View File

@ -13,9 +13,9 @@ 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.level.material.PushReaction;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
public class TurtleMoveCommand implements TurtleCommand {
private final MoveDirection direction;
@ -30,57 +30,32 @@ public class TurtleMoveCommand implements TurtleCommand {
var direction = this.direction.toWorldDir(turtle);
// Check if we can move
var oldWorld = (ServerLevel) turtle.getLevel();
var level = (ServerLevel) turtle.getLevel();
var oldPosition = turtle.getPosition();
var newPosition = oldPosition.relative(direction);
var turtlePlayer = TurtlePlayer.getWithPosition(turtle, oldPosition, direction);
var canEnterResult = canEnter(turtlePlayer, oldWorld, newPosition);
if (!canEnterResult.isSuccess()) {
return canEnterResult;
}
var canEnterResult = canEnter(turtlePlayer, level, newPosition);
if (!canEnterResult.isSuccess()) return canEnterResult;
// Check existing block is air or replaceable
var state = oldWorld.getBlockState(newPosition);
if (!oldWorld.isEmptyBlock(newPosition) &&
!WorldUtil.isLiquidBlock(oldWorld, newPosition) &&
!state.canBeReplaced()) {
// Check existing block is air or replaceable.
var existingState = level.getBlockState(newPosition);
if (!(WorldUtil.isEmptyBlock(existingState) || existingState.canBeReplaced())) {
return TurtleCommandResult.failure("Movement obstructed");
}
// Check there isn't anything in the way
var collision = state.getCollisionShape(oldWorld, oldPosition).move(
newPosition.getX(),
newPosition.getY(),
newPosition.getZ()
);
if (!oldWorld.isUnobstructed(null, collision)) {
if (!Config.turtlesCanPush || this.direction == MoveDirection.UP || this.direction == MoveDirection.DOWN) {
return TurtleCommandResult.failure("Movement obstructed");
}
// Check there is space for all the pushable entities to be pushed
var list = oldWorld.getEntitiesOfClass(Entity.class, getBox(collision), x -> x != null && x.isAlive() && x.blocksBuilding);
for (var entity : list) {
var pushedBB = entity.getBoundingBox().move(
direction.getStepX(),
direction.getStepY(),
direction.getStepZ()
);
if (!oldWorld.isUnobstructed(null, Shapes.create(pushedBB))) {
return TurtleCommandResult.failure("Movement obstructed");
}
}
// Check there isn't an entity in the way.
var turtleShape = level.getBlockState(oldPosition).getCollisionShape(level, oldPosition)
.move(newPosition.getX(), newPosition.getY(), newPosition.getZ());
if (!level.isUnobstructed(null, turtleShape) && !canPushEntities(level, turtleShape.bounds())) {
return TurtleCommandResult.failure("Movement obstructed");
}
// Check fuel level
if (turtle.isFuelNeeded() && turtle.getFuelLevel() < 1) {
return TurtleCommandResult.failure("Out of fuel");
}
if (turtle.isFuelNeeded() && turtle.getFuelLevel() < 1) return TurtleCommandResult.failure("Out of fuel");
// Move
if (!turtle.teleportTo(oldWorld, newPosition)) return TurtleCommandResult.failure("Movement failed");
if (!turtle.teleportTo(level, newPosition)) return TurtleCommandResult.failure("Movement failed");
// Consume fuel
turtle.consumeFuel(1);
@ -114,9 +89,20 @@ public class TurtleMoveCommand implements TurtleCommand {
return TurtleCommandResult.success();
}
private static AABB getBox(VoxelShape shape) {
return shape.isEmpty() ? EMPTY_BOX : shape.bounds();
}
private static final AABB EMPTY_BOX = new AABB(0, 0, 0, 0, 0, 0);
/**
* Determine if all entities in the given bounds can be pushed by the turtle.
*
* @param level The current level.
* @param bounds The bounding box.
* @return Whether all entities can be pushed.
*/
private boolean canPushEntities(Level level, AABB bounds) {
if (!Config.turtlesCanPush) return false;
// Check there is space for all the pushable entities to be pushed
return level.getEntities((Entity) null, bounds, e -> e.isAlive()
&& !e.isSpectator() && e.blocksBuilding && e.getPistonPushReaction() == PushReaction.IGNORE
).isEmpty();
}
}

View File

@ -1,66 +0,0 @@
// SPDX-FileCopyrightText: 2024 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.shared.util;
import dan200.computercraft.api.component.ComputerComponent;
import dan200.computercraft.core.metrics.MetricsObserver;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
/**
* An immutable map of components.
*/
public final class ComponentMap {
public static final ComputerComponent<MetricsObserver> METRICS = ComputerComponent.create("computercraft", "metrics");
private static final ComponentMap EMPTY = new ComponentMap(Map.of());
private final Map<ComputerComponent<?>, Object> components;
private ComponentMap(Map<ComputerComponent<?>, Object> components) {
this.components = components;
}
@SuppressWarnings("unchecked")
public <T> @Nullable T get(ComputerComponent<T> component) {
return (T) components.get(component);
}
public static ComponentMap empty() {
return EMPTY;
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private final Map<ComputerComponent<?>, Object> components = new HashMap<>();
private Builder() {
}
public <T> Builder add(ComputerComponent<T> component, T value) {
addImpl(component, value);
return this;
}
public Builder add(ComponentMap components) {
for (var component : components.components.entrySet()) addImpl(component.getKey(), component.getValue());
return this;
}
private void addImpl(ComputerComponent<?> component, Object value) {
if (components.containsKey(component)) throw new IllegalArgumentException(component + " is already set");
components.put(component, value);
}
public ComponentMap build() {
return new ComponentMap(Map.copyOf(components));
}
}
}

View File

@ -28,9 +28,14 @@ import net.minecraft.world.phys.shapes.VoxelShape;
import javax.annotation.Nullable;
public final class WorldUtil {
@SuppressWarnings("deprecation")
public static boolean isLiquidBlock(Level world, BlockPos pos) {
if (!world.isInWorldBounds(pos)) return false;
return world.getBlockState(pos).liquid();
return world.isInWorldBounds(pos) && world.getBlockState(pos).liquid();
}
@SuppressWarnings("deprecation")
public static boolean isEmptyBlock(BlockState state) {
return state.isAir() || state.liquid();
}
public static boolean isVecInside(VoxelShape shape, Vec3 vec) {

View File

@ -18,6 +18,7 @@ import net.minecraft.commands.synchronization.ArgumentTypeInfo;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.network.chat.Component;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
@ -26,10 +27,10 @@ import net.minecraft.server.level.ServerPlayer;
import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
@ -77,7 +78,7 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
}
@Override
public void openMenu(Player player, MenuProvider owner, ContainerData menu) {
public void openMenu(Player player, Component title, MenuConstructor menu, ContainerData data) {
throw new UnsupportedOperationException("Cannot open menu inside tests");
}

View File

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: 2025 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package dan200.computercraft.gametest
import dan200.computercraft.gametest.api.assertNoPeripheral
import dan200.computercraft.gametest.api.assertPeripheral
import dan200.computercraft.gametest.api.immediate
import dan200.computercraft.shared.ModRegistry
import dan200.computercraft.shared.platform.ComponentAccess
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.gametest.framework.GameTest
import net.minecraft.gametest.framework.GameTestHelper
import java.util.*
/**
* Checks that we expose [ComponentAccess] for various blocks/block entities
*/
class Component_Test {
@GameTest(template = "default")
fun Peripheral(context: GameTestHelper) = context.immediate {
val pos = BlockPos(2, 2, 2)
// We fetch peripherals from the NORTH, as that is the default direction for modems. This is a bit of a hack,
// but avoids having to override the block state.
val side = Direction.NORTH
for ((block, type) in mapOf(
// Computers
ModRegistry.Blocks.COMPUTER_NORMAL to Optional.of("computer"),
ModRegistry.Blocks.COMPUTER_ADVANCED to Optional.of("computer"),
ModRegistry.Blocks.COMPUTER_COMMAND to Optional.empty(),
// Turtles
ModRegistry.Blocks.TURTLE_NORMAL to Optional.of("turtle"),
ModRegistry.Blocks.TURTLE_ADVANCED to Optional.of("turtle"),
// Peripherals
ModRegistry.Blocks.SPEAKER to Optional.of("speaker"),
ModRegistry.Blocks.DISK_DRIVE to Optional.of("drive"),
ModRegistry.Blocks.PRINTER to Optional.of("printer"),
ModRegistry.Blocks.MONITOR_NORMAL to Optional.of("monitor"),
ModRegistry.Blocks.MONITOR_ADVANCED to Optional.of("monitor"),
ModRegistry.Blocks.WIRELESS_MODEM_NORMAL to Optional.of("modem"),
ModRegistry.Blocks.WIRELESS_MODEM_ADVANCED to Optional.of("modem"),
ModRegistry.Blocks.WIRED_MODEM_FULL to Optional.of("modem"),
ModRegistry.Blocks.REDSTONE_RELAY to Optional.of("redstone_relay"),
)) {
context.setBlock(pos, block.get())
if (type.isPresent) {
context.assertPeripheral(pos, side, type.get())
} else {
context.assertNoPeripheral(pos, side)
}
}
}
}

View File

@ -95,17 +95,6 @@ class Computer_Test {
}
}
/**
* Check computers and turtles expose peripherals.
*/
@GameTest
fun Computer_peripheral(context: GameTestHelper) = context.sequence {
thenExecute {
context.assertPeripheral(BlockPos(3, 2, 2), type = "computer")
context.assertPeripheral(BlockPos(1, 2, 2), type = "turtle")
}
}
/**
* Check chest peripherals are reattached with a new size.
*/

View File

@ -658,6 +658,27 @@ class Turtle_Test {
}
}
/**
* Test turtles can push entities.
*/
@GameTest
fun Move_push_entity(helper: GameTestHelper) = helper.sequence {
thenOnComputer { turtle.up().await().assertArrayEquals(true) }
thenIdle(9)
thenExecute {
// The turtle has moved up
helper.assertBlockPresent(ModRegistry.Blocks.TURTLE_NORMAL.get(), BlockPos(2, 3, 2))
// As has the villager
val pos = BlockPos(2, 4, 2)
helper.assertEntityPresent(EntityType.VILLAGER, pos)
val villager = helper.getEntity(EntityType.VILLAGER)
val expectedY = helper.absolutePos(pos).y - 0.125
if (villager.y < expectedY) helper.fail("Expected villager at y>=$expectedY, but at ${villager.y}", pos)
}
}
/**
* Test a turtle can attack an entity and capture its drops.
*/

View File

@ -128,6 +128,14 @@ fun GameTestHelper.sequence(run: GameTestSequence.() -> Unit) {
sequence.thenSucceed()
}
/**
* Run a function immediately, and then succeed.
*/
fun GameTestHelper.immediate(run: () -> Unit) {
run()
succeed()
}
/**
* A custom instance of [GameTestAssertPosException] which allows for longer error messages.
*/
@ -237,15 +245,17 @@ private fun GameTestHelper.getPeripheralAt(pos: BlockPos, direction: Direction):
fun GameTestHelper.assertPeripheral(pos: BlockPos, direction: Direction = Direction.UP, type: String) {
val peripheral = getPeripheralAt(pos, direction)
val block = getBlockState(pos).block.name.string
when {
peripheral == null -> fail("No peripheral at position", pos)
peripheral.type != type -> fail("Peripheral is of type ${peripheral.type}, expected $type", pos)
peripheral == null -> fail("No peripheral for '$block'", pos)
peripheral.type != type -> fail("Peripheral for '$block' is of type ${peripheral.type}, expected $type", pos)
}
}
fun GameTestHelper.assertNoPeripheral(pos: BlockPos, direction: Direction = Direction.UP) {
val peripheral = getPeripheralAt(pos, direction)
if (peripheral != null) fail("Expected no peripheral, got a ${peripheral.type}", pos)
val block = getBlockState(pos).block.name
if (peripheral != null) fail("Expected no peripheral for '$block', got a ${peripheral.type}", pos)
}
fun GameTestHelper.assertExactlyItems(vararg expected: ItemStack, message: String? = null) {

View File

@ -36,6 +36,10 @@ object ManagedComputers : ILuaMachine.Factory {
private val LOGGER = LoggerFactory.getLogger(ManagedComputers::class.java)
private val computers: MutableMap<String, Queue<suspend LuaTaskContext.() -> Unit>> = mutableMapOf()
internal fun reset() {
computers.clear()
}
internal fun enqueue(test: GameTestInfo, label: String, task: suspend LuaTaskContext.() -> Unit): Monitor {
val monitor = Monitor(test, label)
computers.computeIfAbsent(label) { ConcurrentLinkedDeque() }.add {

View File

@ -84,6 +84,8 @@ object TestHooks {
StructureUtils.clearSpaceForStructure(StructureUtils.getStructureBoundingBox(structure), level)
}
ManagedComputers.reset()
// Delete server context and add one with a mutable machine factory. This allows us to set the factory for
// specific test batches without having to reset all computers.
for (computer in ServerContext.get(server).registry().computers) {
@ -99,6 +101,7 @@ object TestHooks {
fun areComputersIdle(server: MinecraftServer) = ComputerThreadReflection.isFullyIdle(ServerContext.get(server))
private val testClasses = listOf(
Component_Test::class.java,
Computer_Test::class.java,
CraftOs_Test::class.java,
Details_Test::class.java,

View File

@ -1,5 +1,5 @@
{
DataVersion: 2975,
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:polished_andesite"},
@ -33,19 +33,19 @@
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "computercraft:turtle_advanced{facing:south,waterlogged:false}", nbt: {Fuel: 0, Items: [], On: 0b, Owner: {LowerId: -6876936588741668278L, Name: "Dev", UpperId: 4039158846114182220L}, Slot: 0, id: "computercraft:turtle_advanced"}},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:white_stained_glass"},
{pos: [1, 1, 2], state: "minecraft:white_stained_glass"},
{pos: [1, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:white_stained_glass"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:west,waterlogged:false}", nbt: {ComputerId: 1, Label: "turtle_test.move_push_entity", Fuel: 80, Items: [], On: 1b, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "computercraft:computer_advanced{facing:north,state:off}", nbt: {On: 0b, id: "computercraft:computer_advanced"}},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:white_stained_glass"},
{pos: [3, 1, 2], state: "minecraft:white_stained_glass"},
{pos: [3, 1, 3], state: "minecraft:white_stained_glass"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
@ -58,19 +58,19 @@
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:white_stained_glass"},
{pos: [1, 2, 2], state: "minecraft:white_stained_glass"},
{pos: [1, 2, 3], state: "minecraft:white_stained_glass"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:white_stained_glass"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:white_stained_glass"},
{pos: [3, 2, 2], state: "minecraft:white_stained_glass"},
{pos: [3, 2, 3], state: "minecraft:white_stained_glass"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
@ -83,19 +83,19 @@
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:white_stained_glass"},
{pos: [1, 3, 2], state: "minecraft:white_stained_glass"},
{pos: [1, 3, 3], state: "minecraft:white_stained_glass"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:white_stained_glass"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:white_stained_glass"},
{pos: [3, 3, 2], state: "minecraft:white_stained_glass"},
{pos: [3, 3, 3], state: "minecraft:white_stained_glass"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
@ -108,19 +108,19 @@
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:white_stained_glass"},
{pos: [1, 4, 2], state: "minecraft:white_stained_glass"},
{pos: [1, 4, 3], state: "minecraft:white_stained_glass"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:white_stained_glass"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:white_stained_glass"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:white_stained_glass"},
{pos: [3, 4, 2], state: "minecraft:white_stained_glass"},
{pos: [3, 4, 3], state: "minecraft:white_stained_glass"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
@ -128,11 +128,13 @@
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
entities: [
{blockPos: [2, 1, 2], pos: [2.5d, 1.875d, 2.5d], nbt: {AbsorptionAmount: 0.0f, Age: 0, Air: 300s, ArmorDropChances: [0.085f, 0.085f, 0.085f, 0.085f], ArmorItems: [{}, {}, {}, {}], Attributes: [{Base: 0.5d, Name: "minecraft:generic.movement_speed"}, {Base: 48.0d, Modifiers: [{Amount: -0.01165046535152748d, Name: "Random spawn bonus", Operation: 1, UUID: [I; 1412502412, 1522745411, -1211155694, 2103054347]}], Name: "minecraft:generic.follow_range"}], Brain: {memories: {}}, CanPickUpLoot: 1b, DeathTime: 0s, FallDistance: 0.0f, FallFlying: 0b, Fire: -1s, FoodLevel: 0b, ForcedAge: 0, Gossips: [], HandDropChances: [0.085f, 0.085f], HandItems: [{}, {}], Health: 20.0f, HurtByTimestamp: 0, HurtTime: 0s, Inventory: [], Invulnerable: 0b, LastGossipDecay: 52357L, LastRestock: 0L, LeftHanded: 0b, Motion: [0.0d, -0.0784000015258789d, 0.0d], OnGround: 1b, PersistenceRequired: 0b, PortalCooldown: 0, Pos: [-33.5d, 58.875d, -21.5d], RestocksToday: 0, Rotation: [-102.704926f, 0.0f], UUID: [I; 164071932, -867285780, -1817215456, -2129864016], VillagerData: {level: 1, profession: "minecraft:none", type: "minecraft:desert"}, Xp: 0, id: "minecraft:villager"}}
],
palette: [
"minecraft:polished_andesite",
"minecraft:white_stained_glass",
"minecraft:air",
"computercraft:turtle_advanced{facing:south,waterlogged:false}",
"computercraft:computer_advanced{facing:north,state:off}"
"computercraft:turtle_normal{facing:west,waterlogged:false}"
]
}

View File

@ -47,12 +47,22 @@ local function sortCoords(startX, startY, endX, endY)
return minX, maxX, minY, maxY
end
--- Parses an image from a multi-line string
--
-- @tparam string image The string containing the raw-image data.
-- @treturn table The parsed image data, suitable for use with
-- [`paintutils.drawImage`].
-- @since 1.80pr1
--[=[- Parses an image from a multi-line string
@tparam string image The string containing the raw-image data.
@treturn table The parsed image data, suitable for use with [`paintutils.drawImage`].
@usage Parse an image from a string, and draw it.
local image = paintutils.parseImage([[
e e
e e
eeee
]])
paintutils.drawImage(image, term.getCursorPos())
@since 1.80pr1
]=]
function parseImage(image)
expect(1, image, "string")
local tImage = {}

View File

@ -1,3 +1,12 @@
# New features in CC: Tweaked 1.114.4
* Allow typing/pasting any character in the CC charset.
* Add a new `computercraft:storage_capacity` to override a disk or computer's capacity.
Several bug fixes:
* Fix command computers having NBT set when placed in a Create contraption.
* Use correct bounding box when checking for entities in turtle movement.
# New features in CC: Tweaked 1.114.3
* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69).

View File

@ -1,10 +1,10 @@
New features in CC: Tweaked 1.114.3
New features in CC: Tweaked 1.114.4
* `wget` now prints the error that occurred, rather than a generic "Failed" (tizu69).
* Update several translations.
* Allow typing/pasting any character in the CC charset.
* Add a new `computercraft:storage_capacity` to override a disk or computer's capacity.
Several bug fixes:
* Fix `fs.isDriveRoot` returning true for non-existent files.
* Fix possible memory leak when sending terminal contents.
* Fix command computers having NBT set when placed in a Create contraption.
* Use correct bounding box when checking for entities in turtle movement.
Type "help changelog" to see the full version history.

View File

@ -47,11 +47,11 @@ import net.minecraft.tags.TagKey;
import net.minecraft.world.Container;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.world.inventory.MenuConstructor;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.*;
import net.minecraft.world.item.crafting.Ingredient;
@ -105,8 +105,8 @@ public class PlatformHelperImpl implements PlatformHelper {
}
@Override
public void openMenu(Player player, MenuProvider owner, ContainerData menu) {
player.openMenu(new WrappedMenuProvider<>(owner, menu));
public void openMenu(Player player, Component title, MenuConstructor menu, ContainerData data) {
player.openMenu(new WrappedMenuProvider<>(title, menu, data));
}
@Override
@ -277,22 +277,22 @@ public class PlatformHelperImpl implements PlatformHelper {
}
private record WrappedMenuProvider<T extends ContainerData>(
MenuProvider owner, T menu
Component title, MenuConstructor menu, T data
) implements ExtendedScreenHandlerFactory<T> {
@Nullable
@Override
public AbstractContainerMenu createMenu(int id, Inventory inventory, Player player) {
return owner.createMenu(id, inventory, player);
return menu.createMenu(id, inventory, player);
}
@Override
public Component getDisplayName() {
return owner.getDisplayName();
return title;
}
@Override
public T getScreenOpeningData(ServerPlayer player) {
return menu;
return data;
}
}

View File

@ -23,19 +23,18 @@ import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.core.Registry;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
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.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.MenuProvider;
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.MenuConstructor;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.Item;
@ -95,8 +94,8 @@ public class PlatformHelperImpl implements PlatformHelper {
}
@Override
public void openMenu(Player player, MenuProvider owner, ContainerData menu) {
player.openMenu(owner, menu::toBytes);
public void openMenu(Player player, Component title, MenuConstructor menu, ContainerData data) {
player.openMenu(new SimpleMenuProvider(menu, title), data::toBytes);
}
@Override