mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-11-04 15:43:00 +00:00 
			
		
		
		
	Merge branch 'mc-1.17.x' into mc-1.18.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.getChannel().attachBufferStream( sound.stream );
 | 
			
		||||
        event.getChannel().play();
 | 
			
		||||
 | 
			
		||||
        sound.channel = event.getChannel();
 | 
			
		||||
        sound.executor = event.getEngine().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