1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-28 16:22:18 +00:00

fix: another part of syncing with Tweaked codebase

This commit is contained in:
Nikita Savyolov 2021-10-03 22:54:13 +03:00
parent d4f1e34023
commit 861a9e199d
No known key found for this signature in database
GPG Key ID: 32C1EF023AFC184B
24 changed files with 631 additions and 84 deletions

View File

@ -75,6 +75,8 @@ public final class ComputerCraft implements ModInitializer
) ); ) );
public static int httpMaxRequests = 16; public static int httpMaxRequests = 16;
public static int httpMaxWebsockets = 4; 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 boolean enableCommandBlock = false;
public static int modemRange = 64; public static int modemRange = 64;

View File

@ -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<UUID, MoveableSound> 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()
{
}
}
}

View File

@ -62,6 +62,7 @@ public interface IAPIEnvironment
@Nullable @Nullable
IPeripheral getPeripheral( ComputerSide side ); IPeripheral getPeripheral( ComputerSide side );
@Nullable
String getLabel(); String getLabel();
void setLabel( @Nullable String label ); void setLabel( @Nullable String label );

View File

@ -96,9 +96,8 @@ public abstract class HandleGeneric
protected static SeekableByteChannel asSeekable( Channel channel ) protected static SeekableByteChannel asSeekable( Channel channel )
{ {
if( !(channel instanceof SeekableByteChannel) ) return null; if( !(channel instanceof SeekableByteChannel seekable) ) return null;
SeekableByteChannel seekable = (SeekableByteChannel) channel;
try try
{ {
seekable.position( seekable.position() ); seekable.position( seekable.position() );

View File

@ -20,6 +20,8 @@ import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.traffic.AbstractTrafficShapingHandler;
import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
@ -28,9 +30,7 @@ import javax.net.ssl.TrustManagerFactory;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
import java.security.KeyStore; import java.security.KeyStore;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -38,10 +38,8 @@ import java.util.concurrent.TimeUnit;
*/ */
public final class NetworkUtils public final class NetworkUtils
{ {
public static final ExecutorService EXECUTOR = new ThreadPoolExecutor( public static final ScheduledThreadPoolExecutor EXECUTOR = new ScheduledThreadPoolExecutor(
4, Integer.MAX_VALUE, 4,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
ThreadUtils.builder( "Network" ) ThreadUtils.builder( "Network" )
.setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 ) .setPriority( Thread.MIN_PRIORITY + (Thread.NORM_PRIORITY - Thread.MIN_PRIORITY) / 2 )
.build() .build()
@ -52,6 +50,15 @@ public final class NetworkUtils
.build() .build()
); );
public static final AbstractTrafficShapingHandler SHAPING_HANDLER = new GlobalTrafficShapingHandler(
EXECUTOR, ComputerCraft.httpUploadBandwidth, ComputerCraft.httpDownloadBandwidth
);
static
{
EXECUTOR.setKeepAliveTime( 60, TimeUnit.SECONDS );
}
private NetworkUtils() 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. * Note, this may require a DNS lookup, and so should not be executed on the main CC thread.
* *

View File

@ -167,6 +167,7 @@ public class HttpRequest extends Resource<HttpRequest>
} }
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast( NetworkUtils.SHAPING_HANDLER );
if( sslContext != null ) if( sslContext != null )
{ {
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );

View File

@ -100,9 +100,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
{ {
if( closed || request.checkClosed() ) return; if( closed || request.checkClosed() ) return;
if( message instanceof HttpResponse ) if( message instanceof HttpResponse response )
{ {
HttpResponse response = (HttpResponse) message;
if( request.redirects.get() > 0 ) if( request.redirects.get() > 0 )
{ {
@ -137,9 +136,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
responseHeaders.add( response.headers() ); responseHeaders.add( response.headers() );
} }
if( message instanceof HttpContent ) if( message instanceof HttpContent content )
{ {
HttpContent content = (HttpContent) message;
if( responseBody == null ) if( responseBody == null )
{ {
@ -162,9 +160,8 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
responseBody.addComponent( true, partial.retain() ); responseBody.addComponent( true, partial.retain() );
} }
if( message instanceof LastHttpContent ) if( message instanceof LastHttpContent last )
{ {
LastHttpContent last = (LastHttpContent) message;
responseHeaders.add( last.trailingHeaders() ); responseHeaders.add( last.trailingHeaders() );
// Set the content length, if not already given. // Set the content length, if not already given.

View File

@ -22,6 +22,7 @@ import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
@ -145,20 +146,22 @@ public class Websocket extends Resource<Websocket>
protected void initChannel( SocketChannel ch ) protected void initChannel( SocketChannel ch )
{ {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
p.addLast( NetworkUtils.SHAPING_HANDLER );
if( sslContext != null ) if( sslContext != null )
{ {
p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) );
} }
String subprotocol = headers.get( HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL );
WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, headers, uri, WebSocketVersion.V13, subprotocol, true, headers,
options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage
); );
p.addLast( p.addLast(
new HttpClientCodec(), new HttpClientCodec(),
new HttpObjectAggregator( 8192 ), new HttpObjectAggregator( 8192 ),
WebSocketClientCompressionHandler.INSTANCE, WebsocketCompressionHandler.INSTANCE,
new WebsocketHandler( Websocket.this, handshaker, options ) new WebsocketHandler( Websocket.this, handshaker, options )
); );
} }

View File

@ -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 <em>slightly</em> 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 )
);
}
}

View File

@ -55,9 +55,8 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
return; 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 ) + ')' ); throw new IllegalStateException( "Unexpected FullHttpResponse (getStatus=" + response.status() + ", content=" + response.content().toString( CharsetUtil.UTF_8 ) + ')' );
} }
@ -76,9 +75,8 @@ public class WebsocketHandler extends SimpleChannelInboundHandler<Object>
websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length ); websocket.environment().addTrackingChange( TrackingField.WEBSOCKET_INCOMING, converted.length );
websocket.environment().queueEvent( MESSAGE_EVENT, websocket.address(), converted, true ); 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() ); websocket.close( closeFrame.statusCode(), closeFrame.reasonText() );
} }
else if( frame instanceof PingWebSocketFrame ) else if( frame instanceof PingWebSocketFrame )

