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.recipe.RecipeCategory;
import net.minestom.server.recipe.ShapedRecipe;
import net.minestom.server.recipe.ShapelessRecipe;
import net.minestom.server.utils.identity.NamedAndIdentified;
import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
@ -33,11 +34,6 @@ import java.util.List;
public class Main {
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");
MinecraftServer.setCompressionThreshold(0);
@ -141,22 +137,22 @@ public class Main {
}
};
MinecraftServer.getRecipeManager().addRecipe(ironBlockRecipe);
// var recipe = new ShapelessRecipe(
// "minestom:test2", "abc",
// RecipeCategory.Crafting.MISC,
// List.of(
// new DeclareRecipesPacket.Ingredient(List.of(ItemStack.AIR))
// ),
// ItemStack.builder(Material.GOLD_BLOCK)
// .set(ItemComponent.CUSTOM_NAME, Component.text("abc"))
// .build()
// ) {
// @Override
// public boolean shouldShow(@NotNull Player player) {
// return true;
// }
// };
// MinecraftServer.getRecipeManager().addRecipe(recipe);
var recipe = new ShapelessRecipe(
"minestom:test2", "abc",
RecipeCategory.Crafting.MISC,
List.of(
new DeclareRecipesPacket.Ingredient(List.of(ItemStack.of(Material.DIRT)))
),
ItemStack.builder(Material.GOLD_BLOCK)
.set(ItemComponent.CUSTOM_NAME, Component.text("abc"))
.build()
) {
@Override
public boolean shouldShow(@NotNull Player player) {
return true;
}
};
MinecraftServer.getRecipeManager().addRecipe(recipe);
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.adventure.MinestomAdventure;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
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.monitoring.BenchmarkManager;
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.PotionEffect;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.List;
@ -98,8 +90,6 @@ public class PlayerInit {
itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));
Vec velocity = playerPos.direction().mul(6);
itemEntity.setVelocity(velocity);
player.sendPacket(makeExplosion(playerPos, velocity));
})
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
.addListener(AsyncPlayerConfigurationEvent.class, event -> {
@ -188,20 +178,6 @@ public class PlayerInit {
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 {
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
@ -250,8 +226,7 @@ public class PlayerInit {
BenchmarkManager benchmarkManager = MinecraftServer.getBenchmarkManager();
MinecraftServer.getSchedulerManager().buildTask(() -> {
if (LAST_TICK.get() == null) return;
if (MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0)
if (LAST_TICK.get() == null || MinecraftServer.getConnectionManager().getOnlinePlayerCount() == 0)
return;
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 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 DATA_VERSION = 3837;
public static final int DATA_VERSION = 3839;
// Threads
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
// collision check against the current shape since the below shape isn't tall
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
return belowShape.intersectBoxSwept(entityPosition, entityVelocity, belowPos, boundingBox, finalResult) |
(currentCollidable && currentShape.intersectBoxSwept(entityPosition, entityVelocity, currentPos, boundingBox, finalResult));

View File

@ -1,6 +1,7 @@
package net.minestom.server.component;
import net.kyori.adventure.nbt.BinaryTag;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.collection.ObjectArray;
@ -18,10 +19,10 @@ record DataComponentImpl<T>(
@Nullable NetworkBuffer.Type<T> network,
@Nullable BinaryTagSerializer<T> nbt
) implements DataComponent<T> {
static final Map<String, DataComponent<?>> NAMESPACES = new HashMap<>(32);
static final ObjectArray<DataComponent<?>> IDS = ObjectArray.singleThread(32);
@Override
public @NotNull T read(@NotNull BinaryTag tag) {
Check.notNull(nbt, "{0} cannot be deserialized from NBT", this);
@ -50,4 +51,14 @@ record DataComponentImpl<T>(
public String toString() {
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
*/
public void setFoodSaturation(float foodSaturation) {
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;
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.event.trait.PlayerEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.server.configuration.ResetChatPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -20,6 +21,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
private final boolean isFirstConfig;
private boolean hardcore;
private boolean clearChat;
private boolean sendRegistryData;
private Instance spawningInstance;
@ -28,6 +30,7 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
this.isFirstConfig = isFirstConfig;
this.hardcore = false;
this.clearChat = false;
this.sendRegistryData = isFirstConfig;
this.spawningInstance = null;
}
@ -52,6 +55,29 @@ public class AsyncPlayerConfigurationEvent implements PlayerEvent {
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() {
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;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.LongArrayBinaryTag;
import net.minestom.server.MinecraftServer;
import net.minestom.server.ServerFlag;
import net.minestom.server.collision.Shape;

View File

@ -1,13 +1,31 @@
package net.minestom.server.item.attribute;
import net.minestom.server.entity.EquipmentSlot;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public enum AttributeSlot {
ANY,
MAINHAND,
OFFHAND,
FEET,
LEGS,
CHEST,
HEAD,
ARMOR,
BODY,
ANY(EquipmentSlot.values()),
MAINHAND(EquipmentSlot.MAIN_HAND),
OFFHAND(EquipmentSlot.OFF_HAND),
FEET(EquipmentSlot.BOOTS),
LEGS(EquipmentSlot.LEGGINGS),
CHEST(EquipmentSlot.CHESTPLATE),
HEAD(EquipmentSlot.HELMET),
ARMOR(EquipmentSlot.CHESTPLATE, EquipmentSlot.LEGGINGS, EquipmentSlot.BOOTS, EquipmentSlot.HELMET),
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;
public record ItemAttribute(@NotNull UUID uuid,
@NotNull String name,
@NotNull Attribute attribute,
@NotNull AttributeOperation operation, double amount,
@NotNull AttributeSlot slot) {
public record ItemAttribute(
@NotNull UUID uuid,
@NotNull String name,
@NotNull Attribute attribute,
@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);
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
// 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)
refresh(player, chunk);

View File

@ -96,6 +96,7 @@ public final class PacketListenerManager {
setPlayListener(ClientChunkBatchReceivedPacket.class, ChunkBatchListener::batchReceivedListener);
setPlayListener(ClientPingRequestPacket.class, PlayPingListener::requestListener);
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.message.Messenger;
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.PluginMessagePacket;
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.ResetChatPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.packet.server.play.StartConfigurationPacket;
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> unmodifiablePlayPlayers = Collections.unmodifiableSet(playPlayers);
private final CachedPacket resetChatPacket = new CachedPacket(new ResetChatPacket());
// The uuid provider once a player login
private volatile UuidProvider uuidProvider = (playerConnection, username) -> UUID.randomUUID();
@ -276,6 +280,10 @@ public final class ConnectionManager {
final Instance spawningInstance = event.getSpawningInstance();
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)
if (event.willSendRegistryData()) {

View File

@ -18,7 +18,9 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA
public ClientHandshakePacket(@NotNull NetworkBuffer reader) {
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
@ -30,6 +32,7 @@ public record ClientHandshakePacket(int protocolVersion, @NotNull String serverA
}
writer.write(STRING, serverAddress);
writer.write(UNSIGNED_SHORT, serverPort);
// Not a writeEnum call because the indices are not 0-based
writer.write(VAR_INT, intent.id());
}

View File

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