diff --git a/src/main/java/de/shiewk/smoderation/inventory/ConfirmationInventory.java b/src/main/java/de/shiewk/smoderation/inventory/ConfirmationInventory.java new file mode 100644 index 0000000..c75a919 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/inventory/ConfirmationInventory.java @@ -0,0 +1,68 @@ +package de.shiewk.smoderation.inventory; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class ConfirmationInventory implements CustomInventory { + private final Inventory inventory; + private final Player player; + private final String prompt; + private final ItemStack yesStack; + private final ItemStack noStack; + private final Runnable onAccept; + private final Runnable onReject; + private final boolean reversed; + + public ConfirmationInventory(Player player, String prompt, Runnable onAccept, Runnable onReject, boolean reversed) { + this.player = player; + this.prompt = prompt; + this.onAccept = onAccept; + this.onReject = onReject; + this.reversed = reversed; + inventory = Bukkit.createInventory(this, InventoryType.HOPPER, Component.text(this.prompt)); + yesStack = new ItemStack(Material.LIME_STAINED_GLASS_PANE); + noStack = new ItemStack(Material.RED_STAINED_GLASS_PANE); + } + + @Override + public void refresh() { + yesStack.editMeta(meta -> meta.displayName(applyFormatting(Component.text("Yes").color(NamedTextColor.GREEN)))); + noStack.editMeta(meta -> meta.displayName(applyFormatting(Component.text("No").color(NamedTextColor.RED)))); + ItemStack confirmation = new ItemStack(Material.PAPER); + confirmation.editMeta(meta -> meta.displayName(applyFormatting(Component.text(prompt).color(NamedTextColor.GOLD)))); + + inventory.setItem(reversed ? 4 : 0, noStack); + inventory.setItem(2, confirmation); + inventory.setItem(reversed ? 0 : 4, yesStack); + } + + @Override + public void open() { + refresh(); + player.openInventory(getInventory()); + } + + @Override + public void click(ItemStack stack, InventoryClickEvent event) { + if (yesStack.equals(stack)){ + inventory.close(); + onAccept.run(); + } else if (noStack.equals(stack)) { + inventory.close(); + onReject.run(); + } + } + + @Override + public @NotNull Inventory getInventory() { + return inventory; + } +} diff --git a/src/main/java/de/shiewk/smoderation/inventory/SModMenu.java b/src/main/java/de/shiewk/smoderation/inventory/SModMenu.java index bd91bc1..794533c 100644 --- a/src/main/java/de/shiewk/smoderation/inventory/SModMenu.java +++ b/src/main/java/de/shiewk/smoderation/inventory/SModMenu.java @@ -10,12 +10,16 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -26,8 +30,8 @@ import java.util.function.Predicate; public class SModMenu extends PageableCustomInventory { public enum Filter { - ACTIVE("Active punishments", p -> p.until > System.currentTimeMillis()), - OLD("Expired punishments", p -> p.until < System.currentTimeMillis()), + ACTIVE("Active punishments", Punishment::isActive), + OLD("Old punishments", p -> !p.isActive()), ALL("All punishments", p -> true); public static final Material ICON = Material.HOPPER; @@ -60,6 +64,7 @@ public class SModMenu extends PageableCustomInventory { public static final NamedTextColor PRIMARY_COLOR = NamedTextColor.AQUA; public static final NamedTextColor SECONDARY_COLOR = NamedTextColor.GREEN; public static final NamedTextColor INACTIVE_COLOR = NamedTextColor.GRAY; + private static final NamespacedKey PUNISHMENT_STORE_KEY = new NamespacedKey("smod", "punishmentid"); private final Inventory inventory; private final Player player; @@ -201,6 +206,14 @@ public class SModMenu extends PageableCustomInventory { lore.add(applyFormatting(Component.text("Expires: ").color(SECONDARY_COLOR).append(Component.text(expires).color(PRIMARY_COLOR)))); } lore.add(applyFormatting(Component.text("Reason: ").color(SECONDARY_COLOR).append(Component.text(punishment.reason).color(PRIMARY_COLOR)))); + if (punishment.wasCancelled()){ + lore.add(applyFormatting(Component.text("Cancelled by: ").color(NamedTextColor.RED).append(Component.text(PlayerUtil.offlinePlayerName(punishment.cancelledBy())).color(NamedTextColor.GOLD)))); + } else if (punishment.isActive()) { + if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.cancelBan")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.cancelMute"))){ + lore.add(Component.empty()); + lore.add(applyFormatting(Component.text("\u00BB Click to cancel punishment").color(NamedTextColor.GOLD))); + } + } meta.lore(lore); }); return stack; @@ -219,7 +232,16 @@ public class SModMenu extends PageableCustomInventory { for (int i = 0; i < 45; i++) { int ci = i + (getPage() * 45); if (punishments.size() > ci){ - inventory.setItem(i, createPunishmentItem(punishments.get(ci))); + final Punishment punishment = punishments.get(ci); + final ItemStack item = createPunishmentItem(punishment); + if (punishment.isActive()){ + if ((punishment.type == PunishmentType.BAN && player.hasPermission("smod.cancelBan")) || (punishment.type == PunishmentType.MUTE && player.hasPermission("smod.cancelMute"))) { + item.editMeta(meta -> meta.getPersistentDataContainer().set(PUNISHMENT_STORE_KEY, PersistentDataType.LONG, punishment.time)); + } else { + System.out.println("asd"); + } + } + inventory.setItem(i, item); } else { inventory.setItem(i, new ItemStack(Material.AIR)); } @@ -235,6 +257,25 @@ public class SModMenu extends PageableCustomInventory { } else if (stack.equals(sortStack)){ cycleSort(event.isRightClick()); } + final ItemMeta itemMeta = stack.getItemMeta(); + if (itemMeta != null) { + final PersistentDataContainer persistentDataContainer = itemMeta.getPersistentDataContainer(); + final Long timestamp = persistentDataContainer.get(PUNISHMENT_STORE_KEY, PersistentDataType.LONG); + if (timestamp != null) { + final Punishment punishment = container.findByTimestamp(timestamp); + if (punishment != null) { + new ConfirmationInventory(player, "Do you want to cancel this punishment?", () -> { + punishment.cancel(player.getUniqueId()); + player.playSound(player, Sound.BLOCK_NOTE_BLOCK_PLING, 1f, 2f); + this.open(); + }, this::open, false).open(); + } else { + System.out.println("kalsjkdaklsjd"); + } + } else { + System.out.println("asasdasd"); + } + } } } diff --git a/src/main/java/de/shiewk/smoderation/listener/PunishmentListener.java b/src/main/java/de/shiewk/smoderation/listener/PunishmentListener.java index 0ca1f2c..f0420c8 100644 --- a/src/main/java/de/shiewk/smoderation/listener/PunishmentListener.java +++ b/src/main/java/de/shiewk/smoderation/listener/PunishmentListener.java @@ -20,7 +20,7 @@ public class PunishmentListener implements Listener { Punishment punishment = SModeration.container.find(p -> p.type == PunishmentType.BAN && p.to.equals(event.getPlayer().getUniqueId()) - && p.until >= System.currentTimeMillis()); + && p.isActive()); if (punishment != null){ event.disallow(PlayerLoginEvent.Result.KICK_BANNED, punishment.playerMessage()); } @@ -32,7 +32,7 @@ public class PunishmentListener implements Listener { final Punishment punishment = SModeration.container.find(p -> p.type == PunishmentType.MUTE && p.to.equals(player.getUniqueId()) - && p.until >= System.currentTimeMillis()); + && p.isActive()); if (punishment != null) { event.setCancelled(true); player.sendMessage(punishment.playerMessage()); @@ -43,7 +43,7 @@ public class PunishmentListener implements Listener { public void onPunishmentIssue(PunishmentIssueEvent event){ final Punishment punishment = event.getPunishment(); final PunishmentContainer container = event.getContainer(); - final Punishment duplicate = container.find(p -> p.to.equals(punishment.to) && p.type == punishment.type && p.until >= punishment.time); + final Punishment duplicate = container.find(p -> p.to.equals(punishment.to) && p.type == punishment.type && p.isActive()); if (duplicate != null){ container.remove(duplicate); container.add(new Punishment(duplicate.type, duplicate.time, System.currentTimeMillis(), duplicate.by, duplicate.to, duplicate.reason)); diff --git a/src/main/java/de/shiewk/smoderation/punishments/Punishment.java b/src/main/java/de/shiewk/smoderation/punishments/Punishment.java index 9516c85..bf71dca 100644 --- a/src/main/java/de/shiewk/smoderation/punishments/Punishment.java +++ b/src/main/java/de/shiewk/smoderation/punishments/Punishment.java @@ -25,6 +25,7 @@ public class Punishment { public final UUID by; public final UUID to; public final String reason; + private UUID cancelledBy; public Punishment(PunishmentType type, long time, long until, UUID by, UUID to, String reason) { this.type = type; @@ -35,6 +36,25 @@ public class Punishment { this.reason = reason; } + public boolean wasCancelled(){ + return cancelledBy != null; + } + + public UUID cancelledBy() { + return cancelledBy; + } + + public void cancel(UUID cancelledBy){ + if (this.cancelledBy != null){ + throw new IllegalArgumentException("This punishment is already cancelled."); + } + this.cancelledBy = cancelledBy; + } + + public boolean isActive(){ + return until > System.currentTimeMillis() && !wasCancelled(); + } + public static Punishment mute(long time, long until, UUID by, UUID to, String reason){ return new Punishment(PunishmentType.MUTE, time, until, by, to, reason); } @@ -51,7 +71,7 @@ public class Punishment { public byte[] toBytes(){ final byte[] reasonBytes = reason.getBytes(); - ByteBuffer buffer = ByteBuffer.allocate(BUFFER_LENGTH + reasonBytes.length); + ByteBuffer buffer = ByteBuffer.allocate(BUFFER_LENGTH + reasonBytes.length + (cancelledBy != null ? 17 : 1)); buffer.putInt(0, type.ordinal()); buffer.putLong(4, time); buffer.putLong(12, until); @@ -59,6 +79,10 @@ public class Punishment { buffer.put(36, ByteUtil.uuidToBytes(to)); buffer.putInt(40, reason.length()); buffer.put(44, reasonBytes); + buffer.put(44+reasonBytes.length, cancelledBy != null ? (byte) 1 : (byte) 0); + if (cancelledBy != null){ + buffer.put(44+reasonBytes.length+1, ByteUtil.uuidToBytes(cancelledBy)); + } return buffer.array(); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index bdcdc9d..d6ae3e5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,9 @@ version: '${version}' main: de.shiewk.smoderation.SModeration api-version: '1.20' load: STARTUP +authors: + - Shiewk +description: "SModeration is an easy-to-use minecraft plugin for moderating your server." commands: mute: usage: "§cUsage: /mute " @@ -47,4 +50,10 @@ permissions: description: Allows the player to use the SModeration menu. smod.notifications: default: op - description: Allows the player to be notified when a punishment is issued. \ No newline at end of file + description: Allows the player to be notified when a punishment is issued. + smod.cancelMute: + default: op + description: Allows the player to unmute other players. + smod.cancelBan: + default: op + description: Allows the player to unban other players. \ No newline at end of file