From e3ee41e09cb2eaf0cdb474a8510c13dbfae2570d Mon Sep 17 00:00:00 2001
From: Phillipp Glanz <6745190+TheMeinerLP@users.noreply.github.com>
Date: Sun, 26 May 2024 20:15:06 +0200
Subject: [PATCH] [#34] Implement new notification system with tests
---
.../server/notifications/BuilderImpl.java | 43 +++++++
.../server/notifications/Notification.java | 112 ++++++++++++++++++
.../notifications/NotificationImpl.java | 44 +++++++
.../NotificationIntegrationTest.java | 58 +++++++++
4 files changed, 257 insertions(+)
create mode 100644 src/main/java/net/minestom/server/notifications/BuilderImpl.java
create mode 100644 src/main/java/net/minestom/server/notifications/Notification.java
create mode 100644 src/main/java/net/minestom/server/notifications/NotificationImpl.java
create mode 100644 src/test/java/net/minestom/server/notifications/NotificationIntegrationTest.java
diff --git a/src/main/java/net/minestom/server/notifications/BuilderImpl.java b/src/main/java/net/minestom/server/notifications/BuilderImpl.java
new file mode 100644
index 00000000000..b23c8885806
--- /dev/null
+++ b/src/main/java/net/minestom/server/notifications/BuilderImpl.java
@@ -0,0 +1,43 @@
+package net.minestom.server.notifications;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.advancements.FrameType;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import org.jetbrains.annotations.NotNull;
+
+final class BuilderImpl implements Notification.Builder {
+ private Component title;
+ private FrameType type;
+ private ItemStack icon;
+
+
+ @Override
+ public Notification.Builder title(@NotNull Component component) {
+ this.title = component;
+ return this;
+ }
+
+ @Override
+ public Notification.Builder frameType(@NotNull FrameType frameType) {
+ this.type = frameType;
+ return this;
+ }
+
+ @Override
+ public Notification.Builder icon(@NotNull Material material) {
+ this.icon = ItemStack.of(material);
+ return this;
+ }
+
+ @Override
+ public Notification.Builder icon(@NotNull ItemStack itemStack) {
+ this.icon = itemStack;
+ return this;
+ }
+
+ @Override
+ public Notification build() {
+ return new NotificationImpl(title, type, icon);
+ }
+}
diff --git a/src/main/java/net/minestom/server/notifications/Notification.java b/src/main/java/net/minestom/server/notifications/Notification.java
new file mode 100644
index 00000000000..bf4b8efc621
--- /dev/null
+++ b/src/main/java/net/minestom/server/notifications/Notification.java
@@ -0,0 +1,112 @@
+package net.minestom.server.notifications;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.advancements.FrameType;
+import net.minestom.server.entity.Player;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import net.minestom.server.network.packet.server.play.AdvancementsPacket;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Is used to send temporary advancements to the client, which are called notifications.
+ *
+ * Here is an example of its use:
+ *
+ * Notification notification = Notification.builder()
+ * .frameType(FrameType.TASK)
+ * .title(Component.text("Welcome!"))
+ * .icon(Material.IRON_SWORD).build();
+ * notification.send(player);
+ *
+ */
+public sealed interface Notification permits NotificationImpl {
+
+ String IDENTIFIER = "minestom:notification";
+ AdvancementsPacket REMOVE_PACKET = new AdvancementsPacket(false, List.of(), List.of(IDENTIFIER), List.of());
+
+ /**
+ * Creates a new builder instance
+ * @return
+ */
+ @Contract(pure = true)
+ static @NotNull Builder builder() {
+ return new BuilderImpl();
+ }
+
+ /**
+ * Send the notification to the client
+ * @param player to get be sent
+ */
+ void send(@NotNull Player player);
+
+ /**
+ * Send the notification to a collection of clients
+ * @param players to get be sent
+ */
+ void send(@NotNull Collection<@NotNull Player> players);
+
+
+ /**
+ * Gets the title of the notification as a {@link Component}
+ * @return the title {@link Component}
+ */
+ @NotNull Component title();
+
+ /**
+ * Get the {@link FrameType} of the notification
+ * @return the type
+ */
+ @NotNull FrameType type();
+
+ /**
+ * Get the displayed icon of the notification as {@link ItemStack}
+ * @return the {@link ItemStack}
+ */
+ @NotNull ItemStack icon();
+
+ sealed interface Builder permits BuilderImpl {
+ /**
+ * Set the title for a notification as component.
+ *
+ * If you're using a resource pack you can use {@link Component#translatable(String)}
+ *
+ * @param component to get send to the client
+ * @return the builder
+ */
+ Builder title(@NotNull Component component);
+
+ /**
+ * Set the frame typ of the notification
+ * @param frameType to showed for the client
+ * @return the builder
+ */
+ Builder frameType(@NotNull FrameType frameType);
+
+ /**
+ * Set the {@link Material} for the icon
+ * @param material to be shown to the client
+ * @return the builder
+ */
+ Builder icon(@NotNull Material material);
+
+ /**
+ * Set the {@link ItemStack} for the icon
+ * @param itemStack to be shown to the client
+ * @return the builder
+ */
+ Builder icon(@NotNull ItemStack itemStack);
+
+ /**
+ * Returns an instance of the creation notification
+ * @return the instance
+ */
+ Notification build();
+ }
+
+
+}
diff --git a/src/main/java/net/minestom/server/notifications/NotificationImpl.java b/src/main/java/net/minestom/server/notifications/NotificationImpl.java
new file mode 100644
index 00000000000..2d9ddef8eea
--- /dev/null
+++ b/src/main/java/net/minestom/server/notifications/NotificationImpl.java
@@ -0,0 +1,44 @@
+package net.minestom.server.notifications;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.advancements.FrameType;
+import net.minestom.server.entity.Player;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.network.packet.server.play.AdvancementsPacket;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.List;
+
+record NotificationImpl(@NotNull Component title, @NotNull FrameType type,
+ @NotNull ItemStack icon) implements Notification {
+ @Override
+ public void send(@NotNull Player player) {
+ player.sendPacket(createPacket());
+ player.sendPacket(REMOVE_PACKET);
+ }
+
+ @Override
+ public void send(@NotNull Collection<@NotNull Player> players) {
+ players.forEach(this::send);
+ }
+
+ AdvancementsPacket createPacket() {
+ final var displayData = new AdvancementsPacket.DisplayData(
+ title(), Component.empty(),
+ icon(), type(),
+ 0x6, null, 0f, 0f);
+
+ final var criteria = new AdvancementsPacket.Criteria("minestom:some_criteria",
+ new AdvancementsPacket.CriterionProgress(System.currentTimeMillis()));
+
+ final var advancement = new AdvancementsPacket.Advancement(null, displayData,
+ List.of(new AdvancementsPacket.Requirement(List.of(criteria.criterionIdentifier()))),
+ false);
+
+ final var mapping = new AdvancementsPacket.AdvancementMapping(IDENTIFIER, advancement);
+ final var progressMapping = new AdvancementsPacket.ProgressMapping(IDENTIFIER,
+ new AdvancementsPacket.AdvancementProgress(List.of(criteria)));
+ return new AdvancementsPacket(false, List.of(mapping), List.of(), List.of(progressMapping));
+ }
+}
diff --git a/src/test/java/net/minestom/server/notifications/NotificationIntegrationTest.java b/src/test/java/net/minestom/server/notifications/NotificationIntegrationTest.java
new file mode 100644
index 00000000000..01447aac5de
--- /dev/null
+++ b/src/test/java/net/minestom/server/notifications/NotificationIntegrationTest.java
@@ -0,0 +1,58 @@
+package net.minestom.server.notifications;
+
+import net.kyori.adventure.text.Component;
+import net.minestom.server.advancements.FrameType;
+import net.minestom.server.coordinate.Pos;
+import net.minestom.server.item.ItemStack;
+import net.minestom.server.item.Material;
+import net.minestom.server.network.packet.server.play.AdvancementsPacket;
+import net.minestom.testing.Collector;
+import net.minestom.testing.Env;
+import net.minestom.testing.EnvTest;
+import org.junit.jupiter.api.Test;
+
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@EnvTest
+class NotificationIntegrationTest {
+
+ @Test
+ void testBuilder(Env env) {
+ var notification = Notification.builder()
+ .icon(Material.ITEM_FRAME)
+ .title(Component.text("unit test"))
+ .frameType(FrameType.TASK)
+ .build();
+ assertEquals(notification.type(), FrameType.TASK);
+ assertEquals(notification.icon(), ItemStack.of(Material.ITEM_FRAME));
+ assertEquals(notification.title(), Component.text("unit test"));
+ }
+
+ @Test
+ void testSend(Env env) {
+ var instance = env.createFlatInstance();
+ var connection = env.createConnection();
+ Collector