From e6b80ac35b3cf6b9e5b848ffeddcac6f60b4531c Mon Sep 17 00:00:00 2001 From: mudkipdev Date: Tue, 3 Feb 2026 11:30:04 -0700 Subject: [PATCH] create menu utility --- battle/README.md | 1 + blocksumo/README.md | 1 + .../dev/emortal/minestom/core/utils/Menu.java | 73 ++++++ lazertag/README.md | 1 + lobby/README.md | 0 .../emortal/minestom/lobby/LobbyEvents.java | 35 +-- .../emortal/minestom/lobby/LobbyModule.java | 41 ++-- .../emortal/minestom/lobby/emote/Emote.java | 6 - .../minestom/lobby/emote/EmoteInventory.java | 69 ------ .../minestom/lobby/emote/EmoteMenu.java | 41 ++++ .../lobby/game/ConfigItemConverter.java | 14 +- .../minestom/lobby/game/ServerSelector.java | 217 ------------------ .../lobby/game/ServerSelectorMenu.java | 88 +++++++ .../lobby/util/MusicPlayerInventory.java | 91 -------- .../minestom/lobby/util/MusicPlayerMenu.java | 71 ++++++ marathon/README.md | 2 + minesweeper/README.md | 1 + parkourtag/README.md | 1 + 18 files changed, 328 insertions(+), 425 deletions(-) create mode 100644 battle/README.md create mode 100644 blocksumo/README.md create mode 100644 core/src/main/java/dev/emortal/minestom/core/utils/Menu.java create mode 100644 lazertag/README.md create mode 100644 lobby/README.md delete mode 100644 lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteInventory.java create mode 100644 lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteMenu.java delete mode 100755 lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelector.java create mode 100755 lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelectorMenu.java delete mode 100644 lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerInventory.java create mode 100644 lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerMenu.java create mode 100644 marathon/README.md create mode 100644 minesweeper/README.md create mode 100644 parkourtag/README.md diff --git a/battle/README.md b/battle/README.md new file mode 100644 index 0000000..79b188e --- /dev/null +++ b/battle/README.md @@ -0,0 +1 @@ +# Battle \ No newline at end of file diff --git a/blocksumo/README.md b/blocksumo/README.md new file mode 100644 index 0000000..e10a444 --- /dev/null +++ b/blocksumo/README.md @@ -0,0 +1 @@ +# Block Sumo \ No newline at end of file diff --git a/core/src/main/java/dev/emortal/minestom/core/utils/Menu.java b/core/src/main/java/dev/emortal/minestom/core/utils/Menu.java new file mode 100644 index 0000000..be8ccf5 --- /dev/null +++ b/core/src/main/java/dev/emortal/minestom/core/utils/Menu.java @@ -0,0 +1,73 @@ +package dev.emortal.minestom.core.utils; + +import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import net.minestom.server.entity.Player; +import net.minestom.server.event.EventListener; +import net.minestom.server.event.inventory.InventoryPreClickEvent; +import net.minestom.server.event.trait.InventoryEvent; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; + +import java.util.function.Consumer; + +public abstract class Menu { + protected final Player player; + protected final Inventory inventory; + protected final Int2ObjectMap> listeners; + + protected Menu(Player player, InventoryType type, String title) { + this.player = player; + this.inventory = new Inventory(type, title); + this.listeners = new Int2ObjectArrayMap<>(); + } + + public final Player getPlayer() { + return this.player; + } + + public final Inventory getInventory() { + return this.inventory; + } + + protected final void set(int slot, ItemStack itemStack, Consumer handler) { + // clean up old listeners when replacing an item + for (var listener : this.listeners.int2ObjectEntrySet()) { + if (listener.getIntKey() != slot) continue; + this.inventory.eventNode().removeListener(listener.getValue()); + } + + this.listeners.remove(slot); + + // add new listeners + this.inventory.setItemStack(slot, itemStack); + var listener = this.eventListener(slot, handler); + this.inventory.eventNode().addListener(listener); + this.listeners.put(slot, listener); + } + + protected final void set(int slot, ItemStack itemStack) { + this.set(slot, itemStack, _ -> {}); + } + + protected final void add(ItemStack itemStack) { + this.inventory.addItemStack(itemStack); + } + + protected final void clear() { + this.inventory.clear(); + this.listeners.forEach((_, listener) -> this.inventory.eventNode().removeListener(listener)); + this.listeners.clear(); + } + + private EventListener eventListener(int slot, Consumer handler) { + return EventListener.of(InventoryPreClickEvent.class, event -> { + if (event.getSlot() == slot) { + event.setCancelled(true); + handler.accept(event.getClick()); + } + }); + } +} diff --git a/lazertag/README.md b/lazertag/README.md new file mode 100644 index 0000000..1f42f3c --- /dev/null +++ b/lazertag/README.md @@ -0,0 +1 @@ +# LazerTag \ No newline at end of file diff --git a/lobby/README.md b/lobby/README.md new file mode 100644 index 0000000..e69de29 diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyEvents.java b/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyEvents.java index e590d7b..aa5271c 100755 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyEvents.java +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyEvents.java @@ -1,15 +1,18 @@ package dev.emortal.minestom.lobby; +import dev.emortal.api.modules.ModuleManager; import dev.emortal.minestom.core.module.core.playerprovider.EmortalPlayer; import dev.emortal.minestom.lobby.emote.Emote; +import dev.emortal.minestom.lobby.emote.EmoteMenu; import dev.emortal.minestom.lobby.gadget.Fireball; import dev.emortal.minestom.lobby.gadget.Gadget; import dev.emortal.minestom.lobby.gadget.LightningRod; import dev.emortal.minestom.lobby.gadget.Trumpet; import dev.emortal.minestom.lobby.gadget.blaster.InkBlaster; import dev.emortal.minestom.lobby.gadget.lobber.BlockLobber; +import dev.emortal.minestom.lobby.game.ServerSelectorMenu; import dev.emortal.minestom.lobby.util.CustomModels; -import dev.emortal.minestom.lobby.util.MusicPlayerInventory; +import dev.emortal.minestom.lobby.util.MusicPlayerMenu; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; @@ -31,21 +34,17 @@ import java.util.List; public final class LobbyEvents { - public static final Tag<@NotNull Boolean> SERVER_SELECTOR_TAG = Tag.Boolean("serverSelector"); public static final ItemStack SERVER_SELECTOR_ITEM = ItemStack.builder(Material.COMPASS) .set(DataComponents.ITEM_NAME, Component.text("Server Selector", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false)) - .set(SERVER_SELECTOR_TAG, true) .build(); - public static final Tag<@NotNull Boolean> MUSIC_PLAYER_TAG = Tag.Boolean("musicPlayer"); + public static final ItemStack MUSIC_PLAYER_ITEM = ItemStack.builder(Material.JUKEBOX) .set(DataComponents.ITEM_NAME, Component.text("Music Player", NamedTextColor.LIGHT_PURPLE).decoration(TextDecoration.ITALIC, false)) - .set(MUSIC_PLAYER_TAG, true) .build(); - public static final Tag<@NotNull Boolean> EMOTES_TAG = Tag.Boolean("emotes"); + public static final ItemStack EMOTES_ITEM = ItemStack.builder(Material.PHANTOM_MEMBRANE) .itemModel(CustomModels.EMOTES.getModelId()) .set(DataComponents.ITEM_NAME, Component.text("Emotes", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)) - .set(EMOTES_TAG, true) .build(); private static final String EMORTAL_UUID = "7bd5b459-1e6b-4753-8274-1fbd2fe9a4d5"; @@ -59,9 +58,9 @@ public final class LobbyEvents { // new BubbleBlower() ); - public static void registerGeneric(@NotNull EventNode<@NotNull Event> eventNode, @NotNull Instance instance) { + public static void registerGeneric(LobbyModule lobbyModule, @NotNull EventNode<@NotNull Event> eventNode, @NotNull Instance instance) { eventNode.addListener(PlayerSpawnEvent.class, event -> onSpawn(event.getPlayer(), event.getInstance(), instance)); - eventNode.addListener(PlayerUseItemEvent.class, LobbyEvents::onItemUse); + eventNode.addListener(PlayerUseItemEvent.class, event -> onItemUse(lobbyModule, event)); for (Gadget gadget : GADGETS) { gadget.registerListeners(eventNode); @@ -96,23 +95,27 @@ public static void onSpawn(@NotNull Player player, @NotNull Instance spawnInstan player.setTag(LobbyTags.LOBBABLE, true); } - private static void onItemUse(@NotNull PlayerUseItemEvent event) { + private static void onItemUse(LobbyModule lobbyModule, @NotNull PlayerUseItemEvent event) { if (event.getHand() != PlayerHand.MAIN) return; Player player = event.getPlayer(); ItemStack mainHandItem = player.getItemInMainHand(); - if (mainHandItem.hasTag(MUSIC_PLAYER_TAG)) { + + if (SERVER_SELECTOR_ITEM.equals(mainHandItem)) { cancel(event); - player.openInventory(MusicPlayerInventory.getInventory()); + ServerSelectorMenu menu = new ServerSelectorMenu(player, lobbyModule.matchmaker, lobbyModule.playerTracker, lobbyModule.configProvider); + player.openInventory(menu.getInventory()); } - if (mainHandItem.hasTag(EMOTES_TAG)) { + if (MUSIC_PLAYER_ITEM.equals(mainHandItem)) { cancel(event); - Emote.openInventory(player); + player.openInventory(new MusicPlayerMenu(player).getInventory()); } - - + if (EMOTES_ITEM.equals(mainHandItem)) { + cancel(event); + player.openInventory(new EmoteMenu(player).getInventory()); + } } public static void registerProtectionEvents(@NotNull EventNode<@NotNull Event> eventNode, @NotNull Instance spawnInstance) { diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyModule.java b/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyModule.java index a3ed708..c4d39f2 100755 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyModule.java +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/LobbyModule.java @@ -6,6 +6,7 @@ import dev.emortal.api.modules.annotation.ModuleData; import dev.emortal.api.modules.env.ModuleEnvironment; import dev.emortal.api.service.matchmaker.MatchmakerService; +import dev.emortal.api.service.playertracker.PlayerTrackerService; import dev.emortal.api.utils.GrpcStubCollection; import dev.emortal.minestom.core.module.MinestomModule; import dev.emortal.minestom.core.module.kubernetes.KubernetesModule; @@ -17,7 +18,6 @@ import dev.emortal.minestom.lobby.emote.Emote; import dev.emortal.minestom.lobby.events.EventManager; import dev.emortal.minestom.lobby.features.*; -import dev.emortal.minestom.lobby.game.ServerSelector; import dev.emortal.minestom.lobby.util.PolarConvertingLoader; import net.hollowcube.polar.ChunkSelector; import net.minestom.server.MinecraftServer; @@ -31,6 +31,7 @@ import net.minestom.server.instance.block.BlockManager; import net.minestom.server.network.packet.server.play.TeamsPacket; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,6 +45,10 @@ public final class LobbyModule extends MinestomModule { public static final Pos SPAWN_POINT = new Pos(0.5, 66, 0.5, 180f, 0f); private static final int SPAWN_CHUNK_RADIUS = 5; + @Nullable MatchmakerService matchmaker; + @Nullable PlayerTrackerService playerTracker; + ConfigProvider configProvider; + LobbyModule(@NotNull ModuleEnvironment environment) { super(environment); } @@ -71,16 +76,30 @@ public boolean onLoad() { this.spawnFeatures(instance); LiveConfigModule liveConfigModule = this.environment.moduleProvider().getModule(LiveConfigModule.class); - if (liveConfigModule != null) this.loadSelectorAndNpcs(liveConfigModule, instance); + + if (liveConfigModule != null) { + this.configProvider = liveConfigModule.getGameModes(); + + if (this.configProvider == null) { + LOGGER.warn("GameModeCollection is not present in LiveConfigModule"); + } else { + Collection allConfigs = this.configProvider.allConfigs(); + LOGGER.info("Loaded modes ({}): {}", allConfigs.size(), allConfigs.stream().map(GameModeConfig::friendlyName).collect(Collectors.joining(", "))); + LOGGER.debug("Game modes: {}", allConfigs); + this.matchmaker = GrpcStubCollection.getMatchmakerService().orElse(null); + this.playerTracker = GrpcStubCollection.getPlayerTrackerService().orElse(null); + } + } MessagingModule messagingModule = this.environment.moduleProvider().getModule(MessagingModule.class); + if (messagingModule != null) GrpcStubCollection.getPartyService().ifPresent(partyService -> { EventNode<@NotNull PlayerEvent> eventManagerNode = EventNode.type("event-manager", EventFilter.PLAYER); MinecraftServer.getGlobalEventHandler().addChild(eventManagerNode); new EventManager(messagingModule, partyService, eventManagerNode, instance); }); - LobbyEvents.registerGeneric(this.eventNode, instance); + LobbyEvents.registerGeneric(this, this.eventNode, instance); LobbyEvents.registerProtectionEvents(this.eventNode, instance); CommandManager commandManager = MinecraftServer.getCommandManager(); @@ -114,22 +133,6 @@ private void spawnFeatures(@NotNull Instance instance) { new ModelDecorationFeature().register(instance); } - private void loadSelectorAndNpcs(@NotNull LiveConfigModule module, @NotNull Instance instance) { - ConfigProvider gameModes = module.getGameModes(); - if (gameModes == null) { - LOGGER.warn("GameModeCollection is not present in LiveConfigModule"); - return; - } - - Collection allConfigs = gameModes.allConfigs(); - - LOGGER.info("Loaded modes ({}): {}", allConfigs.size(), allConfigs.stream().map(GameModeConfig::friendlyName).collect(Collectors.joining(", "))); - LOGGER.debug("Game modes: {}", allConfigs); - - MatchmakerService matchmaker = GrpcStubCollection.getMatchmakerService().orElse(null); - new ServerSelector(instance, matchmaker, GrpcStubCollection.getPlayerTrackerService().orElse(null), this.eventNode, gameModes); - } - private void loadKubernetesFeatures() { KubernetesModule kubernetes = this.getOptionalModule(KubernetesModule.class); if (kubernetes == null || kubernetes.getAgonesSdk() == null) return; diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/emote/Emote.java b/lobby/src/main/java/dev/emortal/minestom/lobby/emote/Emote.java index 17fac9b..846dbe0 100644 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/emote/Emote.java +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/emote/Emote.java @@ -26,7 +26,6 @@ public class Emote { private static final Path MODEL_PATH = Path.of("emotes.bbmodel"); public static BBModel MODEL; - private static final EmoteInventory INVENTORY = new EmoteInventory(); private static final Tag<@NotNull EmoteTask> TASK_TAG = Tag.Transient("emoteTask"); public static void init(EventNode<@NotNull Event> eventNode) { @@ -59,11 +58,6 @@ public static void play(Player player, Type emote) { player.setTag(TASK_TAG, emoteTask); } - public static void openInventory(Player player) { - player.openInventory(INVENTORY); - } - - public static class EmoteTask implements Supplier { private final Pos originalPos; private int ticks = 0; diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteInventory.java b/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteInventory.java deleted file mode 100644 index a8053c0..0000000 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteInventory.java +++ /dev/null @@ -1,69 +0,0 @@ -package dev.emortal.minestom.lobby.emote; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import net.minestom.server.MinecraftServer; -import net.minestom.server.component.DataComponents; -import net.minestom.server.entity.Player; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.tag.Tag; - -public class EmoteInventory extends Inventory { - - private static final Tag EMOTE_TAG = Tag.String("emoteName"); - - public EmoteInventory() { - super(InventoryType.CHEST_1_ROW, Component.text("Emotes", NamedTextColor.AQUA)); - - int slotI = 0; - for (Emote.Type value : Emote.Type.values()) { - ItemStack itemStack = ItemStack.builder(Material.DIAMOND) - .set(DataComponents.ITEM_NAME, Component.text(value.getFriendlyName(), NamedTextColor.WHITE).decoration(TextDecoration.ITALIC, false)) - .set(EMOTE_TAG, value.name()) - .build(); - this.setItemStack(slotI, itemStack); - - slotI++; - } - - this.setItemStack(8, ItemStack.builder(Material.BARRIER) - .set(DataComponents.ITEM_NAME, Component.text("Stop", NamedTextColor.RED)) - .build()); - - MinecraftServer.getGlobalEventHandler().addListener(InventoryPreClickEvent.class, e -> { - if (e.getInventory() == this) return; - if (e.getPlayer().getOpenInventory() != this) return; - e.setCancelled(true); - }); - - this.eventNode().addListener(InventoryPreClickEvent.class, event -> { - event.setCancelled(true); - - Player plr = event.getPlayer(); - int slot = event.getSlot(); - - if (slot == 8) { - Emote.stop(plr); - return; - } - - ItemStack item = this.getItemStack(slot); - if (!item.hasTag(EMOTE_TAG)) return; - - Emote.Type type = Emote.Type.valueOf(item.getTag(EMOTE_TAG)); - Emote.stop(plr); - plr.scheduleNextTick((i) -> { - Emote.play(plr, type); - plr.setHeldItemSlot((byte)2); - }); - plr.closeInventory(); - }); - - } - -} diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteMenu.java b/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteMenu.java new file mode 100644 index 0000000..15e9be5 --- /dev/null +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/emote/EmoteMenu.java @@ -0,0 +1,41 @@ +package dev.emortal.minestom.lobby.emote; + +import dev.emortal.minestom.core.utils.Menu; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.component.DataComponents; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; + +public class EmoteMenu extends Menu { + public EmoteMenu(Player player) { + super(player, InventoryType.CHEST_1_ROW, "Emotes"); + + int slotI = 0; + + for (Emote.Type emote : Emote.Type.values()) { + Component itemName = Component.text(emote.getFriendlyName(), NamedTextColor.WHITE).decoration(TextDecoration.ITALIC, false); + + this.set(slotI, ItemStack.of(Material.DIAMOND).with(DataComponents.ITEM_NAME, itemName), _ -> { + Emote.stop(player); + + player.scheduleNextTick(_ -> { + Emote.play(player, emote); + player.setHeldItemSlot((byte) 2); + }); + + player.closeInventory(); + }); + + slotI++; + } + + ItemStack stopItem = ItemStack.of(Material.BARRIER) + .with(DataComponents.ITEM_NAME, Component.text("Stop", NamedTextColor.RED)); + + this.set(8, stopItem, _ -> Emote.stop(this.player)); + } +} diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/game/ConfigItemConverter.java b/lobby/src/main/java/dev/emortal/minestom/lobby/game/ConfigItemConverter.java index 1abffd3..6058952 100644 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/game/ConfigItemConverter.java +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/game/ConfigItemConverter.java @@ -55,13 +55,13 @@ final class ConfigItemConverter { lore.add(Component.text("Right click to select map", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)); } -// lore.add(Component.empty()); -// lore.add(Component.text() -// .append(Component.text("● ", NamedTextColor.GREEN)) -// .append(Component.text(playerCount, NamedTextColor.GREEN, TextDecoration.BOLD)) -// .append(Component.text(" playing", NamedTextColor.GREEN)) -// .build() -// .decoration(TextDecoration.ITALIC, false)); + lore.add(Component.empty()); + lore.add(Component.text() + .append(Component.text("● ", NamedTextColor.GREEN)) + .append(Component.text(playerCount, NamedTextColor.GREEN, TextDecoration.BOLD)) + .append(Component.text(" playing", NamedTextColor.GREEN)) + .build() + .decoration(TextDecoration.ITALIC, false)); return lore; } diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelector.java b/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelector.java deleted file mode 100755 index 73f1f34..0000000 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelector.java +++ /dev/null @@ -1,217 +0,0 @@ -package dev.emortal.minestom.lobby.game; - -import dev.emortal.api.liveconfigparser.configs.ConfigProvider; -import dev.emortal.api.liveconfigparser.configs.ConfigUpdate; -import dev.emortal.api.liveconfigparser.configs.common.ConfigItem; -import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; -import dev.emortal.api.service.matchmaker.MatchmakerService; -import dev.emortal.api.service.playertracker.PlayerTrackerService; -import dev.emortal.minestom.lobby.LobbyEvents; -import io.grpc.StatusRuntimeException; -import net.kyori.adventure.text.Component; -import net.minestom.server.MinecraftServer; -import net.minestom.server.entity.Player; -import net.minestom.server.entity.PlayerHand; -import net.minestom.server.event.Event; -import net.minestom.server.event.EventNode; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.event.player.PlayerUseItemEvent; -import net.minestom.server.instance.Instance; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.inventory.click.Click; -import net.minestom.server.item.ItemStack; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.time.temporal.ChronoUnit; -import java.util.*; - -public final class ServerSelector { - private static final Logger LOGGER = LoggerFactory.getLogger(ServerSelector.class); - - private final @Nullable MatchmakerService matchmaker; - private final @Nullable PlayerTrackerService playerTracker; - private final @NotNull ConfigProvider configProvider; - private final @NotNull GameNpcHandler npcHandler; - - private final Inventory inventory = new Inventory(InventoryType.CHEST_4_ROW, "Pick a game, any game!"); - private final Map slotToGameMode = new HashMap<>(); - - public ServerSelector(@NotNull Instance instance, @Nullable MatchmakerService matchmaker, @Nullable PlayerTrackerService playerTracker, - @NotNull EventNode eventNode, @NotNull ConfigProvider configProvider) { - this.matchmaker = matchmaker; - this.playerTracker = playerTracker; - this.configProvider = configProvider; - this.npcHandler = new GameNpcHandler(configProvider, matchmaker, instance); - - this.registerListeners(eventNode); - - Collection configs = configProvider.allConfigs(); - configProvider.addGlobalUpdateListener(this::handleUpdate); - for (GameModeConfig config : configs) { - if (!config.enabled()) continue; - - this.createDisplayItem(config, 0); - } - - MinecraftServer.getGlobalEventHandler().addListener(InventoryPreClickEvent.class, e -> { - if (e.getInventory() == this.inventory) return; - if (e.getPlayer().getOpenInventory() != this.inventory) return; - e.setCancelled(true); - }); - - this.inventory.eventNode().addListener(InventoryPreClickEvent.class, this::handleInventoryClick); - - MinecraftServer.getSchedulerManager().buildTask(this::updatePlayerCounts) - .repeat(2, ChronoUnit.SECONDS) - .schedule(); - } - - private void handleInventoryClick(@NotNull InventoryPreClickEvent event) { - Player player = event.getPlayer(); - int slot = event.getSlot(); - - event.setCancelled(true); - - GameModeConfig config = this.slotToGameMode.get(slot); - if (config == null) return; // clicked empty slot - - if (event.getClick() instanceof Click.Left) { - QueueGameClickHandler.leftClick(player, config, this.matchmaker); - player.closeInventory(); - } - if (event.getClick() instanceof Click.Right) { - QueueGameClickHandler.rightClick(player, config, this.matchmaker); - } - } - - private void registerListeners(@NotNull EventNode eventNode) { - eventNode.addListener(PlayerUseItemEvent.class, event -> { - if (event.getHand() != PlayerHand.MAIN) return; - - Player player = event.getPlayer(); - if (player.getItemInMainHand().hasTag(LobbyEvents.SERVER_SELECTOR_TAG)) player.openInventory(this.inventory); - }); - } - - private void handleUpdate(@NotNull ConfigUpdate update) { - switch (update) { - case ConfigUpdate.Create(GameModeConfig newConfig) -> this.addGameMode(newConfig); - case ConfigUpdate.Delete(GameModeConfig oldConfig) -> this.removeGameMode(oldConfig); - case ConfigUpdate.Modify(GameModeConfig oldConfig, GameModeConfig newConfig) -> - this.updateGameMode(oldConfig, newConfig); - default -> throw new IllegalStateException("Invalid config update: " + update); - } - } - - private void addGameMode(@NotNull GameModeConfig config) { - if (!config.enabled()) return; - - // Display item - this.createDisplayItem(config, 0); - - // NPCs - this.npcHandler.renderNpcs(); - } - - private void removeGameMode(@NotNull GameModeConfig config) { - ConfigItem item = config.displayItem(); - if (item != null) this.removeDisplayItem(item); - - this.npcHandler.renderNpcs(); - } - - private void updateGameMode(@NotNull GameModeConfig oldConfig, @NotNull GameModeConfig newConfig) { - this.updateGameModeDisplayItem(newConfig, oldConfig.displayItem()); - - // Re-render NPCs no matter what as game mode might have been disabled - this.npcHandler.renderNpcs(); - } - - private void updateGameModeDisplayItem(@NotNull GameModeConfig newConfig, @Nullable ConfigItem oldItem) { - if (oldItem != null) { - // We always remove the old item, as we will re-add it if we have a new item, and we won't if we don't - this.removeDisplayItem(oldItem); - } - - this.createDisplayItem(newConfig, 0); - } - - private void createDisplayItem(@NotNull GameModeConfig config, long playerCount) { - ConfigItem item = config.displayItem(); - if (item == null) return; // If the item is null we have no item to create - if (!config.enabled()) return; // If the config isn't enabled then we don't want to create an item - - List lore = ConfigItemConverter.createDisplayItemLore(item, config.maps() != null && !config.maps().isEmpty(), playerCount); - ItemStack stack = ConfigItemConverter.convert(item, lore); - if (stack == null) return; - - this.addDisplayItem(config, item, stack); - } - - private void addDisplayItem(@NotNull GameModeConfig config, @NotNull ConfigItem item, @NotNull ItemStack stack) { - this.slotToGameMode.put(item.slot(), config); - this.inventory.setItemStack(item.slot(), stack); - } - - private void removeDisplayItem(@NotNull ConfigItem item) { - this.slotToGameMode.remove(item.slot()); - this.inventory.setItemStack(item.slot(), ItemStack.AIR); - } - - private void updatePlayerCounts() { - if (this.playerTracker == null) return; - - List fleetNames = new ArrayList<>(); - for (GameModeConfig config : this.configProvider.allConfigs()) { - if (!config.enabled()) continue; - fleetNames.add(config.fleetName()); - } - - // GetFleetPlayerCounts has to have the fleet names set, and we may not have any game modes enabled - if (fleetNames.isEmpty()) return; - - Map playerCounts; - try { - playerCounts = this.playerTracker.getFleetPlayerCounts(fleetNames); - } catch (StatusRuntimeException exception) { - LOGGER.error("Failed to get player counts for fleets", exception); - return; - } - - this.updatePlayerCounts(playerCounts); - } - - private void updatePlayerCounts(@NotNull Map playerCounts) { - for (Map.Entry entry : playerCounts.entrySet()) { - this.npcHandler.updatePlayerCount(entry.getKey(), entry.getValue()); - - for (GameModeConfig config : this.configProvider.allConfigs()) { - if (!config.enabled()) continue; - if (!config.fleetName().equals(entry.getKey())) continue; - if (config.displayItem() == null) continue; - - this.updatePlayerCountInDisplayItem(config, entry.getValue()); - } - } - } - - private void updatePlayerCountInDisplayItem(@NotNull GameModeConfig config, long playerCount) { - ConfigItem item = config.displayItem(); - if (item == null) return; - - boolean hasMaps = config.maps() != null && !config.maps().isEmpty(); - int slot = item.slot(); - - ItemStack stack = this.inventory.getItemStack(slot); - if (stack.isAir()) return; - - List newLore = ConfigItemConverter.createDisplayItemLore(item, hasMaps, playerCount); - ItemStack newStack = stack.withLore(newLore); - - this.addDisplayItem(config, item, newStack); - } -} diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelectorMenu.java b/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelectorMenu.java new file mode 100755 index 0000000..fa12d5c --- /dev/null +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/game/ServerSelectorMenu.java @@ -0,0 +1,88 @@ +package dev.emortal.minestom.lobby.game; + +import dev.emortal.api.liveconfigparser.configs.ConfigProvider; +import dev.emortal.api.liveconfigparser.configs.common.ConfigItem; +import dev.emortal.api.liveconfigparser.configs.gamemode.GameModeConfig; +import dev.emortal.api.service.matchmaker.MatchmakerService; +import dev.emortal.api.service.playertracker.PlayerTrackerService; +import dev.emortal.minestom.core.utils.Menu; +import io.grpc.StatusRuntimeException; +import net.kyori.adventure.text.Component; +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.inventory.click.Click; +import net.minestom.server.item.ItemStack; +import net.minestom.server.utils.time.TimeUnit; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public final class ServerSelectorMenu extends Menu { + private static final Logger LOGGER = LoggerFactory.getLogger(ServerSelectorMenu.class); + + private final @Nullable MatchmakerService matchmaker; + private final @Nullable PlayerTrackerService playerTracker; + private final @NotNull ConfigProvider configProvider; + private final Map playerCounts; + + public ServerSelectorMenu(Player player, @Nullable MatchmakerService matchmaker, @Nullable PlayerTrackerService playerTracker, @NotNull ConfigProvider configProvider) { + super(player, InventoryType.CHEST_4_ROW, "Pick a game, any game!"); + this.matchmaker = matchmaker; + this.playerTracker = playerTracker; + this.configProvider = configProvider; + this.playerCounts = new HashMap<>(); + + MinecraftServer.getSchedulerManager().buildTask(() -> { + this.fetchPlayerCounts(); + this.refreshInventory(); + }) + .repeat(2, TimeUnit.SECOND) + .schedule(); + } + + private void fetchPlayerCounts() { + if (this.playerTracker == null) { + return; + } + + var fleetNames = this.configProvider.allConfigs().stream().map(GameModeConfig::fleetName).toList(); + + try { + this.playerCounts.clear(); + this.playerCounts.putAll(this.playerTracker.getFleetPlayerCounts(fleetNames)); + } catch (StatusRuntimeException exception) { + LOGGER.error("Failed to get player counts for fleets", exception); + } + } + + private void refreshInventory() { + for (GameModeConfig config : this.configProvider.allConfigs()) { + if (!config.enabled()) { + continue; + } + + int slot = Objects.requireNonNull(config.displayItem()).slot(); + long playerCount = this.playerCounts.getOrDefault(config.fleetName(), 0L); + + this.set(slot, createItemStack(config, playerCount), click -> { + if (click instanceof Click.Left) { + QueueGameClickHandler.leftClick(this.player, config, this.matchmaker); + this.player.closeInventory(); + } else if (click instanceof Click.Right) { + QueueGameClickHandler.rightClick(this.player, config, this.matchmaker); + } + }); + } + } + + private static ItemStack createItemStack(GameModeConfig config, long playerCount) { + ConfigItem item = Objects.requireNonNull(config.displayItem()); + boolean hasMaps = config.maps() != null && !config.maps().isEmpty(); + List lore = ConfigItemConverter.createDisplayItemLore(item, hasMaps, playerCount); + return ConfigItemConverter.convert(item, lore); + } +} diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerInventory.java b/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerInventory.java deleted file mode 100644 index 997f401..0000000 --- a/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerInventory.java +++ /dev/null @@ -1,91 +0,0 @@ -package dev.emortal.minestom.lobby.util; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.sound.Sound; -import net.kyori.adventure.sound.SoundStop; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextDecoration; -import net.minestom.server.MinecraftServer; -import net.minestom.server.component.DataComponents; -import net.minestom.server.event.inventory.InventoryPreClickEvent; -import net.minestom.server.instance.block.jukebox.JukeboxSong; -import net.minestom.server.inventory.Inventory; -import net.minestom.server.inventory.InventoryType; -import net.minestom.server.item.ItemStack; -import net.minestom.server.item.Material; -import net.minestom.server.registry.Registries; -import net.minestom.server.registry.RegistryKey; -import net.minestom.server.tag.Tag; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("PatternValidation") -public class MusicPlayerInventory { - - private static final Tag<@NotNull String> PLAYING_DISC_TAG = Tag.Transient("playingDisc"); - - private static MusicPlayerInventory INSTANCE; - private final Inventory inventory; - - public MusicPlayerInventory() { - Component inventoryTitle = Component.text("Music Discs", NamedTextColor.BLACK); - Inventory inventory = new Inventory(InventoryType.CHEST_6_ROW, inventoryTitle); - - Registries registries = MinecraftServer.process(); - - var i = 10; - for (RegistryKey<@NotNull JukeboxSong> song : registries.jukeboxSong().keys()) { - if ((i + 1) % 9 == 0) i += 2; - - inventory.setItemStack(i, itemFromJukeboxSong(song)); - - i++; - } - - inventory.setItemStack(49, ItemStack.builder(Material.BARRIER) - .set(DataComponents.ITEM_NAME, Component.text("Stop", NamedTextColor.RED, TextDecoration.BOLD)) - .build()); - - MinecraftServer.getGlobalEventHandler().addListener(InventoryPreClickEvent.class, e -> { - if (e.getInventory() == INSTANCE.inventory) return; - if (e.getPlayer().getOpenInventory() != INSTANCE.inventory) return; - e.setCancelled(true); - }); - - inventory.eventNode().addListener(InventoryPreClickEvent.class, e -> { - e.setCancelled(true); - - if (e.getClickedItem() == ItemStack.AIR) return; - - String currentlyPlaying = e.getPlayer().getTag(PLAYING_DISC_TAG); - if (currentlyPlaying != null) { - e.getPlayer().stopSound(SoundStop.named(Key.key(currentlyPlaying))); - } - if (e.getSlot() == 49) return; - - RegistryKey<@NotNull JukeboxSong> songKey = e.getClickedItem().get(DataComponents.JUKEBOX_PLAYABLE); - JukeboxSong song = registries.jukeboxSong().get(songKey); - e.getPlayer().playSound(Sound.sound(song.soundEvent(), Sound.Source.RECORD, 1f, 1f), Sound.Emitter.self()); - e.getPlayer().setTag(PLAYING_DISC_TAG, song.soundEvent().name()); - }); - - this.inventory = inventory; - } - - public static Inventory getInventory() { - if (INSTANCE == null) { - INSTANCE = new MusicPlayerInventory(); - } - - return INSTANCE.inventory; - } - - private static ItemStack itemFromJukeboxSong(RegistryKey<@NotNull JukeboxSong> songKey) { - Registries registries = MinecraftServer.process(); - JukeboxSong song = registries.jukeboxSong().get(songKey); - - return ItemStack.builder(Material.fromKey(song.soundEvent().name().replace(".", "_"))) - .set(DataComponents.JUKEBOX_PLAYABLE, songKey) - .build(); - } -} \ No newline at end of file diff --git a/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerMenu.java b/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerMenu.java new file mode 100644 index 0000000..f0c778c --- /dev/null +++ b/lobby/src/main/java/dev/emortal/minestom/lobby/util/MusicPlayerMenu.java @@ -0,0 +1,71 @@ +package dev.emortal.minestom.lobby.util; + +import dev.emortal.minestom.core.utils.Menu; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.sound.SoundStop; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.component.DataComponents; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.block.jukebox.JukeboxSong; +import net.minestom.server.inventory.InventoryType; +import net.minestom.server.item.ItemStack; +import net.minestom.server.item.Material; +import net.minestom.server.registry.Registries; +import net.minestom.server.registry.RegistryKey; +import net.minestom.server.tag.Tag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +@SuppressWarnings("PatternValidation") +public class MusicPlayerMenu extends Menu { + private static final Tag<@NotNull String> PLAYING_DISC_TAG = Tag.Transient("playingDisc"); + + public MusicPlayerMenu(Player player) { + super(player, InventoryType.CHEST_6_ROW, "Music Discs"); + + var i = 10; + + for (RegistryKey<@NotNull JukeboxSong> song : MinecraftServer.getJukeboxSongRegistry().keys()) { + if ((i + 1) % 9 == 0) i += 2; + this.set(i, itemFromJukeboxSong(song), _ -> this.startPlaying(song)); + i++; + } + + ItemStack stopItem = ItemStack.of(Material.BARRIER) + .with(DataComponents.ITEM_NAME, Component.text("Stop", NamedTextColor.RED, TextDecoration.BOLD)); + + this.set(49, stopItem, _ -> this.startPlaying(null)); + } + + private void startPlaying(@Nullable RegistryKey newSong) { + String currentSong = this.player.getTag(PLAYING_DISC_TAG); + + if (currentSong != null) { + this.player.stopSound(SoundStop.named(Key.key(currentSong))); + } + + if (newSong != null) { + JukeboxSong song = Objects.requireNonNull(MinecraftServer.getJukeboxSongRegistry().get(newSong)); + this.player.playSound(Sound.sound(song.soundEvent(), Sound.Source.RECORD, 1f, 1f), Sound.Emitter.self()); + this.player.setTag(PLAYING_DISC_TAG, song.soundEvent().name()); + } + } + + private static ItemStack itemFromJukeboxSong(RegistryKey<@NotNull JukeboxSong> songKey) { + Registries registries = MinecraftServer.process(); + JukeboxSong song = registries.jukeboxSong().get(songKey); + if (song == null) return ItemStack.AIR; + Material discMaterial = Material.fromKey(song.soundEvent().name().replace(".", "_")); + if (discMaterial == null) return ItemStack.AIR; + + return ItemStack.builder(discMaterial) + .set(DataComponents.JUKEBOX_PLAYABLE, songKey) + .build(); + } +} \ No newline at end of file diff --git a/marathon/README.md b/marathon/README.md new file mode 100644 index 0000000..d3e7c06 --- /dev/null +++ b/marathon/README.md @@ -0,0 +1,2 @@ +# Marathon +Infinite parkour generator game mode diff --git a/minesweeper/README.md b/minesweeper/README.md new file mode 100644 index 0000000..603a0d8 --- /dev/null +++ b/minesweeper/README.md @@ -0,0 +1 @@ +# Minesweeper \ No newline at end of file diff --git a/parkourtag/README.md b/parkourtag/README.md new file mode 100644 index 0000000..78f3d0d --- /dev/null +++ b/parkourtag/README.md @@ -0,0 +1 @@ +# Parkour Tag \ No newline at end of file