chore: add PlayerAnvilInputEvent, other minor tweaks from self review

This commit is contained in:
mworzala 2024-05-03 23:16:56 -04:00
parent 54e30c2d67
commit 0c35fdfd7e
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
18 changed files with 172 additions and 73 deletions

View File

@ -23,6 +23,7 @@ import net.minestom.server.network.packet.server.play.DeclareRecipesPacket;
import net.minestom.server.ping.ResponseData; import net.minestom.server.ping.ResponseData;
import net.minestom.server.recipe.RecipeCategory; import net.minestom.server.recipe.RecipeCategory;
import net.minestom.server.recipe.ShapedRecipe; import net.minestom.server.recipe.ShapedRecipe;
import net.minestom.server.recipe.ShapelessRecipe;
import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.identity.NamedAndIdentified;
import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -33,11 +34,6 @@ import java.util.List;
public class Main { public class Main {
public static void main(String[] args) { public static void main(String[] args) {
try {
Class.forName(ItemComponent.class.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
System.setProperty("minestom.experiment.pose-updates", "true"); System.setProperty("minestom.experiment.pose-updates", "true");
MinecraftServer.setCompressionThreshold(0); MinecraftServer.setCompressionThreshold(0);
@ -141,22 +137,22 @@ public class Main {
} }
}; };
MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe); MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe);
// var recipe = new ShapelessRecipe( var recipe = new ShapelessRecipe(
// "minestom:test2", "abc", "minestom:test2", "abc",
// RecipeCategory.Crafting.MISC, RecipeCategory.Crafting.MISC,
// List.of( List.of(
// new DeclareRecipesPacket.Ingredient(List.of(ItemStack.AIR)) new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.DIRT)))
// ), ),
// ItemStack.builder(Material.GOLD_BLOCK) ItemStack.builder(Material.GOLD_BLOCK)
// .set(ItemComponent.CUSTOM_NAME, Component.text("abc")) .set(ItemComponent.CUSTOM_NAME, Component.text("abc"))
// .build() .build()
// ) { ) {
// @Override @Override
// public boolean shouldShow(@NotNull Player player) { public boolean shouldShow(@NotNull Player player) {
// return true; return true;
// } }
// }; };
// MinecraftServer.getRecipeManager().addRecipe(recipe); MinecraftServer.getRecipeManager().addRecipe(recipe);
PlayerInit.init(); PlayerInit.init();

View File

@ -7,7 +7,6 @@ import net.minestom.server.advancements.notifications.Notification;
import net.minestom.server.advancements.notifications.NotificationCenter; import net.minestom.server.advancements.notifications.NotificationCenter;
import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.adventure.audience.Audiences; import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
@ -39,18 +38,11 @@ import net.minestom.server.item.component.ItemBlockState;
import net.minestom.server.item.component.PotionContents; import net.minestom.server.item.component.PotionContents;
import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.play.ExplosionPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.data.BlockParticleData;
import net.minestom.server.potion.CustomPotionEffect; import net.minestom.server.potion.CustomPotionEffect;
import net.minestom.server.potion.PotionEffect; import net.minestom.server.potion.PotionEffect;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.MathUtils; import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType; import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.time.Duration; import java.time.Duration;
import java.util.List; import java.util.List;
@ -98,8 +90,6 @@ public class PlayerInit {
itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5)); itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));
Vec velocity = playerPos.direction().mul(6); Vec velocity = playerPos.direction().mul(6);
itemEntity.setVelocity(velocity); itemEntity.setVelocity(velocity);
player.sendPacket(makeExplosion(playerPos, velocity));
}) })
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername())) .addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
.addListener(AsyncPlayerConfigurationEvent.class, event -> { .addListener(AsyncPlayerConfigurationEvent.class, event -> {
@ -188,20 +178,6 @@ public class PlayerInit {
event.getInstance().setBlock(event.getBlockPosition(), block); event.getInstance().setBlock(event.getBlockPosition(), block);
}); });
private static final byte[] AIR_BLOCK_PARTICLE = NetworkBuffer.makeArray(new BlockParticleData(Block.AIR)::write);
private static @NotNull ExplosionPacket makeExplosion(@NotNull Point position, @NotNull Vec motion) {
return new ExplosionPacket(
position.x(), position.y(), position.z(),
0, new byte[0],
(float) motion.x(), (float) motion.y(), (float) motion.z(),
ExplosionPacket.BlockInteraction.KEEP,
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
SoundEvent.of(NamespaceID.from("not.a.real.sound"), 0f)
);
}
static { static {
InstanceManager instanceManager = MinecraftServer.getInstanceManager(); InstanceManager instanceManager = MinecraftServer.getInstanceManager();
@ -250,8 +226,7 @@ public class PlayerInit {
BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager(); BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager();
MinecraftServer.getSchedulerManager().buildTask(() -> { MinecraftServer.getSchedulerManager().buildTask(() -> {
if (LAST_TICK.get() == null) return; if (LAST_TICK.get() == null || MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0)
if (MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0)
return; return;
long ramUsage = benchmarkManager.getUsedMemory(); long ramUsage = benchmarkManager.getUsedMemory();

View File

@ -44,9 +44,9 @@ public final class MinecraftServer {
public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class);
public static final String VERSION_NAME = "1.20.5"; public static final String VERSION_NAME = "1.20.6";
public static final int PROTOCOL_VERSION = 766; public static final int PROTOCOL_VERSION = 766;
public static final int DATA_VERSION = 3837; public static final int DATA_VERSION = 3839;
// Threads // Threads
public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark";

View File

@ -298,7 +298,7 @@ final class BlockCollision {
// don't fall out of if statement, we could end up redundantly grabbing a block, and we only need to // don't fall out of if statement, we could end up redundantly grabbing a block, and we only need to
// collision check against the current shape since the below shape isn't tall // collision check against the current shape since the below shape isn't tall
if (belowShape.relativeEnd().y() > 1) { if (belowShape.relativeEnd().y() > 1) {
// we should always check both shapes, so no short-circuit here, to handle properties where the bounding box // we should always check both shapes, so no short-circuit here, to handle cases where the bounding box
// hits the current solid but misses the tall solid // hits the current solid but misses the tall solid
return belowShape.intersectBoxSwept(entityPosition, entityVelocity, belowPos, boundingBox, finalResult) | return belowShape.intersectBoxSwept(entityPosition, entityVelocity, belowPos, boundingBox, finalResult) |
(currentCollidable && currentShape.intersectBoxSwept(entityPosition, entityVelocity, currentPos, boundingBox, finalResult)); (currentCollidable && currentShape.intersectBoxSwept(entityPosition, entityVelocity, currentPos, boundingBox, finalResult));

View File

@ -1,6 +1,7 @@
package net.minestom.server.component; package net.minestom.server.component;
import net.kyori.adventure.nbt.BinaryTag; import net.kyori.adventure.nbt.BinaryTag;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.NamespaceID; import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray; import net.minestom.server.utils.collection.ObjectArray;
@ -18,10 +19,10 @@ record DataComponentImpl<T>(
@Nullable NetworkBuffer.Type<T> network, @Nullable NetworkBuffer.Type<T> network,
@Nullable BinaryTagSerializer<T> nbt @Nullable BinaryTagSerializer<T> nbt
) implements DataComponent<T> { ) implements DataComponent<T> {
static final Map<String, DataComponent<?>> NAMESPACES = new HashMap<>(32); static final Map<String, DataComponent<?>> NAMESPACES = new HashMap<>(32);
static final ObjectArray<DataComponent<?>> IDS = ObjectArray.singleThread(32); static final ObjectArray<DataComponent<?>> IDS = ObjectArray.singleThread(32);
@Override @Override
public @NotNull T read(@NotNull BinaryTag tag) { public @NotNull T read(@NotNull BinaryTag tag) {
Check.notNull(nbt, "{0} cannot be deserialized from NBT", this); Check.notNull(nbt, "{0} cannot be deserialized from NBT", this);
@ -50,4 +51,14 @@ record DataComponentImpl<T>(
public String toString() { public String toString() {
return name(); return name();
} }
static {
try {
// Force init of item component for the weird edge case of tests which reference a component by
// loading it (with fromNamespaceId) before referencing a component by name
Class.forName(ItemComponent.class.getName());
} catch (ClassNotFoundException e) {
// Ignored
}
}
} }

View File

@ -1096,14 +1096,14 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
} }
/** /**
* Sets and refresh client food saturationModifier. * Sets and refresh client food saturation.
* *
* @param foodSaturation the food saturationModifier * @param foodSaturation the food saturation
* @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20 * @throws IllegalArgumentException if {@code foodSaturation} is not between 0 and 20
*/ */
public void setFoodSaturation(float foodSaturation) { public void setFoodSaturation(float foodSaturation) {
Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20), Check.argCondition(!MathUtils.isBetween(foodSaturation, 0, 20),
"Food saturationModifier has to be between 0 and 20"); "Food saturation has to be between 0 and 20");
this.foodSaturation = foodSaturation; this.foodSaturation = foodSaturation;
sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation)); sendPacket(new UpdateHealthPacket(getHealth(), food, foodSaturation));
} }

View File

@ -3,6 +3,7 @@ package net.minestom.server.event.player;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerEvent; import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.server.configuration.ResetChatPacket;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -20,6 +21,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
private final boolean isFirstConfig; private final boolean isFirstConfig;
private boolean hardcore; private boolean hardcore;
private boolean clearChat;
private boolean sendRegistryData; private boolean sendRegistryData;
private Instance spawningInstance; private Instance spawningInstance;
@ -28,6 +30,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
this.isFirstConfig = isFirstConfig; this.isFirstConfig = isFirstConfig;
this.hardcore = false; this.hardcore = false;
this.clearChat = false;
this.sendRegistryData = isFirstConfig; this.sendRegistryData = isFirstConfig;
this.spawningInstance = null; this.spawningInstance = null;
} }
@ -52,6 +55,29 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
this.hardcore = hardcore; this.hardcore = hardcore;
} }
/**
* If true, the player's chat will be cleared when exiting the configuration state, otherwise
* it will be preserved. The default is not to clear the chat.
*
* @return true if the chat will be cleared, false otherwise
*
* @see ResetChatPacket
*/
public boolean willClearChat() {
return clearChat;
}
/**
* Set whether the player's chat will be cleared when exiting the configuration state.
*
* @param clearChat true to clear the chat, false otherwise
*
* @see ResetChatPacket
*/
public void setClearChat(boolean clearChat) {
this.clearChat = clearChat;
}
public boolean willSendRegistryData() { public boolean willSendRegistryData() {
return sendRegistryData; return sendRegistryData;
} }

View File

@ -0,0 +1,31 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerInstanceEvent;
import net.minestom.server.network.packet.client.play.ClientNameItemPacket;
import org.jetbrains.annotations.NotNull;
/**
* Called every time a {@link Player} types a letter in an anvil GUI.
*
* @see ClientNameItemPacket
*/
public class PlayerAnvilInputEvent implements PlayerInstanceEvent {
private final Player player;
private final String input;
public PlayerAnvilInputEvent(@NotNull Player player, @NotNull String input) {
this.player = player;
this.input = input;
}
@Override
public @NotNull Player getPlayer() {
return player;
}
public @NotNull String getInput() {
return input;
}
}

View File

@ -1,7 +1,5 @@
package net.minestom.server.instance; package net.minestom.server.instance;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.LongArrayBinaryTag;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag; import net.minestom.server.ServerFlag;
import net.minestom.server.collision.Shape; import net.minestom.server.collision.Shape;

View File

@ -1,13 +1,31 @@
package net.minestom.server.item.attribute; package net.minestom.server.item.attribute;
import net.minestom.server.entity.EquipmentSlot;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public enum AttributeSlot { public enum AttributeSlot {
ANY, ANY(EquipmentSlot.values()),
MAINHAND, MAINHAND(EquipmentSlot.MAIN_HAND),
OFFHAND, OFFHAND(EquipmentSlot.OFF_HAND),
FEET, FEET(EquipmentSlot.BOOTS),
LEGS, LEGS(EquipmentSlot.LEGGINGS),
CHEST, CHEST(EquipmentSlot.CHESTPLATE),
HEAD, HEAD(EquipmentSlot.HELMET),
ARMOR, ARMOR(EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS, EquipmentSlot.BOOTS, EquipmentSlot.HELMET),
BODY, BODY(EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS);
private final List<EquipmentSlot> equipmentSlots;
AttributeSlot(@NotNull EquipmentSlot... equipmentSlots) {
this.equipmentSlots = List.of(equipmentSlots);
}
/**
* Returns the (potentially multiple) equipment slots associated with this attribute slot.
*/
public @NotNull List<EquipmentSlot> equipmentSlots() {
return this.equipmentSlots;
}
} }

View File

@ -6,9 +6,12 @@ import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
public record ItemAttribute(@NotNull UUID uuid, public record ItemAttribute(
@NotNull String name, @NotNull UUID uuid,
@NotNull Attribute attribute, @NotNull String name,
@NotNull AttributeOperation operation, double amount, @NotNull Attribute attribute,
@NotNull AttributeSlot slot) { @NotNull AttributeOperation operation,
double amount,
@NotNull AttributeSlot slot
) {
} }

View File

@ -83,4 +83,8 @@ public record EnchantmentList(@NotNull Map<Enchantment, Integer> enchantments, b
newEnchantments.put(enchantment, level); newEnchantments.put(enchantment, level);
return new EnchantmentList(newEnchantments, showInTooltip); return new EnchantmentList(newEnchantments, showInTooltip);
} }
public @NotNull EnchantmentList withTooltip(boolean showInTooltip) {
return new EnchantmentList(enchantments, showInTooltip);
}
} }

