Fixed GUI, rate limit, and some NBT checks

This commit is contained in:
2025-03-21 23:39:07 -05:00
parent 5082e6ff6a
commit 0afbbc8549
30 changed files with 357 additions and 770 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,13 +1,22 @@
package me.trouper.sentinel.data.storage;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.github.retrooper.packetevents.protocol.nbt.serializer.NBTSerializer;
import de.tr7zw.changeme.nbtapi.NBTContainer;
import de.tr7zw.changeme.nbtapi.NBTItem;
import io.github.itzispyder.pdk.plugin.builders.ItemBuilder;
import io.github.itzispyder.pdk.utils.misc.config.JsonSerializable;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.utils.ServerUtils;
import me.trouper.sentinel.utils.Text;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -16,22 +25,7 @@ public class NBTStorage implements JsonSerializable<NBTStorage> {
// Mapping from file name to owner UUID (as a String)
public Map<String, String> caughtItems = new HashMap<>();
private final File mappingFile;
private final File storageDir;
public NBTStorage() {
// Create the storage directory: /storage/nbt/ inside the plugin data folder
File dataFolder = Sentinel.getInstance().getDirector().io.getDataFolder();
storageDir = new File(dataFolder, "storage/nbt");
if (!storageDir.exists()) {
storageDir.mkdirs();
}
// The mapping file that stores the file-name to owner UUID mapping
mappingFile = new File(dataFolder, "storage/nbt.json");
mappingFile.getParentFile().mkdirs();
}
/**
* Stores an ItemStack's serialized NBT to a unique file
* and maps the generated file name to the owner UUID.
@@ -41,11 +35,12 @@ public class NBTStorage implements JsonSerializable<NBTStorage> {
*/
public void storeItem(ItemStack item, UUID owner) {
// Generate a unique file name with a .nbt extension
File storageDir = new File(Sentinel.getInstance().getDirector().io.getDataFolder(), "storage/nbt");
String fileName = UUID.randomUUID().toString() + ".nbt";
File file = new File(storageDir, fileName);
try (FileOutputStream fos = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(fos)) {
OutputStreamWriter writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) {
String nbt = serializeItem(item);
writer.write(nbt);
} catch (IOException e) {
@@ -55,25 +50,98 @@ public class NBTStorage implements JsonSerializable<NBTStorage> {
caughtItems.put(fileName, owner.toString());
save();
}
public boolean deleteItem(String fileName) {
File storageDir = new File(Sentinel.getInstance().getDirector().io.getDataFolder(), "storage/nbt");
File file = new File(storageDir, fileName);
caughtItems.remove(fileName);
save();
return file.delete();
}
/**
* Placeholder for item serialization.
* Replace this with an actual NBT serialization logic.
*
* @param item the ItemStack to serialize
* @return a String representing the NBT data of the item
*/
private String serializeItem(ItemStack item) {
return item.toString();
public static ItemStack getItem(String fileName) {
File storageDir = new File(Sentinel.getInstance().getDirector().io.getDataFolder(), "storage/nbt");
File file = new File(storageDir, fileName);
try (FileInputStream fis = new FileInputStream(file)) {
StringBuilder b64 = new StringBuilder();
int content;
while ((content = fis.read()) != -1) {
b64.append((char) content);
}
//ServerUtils.verbose("Getting item with fis: " + b64);
return deserializeItem(b64.toString());
} catch (FileNotFoundException e) {
Sentinel.getInstance().getDirector().io.nbtStorage.caughtItems.remove(fileName);
Sentinel.getInstance().getDirector().io.nbtStorage.save();
return new ItemBuilder().material(Material.STRUCTURE_VOID)
.name(Text.color("&cFile not found."))
.lore(Text.color("&7This item no longer exists and has been removed from the list."))
.build();
} catch (IOException e) {
e.printStackTrace();
return new ItemBuilder().material(Material.STRUCTURE_VOID)
.name(Text.color("&cUnknown IO exception."))
.lore(Text.color("&4Check Console."))
.build();
}
}
// Make a deserialize method too.
public static String serializeItem(ItemStack item) {
if (item == null) {
return null;
}
try {
// Serialize ItemStack to a Map
Map<String, Object> serializedItem = item.serialize();
// Save the Map into a YAML configuration
YamlConfiguration config = new YamlConfiguration();
config.set("item", serializedItem);
String yamlString = config.saveToString();
// Encode YAML string to Base64
return Base64.getEncoder().encodeToString(yamlString.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static ItemStack deserializeItem(String data) {
if (data == null || data.isEmpty()) {
return null;
}
try {
// Decode Base64 to YAML string
byte[] decodedData = Base64.getDecoder().decode(data);
String yamlString = new String(decodedData, StandardCharsets.UTF_8);
// Load YAML configuration from string
YamlConfiguration config = new YamlConfiguration();
config.loadFromString(yamlString);
// Extract the serialized Map from the configuration
ConfigurationSection itemSection = config.getConfigurationSection("item");
if (itemSection == null) {
return null; // Invalid data
}
// Convert ConfigurationSection to a nested Map
Map<String, Object> serializedItem = itemSection.getValues(true);
// Deserialize the Map back into an ItemStack
return ItemStack.deserialize(serializedItem);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public File getFile() {
return mappingFile;
File file = new File(Sentinel.getInstance().getDirector().io.getDataFolder(), "storage/nbt.json");
new File(Sentinel.getInstance().getDirector().io.getDataFolder(), "storage/nbt").mkdirs();
file.getParentFile().mkdirs();
return file;
}
}
}

View File

@@ -76,47 +76,57 @@ public class CommandBlockHolder {
}
public boolean present() {
if (this.loc.isUUID()) {
Entity cart = Bukkit.getEntity(this.loc.toUIID());
if (!(cart instanceof CommandMinecart cm)) return false;
return this.command.equals(cm.getCommand());
} else {
Location where = loc.translate();
boolean preLoaded = where.isChunkLoaded();
where.getChunk().load(false);
Block b = where.getBlock();
if (!(b.getState() instanceof CommandBlock c) || !(b.getBlockData() instanceof org.bukkit.block.data.type.CommandBlock cb)) {
ServerUtils.verbose(1,"Block is not present due to not being a command block. Whitelisted: %s",this.isWhitelisted());
if (!this.isWhitelisted()) this.delete();
return false;
try {
if (this.loc.isUUID()) {
Entity cart = Bukkit.getEntity(this.loc.toUIID());
if (!(cart instanceof CommandMinecart cm)) return false;
return this.command.equals(cm.getCommand());
} else {
Location where = loc.translate();
boolean preLoaded = where.isChunkLoaded();
if (!where.isChunkLoaded()) where.getChunk().load(false);
Block b = where.getBlock();
if (!(b.getState() instanceof CommandBlock c) || !(b.getBlockData() instanceof org.bukkit.block.data.type.CommandBlock cb)) {
ServerUtils.verbose(1,"Block is not present due to not being a command block. Whitelisted: %s",this.isWhitelisted());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!this.getDirection().equals(cb.getFacing())) {
ServerUtils.verbose("Block is not present due to facing mismatch. Should be '%s', is '%s'",this.facing(),cb.getFacing());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!this.getType().equals(c.getType())) {
ServerUtils.verbose("Block is not present due to type mismatch. Should be '%s', is '%s'",this.type(),c.getType());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!this.command().equals(c.getCommand())) {
ServerUtils.verbose("Block is not present due to command mismatch. Should be '%s', is '%s'",this.command(),c.getCommand());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (this.isConditional() != cb.isConditional()) {
ServerUtils.verbose("Block is not present due to conditional mismatch.");
if (!this.isWhitelisted()) this.delete();
return false;
}
if (this.isAuto() != (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1)) {
ServerUtils.verbose("Block is not present due to auto mismatch.");
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!preLoaded) where.getChunk().unload();
return true;
}
if (!this.getDirection().equals(cb.getFacing())) {
ServerUtils.verbose("Block is not present due to facing mismatch. Should be '%s', is '%s'",this.facing(),cb.getFacing());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!this.getType().equals(c.getType())) {
ServerUtils.verbose("Block is not present due to type mismatch. Should be '%s', is '%s'",this.type(),c.getType());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!this.command().equals(c.getCommand())) {
ServerUtils.verbose("Block is not present due to command mismatch. Should be '%s', is '%s'",this.command(),c.getCommand());
if (!this.isWhitelisted()) this.delete();
return false;
}
if (this.isConditional() != cb.isConditional()) {
ServerUtils.verbose("Block is not present due to conditional mismatch.");
if (!this.isWhitelisted()) this.delete();
return false;
}
if (this.isAuto() != (c.getPersistentDataContainer().getOrDefault(Sentinel.getInstance().getNamespace("auto"), PersistentDataType.BYTE,(byte) 0) == (byte) 1)) {
ServerUtils.verbose("Block is not present due to auto mismatch.");
if (!this.isWhitelisted()) this.delete();
return false;
}
if (!preLoaded) where.getChunk().unload();
return true;
} catch (IllegalStateException ex) {
ServerUtils.verbose("Do not check present command blocks asynchronously. Bukkit has something to say about this.");
ex.printStackTrace();
ServerUtils.verbose("Not present because the command block is not loaded. I really should make this not call async, and just have a variable that I update every so often...");
return false;
}
}

View File

@@ -13,10 +13,7 @@ import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.types.IPLocation;
import me.trouper.sentinel.data.types.SerialLocation;
import me.trouper.sentinel.server.events.extras.ShadowRealmEvents;
import me.trouper.sentinel.utils.IPUtils;
import me.trouper.sentinel.utils.ImageUtils;
import me.trouper.sentinel.utils.Random;
import me.trouper.sentinel.utils.Text;
import me.trouper.sentinel.utils.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
@@ -36,6 +33,10 @@ import java.util.concurrent.atomic.AtomicInteger;
public class ExtraCommand implements CustomCommand {
@Override
public void dispatchCommand(CommandSender sender, Command command, String s, Args args) {
if (!PlayerUtils.isTrusted(sender)) {
sender.sendMessage(Sentinel.getInstance().getDirector().io.lang.permissions.noTrust);
return;
}
if (args.getSize() < 2) {
sender.sendMessage(Text.prefix("""
&r&6Extra's &7Guide&f:
@@ -106,7 +107,7 @@ public class ExtraCommand implements CustomCommand {
private void crashPlayer(CommandSender sender, Player victim, String target) {
var player = PacketEvents.getAPI().getPlayerManager().getUser(victim);
player.sendPacket(new WrapperPlayServerUpdateViewDistance(4000));
player.sendPacket(new WrapperPlayServerUpdateViewDistance(32000));
sender.sendMessage(Text.prefix("Crashing %s.".formatted(target)));
}

View File

@@ -142,7 +142,6 @@ public abstract class AbstractViolation implements CustomListener {
itemInfo.addKeyValue(Sentinel.getInstance().getDirector().io.lang.violations.protections.infoNode.hasLore,item.getItemMeta().hasLore() ? Sentinel.getInstance().getDirector().io.lang.generic.yes : Sentinel.getInstance().getDirector().io.lang.generic.no);
itemInfo.addKeyValue(Sentinel.getInstance().getDirector().io.lang.violations.protections.infoNode.hasAttributes,item.getItemMeta().hasAttributeModifiers() ? Sentinel.getInstance().getDirector().io.lang.generic.yes : Sentinel.getInstance().getDirector().io.lang.generic.no);
itemInfo.addKeyValue(Sentinel.getInstance().getDirector().io.lang.violations.protections.infoNode.hasEnchants,item.getItemMeta().hasEnchants() ? Sentinel.getInstance().getDirector().io.lang.generic.yes : Sentinel.getInstance().getDirector().io.lang.generic.no);
itemInfo.addField(Sentinel.getInstance().getDirector().io.lang.violations.protections.infoNode.nbtStored, FileUtils.createNBTLog(item));
}
return itemInfo;

View File

@@ -31,7 +31,7 @@ import me.trouper.sentinel.server.gui.config.chat.ProfanityFilterGUI;
import me.trouper.sentinel.server.gui.config.chat.SpamFilterGUI;
import me.trouper.sentinel.server.gui.config.chat.UnicodeFilterGUI;
import me.trouper.sentinel.server.gui.config.chat.UrlFilterGUI;
import me.trouper.sentinel.server.gui.whitelist.NewWhitelistGUI;
import me.trouper.sentinel.server.gui.whitelist.WhitelistGUI;
import me.trouper.sentinel.utils.PlayerUtils;
import me.trouper.sentinel.utils.ServerUtils;
import org.bukkit.entity.Player;
@@ -60,7 +60,7 @@ public class ChatEvent implements CustomListener {
UrlFilterGUI.updater.invokeCallbacks(e);
ProfanityFilterGUI.updater.invokeCallbacks(e);
SpamFilterGUI.updater.invokeCallbacks(e);
NewWhitelistGUI.updater.invokeCallbacks(e);
WhitelistGUI.updater.invokeCallbacks(e);
DangerousCommand.updater.invokeCallbacks(e);
LoggedCommand.updater.invokeCallbacks(e);
SpecificCommand.updater.invokeCallbacks(e);

View File

@@ -24,6 +24,7 @@ import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class CreativeHotbar extends AbstractViolation {
@@ -45,7 +46,10 @@ public class CreativeHotbar extends AbstractViolation {
if (!new RateLimitCheck().passes(new Pair<>(p,i))) {
List<String> punishmentCommands = new ArrayList<>();
for (String punishmentCommand : Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.punishmentCommands) {
punishmentCommands.add(punishmentCommand.formatted(RateLimitCheck.dataUsed.get(p.getUniqueId()),Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitBytes));
try {
punishmentCommand = punishmentCommand.formatted(RateLimitCheck.dataUsed.get(p.getUniqueId()),Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitBytes);
} catch (Exception ignored) {}
punishmentCommands.add(punishmentCommand);
}
ServerUtils.verbose("Player flags rate limit, performing action");
@@ -69,8 +73,7 @@ public class CreativeHotbar extends AbstractViolation {
if (new ItemCheck().passes(i)) return;
ServerUtils.verbose("NBT: Item doesn't pass, performing action");
Sentinel.getInstance().getDirector().io.nbtStorage.caughtItems.put(NBTStorage.toB64(i),p.getUniqueId().toString());
Sentinel.getInstance().getDirector().io.nbtStorage.save();
Sentinel.getInstance().getDirector().io.nbtStorage.storeItem(i, p.getUniqueId());
ActionConfiguration.Builder config = new ActionConfiguration.Builder()
.setEvent(e)

View File

@@ -4,7 +4,6 @@ import io.github.itzispyder.pdk.plugin.gui.CustomGui;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.server.gui.config.ConfigGUI;
import me.trouper.sentinel.server.gui.nbt.NBTGui;
import me.trouper.sentinel.server.gui.whitelist.NewWhitelistGUI;
import me.trouper.sentinel.server.gui.whitelist.WhitelistGUI;
import me.trouper.sentinel.utils.PlayerUtils;
import me.trouper.sentinel.utils.Text;
@@ -32,7 +31,7 @@ public class MainGUI {
.build();
private void openWhitelist(InventoryClickEvent e) {
e.getWhoClicked().openInventory(new NewWhitelistGUI().createGUI((Player) e.getWhoClicked()).getInventory());
e.getWhoClicked().openInventory(new WhitelistGUI().createGUI((Player) e.getWhoClicked()).getInventory());
}
private void openNBT(InventoryClickEvent e) {
e.getWhoClicked().openInventory(new NBTGui().createGUI((Player) e.getWhoClicked()).getInventory());

View File

@@ -3,7 +3,6 @@ package me.trouper.sentinel.server.gui;
import io.github.itzispyder.pdk.plugin.builders.ItemBuilder;
import io.github.itzispyder.pdk.plugin.gui.CustomGui;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.types.CommandBlockHolder;
import me.trouper.sentinel.utils.ServerUtils;
import me.trouper.sentinel.utils.Text;
import org.bukkit.Bukkit;
@@ -13,8 +12,10 @@ import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class PaginatedGUI<T> {
@@ -24,6 +25,10 @@ public abstract class PaginatedGUI<T> {
protected static final Map<UUID, FilterOperator> chosenOperator = new HashMap<>();
protected abstract CustomGui backGUI();
protected boolean isAsynchronous() {
return false;
};
public CustomGui createGUI(Player p) {
ServerUtils.verbose("Creating GUI for player: %s", p.getName());
@@ -31,7 +36,7 @@ public abstract class PaginatedGUI<T> {
return CustomGui.create()
.title(getTitle(p))
.size(54)
.onDefine(inv -> setupPage(p, inv))
.onDefine(inv -> setupPage(p, inv, isAsynchronous()))
.defineMain(e -> handleMainClick(p, e))
.define(45, createNavigationItem("Previous", page - 1), e -> changePage(p, -1))
.define(49, createFilterItem(p), e -> openFilterMenu(p))
@@ -41,29 +46,63 @@ public abstract class PaginatedGUI<T> {
protected abstract String getTitle(Player p);
protected void setupPage(Player p, Inventory inv) {
ServerUtils.verbose(1,"Setting up page for player: %s", p.getName());
protected void setupPage(Player p, Inventory inv, boolean runAsynchronously) {
ServerUtils.verbose(1, "Setting up page for player: %s", p.getName());
int page = currentPages.compute(p.getUniqueId(), (k, v) -> realizePage(p, v == null ? 0 : v));
List<T> filtered = filterEntries(p, chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND));
ServerUtils.verbose(1,"Current page: %d, Total entries: %d", page, filtered.size());
// Clear previous items
for (int i = 0; i < ITEMS_PER_PAGE; i++) {
inv.setItem(i, null);
}
// Add paginated items
for (int i = page * ITEMS_PER_PAGE; i < (page + 1) * ITEMS_PER_PAGE && i < filtered.size(); i++) {
T item = filtered.get(i);
inv.setItem(i % ITEMS_PER_PAGE, createDisplayItem(item));
}
// Add persistent bottom items
FilterOperator operator = chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND);
// Add persistent bottom items (navigation and filter)
inv.setItem(45, createNavigationItem("Previous", realizePage(p, page - 1)));
inv.setItem(49, createFilterItem(p));
inv.setItem(53, createNavigationItem("Next", realizePage(p, page + 1)));
}
// Fill the remaining bottom slots with red stained glass
for (int slot : new int[]{46, 47, 48, 50, 51, 52}) {
inv.setItem(slot, createPlaceholderItem(true));
}
Runnable task = ()->{
List<T> filtered = filterEntries(p, operator);
int totalEntries = filtered.size();
int startIndex = page * ITEMS_PER_PAGE;
int endIndex = Math.min(startIndex + ITEMS_PER_PAGE, totalEntries);
List<T> pageEntries = filtered.subList(startIndex, endIndex);
int pageSize = pageEntries.size();
AtomicInteger remaining = new AtomicInteger(pageSize);
// Process each entry and update GUI as each item loads
for (int i = 0; i < pageSize; i++) {
T entry = pageEntries.get(i);
ItemStack displayItem = createDisplayItem(entry);
int slot = i;
Bukkit.getScheduler().runTask(Sentinel.getInstance(), () -> {
inv.setItem(slot, displayItem);
if (remaining.decrementAndGet() == 0) {
// Update remaining main slots and bottom slots to lime
for (int bottomSlot : new int[]{46, 47, 48, 50, 51, 52}) {
inv.setItem(bottomSlot, createPlaceholderItem(false));
}
}
});
}
// Handle case where there are no items
if (pageSize == 0) {
Bukkit.getScheduler().runTask(Sentinel.getInstance(), () -> {
for (int bottomSlot : new int[]{46, 47, 48, 50, 51, 52}) {
inv.setItem(bottomSlot, createPlaceholderItem(false));
}
});
}
};
// Start async loading of items
if (runAsynchronously) Bukkit.getScheduler().runTaskAsynchronously(Sentinel.getInstance(), task);
else task.run();
}
protected abstract void handleMainClick(Player p, InventoryClickEvent e);
protected abstract ItemStack createDisplayItem(T item);
@@ -130,6 +169,15 @@ public abstract class PaginatedGUI<T> {
.build();
}
private ItemStack createPlaceholderItem(boolean isRed) {
Material material = isRed ? Material.RED_STAINED_GLASS_PANE : Material.LIME_STAINED_GLASS_PANE;
String name = isRed ? "&cComputing Entries..." : "&aAll Entries Loaded.";
return new ItemBuilder()
.material(material)
.name(Text.color(name))
.build();
}
private ItemStack createFilterItem(Player p) {
List<String> operatorList = new ArrayList<>();
FilterOperator chosen = chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND);

View File

@@ -1,110 +0,0 @@
package me.trouper.sentinel.server.gui.nbt;
import io.github.itzispyder.pdk.plugin.builders.ItemBuilder;
import io.github.itzispyder.pdk.plugin.gui.CustomGui;
import io.github.itzispyder.pdk.utils.misc.Pair;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.storage.NBTStorage;
import me.trouper.sentinel.server.gui.MainGUI;
import me.trouper.sentinel.server.gui.PaginatedGUI;
import me.trouper.sentinel.utils.ServerUtils;
import me.trouper.sentinel.utils.Text;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.stream.Collectors;
public class NBTGui extends PaginatedGUI<Map.Entry<String,String>> {
private final NBTStorage nbtStorage;
public NBTGui() {
this.nbtStorage = Sentinel.getInstance().getDirector().io.nbtStorage;
}
@Override
protected CustomGui backGUI() {
return new MainGUI().home;
}
@Override
protected String getTitle(Player p) {
return Text.color("&6&lItem Ownership &7(" + getFilterCount(p) + " items)");
}
@Override
protected void handleMainClick(Player p, InventoryClickEvent e) {
int slot = e.getSlot();
if (slot >= 45) return;
if (e.getInventory().getItem(slot) == null) return;
int page = currentPages.compute(p.getUniqueId(), (k, v) -> realizePage(p, v == null ? 0 : v));
List<Map.Entry<String, String>> filtered = filterEntries(p, chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND));
int index = page * ITEMS_PER_PAGE + slot;
if (index < filtered.size()) {
Map.Entry<String, String> entry = filtered.get(index);
ItemStack item = NBTStorage.toItem(entry.getKey());
if (item != null) {
if (e.isLeftClick()) {
p.getInventory().addItem(item);
p.playSound(p.getLocation(), Sound.ENTITY_ITEM_PICKUP, 1, 1F);
} else if (e.isRightClick()) {
nbtStorage.caughtItems.remove(entry.getKey());
p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1, 2F);
p.openInventory(createGUI(p).getInventory());
}
}
}
}
@Override
protected ItemStack createDisplayItem(Map.Entry<String, String> entry) {
ItemStack item = NBTStorage.toItem(entry.getKey());
if (item == null) return null;
List<String> lore = new ArrayList<>();
lore.add(Text.color("&7Owner: " + Bukkit.getOfflinePlayer(UUID.fromString(entry.getValue())).getName()));
lore.add("");
lore.add(Text.color("&eLeft-Click to give item"));
lore.add(Text.color("&eRight-Click to delete item"));
return new ItemBuilder()
.material(item.getType())
.name(Text.color("&b" + item.getType().name()))
.lore(lore)
.build();
}
@Override
protected void addFilterItems(CustomGui.GuiBuilder filterGui, Player p, Set<String> filters) {
// Add any specific filter items here if needed
}
@Override
protected List<Map.Entry<String, String>> filterEntries(Player p, FilterOperator operator) {
Set<String> filters = activeFilters.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>());
ServerUtils.verbose("Filtering entries for %s. Current: ", p, filters.toString());
return nbtStorage.caughtItems.entrySet().stream()
.filter(entry -> {
if (filters.isEmpty()) return true;
boolean result = (operator == FilterOperator.AND); // AND starts true, OR starts false
for (String filter : filters) {
boolean conditionMet = switch (filter) {
case "OWNER" -> entry.getValue().equals(p.getUniqueId().toString());
default -> false;
};
result = operator.apply(result, conditionMet);
// Early exit for AND (false means no need to check further)
if (operator == FilterOperator.AND && !result) return false;
// Early exit for OR (true means we already pass)
if (operator == FilterOperator.OR && result) return true;
}
return result;
})
.collect(Collectors.toList());
}
}

View File

@@ -1,241 +0,0 @@
package me.trouper.sentinel.server.gui.whitelist;
import io.github.itzispyder.pdk.commands.Args;
import io.github.itzispyder.pdk.plugin.builders.ItemBuilder;
import io.github.itzispyder.pdk.plugin.gui.CustomGui;
import io.github.itzispyder.pdk.utils.misc.config.ConfigUpdater;
import io.papermc.paper.event.player.AsyncChatEvent;
import me.trouper.sentinel.Sentinel;
import me.trouper.sentinel.data.config.ViolationConfig;
import me.trouper.sentinel.data.types.CommandBlockHolder;
import me.trouper.sentinel.server.gui.Items;
import me.trouper.sentinel.server.gui.MainGUI;
import me.trouper.sentinel.server.gui.PaginatedGUI;
import me.trouper.sentinel.utils.ServerUtils;
import me.trouper.sentinel.utils.Text;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
public class NewWhitelistGUI extends PaginatedGUI<CommandBlockHolder> {
private static final Map<UUID, String> chosenPlayer = new HashMap<>();
@Override
protected CustomGui backGUI() {
return new MainGUI().home;
}
@Override
protected String getTitle(Player p) {
return Text.color("&6&lCommand Blocks &7(" + getFilterCount(p) + " filters)");
}
@Override
protected void handleMainClick(Player p, InventoryClickEvent e) {
int slot = e.getSlot();
if (slot >= 45) return;
if (e.getInventory().getItem(slot) == null) return;
int page = currentPages.compute(p.getUniqueId(), (k, v) -> realizePage(p, v == null ? 0 : v));
List<CommandBlockHolder> filtered = filterEntries(p, chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND));
int index = page * ITEMS_PER_PAGE + slot;
if (index < filtered.size()) {
CommandBlockHolder holder = filtered.get(index);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1, 0.8F);
openManagementMenu(p, holder);
}
}
@Override
protected ItemStack createDisplayItem(CommandBlockHolder holder) {
Material type = holder.getType();
String name = holder.isCart() ?
"Minecart: " + holder.loc().toUIID() :
String.format("X: %d, Y: %d, Z: %d",
(int) holder.loc().x(),
(int) holder.loc().y(),
(int) holder.loc().z());
List<String> lore = new ArrayList<>();
lore.add(Text.color("&7Owner: " + Bukkit.getOfflinePlayer(holder.owner()).getName()));
lore.add(Text.color("&7Command: &f" + holder.command()));
lore.add(Text.color("&7Type: &f" + holder.type()));
lore.add(Text.color("&7Whitelisted: " + (holder.isWhitelisted() ? "&aYes" : "&cNo")));
lore.add(Text.color("&7Present: " + (holder.present() ? "&aYes" : "&cNo")));
lore.add("");
lore.add(Text.color("&eClick to manage!"));
return new ItemBuilder()
.material(type)
.name(Text.color("&b" + name))
.lore(lore)
.build();
}
@Override
protected void addFilterItems(CustomGui.GuiBuilder filterGui, Player p, Set<String> filters) {
filterGui.define(0, createFilterToggleItem("Your Blocks", Material.PLAYER_HEAD, filters.contains("OWNER")), e -> toggleFilter(p, "OWNER"));
filterGui.define(1, createFilterToggleItem("Other Owners", Material.SPYGLASS, filters.contains("OTHER_OWNERS")), e -> toggleFilter(p, "OTHER_OWNERS"));
filterGui.define(2, createFilterToggleItem("Current World", Material.TARGET, filters.contains("CURRENT_WORLD")), e -> toggleFilter(p, "CURRENT_WORLD"));
filterGui.define(3, createFilterToggleItem("Whitelisted Blocks", Material.PAPER, filters.contains("WHITELISTED")), e -> toggleFilter(p, "WHITELISTED"));
filterGui.define(4, createFilterToggleItem("Not Whitelisted Only", Material.BARRIER, filters.contains("NOT_WHITELISTED")), e -> toggleFilter(p, "NOT_WHITELISTED"));
filterGui.define(5, createFilterToggleItem("Missing Command Blocks", Material.GLASS, filters.contains("NOT_PRESENT")), e -> toggleFilter(p, "NOT_PRESENT"));
filterGui.define(6, createFilterToggleItem("Repeating Command Blocks", Material.REPEATING_COMMAND_BLOCK, filters.contains("REPEAT")), e -> toggleFilter(p, "REPEAT"));
filterGui.define(7, createFilterToggleItem("Chain Command Blocks", Material.CHAIN_COMMAND_BLOCK, filters.contains("CHAIN")), e -> toggleFilter(p, "CHAIN"));
filterGui.define(8, createFilterToggleItem("Impulse Command Blocks", Material.COMMAND_BLOCK, filters.contains("IMPULSE")), e -> toggleFilter(p, "IMPULSE"));
filterGui.define(9, createFilterToggleItem("Minecart Commands", Material.COMMAND_BLOCK_MINECART, filters.contains("MINECART")), e -> toggleFilter(p, "MINECART"));
filterGui.define(10, createFilterToggleItemValue("Specific Player",Material.BOW,filters.contains("USER"),chosenPlayer.getOrDefault(p.getUniqueId(),"null")),
e -> {
if (e.isLeftClick()) toggleFilter(p, "USER");
else if (e.isRightClick()) {
queuePlayer(p,(cfg,value)->{
chosenPlayer.put(p.getUniqueId(),value.getAll().toString());
},chosenPlayer.getOrDefault(p.getUniqueId(),"null"));
}
});
}
public static ConfigUpdater<AsyncChatEvent, ViolationConfig> updater = new ConfigUpdater<>(Sentinel.getInstance().getDirector().io.violationConfig);
protected void queuePlayer(Player player, BiConsumer<ViolationConfig, Args> action, String currentValue) {
MainGUI.awaitingCallback.add(player.getUniqueId());
player.closeInventory();
updater.queuePlayer(player, 20*60, (e)->{
e.setCancelled(true);
return LegacyComponentSerializer.legacySection().serialize(e.message());
}, (cfg, newValue) -> {
action.accept(cfg,new Args(newValue.split("\\s+")));
player.sendMessage(Text.prefix("Value updated successfully"));
openFilterMenu(player);
});
player.sendMessage(Component.text(Text.prefix("Enter the new value in chat. The value is currently set to &b%s&7. (Click to insert)".formatted(currentValue))).clickEvent(ClickEvent.suggestCommand(currentValue)));
}
@Override
protected List<CommandBlockHolder> filterEntries(Player p, FilterOperator operator) {
Set<String> filters = activeFilters.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>());
ServerUtils.verbose("Filtering entries for %s. Current: ", p, filters.toString());
return Sentinel.getInstance().getDirector().io.commandBlocks.holders.stream()
.filter(holder -> {
if (filters.isEmpty()) return true;
boolean result = (operator == FilterOperator.AND); // AND starts true, OR starts false
for (String filter : filters) {
boolean conditionMet = switch (filter) {
case "OWNER" -> holder.owner().equals(p.getUniqueId().toString());
case "CURRENT_WORLD" -> holder.loc().world().equals(p.getWorld().getName());
case "OTHER_OWNERS" -> !holder.owner().equals(p.getUniqueId().toString());
case "MINECART" -> holder.getType().equals(Material.COMMAND_BLOCK_MINECART);
case "REPEAT" -> holder.getType().equals(Material.REPEATING_COMMAND_BLOCK);
case "CHAIN" -> holder.getType().equals(Material.CHAIN_COMMAND_BLOCK);
case "IMPULSE" -> holder.getType().equals(Material.COMMAND_BLOCK);
case "WHITELISTED" -> holder.isWhitelisted();
case "NOT_WHITELISTED" -> !holder.isWhitelisted();
case "NOT_PRESENT" -> !holder.present();
case "USER" -> holder.owner().equals(chosenPlayer.get(p.getUniqueId()));
default -> false;
};
result = operator.apply(result, conditionMet);
// Early exit for AND (false means no need to check further)
if (operator == FilterOperator.AND && !result) return false;
// Early exit for OR (true means we already pass)
if (operator == FilterOperator.OR && result) return true;
}
return result;
})
.collect(Collectors.toList());
}
private void openManagementMenu(Player p, CommandBlockHolder holder) {
ServerUtils.verbose("Opening management menu for %s", holder.owner());
boolean whitelisted = holder.isWhitelisted();
CustomGui menu = CustomGui.create()
.title(Text.color("&l ⬇ &6&lManaging Command Block"))
.size(9)
.defineMain(e -> e.setCancelled(true))
.define(0, createDisplayItem(holder))
.define(2, createActionItem(whitelisted ? "Un-Whitelist" : "Whitelist", whitelisted ? Material.BARRIER : Material.PAPER), e -> {
holder.setWhitelisted(!whitelisted);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1F);
openManagementMenu(p, holder);
})
.define(3, createActionItem("Teleport", Material.ENDER_PEARL), e -> {
if (holder.loc().isUUID()) {
// Handle minecart teleport
Entity entity = Bukkit.getEntity(holder.loc().toUIID());
if (entity == null) {
e.getInventory().setItem(e.getSlot(), new ItemBuilder()
.material(Material.BARRIER)
.name("&cTeleport Unavailable")
.lore("&7This entity is not loaded.")
.build());
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1F);
return;
}
p.teleport(entity.getLocation());
} else {
p.teleport(holder.loc().translate());
}
p.playSound(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1, 0.5F);
p.closeInventory();
})
.define(4, createActionItem("Restore", Material.DISPENSER), e -> {
holder.restore();
p.openInventory(createGUI(p).getInventory());
p.playSound(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1, 1F);
})
.define(5, createActionItem("Destroy (Shift-Click)", Material.NETHERITE_PICKAXE), e -> {
if (!e.isShiftClick()) return;
holder.destroy();
p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1, 2F);
p.openInventory(createGUI(p).getInventory());
})
.define(6, createActionItem("Take Ownership", Material.NAME_TAG), e -> {
holder.setOwner(p.getUniqueId().toString());
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_TRADE, 1, 1F);
openManagementMenu(p, holder);
})
.define(8, Items.BACK, e -> {
p.playSound(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1, 0.8F);
p.openInventory(createGUI(p).getInventory());
})
.build();
p.openInventory(menu.getInventory());
}
private ItemStack createActionItem(String name, Material mat) {
return new ItemBuilder()
.material(mat)
.name(Text.color("&b" + name))
.lore(Text.color("&7Click to " + name.toLowerCase()))
.build();
}
private ItemStack createFilterToggleItem(String name, Material mat, boolean active) {
return new ItemBuilder()
.material(mat)
.name(Text.color((active ? "&a" : "&c") + name))
.lore(Text.color("&7Click to " + (active ? "disable" : "enable")))
.build();
}
private ItemStack createFilterToggleItemValue(String name, Material mat, boolean active, String value) {
return new ItemBuilder()
.material(mat)
.name(Text.color((active ? "&a" : "&c") + name))
.lore(Text.color("&7Value&f: &b" + value))
.lore(Text.color("&7Left Click to " + (active ? "disable" : "enable")))
.lore(Text.color("&7Right Click to set value."))
.build();
}
}

View File

@@ -10,6 +10,7 @@ import me.trouper.sentinel.data.config.ViolationConfig;
import me.trouper.sentinel.data.types.CommandBlockHolder;
import me.trouper.sentinel.server.gui.Items;
import me.trouper.sentinel.server.gui.MainGUI;
import me.trouper.sentinel.server.gui.PaginatedGUI;
import me.trouper.sentinel.utils.ServerUtils;
import me.trouper.sentinel.utils.Text;
import net.kyori.adventure.text.Component;
@@ -21,98 +22,44 @@ import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
public class WhitelistGUI {
public class WhitelistGUI extends PaginatedGUI<CommandBlockHolder> {
private static final Map<UUID, String> chosenPlayer = new HashMap<>();
private static final Map<UUID, Integer> currentPages = new HashMap<>();
private static final Map<UUID, Set<Filter>> activeFilters = new HashMap<>();
private static final Map<UUID, FilterOperator> chosenOperator = new HashMap<>();
public CustomGui createGUI(Player p) {
ServerUtils.verbose("Creating GUI for player: %s", p.getName());
int page = currentPages.compute(p.getUniqueId(), (k,v) -> realizePage(p,realizePage(p,(v == null ? 0 : v))));
return CustomGui.create()
.title(Text.color("&6&lCommand Blocks &7(" + getFilterCount(p) + " filters)"))
.size(54)
.onDefine(inv -> setupPage(p, inv))
.defineMain(e -> {
e.setCancelled(true);
handleMainClick(p, e);
})
.define(45, createNavigationItem("Previous",page - 1), e -> {
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_HAT,1,0.9F);
changePage(p, -1);
})
.define(49, createFilterItem(p), e -> {
if (e.isShiftClick()) {
FilterOperator op = chosenOperator.computeIfAbsent(p.getUniqueId(),v-> FilterOperator.AND);
FilterOperator[] values = FilterOperator.values();
chosenOperator.put(p.getUniqueId(),values[(op.ordinal() + 1) % values.length]);
e.getClickedInventory().setItem(e.getSlot(),createFilterItem(p));
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_HAT,1,1.3F);
return;
}
openFilterMenu(p);
})
.define(53, createNavigationItem("Next",page + 1), e -> {
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_HAT,1,1.1F);
changePage(p, 1);
})
.build();
@Override
protected CustomGui backGUI() {
return new MainGUI().home;
}
private void setupPage(Player p, Inventory inv) {
ServerUtils.verbose("Setting up page for player: %s", p.getName());
int page = currentPages.compute(p.getUniqueId(), (k,v) -> realizePage(p,realizePage(p,(v == null ? 0 : v))));
List<CommandBlockHolder> filtered = filterEntries(p,chosenOperator.computeIfAbsent(p.getUniqueId(),v->FilterOperator.AND));
ServerUtils.verbose("Current page: %d, Total entries: %d", page, filtered.size());
// Clear previous items
for (int i = 0; i < 45; i++) {
inv.setItem(i, null);
}
// Add paginated items
for (int i = page * 45; i < (page + 1) * 45 && i < filtered.size(); i++) {
CommandBlockHolder holder = filtered.get(i);
inv.setItem(i % 45, createDisplayItem(holder));
}
// Add persistent bottom items
inv.setItem(45, createNavigationItem("Previous",realizePage(p, page - 1)));
inv.setItem(49, createFilterItem(p));
inv.setItem(53, createNavigationItem("Next", realizePage(p,page + 1)));
@Override
protected String getTitle(Player p) {
return Text.color("&6&lCommand Blocks &7(%s/%s filtered)".formatted(getFilterCount(p),Sentinel.getInstance().getDirector().io.commandBlocks.holders.size()));
}
private void handleMainClick(Player p, InventoryClickEvent e) {
@Override
protected void handleMainClick(Player p, InventoryClickEvent e) {
int slot = e.getSlot();
if (slot >= 45) return;
if (e.getInventory().getItem(slot) == null) return;
int page = currentPages.compute(p.getUniqueId(), (k,v) -> realizePage(p,realizePage(p,(v == null ? 0 : v))));
List<CommandBlockHolder> filtered = filterEntries(p,chosenOperator.computeIfAbsent(p.getUniqueId(),v->FilterOperator.AND));
int index = page * 45 + slot;
int page = currentPages.compute(p.getUniqueId(), (k, v) -> realizePage(p, v == null ? 0 : v));
List<CommandBlockHolder> filtered = filterEntries(p, chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND));
int index = page * ITEMS_PER_PAGE + slot;
if (index < filtered.size()) {
CommandBlockHolder holder = filtered.get(index);
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_CHIME,1,0.8F);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_CHIME, 1, 0.8F);
openManagementMenu(p, holder);
}
}
private ItemStack createDisplayItem(CommandBlockHolder holder) {
//ServerUtils.verbose("Creating Display Item for a command block owned by %s. Type is ", holder.owner(), holder.type());
@Override
protected ItemStack createDisplayItem(CommandBlockHolder holder) {
Material type = holder.getType();
//ServerUtils.verbose("Type material is %s", type.name());
String name = holder.isCart() ?
"Minecart: " + holder.loc().toUIID() :
String.format("X: %d, Y: %d, Z: %d",
@@ -120,24 +67,15 @@ public class WhitelistGUI {
(int) holder.loc().y(),
(int) holder.loc().z());
//ServerUtils.verbose("Name is %s", name);
List<String> lore = new ArrayList<>();
lore.add(Text.color("&7Owner: " + Bukkit.getOfflinePlayer(UUID.fromString(holder.owner())).getName()));
//ServerUtils.verbose("Got owner");
lore.add(Text.color("&7Owner: " + Bukkit.getOfflinePlayer(holder.owner()).getName()));
lore.add(Text.color("&7Command: &f" + holder.command()));
//ServerUtils.verbose("Got command");
lore.add(Text.color("&7Type: &f" + holder.type()));
//ServerUtils.verbose("Got type");
lore.add(Text.color("&7Whitelisted: " + (holder.isWhitelisted() ? "&aYes" : "&cNo")));
//ServerUtils.verbose("Got whitelist status");
lore.add(Text.color("&7Present: " + (holder.present() ? "&aYes" : "&cNo")));
//ServerUtils.verbose("Got Present Status");
lore.add("");
lore.add(Text.color("&eClick to manage!"));
//ServerUtils.verbose("Successfully created item!");
return new ItemBuilder()
.material(type)
.name(Text.color("&b" + name))
@@ -145,58 +83,129 @@ public class WhitelistGUI {
.build();
}
@Override
protected void addFilterItems(CustomGui.GuiBuilder filterGui, Player p, Set<String> filters) {
filterGui.define(0, createFilterToggleItem("Your Blocks", Material.PLAYER_HEAD, filters.contains("OWNER")), e -> toggleFilter(p, "OWNER"));
filterGui.define(1, createFilterToggleItem("Other Owners", Material.SPYGLASS, filters.contains("OTHER_OWNERS")), e -> toggleFilter(p, "OTHER_OWNERS"));
filterGui.define(2, createFilterToggleItem("Current World", Material.TARGET, filters.contains("CURRENT_WORLD")), e -> toggleFilter(p, "CURRENT_WORLD"));
filterGui.define(3, createFilterToggleItem("Whitelisted Blocks", Material.PAPER, filters.contains("WHITELISTED")), e -> toggleFilter(p, "WHITELISTED"));
filterGui.define(4, createFilterToggleItem("Not Whitelisted Only", Material.BARRIER, filters.contains("NOT_WHITELISTED")), e -> toggleFilter(p, "NOT_WHITELISTED"));
filterGui.define(5, createFilterToggleItem("Missing Command Blocks", Material.GLASS, filters.contains("NOT_PRESENT")), e -> toggleFilter(p, "NOT_PRESENT"));
filterGui.define(6, createFilterToggleItem("Repeating Command Blocks", Material.REPEATING_COMMAND_BLOCK, filters.contains("REPEAT")), e -> toggleFilter(p, "REPEAT"));
filterGui.define(7, createFilterToggleItem("Chain Command Blocks", Material.CHAIN_COMMAND_BLOCK, filters.contains("CHAIN")), e -> toggleFilter(p, "CHAIN"));
filterGui.define(8, createFilterToggleItem("Impulse Command Blocks", Material.COMMAND_BLOCK, filters.contains("IMPULSE")), e -> toggleFilter(p, "IMPULSE"));
filterGui.define(9, createFilterToggleItem("Minecart Commands", Material.COMMAND_BLOCK_MINECART, filters.contains("MINECART")), e -> toggleFilter(p, "MINECART"));
filterGui.define(10, createFilterToggleItemValue("Specific Player",Material.BOW,filters.contains("USER"),chosenPlayer.getOrDefault(p.getUniqueId(),"null")),
e -> {
if (e.isLeftClick()) toggleFilter(p, "USER");
else if (e.isRightClick()) {
queuePlayer(p,(cfg,value)->{
chosenPlayer.put(p.getUniqueId(),value.getAll().toString());
},chosenPlayer.getOrDefault(p.getUniqueId(),"null"));
}
});
}
public static ConfigUpdater<AsyncChatEvent, ViolationConfig> updater = new ConfigUpdater<>(Sentinel.getInstance().getDirector().io.violationConfig);
protected void queuePlayer(Player player, BiConsumer<ViolationConfig, Args> action, String currentValue) {
MainGUI.awaitingCallback.add(player.getUniqueId());
player.closeInventory();
updater.queuePlayer(player, 20*60, (e)->{
e.setCancelled(true);
return LegacyComponentSerializer.legacySection().serialize(e.message());
}, (cfg, newValue) -> {
action.accept(cfg,new Args(newValue.split("\\s+")));
player.sendMessage(Text.prefix("Value updated successfully"));
openFilterMenu(player);
});
player.sendMessage(Component.text(Text.prefix("Enter the new value in chat. The value is currently set to &b%s&7. (Click to insert)".formatted(currentValue))).clickEvent(ClickEvent.suggestCommand(currentValue)));
}
@Override
protected List<CommandBlockHolder> filterEntries(Player p, FilterOperator operator) {
Set<String> filters = activeFilters.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>());
ServerUtils.verbose("Filtering entries for %s. Current: ", p, filters.toString());
return Sentinel.getInstance().getDirector().io.commandBlocks.holders.stream()
.filter(holder -> {
if (filters.isEmpty()) return true;
boolean result = (operator == FilterOperator.AND); // AND starts true, OR starts false
for (String filter : filters) {
boolean conditionMet = switch (filter) {
case "OWNER" -> holder.owner().equals(p.getUniqueId().toString());
case "CURRENT_WORLD" -> holder.loc().world().equals(p.getWorld().getName());
case "OTHER_OWNERS" -> !holder.owner().equals(p.getUniqueId().toString());
case "MINECART" -> holder.getType().equals(Material.COMMAND_BLOCK_MINECART);
case "REPEAT" -> holder.getType().equals(Material.REPEATING_COMMAND_BLOCK);
case "CHAIN" -> holder.getType().equals(Material.CHAIN_COMMAND_BLOCK);
case "IMPULSE" -> holder.getType().equals(Material.COMMAND_BLOCK);
case "WHITELISTED" -> holder.isWhitelisted();
case "NOT_WHITELISTED" -> !holder.isWhitelisted();
case "NOT_PRESENT" -> !holder.present();
case "USER" -> holder.owner().equals(chosenPlayer.get(p.getUniqueId()));
default -> false;
};
result = operator.apply(result, conditionMet);
// Early exit for AND (false means no need to check further)
if (operator == FilterOperator.AND && !result) return false;
// Early exit for OR (true means we already pass)
if (operator == FilterOperator.OR && result) return true;
}
return result;
})
.collect(Collectors.toList());
}
private void openManagementMenu(Player p, CommandBlockHolder holder) {
ServerUtils.verbose("Opening management menu for %s", holder.owner());
boolean whitelisted = holder.isWhitelisted();
CustomGui menu = CustomGui.create()
.title(Text.color("&l ⬇ &6&lManaging Command Block"))
.size(9)
.defineMain(e -> e.setCancelled(true))
.define(0,createDisplayItem(holder))
.define(2, createActionItem(whitelisted ? "Un-Whitelist" : "Whitelist", whitelisted ? Material.BARRIER : Material.PAPER), e -> {
.define(0, createDisplayItem(holder))
.define(2, createActionItem(whitelisted ? "Un-Whitelist" : "Whitelist", whitelisted ? Material.BARRIER : Material.PAPER), e -> {
holder.setWhitelisted(!whitelisted);
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_PLING,1,1F);
openManagementMenu(p,holder);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 1, 1F);
openManagementMenu(p, holder);
})
.define(3, createActionItem("Teleport", Material.ENDER_PEARL), e -> {
if (holder.loc().isUUID()) {
// Handle minecart teleport
Entity entity = Bukkit.getEntity(holder.loc().toUIID());
if (entity == null) {
e.getInventory().setItem(e.getSlot(),ItemBuilder.create()
.material(Material.BARRIER)
.name("&cTeleport Unavailable")
.lore("&7This entity is not loaded.")
e.getInventory().setItem(e.getSlot(), new ItemBuilder()
.material(Material.BARRIER)
.name("&cTeleport Unavailable")
.lore("&7This entity is not loaded.")
.build());
p.playSound(p.getLocation(),Sound.BLOCK_NOTE_BLOCK_BASS,1,1F);
p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 1F);
return;
}
p.teleport(entity.getLocation());
} else {
p.teleport(holder.loc().translate());
}
p.playSound(p.getLocation(),Sound.ENTITY_ENDERMAN_TELEPORT,1,0.5F);
p.playSound(p.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1, 0.5F);
p.closeInventory();
})
.define(4, createActionItem("Restore", Material.DISPENSER), e -> {
holder.restore();
p.openInventory(createGUI(p).getInventory());
p.playSound(p.getLocation(),Sound.BLOCK_AMETHYST_BLOCK_RESONATE,1,1F);
p.playSound(p.getLocation(), Sound.BLOCK_AMETHYST_BLOCK_RESONATE, 1, 1F);
})
.define(5, createActionItem("Destroy (Shift-Click)", Material.NETHERITE_PICKAXE), e -> {
if (!e.isShiftClick()) return;
holder.destroy();
p.playSound(p.getLocation(),Sound.ENTITY_GENERIC_EXPLODE,1,2F);
p.playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 1, 2F);
p.openInventory(createGUI(p).getInventory());
})
.define(6,createActionItem("Take Ownership",Material.NAME_TAG), e -> {
.define(6, createActionItem("Take Ownership", Material.NAME_TAG), e -> {
holder.setOwner(p.getUniqueId().toString());
p.playSound(p.getLocation(),Sound.ENTITY_VILLAGER_TRADE,1,1F);
openManagementMenu(p,holder);
p.playSound(p.getLocation(), Sound.ENTITY_VILLAGER_TRADE, 1, 1F);
openManagementMenu(p, holder);
})
.define(8,Items.BACK,e->{
p.playSound(p.getLocation(),Sound.ITEM_BOOK_PAGE_TURN,1,0.8F);
.define(8, Items.BACK, e -> {
p.playSound(p.getLocation(), Sound.ITEM_BOOK_PAGE_TURN, 1, 0.8F);
p.openInventory(createGUI(p).getInventory());
})
.build();
@@ -212,103 +221,6 @@ public class WhitelistGUI {
.build();
}
// Filter handling methods
private enum Filter {
OWNER, CURRENT_WORLD, OTHER_OWNERS,
MINECART, REPEAT, CHAIN, IMPULSE,
WHITELISTED, NOT_WHITELISTED, NOT_PRESENT
}
public enum FilterOperator {
AND, // All conditions must be met
OR, // At least one condition must be met
NAND, // At least one condition must NOT be met
XOR; // Exactly one condition must be met
public boolean apply(boolean currentValue, boolean newCondition) {
return switch (this) {
case AND -> currentValue & newCondition;
case OR -> currentValue | newCondition;
case NAND -> !(currentValue & newCondition);
case XOR -> currentValue ^ newCondition;
};
}
}
private List<CommandBlockHolder> filterEntries(Player p, FilterOperator operator) {
Set<Filter> filters = activeFilters.computeIfAbsent(p.getUniqueId(), v -> new HashSet<>());
ServerUtils.verbose("Filtering entries for %s. Current: ", p,filters.toString());
return Sentinel.getInstance().getDirector().io.commandBlocks.holders.stream()
.filter(holder -> {
if (filters.isEmpty()) return true;
boolean result = (operator == FilterOperator.AND); // AND starts true, OR starts false
for (Filter filter : filters) {
boolean conditionMet = switch (filter) {
case OWNER -> holder.owner().equals(p.getUniqueId().toString());
case CURRENT_WORLD -> holder.loc().world().equals(p.getWorld().getName());
case OTHER_OWNERS -> !holder.owner().equals(p.getUniqueId().toString());
case MINECART -> holder.getType().equals(Material.COMMAND_BLOCK_MINECART);
case REPEAT -> holder.getType().equals(Material.REPEATING_COMMAND_BLOCK);
case CHAIN -> holder.getType().equals(Material.CHAIN_COMMAND_BLOCK);
case IMPULSE -> holder.getType().equals(Material.COMMAND_BLOCK);
case WHITELISTED -> holder.isWhitelisted();
case NOT_WHITELISTED -> !holder.isWhitelisted();
case NOT_PRESENT -> !holder.present();
};
result = operator.apply(result, conditionMet);
// Early exit for AND (false means no need to check further)
if (operator == FilterOperator.AND && !result) return false;
// Early exit for OR (true means we already pass)
if (operator == FilterOperator.OR && result) return true;
}
return result;
})
.collect(Collectors.toList());
}
private void openFilterMenu(Player p) {
ServerUtils.verbose("Creating filter menu for %s", p);
Set<Filter> filters = activeFilters.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>());
CustomGui filterGui = CustomGui.create()
.title(Text.color("&6&lFilters"))
.size(27)
.defineMain(e -> e.setCancelled(true))
.define(0, createFilterToggleItem("Your Blocks", Material.PLAYER_HEAD, filters.contains(Filter.OWNER)),
e -> toggleFilter(p, Filter.OWNER))
.define(1, createFilterToggleItem("Other Owners", Material.SPYGLASS, filters.contains(Filter.OTHER_OWNERS)),
e -> toggleFilter(p, Filter.OTHER_OWNERS))
.define(2, createFilterToggleItem("Current World", Material.TARGET, filters.contains(Filter.CURRENT_WORLD)),
e -> toggleFilter(p, Filter.CURRENT_WORLD))
.define(3, createFilterToggleItem("Whitelisted Blocks", Material.PAPER, filters.contains(Filter.WHITELISTED)),
e -> toggleFilter(p, Filter.WHITELISTED))
.define(4, createFilterToggleItem("Not Whitelisted Only", Material.BARRIER, filters.contains(Filter.NOT_WHITELISTED)),
e -> toggleFilter(p, Filter.NOT_WHITELISTED))
.define(5, createFilterToggleItem("Missing Command Blocks", Material.GLASS, filters.contains(Filter.NOT_PRESENT)),
e -> toggleFilter(p, Filter.NOT_PRESENT))
.define(6, createFilterToggleItem("Repeating Command Blocks", Material.REPEATING_COMMAND_BLOCK, filters.contains(Filter.REPEAT)),
e -> toggleFilter(p, Filter.REPEAT))
.define(7, createFilterToggleItem("Chain Command Blocks", Material.CHAIN_COMMAND_BLOCK, filters.contains(Filter.CHAIN)),
e -> toggleFilter(p, Filter.CHAIN))
.define(8, createFilterToggleItem("Impulse Command Blocks", Material.COMMAND_BLOCK, filters.contains(Filter.IMPULSE)),
e -> toggleFilter(p, Filter.IMPULSE))
.define(9, createFilterToggleItem("Minecart Commands", Material.COMMAND_BLOCK_MINECART, filters.contains(Filter.MINECART)),
e -> toggleFilter(p, Filter.MINECART))
.define(26, Items.BACK,
e-> {
p.playSound(p.getLocation(),Sound.ITEM_BOOK_PAGE_TURN,1,0.8F);
p.openInventory(createGUI(p).getInventory());
})
.build();
p.openInventory(filterGui.getInventory());
}
private ItemStack createFilterToggleItem(String name, Material mat, boolean active) {
return new ItemBuilder()
.material(mat)
@@ -317,60 +229,13 @@ public class WhitelistGUI {
.build();
}
private void toggleFilter(Player p, Filter filter) {
Set<Filter> filters = activeFilters.computeIfAbsent(p.getUniqueId(), k -> new HashSet<>());
ServerUtils.verbose("%s is now toggling the %s filter. Current %s", p,filter,filters);
if (filters.contains(filter)) filters.remove(filter);
else filters.add(filter);
ServerUtils.verbose("Current filters for %s: %s", p,filters);
openFilterMenu(p);
}
private int getFilterCount(Player p) {
return activeFilters.getOrDefault(p.getUniqueId(), new HashSet<>()).size();
}
private void changePage(Player p, int direction) {
int current = currentPages.getOrDefault(p.getUniqueId(), 0);
int newPage = realizePage(p, current + direction);
currentPages.put(p.getUniqueId(), newPage);
p.openInventory(createGUI(p).getInventory());
}
private int realizePage(Player p, int requested) {
int validRequested = Math.max(0, requested);
int totalEntries = filterEntries(p,
chosenOperator.computeIfAbsent(p.getUniqueId(), v -> FilterOperator.AND)).size();
int maxPages = Math.max(0, Math.ceilDiv(totalEntries, 45) - 1);
return Math.min(validRequested, maxPages);
}
private ItemStack createNavigationItem(String direction, int pageTo) {
private ItemStack createFilterToggleItemValue(String name, Material mat, boolean active, String value) {
return new ItemBuilder()
.material(Material.ARROW)
.name(Text.color("&b" + direction + "&7 Page"))
.lore(Text.color("&7 > &b" + pageTo))
.material(mat)
.name(Text.color((active ? "&a" : "&c") + name))
.lore(Text.color("&7Value&f: &b" + value))
.lore(Text.color("&7Left Click to " + (active ? "disable" : "enable")))
.lore(Text.color("&7Right Click to set value."))
.build();
}
private ItemStack createFilterItem(Player p) {
List<String> operatorList = new ArrayList<>();
FilterOperator chosen = chosenOperator.computeIfAbsent(p.getUniqueId(),v->FilterOperator.AND);
for (FilterOperator value : FilterOperator.values()) {
if (value.equals(chosen)) operatorList.add(Text.color("&b&n" + value.name()));
else operatorList.add(Text.color("&b" + value.name()));
}
return new ItemBuilder()
.material(Material.HOPPER)
.name(Text.color("&6&lFilters"))
.lore(Text.color("&7Filters Selected: &e" + getFilterCount(p)))
.lore(Text.color("&7Shift-Click to cycle filter operator."))
.lore(Text.color("&7Operator: "))
.lore(operatorList)
.build();
}
}

View File

@@ -46,63 +46,6 @@ public final class FileUtils {
}
}
public static String createNBTLog(String contents) {
ServerUtils.verbose("FileUtils: Creating NBT log");
String fileName = "nbt_log-" + Random.generateID();
File dataFolder = Sentinel.getInstance().getDirector().io.getDataFolder();
File loggedNBTFolder = new File(dataFolder,"LoggedNBT");
if (!loggedNBTFolder.exists()) {
loggedNBTFolder.mkdirs();
}
File file = new File(loggedNBTFolder, fileName + ".txt");
try {
if (!file.exists()) {
file.createNewFile();
}
BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
writer.append(contents);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return fileName;
}
public static String createNBTLog(ItemStack i) {
ServerUtils.verbose("FileUtils: Creating NBT log");
String item = i.getType().name().toLowerCase() + i.getItemMeta().getAsString();
String fileName = "nbt_log-" + Random.generateID();
File dataFolder = Sentinel.getInstance().getDirector().io.getDataFolder();
File loggedNBTFolder = new File(dataFolder,"LoggedNBT");
if (!loggedNBTFolder.exists()) {
loggedNBTFolder.mkdirs();
}
File file = new File(loggedNBTFolder, fileName + ".txt");
try {
if (!file.exists()) {
file.createNewFile();
}
BufferedWriter writer = new BufferedWriter(new FileWriter(file, true));
writer.append(item);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
return fileName;
}
public static String createCommandLog(String command) {

View File

@@ -4,6 +4,7 @@ package me.trouper.sentinel.utils;
import me.trouper.sentinel.Sentinel;
import org.bukkit.Location;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -115,7 +116,7 @@ public final class Text {
}
public static String cleanName(String type) {
return type.replaceAll("_"," ").toLowerCase();
return type.replaceAll("_"," ").toUpperCase(Locale.US);
}
public static String formatMillis(long millis) {

View File

@@ -8,13 +8,13 @@ import org.bukkit.World;
import org.bukkit.entity.BlockDisplay;
import org.bukkit.entity.Display;
import org.bukkit.entity.Player;
import org.bukkit.util.Consumer;
import org.bukkit.util.Transformation;
import org.bukkit.util.Vector;
import org.joml.AxisAngle4f;
import org.joml.Vector3f;
import java.util.List;
import java.util.function.Consumer;
public class BlockDisplayRaytracer {

View File

@@ -24,7 +24,8 @@ public class EmbedFormatter {
try {
webhook.send(spec);
} catch (IOException e) {
Sentinel.getInstance().getLogger().warning(e.getMessage());
Sentinel.getInstance().getLogger().info("Discord declined the web request: " + e.getMessage());
Sentinel.getInstance().getLogger().info("Please insure your webhook URL is correct, otherwise nothing will be logged to discord.");
success.set(false);
return;
}