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:
commit
9bb62b047a
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -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.
|
||||
|
||||

|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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}"
|
||||
]
|
||||
}
|
@ -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 = {}
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user