View File

@ -0,0 +1,25 @@
package net.minestom.server.listener;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.player.PlayerAnvilInputEvent;
import net.minestom.server.inventory.ContainerInventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.network.packet.client.play.ClientNameItemPacket;
import org.jetbrains.annotations.NotNull;
public final class AnvilListener {
public static void nameItemListener(@NotNull ClientNameItemPacket packet, @NotNull Player player) {
if (!(player.getOpenInventory() instanceof ContainerInventory openInventory))
return;
if (openInventory.getInventoryType() != InventoryType.ANVIL)
return;
EventDispatcher.call(new PlayerAnvilInputEvent(player, packet.itemName()));
}
private AnvilListener() {
}
}

View File

@ -149,7 +149,7 @@ public class BlockPlacementListener {
// If a player is trying to place a block on themselves, the client will send a block change but will not set the block on the client // If a player is trying to place a block on themselves, the client will send a block change but will not set the block on the client
// For this reason, the block doesn't need to be updated for the client // For this reason, the block doesn't need to be updated for the client
// Client also doesn't predict placement of blocks on entities, but we need to refresh for properties where bounding boxes on the server don't match the client // Client also doesn't predict placement of blocks on entities, but we need to refresh for cases where bounding boxes on the server don't match the client
if (collisionEntity != player) if (collisionEntity != player)
refresh(player, chunk); refresh(player, chunk);

View File

@ -96,6 +96,7 @@ public final class PacketListenerManager {
setPlayListener(ClientChunkBatchReceivedPacket.class, ChunkBatchListener::batchReceivedListener); setPlayListener(ClientChunkBatchReceivedPacket.class, ChunkBatchListener::batchReceivedListener);
setPlayListener(ClientPingRequestPacket.class, PlayPingListener::requestListener); setPlayListener(ClientPingRequestPacket.class, PlayPingListener::requestListener);
setListener(ConnectionState.PLAY, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse); setListener(ConnectionState.PLAY, ClientCookieResponsePacket.class, CookieListener::handleCookieResponse);
setPlayListener(ClientNameItemPacket.class, AnvilListener::nameItemListener);
} }
/** /**

View File

@ -13,10 +13,12 @@ import net.minestom.server.instance.Instance;
import net.minestom.server.listener.preplay.LoginListener; import net.minestom.server.listener.preplay.LoginListener;
import net.minestom.server.message.Messenger; import net.minestom.server.message.Messenger;
import net.minestom.server.network.packet.client.login.ClientLoginStartPacket; import net.minestom.server.network.packet.client.login.ClientLoginStartPacket;
import net.minestom.server.network.packet.server.CachedPacket;
import net.minestom.server.network.packet.server.common.KeepAlivePacket; import net.minestom.server.network.packet.server.common.KeepAlivePacket;
import net.minestom.server.network.packet.server.common.PluginMessagePacket; import net.minestom.server.network.packet.server.common.PluginMessagePacket;
import net.minestom.server.network.packet.server.common.TagsPacket; import net.minestom.server.network.packet.server.common.TagsPacket;
import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket; import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket;
import net.minestom.server.network.packet.server.configuration.ResetChatPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.packet.server.play.StartConfigurationPacket; import net.minestom.server.network.packet.server.play.StartConfigurationPacket;
import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerConnection;
@ -63,6 +65,8 @@ public final class ConnectionManager {
private final Set<Player> unmodifiableConfigurationPlayers = Collections.unmodifiableSet(configurationPlayers); private final Set<Player> unmodifiableConfigurationPlayers = Collections.unmodifiableSet(configurationPlayers);
private final Set<Player> unmodifiablePlayPlayers = Collections.unmodifiableSet(playPlayers); private final Set<Player> unmodifiablePlayPlayers = Collections.unmodifiableSet(playPlayers);
private final CachedPacket resetChatPacket = new CachedPacket(new ResetChatPacket());
// The uuid provider once a player login // The uuid provider once a player login
private volatile UuidProvider uuidProvider = (playerConnection, username) -> UUID.randomUUID(); private volatile UuidProvider uuidProvider = (playerConnection, username) -> UUID.randomUUID();
@ -276,6 +280,10 @@ public final class ConnectionManager {
final Instance spawningInstance = event.getSpawningInstance(); final Instance spawningInstance = event.getSpawningInstance();
Check.notNull(spawningInstance, "You need to specify a spawning instance in the AsyncPlayerConfigurationEvent"); Check.notNull(spawningInstance, "You need to specify a spawning instance in the AsyncPlayerConfigurationEvent");
if (event.willClearChat()) {
player.sendPacket(resetChatPacket);
}
// Registry data (if it should be sent) // Registry data (if it should be sent)
if (event.willSendRegistryData()) { if (event.willSendRegistryData()) {

View File

@ -18,7 +18,9 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA
public ClientHandshakePacket(@NotNull NetworkBuffer reader) { public ClientHandshakePacket(@NotNull NetworkBuffer reader) {
this(reader.read(VAR_INT), reader.read(STRING), this(reader.read(VAR_INT), reader.read(STRING),
reader.read(UNSIGNED_SHORT), Intent.fromId(reader.read(VAR_INT))); reader.read(UNSIGNED_SHORT),
// Not a readEnum call because the indices are not 0-based
Intent.fromId(reader.read(VAR_INT)));
} }
@Override @Override
@ -30,6 +32,7 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA
} }
writer.write(STRING, serverAddress); writer.write(STRING, serverAddress);
writer.write(UNSIGNED_SHORT, serverPort); writer.write(UNSIGNED_SHORT, serverPort);
// Not a writeEnum call because the indices are not 0-based
writer.write(VAR_INT, intent.id()); writer.write(VAR_INT, intent.id());
} }

View File

@ -142,7 +142,7 @@ public interface BinaryTagSerializer<T> {
@Override @Override
public @NotNull Integer read(@NotNull BinaryTag tag) { public @NotNull Integer read(@NotNull BinaryTag tag) {
return tag instanceof IntBinaryTag intBinaryTag ? intBinaryTag.value() : 0; return tag instanceof NumberBinaryTag numberTag ? numberTag.intValue() : 0;
} }
}; };