diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java new file mode 100644 index 000000000..791c425ad --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java @@ -0,0 +1,52 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2021. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ +package dan200.computercraft.shared.peripheral.monitor; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class MonitorState +{ + public static final MonitorState UNLOADED = new MonitorState( State.UNLOADED, null ); + public static final MonitorState MISSING = new MonitorState( State.MISSING, null ); + + private final State state; + private final TileMonitor monitor; + + private MonitorState( @Nonnull State state, @Nullable TileMonitor monitor ) + { + this.state = state; + this.monitor = monitor; + } + + public static MonitorState present( @Nonnull TileMonitor monitor ) + { + return new MonitorState( State.PRESENT, monitor ); + } + + public boolean isPresent() + { + return state == State.PRESENT; + } + + public boolean isMissing() + { + return state == State.MISSING; + } + + @Nullable + public TileMonitor getMonitor() + { + return monitor; + } + + enum State + { + UNLOADED, + MISSING, + PRESENT, + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java index 0fc154e0e..637fd81e3 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java @@ -56,6 +56,7 @@ public class TileMonitor extends TileGeneric private final Set computers = new HashSet<>(); private boolean needsUpdate = false; + private boolean needsValidating = false; private boolean destroyed = false; private boolean visiting = false; @@ -78,6 +79,7 @@ public TileMonitor( TileEntityType type, boolean advanced public void onLoad() { super.onLoad(); + needsValidating = true; TickScheduler.schedule( this ); } @@ -148,6 +150,12 @@ public void load( @Nonnull CompoundNBT tag ) @Override public void blockTick() { + if( needsValidating ) + { + needsValidating = false; + validate(); + } + if( needsUpdate ) { needsUpdate = false; @@ -164,7 +172,7 @@ public void blockTick() { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) continue; for( IComputerAccess computer : monitor.computers ) @@ -208,7 +216,7 @@ private ServerMonitor getServerMonitor() { if( serverMonitor != null ) return serverMonitor; - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin == null ) return null; return serverMonitor = origin.serverMonitor; @@ -229,7 +237,7 @@ private ServerMonitor createServerMonitor() { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor != null ) monitor.serverMonitor = serverMonitor; } } @@ -374,24 +382,24 @@ public int getYIndex() return yIndex; } - private TileMonitor getSimilarMonitorAt( BlockPos pos ) + @Nonnull + private MonitorState getSimilarMonitorAt( BlockPos pos ) { - if( pos.equals( getBlockPos() ) ) return this; + if( pos.equals( getBlockPos() ) ) return MonitorState.present( this ); - int y = pos.getY(); World world = getLevel(); - if( world == null || !world.isAreaLoaded( pos, 0 ) ) return null; + if( world == null || !world.isAreaLoaded( pos, 0 ) ) return MonitorState.UNLOADED; TileEntity tile = world.getBlockEntity( pos ); - if( !(tile instanceof TileMonitor) ) return null; + if( !(tile instanceof TileMonitor) ) return MonitorState.MISSING; TileMonitor monitor = (TileMonitor) tile; return !monitor.visiting && !monitor.destroyed && advanced == monitor.advanced && getDirection() == monitor.getDirection() && getOrientation() == monitor.getOrientation() - ? monitor : null; + ? MonitorState.present( monitor ) : MonitorState.MISSING; } - private TileMonitor getNeighbour( int x, int y ) + private MonitorState getNeighbour( int x, int y ) { BlockPos pos = getBlockPos(); Direction right = getRight(); @@ -401,7 +409,7 @@ private TileMonitor getNeighbour( int x, int y ) return getSimilarMonitorAt( pos.relative( right, xOffset ).relative( down, yOffset ) ); } - private TileMonitor getOrigin() + private MonitorState getOrigin() { return getNeighbour( 0, 0 ); } @@ -425,7 +433,7 @@ private void resize( int width, int height ) { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor != null && monitor.peripheral != null ) { needsTerminal = true; @@ -453,7 +461,7 @@ private void resize( int width, int height ) { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) continue; monitor.xIndex = x; @@ -469,13 +477,13 @@ private void resize( int width, int height ) private boolean mergeLeft() { - TileMonitor left = getNeighbour( -1, 0 ); + TileMonitor left = getNeighbour( -1, 0 ).getMonitor(); if( left == null || left.yIndex != 0 || left.height != height ) return false; int width = left.width + this.width; if( width > ComputerCraft.monitorWidth ) return false; - TileMonitor origin = left.getOrigin(); + TileMonitor origin = left.getOrigin().getMonitor(); if( origin != null ) origin.resize( width, height ); left.expand(); return true; @@ -483,13 +491,13 @@ private boolean mergeLeft() private boolean mergeRight() { - TileMonitor right = getNeighbour( width, 0 ); + TileMonitor right = getNeighbour( width, 0 ).getMonitor(); if( right == null || right.yIndex != 0 || right.height != height ) return false; int width = this.width + right.width; if( width > ComputerCraft.monitorWidth ) return false; - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin != null ) origin.resize( width, height ); expand(); return true; @@ -497,13 +505,13 @@ private boolean mergeRight() private boolean mergeUp() { - TileMonitor above = getNeighbour( 0, height ); + TileMonitor above = getNeighbour( 0, height ).getMonitor(); if( above == null || above.xIndex != 0 || above.width != width ) return false; int height = above.height + this.height; if( height > ComputerCraft.monitorHeight ) return false; - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin != null ) origin.resize( width, height ); expand(); return true; @@ -511,13 +519,13 @@ private boolean mergeUp() private boolean mergeDown() { - TileMonitor below = getNeighbour( 0, -1 ); + TileMonitor below = getNeighbour( 0, -1 ).getMonitor(); if( below == null || below.xIndex != 0 || below.width != width ) return false; int height = this.height + below.height; if( height > ComputerCraft.monitorHeight ) return false; - TileMonitor origin = below.getOrigin(); + TileMonitor origin = below.getOrigin().getMonitor(); if( origin != null ) origin.resize( width, height ); below.expand(); return true; @@ -546,22 +554,22 @@ void contractNeighbours() visiting = true; if( xIndex > 0 ) { - TileMonitor left = getNeighbour( xIndex - 1, yIndex ); + TileMonitor left = getNeighbour( xIndex - 1, yIndex ).getMonitor(); if( left != null ) left.contract(); } if( xIndex + 1 < width ) { - TileMonitor right = getNeighbour( xIndex + 1, yIndex ); + TileMonitor right = getNeighbour( xIndex + 1, yIndex ).getMonitor(); if( right != null ) right.contract(); } if( yIndex > 0 ) { - TileMonitor below = getNeighbour( xIndex, yIndex - 1 ); + TileMonitor below = getNeighbour( xIndex, yIndex - 1 ).getMonitor(); if( below != null ) below.contract(); } if( yIndex + 1 < height ) { - TileMonitor above = getNeighbour( xIndex, yIndex + 1 ); + TileMonitor above = getNeighbour( xIndex, yIndex + 1 ).getMonitor(); if( above != null ) above.contract(); } visiting = false; @@ -572,11 +580,11 @@ void contract() int height = this.height; int width = this.width; - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin == null ) { - TileMonitor right = width > 1 ? getNeighbour( 1, 0 ) : null; - TileMonitor below = height > 1 ? getNeighbour( 0, 1 ) : null; + TileMonitor right = width > 1 ? getNeighbour( 1, 0 ).getMonitor() : null; + TileMonitor below = height > 1 ? getNeighbour( 0, 1 ).getMonitor() : null; if( right != null ) right.resize( width - 1, 1 ); if( below != null ) below.resize( width, height - 1 ); @@ -590,7 +598,7 @@ void contract() { for( int x = 0; x < width; x++ ) { - TileMonitor monitor = origin.getNeighbour( x, y ); + TileMonitor monitor = origin.getNeighbour( x, y ).getMonitor(); if( monitor != null ) continue; // Decompose @@ -606,17 +614,17 @@ void contract() } if( x > 0 ) { - left = origin.getNeighbour( 0, y ); + left = origin.getNeighbour( 0, y ).getMonitor(); left.resize( x, 1 ); } if( x + 1 < width ) { - right = origin.getNeighbour( x + 1, y ); + right = origin.getNeighbour( x + 1, y ).getMonitor(); right.resize( width - (x + 1), 1 ); } if( y + 1 < height ) { - below = origin.getNeighbour( 0, y + 1 ); + below = origin.getNeighbour( 0, y + 1 ).getMonitor(); below.resize( width, height - (y + 1) ); } @@ -630,6 +638,38 @@ void contract() } } + private boolean checkMonitorAt( int xIndex, int yIndex ) + { + BlockPos pos = getBlockPos(); + Direction right = getRight(); + Direction down = getDown(); + + MonitorState state = getSimilarMonitorAt( pos.relative( right, xIndex ).relative( down, yIndex ) ); + + if( state.isMissing() ) return false; + + TileMonitor 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( 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. + resize( 1, 1 ); + needsUpdate = true; + } + private void monitorTouched( float xPos, float yPos, float zPos ) { XYPair pair = XYPair @@ -657,7 +697,7 @@ private void monitorTouched( float xPos, float yPos, float zPos ) { for( int x = 0; x < width; x++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) continue; for( IComputerAccess computer : monitor.computers ) @@ -683,8 +723,8 @@ void removeComputer( IComputerAccess computer ) @Override public AxisAlignedBB getRenderBoundingBox() { - TileMonitor start = getNeighbour( 0, 0 ); - TileMonitor end = getNeighbour( width - 1, height - 1 ); + TileMonitor start = getNeighbour( 0, 0 ).getMonitor(); + TileMonitor end = getNeighbour( width - 1, height - 1 ).getMonitor(); if( start != null && end != null ) { BlockPos startPos = start.getBlockPos(); diff --git a/src/test/java/dan200/computercraft/ingame/MonitorTest.kt b/src/test/java/dan200/computercraft/ingame/MonitorTest.kt new file mode 100644 index 000000000..6954f2522 --- /dev/null +++ b/src/test/java/dan200/computercraft/ingame/MonitorTest.kt @@ -0,0 +1,39 @@ +package dan200.computercraft.ingame + +import dan200.computercraft.ingame.api.* +import dan200.computercraft.shared.Registry +import dan200.computercraft.shared.peripheral.monitor.TileMonitor +import net.minecraft.block.Blocks +import net.minecraft.command.arguments.BlockStateInput +import net.minecraft.nbt.CompoundNBT +import net.minecraft.util.math.BlockPos +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.fail +import java.util.* + +class MonitorTest { + @GameTest + suspend fun `Ensures valid on place`(context: TestContext) { + val pos = BlockPos(2, 0, 2) + val tag = CompoundNBT() + tag.putInt("Width", 2) + tag.putInt("Height", 2) + + val toSet = BlockStateInput( + Registry.ModBlocks.MONITOR_ADVANCED.get().defaultBlockState(), + Collections.emptySet(), + tag + ) + + context.setBlock(pos, Blocks.AIR.defaultBlockState()) + context.setBlock(pos, toSet) + + context.sleep(2) + + val tile = context.getTile(pos) + if (tile !is TileMonitor) fail("Expected tile to be monitor, is $tile") + + assertEquals(1, tile.width, "Width should be 1") + assertEquals(1, tile.height, "Width should be 1") + } +} diff --git a/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt b/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt index aca550400..bac096f40 100644 --- a/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt +++ b/src/test/java/dan200/computercraft/ingame/api/TestExtensions.kt @@ -3,6 +3,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import net.minecraft.block.BlockState +import net.minecraft.command.arguments.BlockStateInput import net.minecraft.entity.Entity import net.minecraft.tileentity.TileEntity import net.minecraft.util.math.AxisAlignedBB @@ -54,6 +55,11 @@ tracker.level.setBlockAndUpdate(offset(pos), state) } +/** + * Set a block within the test structure. + */ +fun TestContext.setBlock(pos: BlockPos, state: BlockStateInput) = state.place(tracker.level, offset(pos), 3) + /** * Modify a block state within the test. */ diff --git a/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt b/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt new file mode 100644 index 000000000..eef6627f5 --- /dev/null +++ b/src/test/server-files/structures/monitor_test.ensures_valid_on_place.snbt @@ -0,0 +1,515 @@ +{ + size: [5, 5, 5], + entities: [], + blocks: [ + { + pos: [0, 0, 0], + state: 0 + }, + { + pos: [1, 0, 0], + state: 0 + }, + { + pos: [2, 0, 0], + state: 0 + }, + { + pos: [3, 0, 0], + state: 0 + }, + { + pos: [4, 0, 0], + state: 0 + }, + { + pos: [0, 0, 1], + state: 0 + }, + { + pos: [1, 0, 1], + state: 0 + }, + { + pos: [2, 0, 1], + state: 0 + }, + { + pos: [3, 0, 1], + state: 0 + }, + { + pos: [4, 0, 1], + state: 0 + }, + { + pos: [0, 0, 2], + state: 0 + }, + { + pos: [1, 0, 2], + state: 0 + }, + { + pos: [2, 0, 2], + state: 0 + }, + { + pos: [3, 0, 2], + state: 0 + }, + { + pos: [4, 0, 2], + state: 0 + }, + { + pos: [0, 0, 3], + state: 0 + }, + { + pos: [1, 0, 3], + state: 0 + }, + { + pos: [2, 0, 3], + state: 0 + }, + { + pos: [3, 0, 3], + state: 0 + }, + { + pos: [4, 0, 3], + state: 0 + }, + { + pos: [0, 0, 4], + state: 0 + }, + { + pos: [1, 0, 4], + state: 0 + }, + { + pos: [2, 0, 4], + state: 0 + }, + { + pos: [3, 0, 4], + state: 0 + }, + { + pos: [4, 0, 4], + state: 0 + }, + { + pos: [0, 1, 0], + state: 1 + }, + { + pos: [1, 1, 0], + state: 1 + }, + { + pos: [2, 1, 0], + state: 1 + }, + { + pos: [3, 1, 0], + state: 1 + }, + { + pos: [4, 1, 0], + state: 1 + }, + { + pos: [0, 2, 0], + state: 1 + }, + { + pos: [1, 2, 0], + state: 1 + }, + { + pos: [2, 2, 0], + state: 1 + }, + { + pos: [3, 2, 0], + state: 1 + }, + { + pos: [4, 2, 0], + state: 1 + }, + { + pos: [0, 3, 0], + state: 1 + }, + { + pos: [1, 3, 0], + state: 1 + }, + { + pos: [2, 3, 0], + state: 1 + }, + { + pos: [3, 3, 0], + state: 1 + }, + { + pos: [4, 3, 0], + state: 1 + }, + { + pos: [0, 4, 0], + state: 1 + }, + { + pos: [1, 4, 0], + state: 1 + }, + { + pos: [2, 4, 0], + state: 1 + }, + { + pos: [3, 4, 0], + state: 1 + }, + { + pos: [4, 4, 0], + state: 1 + }, + { + pos: [0, 1, 1], + state: 1 + }, + { + pos: [1, 1, 1], + state: 1 + }, + { + pos: [2, 1, 1], + state: 1 + }, + { + pos: [3, 1, 1], + state: 1 + }, + { + pos: [4, 1, 1], + state: 1 + }, + { + pos: [0, 2, 1], + state: 1 + }, + { + pos: [1, 2, 1], + state: 1 + }, + { + pos: [2, 2, 1], + state: 1 + }, + { + pos: [3, 2, 1], + state: 1 + }, + { + pos: [4, 2, 1], + state: 1 + }, + { + pos: [0, 3, 1], + state: 1 + }, + { + pos: [1, 3, 1], + state: 1 + }, + { + pos: [2, 3, 1], + state: 1 + }, + { + pos: [3, 3, 1], + state: 1 + }, + { + pos: [4, 3, 1], + state: 1 + }, + { + pos: [0, 4, 1], + state: 1 + }, + { + pos: [1, 4, 1], + state: 1 + }, + { + pos: [2, 4, 1], + state: 1 + }, + { + pos: [3, 4, 1], + state: 1 + }, + { + pos: [4, 4, 1], + state: 1 + }, + { + pos: [0, 1, 2], + state: 1 + }, + { + pos: [1, 1, 2], + state: 1 + }, + { + pos: [2, 1, 2], + state: 1 + }, + { + pos: [3, 1, 2], + state: 1 + }, + { + pos: [4, 1, 2], + state: 1 + }, + { + pos: [0, 2, 2], + state: 1 + }, + { + pos: [1, 2, 2], + state: 1 + }, + { + pos: [2, 2, 2], + state: 1 + }, + { + pos: [3, 2, 2], + state: 1 + }, + { + pos: [4, 2, 2], + state: 1 + }, + { + pos: [0, 3, 2], + state: 1 + }, + { + pos: [1, 3, 2], + state: 1 + }, + { + pos: [2, 3, 2], + state: 1 + }, + { + pos: [3, 3, 2], + state: 1 + }, + { + pos: [4, 3, 2], + state: 1 + }, + { + pos: [0, 4, 2], + state: 1 + }, + { + pos: [1, 4, 2], + state: 1 + }, + { + pos: [2, 4, 2], + state: 1 + }, + { + pos: [3, 4, 2], + state: 1 + }, + { + pos: [4, 4, 2], + state: 1 + }, + { + pos: [0, 1, 3], + state: 1 + }, + { + pos: [1, 1, 3], + state: 1 + }, + { + pos: [2, 1, 3], + state: 1 + }, + { + pos: [3, 1, 3], + state: 1 + }, + { + pos: [4, 1, 3], + state: 1 + }, + { + pos: [0, 2, 3], + state: 1 + }, + { + pos: [1, 2, 3], + state: 1 + }, + { + pos: [2, 2, 3], + state: 1 + }, + { + pos: [3, 2, 3], + state: 1 + }, + { + pos: [4, 2, 3], + state: 1 + }, + { + pos: [0, 3, 3], + state: 1 + }, + { + pos: [1, 3, 3], + state: 1 + }, + { + pos: [2, 3, 3], + state: 1 + }, + { + pos: [3, 3, 3], + state: 1 + }, + { + pos: [4, 3, 3], + state: 1 + }, + { + pos: [0, 4, 3], + state: 1 + }, + { + pos: [1, 4, 3], + state: 1 + }, + { + pos: [2, 4, 3], + state: 1 + }, + { + pos: [3, 4, 3], + state: 1 + }, + { + pos: [4, 4, 3], + state: 1 + }, + { + pos: [0, 1, 4], + state: 1 + }, + { + pos: [1, 1, 4], + state: 1 + }, + { + pos: [2, 1, 4], + state: 1 + }, + { + pos: [3, 1, 4], + state: 1 + }, + { + pos: [4, 1, 4], + state: 1 + }, + { + pos: [0, 2, 4], + state: 1 + }, + { + pos: [1, 2, 4], + state: 1 + }, + { + pos: [2, 2, 4], + state: 1 + }, + { + pos: [3, 2, 4], + state: 1 + }, + { + pos: [4, 2, 4], + state: 1 + }, + { + pos: [0, 3, 4], + state: 1 + }, + { + pos: [1, 3, 4], + state: 1 + }, + { + pos: [2, 3, 4], + state: 1 + }, + { + pos: [3, 3, 4], + state: 1 + }, + { + pos: [4, 3, 4], + state: 1 + }, + { + pos: [0, 4, 4], + state: 1 + }, + { + pos: [1, 4, 4], + state: 1 + }, + { + pos: [2, 4, 4], + state: 1 + }, + { + pos: [3, 4, 4], + state: 1 + }, + { + pos: [4, 4, 4], + state: 1 + } + ], + palette: [ + { + Name: "minecraft:polished_andesite" + }, + { + Name: "minecraft:air" + } + ], + DataVersion: 2230 +}