Skip to content

Commit

Permalink
[#34] Implement new notification system with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMeinerLP committed May 26, 2024
1 parent e327192 commit e3ee41e
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 0 deletions.
43 changes: 43 additions & 0 deletions src/main/java/net/minestom/server/notifications/BuilderImpl.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
112 changes: 112 additions & 0 deletions src/main/java/net/minestom/server/notifications/Notification.java
Original file line number Diff line number Diff line change
@@ -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.
* <br>
* Here is an example of its use:
* <pre><code>
* Notification notification = Notification.builder()
* .frameType(FrameType.TASK)
* .title(Component.text("Welcome!"))
* .icon(Material.IRON_SWORD).build();
* notification.send(player);
* </code></pre>
*/
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();
}


}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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<AdvancementsPacket> advancementsPacketCollector = connection.trackIncoming(AdvancementsPacket.class);
var player = connection.connect(instance, new Pos(0, 42, 0)).join();
var notification = Notification.builder()
.icon(Material.ITEM_FRAME)
.title(Component.text("unit test"))
.frameType(FrameType.TASK)
.build();
notification.send(player);
advancementsPacketCollector.assertCount(2);
AdvancementsPacket advancementsPacket = advancementsPacketCollector.collect().get(1);
assertNotNull(advancementsPacket);
Optional<AdvancementsPacket.AdvancementMapping> advancementMapping = advancementsPacket.advancementMappings().stream().findFirst();
advancementMapping.ifPresent(advancementMapping1 -> {
AdvancementsPacket.Advancement advancement = advancementMapping1.value();
assertFalse(advancement.sendTelemetryData());
var displayData = advancement.displayData();
assertEquals(displayData.icon(), ItemStack.of(Material.ITEM_FRAME));
assertEquals(displayData.title(), Component.text("unit test"));
assertEquals(displayData.frameType(), FrameType.TASK);
});
}
}

0 comments on commit e3ee41e

Please sign in to comment.