mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-30 21:23:00 +00:00 
			
		
		
		
	Merge branch 'mc-1.16.x' into mc-1.17.x
This commit is contained in:
		| @@ -5,9 +5,8 @@ | ||||
|  */ | ||||
| package dan200.computercraft.api.lua; | ||||
| 
 | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import static dan200.computercraft.api.lua.LuaValues.*; | ||||
|   | ||||
| @@ -115,7 +115,9 @@ class DfpwmStream implements AudioStream | ||||
|         } | ||||
| 
 | ||||
|         result.flip(); | ||||
|         return result; | ||||
| 
 | ||||
|         // This is naughty, but ensures we're not enqueuing empty buffers when the stream is exhausted. | ||||
|         return result.remaining() == 0 ? null : result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @@ -123,4 +125,9 @@ class DfpwmStream implements AudioStream | ||||
|     { | ||||
|         buffers.clear(); | ||||
|     } | ||||
| 
 | ||||
|     public boolean isEmpty() | ||||
|     { | ||||
|         return buffers.isEmpty(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -27,8 +27,20 @@ public class SpeakerInstance | ||||
| 
 | ||||
|     public synchronized void pushAudio( ByteBuf buffer ) | ||||
|     { | ||||
|         if( currentStream == null ) currentStream = new DfpwmStream(); | ||||
|         SpeakerSound sound = this.sound; | ||||
| 
 | ||||
|         DfpwmStream stream = currentStream; | ||||
|         if( stream == null ) stream = currentStream = new DfpwmStream(); | ||||
|         boolean exhausted = stream.isEmpty(); | ||||
|         currentStream.push( buffer ); | ||||
| 
 | ||||
|         // If we've got nothing left in the buffer, enqueue an additional one just in case. | ||||
|         if( exhausted && sound != null && sound.stream == stream && sound.channel != null ) | ||||
|         { | ||||
|             sound.executor.execute( () -> { | ||||
|                 if( !sound.channel.stopped() ) sound.channel.pumpBuffers( 1 ); | ||||
|             } ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void playAudio( Vec3 position, float volume ) | ||||
|   | ||||
| @@ -26,12 +26,14 @@ public class SpeakerManager | ||||
|     @SubscribeEvent | ||||
|     public static void playStreaming( PlayStreamingSourceEvent event ) | ||||
|     { | ||||
|         if( !(event.getSound() instanceof SpeakerSound) ) return; | ||||
|         SpeakerSound sound = (SpeakerSound) event.getSound(); | ||||
|         if( !(event.getSound() instanceof SpeakerSound sound) ) return; | ||||
|         if( sound.stream == null ) return; | ||||
| 
 | ||||
|         event.getSource().attachBufferStream( sound.stream ); | ||||
|         event.getSource().play(); | ||||
| 
 | ||||
|         sound.channel = event.getSource(); | ||||
|         sound.executor = event.getManager().executor; | ||||
|     } | ||||
| 
 | ||||
|     public static SpeakerInstance getSound( UUID source ) | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
|  */ | ||||
| package dan200.computercraft.client.sound; | ||||
| 
 | ||||
| import com.mojang.blaze3d.audio.Channel; | ||||
| import net.minecraft.client.resources.sounds.AbstractSoundInstance; | ||||
| import net.minecraft.client.resources.sounds.TickableSoundInstance; | ||||
| import net.minecraft.client.sounds.AudioStream; | ||||
| @@ -13,9 +14,12 @@ import net.minecraft.sounds.SoundSource; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| 
 | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.concurrent.Executor; | ||||
| 
 | ||||
| public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance | ||||
| { | ||||
|     Channel channel; | ||||
|     Executor executor; | ||||
|     DfpwmStream stream; | ||||
| 
 | ||||
|     SpeakerSound( ResourceLocation sound, DfpwmStream stream, Vec3 position, float volume, float pitch ) | ||||
|   | ||||
| @@ -30,7 +30,36 @@ import java.util.OptionalLong; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| /** | ||||
|  * The FS API allows you to manipulate files and the filesystem. | ||||
|  * The FS API provides access to the computer's files and filesystem, allowing you to manipulate files, directories and | ||||
|  * paths. This includes: | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li>**Reading and writing files:** Call {@link #open} to obtain a file "handle", which can be used to read from or | ||||
|  * write to a file.</li> | ||||
|  * <li>**Path manipulation:** {@link #combine}, {@link #getName} and {@link #getDir} allow you to manipulate file | ||||
|  * paths, joining them together or extracting components.</li> | ||||
|  * <li>**Querying paths:** For instance, checking if a file exists, or whether it's a directory. See {@link #getSize}, | ||||
|  * {@link #exists}, {@link #isDir}, {@link #isReadOnly} and {@link #attributes}.</li> | ||||
|  * <li>**File and directory manipulation:** For instance, moving or copying files. See {@link #makeDir}, {@link #move}, | ||||
|  * {@link #copy} and {@link #delete}.</li> | ||||
|  * </ul> | ||||
|  * | ||||
|  * :::note | ||||
|  * All functions in the API work on absolute paths, and do not take the @{shell.dir|current directory} into account. | ||||
|  * You can use @{shell.resolve} to convert a relative path into an absolute one. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Mounts | ||||
|  * While a computer can only have one hard drive and filesystem, other filesystems may be "mounted" inside it. For | ||||
|  * instance, the {@link dan200.computercraft.shared.peripheral.diskdrive.DiskDrivePeripheral drive peripheral} mounts | ||||
|  * its disk's contents at {@code "disk/"}, {@code "disk1/"}, etc... | ||||
|  * | ||||
|  * You can see which mount a path belongs to with the {@link #getDrive} function. This returns {@code "hdd"} for the | ||||
|  * computer's main filesystem ({@code "/"}), {@code "rom"} for the rom ({@code "rom/"}). | ||||
|  * | ||||
|  * Most filesystems have a limited capacity, operations which would cause that capacity to be reached (such as writing | ||||
|  * an incredibly large file) will fail. You can see a mount's capacity with {@link #getCapacity} and the remaining | ||||
|  * space with {@link #getFreeSpace}. | ||||
|  * | ||||
|  * @cc.module fs | ||||
|  */ | ||||
| @@ -440,6 +469,12 @@ public class FSAPI implements ILuaAPI | ||||
|      * @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. | ||||
|      * @throws LuaException If the path doesn't exist. | ||||
|      * @cc.treturn string The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. | ||||
|      * @cc.usage Print the drives of a couple of mounts: | ||||
|      * | ||||
|      * <pre>{@code | ||||
|      * print("/: " .. fs.getDrive("/")) | ||||
|      * print("/rom/: " .. fs.getDrive("rom")) | ||||
|      * }</pre> | ||||
|      */ | ||||
|     @LuaFunction | ||||
|     public final Object[] getDrive( String path ) throws LuaException | ||||
|   | ||||
| @@ -22,7 +22,7 @@ import dan200.computercraft.core.computer.ComputerSide; | ||||
|  * as those from Project:Red. These allow you to send 16 separate on/off signals. Each channel corresponds to a | ||||
|  * colour, with the first being @{colors.white} and the last @{colors.black}. | ||||
|  * | ||||
|  * Whenever a redstone input changes, a {@code redstone} event will be fired. This may be used instead of repeativly | ||||
|  * Whenever a redstone input changes, a @{event!redstone} event will be fired. This may be used instead of repeativly | ||||
|  * polling. | ||||
|  * | ||||
|  * This module may also be referred to as {@code rs}. For example, one may call {@code rs.getSides()} instead of | ||||
|   | ||||
| @@ -19,10 +19,10 @@ import javax.annotation.Nonnull; | ||||
|  * | ||||
|  * This works with energy storage blocks, as well as generators and machines which consume energy. | ||||
|  * | ||||
|  * <blockquote> | ||||
|  * <strong>Note:</strong> Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF | ||||
|  * :::note | ||||
|  * Due to limitations with Forge's energy API, it is not possible to measure throughput (i.e. RF | ||||
|  * used/generated per tick). | ||||
|  * </blockquote> | ||||
|  * ::: | ||||
|  * | ||||
|  * @cc.module energy_storage | ||||
|  */ | ||||
|   | ||||
| @@ -21,9 +21,60 @@ import java.util.HashSet; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| /** | ||||
|  * The modem peripheral allows you to send messages between computers. | ||||
|  * Modems allow you to send messages between computers over long distances. | ||||
|  * | ||||
|  * :::tip | ||||
|  * Modems provide a fairly basic set of methods, which makes them very flexible but often hard to work with. The | ||||
|  * {@literal @}{rednet} API is built on top of modems, and provides a more user-friendly interface. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Sending and receiving messages | ||||
|  * Modems operate on a series of channels, a bit like frequencies on a radio. Any modem can send a message on a | ||||
|  * particular channel, but only those which have {@link #open opened} the channel and are "listening in" can receive | ||||
|  * messages. | ||||
|  * | ||||
|  * Channels are represented as an integer between 0 and 65535 inclusive. These channels don't have any defined meaning, | ||||
|  * though some APIs or programs will assign a meaning to them. For instance, the @{gps} module sends all its messages on | ||||
|  * channel 65534 (@{gps.CHANNEL_GPS}), while @{rednet} uses channels equal to the computer's ID. | ||||
|  * | ||||
|  * - Sending messages is done with the {@link #transmit(int, int, Object)} message. | ||||
|  * - Receiving messages is done by listening to the @{modem_message} event. | ||||
|  * | ||||
|  * ## Types of modem | ||||
|  * CC: Tweaked comes with three kinds of modem, with different capabilities. | ||||
|  * | ||||
|  * <ul> | ||||
|  * <li><strong>Wireless modems:</strong> Wireless modems can send messages to any other wireless modem. They can be placed next to a | ||||
|  * computer, or equipped as a pocket computer or turtle upgrade. | ||||
|  * | ||||
|  * Wireless modems have a limited range, only sending messages to modems within 64 blocks. This range increases | ||||
|  * linearly once the modem is above y=96, to a maximum of 384 at world height.</li> | ||||
|  * <li><strong>Ender modems:</strong> These are upgraded versions of normal wireless modems. They do not have a distance | ||||
|  * limit, and can send messages between dimensions.</li> | ||||
|  * <li><strong>Wired modems:</strong> These send messages to other any other wired modems connected to the same network | ||||
|  * (using <em>Networking Cable</em>). They also can be used to attach additional peripherals to a computer.</li></ul> | ||||
|  * | ||||
|  * @cc.module modem | ||||
|  * @cc.see modem_message Queued when a modem receives a message on an {@link #open(int) open channel}. | ||||
|  * @cc.see rednet A networking API built on top of the modem peripheral. | ||||
|  * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. Then wait for a message to | ||||
|  * arrive on channel 43 and print it. | ||||
|  * | ||||
|  * <pre>{@code | ||||
|  * local modem = peripheral.find("modem") or error("No modem attached", 0) | ||||
|  * modem.open(43) -- Open 43 so we can receive replies | ||||
|  * | ||||
|  * -- Send our message | ||||
|  * modem.transmit(15, 43, "Hello, world!") | ||||
|  * | ||||
|  * -- And wait for a reply | ||||
|  * local event, side, channel, replyChannel, message, distance | ||||
|  * repeat | ||||
|  *   event, side, channel, replyChannel, message, distance = os.pullEvent("modem_message") | ||||
|  * until channel == 43 | ||||
|  * | ||||
|  * print("Received a reply: " .. tostring(message)) | ||||
|  * }</pre> | ||||
|  */ | ||||
| public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPacketReceiver | ||||
| { | ||||
| @@ -157,12 +208,23 @@ public abstract class ModemPeripheral implements IPeripheral, IPacketSender, IPa | ||||
|      * Sends a modem message on a certain channel. Modems listening on the channel will queue a {@code modem_message} | ||||
|      * event on adjacent computers. | ||||
|      * | ||||
|      * <blockquote><strong>Note:</strong> The channel does not need be open to send a message.</blockquote> | ||||
|      * :::note | ||||
|      * The channel does not need be open to send a message. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param channel      The channel to send messages on. | ||||
|      * @param replyChannel The channel that responses to this message should be sent on. | ||||
|      * @param payload      The object to send. This can be a boolean, string, number, or table. | ||||
|      * @param replyChannel The channel that responses to this message should be sent on. This can be the same as | ||||
|      *                     {@code channel} or entirely different. The channel must have been {@link #open opened} on | ||||
|      *                     the sending computer in order to receive the replies. | ||||
|      * @param payload      The object to send. This can be any primitive type (boolean, number, string) as well as | ||||
|      *                     tables. Other types (like functions), as well as metatables, will not be transmitted. | ||||
|      * @throws LuaException If the channel is out of range. | ||||
|      * @cc.usage Wrap a modem and a message on channel 15, requesting a response on channel 43. | ||||
|      * | ||||
|      * <pre>{@code | ||||
|      * local modem = peripheral.find("modem") or error("No modem attached", 0) | ||||
|      * modem.transmit(15, 43, "Hello, world!") | ||||
|      * }</pre> | ||||
|      */ | ||||
|     @LuaFunction | ||||
|     public final void transmit( int channel, int replyChannel, Object payload ) throws LuaException | ||||
|   | ||||
| @@ -80,8 +80,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|      * If this computer is attached to the network, it _will not_ be included in | ||||
|      * this list. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @return Remote peripheral names on the network. | ||||
| @@ -95,8 +96,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Determine if a peripheral is available on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -112,8 +114,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Get the type of a peripheral is available on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -132,8 +135,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Check a peripheral is of a particular type. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -153,8 +157,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Get all available methods for the remote peripheral with the given name. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer The calling computer. | ||||
|      * @param name     The peripheral's name. | ||||
| @@ -174,8 +179,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|     /** | ||||
|      * Call a method on a peripheral on this wired network. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @param computer  The calling computer. | ||||
|      * @param context   The Lua context we're executing in. | ||||
| @@ -204,8 +210,9 @@ public abstract class WiredModemPeripheral extends ModemPeripheral implements IW | ||||
|      * may be used by other computers on the network to wrap this computer as a | ||||
|      * peripheral. | ||||
|      * | ||||
|      * <blockquote><strong>Important:</strong> This function only appears on wired modems. Check {@link #isWireless} | ||||
|      * returns false before calling it.</blockquote> | ||||
|      * :::note | ||||
|      * This function only appears on wired modems. Check {@link #isWireless} returns false before calling it. | ||||
|      * ::: | ||||
|      * | ||||
|      * @return The current computer's name. | ||||
|      * @cc.treturn string|nil The current computer's name on the wired network. | ||||
|   | ||||
| @@ -7,6 +7,7 @@ package dan200.computercraft.shared.peripheral.speaker; | ||||
| 
 | ||||
| import dan200.computercraft.api.lua.LuaException; | ||||
| import dan200.computercraft.api.lua.LuaTable; | ||||
| import dan200.computercraft.shared.util.PauseAwareTimer; | ||||
| import net.minecraft.util.Mth; | ||||
| 
 | ||||
| import javax.annotation.Nonnull; | ||||
| @@ -27,7 +28,7 @@ class DfpwmState | ||||
|      * The minimum size of the client's audio buffer. Once we have less than this on the client, we should send another | ||||
|      * batch of audio. | ||||
|      */ | ||||
|     private static final long CLIENT_BUFFER = (long) (SECOND * 1.5); | ||||
|     private static final long CLIENT_BUFFER = (long) (SECOND * 0.5); | ||||
| 
 | ||||
|     private static final int PREC = 10; | ||||
| 
 | ||||
| @@ -36,7 +37,7 @@ class DfpwmState | ||||
|     private boolean previousBit = false; | ||||
| 
 | ||||
|     private boolean unplayed = true; | ||||
|     private long clientEndTime = System.nanoTime(); | ||||
|     private long clientEndTime = PauseAwareTimer.getTime(); | ||||
|     private float pendingVolume = 1.0f; | ||||
|     private ByteBuffer pendingAudio; | ||||
| 
 | ||||
| @@ -107,7 +108,7 @@ class DfpwmState | ||||
| 
 | ||||
|     boolean isPlaying() | ||||
|     { | ||||
|         return unplayed || clientEndTime >= System.nanoTime(); | ||||
|         return unplayed || clientEndTime >= PauseAwareTimer.getTime(); | ||||
|     } | ||||
| 
 | ||||
|     float getVolume() | ||||
|   | ||||
| @@ -17,6 +17,7 @@ import dan200.computercraft.shared.network.client.SpeakerAudioClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerMoveClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerPlayClientMessage; | ||||
| import dan200.computercraft.shared.network.client.SpeakerStopClientMessage; | ||||
| import dan200.computercraft.shared.util.PauseAwareTimer; | ||||
| import net.minecraft.ResourceLocationException; | ||||
| import net.minecraft.core.BlockPos; | ||||
| import net.minecraft.network.protocol.game.ClientboundCustomSoundPacket; | ||||
| @@ -119,7 +120,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         long now = System.nanoTime(); | ||||
|         long now = PauseAwareTimer.getTime(); | ||||
|         if( sound != null ) | ||||
|         { | ||||
|             lastPlayTime = clock; | ||||
| @@ -342,7 +343,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|         // TODO: Use ArgumentHelpers instead? | ||||
|         int length = audio.length(); | ||||
|         if( length <= 0 ) throw new LuaException( "Cannot play empty audio" ); | ||||
|         if( length > 1024 * 16 * 8 ) throw new LuaException( "Audio data is too large" ); | ||||
|         if( length > 128 * 1024 ) throw new LuaException( "Audio data is too large" ); | ||||
| 
 | ||||
|         DfpwmState state; | ||||
|         synchronized( lock ) | ||||
| @@ -387,17 +388,7 @@ public abstract class SpeakerPeripheral implements IPeripheral | ||||
|         computers.remove( computer ); | ||||
|     } | ||||
| 
 | ||||
|     private static final class PendingSound | ||||
|     private record PendingSound(ResourceLocation location, float volume, float pitch) | ||||
|     { | ||||
|         final ResourceLocation location; | ||||
|         final float volume; | ||||
|         final float pitch; | ||||
| 
 | ||||
|         private PendingSound( ResourceLocation location, float volume, float pitch ) | ||||
|         { | ||||
|             this.location = location; | ||||
|             this.volume = volume; | ||||
|             this.pitch = pitch; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -17,7 +17,6 @@ import dan200.computercraft.shared.common.IColouredItem; | ||||
| import dan200.computercraft.shared.computer.core.ClientComputer; | ||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||
| import dan200.computercraft.shared.computer.core.ComputerState; | ||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | ||||
| import dan200.computercraft.shared.computer.items.IComputerItem; | ||||
| import dan200.computercraft.shared.network.container.ComputerContainerData; | ||||
| import dan200.computercraft.shared.pocket.apis.PocketAPI; | ||||
| @@ -34,6 +33,7 @@ import net.minecraft.world.InteractionHand; | ||||
| import net.minecraft.world.InteractionResult; | ||||
| import net.minecraft.world.InteractionResultHolder; | ||||
| import net.minecraft.world.entity.Entity; | ||||
| import net.minecraft.world.entity.item.ItemEntity; | ||||
| import net.minecraft.world.entity.player.Player; | ||||
| import net.minecraft.world.item.CreativeModeTab; | ||||
| import net.minecraft.world.item.Item; | ||||
| @@ -80,53 +80,65 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         PocketUpgrades.getVanillaUpgrades().map( x -> create( -1, null, -1, x ) ).forEach( stacks::add ); | ||||
|     } | ||||
| 
 | ||||
|     private boolean tick( @Nonnull ItemStack stack, @Nonnull Level world, @Nonnull Entity entity, @Nonnull PocketServerComputer computer ) | ||||
|     { | ||||
|         IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
| 
 | ||||
|         computer.setLevel( world ); | ||||
|         computer.updateValues( entity, stack, upgrade ); | ||||
| 
 | ||||
|         boolean changed = false; | ||||
| 
 | ||||
|         // Sync ID | ||||
|         int id = computer.getID(); | ||||
|         if( id != getComputerID( stack ) ) | ||||
|         { | ||||
|             changed = true; | ||||
|             setComputerID( stack, id ); | ||||
|         } | ||||
| 
 | ||||
|         // Sync label | ||||
|         String label = computer.getLabel(); | ||||
|         if( !Objects.equal( label, getLabel( stack ) ) ) | ||||
|         { | ||||
|             changed = true; | ||||
|             setLabel( stack, label ); | ||||
|         } | ||||
| 
 | ||||
|         // Update pocket upgrade | ||||
|         if( upgrade != null ) upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
| 
 | ||||
|         return changed; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void inventoryTick( @Nonnull ItemStack stack, Level world, @Nonnull Entity entity, int slotNum, boolean selected ) | ||||
|     { | ||||
|         if( !world.isClientSide ) | ||||
|         { | ||||
|             // Server side | ||||
|             Container inventory = entity instanceof Player ? ((Player) entity).getInventory() : null; | ||||
|             PocketServerComputer computer = createServerComputer( world, inventory, entity, stack ); | ||||
|             if( computer != null ) | ||||
|             { | ||||
|                 IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
|             Container inventory = entity instanceof Player player ? player.getInventory() : null; | ||||
|             PocketServerComputer computer = createServerComputer( world, entity, inventory, stack ); | ||||
|             computer.keepAlive(); | ||||
| 
 | ||||
|                 // Ping computer | ||||
|                 computer.keepAlive(); | ||||
|                 computer.setLevel( world ); | ||||
|                 computer.updateValues( entity, stack, upgrade ); | ||||
| 
 | ||||
|                 // Sync ID | ||||
|                 int id = computer.getID(); | ||||
|                 if( id != getComputerID( stack ) ) | ||||
|                 { | ||||
|                     setComputerID( stack, id ); | ||||
|                     if( inventory != null ) inventory.setChanged(); | ||||
|                 } | ||||
| 
 | ||||
|                 // Sync label | ||||
|                 String label = computer.getLabel(); | ||||
|                 if( !Objects.equal( label, getLabel( stack ) ) ) | ||||
|                 { | ||||
|                     setLabel( stack, label ); | ||||
|                     if( inventory != null ) inventory.setChanged(); | ||||
|                 } | ||||
| 
 | ||||
|                 // Update pocket upgrade | ||||
|                 if( upgrade != null ) | ||||
|                 { | ||||
|                     upgrade.update( computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
|                 } | ||||
|             } | ||||
|             boolean changed = tick( stack, world, entity, computer ); | ||||
|             if( changed && inventory != null ) inventory.setChanged(); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Client side | ||||
|             createClientComputer( stack ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onEntityItemUpdate( ItemStack stack, ItemEntity entity ) | ||||
|     { | ||||
|         if( entity.level.isClientSide ) return false; | ||||
| 
 | ||||
|         PocketServerComputer computer = getServerComputer( stack ); | ||||
|         if( computer != null && tick( stack, entity.level, entity, computer ) ) entity.setItem( stack.copy() ); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     @Nonnull | ||||
|     @Override | ||||
|     public InteractionResultHolder<ItemStack> use( Level world, Player player, @Nonnull InteractionHand hand ) | ||||
| @@ -134,22 +146,18 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         ItemStack stack = player.getItemInHand( hand ); | ||||
|         if( !world.isClientSide ) | ||||
|         { | ||||
|             PocketServerComputer computer = createServerComputer( world, player.getInventory(), player, stack ); | ||||
|             PocketServerComputer computer = createServerComputer( world, player, player.getInventory(), stack ); | ||||
|             computer.turnOn(); | ||||
| 
 | ||||
|             boolean stop = false; | ||||
|             if( computer != null ) | ||||
|             IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
|             if( upgrade != null ) | ||||
|             { | ||||
|                 computer.turnOn(); | ||||
| 
 | ||||
|                 IPocketUpgrade upgrade = getUpgrade( stack ); | ||||
|                 if( upgrade != null ) | ||||
|                 { | ||||
|                     computer.updateValues( player, stack, upgrade ); | ||||
|                     stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
|                 } | ||||
|                 computer.updateValues( player, stack, upgrade ); | ||||
|                 stop = upgrade.onRightClick( world, computer, computer.getPeripheral( ComputerSide.BACK ) ); | ||||
|             } | ||||
| 
 | ||||
|             if( !stop && computer != null ) | ||||
|             if( !stop ) | ||||
|             { | ||||
|                 boolean isTypingOnly = hand == InteractionHand.OFF_HAND; | ||||
|                 new ComputerContainerData( computer ).open( player, new PocketComputerMenuProvider( computer, stack, this, hand, isTypingOnly ) ); | ||||
| @@ -207,17 +215,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return super.getCreatorModId( stack ); | ||||
|     } | ||||
| 
 | ||||
|     public PocketServerComputer createServerComputer( final Level world, Container inventory, Entity entity, @Nonnull ItemStack stack ) | ||||
|     @Nonnull | ||||
|     public PocketServerComputer createServerComputer( Level world, Entity entity, @Nullable Container inventory, @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         if( world.isClientSide ) return null; | ||||
|         if( world.isClientSide ) throw new IllegalStateException( "Cannot call createServerComputer on the client" ); | ||||
| 
 | ||||
|         PocketServerComputer computer; | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|         int sessionID = getSessionID( stack ); | ||||
|         int correctSessionID = ComputerCraft.serverComputerRegistry.getSessionID(); | ||||
| 
 | ||||
|         if( instanceID >= 0 && sessionID == correctSessionID && | ||||
|             ComputerCraft.serverComputerRegistry.contains( instanceID ) ) | ||||
|         if( instanceID >= 0 && sessionID == correctSessionID && ComputerCraft.serverComputerRegistry.contains( instanceID ) ) | ||||
|         { | ||||
|             computer = (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ); | ||||
|         } | ||||
| @@ -235,13 +243,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|                 computerID = ComputerCraftAPI.createUniqueNumberedSaveDir( world, "computer" ); | ||||
|                 setComputerID( stack, computerID ); | ||||
|             } | ||||
|             computer = new PocketServerComputer( | ||||
|                 world, | ||||
|                 computerID, | ||||
|                 getLabel( stack ), | ||||
|                 instanceID, | ||||
|                 getFamily() | ||||
|             ); | ||||
|             computer = new PocketServerComputer( world, computerID, getLabel( stack ), instanceID, getFamily() ); | ||||
|             computer.updateValues( entity, stack, getUpgrade( stack ) ); | ||||
|             computer.addAPI( new PocketAPI( computer ) ); | ||||
|             ComputerCraft.serverComputerRegistry.add( instanceID, computer ); | ||||
| @@ -251,15 +253,17 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return computer; | ||||
|     } | ||||
| 
 | ||||
|     public static ServerComputer getServerComputer( @Nonnull ItemStack stack ) | ||||
|     @Nullable | ||||
|     public static PocketServerComputer getServerComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int session = getSessionID( stack ); | ||||
|         if( session != ComputerCraft.serverComputerRegistry.getSessionID() ) return null; | ||||
| 
 | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|         return instanceID >= 0 ? ComputerCraft.serverComputerRegistry.get( instanceID ) : null; | ||||
|         return instanceID >= 0 ? (PocketServerComputer) ComputerCraft.serverComputerRegistry.get( instanceID ) : null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     public static ClientComputer createClientComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int instanceID = getInstanceID( stack ); | ||||
| @@ -274,6 +278,7 @@ public class ItemPocketComputer extends Item implements IComputerItem, IMedia, I | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static ClientComputer getClientComputer( @Nonnull ItemStack stack ) | ||||
|     { | ||||
|         int instanceID = getInstanceID( stack ); | ||||
|   | ||||
| @@ -22,8 +22,50 @@ import java.util.Map; | ||||
| import java.util.Optional; | ||||
| 
 | ||||
| /** | ||||
|  * The turtle API allows you to control your turtle. | ||||
|  * Turtles are a robotic device, which can break and place blocks, attack mobs, and move about the world. They have | ||||
|  * an internal inventory of 16 slots, allowing them to store blocks they have broken or would like to place. | ||||
|  * | ||||
|  * ## Movement | ||||
|  * Turtles are capable of moving throug the world. As turtles are blocks themselves, they are confined to Minecraft's | ||||
|  * grid, moving a single block at a time. | ||||
|  * | ||||
|  * {@literal @}{turtle.forward} and @{turtle.back} move the turtle in the direction it is facing, while @{turtle.up} and | ||||
|  * {@literal @}{turtle.down} move it up and down (as one might expect!). In order to move left or right, you first need | ||||
|  * to turn the turtle using @{turtle.turnLeft}/@{turtle.turnRight} and then move forward or backwards. | ||||
|  * | ||||
|  * :::info | ||||
|  * The name "turtle" comes from [Turtle graphics], which originated from the Logo programming language. Here you'd move | ||||
|  * a turtle with various commands like "move 10" and "turn left", much like ComputerCraft's turtles! | ||||
|  * ::: | ||||
|  * | ||||
|  * Moving a turtle (though not turning it) consumes *fuel*. If a turtle does not have any @{turtle.refuel|fuel}, it | ||||
|  * won't move, and the movement functions will return @{false}. If your turtle isn't going anywhere, the first thing to | ||||
|  * check is if you've fuelled your turtle. | ||||
|  * | ||||
|  * :::tip Handling errors | ||||
|  * Many turtle functions can fail in various ways. For instance, a turtle cannot move forward if there's already a block | ||||
|  * there. Instead of erroring, functions which can fail either return @{true} if they succeed, or @{false} and some | ||||
|  * error message if they fail. | ||||
|  * | ||||
|  * Unexpected failures can often lead to strange behaviour. It's often a good idea to check the return values of these | ||||
|  * functions, or wrap them in @{assert} (for instance, use `assert(turtle.forward())` rather than `turtle.forward()`), | ||||
|  * so the program doesn't misbehave. | ||||
|  * ::: | ||||
|  * | ||||
|  * ## Turtle upgrades | ||||
|  * While a normal turtle can move about the world and place blocks, its functionality is limited. Thankfully, turtles | ||||
|  * can be upgraded with *tools* and @{peripheral|peripherals}. Turtles have two upgrade slots, one on the left and right | ||||
|  * sides. Upgrades can be equipped by crafting a turtle with the upgrade, or calling the @{turtle.equipLeft}/@{turtle.equipRight} | ||||
|  * functions. | ||||
|  * | ||||
|  * Turtle tools allow you to break blocks (@{turtle.dig}) and attack entities (@{turtle.attack}). Some tools are more | ||||
|  * suitable to a task than others. For instance, a diamond pickaxe can break every block, while a sword does more | ||||
|  * damage. Other tools have more niche use-cases, for instance hoes can til dirt. | ||||
|  * | ||||
|  * Peripherals (such as the @{modem|wireless modem} or @{speaker}) can also be equipped as upgrades. These are then | ||||
|  * accessible by accessing the `"left"` or `"right"` peripheral. | ||||
|  * | ||||
|  * [Turtle Graphics]: https://en.wikipedia.org/wiki/Turtle_graphics "Turtle graphics" | ||||
|  * @cc.module turtle | ||||
|  * @cc.since 1.3 | ||||
|  */ | ||||
|   | ||||
| @@ -31,7 +31,6 @@ import net.minecraft.resources.ResourceLocation; | ||||
| import net.minecraft.tags.FluidTags; | ||||
| import net.minecraft.world.Container; | ||||
| import net.minecraft.world.entity.Entity; | ||||
| import net.minecraft.world.entity.EntitySelector; | ||||
| import net.minecraft.world.entity.MoverType; | ||||
| import net.minecraft.world.item.DyeColor; | ||||
| import net.minecraft.world.level.Level; | ||||
| @@ -39,6 +38,7 @@ import net.minecraft.world.level.block.Block; | ||||
| import net.minecraft.world.level.block.entity.BlockEntity; | ||||
| import net.minecraft.world.level.block.state.BlockState; | ||||
| import net.minecraft.world.level.material.FluidState; | ||||
| import net.minecraft.world.level.material.PushReaction; | ||||
| import net.minecraft.world.phys.AABB; | ||||
| import net.minecraft.world.phys.Vec3; | ||||
| import net.minecraftforge.items.IItemHandlerModifiable; | ||||
| @@ -48,6 +48,7 @@ import javax.annotation.Nonnull; | ||||
| import javax.annotation.Nullable; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| import static dan200.computercraft.shared.common.IColouredItem.NBT_COLOUR; | ||||
| import static dan200.computercraft.shared.util.WaterloggableHelpers.WATERLOGGED; | ||||
| @@ -65,6 +66,8 @@ public class TurtleBrain implements ITurtleAccess | ||||
| 
 | ||||
|     private static final int ANIM_DURATION = 8; | ||||
| 
 | ||||
|     public static final Predicate<Entity> PUSHABLE_ENTITY = entity -> !entity.isSpectator() && entity.getPistonPushReaction() != PushReaction.IGNORE; | ||||
| 
 | ||||
|     private TileTurtle owner; | ||||
|     private ComputerProxy proxy; | ||||
|     private GameProfile owningPlayer; | ||||
| @@ -886,7 +889,7 @@ public class TurtleBrain implements ITurtleAccess | ||||
|                     } | ||||
| 
 | ||||
|                     AABB aabb = new AABB( minX, minY, minZ, maxX, maxY, maxZ ); | ||||
|                     List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, EntitySelector.NO_SPECTATORS ); | ||||
|                     List<Entity> list = world.getEntitiesOfClass( Entity.class, aabb, PUSHABLE_ENTITY ); | ||||
|                     if( !list.isEmpty() ) | ||||
|                     { | ||||
|                         double pushStep = 1.0f / ANIM_DURATION; | ||||
|   | ||||
| @@ -0,0 +1,53 @@ | ||||
| /* | ||||
|  * 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.util; | ||||
| 
 | ||||
| import net.minecraft.Util; | ||||
| import net.minecraft.client.Minecraft; | ||||
| import net.minecraftforge.api.distmarker.Dist; | ||||
| import net.minecraftforge.event.TickEvent; | ||||
| import net.minecraftforge.eventbus.api.SubscribeEvent; | ||||
| import net.minecraftforge.fml.common.Mod; | ||||
| 
 | ||||
| /** | ||||
|  * A monotonically increasing clock which accounts for the game being paused. | ||||
|  */ | ||||
| @Mod.EventBusSubscriber( Dist.CLIENT ) | ||||
| public final class PauseAwareTimer | ||||
| { | ||||
|     private static boolean paused; | ||||
|     private static long pauseTime; | ||||
|     private static long pauseOffset; | ||||
| 
 | ||||
|     private PauseAwareTimer() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
|     public static long getTime() | ||||
|     { | ||||
|         return (paused ? pauseTime : Util.getNanos()) - pauseOffset; | ||||
|     } | ||||
| 
 | ||||
|     @SubscribeEvent | ||||
|     public static void tick( TickEvent.RenderTickEvent event ) | ||||
|     { | ||||
|         if( event.phase != TickEvent.Phase.START ) return; | ||||
| 
 | ||||
|         boolean isPaused = Minecraft.getInstance().isPaused(); | ||||
|         if( isPaused == paused ) return; | ||||
| 
 | ||||
|         if( isPaused ) | ||||
|         { | ||||
|             pauseTime = Util.getNanos(); | ||||
|             paused = true; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             pauseOffset += Util.getNanos() - pauseTime; | ||||
|             paused = false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates