From 861a9e199d47ffea0f4327aa7fca24fc3195bf7e Mon Sep 17 00:00:00 2001 From: Nikita Savyolov Date: Sun, 3 Oct 2021 22:54:13 +0300 Subject: [PATCH] fix: another part of syncing with Tweaked codebase --- .../dan200/computercraft/ComputerCraft.java | 2 + .../computercraft/client/SoundManager.java | 84 ++++++++++++ .../core/apis/IAPIEnvironment.java | 1 + .../core/apis/handles/HandleGeneric.java | 3 +- .../core/apis/http/NetworkUtils.java | 33 +++-- .../core/apis/http/request/HttpRequest.java | 1 + .../apis/http/request/HttpRequestHandler.java | 9 +- .../core/apis/http/websocket/Websocket.java | 7 +- .../WebsocketCompressionHandler.java | 38 ++++++ .../apis/http/websocket/WebsocketHandler.java | 6 +- .../computercraft/core/asm/Reflect.java | 7 +- .../shared/common/ClientTerminal.java | 15 +++ .../shared/network/NetworkHandler.java | 33 ++++- .../client/SpeakerMoveClientMessage.java | 58 ++++++++ .../client/SpeakerPlayClientMessage.java | 74 ++++++++++ .../client/SpeakerStopClientMessage.java | 51 +++++++ .../peripheral/monitor/BlockMonitor.java | 5 +- .../peripheral/monitor/MonitorState.java | 52 ++++++++ .../peripheral/monitor/TileMonitor.java | 126 +++++++++++++----- .../peripheral/speaker/SpeakerPeripheral.java | 63 +++++++-- .../peripheral/speaker/TileSpeaker.java | 8 ++ .../speaker/UpgradeSpeakerPeripheral.java | 33 +++++ .../peripherals/PocketSpeakerPeripheral.java | 3 +- .../shared/turtle/upgrades/TurtleSpeaker.java | 3 +- 24 files changed, 631 insertions(+), 84 deletions(-) create mode 100644 src/main/java/dan200/computercraft/client/SoundManager.java create mode 100644 src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java create mode 100644 src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java create mode 100644 src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java create mode 100644 src/main/java/dan200/computercraft/shared/network/client/SpeakerStopClientMessage.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/monitor/MonitorState.java create mode 100644 src/main/java/dan200/computercraft/shared/peripheral/speaker/UpgradeSpeakerPeripheral.java diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index ced11128e..b5a4ec75d 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -75,6 +75,8 @@ public final class ComputerCraft implements ModInitializer ) ); public static int httpMaxRequests = 16; public static int httpMaxWebsockets = 4; + public static int httpDownloadBandwidth = 32 * 1024 * 1024; + public static int httpUploadBandwidth = 32 * 1024 * 1024; public static boolean enableCommandBlock = false; public static int modemRange = 64; diff --git a/src/main/java/dan200/computercraft/client/SoundManager.java b/src/main/java/dan200/computercraft/client/SoundManager.java new file mode 100644 index 000000000..b83f89e67 --- /dev/null +++ b/src/main/java/dan200/computercraft/client/SoundManager.java @@ -0,0 +1,84 @@ +/* + * 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.client; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.sound.AbstractSoundInstance; +import net.minecraft.client.sound.SoundInstance; +import net.minecraft.client.sound.TickableSoundInstance; +import net.minecraft.sound.SoundCategory; +import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.Vec3d; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class SoundManager +{ + private static final Map sounds = new HashMap<>(); + + public static void playSound(UUID source, Vec3d position, SoundEvent event, float volume, float pitch ) + { + var soundManager = MinecraftClient.getInstance().getSoundManager(); + + MoveableSound oldSound = sounds.get( source ); + if( oldSound != null ) soundManager.stop( oldSound ); + + MoveableSound newSound = new MoveableSound( event, position, volume, pitch ); + sounds.put( source, newSound ); + soundManager.play( newSound ); + } + + public static void stopSound( UUID source ) + { + SoundInstance sound = sounds.remove( source ); + if( sound == null ) return; + + MinecraftClient.getInstance().getSoundManager().stop( sound ); + } + + public static void moveSound( UUID source, Vec3d position ) + { + MoveableSound sound = sounds.get( source ); + if( sound != null ) sound.setPosition( position ); + } + + public static void reset() + { + sounds.clear(); + } + + private static class MoveableSound extends AbstractSoundInstance implements TickableSoundInstance + { + protected MoveableSound( SoundEvent sound, Vec3d position, float volume, float pitch ) + { + super( sound, SoundCategory.RECORDS ); + setPosition( position ); + this.volume = volume; + this.pitch = pitch; + attenuationType = SoundInstance.AttenuationType.LINEAR; + } + + void setPosition( Vec3d position ) + { + x = (float) position.getX(); + y = (float) position.getY(); + z = (float) position.getZ(); + } + + @Override + public boolean isDone() + { + return false; + } + + @Override + public void tick() + { + } + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java index 25960c580..1f6ffe08a 100644 --- a/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java +++ b/src/main/java/dan200/computercraft/core/apis/IAPIEnvironment.java @@ -62,6 +62,7 @@ public interface IAPIEnvironment @Nullable IPeripheral getPeripheral( ComputerSide side ); + @Nullable String getLabel(); void setLabel( @Nullable String label ); diff --git a/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java index fc1954354..e799c37cd 100644 --- a/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java +++ b/src/main/java/dan200/computercraft/core/apis/handles/HandleGeneric.java @@ -96,9 +96,8 @@ public abstract class HandleGeneric protected static SeekableByteChannel asSeekable( Channel channel ) { - if( !(channel instanceof SeekableByteChannel) ) return null; + if( !(channel instanceof SeekableByteChannel seekable) ) return null; - SeekableByteChannel seekable = (SeekableByteChannel) channel; try { seekable.position( seekable.position() ); diff --git a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java index cbbaf20c5..d5b1b18f2 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -20,6 +20,8 @@ import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.handler.traffic.AbstractTrafficShapingHandler; +import io.netty.handler.traffic.GlobalTrafficShapingHandler; import javax.annotation.Nonnull; import javax.net.ssl.SSLException; @@ -28,9 +30,7 @@ import javax.net.ssl.TrustManagerFactory; import java.net.InetSocketAddress; import java.net.URI; import java.security.KeyStore; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.SynchronousQueue; -import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** @@ -38,10 +38,8 @@ import java.util.concurrent.TimeUnit; */ public final class NetworkUtils { - public static final ExecutorService EXECUTOR = new ThreadPoolExecutor( - 4, Integer.MAX_VALUE, - 60L, TimeUnit.SECONDS, - new SynchronousQueue<>(), + public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor( + 4, ThreadUtils.builder( "Network" ) .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) .build() @@ -52,6 +50,15 @@ public final class NetworkUtils .build() ); + public static final AbstractTrafficShapingHandler SHAPING_HANDLER = new GlobalTrafficShapingHandler( + EXECUTOR, ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth + ); + + static + { + EXECUTOR.setKeepAliveTime( 60, TimeUnit.SECONDS ); + } + private NetworkUtils() { } @@ -107,8 +114,18 @@ public final class NetworkUtils } } + public static void reloadConfig() + { + SHAPING_HANDLER.configure( ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth ); + } + + public static void reset() + { + SHAPING_HANDLER.trafficCounter().resetCumulativeTime(); + } + /** - * Create a {@link InetSocketAddress} from a {@link URI}. + * Create a {@link InetSocketAddress} from a {@link java.net.URI}. * * Note, this may require a DNS lookup, and so should not be executed on the main CC thread. * diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java index 2049575ff..74957b204 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java +++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java @@ -167,6 +167,7 @@ public class HttpRequest extends Resource } ChannelPipeline p = ch.pipeline(); + p.addLast( NetworkUtils.SHAPING_HANDLER ); if( sslContext != null ) { p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java index 4ef42ff44..003b3da5f 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java +++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java @@ -100,9 +100,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler 0 ) { @@ -137,9 +136,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler protected void initChannel( SocketChannel ch ) { ChannelPipeline p = ch.pipeline(); + p.addLast( NetworkUtils.SHAPING_HANDLER ); if( sslContext != null ) { p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); } + String subprotocol = headers.get( HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL ); WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( - uri, WebSocketVersion.V13, null, true, headers, + uri, WebSocketVersion.V13, subprotocol, true, headers, options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage ); p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), - WebSocketClientCompressionHandler.INSTANCE, + WebsocketCompressionHandler.INSTANCE, new WebsocketHandler( Websocket.this, handshaker, options ) ); } diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java new file mode 100644 index 000000000..e29a87837 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketCompressionHandler.java @@ -0,0 +1,38 @@ +/* + * 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.core.apis.http.websocket; + +import io.netty.channel.ChannelHandler; +import io.netty.handler.codec.compression.ZlibCodecFactory; +import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; +import io.netty.handler.codec.http.websocketx.extensions.compression.DeflateFrameClientExtensionHandshaker; +import io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateClientExtensionHandshaker; +import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler; + +import static io.netty.handler.codec.http.websocketx.extensions.compression.PerMessageDeflateServerExtensionHandshaker.MAX_WINDOW_SIZE; + +/** + * An alternative to {@link WebSocketClientCompressionHandler} which supports the {@literal client_no_context_takeover} + * extension. Makes CC slightly more flexible. + */ +@ChannelHandler.Sharable +final class WebsocketCompressionHandler extends WebSocketClientExtensionHandler +{ + public static final WebsocketCompressionHandler INSTANCE = new WebsocketCompressionHandler(); + + private WebsocketCompressionHandler() + { + super( + new PerMessageDeflateClientExtensionHandshaker( + 6, ZlibCodecFactory.isSupportingWindowSizeAndMemLevel(), MAX_WINDOW_SIZE, + true, false + ), + new DeflateFrameClientExtensionHandshaker( false ), + new DeflateFrameClientExtensionHandshaker( true ) + ); + + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java index 4916f5fbd..25001e19c 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java @@ -55,9 +55,8 @@ public class WebsocketHandler extends SimpleChannelInboundHandler return; } - if( msg instanceof FullHttpResponse ) + if( msg instanceof FullHttpResponse response ) { - FullHttpResponse response = (FullHttpResponse) msg; throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString( CharsetUtil.UTF_8 ) + ')' ); } @@ -76,9 +75,8 @@ public class WebsocketHandler extends SimpleChannelInboundHandler websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length ); websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), converted, true ); } - else if( frame instanceof CloseWebSocketFrame ) + else if( frame instanceof CloseWebSocketFrame closeFrame ) { - CloseWebSocketFrame closeFrame = (CloseWebSocketFrame) frame; websocket.close( closeFrame.statusCode(), closeFrame.reasonText() ); } else if( frame instanceof PingWebSocketFrame ) diff --git a/src/main/java/dan200/computercraft/core/asm/Reflect.java b/src/main/java/dan200/computercraft/core/asm/Reflect.java index 5504f3bc8..57a624683 100644 --- a/src/main/java/dan200/computercraft/core/asm/Reflect.java +++ b/src/main/java/dan200/computercraft/core/asm/Reflect.java @@ -18,7 +18,7 @@ import static org.objectweb.asm.Opcodes.ICONST_0; final class Reflect { - static final Type OPTIONAL_IN = Optional.class.getTypeParameters()[0]; + static final java.lang.reflect.Type OPTIONAL_IN = Optional.class.getTypeParameters()[0]; private Reflect() { @@ -52,12 +52,11 @@ final class Reflect { if( underlying instanceof Class ) return (Class) underlying; - if( underlying instanceof ParameterizedType ) + if( underlying instanceof ParameterizedType type ) { - ParameterizedType type = (ParameterizedType) underlying; if( !allowParameter ) { - for( Type arg : type.getActualTypeArguments() ) + for( java.lang.reflect.Type arg : type.getActualTypeArguments() ) { if( arg instanceof WildcardType ) continue; if( arg instanceof TypeVariable && ((TypeVariable) arg).getName().startsWith( "capture#" ) ) diff --git a/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java b/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java index 29749af6e..ae0433e11 100644 --- a/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java +++ b/src/main/java/dan200/computercraft/shared/common/ClientTerminal.java @@ -79,4 +79,19 @@ public class ClientTerminal implements ITerminal terminalChanged = true; } } + + public void readDescription( NbtCompound nbt ) + { + colour = nbt.getBoolean( "colour" ); + if( nbt.contains( "terminal" ) ) + { + NbtCompound terminal = nbt.getCompound( "terminal" ); + resizeTerminal( terminal.getInt( "term_width" ), terminal.getInt( "term_height" ) ); + this.terminal.readFromNBT( terminal ); + } + else + { + deleteTerminal(); + } + } } diff --git a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java index cf74f8300..5b0af2829 100644 --- a/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java +++ b/src/main/java/dan200/computercraft/shared/network/NetworkHandler.java @@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import me.shedaniel.cloth.api.utils.v1.GameInstanceUtils; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; @@ -30,6 +31,7 @@ import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.util.Identifier; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; +import net.minecraft.world.chunk.WorldChunk; import java.util.function.BiConsumer; import java.util.function.Function; @@ -70,6 +72,10 @@ public final class NetworkHandler registerMainThread( 12, ComputerDeletedClientMessage.class, ComputerDeletedClientMessage::new ); registerMainThread( 13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new ); registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::new ); + registerMainThread( 15, MonitorClientMessage.class, MonitorClientMessage::new ); + registerMainThread( 16, SpeakerPlayClientMessage.class, SpeakerPlayClientMessage::new ); + registerMainThread( 17, SpeakerStopClientMessage.class, SpeakerStopClientMessage::new ); + registerMainThread( 18, SpeakerMoveClientMessage.class, SpeakerMoveClientMessage::new ); registerMainThread( 19, UploadResultMessage.class, UploadResultMessage::new ); } @@ -105,11 +111,6 @@ public final class NetworkHandler .getClass(); } - public static void sendToPlayer( PlayerEntity player, NetworkMessage packet ) - { - ((ServerPlayerEntity) player).networkHandler.sendPacket( new CustomPayloadS2CPacket( ID, encode( packet ) ) ); - } - private static PacketByteBuf encode( NetworkMessage message ) { PacketByteBuf buf = new PacketByteBuf( Unpooled.buffer() ); @@ -118,6 +119,18 @@ public final class NetworkHandler return buf; } + public static void sendToPlayer( PlayerEntity player, NetworkMessage packet ) + { + ((ServerPlayerEntity) player).networkHandler.sendPacket( new CustomPayloadS2CPacket( ID, encode( packet ) ) ); + } + + public static void sendToAllPlayers( NetworkMessage packet ) + { + MinecraftServer server = GameInstanceUtils.getServer(); + server.getPlayerManager() + .sendToAll( new CustomPayloadS2CPacket( ID, encode( packet ) ) ); + } + public static void sendToAllPlayers( MinecraftServer server, NetworkMessage packet ) { server.getPlayerManager() @@ -136,4 +149,14 @@ public final class NetworkHandler .getPlayerManager() .sendToAround( null, pos.x, pos.y, pos.z, range, world.getRegistryKey(), new CustomPayloadS2CPacket( ID, encode( packet ) ) ); } + + public static void sendToAllTracking( NetworkMessage packet, WorldChunk chunk ) + { + // maybe bug with worlds + for(PlayerEntity player : chunk.getWorld().getPlayers()) { + if (player.getChunkPos().equals(chunk.getPos())) { + ((ServerPlayerEntity) player).networkHandler.sendPacket( new CustomPayloadS2CPacket( ID, encode( packet ) ) ); + } + } + } } diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java new file mode 100644 index 000000000..9bfe74a03 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerMoveClientMessage.java @@ -0,0 +1,58 @@ +/* + * 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.network.client; + +import dan200.computercraft.client.SoundManager; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.util.math.Vec3d; + +import javax.annotation.Nonnull; +import java.util.UUID; + +/** + * Starts a sound on the client. + * + * Used by speakers to play sounds. + * + * @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker + */ +public class SpeakerMoveClientMessage implements NetworkMessage +{ + private final UUID source; + private final Vec3d pos; + + public SpeakerMoveClientMessage( UUID source, Vec3d pos ) + { + this.source = source; + this.pos = pos; + } + + public SpeakerMoveClientMessage( PacketByteBuf buf ) + { + source = buf.readUuid(); + pos = new Vec3d( buf.readDouble(), buf.readDouble(), buf.readDouble() ); + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeUuid( source ); + buf.writeDouble( pos.getX() ); + buf.writeDouble( pos.getY() ); + buf.writeDouble( pos.getZ() ); + } + + @Override + @Environment( EnvType.CLIENT ) + public void handle( PacketContext context ) + { + SoundManager.moveSound( source, pos ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java new file mode 100644 index 000000000..32b0169d6 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerPlayClientMessage.java @@ -0,0 +1,74 @@ +/* + * 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.network.client; + +import dan200.computercraft.client.SoundManager; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.sound.SoundEvent; +import net.minecraft.sound.SoundEvents; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.Vec3d; + +import javax.annotation.Nonnull; +import java.util.UUID; + +/** + * Starts a sound on the client. + * + * Used by speakers to play sounds. + * + * @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker + */ +public class SpeakerPlayClientMessage implements NetworkMessage +{ + private final UUID source; + private final Vec3d pos; + private final Identifier sound; + private final float volume; + private final float pitch; + + public SpeakerPlayClientMessage( UUID source, Vec3d pos, Identifier event, float volume, float pitch ) + { + this.source = source; + this.pos = pos; + sound = event; + this.volume = volume; + this.pitch = pitch; + } + + public SpeakerPlayClientMessage( PacketByteBuf buf ) + { + source = buf.readUuid(); + pos = new Vec3d( buf.readDouble(), buf.readDouble(), buf.readDouble() ); + sound = buf.readIdentifier(); + volume = buf.readFloat(); + pitch = buf.readFloat(); + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeUuid( source ); + buf.writeDouble( pos.getX() ); + buf.writeDouble( pos.getY() ); + buf.writeDouble( pos.getZ() ); + buf.writeIdentifier( sound ); + buf.writeFloat( volume ); + buf.writeFloat( pitch ); + } + + @Override + @Environment( EnvType.CLIENT ) + public void handle( PacketContext context ) + { + SoundEvent sound = new SoundEvent(this.sound); + SoundManager.playSound( source, pos, sound, volume, pitch ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/network/client/SpeakerStopClientMessage.java b/src/main/java/dan200/computercraft/shared/network/client/SpeakerStopClientMessage.java new file mode 100644 index 000000000..d322928a6 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/network/client/SpeakerStopClientMessage.java @@ -0,0 +1,51 @@ +/* + * 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.network.client; + +import dan200.computercraft.client.SoundManager; +import dan200.computercraft.shared.network.NetworkMessage; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.PacketContext; +import net.minecraft.network.PacketByteBuf; + +import javax.annotation.Nonnull; +import java.util.UUID; + +/** + * Stops a sound on the client + * + * Called when a speaker is broken. + * + * @see dan200.computercraft.shared.peripheral.speaker.TileSpeaker + */ +public class SpeakerStopClientMessage implements NetworkMessage +{ + private final UUID source; + + public SpeakerStopClientMessage( UUID source ) + { + this.source = source; + } + + public SpeakerStopClientMessage( PacketByteBuf buf ) + { + source = buf.readUuid(); + } + + @Override + public void toBytes( @Nonnull PacketByteBuf buf ) + { + buf.writeUuid( source ); + } + + @Override + @Environment(EnvType.CLIENT) + public void handle( PacketContext context ) + { + SoundManager.stopSound( source ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java index 837d5276e..0373c5ad1 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/BlockMonitor.java @@ -36,7 +36,7 @@ public class BlockMonitor extends BlockGeneric static final EnumProperty STATE = EnumProperty.of( "state", MonitorEdgeState.class ); - public boolean advanced = false; + public boolean advanced; public BlockMonitor( Settings settings, BlockEntityType type, boolean advanced ) { @@ -83,9 +83,8 @@ public class BlockMonitor extends BlockGeneric super.onPlaced( world, pos, blockState, livingEntity, itemStack ); BlockEntity entity = world.getBlockEntity( pos ); - if( entity instanceof TileMonitor && !world.isClient ) + if( entity instanceof TileMonitor monitor && !world.isClient ) { - TileMonitor monitor = (TileMonitor) entity; // Defer the block update if we're being placed by another TE. See #691 if( livingEntity == null || livingEntity instanceof FakePlayer ) { 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 e4b263ca1..e521e2f08 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/monitor/TileMonitor.java @@ -51,6 +51,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile private ClientMonitor clientMonitor; private MonitorPeripheral peripheral; private boolean needsUpdate = false; + private boolean needsValidating = false; private boolean destroyed = false; private boolean visiting = false; private int width = 1; @@ -124,6 +125,13 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile @Override public void blockTick() { + + if( needsValidating ) + { + needsValidating = false; + validate(); + } + if( needsUpdate ) { needsUpdate = false; @@ -143,7 +151,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) { continue; @@ -170,6 +178,8 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile int oldXIndex = xIndex; int oldYIndex = yIndex; + int oldWidth = width; + int oldHeight = height; xIndex = nbt.getInt( NBT_X ); yIndex = nbt.getInt( NBT_Y ); @@ -180,14 +190,27 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { // If our index has changed then it's possible the origin monitor has changed. Thus // we'll clear our cache. If we're the origin then we'll need to remove the glList as well. - if( oldXIndex == 0 && oldYIndex == 0 && clientMonitor != null ) clientMonitor.destroy(); + if( oldXIndex == 0 && oldYIndex == 0 && clientMonitor != null ) + { + clientMonitor.destroy(); + } clientMonitor = null; } if( xIndex == 0 && yIndex == 0 ) { // If we're the origin terminal then create it. - if( clientMonitor == null ) clientMonitor = new ClientMonitor( advanced, this ); + if( clientMonitor == null ) + { + clientMonitor = new ClientMonitor( advanced, this ); + } + clientMonitor.readDescription( nbt ); + } + + if( oldXIndex != xIndex || oldYIndex != yIndex || oldWidth != width || oldHeight != height ) + { + // One of our properties has changed, so ensure we redraw the block + updateBlock(); } } @@ -199,9 +222,14 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile nbt.putInt( NBT_Y, yIndex ); nbt.putInt( NBT_WIDTH, width ); nbt.putInt( NBT_HEIGHT, height ); + + if( xIndex == 0 && yIndex == 0 && serverMonitor != null ) + { + serverMonitor.writeDescription( nbt ); + } } - private TileMonitor getNeighbour( int x, int y ) + private MonitorState getNeighbour( int x, int y ) { BlockPos pos = getPos(); Direction right = getRight(); @@ -227,28 +255,27 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite(); } - private TileMonitor getSimilarMonitorAt( BlockPos pos ) + private MonitorState getSimilarMonitorAt( BlockPos pos ) { if( pos.equals( getPos() ) ) { - return this; + return MonitorState.present(this); } - int y = pos.getY(); World world = getWorld(); if( world == null || !world.isChunkLoaded( pos ) ) { - return null; + return MonitorState.UNLOADED; } BlockEntity tile = world.getBlockEntity( pos ); if( !(tile instanceof TileMonitor) ) { - return null; + return MonitorState.MISSING; } TileMonitor monitor = (TileMonitor) tile; - return !monitor.visiting && !monitor.destroyed && advanced == monitor.advanced && getDirection() == monitor.getDirection() && getOrientation() == monitor.getOrientation() ? monitor : null; + return !monitor.visiting && !monitor.destroyed && advanced == monitor.advanced && getDirection() == monitor.getDirection() && getOrientation() == monitor.getOrientation() ? MonitorState.present( monitor ) : MonitorState.MISSING; } // region Sizing and placement stuff @@ -302,6 +329,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile public void cancelRemoval() { super.cancelRemoval(); + needsValidating = true; TickScheduler.schedule( this ); } @@ -329,7 +357,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return serverMonitor; } - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin == null ) { return null; @@ -356,7 +384,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor != null ) { monitor.serverMonitor = serverMonitor; @@ -450,7 +478,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return yIndex; } - private TileMonitor getOrigin() + private MonitorState getOrigin() { return getNeighbour( 0, 0 ); } @@ -477,7 +505,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { 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; @@ -511,7 +539,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { for( int y = 0; y < height; y++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) { continue; @@ -530,7 +558,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile 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; @@ -542,7 +570,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return false; } - TileMonitor origin = left.getOrigin(); + TileMonitor origin = left.getOrigin().getMonitor(); if( origin != null ) { origin.resize( width, height ); @@ -553,7 +581,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile 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; @@ -565,7 +593,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return false; } - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin != null ) { origin.resize( width, height ); @@ -576,7 +604,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile 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; @@ -588,7 +616,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return false; } - TileMonitor origin = getOrigin(); + TileMonitor origin = getOrigin().getMonitor(); if( origin != null ) { origin.resize( width, height ); @@ -599,7 +627,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile 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; @@ -611,7 +639,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile return false; } - TileMonitor origin = below.getOrigin(); + TileMonitor origin = below.getOrigin().getMonitor(); if( origin != null ) { origin.resize( width, height ); @@ -643,7 +671,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile visiting = true; if( xIndex > 0 ) { - TileMonitor left = getNeighbour( xIndex - 1, yIndex ); + TileMonitor left = getNeighbour( xIndex - 1, yIndex ).getMonitor(); if( left != null ) { left.contract(); @@ -651,7 +679,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } if( xIndex + 1 < width ) { - TileMonitor right = getNeighbour( xIndex + 1, yIndex ); + TileMonitor right = getNeighbour( xIndex + 1, yIndex ).getMonitor(); if( right != null ) { right.contract(); @@ -659,7 +687,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } if( yIndex > 0 ) { - TileMonitor below = getNeighbour( xIndex, yIndex - 1 ); + TileMonitor below = getNeighbour( xIndex, yIndex - 1 ).getMonitor(); if( below != null ) { below.contract(); @@ -667,7 +695,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } if( yIndex + 1 < height ) { - TileMonitor above = getNeighbour( xIndex, yIndex + 1 ); + TileMonitor above = getNeighbour( xIndex, yIndex + 1 ).getMonitor(); if( above != null ) { above.contract(); @@ -681,11 +709,11 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile 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 ) { @@ -711,7 +739,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { for( int x = 0; x < width; x++ ) { - TileMonitor monitor = origin.getNeighbour( x, y ); + TileMonitor monitor = origin.getNeighbour( x, y ).getMonitor(); if( monitor != null ) { continue; @@ -730,17 +758,17 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } 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) ); } @@ -767,6 +795,34 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile } // endregion + private boolean checkMonitorAt( int xIndex, int yIndex ) + { + MonitorState state = getNeighbour( xIndex, 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. + ComputerCraft.log.warn( "Monitor is malformed, resetting to 1x1." ); + resize( 1, 1 ); + needsUpdate = true; + } + private void monitorTouched( float xPos, float yPos, float zPos ) { XYPair pair = XYPair.of( xPos, yPos, zPos, getDirection(), getOrientation() ) @@ -799,7 +855,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile { for( int x = 0; x < width; x++ ) { - TileMonitor monitor = getNeighbour( x, y ); + TileMonitor monitor = getNeighbour( x, y ).getMonitor(); if( monitor == null ) { continue; diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java index c44653cbd..433b6ceec 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/SpeakerPeripheral.java @@ -12,17 +12,23 @@ import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.fabric.mixin.SoundEventAccess; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage; +import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; import net.minecraft.block.enums.Instrument; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.sound.SoundCategory; import net.minecraft.util.Identifier; import net.minecraft.util.InvalidIdentifierException; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import javax.annotation.Nonnull; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import static dan200.computercraft.api.lua.LuaValues.checkFinite; @@ -34,16 +40,40 @@ import static dan200.computercraft.api.lua.LuaValues.checkFinite; */ public abstract class SpeakerPeripheral implements IPeripheral { + private static final int MIN_TICKS_BETWEEN_SOUNDS = 1; + private final AtomicInteger notesThisTick = new AtomicInteger(); private long clock = 0; private long lastPlayTime = 0; + private long lastPositionTime; + private Vec3d lastPosition; + public void update() { clock++; notesThisTick.set( 0 ); + + // Push position updates to any speakers which have ever played a note, + // have moved by a non-trivial amount and haven't had a position update + // in the last second. + if( lastPlayTime > 0 && (clock - lastPositionTime) >= 20 ) + { + Vec3d position = getPosition(); + if( lastPosition == null || lastPosition.distanceTo( position ) >= 0.1 ) + { + lastPosition = position; + lastPositionTime = clock; + NetworkHandler.sendToAllTracking( + new SpeakerMoveClientMessage( getSource(), position ), + getWorld().getWorldChunk( new BlockPos( position ) ) + ); + } + } } + protected abstract UUID getSource(); + public boolean madeSound( long ticks ) { return clock - lastPlayTime <= ticks; @@ -90,16 +120,20 @@ public abstract class SpeakerPeripheral implements IPeripheral private synchronized boolean playSound( ILuaContext context, Identifier name, float volume, float pitch, boolean isNote ) throws LuaException { - if( clock - lastPlayTime < TileSpeaker.MIN_TICKS_BETWEEN_SOUNDS && (!isNote || clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick) ) + if( clock - lastPlayTime < MIN_TICKS_BETWEEN_SOUNDS ) { - // Rate limiting occurs when we've already played a sound within the last tick, or we've - // played more notes than allowable within the current tick. - return false; + // Rate limiting occurs when we've already played a sound within the last tick. + if( !isNote ) return false; + // Or we've played more notes than allowable within the current tick. + if( clock - lastPlayTime != 0 || notesThisTick.get() >= ComputerCraft.maxNotesPerTick ) return false; } World world = getWorld(); Vec3d pos = getPosition(); + float actualVolume = MathHelper.clamp( volume, 0.0f, 3.0f ); + float range = actualVolume * 16; + context.issueMainThreadTask( () -> { MinecraftServer server = world.getServer(); if( server == null ) @@ -107,15 +141,18 @@ public abstract class SpeakerPeripheral implements IPeripheral return null; } - float adjVolume = Math.min( volume, 3.0f ); - server.getPlayerManager() - .sendToAround( null, - pos.x, - pos.y, - pos.z, - adjVolume > 1.0f ? 16 * adjVolume : 16.0, - world.getRegistryKey(), - new PlaySoundIdS2CPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch ) ); + if( isNote ) + { + server.getPlayerManager().sendToAround( + null, pos.x, pos.y, pos.z, range, world.getRegistryKey(), + new PlaySoundIdS2CPacket( name, SoundCategory.RECORDS, pos, actualVolume, pitch ) + ); + } else { + NetworkHandler.sendToAllAround( + new SpeakerPlayClientMessage( getSource(), pos, name, actualVolume, pitch ), + world, pos, range + ); + } return null; } ); diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java index 8cb853567..8df1eee47 100644 --- a/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/TileSpeaker.java @@ -19,12 +19,14 @@ import net.minecraft.world.World; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.util.UUID; public class TileSpeaker extends TileGeneric implements IPeripheralTile { public static final int MIN_TICKS_BETWEEN_SOUNDS = 1; private final SpeakerPeripheral peripheral; + private final UUID source = UUID.randomUUID(); public TileSpeaker( BlockEntityType type, BlockPos pos, BlockState state ) { @@ -66,6 +68,12 @@ public class TileSpeaker extends TileGeneric implements IPeripheralTile return new Vec3d( pos.getX(), pos.getY(), pos.getZ() ); } + @Override + protected UUID getSource() + { + return speaker.source; + } + @Override public boolean equals( @Nullable IPeripheral other ) { diff --git a/src/main/java/dan200/computercraft/shared/peripheral/speaker/UpgradeSpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/peripheral/speaker/UpgradeSpeakerPeripheral.java new file mode 100644 index 000000000..cbe318845 --- /dev/null +++ b/src/main/java/dan200/computercraft/shared/peripheral/speaker/UpgradeSpeakerPeripheral.java @@ -0,0 +1,33 @@ +/* + * 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.speaker; + +import dan200.computercraft.api.peripheral.IComputerAccess; +import dan200.computercraft.shared.network.NetworkHandler; +import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; + +import javax.annotation.Nonnull; +import java.util.UUID; + +/** + * A speaker peripheral which is used on an upgrade, and so is only attached to one computer. + */ +public abstract class UpgradeSpeakerPeripheral extends SpeakerPeripheral +{ + private final UUID source = UUID.randomUUID(); + + @Override + protected final UUID getSource() + { + return source; + } + + @Override + public void detach( @Nonnull IComputerAccess computer ) + { + NetworkHandler.sendToAllPlayers( new SpeakerStopClientMessage( source ) ); + } +} diff --git a/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java b/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java index dcec95bb9..0583edbb2 100644 --- a/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java +++ b/src/main/java/dan200/computercraft/shared/pocket/peripherals/PocketSpeakerPeripheral.java @@ -8,10 +8,11 @@ package dan200.computercraft.shared.pocket.peripherals; import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; +import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; -public class PocketSpeakerPeripheral extends SpeakerPeripheral +public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral { private World world = null; private Vec3d position = Vec3d.ZERO; diff --git a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java index 3fbc845ca..d0ecbf7f9 100644 --- a/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java +++ b/src/main/java/dan200/computercraft/shared/turtle/upgrades/TurtleSpeaker.java @@ -13,6 +13,7 @@ import dan200.computercraft.api.turtle.TurtleSide; import dan200.computercraft.api.turtle.TurtleUpgradeType; import dan200.computercraft.shared.ComputerCraftRegistry; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; +import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.util.ModelIdentifier; @@ -71,7 +72,7 @@ public class TurtleSpeaker extends AbstractTurtleUpgrade } } - private static class Peripheral extends SpeakerPeripheral + private static class Peripheral extends UpgradeSpeakerPeripheral { ITurtleAccess turtle;