View File

@ -18,7 +18,7 @@ import static org.objectweb.asm.Opcodes.ICONST_0;
final class Reflect 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() private Reflect()
{ {
@ -52,12 +52,11 @@ final class Reflect
{ {
if( underlying instanceof Class<?> ) return (Class<?>) underlying; if( underlying instanceof Class<?> ) return (Class<?>) underlying;
if( underlying instanceof ParameterizedType ) if( underlying instanceof ParameterizedType type )
{ {
ParameterizedType type = (ParameterizedType) underlying;
if( !allowParameter ) if( !allowParameter )
{ {
for( Type arg : type.getActualTypeArguments() ) for( java.lang.reflect.Type arg : type.getActualTypeArguments() )
{ {
if( arg instanceof WildcardType ) continue; if( arg instanceof WildcardType ) continue;
if( arg instanceof TypeVariable && ((TypeVariable<?>) arg).getName().startsWith( "capture#" ) ) if( arg instanceof TypeVariable && ((TypeVariable<?>) arg).getName().startsWith( "capture#" ) )

View File

@ -79,4 +79,19 @@ public class ClientTerminal implements ITerminal
terminalChanged = true; 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();
}
}
} }

View File

@ -14,6 +14,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import me.shedaniel.cloth.api.utils.v1.GameInstanceUtils;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; 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.Identifier;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import net.minecraft.world.chunk.WorldChunk;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function; import java.util.function.Function;
@ -70,6 +72,10 @@ public final class NetworkHandler
registerMainThread( 12, ComputerDeletedClientMessage.class, ComputerDeletedClientMessage::new ); registerMainThread( 12, ComputerDeletedClientMessage.class, ComputerDeletedClientMessage::new );
registerMainThread( 13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new ); registerMainThread( 13, ComputerTerminalClientMessage.class, ComputerTerminalClientMessage::new );
registerMainThread( 14, PlayRecordClientMessage.class, PlayRecordClientMessage::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 ); registerMainThread( 19, UploadResultMessage.class, UploadResultMessage::new );
} }
@ -105,11 +111,6 @@ public final class NetworkHandler
.getClass(); .getClass();
} }
public static void sendToPlayer( PlayerEntity player, NetworkMessage packet )
{
((ServerPlayerEntity) player).networkHandler.sendPacket( new CustomPayloadS2CPacket( ID, encode( packet ) ) );
}
private static PacketByteBuf encode( NetworkMessage message ) private static PacketByteBuf encode( NetworkMessage message )
{ {
PacketByteBuf buf = new PacketByteBuf( Unpooled.buffer() ); PacketByteBuf buf = new PacketByteBuf( Unpooled.buffer() );
@ -118,6 +119,18 @@ public final class NetworkHandler
return buf; 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 ) public static void sendToAllPlayers( MinecraftServer server, NetworkMessage packet )
{ {
server.getPlayerManager() server.getPlayerManager()
@ -136,4 +149,14 @@ public final class NetworkHandler
.getPlayerManager() .getPlayerManager()
.sendToAround( null, pos.x, pos.y, pos.z, range, world.getRegistryKey(), new CustomPayloadS2CPacket( ID, encode( packet ) ) ); .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 ) ) );
}
}
}
} }

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -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 );
}
}

