620 lines
22 KiB
Java
620 lines
22 KiB
Java
// Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
|
|
//
|
|
// SPDX-License-Identifier: LicenseRef-CCPL
|
|
|
|
package dan200.computercraft.shared.peripheral.monitor;
|
|
|
|
import com.google.common.annotations.VisibleForTesting;
|
|
import dan200.computercraft.annotations.ForgeOverride;
|
|
import dan200.computercraft.api.peripheral.IComputerAccess;
|
|
import dan200.computercraft.api.peripheral.IPeripheral;
|
|
import dan200.computercraft.core.terminal.Terminal;
|
|
import dan200.computercraft.shared.computer.terminal.TerminalState;
|
|
import dan200.computercraft.shared.config.Config;
|
|
import dan200.computercraft.shared.util.BlockEntityHelpers;
|
|
import dan200.computercraft.shared.util.TickScheduler;
|
|
import net.minecraft.core.BlockPos;
|
|
import net.minecraft.core.Direction;
|
|
import net.minecraft.nbt.CompoundTag;
|
|
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
|
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
|
import net.minecraft.world.level.block.entity.BlockEntityType;
|
|
import net.minecraft.world.level.block.state.BlockState;
|
|
import net.minecraft.world.phys.AABB;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import javax.annotation.Nullable;
|
|
import java.util.Collections;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.function.Consumer;
|
|
|
|
public class MonitorBlockEntity extends BlockEntity {
|
|
private static final Logger LOG = LoggerFactory.getLogger(MonitorBlockEntity.class);
|
|
|
|
public static final double RENDER_BORDER = 2.0 / 16.0;
|
|
public static final double RENDER_MARGIN = 0.5 / 16.0;
|
|
public static final double RENDER_PIXEL_SCALE = 1.0 / 64.0;
|
|
|
|
private static final String NBT_X = "XIndex";
|
|
private static final String NBT_Y = "YIndex";
|
|
private static final String NBT_WIDTH = "Width";
|
|
private static final String NBT_HEIGHT = "Height";
|
|
|
|
private final boolean advanced;
|
|
|
|
private @Nullable ServerMonitor serverMonitor;
|
|
|
|
/**
|
|
* The monitor's state on the client. This is defined iff we're the origin monitor
|
|
* ({@code xIndex == 0 && yIndex == 0}).
|
|
*/
|
|
private @Nullable ClientMonitor clientMonitor;
|
|
|
|
private @Nullable MonitorPeripheral peripheral;
|
|
private final Set<IComputerAccess> computers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
|
|
|
private boolean needsUpdate = false;
|
|
private boolean needsValidating = false;
|
|
|
|
// MonitorWatcher state.
|
|
boolean enqueued;
|
|
@Nullable
|
|
TerminalState cached;
|
|
|
|
private int width = 1;
|
|
private int height = 1;
|
|
private int xIndex = 0;
|
|
private int yIndex = 0;
|
|
|
|
private @Nullable BlockPos bbPos;
|
|
private @Nullable BlockState bbState;
|
|
private int bbX, bbY, bbWidth, bbHeight;
|
|
private @Nullable AABB boundingBox;
|
|
|
|
TickScheduler.Token tickToken = new TickScheduler.Token(this);
|
|
|
|
public MonitorBlockEntity(BlockEntityType<? extends MonitorBlockEntity> type, BlockPos pos, BlockState state, boolean advanced) {
|
|
super(type, pos, state);
|
|
this.advanced = advanced;
|
|
}
|
|
|
|
@Override
|
|
public void clearRemoved() {
|
|
super.clearRemoved();
|
|
needsValidating = true; // Same, tbh
|
|
TickScheduler.schedule(tickToken);
|
|
}
|
|
|
|
void destroy() {
|
|
// TODO: Call this before using the block
|
|
if (!getLevel().isClientSide) contractNeighbours();
|
|
}
|
|
|
|
@Override
|
|
public void setRemoved() {
|
|
super.setRemoved();
|
|
if (clientMonitor != null) clientMonitor.destroy();
|
|
}
|
|
|
|
@Override
|
|
public void saveAdditional(CompoundTag tag) {
|
|
tag.putInt(NBT_X, xIndex);
|
|
tag.putInt(NBT_Y, yIndex);
|
|
tag.putInt(NBT_WIDTH, width);
|
|
tag.putInt(NBT_HEIGHT, height);
|
|
super.saveAdditional(tag);
|
|
}
|
|
|
|
@Override
|
|
public void load(CompoundTag nbt) {
|
|
super.load(nbt);
|
|
|
|
var oldXIndex = xIndex;
|
|
var oldYIndex = yIndex;
|
|
|
|
xIndex = nbt.getInt(NBT_X);
|
|
yIndex = nbt.getInt(NBT_Y);
|
|
width = nbt.getInt(NBT_WIDTH);
|
|
height = nbt.getInt(NBT_HEIGHT);
|
|
|
|
if (level != null && level.isClientSide) onClientLoad(oldXIndex, oldYIndex);
|
|
}
|
|
|
|
void blockTick() {
|
|
if (needsValidating) {
|
|
needsValidating = false;
|
|
validate();
|
|
}
|
|
|
|
if (needsUpdate) {
|
|
needsUpdate = false;
|
|
expand();
|
|
}
|
|
|
|
if (xIndex != 0 || yIndex != 0 || serverMonitor == null) return;
|
|
|
|
if (serverMonitor.pollResized()) eachComputer(c -> c.queueEvent("monitor_resize", c.getAttachmentName()));
|
|
if (serverMonitor.pollTerminalChanged()) MonitorWatcher.enqueue(this);
|
|
}
|
|
|
|
@Nullable
|
|
@VisibleForTesting
|
|
public ServerMonitor getCachedServerMonitor() {
|
|
return serverMonitor;
|
|
}
|
|
|
|
@Nullable
|
|
private ServerMonitor getServerMonitor() {
|
|
if (serverMonitor != null) return serverMonitor;
|
|
|
|
var origin = getOrigin();
|
|
if (origin == null) return null;
|
|
|
|
return serverMonitor = origin.serverMonitor;
|
|
}
|
|
|
|
@Nullable
|
|
private ServerMonitor createServerMonitor() {
|
|
if (serverMonitor != null) return serverMonitor;
|
|
|
|
if (xIndex == 0 && yIndex == 0) {
|
|
// If we're the origin, set up the new monitor
|
|
serverMonitor = new ServerMonitor(advanced, this);
|
|
|
|
// And propagate it to child monitors
|
|
for (var x = 0; x < width; x++) {
|
|
for (var y = 0; y < height; y++) {
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
|
if (monitor != null) monitor.serverMonitor = serverMonitor;
|
|
}
|
|
}
|
|
|
|
return serverMonitor;
|
|
} else {
|
|
// Otherwise fetch the origin and attempt to get its monitor
|
|
// Note this may load chunks, but we don't really have a choice here.
|
|
var te = getLevel().getBlockEntity(toWorldPos(0, 0));
|
|
if (!(te instanceof MonitorBlockEntity monitor)) return null;
|
|
|
|
return serverMonitor = monitor.createServerMonitor();
|
|
}
|
|
}
|
|
|
|
private void createServerTerminal() {
|
|
var monitor = createServerMonitor();
|
|
if (monitor != null && monitor.getTerminal() == null) monitor.rebuild();
|
|
}
|
|
|
|
@Nullable
|
|
public ClientMonitor getOriginClientMonitor() {
|
|
if (clientMonitor != null) return clientMonitor;
|
|
|
|
var origin = getOrigin();
|
|
return origin == null ? null : origin.clientMonitor;
|
|
}
|
|
|
|
// Networking stuff
|
|
|
|
@Override
|
|
public final ClientboundBlockEntityDataPacket getUpdatePacket() {
|
|
return ClientboundBlockEntityDataPacket.create(this);
|
|
}
|
|
|
|
@Override
|
|
public final CompoundTag getUpdateTag() {
|
|
var nbt = super.getUpdateTag();
|
|
nbt.putInt(NBT_X, xIndex);
|
|
nbt.putInt(NBT_Y, yIndex);
|
|
nbt.putInt(NBT_WIDTH, width);
|
|
nbt.putInt(NBT_HEIGHT, height);
|
|
return nbt;
|
|
}
|
|
|
|
private void onClientLoad(int oldXIndex, int oldYIndex) {
|
|
if ((oldXIndex != xIndex || oldYIndex != yIndex) && clientMonitor != null) {
|
|
// If our index has changed, and we were the origin, then destroy the current monitor.
|
|
clientMonitor.destroy();
|
|
clientMonitor = null;
|
|
}
|
|
|
|
// If we're the origin terminal then create it.
|
|
if (xIndex == 0 && yIndex == 0 && clientMonitor == null) clientMonitor = new ClientMonitor(this);
|
|
}
|
|
|
|
public final void read(@Nullable TerminalState state) {
|
|
if (xIndex != 0 || yIndex != 0) {
|
|
LOG.warn("Receiving monitor state for non-origin terminal at {}", getBlockPos());
|
|
return;
|
|
}
|
|
|
|
if (clientMonitor == null) clientMonitor = new ClientMonitor(this);
|
|
clientMonitor.read(state);
|
|
}
|
|
|
|
// Sizing and placement stuff
|
|
|
|
private void updateBlockState() {
|
|
getLevel().setBlock(getBlockPos(), getBlockState()
|
|
.setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
|
|
yIndex < height - 1, yIndex > 0,
|
|
xIndex > 0, xIndex < width - 1)), 2);
|
|
}
|
|
|
|
// region Sizing and placement stuff
|
|
public Direction getDirection() {
|
|
// Ensure we're actually a monitor block. This _should_ always be the case, but sometimes there's
|
|
// fun problems with the block being missing on the client.
|
|
var state = getBlockState();
|
|
return state.hasProperty(MonitorBlock.FACING) ? state.getValue(MonitorBlock.FACING) : Direction.NORTH;
|
|
}
|
|
|
|
public Direction getOrientation() {
|
|
var state = getBlockState();
|
|
return state.hasProperty(MonitorBlock.ORIENTATION) ? state.getValue(MonitorBlock.ORIENTATION) : Direction.NORTH;
|
|
}
|
|
|
|
public Direction getFront() {
|
|
var orientation = getOrientation();
|
|
return orientation == Direction.NORTH ? getDirection() : orientation;
|
|
}
|
|
|
|
public Direction getRight() {
|
|
return getDirection().getCounterClockWise();
|
|
}
|
|
|
|
public Direction getDown() {
|
|
var orientation = getOrientation();
|
|
if (orientation == Direction.NORTH) return Direction.UP;
|
|
return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite();
|
|
}
|
|
|
|
public int getWidth() {
|
|
return width;
|
|
}
|
|
|
|
public int getHeight() {
|
|
return height;
|
|
}
|
|
|
|
public int getXIndex() {
|
|
return xIndex;
|
|
}
|
|
|
|
public int getYIndex() {
|
|
return yIndex;
|
|
}
|
|
|
|
boolean isCompatible(MonitorBlockEntity other) {
|
|
return advanced == other.advanced && getOrientation() == other.getOrientation() && getDirection() == other.getDirection();
|
|
}
|
|
|
|
/**
|
|
* Get a tile within the current monitor only if it is loaded and compatible.
|
|
*
|
|
* @param x Absolute X position in monitor coordinates
|
|
* @param y Absolute Y position in monitor coordinates
|
|
* @return The located monitor
|
|
*/
|
|
private MonitorState getLoadedMonitor(int x, int y) {
|
|
if (x == xIndex && y == yIndex) return MonitorState.present(this);
|
|
var pos = toWorldPos(x, y);
|
|
|
|
var world = getLevel();
|
|
if (world == null || !world.isLoaded(pos)) return MonitorState.UNLOADED;
|
|
|
|
var tile = world.getBlockEntity(pos);
|
|
if (!(tile instanceof MonitorBlockEntity monitor)) return MonitorState.MISSING;
|
|
|
|
return isCompatible(monitor) ? MonitorState.present(monitor) : MonitorState.MISSING;
|
|
}
|
|
|
|
private @Nullable MonitorBlockEntity getOrigin() {
|
|
return getLoadedMonitor(0, 0).getMonitor();
|
|
}
|
|
|
|
/**
|
|
* Convert monitor coordinates to world coordinates.
|
|
*
|
|
* @param x Absolute X position in monitor coordinates
|
|
* @param y Absolute Y position in monitor coordinates
|
|
* @return The monitor's position.
|
|
*/
|
|
BlockPos toWorldPos(int x, int y) {
|
|
if (xIndex == x && yIndex == y) return getBlockPos();
|
|
return getBlockPos().relative(getRight(), -xIndex + x).relative(getDown(), -yIndex + y);
|
|
}
|
|
|
|
void resize(int width, int height) {
|
|
// If we're not already the origin then we'll need to generate a new terminal.
|
|
if (xIndex != 0 || yIndex != 0) serverMonitor = null;
|
|
|
|
xIndex = 0;
|
|
yIndex = 0;
|
|
this.width = width;
|
|
this.height = height;
|
|
|
|
// Determine if we actually need a monitor. In order to do this, simply check if
|
|
// any component monitor been wrapped as a peripheral. Whilst this flag may be
|
|
// out of date,
|
|
var needsTerminal = false;
|
|
terminalCheck:
|
|
for (var x = 0; x < width; x++) {
|
|
for (var y = 0; y < height; y++) {
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
|
if (monitor != null && monitor.peripheral != null) {
|
|
needsTerminal = true;
|
|
break terminalCheck;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Either delete the current monitor or sync a new one.
|
|
if (needsTerminal) {
|
|
if (serverMonitor == null) serverMonitor = new ServerMonitor(advanced, this);
|
|
|
|
// Update the terminal's width and height and rebuild it. This ensures the monitor
|
|
// is consistent when syncing it to other monitors.
|
|
serverMonitor.rebuild();
|
|
} else {
|
|
// Remove the terminal from the serverMonitor, but keep it around - this ensures that we sync
|
|
// the (now blank) monitor to the client.
|
|
if (serverMonitor != null) serverMonitor.reset();
|
|
}
|
|
|
|
// Update the other monitors, setting coordinates, dimensions and the server terminal
|
|
var pos = getBlockPos();
|
|
Direction down = getDown(), right = getRight();
|
|
for (var x = 0; x < width; x++) {
|
|
for (var y = 0; y < height; y++) {
|
|
var other = getLevel().getBlockEntity(pos.relative(right, x).relative(down, y));
|
|
if (!(other instanceof MonitorBlockEntity monitor) || !isCompatible(monitor)) continue;
|
|
|
|
monitor.xIndex = x;
|
|
monitor.yIndex = y;
|
|
monitor.width = width;
|
|
monitor.height = height;
|
|
monitor.serverMonitor = serverMonitor;
|
|
monitor.needsUpdate = monitor.needsValidating = false;
|
|
monitor.updateBlockState();
|
|
BlockEntityHelpers.updateBlock(monitor);
|
|
}
|
|
}
|
|
|
|
assertInvariant();
|
|
}
|
|
|
|
void updateNeighborsDeferred() {
|
|
needsUpdate = true;
|
|
}
|
|
|
|
void expand() {
|
|
var monitor = getOrigin();
|
|
if (monitor != null && monitor.xIndex == 0 && monitor.yIndex == 0) new Expander(monitor).expand();
|
|
}
|
|
|
|
private void contractNeighbours() {
|
|
if (width == 1 && height == 1) return;
|
|
|
|
var pos = getBlockPos();
|
|
Direction down = getDown(), right = getRight();
|
|
var origin = toWorldPos(0, 0);
|
|
|
|
MonitorBlockEntity toLeft = null, toAbove = null, toRight = null, toBelow = null;
|
|
if (xIndex > 0) toLeft = tryResizeAt(pos.relative(right, -xIndex), xIndex, 1);
|
|
if (yIndex > 0) toAbove = tryResizeAt(origin, width, yIndex);
|
|
if (xIndex < width - 1) toRight = tryResizeAt(pos.relative(right, 1), width - xIndex - 1, 1);
|
|
if (yIndex < height - 1) {
|
|
toBelow = tryResizeAt(origin.relative(down, yIndex + 1), width, height - yIndex - 1);
|
|
}
|
|
|
|
if (toLeft != null) toLeft.expand();
|
|
if (toAbove != null) toAbove.expand();
|
|
if (toRight != null) toRight.expand();
|
|
if (toBelow != null) toBelow.expand();
|
|
}
|
|
|
|
@Nullable
|
|
private MonitorBlockEntity tryResizeAt(BlockPos pos, int width, int height) {
|
|
var tile = getLevel().getBlockEntity(pos);
|
|
if (tile instanceof MonitorBlockEntity monitor && isCompatible(monitor)) {
|
|
monitor.resize(width, height);
|
|
return monitor;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
private boolean checkMonitorAt(int xIndex, int yIndex) {
|
|
var state = getLoadedMonitor(xIndex, yIndex);
|
|
if (state.isMissing()) return false;
|
|
|
|
var monitor = state.getMonitor();
|
|
if (monitor == null) return true;
|
|
|
|
return monitor.xIndex == xIndex && monitor.yIndex == yIndex && monitor.width == width && monitor.height == height;
|
|
}
|
|
|
|
private void validate() {
|
|
if (xIndex == 0 && yIndex == 0 && width == 1 && height == 1) return;
|
|
|
|
if (xIndex >= 0 && xIndex <= width && width > 0 && width <= Config.monitorWidth &&
|
|
yIndex >= 0 && yIndex <= height && height > 0 && height <= Config.monitorHeight &&
|
|
checkMonitorAt(0, 0) && checkMonitorAt(0, height - 1) &&
|
|
checkMonitorAt(width - 1, 0) && checkMonitorAt(width - 1, height - 1)) {
|
|
return;
|
|
}
|
|
|
|
// Something in our monitor is invalid. For now, let's just reset ourselves and then try to integrate ourselves
|
|
// later.
|
|
LOG.warn("Monitor is malformed, resetting to 1x1.");
|
|
resize(1, 1);
|
|
needsUpdate = true;
|
|
}
|
|
// endregion
|
|
|
|
void monitorTouched(float xPos, float yPos, float zPos) {
|
|
if (!advanced) return;
|
|
|
|
var pair = XYPair
|
|
.of(xPos, yPos, zPos, getDirection(), getOrientation())
|
|
.add(xIndex, height - yIndex - 1);
|
|
|
|
if (pair.x() > width - RENDER_BORDER || pair.y() > height - RENDER_BORDER || pair.x() < RENDER_BORDER || pair.y() < RENDER_BORDER) {
|
|
return;
|
|
}
|
|
|
|
var serverTerminal = getServerMonitor();
|
|
if (serverTerminal == null) return;
|
|
|
|
Terminal originTerminal = serverTerminal.getTerminal();
|
|
if (originTerminal == null) return;
|
|
|
|
var xCharWidth = (width - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getWidth();
|
|
var yCharHeight = (height - (RENDER_BORDER + RENDER_MARGIN) * 2.0) / originTerminal.getHeight();
|
|
|
|
var xCharPos = (int) Math.min(originTerminal.getWidth(), Math.max((pair.x() - RENDER_BORDER - RENDER_MARGIN) / xCharWidth + 1.0, 1.0));
|
|
var yCharPos = (int) Math.min(originTerminal.getHeight(), Math.max((pair.y() - RENDER_BORDER - RENDER_MARGIN) / yCharHeight + 1.0, 1.0));
|
|
|
|
eachComputer(c -> c.queueEvent("monitor_touch", c.getAttachmentName(), xCharPos, yCharPos));
|
|
}
|
|
|
|
private void eachComputer(Consumer<IComputerAccess> fun) {
|
|
for (var x = 0; x < width; x++) {
|
|
for (var y = 0; y < height; y++) {
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
|
if (monitor == null) continue;
|
|
|
|
for (var computer : monitor.computers) fun.accept(computer);
|
|
}
|
|
}
|
|
}
|
|
|
|
public IPeripheral peripheral() {
|
|
createServerTerminal();
|
|
var peripheral = this.peripheral != null ? this.peripheral : (this.peripheral = new MonitorPeripheral(this));
|
|
assertInvariant();
|
|
return peripheral;
|
|
}
|
|
|
|
void addComputer(IComputerAccess computer) {
|
|
computers.add(computer);
|
|
}
|
|
|
|
void removeComputer(IComputerAccess computer) {
|
|
computers.remove(computer);
|
|
}
|
|
|
|
@ForgeOverride
|
|
public AABB getRenderBoundingBox() {
|
|
// We attempt to cache the bounding box to save having to do property lookups (and allocations!) on every frame.
|
|
// Unfortunately the AABB does depend on quite a lot of state, so we need to add a bunch of extra fields -
|
|
// ideally these'd be a single object, but I don't think worth doing until Java has value types.
|
|
if (boundingBox != null && getBlockState().equals(bbState) && getBlockPos().equals(bbPos) &&
|
|
xIndex == bbX && yIndex == bbY && width == bbWidth && height == bbHeight) {
|
|
return boundingBox;
|
|
}
|
|
|
|
bbState = getBlockState();
|
|
bbPos = getBlockPos();
|
|
bbX = xIndex;
|
|
bbY = yIndex;
|
|
bbWidth = width;
|
|
bbHeight = height;
|
|
|
|
var startPos = toWorldPos(0, 0);
|
|
var endPos = toWorldPos(width, height);
|
|
return boundingBox = new AABB(
|
|
Math.min(startPos.getX(), endPos.getX()),
|
|
Math.min(startPos.getY(), endPos.getY()),
|
|
Math.min(startPos.getZ(), endPos.getZ()),
|
|
Math.max(startPos.getX(), endPos.getX()) + 1,
|
|
Math.max(startPos.getY(), endPos.getY()) + 1,
|
|
Math.max(startPos.getZ(), endPos.getZ()) + 1
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Assert all {@linkplain #checkInvariants() monitor invariants} hold.
|
|
*/
|
|
private void assertInvariant() {
|
|
assert checkInvariants() : "Monitor invariants failed. See logs.";
|
|
}
|
|
|
|
/**
|
|
* Check various invariants about this monitor multiblock. This is only called when assertions are enabled, so
|
|
* will be skipped outside of tests.
|
|
*
|
|
* @return Whether all invariants passed.
|
|
*/
|
|
private boolean checkInvariants() {
|
|
LOG.debug("Checking monitor invariants at {}", getBlockPos());
|
|
|
|
var okay = true;
|
|
|
|
if (width <= 0 || height <= 0) {
|
|
okay = false;
|
|
LOG.error("Monitor {} has non-positive of {}x{}", getBlockPos(), width, height);
|
|
}
|
|
|
|
var hasPeripheral = false;
|
|
var origin = getOrigin();
|
|
var serverMonitor = origin != null ? origin.serverMonitor : this.serverMonitor;
|
|
for (var x = 0; x < width; x++) {
|
|
for (var y = 0; y < height; y++) {
|
|
var monitor = getLoadedMonitor(x, y).getMonitor();
|
|
if (monitor == null) continue;
|
|
|
|
hasPeripheral |= monitor.peripheral != null;
|
|
|
|
if (monitor.serverMonitor != null && monitor.serverMonitor != serverMonitor) {
|
|
okay = false;
|
|
LOG.error(
|
|
"Monitor {} expected to be have serverMonitor={}, but was {}",
|
|
monitor.getBlockPos(), serverMonitor, monitor.serverMonitor
|
|
);
|
|
}
|
|
|
|
if (monitor.xIndex != x || monitor.yIndex != y) {
|
|
okay = false;
|
|
LOG.error(
|
|
"Monitor {} expected to be at {},{}, but believes it is {},{}",
|
|
monitor.getBlockPos(), x, y, monitor.xIndex, monitor.yIndex
|
|
);
|
|
}
|
|
|
|
if (monitor.width != width || monitor.height != height) {
|
|
okay = false;
|
|
LOG.error(
|
|
"Monitor {} expected to be size {},{}, but believes it is {},{}",
|
|
monitor.getBlockPos(), width, height, monitor.width, monitor.height
|
|
);
|
|
}
|
|
|
|
var expectedState = getBlockState().setValue(MonitorBlock.STATE, MonitorEdgeState.fromConnections(
|
|
y < height - 1, y > 0, x > 0, x < width - 1
|
|
));
|
|
if (monitor.getBlockState() != expectedState) {
|
|
okay = false;
|
|
LOG.error(
|
|
"Monitor {} expected to have state {}, but has state {}",
|
|
monitor.getBlockState(), expectedState, monitor.getBlockState()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasPeripheral != (serverMonitor != null && serverMonitor.getTerminal() != null)) {
|
|
okay = false;
|
|
LOG.error(
|
|
"Peripheral is {}, but serverMonitor={} and serverMonitor.terminal={}",
|
|
hasPeripheral, serverMonitor, serverMonitor == null ? null : serverMonitor.getTerminal()
|
|
);
|
|
}
|
|
|
|
return okay;
|
|
}
|
|
}
|