diff --git a/common/src/main/java/net/onelitefeather/cygnus/common/Messages.java b/common/src/main/java/net/onelitefeather/cygnus/common/Messages.java index df1243d0..818bf408 100644 --- a/common/src/main/java/net/onelitefeather/cygnus/common/Messages.java +++ b/common/src/main/java/net/onelitefeather/cygnus/common/Messages.java @@ -10,9 +10,11 @@ import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; +import java.util.List; + /** * @author theEvilReaper - * @version 1.1.0 + * @version 1.2.0 * @since 1.0.0 **/ public final class Messages { @@ -34,6 +36,7 @@ public final class Messages { private static final Component SURVIVOR_JOIN_PART_UPPER; private static final Component SURVIVOR_JOIN_LOWER_PART; public static final Component SLENDER_JOIN_PART; + private static final int MAP_ANNOUNCEMENT_MIN_WIDTH = 20; static { int forceStartTime = GameConfig.FORCE_START_TIME - 1; @@ -171,4 +174,38 @@ public static Component getSurvivorJoinMessage(String pageCount) { .append(Component.newline()) .append(SURVIVOR_JOIN_LOWER_PART); } + + /** + * Returns a {@link Component} which contains information about which map is used for the game. + * + * @param mapName of the map + * @param builders of the map + * @return the created {@link Component} reference + */ + @Contract(value = "_, _ -> new", pure = true) + public static Component getMapAnnouncementMessage(String mapName, @Nullable List builders) { + boolean hasBuilders = builders != null && !builders.isEmpty(); + String joinedBuilders = hasBuilders ? String.join(", ", builders) : ""; + + int width = Math.max(MAP_ANNOUNCEMENT_MIN_WIDTH, ("Now playing: " + mapName).length()); + if (hasBuilders) { + width = Math.max(width, ("Built by: " + joinedBuilders).length()); + } + Component separator = Component.text("─".repeat(width), NamedTextColor.DARK_GRAY); + + Component message = Component.newline().append(separator) + .append(Component.newline()) + .append(Component.text("Now playing: ", NamedTextColor.GRAY)) + .append(Component.text(mapName, NamedTextColor.GOLD)) + .append(Component.newline()); + + if (hasBuilders) { + message = message + .append(Component.text("Built by: ", NamedTextColor.GRAY)) + .append(Component.text(joinedBuilders, NamedTextColor.AQUA)) + .append(Component.newline()); + } + + return message.append(separator).append(Component.newline()); + } } diff --git a/common/src/test/java/net/onelitefeather/cygnus/common/MessagesTest.java b/common/src/test/java/net/onelitefeather/cygnus/common/MessagesTest.java new file mode 100644 index 00000000..cf4ef377 --- /dev/null +++ b/common/src/test/java/net/onelitefeather/cygnus/common/MessagesTest.java @@ -0,0 +1,55 @@ +package net.onelitefeather.cygnus.common; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MessagesTest { + + @Test + void testMapAnnouncementWithBuilders() { + Component message = Messages.getMapAnnouncementMessage("Granskoga", List.of("Alice", "Bob")); + + assertEquals( + "\n──────────────────────\nNow playing: Granskoga\nBuilt by: Alice, Bob\n──────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(message) + ); + } + + @Test + void testMapAnnouncementWithNullBuilders() { + Component message = Messages.getMapAnnouncementMessage("Granskoga", null); + + assertEquals( + "\n──────────────────────\nNow playing: Granskoga\n──────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(message) + ); + } + + @Test + void testMapAnnouncementWithEmptyBuilders() { + Component message = Messages.getMapAnnouncementMessage("Granskoga", List.of()); + + assertEquals( + "\n──────────────────────\nNow playing: Granskoga\n──────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(message) + ); + } + + @Test + void testMapAnnouncementWidthScalesWithLongestLine() { + Component message = Messages.getMapAnnouncementMessage( + "Very Long Map Name Example", + List.of("Alice", "Bob", "Charlie", "Dave") + ); + + assertEquals( + "\n───────────────────────────────────────\nNow playing: Very Long Map Name Example\nBuilt by: Alice, Bob, Charlie, Dave\n───────────────────────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(message) + ); + } +} diff --git a/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java b/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java index ced70c8a..3dfeb864 100644 --- a/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java +++ b/game/src/main/java/net/onelitefeather/cygnus/Cygnus.java @@ -6,6 +6,7 @@ import net.onelitefeather.cygnus.event.GameStartEvent; import net.onelitefeather.cygnus.common.page.event.PageSpawnEvent; import net.onelitefeather.cygnus.listener.game.GameStartListener; +import net.onelitefeather.cygnus.listener.map.GameMapLoadedListener; import net.onelitefeather.cygnus.listener.page.PageSpawnListener; import net.onelitefeather.cygnus.listener.view.ViewUpdateListener; import net.onelitefeather.cygnus.listener.page.PageDiscoveryCompleteListener; @@ -144,6 +145,7 @@ private void initListener() { manager.addListener(GameMapLoadedEvent.class, event -> this.pageProvider.loadPageData(event.gameMap().getPageFaces()) ); + manager.addListener(GameMapLoadedEvent.class, new GameMapLoadedListener()); manager.addListener(PlayerSpawnEvent.class, new PlayerSpawnListener(player -> this.mapProvider.teleportToSpawn(player, false), phaseSupplier)); PlayerQuitListener quitListener = new PlayerQuitListener(phaseSupplier, teamService, this.staminaService, this.gameConfig.minPlayers()); manager.addListener(PlayerDisconnectEvent.class, quitListener); diff --git a/game/src/main/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListener.java b/game/src/main/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListener.java new file mode 100644 index 00000000..b33c3082 --- /dev/null +++ b/game/src/main/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListener.java @@ -0,0 +1,17 @@ +package net.onelitefeather.cygnus.listener.map; + +import net.onelitefeather.cygnus.common.Messages; +import net.onelitefeather.cygnus.common.map.GameMap; +import net.onelitefeather.cygnus.map.event.GameMapLoadedEvent; +import net.theevilreaper.aves.util.Broadcaster; + +import java.util.function.Consumer; + +public final class GameMapLoadedListener implements Consumer { + + @Override + public void accept(GameMapLoadedEvent event) { + GameMap gameMap = event.gameMap(); + Broadcaster.broadcast(Messages.getMapAnnouncementMessage(gameMap.name(), gameMap.builders())); + } +} diff --git a/game/src/main/java/net/onelitefeather/cygnus/listener/map/package-info.java b/game/src/main/java/net/onelitefeather/cygnus/listener/map/package-info.java new file mode 100644 index 00000000..df973649 --- /dev/null +++ b/game/src/main/java/net/onelitefeather/cygnus/listener/map/package-info.java @@ -0,0 +1,4 @@ +@NotNullByDefault +package net.onelitefeather.cygnus.listener.map; + +import org.jetbrains.annotations.NotNullByDefault; diff --git a/game/src/test/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListenerTest.java b/game/src/test/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListenerTest.java new file mode 100644 index 00000000..504728d9 --- /dev/null +++ b/game/src/test/java/net/onelitefeather/cygnus/listener/map/GameMapLoadedListenerTest.java @@ -0,0 +1,85 @@ +package net.onelitefeather.cygnus.listener.map; + +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.instance.Instance; +import net.minestom.server.instance.InstanceContainer; +import net.minestom.server.network.packet.server.play.SystemChatPacket; +import net.minestom.testing.Collector; +import net.minestom.testing.Env; +import net.minestom.testing.TestConnection; +import net.onelitefeather.cygnus.CygnusPlayerTestBase; +import net.onelitefeather.cygnus.common.map.GameMap; +import net.onelitefeather.cygnus.map.event.GameMapLoadedEvent; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class GameMapLoadedListenerTest extends CygnusPlayerTestBase { + + @Test + void testBroadcastsMapAnnouncementToOnlinePlayers(@NotNull Env env) { + Instance instance = env.createFlatInstance(); + InstanceContainer gameInstance = (InstanceContainer) env.createFlatInstance(); + + TestConnection connection = env.createConnection(); + connection.connect(instance); + Collector packets = connection.trackIncoming(SystemChatPacket.class); + + GameMap gameMap = new GameMap( + "Granskoga", + Pos.ZERO, + Pos.ZERO, + Set.of(), + Set.of(), + List.of("Alice", "Bob") + ); + + new GameMapLoadedListener().accept(new GameMapLoadedEvent(gameMap, gameInstance)); + + List received = packets.collect(); + assertEquals(1, received.size(), "Player should have received exactly one map announcement message."); + assertEquals( + "\n──────────────────────\nNow playing: Granskoga\nBuilt by: Alice, Bob\n──────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(received.getFirst().message()) + ); + + env.destroyInstance(instance, true); + env.destroyInstance(gameInstance, true); + } + + @Test + void testOmitsBuilderLineWhenMapHasNoBuilders(@NotNull Env env) { + Instance instance = env.createFlatInstance(); + InstanceContainer gameInstance = (InstanceContainer) env.createFlatInstance(); + + TestConnection connection = env.createConnection(); + connection.connect(instance); + Collector packets = connection.trackIncoming(SystemChatPacket.class); + + GameMap gameMap = new GameMap( + "Granskoga", + Pos.ZERO, + Pos.ZERO, + Set.of(), + Set.of(), + List.of() + ); + + new GameMapLoadedListener().accept(new GameMapLoadedEvent(gameMap, gameInstance)); + + List received = packets.collect(); + assertEquals(1, received.size(), "Player should have received exactly one map announcement message."); + assertEquals( + "\n──────────────────────\nNow playing: Granskoga\n──────────────────────\n", + PlainTextComponentSerializer.plainText().serialize(received.getFirst().message()) + ); + + env.destroyInstance(instance, true); + env.destroyInstance(gameInstance, true); + } +}