Updater improvements, toggle pvp, Dialog API

This commit is contained in:
2026-07-08 23:18:32 -04:00
parent 9e6b42fe59
commit 560adbc40c
21 changed files with 747 additions and 760 deletions
+2 -1
View File
@@ -3,6 +3,7 @@ import java.text.SimpleDateFormat
import java.util.*
val paperApiVersion = "26.1.2"
val supportedMinecraftVersions = listOf("26.1", "26.1.1", "26.1.2", "26.2")
plugins {
java
@@ -132,7 +133,7 @@ tasks {
property("buildNumber", if (System.getenv("BUILD_NUMBER") != null) System.getenv("BUILD_NUMBER") else getBuildNumber())
property("date", SimpleDateFormat("MM/dd/yyyy '<light_purple>at<gold>' hh:mm:ss a z").format(Date()))
property("gitCommit", indraGit.commit().get().name)
property("minecraftVersion", paperApiVersion)
property("minecraftVersion", supportedMinecraftVersions.joinToString(","))
}
}
}
@@ -5,7 +5,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import dev.plex.command.ServerCommand;
import dev.plex.command.ServerCommandContext;
import dev.plex.command.exception.CommandFailException;
import dev.plex.menu.impl.MaterialMenu;
import dev.plex.menu.dialog.MaterialDialog;
import dev.plex.util.GameRuleUtil;
import dev.plex.util.PlexLog;
@@ -126,7 +126,7 @@ public class DebugCMD extends ServerCommand
{
return context.messageComponent("noPermissionConsole");
}
new MaterialMenu().open(playerSender);
new MaterialDialog().open(playerSender);
return null;
}
return context.usage();
@@ -10,6 +10,7 @@ import dev.plex.util.BuildInfo;
import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import dev.plex.util.UpdateChecker;
import java.util.List;
import java.util.stream.Collectors;
@@ -18,6 +19,7 @@ import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.apache.commons.lang3.StringUtils;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class PlexCMD extends ServerCommand
@@ -69,7 +71,7 @@ public class PlexCMD extends ServerCommand
context.send(sender, context.mmString("<light_purple>Authors: <gold>Telesphoreo, Taahh"));
context.send(sender, context.mmString("<light_purple>Built by: <gold>" + BuildInfo.getAuthor() + " <light_purple>on <gold>" + BuildInfo.getDate()));
context.send(sender, context.mmString("<light_purple>Run <gold>/plex modules <light_purple>to see a list of modules."));
plugin.getUpdateChecker().getUpdateStatusMessage(sender, true, 2);
plugin.getApi().scheduler().runAsync(() -> plugin.getUpdateChecker().getUpdateStatusMessage(sender, true, 2));
return null;
}
if (args[0].equalsIgnoreCase("reload"))
@@ -111,12 +113,19 @@ public class PlexCMD extends ServerCommand
else if (args[1].equalsIgnoreCase("update"))
{
context.checkPermission(sender, "plex.modules.update");
for (PlexModule module : plugin.getModuleManager().getModules())
plugin.getApi().scheduler().runAsync(() ->
{
plugin.getUpdateChecker().updateModuleJar(sender, module);
}
plugin.getModuleManager().reloadModules();
return context.mmString("<green>All modules updated and reloaded!");
for (PlexModule module : plugin.getModuleManager().getModules())
{
plugin.getUpdateChecker().updateModuleJar(sender, module);
}
plugin.getApi().scheduler().runGlobal(() ->
{
plugin.getModuleManager().reloadModules();
sender.sendMessage(context.mmString("<green>All modules updated and reloaded!"));
});
});
return null;
}
else if (args[1].equalsIgnoreCase("install"))
{
@@ -126,7 +135,7 @@ public class PlexCMD extends ServerCommand
return context.usage();
}
String moduleName = args[2];
plugin.getUpdateChecker().installModuleJar(sender, moduleName);
plugin.getApi().scheduler().runAsync(() -> plugin.getUpdateChecker().installModuleJar(sender, moduleName));
return context.mmString("<green>Installing module <yellow>" + moduleName + "<green>...");
}
else if (args[1].equalsIgnoreCase("uninstall"))
@@ -154,12 +163,22 @@ public class PlexCMD extends ServerCommand
else if (args[0].equalsIgnoreCase("update"))
{
context.checkPermission(sender, "plex.update");
if (!plugin.getUpdateChecker().getUpdateStatusMessage(sender, false, 0))
plugin.getApi().scheduler().runAsync(() ->
{
return context.mmString("<red>Plex is already up to date!");
}
plugin.getUpdateChecker().updateJar(sender, "Plex", false);
return context.mmString("<red>Alert: Restart the server for the new JAR file to be applied.");
UpdateChecker.UpdateCheckResult result = plugin.getUpdateChecker().checkForUpdates(false);
if (result.status() == UpdateChecker.UpdateCheckStatus.UPDATE_AVAILABLE)
{
plugin.getUpdateChecker().updateJar(sender, result.metadata(), () -> sendMessage(sender, context.mmString("<red>Alert: Restart the server for the new JAR file to be applied.")));
return;
}
if (result.status() == UpdateChecker.UpdateCheckStatus.UP_TO_DATE)
{
sendMessage(sender, context.mmString("<red>Plex is already up to date!"));
return;
}
plugin.getUpdateChecker().sendResultMessage(sender, result, 2);
});
return null;
}
else
{
@@ -167,4 +186,14 @@ public class PlexCMD extends ServerCommand
}
return null;
}
private void sendMessage(CommandSender sender, Component message)
{
if (sender instanceof Player player)
{
plugin.getApi().scheduler().runEntity(player, () -> sender.sendMessage(message));
return;
}
plugin.getApi().scheduler().runGlobal(() -> sender.sendMessage(message));
}
}
@@ -6,8 +6,7 @@ import dev.plex.command.ServerCommand;
import dev.plex.command.ServerCommandContext;
import dev.plex.command.exception.PlayerNotFoundException;
import dev.plex.command.source.RequiredCommandSource;
import dev.plex.menu.impl.PunishedPlayerMenu;
import dev.plex.menu.impl.PunishmentMenu;
import dev.plex.menu.dialog.PunishmentDialog;
import dev.plex.player.PlexPlayer;
@@ -45,9 +44,10 @@ public class PunishmentsCMD extends ServerCommand
CommandSender sender = context.sender();
Player playerSender = context.player();
String[] args = context.args();
PunishmentDialog dialog = new PunishmentDialog(plugin.getPlayerService());
if (args.length == 0)
{
new PunishmentMenu(plugin.getPlayerService()).open(playerSender);
dialog.open(playerSender);
}
else
{
@@ -59,7 +59,7 @@ public class PunishmentsCMD extends ServerCommand
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(args[0]);
final PlexPlayer player = offlinePlayer.isOnline() ? context.getOnlinePlexPlayer(args[0]) : context.getOfflinePlexPlayer(offlinePlayer.getUniqueId());
new PunishedPlayerMenu(player, plugin.getPlayerService()).open(playerSender);
dialog.open(playerSender, player);
}
return null;
@@ -3,7 +3,7 @@ package dev.plex.command.impl;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import dev.plex.command.ServerCommand;
import dev.plex.command.ServerCommandContext;
import dev.plex.menu.impl.ToggleMenu;
import dev.plex.menu.dialog.ToggleDialog;
import dev.plex.util.PlexUtils;
@@ -47,19 +47,8 @@ public class ToggleCMD extends ServerCommand
CommandSender sender = context.sender();
Player playerSender = context.player();
String[] args = context.args();
if (context.isConsole(sender) || playerSender == null)
if (args.length > 0)
{
if (args.length == 0)
{
sender.sendMessage(context.messageComponent("toggleAvailable"));
sender.sendMessage(toggleListItem(context, "toggleExplosions", "explosions"));
sender.sendMessage(toggleListItem(context, "toggleFluidSpread", "fluidspread"));
sender.sendMessage(toggleListItem(context, "toggleDrops", "drops"));
sender.sendMessage(toggleListItem(context, "toggleRedstone", "redstone"));
sender.sendMessage(toggleListItem(context, "togglePvp", "pvp"));
sender.sendMessage(toggleListItem(context, "toggleChat", "chat"));
return null;
}
switch (args[0].toLowerCase())
{
case "explosions" ->
@@ -93,7 +82,18 @@ public class ToggleCMD extends ServerCommand
}
}
}
new ToggleMenu(plugin).open(playerSender);
if (context.isConsole(sender) || playerSender == null)
{
sender.sendMessage(context.messageComponent("toggleAvailable"));
sender.sendMessage(toggleListItem(context, "toggleExplosions", "explosions"));
sender.sendMessage(toggleListItem(context, "toggleFluidSpread", "fluidspread"));
sender.sendMessage(toggleListItem(context, "toggleDrops", "drops"));
sender.sendMessage(toggleListItem(context, "toggleRedstone", "redstone"));
sender.sendMessage(toggleListItem(context, "togglePvp", "pvp"));
sender.sendMessage(toggleListItem(context, "toggleChat", "chat"));
return null;
}
new ToggleDialog(plugin).open(playerSender);
return null;
}
@@ -12,7 +12,6 @@ import dev.plex.listener.impl.CommandListener;
import dev.plex.listener.impl.DropListener;
import dev.plex.listener.impl.FreezeListener;
import dev.plex.listener.impl.GameModeListener;
import dev.plex.listener.impl.MenuListener;
import dev.plex.listener.impl.MobListener;
import dev.plex.listener.impl.MuteListener;
import dev.plex.listener.impl.PlayerListener;
@@ -50,7 +49,6 @@ public class ListenerHandler
register(() -> new DropListener(plugin));
register(() -> new FreezeListener(plugin));
register(() -> new GameModeListener(plugin));
register(() -> new MenuListener(plugin));
register(() -> new MobListener(plugin));
register(() -> new MuteListener(plugin));
register(() -> new PlayerListener(plugin));
@@ -1,76 +0,0 @@
package dev.plex.listener.impl;
import dev.plex.Plex;
import dev.plex.listener.ServerListenerBase;
import dev.plex.menu.AbstractMenu;
import dev.plex.menu.pagination.PageableMenu;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.meta.ItemMeta;
/**
* @author Taah
* @since 7:01 AM [02-09-2023]
*/
public class MenuListener extends ServerListenerBase
{
public MenuListener(Plex plugin)
{
super(plugin);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onClick(InventoryClickEvent event)
{
if (event.getClickedInventory() == null)
{
return;
}
if (event.getCurrentItem() == null)
{
return;
}
if (!(event.getWhoClicked() instanceof Player player))
{
return;
}
AbstractMenu.INVENTORIES.entrySet().stream().filter(entry -> entry.getKey().equals(event.getView().title()))
.findFirst()
.ifPresent(entry ->
{
if (entry.getValue() instanceof PageableMenu.Page page)
{
final ItemMeta meta = event.getCurrentItem().getItemMeta();
if (meta != null && meta.displayName() != null)
{
if (meta.displayName().equals(PageableMenu.previousItem().getItemMeta().displayName()))
{
page.parent().currentPage(page.parent().currentPage() - 1);
page.parent().open(player, page.parent().currentPage());
event.setCancelled(true);
return;
}
else if (meta.displayName().equals(PageableMenu.nextItem().getItemMeta().displayName()))
{
page.parent().currentPage(page.parent().currentPage() + 1);
page.parent().open(player, page.parent().currentPage());
event.setCancelled(true);
return;
}
else if (meta.displayName().equals(PageableMenu.closeItem().getItemMeta().displayName()))
{
player.closeInventory();
event.setCancelled(true);
return;
}
}
}
event.setCancelled(entry.getValue().onClick(event.getView(), event.getClickedInventory(), player, event.getCurrentItem()));
});
}
}
@@ -1,62 +0,0 @@
package dev.plex.menu;
import com.google.common.collect.Maps;
import dev.plex.util.minimessage.SafeMiniMessage;
import java.util.Map;
import lombok.Getter;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
@Getter
@Accessors(fluent = true)
public abstract class AbstractMenu
{
public static final Map<Component, AbstractMenu> INVENTORIES = Maps.newHashMap();
private final Component name;
private final Inventory inventory;
public AbstractMenu(Component name, Rows rows)
{
this.name = name;
this.inventory = Bukkit.createInventory(null, rows.slots, name);
INVENTORIES.put(name, this);
}
public AbstractMenu(String name, Rows rows)
{
this(SafeMiniMessage.mmDeserializeWithoutEvents(name), rows);
}
public abstract boolean onClick(InventoryView view, Inventory inventory, Player player, ItemStack clicked);
public void open(Player player)
{
player.openInventory(this.inventory);
}
public enum Rows
{
ONE(9), TWO(18), THREE(27), FOUR(36), FIVE(45), SIX(54);
private final int slots;
Rows(int slots)
{
this.slots = slots;
}
public int slots()
{
return this.slots;
}
}
}
@@ -0,0 +1,103 @@
package dev.plex.menu.dialog;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import org.bukkit.Material;
import org.bukkit.entity.Player;
public class MaterialDialog
{
private static final int PAGE_SIZE = 40;
private static final ClickCallback.Options CALLBACK_OPTIONS = ClickCallback.Options.builder()
.uses(1)
.lifetime(Duration.ofMinutes(5))
.build();
private final List<Material> materials = Arrays.stream(Material.values())
.filter(material -> !material.isAir())
.toList();
public void open(Player player)
{
open(player, 0);
}
private void open(Player player, int page)
{
player.showDialog(create(page));
}
private Dialog create(int page)
{
int pageCount = Math.max(1, (int) Math.ceil(materials.size() / (double) PAGE_SIZE));
int currentPage = Math.max(0, Math.min(page, pageCount - 1));
int fromIndex = currentPage * PAGE_SIZE;
int toIndex = Math.min(fromIndex + PAGE_SIZE, materials.size());
List<ActionButton> actions = new ArrayList<>(materials.subList(fromIndex, toIndex).stream()
.map(material -> materialButton(material, currentPage))
.toList());
if (currentPage > 0)
{
actions.add(pageButton("Previous Page", currentPage - 1));
}
if (currentPage + 1 < pageCount)
{
actions.add(pageButton("Next Page", currentPage + 1));
}
return Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(Component.text("Materials"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.WAIT_FOR_RESPONSE)
.body(List.of(DialogBody.plainMessage(Component.text("Page " + (currentPage + 1) + " of " + pageCount))))
.build())
.type(DialogType.multiAction(actions, closeButton(), 4)));
}
private ActionButton materialButton(Material material, int page)
{
return ActionButton.builder(Component.text(material.name()))
.width(120)
.action(DialogAction.customClick((response, audience) ->
{
if (audience instanceof Player player)
{
player.sendMessage(Component.text(material.name()));
open(player, page);
}
}, CALLBACK_OPTIONS))
.build();
}
private ActionButton pageButton(String label, int page)
{
return ActionButton.builder(Component.text(label))
.width(150)
.action(DialogAction.customClick((response, audience) ->
{
if (audience instanceof Player player)
{
open(player, page);
}
}, CALLBACK_OPTIONS))
.build();
}
private ActionButton closeButton()
{
return ActionButton.builder(Component.text("Close"))
.width(150)
.build();
}
}
@@ -0,0 +1,142 @@
package dev.plex.menu.dialog;
import dev.plex.player.PlayerService;
import dev.plex.player.PlexPlayer;
import dev.plex.punishment.Punishment;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
public class PunishmentDialog
{
private static final ClickCallback.Options CALLBACK_OPTIONS = ClickCallback.Options.builder()
.uses(1)
.lifetime(Duration.ofMinutes(5))
.build();
private final PlayerService playerService;
public PunishmentDialog(PlayerService playerService)
{
this.playerService = playerService;
}
public void open(Player player)
{
player.showDialog(playerListDialog());
}
public void open(Player viewer, PlexPlayer punishedPlayer)
{
viewer.showDialog(playerPunishmentsDialog(punishedPlayer));
}
private Dialog playerListDialog()
{
List<ActionButton> actions = Bukkit.getOnlinePlayers().stream()
.map(this::playerButton)
.toList();
return Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(PlexUtils.messageComponent("punishmentMenuTitle"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.WAIT_FOR_RESPONSE)
.body(List.of(DialogBody.plainMessage(Component.text("Select an online player to view their punishments."))))
.build())
.type(DialogType.multiAction(actions, closeButton(), 2)));
}
private Dialog playerPunishmentsDialog(PlexPlayer punishedPlayer)
{
List<DialogBody> body = new ArrayList<>();
List<Punishment> punishments = punishedPlayer.getPunishments();
if (punishments.isEmpty())
{
body.add(DialogBody.plainMessage(Component.text("No punishments found.")));
}
else
{
punishments.forEach(punishment -> body.add(DialogBody.plainMessage(punishmentSummary(punishment), 320)));
}
return Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(PlexUtils.messageComponent("punishedPlayerMenuTitle", punishedPlayer.getName()))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.WAIT_FOR_RESPONSE)
.body(body)
.build())
.type(DialogType.multiAction(List.of(backButton()), closeButton(), 2)));
}
private ActionButton playerButton(Player player)
{
return ActionButton.builder(Component.text(player.getName()))
.width(150)
.action(DialogAction.customClick((response, audience) -> openPunishments(audience, player), CALLBACK_OPTIONS))
.build();
}
private ActionButton backButton()
{
return ActionButton.builder(Component.text("Back"))
.width(150)
.action(DialogAction.customClick((response, audience) ->
{
if (audience instanceof Player player)
{
open(player);
}
}, CALLBACK_OPTIONS))
.build();
}
private ActionButton closeButton()
{
return ActionButton.builder(Component.text("Close"))
.width(150)
.build();
}
private void openPunishments(Audience audience, OfflinePlayer selectedPlayer)
{
if (!(audience instanceof Player viewer))
{
return;
}
PlexPlayer punishedPlayer = playerService.getPlayer(selectedPlayer.getUniqueId());
if (punishedPlayer == null)
{
viewer.sendMessage(PlexUtils.messageComponent("punishmentPlayerNotFound"));
return;
}
open(viewer, punishedPlayer);
}
private Component punishmentSummary(Punishment punishment)
{
String punisher = punishment.getPunisher() == null ? "CONSOLE" : playerService.getNameByUUID(punishment.getPunisher());
return Component.text(punishment.getType().name() + "\n"
+ "By: " + punisher + "\n"
+ "Issued: " + TimeUtils.useTimezone(punishment.getIssueDate()) + "\n"
+ "Expire(d/s): " + TimeUtils.useTimezone(punishment.getEndDate()) + "\n"
+ "Reason: " + punishment.getReason());
}
}
@@ -0,0 +1,107 @@
package dev.plex.menu.dialog;
import dev.plex.Plex;
import dev.plex.util.PlexUtils;
import io.papermc.paper.dialog.Dialog;
import io.papermc.paper.registry.data.dialog.ActionButton;
import io.papermc.paper.registry.data.dialog.DialogBase;
import io.papermc.paper.registry.data.dialog.action.DialogAction;
import io.papermc.paper.registry.data.dialog.body.DialogBody;
import io.papermc.paper.registry.data.dialog.type.DialogType;
import java.time.Duration;
import java.util.List;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickCallback;
import org.bukkit.entity.Player;
public class ToggleDialog
{
private static final ClickCallback.Options CALLBACK_OPTIONS = ClickCallback.Options.builder()
.uses(1)
.lifetime(Duration.ofMinutes(5))
.build();
private final Plex plugin;
public ToggleDialog(Plex plugin)
{
this.plugin = plugin;
}
public void open(Player player)
{
player.showDialog(create());
}
private Dialog create()
{
return Dialog.create(builder -> builder.empty()
.base(DialogBase.builder(PlexUtils.messageComponent("toggleMenuTitle"))
.canCloseWithEscape(true)
.pause(false)
.afterAction(DialogBase.DialogAfterAction.WAIT_FOR_RESPONSE)
.body(List.of(
DialogBody.plainMessage(statusLine("toggleExplosions", "explosions", true)),
DialogBody.plainMessage(statusLine("toggleFluidSpread", "fluidspread", false)),
DialogBody.plainMessage(statusLine("toggleDrops", "drops", false)),
DialogBody.plainMessage(statusLine("toggleRedstone", "redstone", false)),
DialogBody.plainMessage(statusLine("togglePvp", "pvp", false)),
DialogBody.plainMessage(Component.text(PlexUtils.messageString("toggleChat") + ": " + PlexUtils.messageString(plugin.toggles.getBoolean("chat") ? "stateOn" : "stateOff")))))
.build())
.type(DialogType.multiAction(List.of(
toggleButton("toggleExplosions", "toggleExplosionsLower", "explosions"),
toggleButton("toggleFluidSpread", "toggleFluidSpreadLower", "fluidspread"),
toggleButton("toggleDrops", "toggleDropsLower", "drops"),
toggleButton("toggleRedstone", "toggleRedstoneLower", "redstone"),
toggleButton("togglePvp", "togglePvpLower", "pvp"),
toggleButton("toggleChat", "toggleChatLower", "chat")),
closeButton(), 2)));
}
private ActionButton toggleButton(String nameKey, String lowerNameKey, String toggle)
{
return ActionButton.builder(Component.text(PlexUtils.messageString(nameKey)))
.tooltip(Component.text(PlexUtils.messageString(plugin.toggles.getBoolean(toggle) ? "stateEnabled" : "stateDisabled")))
.width(150)
.action(DialogAction.customClick((response, audience) -> toggle(audience, lowerNameKey, toggle), CALLBACK_OPTIONS))
.build();
}
private ActionButton closeButton()
{
return ActionButton.builder(Component.text("Close"))
.width(150)
.build();
}
private void toggle(Audience audience, String lowerNameKey, String toggle)
{
if (!(audience instanceof Player player) || !player.hasPermission("plex.toggle"))
{
return;
}
plugin.toggles.set(toggle, !plugin.toggles.getBoolean(toggle));
if ("chat".equals(toggle))
{
PlexUtils.broadcast(PlexUtils.messageComponent("chatToggled", player.getName(), PlexUtils.messageString(plugin.toggles.getBoolean("chat") ? "stateOn" : "stateOff")));
}
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString(lowerNameKey)));
open(player);
}
private Component statusLine(String nameKey, String toggle, boolean enabledIsUnsafe)
{
return Component.text(PlexUtils.messageString(nameKey) + ": " + status(toggle, enabledIsUnsafe));
}
private String status(String toggle, boolean enabledIsUnsafe)
{
if (enabledIsUnsafe)
{
return PlexUtils.messageString(plugin.toggles.getBoolean(toggle) ? "stateEnabledUnsafe" : "stateDisabledSafe");
}
return PlexUtils.messageString(plugin.toggles.getBoolean(toggle) ? "stateEnabled" : "stateDisabled");
}
}
@@ -1,37 +0,0 @@
package dev.plex.menu.impl;
import dev.plex.menu.AbstractMenu;
import dev.plex.menu.pagination.PageableMenu;
import dev.plex.util.item.ItemBuilder;
import java.util.Arrays;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
public class MaterialMenu extends PageableMenu<Material>
{
public MaterialMenu()
{
super("<dark_aqua><bold>Materials", AbstractMenu.Rows.SIX);
onClick((inventoryView, inventory, player, itemStack) ->
{
player.sendMessage(itemStack.displayName());
return true;
});
this.init();
}
@Override
protected ItemStack toItem(Material object)
{
return new ItemBuilder(object).displayName("<blue>" + object.name()).build();
}
@Override
protected List<Material> list()
{
return Arrays.stream(Material.values()).filter(material -> !material.isAir()).toList();
}
}
@@ -1,48 +0,0 @@
package dev.plex.menu.impl;
import dev.plex.menu.AbstractMenu;
import dev.plex.menu.pagination.PageableMenu;
import dev.plex.player.PlayerService;
import dev.plex.player.PlexPlayer;
import dev.plex.punishment.Punishment;
import dev.plex.util.PlexUtils;
import dev.plex.util.TimeUtils;
import dev.plex.util.item.ItemBuilder;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
public class PunishedPlayerMenu extends PageableMenu<Punishment>
{
private final PlexPlayer punishedPlayer;
private final PlayerService playerService;
public PunishedPlayerMenu(PlexPlayer player, PlayerService playerService)
{
super(PlexUtils.messageComponent("punishedPlayerMenuTitle", player.getName()), AbstractMenu.Rows.SIX);
this.punishedPlayer = player;
this.playerService = playerService;
onClick((inventoryView, itemStacks, player1, itemStack) -> true);
this.init();
}
@Override
protected ItemStack toItem(Punishment object)
{
return new ItemBuilder(Material.PAPER)
.displayName(PlexUtils.messageComponent("punishmentItemTitle", object.getType().name()))
.lore(PlexUtils.messageComponent("punishmentItemPunisher", object.getPunisher() == null ? "CONSOLE" : playerService.getNameByUUID(object.getPunisher())),
PlexUtils.messageComponent("punishmentItemIssued", TimeUtils.useTimezone(object.getIssueDate())),
PlexUtils.messageComponent("punishmentItemExpires", TimeUtils.useTimezone(object.getEndDate())),
PlexUtils.messageComponent("punishmentItemReason", object.getReason()))
.build();
}
@Override
protected List<Punishment> list()
{
return this.punishedPlayer.getPunishments();
}
}
@@ -1,65 +0,0 @@
package dev.plex.menu.impl;
import dev.plex.menu.AbstractMenu;
import dev.plex.menu.pagination.PageableMenu;
import dev.plex.player.PlayerService;
import dev.plex.player.PlexPlayer;
import dev.plex.util.PlexLog;
import dev.plex.util.PlexUtils;
import dev.plex.util.item.ItemBuilder;
import java.util.List;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
public class PunishmentMenu extends PageableMenu<Player>
{
private final PlayerService playerService;
public PunishmentMenu(PlayerService playerService)
{
super(PlexUtils.messageComponent("punishmentMenuTitle"), AbstractMenu.Rows.SIX);
this.playerService = playerService;
PlexLog.debug("list: {0}", list().size());
onClick((inventoryView, itemStacks, player, itemStack) ->
{
if (itemStack.getType() != Material.PLAYER_HEAD)
{
return true;
}
final SkullMeta meta = (SkullMeta) itemStack.getItemMeta();
if (meta.getOwningPlayer() == null)
{
meta.setOwningPlayer(Bukkit.getOfflinePlayer("markbyron"));
}
PlexPlayer punishedPlayer = playerService.getPlayer(meta.getOwningPlayer().getUniqueId());
if (punishedPlayer == null)
{
player.sendMessage(PlexUtils.messageComponent("punishmentPlayerNotFound"));
player.closeInventory();
return true;
}
new PunishedPlayerMenu(punishedPlayer, playerService).open(player);
return true;
});
this.init();
}
@Override
protected ItemStack toItem(Player object)
{
return new ItemBuilder(Material.PLAYER_HEAD).owner(object).displayName(PlexUtils.messageComponent("punishmentPlayerItem", object.getName())).build();
}
@Override
protected List<Player> list()
{
return Bukkit.getOnlinePlayers().stream().map(OfflinePlayer::getPlayer).toList();
}
}
@@ -1,149 +0,0 @@
package dev.plex.menu.impl;
import dev.plex.Plex;
import dev.plex.menu.AbstractMenu;
import dev.plex.util.PlexUtils;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class ToggleMenu extends AbstractMenu
{
private final Plex plugin;
public ToggleMenu(Plex plugin)
{
super(PlexUtils.messageComponent("toggleMenuTitle"), Rows.ONE);
this.plugin = plugin;
resetExplosionItem(this.inventory());
resetFluidspreadItem(this.inventory());
resetDropsItem(this.inventory());
resetRedstoneItem(this.inventory());
resetPVPItem(this.inventory());
resetChatItem(this.inventory());
}
private void resetExplosionItem(Inventory inventory)
{
ItemStack explosions = new ItemStack(Material.TNT);
ItemMeta explosionsItemMeta = explosions.getItemMeta();
explosionsItemMeta.displayName(PlexUtils.messageComponent("toggleMenuExplosionsName"));
explosionsItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuExplosionsLore", status("explosions", true))));
explosions.setItemMeta(explosionsItemMeta);
inventory.setItem(0, explosions);
}
private void resetFluidspreadItem(Inventory inventory)
{
ItemStack water = new ItemStack(Material.WATER_BUCKET);
ItemMeta waterItemMeta = water.getItemMeta();
waterItemMeta.displayName(PlexUtils.messageComponent("toggleMenuFluidSpreadName"));
waterItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuFluidSpreadLore", status("fluidspread", false))));
water.setItemMeta(waterItemMeta);
inventory.setItem(1, water);
}
private void resetDropsItem(Inventory inventory)
{
ItemStack feather = new ItemStack(Material.FEATHER);
ItemMeta featherItemMeta = feather.getItemMeta();
featherItemMeta.displayName(PlexUtils.messageComponent("toggleMenuDropsName"));
featherItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuDropsLore", status("drops", false))));
feather.setItemMeta(featherItemMeta);
inventory.setItem(2, feather);
}
private void resetRedstoneItem(Inventory inventory)
{
ItemStack redstone = new ItemStack(Material.REDSTONE);
ItemMeta redstoneItemMeta = redstone.getItemMeta();
redstoneItemMeta.displayName(PlexUtils.messageComponent("toggleMenuRedstoneName"));
redstoneItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuRedstoneLore", status("redstone", false))));
redstone.setItemMeta(redstoneItemMeta);
inventory.setItem(3, redstone);
}
private void resetPVPItem(Inventory inventory)
{
ItemStack pvp = new ItemStack(Material.IRON_SWORD);
ItemMeta pvpItemMeta = pvp.getItemMeta();
pvpItemMeta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
pvpItemMeta.displayName(PlexUtils.messageComponent("toggleMenuPvpName"));
pvpItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuPvpLore", status("pvp", false))));
pvp.setItemMeta(pvpItemMeta);
inventory.setItem(4, pvp);
}
private void resetChatItem(Inventory inventory)
{
ItemStack chat = new ItemStack(Material.OAK_SIGN);
ItemMeta chatItemMeta = chat.getItemMeta();
chatItemMeta.displayName(PlexUtils.messageComponent("toggleMenuChatName"));
chatItemMeta.lore(List.of(PlexUtils.messageComponent("toggleMenuChatLore", PlexUtils.messageString(plugin.toggles.getBoolean("chat") ? "stateOn" : "stateOff"))));
chat.setItemMeta(chatItemMeta);
inventory.setItem(5, chat);
}
@Override
public boolean onClick(InventoryView view, Inventory inventory, Player player, ItemStack clicked)
{
if (!player.hasPermission("plex.toggle"))
{
return false;
}
if (clicked.getType() == Material.TNT)
{
plugin.toggles.set("explosions", !plugin.toggles.getBoolean("explosions"));
resetExplosionItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("toggleExplosionsLower")));
}
if (clicked.getType() == Material.WATER_BUCKET)
{
plugin.toggles.set("fluidspread", !plugin.toggles.getBoolean("fluidspread"));
resetFluidspreadItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("toggleFluidSpreadLower")));
}
if (clicked.getType() == Material.FEATHER)
{
plugin.toggles.set("drops", !plugin.toggles.getBoolean("drops"));
resetDropsItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("toggleDropsLower")));
}
if (clicked.getType() == Material.REDSTONE)
{
plugin.toggles.set("redstone", !plugin.toggles.getBoolean("redstone"));
resetRedstoneItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("toggleRedstoneLower")));
}
if (clicked.getType() == Material.IRON_SWORD)
{
plugin.toggles.set("pvp", !plugin.toggles.getBoolean("pvp"));
resetPVPItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("togglePvpLower")));
}
if (clicked.getType() == Material.OAK_SIGN)
{
plugin.toggles.set("chat", !plugin.toggles.getBoolean("chat"));
PlexUtils.broadcast(PlexUtils.messageComponent("chatToggled", player.getName(), PlexUtils.messageString(plugin.toggles.getBoolean("chat") ? "stateOn" : "stateOff")));
resetChatItem(inventory);
player.sendMessage(PlexUtils.messageComponent("toggleToggled", PlexUtils.messageString("toggleChatLower")));
}
return true;
}
private String status(String toggle, boolean enabledIsUnsafe)
{
if (enabledIsUnsafe)
{
return PlexUtils.messageString(plugin.toggles.getBoolean(toggle) ? "stateEnabledUnsafe" : "stateDisabledSafe");
}
return PlexUtils.messageString(plugin.toggles.getBoolean(toggle) ? "stateEnabled" : "stateDisabled");
}
}
@@ -1,183 +0,0 @@
package dev.plex.menu.pagination;
import com.google.common.collect.Maps;
import dev.plex.menu.AbstractMenu;
import dev.plex.util.PlexUtils;
import dev.plex.util.function.ConditionalQuadConsumer;
import dev.plex.util.item.ItemBuilder;
import dev.plex.util.minimessage.SafeMiniMessage;
import java.util.List;
import java.util.Map;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
@Getter
@Setter
@Accessors(fluent = true)
public abstract class PageableMenu<T>
{
protected final Map<Integer, Page> pages = Maps.newHashMap();
private final Component name;
private final AbstractMenu.Rows rows;
private final Inventory inventory;
private int currentPage;
private boolean initialized;
@Setter(AccessLevel.NONE)
private ConditionalQuadConsumer<InventoryView, Inventory, Player, ItemStack> onClick;
public PageableMenu(Component name, AbstractMenu.Rows rows)
{
this.name = name;
this.rows = rows;
this.inventory = Bukkit.createInventory(null, rows.slots(), name);
if (rows.slots() < AbstractMenu.Rows.TWO.slots())
{
throw new RuntimeException("A pageable menu must have at least two rows to compensate for the navigation!");
}
}
protected void init()
{
this.initialized = true;
// Preload all needed pages, a better solution is probably just to dynamically create the pages as they go but no!
if (list().size() < (rows.slots() - 9))
{
final Page page = new Page(name.append(Component.space()).append(PlexUtils.mmDeserialize("(1)")), rows);
page.parent = this;
page.inventory().setItem(rows.slots() - 5, closeItem());
this.pages.put(0, page);
}
else
{
for (int i = 0; i < Math.ceil(list().size() / (double) (rows.slots() - 9)); i++)
{
final Page page = new Page(name.append(Component.space()).append(PlexUtils.mmDeserialize("(" + (i + 1) + ")")), rows);
page.parent = this;
if (i > 0) // If not first page set previous page button
{
page.inventory().setItem(rows.slots() - 6, previousItem());
}
page.inventory().setItem(rows.slots() - 5, closeItem());
if (i < (list().size() / (rows.slots() - 9)) - 1) // If not last page set next page button
{
page.inventory().setItem(rows.slots() - 4, nextItem());
}
this.pages.put(i, page);
}
}
int currentSlotIndex = 0;
int currentPageIndex = 0;
int currentIndex = 0;
while (currentSlotIndex < rows.slots() - 9 && currentIndex < list().size())
{
final Page page = this.pages.get(currentPageIndex);
if (page == null)
{
return;
}
page.inventory().setItem(currentSlotIndex, toItem(list().get(currentIndex)));
currentIndex++;
currentSlotIndex++;
if (currentSlotIndex == rows.slots() - 9)
{
currentSlotIndex = 0;
currentPageIndex++;
}
}
if (onClick != null) // To make sure this wasn't declared already
{
this.pages.forEach((integer, page) -> page.onClick(this.onClick));
}
}
public PageableMenu(String name, AbstractMenu.Rows rows)
{
this(SafeMiniMessage.mmDeserializeWithoutEvents(name), rows);
}
protected abstract ItemStack toItem(T object);
protected abstract List<T> list();
public void open(Player player)
{
open(player, 0);
}
public void open(Player player, int pageNum)
{
if (!this.initialized)
{
player.sendMessage(PlexUtils.messageComponent("pageableNotInitialized"));
return;
}
final Page page = this.pages.get(pageNum);
if (page == null)
{
player.sendMessage(PlexUtils.messageComponent("pageNotFound"));
return;
}
player.openInventory(page.inventory());
}
public void onClick(ConditionalQuadConsumer<InventoryView, Inventory, Player, ItemStack> onClick)
{
this.onClick = onClick;
this.pages.forEach((integer, page) -> page.onClick(this.onClick));
}
public static ItemStack nextItem()
{
return new ItemBuilder(Material.FEATHER).displayName(PlexUtils.messageComponent("pageNext")).build();
}
public static ItemStack previousItem()
{
return new ItemBuilder(Material.FEATHER).displayName(PlexUtils.messageComponent("pagePrevious")).build();
}
public static ItemStack closeItem()
{
return new ItemBuilder(Material.BARRIER).displayName(PlexUtils.messageComponent("pageClose")).build();
}
@Getter
@Setter
@Accessors(fluent = true)
public static class Page extends AbstractMenu
{
private ConditionalQuadConsumer<InventoryView, Inventory, Player, ItemStack> onClick;
private PageableMenu<?> parent;
private Page(Component name, Rows rows)
{
super(name, rows);
}
private Page(String name, AbstractMenu.Rows rows)
{
this(SafeMiniMessage.mmDeserializeWithoutEvents(name), rows);
}
@Override
public boolean onClick(InventoryView view, Inventory inventory, Player player, ItemStack clicked)
{
return onClick.accept(view, inventory, player, clicked);
}
}
}
@@ -2,12 +2,13 @@ package dev.plex.services.impl;
import dev.plex.Plex;
import dev.plex.services.AbstractService;
import dev.plex.util.UpdateChecker;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import org.bukkit.Bukkit;
public class UpdateCheckerService extends AbstractService
{
private boolean newVersion = false;
private boolean notified = false;
public UpdateCheckerService(Plex plugin)
{
@@ -17,11 +18,16 @@ public class UpdateCheckerService extends AbstractService
@Override
public void run(ScheduledTask task)
{
if (!newVersion)
if (!notified)
{
if (plugin.getUpdateChecker().getUpdateStatusMessage(Bukkit.getConsoleSender(), false, 1))
UpdateChecker.UpdateCheckResult result = plugin.getUpdateChecker().checkForUpdates(false);
plugin.getUpdateChecker().sendResultMessage(Bukkit.getConsoleSender(), result, 1);
if (result.status() == UpdateChecker.UpdateCheckStatus.UPDATE_AVAILABLE
|| result.status() == UpdateChecker.UpdateCheckStatus.MINECRAFT_TOO_OLD
|| result.status() == UpdateChecker.UpdateCheckStatus.MINECRAFT_TOO_NEW
|| result.status() == UpdateChecker.UpdateCheckStatus.MINECRAFT_UNLISTED)
{
newVersion = true;
notified = true;
}
}
}
@@ -73,7 +73,12 @@ public final class ArtifactMetadata
return publishedAt;
}
public Optional<String> validatePlex(UpdateChannel requestedChannel, String minecraftVersion)
public List<String> minecraftVersions()
{
return minecraftVersions == null ? List.of() : List.copyOf(minecraftVersions);
}
public Optional<String> validatePlex(UpdateChannel requestedChannel)
{
Optional<String> commonError = validateCommon(requestedChannel);
if (commonError.isPresent())
@@ -84,9 +89,9 @@ public final class ArtifactMetadata
{
return Optional.of("Plex metadata has unexpected artifact name " + name);
}
if (minecraftVersions == null || !minecraftVersions.contains(minecraftVersion))
if (minecraftVersions == null || minecraftVersions.isEmpty())
{
return Optional.of("metadata does not include Minecraft version " + minecraftVersion);
return Optional.of("Plex metadata is missing minecraftVersions");
}
if (apiCompatibility == null)
{
@@ -95,6 +100,20 @@ public final class ArtifactMetadata
return Optional.empty();
}
public Optional<String> validatePlex(UpdateChannel requestedChannel, String minecraftVersion)
{
Optional<String> plexError = validatePlex(requestedChannel);
if (plexError.isPresent())
{
return plexError;
}
if (!supportsMinecraftVersion(minecraftVersion))
{
return Optional.of("metadata does not include Minecraft version " + minecraftVersion);
}
return Optional.empty();
}
public Optional<String> validateModule(UpdateChannel requestedChannel, String moduleName, int apiCompatibility)
{
Optional<String> commonError = validateCommon(requestedChannel);
@@ -185,6 +204,11 @@ public final class ArtifactMetadata
return Optional.empty();
}
private boolean supportsMinecraftVersion(String minecraftVersion)
{
return minecraftVersions != null && minecraftVersions.contains(minecraftVersion);
}
private static boolean isBlank(String value)
{
return value == null || value.isBlank();
@@ -37,6 +37,18 @@ public final class UpdateMetadataClient
return metadata;
}
public ArtifactMetadata fetchPlexChannelLatest() throws MetadataException
{
String path = "/api/v1/projects/Plex/channels/" + channel.id() + "/latest.json";
ArtifactMetadata metadata = fetch(path);
Optional<String> validationError = metadata.validatePlex(channel);
if (validationError.isPresent())
{
throw new MetadataException(validationError.get(), false);
}
return metadata;
}
public ArtifactMetadata fetchModuleLatest(String moduleName, int apiCompatibility) throws MetadataException
{
return fetchModuleLatest(moduleName, apiCompatibility, List.of());
@@ -158,6 +170,11 @@ public final class UpdateMetadataClient
{
private final boolean notFound;
public static MetadataException localError(String message)
{
return new MetadataException(message, false);
}
private MetadataException(String message, boolean notFound)
{
super(message);
@@ -18,6 +18,7 @@ import java.nio.file.StandardCopyOption;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HexFormat;
import java.util.List;
@@ -36,6 +37,18 @@ public class UpdateChecker
private ArtifactMetadata latestPlexMetadata;
private UpdateMetadataClient.MetadataException latestPlexMetadataFailure;
private long latestPlexMetadataFailureAtMillis;
private ArtifactMetadata latestPlexChannelLatestMetadata;
private UpdateMetadataClient.MetadataException latestPlexChannelLatestMetadataFailure;
private long latestPlexChannelLatestMetadataFailureAtMillis;
public enum UpdateCheckStatus
{
UP_TO_DATE, UPDATE_AVAILABLE, MINECRAFT_TOO_OLD, MINECRAFT_TOO_NEW, MINECRAFT_UNLISTED, ERROR
}
public record UpdateCheckResult(UpdateCheckStatus status, ArtifactMetadata metadata, UpdateMetadataClient.MetadataException error)
{
}
public UpdateChecker(Plex plugin)
{
@@ -44,56 +57,147 @@ public class UpdateChecker
this.metadataClient = new UpdateMetadataClient(channel);
}
public UpdateCheckResult checkForUpdates(boolean useCache)
{
String runningMinecraftVersion = getRunningMinecraftVersion();
if (runningMinecraftVersion == null || runningMinecraftVersion.isBlank() || "unknown".equalsIgnoreCase(runningMinecraftVersion))
{
return new UpdateCheckResult(UpdateCheckStatus.ERROR, null, UpdateMetadataClient.MetadataException.localError("running Minecraft version could not be determined"));
}
try
{
ArtifactMetadata metadata = fetchLatestPlexMetadata(useCache, runningMinecraftVersion);
if (metadata.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
return new UpdateCheckResult(UpdateCheckStatus.UP_TO_DATE, metadata, null);
}
return new UpdateCheckResult(UpdateCheckStatus.UPDATE_AVAILABLE, metadata, null);
}
catch (UpdateMetadataClient.MetadataException e)
{
if (!e.notFound())
{
return new UpdateCheckResult(UpdateCheckStatus.ERROR, null, e);
}
}
ArtifactMetadata channelLatest;
try
{
channelLatest = fetchPlexChannelLatestMetadata(useCache);
}
catch (UpdateMetadataClient.MetadataException e)
{
return new UpdateCheckResult(UpdateCheckStatus.ERROR, null, e);
}
if (channelLatest.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
return new UpdateCheckResult(UpdateCheckStatus.UP_TO_DATE, channelLatest, null);
}
List<String> supportedVersions = sortedSupportedVersions(channelLatest);
if (!supportedVersions.isEmpty())
{
String minimumVersion = supportedVersions.get(0);
String maximumVersion = supportedVersions.get(supportedVersions.size() - 1);
if (compareMinecraftVersions(runningMinecraftVersion, minimumVersion) < 0)
{
return new UpdateCheckResult(UpdateCheckStatus.MINECRAFT_TOO_OLD, channelLatest, null);
}
if (compareMinecraftVersions(runningMinecraftVersion, maximumVersion) > 0)
{
return new UpdateCheckResult(UpdateCheckStatus.MINECRAFT_TOO_NEW, channelLatest, null);
}
}
return new UpdateCheckResult(UpdateCheckStatus.MINECRAFT_UNLISTED, channelLatest, null);
}
public void sendResultMessage(CommandSender sender, UpdateCheckResult result, int verbosity)
{
switch (result.status())
{
case UP_TO_DATE:
if (verbosity == 2)
{
sendMessage(sender, PlexUtils.messageComponent("updateUpToDate", channel.id()));
}
break;
case UPDATE_AVAILABLE:
if (verbosity >= 1)
{
sendMessage(sender, PlexUtils.messageComponent("updateAvailable", result.metadata().version(), channel.id()));
sendMessage(sender, PlexUtils.messageComponent("updateRunCommand"));
}
break;
case MINECRAFT_TOO_OLD:
if (verbosity >= 1)
{
List<String> supportedVersions = sortedSupportedVersions(result.metadata());
sendMessage(sender, PlexUtils.messageComponent("updateRequiresNewerMinecraft",
result.metadata().version(), channel.id(), firstSupportedVersion(supportedVersions), getRunningMinecraftVersion(), String.join(", ", supportedVersions)));
}
break;
case MINECRAFT_TOO_NEW:
if (verbosity >= 1)
{
List<String> supportedVersions = sortedSupportedVersions(result.metadata());
sendMessage(sender, PlexUtils.messageComponent("updateUnsupportedNewerMinecraft",
result.metadata().version(), channel.id(), lastSupportedVersion(supportedVersions), getRunningMinecraftVersion(), String.join(", ", supportedVersions)));
}
break;
case MINECRAFT_UNLISTED:
if (verbosity >= 1)
{
List<String> supportedVersions = sortedSupportedVersions(result.metadata());
sendMessage(sender, PlexUtils.messageComponent("updateRequiresMinecraftVersion",
result.metadata().version(), channel.id(), String.join(", ", supportedVersions), getRunningMinecraftVersion()));
}
break;
case ERROR:
UpdateMetadataClient.MetadataException error = result.error();
if (error.notFound())
{
if (verbosity == 2)
{
sendMessage(sender, PlexUtils.messageComponent("updateMetadataNotFound", channel.id()));
}
break;
}
if (verbosity >= 1)
{
sendMessage(sender, updateMetadataErrorComponent(error));
PlexLog.error("Unable to check for updates: {0}", error.getMessage());
if (error.getCause() != null)
{
error.getCause().printStackTrace();
}
}
break;
}
}
// If verbose is 0, it will display nothing
// If verbose is 1, it will only display a message if there is an update available
// If verbose is 2, it will display all messages
public boolean getUpdateStatusMessage(CommandSender sender, boolean cached, int verbosity)
{
try
{
ArtifactMetadata metadata = fetchLatestPlexMetadata(cached);
if (metadata.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
if (verbosity == 2)
{
sendMessage(sender, PlexUtils.messageComponent("updateUpToDate", channel.id()));
}
return false;
}
if (verbosity >= 1)
{
sendMessage(sender, PlexUtils.messageComponent("updateAvailable", metadata.version(), channel.id()));
sendMessage(sender, PlexUtils.messageComponent("updateRunCommand"));
}
return true;
}
catch (UpdateMetadataClient.MetadataException e)
{
if (verbosity == 2 || (verbosity >= 1 && !e.notFound()))
{
sendMessage(sender, updateMetadataErrorComponent(e));
}
if (!e.notFound())
{
PlexLog.error("Unable to check for updates: {0}", e.getMessage());
if (e.getCause() != null)
{
e.getCause().printStackTrace();
}
}
return false;
}
UpdateCheckResult result = checkForUpdates(cached);
sendResultMessage(sender, result, verbosity);
return result.status() == UpdateCheckStatus.UPDATE_AVAILABLE;
}
public void updateJar(CommandSender sender, String name, boolean module)
public void updateJar(CommandSender sender, ArtifactMetadata metadata, Runnable onSuccess)
{
updateJar(sender, name, module, List.of());
File copyTo = new File(Bukkit.getUpdateFolderFile(), metadata.fileName());
sendMessage(sender, PlexUtils.messageComponent("updateDownloading", metadata.fileName()));
plugin.getApi().scheduler().runAsync(() -> downloadAndInstall(sender, metadata, copyTo, onSuccess));
}
public void installModuleJar(CommandSender sender, String name)
{
updateJar(sender, name, true, List.of(), () -> plugin.getApi().scheduler().runGlobal(() ->
updateJar(sender, name, List.of(), () -> plugin.getApi().scheduler().runGlobal(() ->
{
plugin.getModuleManager().reloadModules();
sendMessage(sender, PlexUtils.messageComponent("moduleRestartRequired"));
@@ -108,31 +212,20 @@ public class UpdateChecker
sendMessage(sender, PlexUtils.messageComponent("moduleUpdateDisabled", moduleFile.getName()));
return;
}
updateJar(sender, moduleFile.getName(), true, moduleFile.getUpdateUrls());
updateJar(sender, moduleFile.getName(), moduleFile.getUpdateUrls());
}
private void updateJar(CommandSender sender, String name, boolean module, List<String> moduleUpdateUrls)
private void updateJar(CommandSender sender, String name, List<String> moduleUpdateUrls)
{
updateJar(sender, name, module, moduleUpdateUrls, null);
updateJar(sender, name, moduleUpdateUrls, null);
}
private void updateJar(CommandSender sender, String name, boolean module, List<String> moduleUpdateUrls, Runnable onSuccess)
private void updateJar(CommandSender sender, String name, List<String> moduleUpdateUrls, Runnable onSuccess)
{
try
{
ArtifactMetadata metadata = module
? metadataClient.fetchModuleLatest(name, plugin.getApi().compatibility().version(), moduleUpdateUrls)
: fetchLatestPlexMetadata(false);
if (!module && metadata.matchesCurrentBuild(plugin.getPluginMeta().getVersion(), BuildInfo.getNumber(), BuildInfo.getCommit()))
{
sendMessage(sender, PlexUtils.messageComponent("updateAlreadyUpToDate", channel.id()));
return;
}
File copyTo = module
? new File(plugin.getModulesFolder(), metadata.fileName())
: new File(Bukkit.getUpdateFolderFile(), metadata.fileName());
ArtifactMetadata metadata = metadataClient.fetchModuleLatest(name, plugin.getApi().compatibility().version(), moduleUpdateUrls);
File copyTo = new File(plugin.getModulesFolder(), metadata.fileName());
sendMessage(sender, PlexUtils.messageComponent("updateDownloading", metadata.fileName()));
plugin.getApi().scheduler().runAsync(() -> downloadAndInstall(sender, metadata, copyTo, onSuccess));
@@ -151,15 +244,15 @@ public class UpdateChecker
}
}
private synchronized ArtifactMetadata fetchLatestPlexMetadata(boolean cached) throws UpdateMetadataClient.MetadataException
private synchronized ArtifactMetadata fetchLatestPlexMetadata(boolean useCache, String runningMinecraftVersion) throws UpdateMetadataClient.MetadataException
{
if (cached)
if (useCache)
{
if (latestPlexMetadata != null)
{
return latestPlexMetadata;
}
if (latestPlexMetadataFailure != null && isPlexMetadataFailureCacheFresh())
if (latestPlexMetadataFailure != null && isPlexMetadataFailureCacheFresh(latestPlexMetadataFailureAtMillis))
{
throw latestPlexMetadataFailure;
}
@@ -167,7 +260,7 @@ public class UpdateChecker
try
{
latestPlexMetadata = metadataClient.fetchPlexLatest(BuildInfo.getMinecraftVersion());
latestPlexMetadata = metadataClient.fetchPlexLatest(runningMinecraftVersion);
latestPlexMetadataFailure = null;
latestPlexMetadataFailureAtMillis = 0L;
return latestPlexMetadata;
@@ -183,9 +276,116 @@ public class UpdateChecker
}
}
private boolean isPlexMetadataFailureCacheFresh()
private synchronized ArtifactMetadata fetchPlexChannelLatestMetadata(boolean useCache) throws UpdateMetadataClient.MetadataException
{
return System.currentTimeMillis() - latestPlexMetadataFailureAtMillis < PLEX_METADATA_FAILURE_CACHE_MILLIS;
if (useCache)
{
if (latestPlexChannelLatestMetadata != null)
{
return latestPlexChannelLatestMetadata;
}
if (latestPlexChannelLatestMetadataFailure != null && isPlexMetadataFailureCacheFresh(latestPlexChannelLatestMetadataFailureAtMillis))
{
throw latestPlexChannelLatestMetadataFailure;
}
}
try
{
latestPlexChannelLatestMetadata = metadataClient.fetchPlexChannelLatest();
latestPlexChannelLatestMetadataFailure = null;
latestPlexChannelLatestMetadataFailureAtMillis = 0L;
return latestPlexChannelLatestMetadata;
}
catch (UpdateMetadataClient.MetadataException e)
{
if (latestPlexChannelLatestMetadata == null)
{
latestPlexChannelLatestMetadataFailure = e;
latestPlexChannelLatestMetadataFailureAtMillis = System.currentTimeMillis();
}
throw e;
}
}
private List<String> sortedSupportedVersions(ArtifactMetadata metadata)
{
List<String> supportedVersions = new ArrayList<>(metadata.minecraftVersions());
supportedVersions.sort(UpdateChecker::compareMinecraftVersions);
return supportedVersions;
}
private String firstSupportedVersion(List<String> supportedVersions)
{
return supportedVersions.isEmpty() ? "unknown" : supportedVersions.get(0);
}
private String lastSupportedVersion(List<String> supportedVersions)
{
return supportedVersions.isEmpty() ? "unknown" : supportedVersions.get(supportedVersions.size() - 1);
}
private static int compareMinecraftVersions(String left, String right)
{
String[] leftParts = left.split("\\.");
String[] rightParts = right.split("\\.");
int length = Math.max(leftParts.length, rightParts.length);
for (int i = 0; i < length; i++)
{
int leftPart = i < leftParts.length ? parseVersionPart(leftParts[i]) : 0;
int rightPart = i < rightParts.length ? parseVersionPart(rightParts[i]) : 0;
if (leftPart != rightPart)
{
return Integer.compare(leftPart, rightPart);
}
}
return 0;
}
private static int parseVersionPart(String part)
{
try
{
return Integer.parseInt(part);
}
catch (NumberFormatException ignored)
{
return -1;
}
}
private boolean isPlexMetadataFailureCacheFresh(long failureAtMillis)
{
return System.currentTimeMillis() - failureAtMillis < PLEX_METADATA_FAILURE_CACHE_MILLIS;
}
private String getRunningMinecraftVersion()
{
String version = Bukkit.getBukkitVersion();
int buildIndex = version.indexOf(".build");
if (buildIndex >= 0)
{
return version.substring(0, buildIndex);
}
int separatorIndex = version.indexOf('-');
if (separatorIndex >= 0)
{
return version.substring(0, separatorIndex);
}
String buildMinecraftVersion = BuildInfo.getMinecraftVersion();
int rangeIndex = buildMinecraftVersion.indexOf(" - ");
if (rangeIndex >= 0)
{
return buildMinecraftVersion.substring(0, rangeIndex);
}
int listIndex = buildMinecraftVersion.indexOf(',');
if (listIndex >= 0)
{
return buildMinecraftVersion.substring(0, listIndex);
}
return buildMinecraftVersion;
}
private void downloadAndInstall(CommandSender sender, ArtifactMetadata metadata, File copyTo, Runnable onSuccess)
+17 -37
View File
@@ -220,24 +220,6 @@ togglePvp: "PVP"
togglePvpLower: "PVP"
toggleChat: "Chat"
toggleChatLower: "chat"
toggleMenuExplosionsName: "<!italic><light_purple>Toggle explosions"
# 0 - The toggle status
toggleMenuExplosionsLore: "<!italic><yellow>Explosions are {0}"
toggleMenuFluidSpreadName: "<!italic><light_purple>Toggle fluid spread"
# 0 - The toggle status
toggleMenuFluidSpreadLore: "<!italic><yellow>Fluid spread is {0}"
toggleMenuDropsName: "<!italic><light_purple>Toggle drops"
# 0 - The toggle status
toggleMenuDropsLore: "<!italic><yellow>Drops are {0}"
toggleMenuRedstoneName: "<!italic><light_purple>Redstone"
# 0 - The toggle status
toggleMenuRedstoneLore: "<!italic><yellow>Redstone is {0}"
toggleMenuPvpName: "<!italic><light_purple>PVP"
# 0 - The toggle status
toggleMenuPvpLore: "<!italic><yellow>PVP is {0}"
toggleMenuChatName: "<!italic><light_purple>Toggle chat"
# 0 - The toggle status
toggleMenuChatLore: "<!italic><yellow>Chat is currently {0}"
specifyLoginMessage: "<red>Please specify a login message."
# 0 - The login message
setOwnLoginMessage: "<gray>Your login message is now:<newline><gray>> <reset>{0}"
@@ -312,27 +294,10 @@ prismRollbackMessage: "<gray>Rolled back {0} activities"
# 0 - Error message returned from Prism
prismRollbackError: "<red>Rollback failed: {0}"
prismNoResult: "<gray>No activities have been rolled back"
pageNext: "<light_purple>Next Page"
pagePrevious: "<light_purple>Previous Page"
pageClose: "<red>Close"
pageableNotInitialized: "<red>Looks like this inventory was not initialized! Please contact a developer to report this."
pageNotFound: "<red>Could not find a page to open"
punishmentMenuTitle: "<aqua><bold>Punishments"
# 0 - The player
punishmentPlayerItem: "<!italic><yellow>{0}"
# 0 - The player
punishedPlayerMenuTitle: "<red><bold>Punishments - {0}"
punishmentPlayerNotFound: '<red>This player does not exist. Try doing /punishments \<player> instead.'
# 0 - The punishment type
punishmentItemTitle: "<!italic><red>{0}"
# 0 - The punisher
punishmentItemPunisher: "<!italic><red>By: <gold>{0}"
# 0 - The issue date
punishmentItemIssued: "<!italic><red>Issued: <gold>{0}"
# 0 - The expiration date
punishmentItemExpires: "<!italic><red>Expire(d/s): <gold>{0}"
# 0 - The reason
punishmentItemReason: "<!italic><red>Reason: <gold>{0}"
# 0 - The amount of active bans
# 1 - The active ban list
activeBansList: "<gold>Active Bans ({0}): <yellow>{1}"
@@ -343,8 +308,6 @@ updateUpToDate: "<green>Plex is up to date on the {0} channel."
# 1 - The channel
updateAvailable: "<red>Plex {0} is available on the {1} channel."
updateRunCommand: "<red>Run: /plex update"
# 0 - The channel
updateAlreadyUpToDate: "<red>Plex is already up to date on the {0} channel."
# 0 - The file name
updateDownloading: "<green>Downloading latest JAR file: {0}"
updateDownloaded: "<green>New JAR file downloaded and verified successfully."
@@ -354,6 +317,23 @@ updateDirectoryFailed: "<red>Unable to create update directory: {0}"
updateDownloadFailed: "<red>Something went wrong while downloading {0}. Please check the log for more information."
# 0 - The channel
updateMetadataNotFound: "<red>No compatible update is available on the {0} channel."
# 0 - The Plex version
# 1 - The channel
# 2 - The supported Minecraft versions
# 3 - The running Minecraft version
updateRequiresMinecraftVersion: "<yellow>No Plex build exists for Minecraft {3}. Plex {0} on the {1} channel supports Minecraft: {2}."
# 0 - The Plex version
# 1 - The channel
# 2 - The oldest supported Minecraft version
# 3 - The running Minecraft version
# 4 - The supported Minecraft versions
updateRequiresNewerMinecraft: "<yellow>Your server is running Minecraft {3}, which is older than any version supported by Plex {0} on the {1} channel (oldest supported: {2}). Supported versions: {4}."
# 0 - The Plex version
# 1 - The channel
# 2 - The newest supported Minecraft version
# 3 - The running Minecraft version
# 4 - The supported Minecraft versions
updateUnsupportedNewerMinecraft: "<yellow>No Plex build exists yet for Minecraft {3}. Plex {0} on the {1} channel supports up to Minecraft {2}. Supported versions: {4}."
# 0 - The error message
updateMetadataError: "<red>There was an error checking update metadata: {0}"
moduleUpdateDisabled: "<yellow>Skipping {0}; module updates are disabled."