View File

@ -36,7 +36,7 @@ public class BlockMonitor extends BlockGeneric
static final EnumProperty<MonitorEdgeState> STATE = EnumProperty.of( "state", MonitorEdgeState.class ); static final EnumProperty<MonitorEdgeState> STATE = EnumProperty.of( "state", MonitorEdgeState.class );
public boolean advanced = false; public boolean advanced;
public BlockMonitor( Settings settings, BlockEntityType<? extends TileMonitor> type, boolean advanced ) public BlockMonitor( Settings settings, BlockEntityType<? extends TileMonitor> type, boolean advanced )
{ {
@ -83,9 +83,8 @@ public class BlockMonitor extends BlockGeneric
super.onPlaced( world, pos, blockState, livingEntity, itemStack ); super.onPlaced( world, pos, blockState, livingEntity, itemStack );
BlockEntity entity = world.getBlockEntity( pos ); 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 // Defer the block update if we're being placed by another TE. See #691
if( livingEntity == null || livingEntity instanceof FakePlayer ) if( livingEntity == null || livingEntity instanceof FakePlayer )
{ {

View File

@ -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,
}
}

View File

@ -51,6 +51,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private ClientMonitor clientMonitor; private ClientMonitor clientMonitor;
private MonitorPeripheral peripheral; private MonitorPeripheral peripheral;
private boolean needsUpdate = false; private boolean needsUpdate = false;
private boolean needsValidating = false;
private boolean destroyed = false; private boolean destroyed = false;
private boolean visiting = false; private boolean visiting = false;
private int width = 1; private int width = 1;
@ -124,6 +125,13 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
@Override @Override
public void blockTick() public void blockTick()
{ {
if( needsValidating )
{
needsValidating = false;
validate();
}
if( needsUpdate ) if( needsUpdate )
{ {
needsUpdate = false; needsUpdate = false;
@ -143,7 +151,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
{ {
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TileMonitor monitor = getNeighbour( x, y ); TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) if( monitor == null )
{ {
continue; continue;
@ -170,6 +178,8 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
int oldXIndex = xIndex; int oldXIndex = xIndex;
int oldYIndex = yIndex; int oldYIndex = yIndex;
int oldWidth = width;
int oldHeight = height;
xIndex = nbt.getInt( NBT_X ); xIndex = nbt.getInt( NBT_X );
yIndex = nbt.getInt( NBT_Y ); 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 // 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. // 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; clientMonitor = null;
} }
if( xIndex == 0 && yIndex == 0 ) if( xIndex == 0 && yIndex == 0 )
{ {
// If we're the origin terminal then create it. // 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_Y, yIndex );
nbt.putInt( NBT_WIDTH, width ); nbt.putInt( NBT_WIDTH, width );
nbt.putInt( NBT_HEIGHT, height ); 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(); BlockPos pos = getPos();
Direction right = getRight(); Direction right = getRight();
@ -227,28 +255,27 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite(); return orientation == Direction.DOWN ? getDirection() : getDirection().getOpposite();
} }
private TileMonitor getSimilarMonitorAt( BlockPos pos ) private MonitorState getSimilarMonitorAt( BlockPos pos )
{ {
if( pos.equals( getPos() ) ) if( pos.equals( getPos() ) )
{ {
return this; return MonitorState.present(this);
} }
int y = pos.getY();
World world = getWorld(); World world = getWorld();
if( world == null || !world.isChunkLoaded( pos ) ) if( world == null || !world.isChunkLoaded( pos ) )
{ {
return null; return MonitorState.UNLOADED;
} }
BlockEntity tile = world.getBlockEntity( pos ); BlockEntity tile = world.getBlockEntity( pos );
if( !(tile instanceof TileMonitor) ) if( !(tile instanceof TileMonitor) )
{ {
return null; return MonitorState.MISSING;
} }
TileMonitor monitor = (TileMonitor) tile; 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 // region Sizing and placement stuff
@ -302,6 +329,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
public void cancelRemoval() public void cancelRemoval()
{ {
super.cancelRemoval(); super.cancelRemoval();
needsValidating = true;
TickScheduler.schedule( this ); TickScheduler.schedule( this );
} }
@ -329,7 +357,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return serverMonitor; return serverMonitor;
} }
TileMonitor origin = getOrigin(); TileMonitor origin = getOrigin().getMonitor();
if( origin == null ) if( origin == null )
{ {
return null; return null;
@ -356,7 +384,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
{ {
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TileMonitor monitor = getNeighbour( x, y ); TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor != null ) if( monitor != null )
{ {
monitor.serverMonitor = serverMonitor; monitor.serverMonitor = serverMonitor;
@ -450,7 +478,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return yIndex; return yIndex;
} }
private TileMonitor getOrigin() private MonitorState getOrigin()
{ {
return getNeighbour( 0, 0 ); return getNeighbour( 0, 0 );
} }
@ -477,7 +505,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
{ {
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TileMonitor monitor = getNeighbour( x, y ); TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor != null && monitor.peripheral != null ) if( monitor != null && monitor.peripheral != null )
{ {
needsTerminal = true; needsTerminal = true;
@ -511,7 +539,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
{ {
for( int y = 0; y < height; y++ ) for( int y = 0; y < height; y++ )
{ {
TileMonitor monitor = getNeighbour( x, y ); TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) if( monitor == null )
{ {
continue; continue;
@ -530,7 +558,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private boolean mergeLeft() private boolean mergeLeft()
{ {
TileMonitor left = getNeighbour( -1, 0 ); TileMonitor left = getNeighbour( -1, 0 ).getMonitor();
if( left == null || left.yIndex != 0 || left.height != height ) if( left == null || left.yIndex != 0 || left.height != height )
{ {
return false; return false;
@ -542,7 +570,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return false; return false;
} }
TileMonitor origin = left.getOrigin(); TileMonitor origin = left.getOrigin().getMonitor();
if( origin != null ) if( origin != null )
{ {
origin.resize( width, height ); origin.resize( width, height );
@ -553,7 +581,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private boolean mergeRight() private boolean mergeRight()
{ {
TileMonitor right = getNeighbour( width, 0 ); TileMonitor right = getNeighbour( width, 0 ).getMonitor();
if( right == null || right.yIndex != 0 || right.height != height ) if( right == null || right.yIndex != 0 || right.height != height )
{ {
return false; return false;
@ -565,7 +593,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return false; return false;
} }
TileMonitor origin = getOrigin(); TileMonitor origin = getOrigin().getMonitor();
if( origin != null ) if( origin != null )
{ {
origin.resize( width, height ); origin.resize( width, height );
@ -576,7 +604,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private boolean mergeUp() private boolean mergeUp()
{ {
TileMonitor above = getNeighbour( 0, height ); TileMonitor above = getNeighbour( 0, height ).getMonitor();
if( above == null || above.xIndex != 0 || above.width != width ) if( above == null || above.xIndex != 0 || above.width != width )
{ {
return false; return false;
@ -588,7 +616,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return false; return false;
} }
TileMonitor origin = getOrigin(); TileMonitor origin = getOrigin().getMonitor();
if( origin != null ) if( origin != null )
{ {
origin.resize( width, height ); origin.resize( width, height );
@ -599,7 +627,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
private boolean mergeDown() private boolean mergeDown()
{ {
TileMonitor below = getNeighbour( 0, -1 ); TileMonitor below = getNeighbour( 0, -1 ).getMonitor();
if( below == null || below.xIndex != 0 || below.width != width ) if( below == null || below.xIndex != 0 || below.width != width )
{ {
return false; return false;
@ -611,7 +639,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
return false; return false;
} }
TileMonitor origin = below.getOrigin(); TileMonitor origin = below.getOrigin().getMonitor();
if( origin != null ) if( origin != null )
{ {
origin.resize( width, height ); origin.resize( width, height );
@ -643,7 +671,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
visiting = true; visiting = true;
if( xIndex > 0 ) if( xIndex > 0 )
{ {
TileMonitor left = getNeighbour( xIndex - 1, yIndex ); TileMonitor left = getNeighbour( xIndex - 1, yIndex ).getMonitor();
if( left != null ) if( left != null )
{ {
left.contract(); left.contract();
@ -651,7 +679,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
if( xIndex + 1 < width ) if( xIndex + 1 < width )
{ {
TileMonitor right = getNeighbour( xIndex + 1, yIndex ); TileMonitor right = getNeighbour( xIndex + 1, yIndex ).getMonitor();
if( right != null ) if( right != null )
{ {
right.contract(); right.contract();
@ -659,7 +687,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
if( yIndex > 0 ) if( yIndex > 0 )
{ {
TileMonitor below = getNeighbour( xIndex, yIndex - 1 ); TileMonitor below = getNeighbour( xIndex, yIndex - 1 ).getMonitor();
if( below != null ) if( below != null )
{ {
below.contract(); below.contract();
@ -667,7 +695,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
if( yIndex + 1 < height ) if( yIndex + 1 < height )
{ {
TileMonitor above = getNeighbour( xIndex, yIndex + 1 ); TileMonitor above = getNeighbour( xIndex, yIndex + 1 ).getMonitor();
if( above != null ) if( above != null )
{ {
above.contract(); above.contract();
@ -681,11 +709,11 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
int height = this.height; int height = this.height;
int width = this.width; int width = this.width;
TileMonitor origin = getOrigin(); TileMonitor origin = getOrigin().getMonitor();
if( origin == null ) if( origin == null )
{ {
TileMonitor right = width > 1 ? getNeighbour( 1, 0 ) : null; TileMonitor right = width > 1 ? getNeighbour( 1, 0 ).getMonitor() : null;
TileMonitor below = height > 1 ? getNeighbour( 0, 1 ) : null; TileMonitor below = height > 1 ? getNeighbour( 0, 1 ).getMonitor() : null;
if( right != null ) if( right != null )
{ {
@ -711,7 +739,7 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
{ {
for( int x = 0; x < width; x++ ) for( int x = 0; x < width; x++ )
{ {
TileMonitor monitor = origin.getNeighbour( x, y ); TileMonitor monitor = origin.getNeighbour( x, y ).getMonitor();
if( monitor != null ) if( monitor != null )
{ {
continue; continue;
@ -730,17 +758,17 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
if( x > 0 ) if( x > 0 )
{ {
left = origin.getNeighbour( 0, y ); left = origin.getNeighbour( 0, y ).getMonitor();
left.resize( x, 1 ); left.resize( x, 1 );
} }
if( x + 1 < width ) if( x + 1 < width )
{ {
right = origin.getNeighbour( x + 1, y ); right = origin.getNeighbour( x + 1, y ).getMonitor();
right.resize( width - (x + 1), 1 ); right.resize( width - (x + 1), 1 );
} }
if( y + 1 < height ) if( y + 1 < height )
{ {
below = origin.getNeighbour( 0, y + 1 ); below = origin.getNeighbour( 0, y + 1 ).getMonitor();
below.resize( width, height - (y + 1) ); below.resize( width, height - (y + 1) );
} }
@ -767,6 +795,34 @@ public class TileMonitor extends TileGeneric implements IPeripheralTile
} }
// endregion // 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 ) private void monitorTouched( float xPos, float yPos, float zPos )
{ {
XYPair pair = XYPair.of( xPos, yPos, zPos, getDirection(), getOrientation() ) 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++ ) for( int x = 0; x < width; x++ )
{ {
TileMonitor monitor = getNeighbour( x, y ); TileMonitor monitor = getNeighbour( x, y ).getMonitor();
if( monitor == null ) if( monitor == null )
{ {
continue; continue;

View File

@ -12,17 +12,23 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.fabric.mixin.SoundEventAccess; 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.block.enums.Instrument;
import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket; import net.minecraft.network.packet.s2c.play.PlaySoundIdS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.sound.SoundCategory; import net.minecraft.sound.SoundCategory;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException; 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.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static dan200.computercraft.api.lua.LuaValues.checkFinite; 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 public abstract class SpeakerPeripheral implements IPeripheral
{ {
private static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
private final AtomicInteger notesThisTick = new AtomicInteger(); private final AtomicInteger notesThisTick = new AtomicInteger();
private long clock = 0; private long clock = 0;
private long lastPlayTime = 0; private long lastPlayTime = 0;
private long lastPositionTime;
private Vec3d lastPosition;
public void update() public void update()
{ {
clock++; clock++;
notesThisTick.set( 0 ); 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 ) public boolean madeSound( long ticks )
{ {
return clock - lastPlayTime <= 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 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 // Rate limiting occurs when we've already played a sound within the last tick.
// played more notes than allowable within the current tick. if( !isNote ) return false;
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(); World world = getWorld();
Vec3d pos = getPosition(); Vec3d pos = getPosition();
float actualVolume = MathHelper.clamp( volume, 0.0f, 3.0f );
float range = actualVolume * 16;
context.issueMainThreadTask( () -> { context.issueMainThreadTask( () -> {
MinecraftServer server = world.getServer(); MinecraftServer server = world.getServer();
if( server == null ) if( server == null )
@ -107,15 +141,18 @@ public abstract class SpeakerPeripheral implements IPeripheral
return null; return null;
} }
float adjVolume = Math.min( volume, 3.0f ); if( isNote )
server.getPlayerManager() {
.sendToAround( null, server.getPlayerManager().sendToAround(
pos.x, null, pos.x, pos.y, pos.z, range, world.getRegistryKey(),
pos.y, new PlaySoundIdS2CPacket( name, SoundCategory.RECORDS, pos, actualVolume, pitch )
pos.z, );
adjVolume > 1.0f ? 16 * adjVolume : 16.0, } else {
world.getRegistryKey(), NetworkHandler.sendToAllAround(
new PlaySoundIdS2CPacket( name, SoundCategory.RECORDS, pos, adjVolume, pitch ) ); new SpeakerPlayClientMessage( getSource(), pos, name, actualVolume, pitch ),
world, pos, range
);
}
return null; return null;
} ); } );

View File

@ -19,12 +19,14 @@ import net.minecraft.world.World;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.UUID;
public class TileSpeaker extends TileGeneric implements IPeripheralTile public class TileSpeaker extends TileGeneric implements IPeripheralTile
{ {
public static final int MIN_TICKS_BETWEEN_SOUNDS = 1; public static final int MIN_TICKS_BETWEEN_SOUNDS = 1;
private final SpeakerPeripheral peripheral; private final SpeakerPeripheral peripheral;
private final UUID source = UUID.randomUUID();
public TileSpeaker( BlockEntityType<TileSpeaker> type, BlockPos pos, BlockState state ) public TileSpeaker( BlockEntityType<TileSpeaker> 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() ); return new Vec3d( pos.getX(), pos.getY(), pos.getZ() );
} }
@Override
protected UUID getSource()
{
return speaker.source;
}
@Override @Override
public boolean equals( @Nullable IPeripheral other ) public boolean equals( @Nullable IPeripheral other )
{ {

View File

@ -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 ) );
}
}

View File

@ -8,10 +8,11 @@ package dan200.computercraft.shared.pocket.peripherals;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.minecraft.util.math.Vec3d; import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World; import net.minecraft.world.World;
public class PocketSpeakerPeripheral extends SpeakerPeripheral public class PocketSpeakerPeripheral extends UpgradeSpeakerPeripheral
{ {
private World world = null; private World world = null;
private Vec3d position = Vec3d.ZERO; private Vec3d position = Vec3d.ZERO;

View File

@ -13,6 +13,7 @@ import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.api.turtle.TurtleUpgradeType; import dan200.computercraft.api.turtle.TurtleUpgradeType;
import dan200.computercraft.shared.ComputerCraftRegistry; import dan200.computercraft.shared.ComputerCraftRegistry;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import dan200.computercraft.shared.peripheral.speaker.UpgradeSpeakerPeripheral;
import net.fabricmc.api.EnvType; import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment; import net.fabricmc.api.Environment;
import net.minecraft.client.util.ModelIdentifier; 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; ITurtleAccess turtle;