1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-11-24 00:54:48 +00:00

Merge branch 'mc-1.16.x' into mc-1.17.x

This commit is contained in:
Jonathan Coates
2021-12-21 15:10:19 +00:00
37 changed files with 758 additions and 326 deletions

View File

@@ -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
*/

View File

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

View File

@@ -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.

View File

@@ -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()

View File

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

View File

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

View File

@@ -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
*/

View File

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

View File

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