1.0.0 SNAPSHOT

This commit is contained in:
redtea committed 2023-07-25 01:32:32 +03:00
1 parent e2748c0d00
commit acce87ea00
96 files changed
+4822

No files matched your search

@@ -0,0 +1,176 @@
package me.redtea.nodropx;
import lombok.Getter;
import me.mattstudios.mf.base.CommandManager;
import me.redtea.nodropx.api.facade.NoDropAPI;
import me.redtea.nodropx.api.facade.impl.NoDropXFacade;
import me.redtea.nodropx.command.NoDropCommand;
import me.redtea.nodropx.factory.item.NoDropItemFactory;
import me.redtea.nodropx.factory.item.impl.NoDropItemFactoryImpl;
import me.redtea.nodropx.factory.text.TextContext;
import me.redtea.nodropx.factory.text.TextServicesFactory;
import me.redtea.nodropx.factory.text.impl.TextServicesFactoryImpl;
import me.redtea.nodropx.gui.facade.GuiFacade;
import me.redtea.nodropx.gui.facade.impl.GuiFacadeImpl;
import me.redtea.nodropx.gui.pages.StorageGui;
import me.redtea.nodropx.libs.bstats.Metrics;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
import me.redtea.nodropx.libs.carcadex.repo.Repo;
import me.redtea.nodropx.libs.carcadex.repo.impl.yaml.YamlRepo;
import me.redtea.nodropx.libs.message.container.Messages;
import me.redtea.nodropx.listener.InventoryHandler;
import me.redtea.nodropx.listener.PlayerHandler;
import me.redtea.nodropx.model.cosmetic.impl.DisplayNameCosmetic;
import me.redtea.nodropx.schema.ItemStackListSchema;
import me.redtea.nodropx.service.cosmetic.CosmeticService;
import me.redtea.nodropx.service.cosmetic.impl.CosmeticServiceImpl;
import me.redtea.nodropx.service.event.EventService;
import me.redtea.nodropx.service.event.impl.EventServiceImpl;
import me.redtea.nodropx.service.nbt.NBTService;
import me.redtea.nodropx.service.nbt.impl.Tr7zwNBTServiceImpl;
import me.redtea.nodropx.service.nodrop.NoDropService;
import me.redtea.nodropx.service.nodrop.impl.NoDropServiceImpl;
import me.redtea.nodropx.service.respawn.RespawnService;
import me.redtea.nodropx.service.respawn.impl.RespawnServiceImpl;
import me.redtea.nodropx.service.storage.StorageService;
import me.redtea.nodropx.service.storage.impl.StorageServiceImpl;
import me.redtea.nodropxforgebridge.ApiProvider;
import me.redtea.nodropxforgebridge.Nodropxforgebridge;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
public final class NoDropX extends JavaPlugin {
private static NoDropAPI noDropAPIInstance;
@Getter
private static NoDropX instance;
private Messages messages;
private CosmeticService cosmeticService;
@Getter
private boolean supportHEX;
@Getter
private boolean supportKyoriLore;
private MutableRepo<UUID, List<ItemStack>> pagesRepo;
private Repo<String, List<Integer>> capacity;
@Override
public void onEnable() {
instance = this;
saveDefaultConfig();
TextServicesFactory textServicesFactory = new TextServicesFactoryImpl();
TextContext textContext = textServicesFactory.create(this);
supportHEX = textContext.isSupportHex();
supportKyoriLore = textContext.isSupportKyori();
messages = supportKyoriLore ? Messages.of(textContext.getMessages()) : Messages.legacy(textContext.getMessages());
cosmeticService = new CosmeticServiceImpl();
cosmeticService.add(new DisplayNameCosmetic());
cosmeticService.add(textContext.getLoreStrategy());
cosmeticService.load(YamlConfiguration.loadConfiguration(textContext.getCosmetics()));
capacity = new YamlRepo<>(
new File(getDataFolder(), "capacity.yml").toPath(),
this,
conf -> conf.getKeys(false).stream().collect(Collectors.toMap(
value -> value,
conf::getIntegerList))
);
NBTService nbtService = new Tr7zwNBTServiceImpl();
NoDropService noDropService = new NoDropServiceImpl(nbtService, cosmeticService);
NoDropItemFactory noDropItemFactory = new NoDropItemFactoryImpl(noDropService);
EventService eventService = new EventServiceImpl(noDropService);
RespawnService respawnService = new RespawnServiceImpl(noDropService, capacity);
pagesRepo = Repo.<UUID, List<ItemStack>>builder()
.autoSave(1000 * 60 * 15L)
.schema(new ItemStackListSchema(new File(getDataFolder(), "storage")))
.plugin(this)
.build();
GuiFacade guiFacade = new GuiFacadeImpl(
new StorageGui(messages, pagesRepo, noDropService, this)
);
StorageService storageService = new StorageServiceImpl(guiFacade, pagesRepo, noDropService);
NoDropAPI noDropAPI = new NoDropXFacade(noDropService, noDropItemFactory, storageService, capacity);
noDropAPIInstance = noDropAPI;
Bukkit.getPluginManager().registerEvents(
new InventoryHandler(eventService),
this
);
Bukkit.getPluginManager().registerEvents(
new PlayerHandler(respawnService, eventService, guiFacade),
this
);
Bukkit.getServicesManager().register(NoDropAPI.class,
noDropAPI, this, ServicePriority.Normal);
CommandManager commandManager = new CommandManager(this);
commandManager.getMessageHandler().register("cmd.no.permission", sender -> messages.get("noPerms").send(sender));
commandManager.getMessageHandler().register("cmd.no.console", sender -> messages.get("noConsole").send(sender));
commandManager.getMessageHandler().register("cmd.no.exists", sender -> messages.get("noExists").send(sender));
commandManager.getMessageHandler().register("cmd.wrong.usage", sender -> messages.get("usage").send(sender));
commandManager.getCompletionHandler().register("#materials", input -> Arrays.stream(Material.values()).map(Enum::name).collect(Collectors.toList()));
commandManager.register(new NoDropCommand(messages, noDropAPI, guiFacade, this));
bStatsInit();
printCredits();
mod();
}
//todo remove
private void mod() {
Nodropxforgebridge.noDropAPI = s -> getAPI().getCapacitySlots(s);
}
private void bStatsInit() {
int pluginId = 19090;
Metrics metrics = new Metrics(this, pluginId);
}
private void printCredits() {
Bukkit.getLogger().info(ChatColor.GREEN + "************************************************************");
Bukkit.getLogger().info(ChatColor.GREEN + "* " + ChatColor.YELLOW + "NoDropX version " + ChatColor.GOLD + getDescription().getVersion() + ChatColor.GRAY + " (uses CarcadeX v. 1.0.7)");
Bukkit.getLogger().info(ChatColor.GREEN + "* " + ChatColor.YELLOW + "By " + ChatColor.RED + "itzRedTea");
Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "Detected bukkit version " + ChatColor.GOLD + Bukkit.getBukkitVersion());
Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "support HEX colors " + (supportHEX ? ChatColor.DARK_GREEN + "YES" : ChatColor.DARK_RED + "NO"));
Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "support Kyori as lore " + (supportKyoriLore ? ChatColor.DARK_GREEN + "YES" : ChatColor.DARK_RED + "NO"));
Bukkit.getLogger().info(ChatColor.GREEN + "************************************************************");
}
public void onReload() {
pagesRepo.saveAll();
capacity.reload();
messages.reload(YamlConfiguration.loadConfiguration(new File(getDataFolder(), "messages.yml")));
cosmeticService.load(YamlConfiguration.loadConfiguration(new File(getDataFolder(), "cosmetics.yml")));
}
@Override
public void onDisable() {
pagesRepo.saveAll();
}
public static NoDropAPI getAPI() {
return noDropAPIInstance;
}
}
@@ -0,0 +1,51 @@
package me.redtea.nodropx.api.event;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* This event is called when item changes no drop status
*/
public class ChangeNoDropEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@Getter
private final boolean newValue;
@Getter
@NotNull
private final ItemStack item;
private boolean isCancelled;
public ChangeNoDropEvent(@NotNull Player who, boolean newValue, @NotNull ItemStack item) {
super(who);
this.newValue = newValue;
this.item = item;
}
@Override
public boolean isCancelled() {
return isCancelled ;
}
@Override
public void setCancelled(boolean cancel) {
isCancelled = cancel;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,39 @@
package me.redtea.nodropx.api.event;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* This event is called when player that has no drop item dead
*/
public class NoDropItemDropOnDeathEvent extends PlayerEvent {
private static final HandlerList handlers = new HandlerList();
@Getter
@NotNull
private final ItemStack item;
@Getter
@Setter
private boolean willDropped = false;
public NoDropItemDropOnDeathEvent(Player who, @NotNull ItemStack item) {
super(who);
this.item = item;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,55 @@
package me.redtea.nodropx.api.event;
import lombok.Getter;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.HandlerList;
import org.bukkit.event.player.PlayerEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* This event is called when no drop item is moved from the player's inventory
*/
public class NoDropItemThrownEvent extends PlayerEvent implements Cancellable {
private static final HandlerList handlers = new HandlerList();
@Getter
@NotNull
private final Reason reason;
@Getter
@NotNull
private final ItemStack item;
private boolean isCancelled;
public NoDropItemThrownEvent(Player who, @NotNull Reason reason, @NotNull ItemStack item) {
super(who);
this.reason = reason;
this.item = item;
}
@Override
public boolean isCancelled() {
return isCancelled ;
}
@Override
public void setCancelled(boolean cancel) {
isCancelled = cancel;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
public enum Reason {
DROP_BY_PLAYER, MOVING_TO_OTHER_INVENTORY
}
}
@@ -0,0 +1,130 @@
package me.redtea.nodropx.api.facade;
import me.redtea.nodropx.api.facade.manipulator.StorageManipulator;
import me.redtea.nodropx.util.MaterialUtils;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.UUID;
/**
* Facade for all plugin functions
*/
public interface NoDropAPI {
/**
* Set no drop to item
* @param item the item who's no drop property are changing
* @param isNoDrop will the item can drop from inventory on death?
* @author redtea
* @since 1.0.0
*/
void setNoDrop(@NotNull ItemStack item, boolean isNoDrop);
/**
* Check if item is no drop item
* @param item the item being checked
* @return true if item can not drop from inventory on death
* @author redtea
* @since 1.0.0
*/
boolean isNoDrop(@NotNull ItemStack item);
/**
* Generate new no drop item by material and amount
* @param material material of item
* @param amount count of item
* @return no drop item
* @author redtea
* @since 1.0.0
*/
ItemStack getNoDrop(@NotNull Material material, int amount);
/**
* Gives the entity no drop item
* @param entity who will get no drop item
* @param material type of item
* @param amount amount of item
* @author redtea
* @since 1.0.0
*/
void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material, int amount);
/**
* returns an object for accessing the storage of a player with UUID playerUniqueId
* @param playerUniqueId UUID of player
* @return object for accessing the storage
* @author redtea
* @since 1.0.0
*/
@NotNull StorageManipulator getStorageManipulator(@NotNull UUID playerUniqueId);
/**
* returns the slot numbers that an no drop item with this material saves at death
* @param material material to check
* @return numbers of saved slots
* @author redtea
* @since 1.0.0
*/
@NotNull Collection<Integer> getCapacitySlots(Material material);
/**
* @see NoDropAPI#getCapacitySlots(Material)
*/
default @NotNull Collection<Integer> getCapacitySlots(String material) {
return getCapacitySlots(MaterialUtils.getMaterialFromString(material));
}
/**
* @see NoDropAPI#getStorageManipulator(UUID)
*/
default @NotNull StorageManipulator getStorageManipulator(OfflinePlayer offlinePlayer) {
return getStorageManipulator(offlinePlayer.getUniqueId());
}
/**
* @see NoDropAPI#giveNoDrop(HumanEntity, Material, int)
*/
default void giveNoDrop(@NotNull HumanEntity entity, @NotNull String material, int amount) {
giveNoDrop(entity, MaterialUtils.getMaterialFromString(material), amount);
}
/**
* @see NoDropAPI#giveNoDrop(HumanEntity, Material, int)
*/
default void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material) {
giveNoDrop(entity, material, 1);
}
/**
* @see NoDropAPI#giveNoDrop(HumanEntity, Material, int)
*/
default void giveNoDrop(@NotNull HumanEntity entity, @NotNull String material) {
giveNoDrop(entity, material, 1);
}
/**
* @see NoDropAPI#getNoDrop(Material, int)
*/
default @NotNull ItemStack getNoDrop(@NotNull String material, int amount) {
return getNoDrop(MaterialUtils.getMaterialFromString(material), amount);
}
/**
* @see NoDropAPI#getNoDrop(Material, int)
*/
default @NotNull ItemStack getNoDrop(@NotNull Material material) {
return getNoDrop(material, 1);
}
/**
* @see NoDropAPI#getNoDrop(Material, int)
*/
default @NotNull ItemStack getNoDrop(@NotNull String material) {
return getNoDrop(material, 1);
}
}
@@ -0,0 +1,73 @@
package me.redtea.nodropx.api.facade.impl;
import me.redtea.nodropx.api.facade.NoDropAPI;
import me.redtea.nodropx.api.facade.manipulator.StorageManipulator;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.factory.item.NoDropItemFactory;
import me.redtea.nodropx.libs.carcadex.repo.Repo;
import me.redtea.nodropx.service.nodrop.NoDropService;
import me.redtea.nodropx.service.storage.StorageService;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
public class NoDropXFacade implements NoDropAPI {
private final NoDropService noDropService;
private final NoDropItemFactory itemFactory;
private final StorageService storageService;
private final Repo<String, List<Integer>> capacity;
@Override
public void setNoDrop(@NotNull ItemStack item, boolean isNoDrop) {
noDropService.setNoDrop(item, isNoDrop);
}
@Override
public boolean isNoDrop(@NotNull ItemStack item) {
return noDropService.isNoDrop(item);
}
@Override
@NotNull
public ItemStack getNoDrop(@NotNull Material material, int amount) {
return itemFactory.createNoDrop(material, amount);
}
@Override
public void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material, int amount) {
entity.getInventory().addItem(getNoDrop(material, amount));
}
@Override
public @NotNull StorageManipulator getStorageManipulator(@NotNull UUID playerUniqueId) {
return new StorageManipulator() {
@Override
public void add(@NotNull ItemStack item) {
storageService.addItem(playerUniqueId, item);
}
@Override
public void remove(@NotNull ItemStack item) {
storageService.removeItem(playerUniqueId, item);
}
@Override
public @NotNull List<ItemStack> all() {
return storageService.getItems(playerUniqueId);
}
};
}
@Override
public @NotNull Collection<Integer> getCapacitySlots(Material material) {
return capacity.get(material.name()).orElse(new ArrayList<>());
}
}
@@ -0,0 +1,30 @@
package me.redtea.nodropx.api.facade.manipulator;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public interface StorageManipulator {
/**
* Adds no drop item to player storage
* @param item - item that will add
* @author redtea
* @since 1.0.0
*/
void add(@NotNull ItemStack item);
/**
* Remove no drop item to player storage
* @param item - item that will remove
* @author redtea
* @since 1.0.0
*/
void remove(@NotNull ItemStack item);
/**
* @return items in storage
* @author redtea
* @since 1.0.0
*/
@NotNull List<ItemStack> all();
}
@@ -0,0 +1,66 @@
package me.redtea.nodropx.command;
import lombok.RequiredArgsConstructor;
import me.mattstudios.mf.annotations.*;
import me.mattstudios.mf.base.CommandBase;
import me.redtea.nodropx.NoDropX;
import me.redtea.nodropx.api.facade.NoDropAPI;
import me.redtea.nodropx.gui.facade.GuiFacade;
import me.redtea.nodropx.libs.message.container.Messages;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.Date;
/**
* Main command of plugin
* <p>/nodrop - usage</p>
* <p>/nodrop give [Player] [Material] [Amount (optional)]</p>
*/
@Command("nodrop")
@RequiredArgsConstructor
public class NoDropCommand extends CommandBase {
private final Messages messages;
private final NoDropAPI noDropAPI;
private final GuiFacade guiFacade;
private final NoDropX plugin;
@Default
public void onDefault(final CommandSender sender) {
messages.get("usage").send(sender);
}
@SubCommand("give")
@Permission("nodropx.give")
@Completion({"#players", "#materials", "#range:1-64"})
public void onGive(final CommandSender sender,
final Player player,
final String material,
@Optional final Integer amount) {
try {
noDropAPI.giveNoDrop(player, material, amount == null ? 1 : amount);
messages.get("give.success")
.replaceAll("%item%", material)
.replaceAll("%player%", player.getName())
.replaceAll("%amount%", amount == null ? "0" : amount.toString())
.send(sender);
} catch (Throwable e) {
messages.get("give.fail").send(sender);
}
}
@SubCommand("reload")
@Permission("nodropx.reload")
public void onReload(final CommandSender sender) {
long time = new Date().getTime();
plugin.onReload();
messages.get("reload").replaceAll("%time%", String.valueOf(new Date().getTime() - time)).send(sender);
}
@SubCommand("gui")
@Permission("nodropx.gui")
public void onGui(final Player sender) {
guiFacade.openNoDropStorage(sender);
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.factory.item;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public interface NoDropItemFactory {
ItemStack createNoDrop(@NotNull Material material, int amount);
}
@@ -0,0 +1,21 @@
package me.redtea.nodropx.factory.item.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.factory.item.NoDropItemFactory;
import me.redtea.nodropx.service.nodrop.NoDropService;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class NoDropItemFactoryImpl implements NoDropItemFactory {
private final NoDropService noDropService;
@Override
@NotNull
public ItemStack createNoDrop(@NotNull Material material, int amount) {
ItemStack result = new ItemStack(material, amount);
noDropService.setNoDrop(result, true);
return result;
}
}
@@ -0,0 +1,15 @@
package me.redtea.nodropx.factory.text;
import lombok.Data;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import java.io.File;
@Data
public class TextContext {
private final CosmeticStrategy loreStrategy;
private final File messages;
private final File cosmetics;
private final boolean supportHex;
private final boolean supportKyori;
}
@@ -0,0 +1,7 @@
package me.redtea.nodropx.factory.text;
import me.redtea.nodropx.NoDropX;
public interface TextServicesFactory {
TextContext create(NoDropX plugin);
}
@@ -0,0 +1,76 @@
package me.redtea.nodropx.factory.text.impl;
import lombok.SneakyThrows;
import me.redtea.nodropx.NoDropX;
import me.redtea.nodropx.factory.text.TextContext;
import me.redtea.nodropx.factory.text.TextServicesFactory;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import me.redtea.nodropx.model.cosmetic.impl.LoreKyoriCosmetic;
import me.redtea.nodropx.model.cosmetic.impl.LoreLegacyCosmetic;
import me.redtea.nodropx.model.cosmetic.impl.LoreMD5Cosmetics;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
public class TextServicesFactoryImpl implements TextServicesFactory {
private CosmeticStrategy cosmeticStrategy;
private boolean isSupportKyoriLore;
private boolean isSupportHEX = Integer.parseInt(Bukkit.getVersion().split("\\.")[1]) >= 16;
public TextContext create(NoDropX plugin) {
try {
Class<?> clazz = Class.forName("com.destroystokyo.paper.utils.PaperPluginLogger");
isSupportKyoriLore = isSupportHEX;
} catch (ClassNotFoundException e) {
isSupportKyoriLore = false;
}
if(plugin.getConfig().getBoolean("forceUseLegacy")) {
isSupportHEX = false;
isSupportKyoriLore = false;
} else if(plugin.getConfig().getBoolean("forceUseMD5Colors")) {
isSupportKyoriLore = false;
}
String folder = "legacy";
if(isSupportKyoriLore) folder = "mm";
else if(isSupportHEX) folder = "md5";
File messages = loadResource(plugin, folder, "messages.yml");
File cosmetics = loadResource(plugin, folder, "cosmetics.yml");
if(!isSupportHEX) {
plugin.getLogger().warning("This minecraft version did not support HEX");
plugin.getLogger().warning("Using legacy message serializer");
cosmeticStrategy = new LoreLegacyCosmetic();
} else if(!isSupportKyoriLore) {
plugin.getLogger().warning("This minecraft version did not support Kyori");
plugin.getLogger().warning("Using legacy MD5 message serializer");
cosmeticStrategy = new LoreMD5Cosmetics();
} else cosmeticStrategy = new LoreKyoriCosmetic();
return new TextContext(cosmeticStrategy, messages, cosmetics, isSupportHEX, isSupportKyoriLore);
}
@SneakyThrows
private File loadResource(Plugin plugin, String folder, String resourceName) {
File file = new File(plugin.getDataFolder(), resourceName);
if(!file.exists()) {
InputStream in = plugin.getResource("text/" + folder + "/" + resourceName);
Files.copy(in, file.toPath());
}
return file;
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.gui.facade;
import org.bukkit.entity.Player;
public interface GuiFacade {
void openNoDropStorage(Player player);
void clearCache(Player player);
void clearAllCache();
}
@@ -0,0 +1,26 @@
package me.redtea.nodropx.gui.facade.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.gui.facade.GuiFacade;
import me.redtea.nodropx.gui.pages.StorageGui;
import org.bukkit.entity.Player;
@RequiredArgsConstructor
public class GuiFacadeImpl implements GuiFacade {
private final StorageGui storageGui;
@Override
public void openNoDropStorage(Player player) {
storageGui.open(player);
}
@Override
public void clearCache(Player player) {
storageGui.clearCache(player);
}
@Override
public void clearAllCache() {
storageGui.clearAllCache();
}
}
@@ -0,0 +1,139 @@
package me.redtea.nodropx.gui.pages;
import dev.triumphteam.gui.builder.item.ItemBuilder;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import dev.triumphteam.gui.guis.ScrollingGui;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
import me.redtea.nodropx.libs.message.container.Messages;
import me.redtea.nodropx.service.nodrop.NoDropService;
import me.redtea.nodropx.util.MaterialUtils;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.*;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class StorageGui {
private final Map<UUID, ScrollingGui> cache = new HashMap<>();
private final Messages messages;
private final MutableRepo<UUID, List<ItemStack>> pagesRepo;
private final NoDropService noDropService;
private final Plugin plugin;
public void open(Player player) {
if(cache.containsKey(player.getUniqueId())) {
cache.get(player.getUniqueId()).open(player);
return;
}
ScrollingGui gui = Gui.scrolling()
.title(messages.get("gui.title").asComponent())
.rows(6)
.pageSize(45)
.disableAllInteractions()
.create();
setFrame(gui);
setButtons(gui);
setAction(gui);
setOnClose(gui);
for(ItemStack item : pagesRepo.get(player.getUniqueId()).orElse(new ArrayList<>())) {
gui.addItem(ItemBuilder.from(item).asGuiItem());
}
gui.open(player);
cache.put(player.getUniqueId(), gui);
}
private void setOnClose(ScrollingGui gui) {
gui.setCloseGuiAction(event -> pagesRepo.update(event.getPlayer().getUniqueId(), gui.getPageItems().stream()
.map(GuiItem::getItemStack)
.collect(Collectors.toList())));
}
private void setAction(ScrollingGui gui) {
gui.setDefaultClickAction(event -> {
if((event.getSlot() >= 45 && event.getSlot() <= 53) || event.getClickedInventory() instanceof PlayerInventory) {
return;
}
if(event.getAction() == InventoryAction.PLACE_ALL && !event.getClick().isShiftClick()) {
if(!noDropService.isNoDrop(event.getCursor())) {
return;
}
ItemStack is = event.getCursor();
event.setCursor(new ItemStack(Material.AIR));
new BukkitRunnable() {
@Override
public void run() {
gui.addItem(ItemBuilder.from(is).asGuiItem());
gui.update();
}
}.runTask(plugin);
} else if(event.getAction() == InventoryAction.SWAP_WITH_CURSOR && !event.getClick().isShiftClick()) {
if(!noDropService.isNoDrop(event.getCursor())) {
return;
}
ItemStack is = event.getCursor();
new BukkitRunnable() {
@Override
public void run() {
gui.addItem(ItemBuilder.from(is).asGuiItem());
gui.update();
}
}.runTask(plugin);
event.setCursor(new ItemStack(Material.AIR));
} else if(event.getAction() == InventoryAction.PICKUP_ALL) {
event.setCursor(event.getCurrentItem());
new BukkitRunnable() {
@Override
public void run() {
gui.removePageItem(event.getCurrentItem());
gui.update();
}
}.runTask(plugin);
}
});
}
private void setFrame(ScrollingGui gui) {
for(int i = 1; i < 10; ++i) {
gui.setItem(6, i, ItemBuilder.from(
MaterialUtils.getMaterialFromString(messages.get("gui.frame.material").asUnparsedString())
).name(messages.get("gui.frame.name").asComponent()).asGuiItem(event -> event.setCancelled(true)));
}
}
private void setButtons(ScrollingGui gui) {
gui.setItem(6, 3, ItemBuilder.from(
MaterialUtils.getMaterialFromString(messages.get("gui.prev.material").asUnparsedString())
).name(messages.get("gui.prev.name").asComponent()).asGuiItem(event -> {
gui.previous();
event.setCancelled(true);
}));
gui.setItem(6, 7, ItemBuilder.from(
MaterialUtils.getMaterialFromString(messages.get("gui.next.material").asUnparsedString())
).name(messages.get("gui.next.name").asComponent()).asGuiItem(event -> {
gui.next();
event.setCancelled(true);
}));
}
public void clearCache(Player player) {
cache.remove(player.getUniqueId());
}
public void clearAllCache() {
cache.clear();
}
}
@@ -0,0 +1,881 @@
/*
* This Metrics class was auto-generated and can be copied into your project if you are
* not using a build tool like Gradle or Maven for dependency management.
*
* IMPORTANT: You are not allowed to modify this class, except changing the package.
*
* Disallowed modifications include but are not limited to:
* - Remove the option for users to opt-out
* - Change the frequency for data submission
* - Obfuscate the code (every obfuscator should allow you to make an exception for specific files)
* - Reformat the code (if you use a linter, add an exception)
*
* Violations will result in a ban of your plugin and account from bStats.
*/
package me.redtea.nodropx.libs.bstats;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class Metrics {
private final Plugin plugin;
private final MetricsBase metricsBase;
/**
* Creates a new Metrics instance.
*
* @param plugin Your plugin instance.
* @param serviceId The id of the service. It can be found at <a
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/
public Metrics(JavaPlugin plugin, int serviceId) {
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
if (!config.isSet("serverUuid")) {
config.addDefault("enabled", true);
config.addDefault("serverUuid", UUID.randomUUID().toString());
config.addDefault("logFailedRequests", false);
config.addDefault("logSentData", false);
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config
.options()
.header(
"bStats (https://bStats.org) collects some basic information for plugin authors, like how\n"
+ "many people use their plugin and their total player count. It's recommended to keep bStats\n"
+ "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n"
+ "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n"
+ "anonymous.")
.copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) {
}
}
// Load the data
boolean enabled = config.getBoolean("enabled", true);
String serverUUID = config.getString("serverUuid");
boolean logErrors = config.getBoolean("logFailedRequests", false);
boolean logSentData = config.getBoolean("logSentData", false);
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
metricsBase =
new MetricsBase(
"bukkit",
serverUUID,
serviceId,
enabled,
this::appendPlatformData,
this::appendServiceData,
submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
plugin::isEnabled,
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
(message) -> this.plugin.getLogger().log(Level.INFO, message),
logErrors,
logSentData,
logResponseStatusText);
}
/** Shuts down the underlying scheduler service. */
public void shutdown() {
metricsBase.shutdown();
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
metricsBase.addCustomChart(chart);
}
private void appendPlatformData(JsonObjectBuilder builder) {
builder.appendField("playerAmount", getPlayerAmount());
builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0);
builder.appendField("bukkitVersion", Bukkit.getVersion());
builder.appendField("bukkitName", Bukkit.getName());
builder.appendField("javaVersion", System.getProperty("java.version"));
builder.appendField("osName", System.getProperty("os.name"));
builder.appendField("osArch", System.getProperty("os.arch"));
builder.appendField("osVersion", System.getProperty("os.version"));
builder.appendField("coreCount", Runtime.getRuntime().availableProcessors());
}
private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", plugin.getDescription().getVersion());
}
private int getPlayerAmount() {
try {
// Around MC 1.8 the return type was changed from an array to a collection,
// This fixes java.lang.NoSuchMethodError:
// org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
return onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
// Just use the new method if the reflection failed
return Bukkit.getOnlinePlayers().size();
}
}
public static class MetricsBase {
/** The version of the Metrics class. */
public static final String METRICS_VERSION = "3.0.2";
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
private final ScheduledExecutorService scheduler;
private final String platform;
private final String serverUuid;
private final int serviceId;
private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer;
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
private final Consumer<Runnable> submitTaskConsumer;
private final Supplier<Boolean> checkServiceEnabledSupplier;
private final BiConsumer<String, Throwable> errorLogger;
private final Consumer<String> infoLogger;
private final boolean logErrors;
private final boolean logSentData;
private final boolean logResponseStatusText;
private final Set<CustomChart> customCharts = new HashSet<>();
private final boolean enabled;
/**
* Creates a new MetricsBase class instance.
*
* @param platform The platform of the service.
* @param serviceId The id of the service.
* @param serverUuid The server uuid.
* @param enabled Whether or not data sending is enabled.
* @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all platform-specific data.
* @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and
* appends all service-specific data.
* @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be
* used to delegate the data collection to a another thread to prevent errors caused by
* concurrency. Can be {@code null}.
* @param checkServiceEnabledSupplier A supplier to check if the service is still enabled.
* @param errorLogger A consumer that accepts log message and an error.
* @param infoLogger A consumer that accepts info log messages.
* @param logErrors Whether or not errors should be logged.
* @param logSentData Whether or not the sent data should be logged.
* @param logResponseStatusText Whether or not the response status text should be logged.
*/
public MetricsBase(
String platform,
String serverUuid,
int serviceId,
boolean enabled,
Consumer<JsonObjectBuilder> appendPlatformDataConsumer,
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
Consumer<Runnable> submitTaskConsumer,
Supplier<Boolean> checkServiceEnabledSupplier,
BiConsumer<String, Throwable> errorLogger,
Consumer<String> infoLogger,
boolean logErrors,
boolean logSentData,
boolean logResponseStatusText) {
ScheduledThreadPoolExecutor scheduler =
new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics"));
// We want delayed tasks (non-periodic) that will execute in the future to be
// cancelled when the scheduler is shutdown.
// Otherwise, we risk preventing the server from shutting down even when
// MetricsBase#shutdown() is called
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.scheduler = scheduler;
this.platform = platform;
this.serverUuid = serverUuid;
this.serviceId = serviceId;
this.enabled = enabled;
this.appendPlatformDataConsumer = appendPlatformDataConsumer;
this.appendServiceDataConsumer = appendServiceDataConsumer;
this.submitTaskConsumer = submitTaskConsumer;
this.checkServiceEnabledSupplier = checkServiceEnabledSupplier;
this.errorLogger = errorLogger;
this.infoLogger = infoLogger;
this.logErrors = logErrors;
this.logSentData = logSentData;
this.logResponseStatusText = logResponseStatusText;
checkRelocation();
if (enabled) {
// WARNING: Removing the option to opt-out will get your plugin banned from
// bStats
startSubmitting();
}
}
public void addCustomChart(CustomChart chart) {
this.customCharts.add(chart);
}
public void shutdown() {
scheduler.shutdown();
}
private void startSubmitting() {
final Runnable submitTask =
() -> {
if (!enabled || !checkServiceEnabledSupplier.get()) {
// Submitting data or service is disabled
scheduler.shutdown();
return;
}
if (submitTaskConsumer != null) {
submitTaskConsumer.accept(this::submitData);
} else {
this.submitData();
}
};
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven
// distribution of requests on the
// bStats backend. To circumvent this problem, we introduce some randomness into
// the initial and second delay.
// WARNING: You must not modify and part of this Metrics class, including the
// submit delay or frequency!
// WARNING: Modifying this code will get your plugin banned on bStats. Just
// don't do it!
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
scheduler.scheduleAtFixedRate(
submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS);
}
private void submitData() {
final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder();
appendPlatformDataConsumer.accept(baseJsonBuilder);
final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder();
appendServiceDataConsumer.accept(serviceJsonBuilder);
JsonObjectBuilder.JsonObject[] chartData =
customCharts.stream()
.map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors))
.filter(Objects::nonNull)
.toArray(JsonObjectBuilder.JsonObject[]::new);
serviceJsonBuilder.appendField("id", serviceId);
serviceJsonBuilder.appendField("customCharts", chartData);
baseJsonBuilder.appendField("service", serviceJsonBuilder.build());
baseJsonBuilder.appendField("serverUUID", serverUuid);
baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION);
JsonObjectBuilder.JsonObject data = baseJsonBuilder.build();
scheduler.execute(
() -> {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logErrors) {
errorLogger.accept("Could not submit bStats metrics data", e);
}
}
});
}
private void sendData(JsonObjectBuilder.JsonObject data) throws Exception {
if (logSentData) {
infoLogger.accept("Sent bStats metrics data: " + data.toString());
}
String url = String.format(REPORT_URL, platform);
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip");
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("User-Agent", "Metrics-Service/1");
connection.setDoOutput(true);
try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) {
outputStream.write(compressedData);
}
StringBuilder builder = new StringBuilder();
try (BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
if (logResponseStatusText) {
infoLogger.accept("Sent data to bStats and received response: " + builder);
}
}
/** Checks that the class was properly relocated. */
private void checkRelocation() {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this
// little "trick" ... :D
final String defaultPackage =
new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
final String examplePackage =
new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure no one just copy & pastes the example and uses the wrong
// package names
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
/**
* Gzips the given string.
*
* @param str The string to gzip.
* @return The gzipped string.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) {
gzip.write(str.getBytes(StandardCharsets.UTF_8));
}
return outputStream.toByteArray();
}
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
// Skip this invalid
continue;
}
allSkipped = false;
valuesBuilder.appendField(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build());
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
}
}
public abstract static class CustomChart {
private final String chartId;
protected CustomChart(String chartId) {
if (chartId == null) {
throw new IllegalArgumentException("chartId must not be null");
}
this.chartId = chartId;
}
public JsonObjectBuilder.JsonObject getRequestJsonObject(
BiConsumer<String, Throwable> errorLogger, boolean logErrors) {
JsonObjectBuilder builder = new JsonObjectBuilder();
builder.appendField("chartId", chartId);
try {
JsonObjectBuilder.JsonObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
builder.appendField("data", data);
} catch (Throwable t) {
if (logErrors) {
errorLogger.accept("Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return builder.build();
}
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
return new JsonObjectBuilder().appendField("value", value).build();
}
}
/**
* An extremely simple JSON builder.
*
* <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough
* for its use-case.
*/
public static class JsonObjectBuilder {
private StringBuilder builder = new StringBuilder();
private boolean hasAtLeastOneField = false;
public JsonObjectBuilder() {
builder.append("{");
}
/**
* Appends a null field to the JSON.
*
* @param key The key of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendNull(String key) {
appendFieldUnescaped(key, "null");
return this;
}
/**
* Appends a string field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String value) {
if (value == null) {
throw new IllegalArgumentException("JSON value must not be null");
}
appendFieldUnescaped(key, "\"" + escape(value) + "\"");
return this;
}
/**
* Appends an integer field to the JSON.
*
* @param key The key of the field.
* @param value The value of the field.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int value) {
appendFieldUnescaped(key, String.valueOf(value));
return this;
}
/**
* Appends an object to the JSON.
*
* @param key The key of the field.
* @param object The object.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject object) {
if (object == null) {
throw new IllegalArgumentException("JSON object must not be null");
}
appendFieldUnescaped(key, object.toString());
return this;
}
/**
* Appends a string array to the JSON.
*
* @param key The key of the field.
* @param values The string array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, String[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values)
.map(value -> "\"" + escape(value) + "\"")
.collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an integer array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, int[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends an object array to the JSON.
*
* @param key The key of the field.
* @param values The integer array.
* @return A reference to this object.
*/
public JsonObjectBuilder appendField(String key, JsonObject[] values) {
if (values == null) {
throw new IllegalArgumentException("JSON values must not be null");
}
String escapedValues =
Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(","));
appendFieldUnescaped(key, "[" + escapedValues + "]");
return this;
}
/**
* Appends a field to the object.
*
* @param key The key of the field.
* @param escapedValue The escaped value of the field.
*/
private void appendFieldUnescaped(String key, String escapedValue) {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
if (key == null) {
throw new IllegalArgumentException("JSON key must not be null");
}
if (hasAtLeastOneField) {
builder.append(",");
}
builder.append("\"").append(escape(key)).append("\":").append(escapedValue);
hasAtLeastOneField = true;
}
/**
* Builds the JSON string and invalidates this builder.
*
* @return The built JSON string.
*/
public JsonObject build() {
if (builder == null) {
throw new IllegalStateException("JSON has already been built");
}
JsonObject object = new JsonObject(builder.append("}").toString());
builder = null;
return object;
}
/**
* Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt.
*
* <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'.
* Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n").
*
* @param value The value to escape.
* @return The escaped value.
*/
private static String escape(String value) {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '"') {
builder.append("\\\"");
} else if (c == '\\') {
builder.append("\\\\");
} else if (c <= '\u000F') {
builder.append("\\u000").append(Integer.toHexString(c));
} else if (c <= '\u001F') {
builder.append("\\u00").append(Integer.toHexString(c));
} else {
builder.append(c);
}
}
return builder.toString();
}
/**
* A super simple representation of a JSON object.
*
* <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not
* allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String,
* JsonObject)}.
*/
public static class JsonObject {
private final String value;
private JsonObject(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}
}
}
@@ -0,0 +1,16 @@
package me.redtea.nodropx.libs.carcadex.reload;
public interface Reloadable {
void init();
void close();
default void reload() {
close();
init();
}
default int priority() {
return 0;
}
}
@@ -0,0 +1,7 @@
package me.redtea.nodropx.libs.carcadex.reload.exception;
public class SimpledReloaderException extends RuntimeException {
public SimpledReloaderException() {
super("Simpled reloader is not implements this operation. Please use classic reloader");
}
}
@@ -0,0 +1,15 @@
package me.redtea.nodropx.libs.carcadex.reload.parameterized;
import me.redtea.nodropx.libs.carcadex.reload.Reloadable;
import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer;
import org.apache.commons.lang.NotImplementedException;
import org.jetbrains.annotations.Nullable;
public abstract class ParameterizedReloadable implements Reloadable {
public abstract void init(@Nullable ReloadContainer container);
@Override
public void init() {
throw new NotImplementedException("This class can be initialized only with init(ReloadContainer) method");
}
}
@@ -0,0 +1,21 @@
package me.redtea.nodropx.libs.carcadex.reload.parameterized.container;
import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.impl.MapReloadContainer;
import java.util.HashMap;
import java.util.Map;
public interface ReloadContainer {
<T>T get(String key);
<T>void set(String key, T t);
boolean contains(String key);
static ReloadContainer of(Object... objects) {
Map<String, Object> map = new HashMap<>();
for(int i = 0; i < objects.length; i += 2) {
String key = (String) objects[i];
Object value = objects[i+1];
map.put(key, value);
}
return new MapReloadContainer(map);
}
}
@@ -0,0 +1,25 @@
package me.redtea.nodropx.libs.carcadex.reload.parameterized.container.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer;
import java.util.Map;
@RequiredArgsConstructor
public class MapReloadContainer implements ReloadContainer {
private final Map<String, Object> params;
@Override
public <T> T get(String key) {
return (T) params.get(key);
}
@Override
public <T> void set(String key, T t) {
params.put(key, t);
}
@Override
public boolean contains(String key) {
return params.containsKey(key);
}
}
@@ -0,0 +1,12 @@
package me.redtea.nodropx.libs.carcadex.repo;
import me.redtea.nodropx.libs.carcadex.reload.Reloadable;
public interface MutableRepo<K, V> extends Repo<K, V>, Reloadable {
V update(K key, V value);
V remove(K key);
void saveAll();
}
@@ -0,0 +1,17 @@
package me.redtea.nodropx.libs.carcadex.repo;
import me.redtea.nodropx.libs.carcadex.reload.Reloadable;
import me.redtea.nodropx.libs.carcadex.repo.builder.RepoBuilder;
import java.util.Collection;
import java.util.Optional;
public interface Repo<K, V> extends Reloadable {
Collection<V> all();
Optional<V> get(K key);
static<K, V> RepoBuilder<K, V> builder() {
return RepoBuilder.get();
}
}
@@ -0,0 +1,38 @@
package me.redtea.nodropx.libs.carcadex.repo.builder;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
import me.redtea.nodropx.libs.carcadex.repo.builder.impl.RepoBuilderImpl;
import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy;
import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
import java.nio.file.Path;
public interface RepoBuilder<K, V> {
RepoBuilder<K, V> serializer(CommonSerializer<V> serializer);
RepoBuilder<K, V> binary(); //requires that V extends Serializable
RepoBuilder<K, V> plugin(Plugin plugin);
RepoBuilder<K, V> autoSave(long period); //in milisec
RepoBuilder<K, V> schema(SchemaStrategy<K,V> schemaStrategy);
RepoBuilder<K, V> dir(Path dir);
RepoBuilder<K, V> dir(String filename); //requires plugin. didnt need if dir set
RepoBuilder<K, V> logging();
RepoBuilder<K, V> debugLogging();
RepoBuilder<K, V> logging(Logger logger);
RepoBuilder<K, V> debugLogging(Logger logger);
RepoBuilder<K, V> threadSafe();
MutableRepo<K, V> build();
static<K, V> RepoBuilder<K, V> get() {
return new RepoBuilderImpl<>();
}
static <K, V> RepoBuilder<K, V> of(Path dir) {
return RepoBuilder.<K, V>get().dir(dir);
}
static <K, V> RepoBuilder<K, V> of(Plugin plugin) {
return RepoBuilder.<K, V>get().plugin(plugin);
}
}
@@ -0,0 +1,159 @@
package me.redtea.nodropx.libs.carcadex.repo.builder.impl;
import lombok.SneakyThrows;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
import me.redtea.nodropx.libs.carcadex.repo.builder.RepoBuilder;
import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.AutoSaveDecorator;
import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.CopyOnWriteDecorator;
import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.LoggingDecorator;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import me.redtea.nodropx.libs.carcadex.repo.impl.common.CommonRepo;
import me.redtea.nodropx.libs.carcadex.repo.impl.schema.SchemaRepo;
import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy;
import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
public class RepoBuilderImpl<K, V> implements RepoBuilder<K, V> {
private CommonSerializer<V> serializer; //null if binary
private boolean autoSave;
private long period;
private Plugin plugin;
private Path dir;
private String filename;
private SchemaStrategy<K, V> schemaStrategy;
private Logger logger = null;
private boolean logging = false;
private boolean debugLogger = false;
private boolean threadSafe = false;
@Override
public RepoBuilder<K, V> serializer(CommonSerializer<V> serializer) {
this.serializer = serializer;
return this;
}
@Override
public RepoBuilder<K, V> binary() {
serializer = null;
return this;
}
@Override
public RepoBuilder<K, V> plugin(Plugin plugin) {
this.plugin = plugin;
return this;
}
@Override
public RepoBuilder<K, V> autoSave(long period) {
autoSave = true;
this.period = period;
return this;
}
@Override
public RepoBuilder<K, V> schema(SchemaStrategy<K, V> schemaStrategy) {
this.schemaStrategy = schemaStrategy;
return this;
}
@Override
public RepoBuilder<K, V> dir(Path path) {
this.dir = path;
return this;
}
@Override
public RepoBuilder<K, V> dir(String filename) {
this.filename = filename;
return this;
}
@Override
public RepoBuilder<K, V> logging() {
logging = true;
debugLogger = false;
return this;
}
@Override
public RepoBuilder<K, V> debugLogging() {
logging = true;
debugLogger = true;
return this;
}
@Override
public RepoBuilder<K, V> logging(Logger logger) {
logging = true;
debugLogger = false;
this.logger = logger;
return this;
}
@Override
public RepoBuilder<K, V> debugLogging(Logger logger) {
logging = true;
debugLogger = true;
this.logger = logger;
return this;
}
@Override
public RepoBuilder<K, V> threadSafe() {
threadSafe = true;
return this;
}
@Override
public MutableRepo<K, V> build() {
if(dir == null) {
checkDir();
}
CacheRepo<K, V> result = null;
if(serializer != null) {
result = new CommonRepo<>(dir, serializer);
}
if(schemaStrategy != null) {
result = new SchemaRepo<>(schemaStrategy);
}
if(result == null) {
result = new CommonRepo<>(dir);
}
if(autoSave) {result = new AutoSaveDecorator<>(result, 0, period);
}
if(logging) {
if(logger != null) result = new LoggingDecorator<>(result, logger, debugLogger);
else result = new LoggingDecorator<>(result, debugLogger);
}
if(threadSafe) {
result = new CopyOnWriteDecorator<>(result);
}
result.init();
return result;
}
@SneakyThrows
private void checkDir() {
if(plugin == null) {
if(filename == null) {
dir = Paths.get("saved-data-carcadex");
} else dir = Paths.get(filename);
} else {
dir = new File(plugin.getDataFolder(), filename == null ? "saved-data-carcadex" : filename).toPath();
}
}
}
@@ -0,0 +1,58 @@
package me.redtea.nodropx.libs.carcadex.repo.decorator;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import java.util.Collection;
import java.util.Optional;
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
public abstract class CacheRepoDecorator<K, V> implements CacheRepo<K, V> {
protected final CacheRepo<K, V> repo;
@Override
public void init() {
repo.init();
}
@Override
public void close() {
repo.close();
}
@Override
public V update(K key, V value) {
return repo.update(key, value);
}
@Override
public V remove(K key) {
return repo.remove(key);
}
@Override
public void saveAll() {
repo.saveAll();
}
@Override
public Collection<V> all() {
return repo.all();
}
@Override
public Optional<V> get(K key) {
return repo.get(key);
}
@Override
public void loadToCache(K k) {
repo.loadToCache(k);
}
@Override
public void removeFromCache(K k) {
repo.remove(k);
}
}
@@ -0,0 +1,19 @@
package me.redtea.nodropx.libs.carcadex.repo.decorator.impl;
import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import java.util.Timer;
import java.util.TimerTask;
public class AutoSaveDecorator<K, V> extends CacheRepoDecorator<K, V> {
public AutoSaveDecorator(CacheRepo<K, V> repo, long delay /*in milisec*/, long period /*in milisec*/) {
super(repo);
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
saveAll();
}
}, delay, period);
}
}
@@ -0,0 +1,105 @@
package me.redtea.nodropx.libs.carcadex.repo.decorator.impl;
import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;
public class CopyOnWriteDecorator<K, V> extends CacheRepoDecorator<K, V> {
private final CacheRepo<K, V> repo;
public CopyOnWriteDecorator(CacheRepo<K, V> repo) {
super(repo);
this.repo = repo;
}
final transient ReentrantLock lock = new ReentrantLock();
@Override
public void loadToCache(K k) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
super.loadToCache(k);
} finally {
lock.unlock();
}
}
@Override
public void removeFromCache(K k) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
super.removeFromCache(k);
} finally {
lock.unlock();
}
}
@Override
public Collection<V> all() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return super.all();
} finally {
lock.unlock();
}
}
@Override
public Optional<V> get(K key) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return super.get(key);
} finally {
lock.unlock();
}
}
@Override
public void saveAll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
super.saveAll();
} finally {
lock.unlock();
}
}
@Override
public V update(K key, V value) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
V oldValue = repo.get(key).orElse(null);
if (oldValue != null && !oldValue.equals(value)) {
repo.update(key, value);
}
return value;
} finally {
lock.unlock();
}
}
public V remove(K key) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
V oldValue = repo.get(key).orElse(null);
if (oldValue != null) {
repo.remove(key);
}
return oldValue;
} finally {
lock.unlock();
}
}
}
@@ -0,0 +1,143 @@
package me.redtea.nodropx.libs.carcadex.repo.decorator.impl;
import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Optional;
public class LoggingDecorator<K, V> extends CacheRepoDecorator<K, V> {
private final Logger logger;
private final boolean onlyOnDebug;
public LoggingDecorator(CacheRepo<K, V> repo, Logger logger, boolean onlyOnDebug) {
super(repo);
this.logger = logger;
this.onlyOnDebug = onlyOnDebug;
}
public LoggingDecorator(CacheRepo<K, V> repo, boolean onlyOnDebug) {
super(repo);
this.logger = LoggerFactory.getLogger(this.getClass());
this.onlyOnDebug = onlyOnDebug;
}
public LoggingDecorator(CacheRepo<K, V> repo) {
super(repo);
this.logger = LoggerFactory.getLogger(this.getClass());
this.onlyOnDebug = false;
}
@Override
public void init() {
try {
log("Start of repository initialization...");
super.init();
} catch (Throwable e) {
logger.error("Error on initialization repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public void close() {
try {
log("Closing the repository...");
super.close();
} catch (Throwable e) {
logger.error("Error on closing repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public V update(K key, V value) {
try {
log("Updating the repository...");
log("Key: " + key + " Value: " + value);
return repo.update(key, value);
} catch (Throwable e) {
logger.error("Error on updating repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public V remove(K key) {
try {
log("Removing from repository...");
log("Key: " + key);
return repo.remove(key);
} catch (Throwable e) {
logger.error("Error on removing from repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public void saveAll() {
try {
log("Saving to storage from repository...");
repo.saveAll();
} catch (Throwable e) {
logger.error("Error on saving to storage from repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public Collection<V> all() {
try {
log("Getting all values from memory...");
return repo.all();
} catch (Throwable e) {
logger.error("Error on getting all values from memory! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public Optional<V> get(K key) {
try {
log("Getting value from repository...");
log("Key: " + key);
return repo.get(key);
} catch (Throwable e) {
logger.error("Error on getting value from repository! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public void loadToCache(K k) {
try {
log("Load to cache value...");
log("Key: " + k);
repo.loadToCache(k);
} catch (Throwable e) {
logger.error("Error on load to cache value! " + e.getLocalizedMessage());
throw e;
}
}
@Override
public void removeFromCache(K k) {
try {
log("Remove from cache value...");
repo.remove(k);
} catch (Throwable e) {
logger.error("Error on remove from cache value! " + e.getLocalizedMessage());
throw e;
}
}
private void log(String log) {
if(onlyOnDebug) logger.debug(log);
else logger.info(log);
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.libs.carcadex.repo.impl;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
public interface CacheRepo<K, V> extends MutableRepo<K, V> {
void loadToCache(K k);
void removeFromCache(K k);
}
@@ -0,0 +1,18 @@
package me.redtea.nodropx.libs.carcadex.repo.impl.common;
import me.redtea.nodropx.libs.carcadex.repo.impl.schema.SchemaRepo;
import me.redtea.nodropx.libs.carcadex.schema.impl.binary.BinarySchemaStrategy;
import me.redtea.nodropx.libs.carcadex.schema.impl.serialize.SerializeSchemaStrategy;
import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer;
import java.nio.file.Path;
public class CommonRepo<K, V> extends SchemaRepo<K, V> {
public CommonRepo(Path dir, CommonSerializer<V> serializer) {
super(new SerializeSchemaStrategy<>(dir, serializer));
}
public CommonRepo(Path dir) {
super(new BinarySchemaStrategy<>(dir));
}
}
@@ -0,0 +1,40 @@
package me.redtea.nodropx.libs.carcadex.repo.impl.map;
import me.redtea.nodropx.libs.carcadex.reload.parameterized.ParameterizedReloadable;
import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer;
import me.redtea.nodropx.libs.carcadex.repo.Repo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* Reload container:
* data - Map.class
* @param <K>
* @param <V>
*/
public abstract class MapRepo<K, V> extends ParameterizedReloadable implements Repo<K, V> {
protected Map<K, V> data = new HashMap<>();
@Override
public Collection<V> all() {
return data.values();
}
@Override
public Optional<V> get(K key) {
return Optional.ofNullable(data.get(key));
}
@Override
public void init(ReloadContainer container) {
if(container != null) data = container.get("data");
}
@Override
public void close() {
data.clear();
}
}
@@ -0,0 +1,84 @@
package me.redtea.nodropx.libs.carcadex.repo.impl.schema;
import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo;
import me.redtea.nodropx.libs.carcadex.repo.impl.map.MapRepo;
import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy;
import java.util.*;
public class SchemaRepo<K, V> extends MapRepo<K, V> implements CacheRepo<K, V> {
protected final Set<K> toRemove = new HashSet<>();
protected final SchemaStrategy<K, V> schemaStrategy;
public SchemaRepo(SchemaStrategy<K, V> schemaStrategy) {
this.schemaStrategy = schemaStrategy;
}
@Override
public V update(K key, V value) {
return data.put(key, value);
}
@Override
public Collection<V> all() {
return schemaStrategy.all();
}
@Override
public Optional<V> get(K key) {
if(toRemove.contains(key)) return Optional.ofNullable(null);
V result = data.get(key);
if(result == null) try {
loadToCache(key);
result = data.get(key);
} catch (Throwable ignored) {}
return Optional.ofNullable(result);
}
@Override
public V remove(K key) {
if(!get(key).isPresent()) throw new NoSuchElementException();
toRemove.add(key);
return data.remove(key);
}
@Override
public void saveAll() {
toRemove.forEach(schemaStrategy::remove);
data.forEach(schemaStrategy::insert);
}
@Override
public void loadToCache(K key) {
data.put(key, schemaStrategy.get(key));
}
@Override
public void removeFromCache(K key) {
data.remove(key);
}
@Override
public void init() {
schemaStrategy.init();
}
@Override
public void close() {
saveAll();
data.clear();
toRemove.clear();
schemaStrategy.close();
}
@Override
public void reload() {
saveAll();
data.clear();
schemaStrategy.reload();
init();
}
}
@@ -0,0 +1,37 @@
package me.redtea.nodropx.libs.carcadex.repo.impl.yaml;
import me.redtea.nodropx.libs.carcadex.repo.impl.map.MapRepo;
import me.redtea.nodropx.libs.carcadex.repo.impl.yaml.strategy.ParseStrategy;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.nio.file.Files;
import java.nio.file.Path;
public class YamlRepo<K, V> extends MapRepo<K, V> {
protected final Path file;
protected final Plugin plugin;
protected final ParseStrategy<K, V> parseStrategy;
public YamlRepo(Path file, Plugin plugin, ParseStrategy<K, V> parseStrategy) {
this.file = file;
this.plugin = plugin;
this.parseStrategy = parseStrategy;
init();
}
protected FileConfiguration initFile() {
if (Files.notExists(file)) plugin.saveResource(file.toFile().getName(), false);
return YamlConfiguration.loadConfiguration(file.toFile());
}
@Override
public void init() {
data.putAll(parseStrategy.fromYaml(initFile()));
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.libs.carcadex.repo.impl.yaml.strategy;
import org.bukkit.configuration.file.FileConfiguration;
import java.util.Map;
public interface ParseStrategy<K, V> {
Map<K, V> fromYaml(FileConfiguration fileConfiguration);
}
@@ -0,0 +1,36 @@
package me.redtea.nodropx.libs.carcadex.schema;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public abstract class AbstractFileSchemaStrategy<K, V> implements SchemaStrategy<K, V> {
protected final Path dir;
public AbstractFileSchemaStrategy(Path dir) {
this.dir = dir;
init();
}
@Override
public void init() {
if(Files.notExists(dir)) {
try {
Files.createDirectory(dir);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void close() {
}
@Override
public void remove(K key) {
File file = new File(dir.toFile(), String.valueOf(key));
if(file.exists()) file.delete();
}
}
@@ -0,0 +1,15 @@
package me.redtea.nodropx.libs.carcadex.schema;
import me.redtea.nodropx.libs.carcadex.reload.Reloadable;
import java.util.Collection;
public interface SchemaStrategy<K, V> extends Reloadable {
Collection<V> all();
V get(K key);
void insert(K key, V value);
void remove(K key);
}
@@ -0,0 +1,62 @@
package me.redtea.nodropx.libs.carcadex.schema.impl.binary;
import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy;
import java.io.*;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
public class BinarySchemaStrategy<K, V> extends AbstractFileSchemaStrategy<K, V> {
public BinarySchemaStrategy(Path dir) {
super(dir);
}
@Override
public Collection<V> all() {
if(dir.toFile().listFiles() != null)
return Arrays.stream(dir.toFile().listFiles()).map(this::read).collect(Collectors.toList());
else return new ArrayList<>();
}
@Override
public V get(K key) {
File file = new File(dir.toFile(), key+".bin");
return read(file);
}
private V read(File file) {
try {
if(!file.exists()) file.createNewFile();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
V result = (V) objectInputStream.readObject();
objectInputStream.close();
return result;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public void insert(K key, V value) {
try {
File file = new File(dir.toFile(), key+".bin");
if(!file.exists()) file.createNewFile();
FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(value);
objectOutputStream.flush();
objectOutputStream.close();
fileOutputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,43 @@
package me.redtea.nodropx.libs.carcadex.schema.impl.filesection;
import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy;
import me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.SectionParser;
import org.bukkit.configuration.ConfigurationSection;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
public abstract class FileSectionSchemaStrategy<K, V> extends AbstractFileSchemaStrategy<K, V> {
private final SectionParser sectionParser;
public FileSectionSchemaStrategy(Path dir, SectionParser sectionParser) {
super(dir);
this.sectionParser = sectionParser;
}
@Override
public Collection<V> all() {
if(dir.toFile().listFiles() == null) return new ArrayList<>();
return Arrays.stream(dir.toFile().listFiles()).map(it -> fromSection(sectionParser.parse(it))).collect(Collectors.toList());
}
@Override
public V get(K key) {
return fromSection(sectionParser.parse(Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key)))
.findFirst().get()));
}
@Override
public void insert(K key, V value) {
File file = Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key)))
.findFirst().get();
sectionParser.save(saveToSection(value, sectionParser.parse(file)), file);
}
abstract V fromSection(ConfigurationSection section);
abstract ConfigurationSection saveToSection(V v, ConfigurationSection section);
}
@@ -0,0 +1,10 @@
package me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser;
import org.bukkit.configuration.ConfigurationSection;
import java.io.File;
public interface SectionParser {
ConfigurationSection parse(File file);
void save(ConfigurationSection section, File file);
}
@@ -0,0 +1,25 @@
package me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.impl;
import me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.SectionParser;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.IOException;
public class YamlSectionParser implements SectionParser {
@Override
public ConfigurationSection parse(File file) {
return YamlConfiguration.loadConfiguration(file);
}
@Override
public void save(ConfigurationSection section, File file) {
try {
((FileConfiguration) section).save(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,67 @@
package me.redtea.nodropx.libs.carcadex.schema.impl.serialize;
import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy;
import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Collectors;
public class SerializeSchemaStrategy<K, V> extends AbstractFileSchemaStrategy<K, V> {
private final CommonSerializer<V> serializer;
public SerializeSchemaStrategy(Path dir, CommonSerializer<V> serializer) {
super(dir);
this.serializer = serializer;
}
@Override
public void close() {
}
@Override
public Collection<V> all() {
return Arrays.stream(dir.toFile().listFiles()).map(file -> {
try {
return serializer.deserialize(String.join("\n", Files.readAllLines(file.toPath())));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}).collect(Collectors.toList());
}
@Override
public V get(K key) {
try {
return serializer.deserialize(
String.join("\n", Files.readAllLines(
Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key)))
.findFirst().get().toPath()
)));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void insert(K key, V value) {
File file = new File(dir.toFile(), String.valueOf(key));
try {
if(!file.exists()) file.createNewFile();
BufferedWriter writer;
writer = new BufferedWriter(new FileWriter(file));
writer.write(serializer.serialize(value));
writer.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,6 @@
package me.redtea.nodropx.libs.carcadex.serializer;
public interface CommonSerializer<T> {
String serialize(T t);
T deserialize(String s);
}
@@ -0,0 +1,242 @@
package me.redtea.nodropx.libs.message.container;
import me.redtea.nodropx.libs.message.container.impl.MessagesImpl;
import me.redtea.nodropx.libs.message.factory.MessageFactory;
import me.redtea.nodropx.libs.message.factory.impl.LegacyFactoryImpl;
import me.redtea.nodropx.libs.message.model.Message;
import me.redtea.nodropx.libs.message.model.impl.NullMessage;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.nio.file.Path;
/**
* <h1>Container of messages</h1>
* <h2>Create container by plugin with file messages.yml</h2>
* <h3>Step 1. Create file messages.yml in resources folder (in jar of plugin)</h3>
* <p>Write in file your messages in YAML format. Use MiniMessages format for color messages. <br>
* See <a href="https://docs.advntr.dev/minimessage/format.html">https://docs.advntr.dev/minimessage/format.html</a></p>
* <p>For example I will be use this content:</p>
* <pre>
* {@code
* error: "<red>Error!"
* someRoot:
* message1: "<green>Message"
* message2:
* - "<green>Message start!"
* - "<green>Message end!"
*
* }
* </pre>
* <h3>Step 2. Load Messages container in your onEnable method</h3>
* <pre>
* {@code
* Messages messages = Messages.of(this);
* }
* </pre>
* <h3>Step 3. Done! Now you can use the messages variable to manage messages.</h3>
* <h2>Create container by plugin with custom file</h2>
* <p>Everything is the same, the same, only in jar create a file with the name you need (or several such files)
* and change OnEnable like this:</p>
* <pre>
* {@code
* Messages messages = Messages.of(this, "YOURFILENAME.yml");
* }
* </pre>
* <h2>Create container by ConfigurationSection</h2>
* <p>Here are a couple of examples of how this can be done:</p>
* <pre>
* {@code
* Messages messages = Messages.of(getConfig().getConfigurationSection("messages"));
*
* //or so
*
* ConfigurationSection section = ...;
* Messages messages = Messages.of(section);
* }
* </pre>
* @since 1.0.0
* @author itzRedTea
*/
public interface Messages {
/**
* <h1>Get message from container by key/yaml path</h1>
*
* <h2>For example</h2>
* <p><b>Yaml structure:</b></p>
* <pre>
* {@code
* error: "<red>Error!"
* someRoot:
* message1: "<green>Message"
* message2:
* - "<green>Message start!"
* - "<green>Message end!"
*
* }
* </pre>
*
* <p><b>Getting messages in code:</b></p>
* <pre>
* {@code
* Messages messages = ...; //Init Messages
* CommandSender sender = ...; //Getting CommandSender
* messages.get("error").send(sender); //Getting error message
* message.get("someRoot.message1").send(sender); //Getting someRoot.message1 message
* message.get("someRoot.message2").send(sender); //Getting someRoot.message2 message
* }
* </pre>
*
* @param key key of message
* @return Message
* @see Message
*/
Message get(String key);
/**
* <h1>Put message to container</h1>
* @param key key/yaml path to message
* @param message message to mut
* @return putted message
* @see Message
*/
Message put(String key, Message message);
/**
* @param key key/yaml path to message
* @return is there a message with such a key in the container?
*/
boolean has(String key);
/**
* <h1>Reload container with messages in section from args.</h1>
* @param section - ConfigurationSection with messages
*/
void reload(ConfigurationSection section);
/**
* <h1>Sets custom message parse factory.</h1>
*/
void factory(MessageFactory messageFactory);
/**
* <h1>Create container using configuration section</h1>
* Create container using messages from configuration section.
* <pre>
* {@code
* ConfigurationSection section = ...;
* Messages messages = Messages.of(section);
* }
* </pre>
* @param section - configuration section with messages
* @return container with messages
*/
static Messages of(ConfigurationSection section) {
return new MessagesImpl(section);
}
/**
* <h1>Create container using current plugin and custom filename</h1>
* Create container using file with your name from jar entry by default.
* A new file in plugin folder will be created with content from your file.
* <pre>
* {@code
* Messages messages = Messages.of(this, "YOURFILENAME.yml");
* }
* </pre>
* @param plugin - plugin that creates a container
* @return container with messages
*/
static Messages of(Plugin plugin, String fileName) {
File file = new File(plugin.getDataFolder(), fileName);
if(!file.exists()) plugin.saveResource(fileName, false);
return of(YamlConfiguration.loadConfiguration(file));
}
/**
* <h1>Create container using current plugin</h1>
* Create container using messages.yml file by default from jar entry.
* A new file in plugin folder will be created with content from messages.yml
* <pre>
* {@code
* Messages messages = Messages.of(this);
* }
* </pre>
* @param plugin - plugin that creates a container
* @return container with messages
*/
static Messages of(Plugin plugin) {
return of(plugin, "text/legacy/messages.yml");
}
/**
* <h1>Create container using file</h1>
* <pre>
* {@code
* File file = new File("msg_file.yml");
* Messages messages = Messages.of(file);
* }
* </pre>
* @param file - file with messages
* @return container with messages
*/
static Messages of(File file) {
return of(YamlConfiguration.loadConfiguration(file));
}
/**
* <h1>Create container using path</h1>
*
* @param path - file with messages
* @return container with messages
* @see Messages#of(File)
*/
static Messages of(Path path) {
return of(path.toFile());
}
/**
* Only legacy color codes. For versions < 1.18
* @return container with messages
*/
static Messages legacy(ConfigurationSection section) {
return asLegacy(of(section));
}
/** @see Messages#legacy(ConfigurationSection) */
static Messages legacy(Plugin plugin, String fileName) {
return asLegacy(of(plugin, fileName));
}
/** @see Messages#legacy(ConfigurationSection) */
static Messages legacy(Plugin plugin) {
return asLegacy(of(plugin));
}
/** @see Messages#legacy(ConfigurationSection) */
static Messages legacy(File file) {
return asLegacy(of(file));
}
/** @see Messages#legacy(ConfigurationSection) */
static Messages asLegacy(Messages messages) {
messages.factory(new LegacyFactoryImpl());
return messages;
}
/**
* @return empty messages container
* Use this with Reloader
*/
static Messages empty() {
return new MessagesImpl();
}
/**
* Singleton NULL_MESSAGE
* @see NullMessage
*/
Message NULL_MESSAGE = new NullMessage();
}
@@ -0,0 +1,85 @@
package me.redtea.nodropx.libs.message.container.impl;
import me.redtea.nodropx.libs.message.container.Messages;
import me.redtea.nodropx.libs.message.factory.MessageFactory;
import me.redtea.nodropx.libs.message.model.Message;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class MessagesImpl implements Messages {
private Map<String, Message> messages;
private MessageFactory factory = MessageFactory.instance;
private final Message NULL_MESSAGE = factory.nullMessage();
public MessagesImpl() {
}
public MessagesImpl(@NotNull ConfigurationSection section) {
messages = fromConfigurationToMap(section);
}
public MessagesImpl(@NotNull File file) {
this(YamlConfiguration.loadConfiguration(file));
}
@Override
public Message get(String key) {
return messages.get(key);
}
@Override
public Message put(String key, Message message) {
if(!has(key)) return NULL_MESSAGE;
return messages.put(key, message);
}
@Override
public boolean has(String key) {
return messages.containsKey(key);
}
@Override
public void reload(ConfigurationSection section) {
messages = fromConfigurationToMap(section);
}
@Override
public void factory(MessageFactory messageFactory) {
this.factory = messageFactory;
}
private Map<String, Message> fromConfigurationToMap(@NotNull ConfigurationSection section) {
Map<String, Message> data = new HashMap<>();
section.getKeys(false).forEach(key -> {
if (section.isConfigurationSection(key)) {
Map<String, Message> newMessages = fromConfigurationToMap(Objects.
requireNonNull(section.getConfigurationSection(key)));
newMessages.forEach((keyMessage, message) -> data.put(key + "." + keyMessage, message));
} else {
Message message = null;
if (section.isString(key)) {
message = factory.message(section.getString(key));
} else if (section.isList(key)) {
message = factory.message(section.getStringList(key));
}
if (message != null) {
data.put(key, message);
}
}
});
return data;
}
}
@@ -0,0 +1,21 @@
package me.redtea.nodropx.libs.message.factory;
import me.redtea.nodropx.libs.message.factory.impl.MessageFactoryImpl;
import me.redtea.nodropx.libs.message.model.Message;
import java.util.List;
import static me.redtea.nodropx.libs.message.container.Messages.NULL_MESSAGE;
public interface MessageFactory {
Message message(List<String> unparsed);
Message message(String msg);
Message legacyMessage(List<String> unparsed);
Message legacyMessage(String msg);
default Message nullMessage() {
return NULL_MESSAGE;
}
MessageFactory instance = new MessageFactoryImpl();
}
@@ -0,0 +1,44 @@
package me.redtea.nodropx.libs.message.factory.impl;
import com.google.common.collect.Lists;
import me.redtea.nodropx.libs.message.factory.MessageFactory;
import me.redtea.nodropx.libs.message.model.Message;
import me.redtea.nodropx.libs.message.model.impl.serialized.impl.LegacyMessage;
import me.redtea.nodropx.util.MD5ColorUtils;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import static net.md_5.bungee.api.ChatColor.COLOR_CHAR;
public class LegacyFactoryImpl implements MessageFactory {
@Override
public Message message(List<String> unparsed) {
if(unparsed == null || unparsed.isEmpty()) return nullMessage();
if(unparsed.stream().anyMatch(it -> it.contains("&"))) unparsed =
unparsed.stream()
.map(MD5ColorUtils::translateHexColorCodes)
.collect(Collectors.toList());
return new LegacyMessage(unparsed);
}
@Override
public Message message(String msg) {
if(msg == null) return nullMessage();
if(msg.contains("&")) msg = MD5ColorUtils.translateHexColorCodes(msg);
return new LegacyMessage(Lists.newArrayList(msg));
}
@Override
public Message legacyMessage(List<String> unparsed) {
return message(unparsed);
}
@Override
public Message legacyMessage(String msg) {
return message(Lists.newArrayList(msg));
}
}
@@ -0,0 +1,33 @@
package me.redtea.nodropx.libs.message.factory.impl;
import com.google.common.collect.Lists;
import me.redtea.nodropx.libs.message.factory.MessageFactory;
import me.redtea.nodropx.libs.message.model.Message;
import me.redtea.nodropx.libs.message.model.impl.ComponentMessage;
import me.redtea.nodropx.libs.message.model.impl.serialized.impl.LegacyMessage;
import java.util.List;
public class MessageFactoryImpl implements MessageFactory {
@Override
public Message message(List<String> unparsed) {
if(unparsed == null || unparsed.isEmpty()) return nullMessage();
return new ComponentMessage(unparsed);
}
@Override
public Message message(String msg) {
return message(Lists.newArrayList(msg));
}
@Override
public Message legacyMessage(List<String> unparsed) {
if(unparsed == null || unparsed.isEmpty()) return nullMessage();
return new LegacyMessage(unparsed);
}
@Override
public Message legacyMessage(String msg) {
return legacyMessage(Lists.newArrayList(msg));
}
}
@@ -0,0 +1,86 @@
package me.redtea.nodropx.libs.message.model;
import me.redtea.nodropx.NoDropX;
import me.redtea.nodropx.libs.message.model.impl.ComponentMessage;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public abstract class AbstractMessage implements Message {
protected final List<String> unparsed;
protected List<Component> parsed = null;
protected final BukkitAudiences adventure;
public AbstractMessage(List<String> unparsed) {
this.unparsed = unparsed;
this.adventure = BukkitAudiences.create(NoDropX.getInstance());
}
protected abstract Component parse(String unparsed);
private void parse() {
parsed = new ArrayList<>();
for(String s : unparsed) {
parsed.add(parse(s));
}
}
@Override
public void send(@NotNull CommandSender sender) {
parse();
Audience audience = adventure.sender(sender);
parsed.forEach(audience::sendMessage);
}
private Component cacheComponent = null;
@Override
@NotNull
public List<Component> asComponentList() {
parse();
return parsed;
}
@Override
@NotNull
public Message replaceAll(@NotNull String from, @NotNull String to) {
return new ComponentMessage(unparsed.stream().map(it -> it.replaceAll(from, to)).collect(Collectors.toList()));
}
@Override
public @NotNull Component asComponent() {
if(cacheComponent == null) {
parse();
Component first = parsed.get(0);
for(int i = 1; i < parsed.size(); i++) {
first = first.append(parsed.get(i)).append(Component.newline());
}
cacheComponent = first;
}
return cacheComponent;
}
@Override
@NotNull
public List<String> asUnparsedStringList() {
return unparsed;
}
private String cacheString = null;
@Override
@NotNull
public String asUnparsedString(@NotNull String newLine) {
if(cacheString == null) cacheString = String.join(newLine, unparsed);
return cacheString;
}
}
@@ -0,0 +1,63 @@
package me.redtea.nodropx.libs.message.model;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* <h1>Message</h1>
* This class provides message management methods. You can also convert it to Component.
* @since 1.0.0
* @author itzRedTea
*/
public interface Message extends ComponentLike {
/**
* Send message to CommandSender
* @param sender recipient of the message
*/
void send(@NotNull CommandSender sender);
/**
* Replaces all matches in unparsed message
* @param from what you want to replace
* @param to what do you want to replace
* @return copy of the Message with the changes applied to it
*/
@NotNull
Message replaceAll(@NotNull String from, @NotNull String to);
/**
* Convert message to list of components
* @return list of components
*/
@NotNull
List<Component> asComponentList();
/**
* Get unparsed value of message
*/
@NotNull
List<String> asUnparsedStringList();
/**
* Get unparsed value of message joined to one string
*/
@NotNull
String asUnparsedString(@NotNull String newLine);
/**
* @see Message#asUnparsedString(String) ()
*/
default String asUnparsedString() {
return asUnparsedString("\n");
}
/**
* @see Message#replaceAll(String, String)
*/
@NotNull
default Message replaceAll(@NotNull Object from, @NotNull Object to) {
return replaceAll(String.valueOf(from), String.valueOf(to));
}
}
@@ -0,0 +1,20 @@
package me.redtea.nodropx.libs.message.model.impl;
import me.redtea.nodropx.libs.message.model.AbstractMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import java.util.List;
public class ComponentMessage extends AbstractMessage {
private final MiniMessage mm = MiniMessage.get();
public ComponentMessage(List<String> unparsed) {
super(unparsed);
}
@Override
protected Component parse(String unparsed) {
return mm.deserialize(unparsed);
}
}
@@ -0,0 +1,41 @@
package me.redtea.nodropx.libs.message.model.impl;
import me.redtea.nodropx.libs.message.model.Message;
import net.kyori.adventure.text.Component;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public final class NullMessage implements Message {
@Override
public void send(@NotNull CommandSender sender) {
}
@Override
public @NotNull Message replaceAll(@NotNull String from, @NotNull String to) {
return this;
}
@Override
public @NotNull List<Component> asComponentList() {
return new ArrayList<>();
}
@Override
public @NotNull List<String> asUnparsedStringList() {
return new ArrayList<>();
}
@Override
public @NotNull String asUnparsedString(@NotNull String newLine) {
return "";
}
@Override
public @NotNull Component asComponent() {
return Component.text("");
}
}
@@ -0,0 +1,22 @@
package me.redtea.nodropx.libs.message.model.impl.serialized;
import me.redtea.nodropx.libs.message.model.AbstractMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import java.util.List;
public class SerializedMessage extends AbstractMessage {
private final ComponentSerializer<Component, TextComponent, String> serializer;
public SerializedMessage(List<String> unparsed, ComponentSerializer<Component, TextComponent, String> serializer) {
super(unparsed);
this.serializer = serializer;
}
@Override
protected Component parse(String unparsed) {
return serializer.deserialize(unparsed);
}
}
@@ -0,0 +1,16 @@
package me.redtea.nodropx.libs.message.model.impl.serialized.impl;
import me.redtea.nodropx.libs.message.model.impl.serialized.SerializedMessage;
import me.redtea.nodropx.util.MD5ColorUtils;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.ChatColor;
import java.util.List;
import java.util.stream.Collectors;
public class LegacyMessage extends SerializedMessage {
public LegacyMessage(List<String> unparsed) {
super(unparsed.stream().map(MD5ColorUtils::translateHexColorCodes).collect(Collectors.toList()),
LegacyComponentSerializer.builder().build());
}
}
@@ -0,0 +1,39 @@
package me.redtea.nodropx.listener;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.api.event.NoDropItemThrownEvent;
import me.redtea.nodropx.service.event.EventService;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
@RequiredArgsConstructor
public class InventoryHandler implements Listener {
private final EventService eventService;
@EventHandler
public void onMove(InventoryClickEvent event) {
if(event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) {
call(event, event.getCurrentItem());
} else if(event.getAction().equals(InventoryAction.PLACE_ALL)
|| event.getAction().equals(InventoryAction.PLACE_ONE)
|| event.getAction().equals(InventoryAction.PLACE_SOME)) {
call(event, event.getCursor());
}
}
private void call(InventoryClickEvent event, ItemStack item) {
eventService.callThrownEvent(
(Player)event.getWhoClicked(),
item,
NoDropItemThrownEvent.Reason.MOVING_TO_OTHER_INVENTORY,
Optional.of(event)
);
}
}
@@ -0,0 +1,50 @@
package me.redtea.nodropx.listener;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.api.event.NoDropItemThrownEvent;
import me.redtea.nodropx.gui.facade.GuiFacade;
import me.redtea.nodropx.service.event.EventService;
import me.redtea.nodropx.service.respawn.RespawnService;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.util.Optional;
@RequiredArgsConstructor
public class PlayerHandler implements Listener {
private final RespawnService respawnService;
private final EventService eventService;
private final GuiFacade guiFacade;
//TODO check for armor
@EventHandler
public void onDrop(PlayerDropItemEvent event) {
eventService.callThrownEvent(
event.getPlayer(),
event.getItemDrop().getItemStack(),
NoDropItemThrownEvent.Reason.DROP_BY_PLAYER,
Optional.of(event)
);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onDeath(PlayerDeathEvent event) {
respawnService.saveItemsBeforeDeath(event);
}
@EventHandler
public void onRespawn(PlayerRespawnEvent event) {
respawnService.giveSavedItems(event.getPlayer());
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
guiFacade.clearCache(event.getPlayer());
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.model.cosmetic;
import org.bukkit.inventory.ItemStack;
public interface CosmeticStrategy {
void apply(ItemStack itemStack);
void loadYaml(Object fromYaml);
String tag();
}
@@ -0,0 +1,26 @@
package me.redtea.nodropx.model.cosmetic.impl;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class DisplayNameCosmetic implements CosmeticStrategy {
private String displayName;
@Override
public void apply(ItemStack itemStack) {
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName(displayName);
itemStack.setItemMeta(meta);
}
@Override
public void loadYaml(Object fromYaml) {
displayName = (String) fromYaml;
}
@Override
public String tag() {
return "name";
}
}
@@ -0,0 +1,37 @@
package me.redtea.nodropx.model.cosmetic.impl;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LoreKyoriCosmetic implements CosmeticStrategy {
private List<Component> lore;
private final MiniMessage mm = MiniMessage.get();
@Override
public void apply(ItemStack itemStack) {
List<Component> lore = itemStack.lore();
if(lore == null) lore = new ArrayList<>();
lore.addAll(this.lore);
itemStack.lore(lore);
}
@Override
public void loadYaml(Object fromYaml) {
List<String> list = (List<String>) fromYaml;
lore = list.stream().map(mm::deserialize).map(it -> Component
.empty()
.decoration(TextDecoration.ITALIC, false)
.append(it)).collect(Collectors.toList());
}
@Override
public String tag() {
return "lore";
}
}
@@ -0,0 +1,37 @@
package me.redtea.nodropx.model.cosmetic.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.NoDropX;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class LoreLegacyCosmetic implements CosmeticStrategy {
private List<String> lore;
@Override
public void apply(ItemStack itemStack) {
ItemMeta meta = itemStack.getItemMeta();
List<String> lore = meta.getLore();
if(lore == null) lore = new ArrayList<>();
lore.addAll(this.lore);
meta.setLore(lore);
itemStack.setItemMeta(meta);
}
@Override
public void loadYaml(Object fromYaml) {
List<String> list = (List<String>) fromYaml;
lore = list.stream().map(it -> ChatColor.translateAlternateColorCodes('&', it)).collect(Collectors.toList());
}
@Override
public String tag() {
return "lore";
}
}
@@ -0,0 +1,43 @@
package me.redtea.nodropx.model.cosmetic.impl;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import me.redtea.nodropx.util.MD5ColorUtils;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static net.md_5.bungee.api.ChatColor.COLOR_CHAR;
public class LoreMD5Cosmetics implements CosmeticStrategy {
private List<String> lore;
@Override
public void apply(ItemStack itemStack) {
ItemMeta meta = itemStack.getItemMeta();
List<String> lore = meta.getLore();
if(lore == null) lore = new ArrayList<>();
lore.addAll(this.lore);
meta.setLore(lore);
itemStack.setItemMeta(meta);
}
@Override
public void loadYaml(Object fromYaml) {
List<String> list = (List<String>) fromYaml;
lore = list.stream().map(MD5ColorUtils::translateHexColorCodes).collect(Collectors.toList());
}
@Override
public String tag() {
return "lore";
}
}
@@ -0,0 +1,73 @@
package me.redtea.nodropx.schema;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.val;
import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import java.io.File;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
public class ItemStackListSchema implements SchemaStrategy<UUID, List<ItemStack>> {
private final File dir;
@Override
public Collection<List<ItemStack>> all() {
val res = new ArrayList<List<ItemStack>>();
for(File file : dir.listFiles()) {
res.add(get(UUID.fromString(file.getName().substring(file.getName().length()-4))));
}
return res;
}
@Override
public List<ItemStack> get(UUID key) {
File file = new File(dir, key.toString() + ".yml");
if(!file.exists()) return new ArrayList<>();
val conf = YamlConfiguration.loadConfiguration(file);
val res = new ArrayList<ItemStack>();
for(String k : conf.getKeys(false)) {
res.add(conf.getItemStack(k));
}
return res;
}
@Override
@SneakyThrows
public void insert(UUID key, List<ItemStack> value) {
File file = new File(dir, key.toString() + ".yml");
if(!file.exists()) file.createNewFile();
val conf = YamlConfiguration.loadConfiguration(file);
for(int i = 0; i < value.size(); ++i) {
conf.set(String.valueOf(i), value.get(i));
}
conf.save(file);
}
@Override
public void remove(UUID key) {
File file = new File(dir, key.toString() + ".yml");
if(file.exists()) file.delete();
}
@Override
@SneakyThrows
public void init() {
if(!dir.exists()) {
Files.createDirectories(dir.toPath());
}
}
@Override
public void close() {
}
}
@@ -0,0 +1,11 @@
package me.redtea.nodropx.service.cosmetic;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
public interface CosmeticService {
void applyAll(ItemStack item);
void add(CosmeticStrategy strategy);
void load(ConfigurationSection section);
}
@@ -0,0 +1,43 @@
package me.redtea.nodropx.service.cosmetic.impl;
import lombok.val;
import me.redtea.nodropx.model.cosmetic.CosmeticStrategy;
import me.redtea.nodropx.service.cosmetic.CosmeticService;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
public class CosmeticServiceImpl implements CosmeticService {
private final List<CosmeticStrategy> strategies = new ArrayList<>();
@Override
public void applyAll(ItemStack item) {
strategies.forEach(it -> {
try {
it.apply(item);
} catch (Throwable e) {
Bukkit.getLogger().severe("[NoDropX Cosmetic] Failed to apply cosmetic with tag " + it.tag());
e.printStackTrace();
}
});
}
@Override
public void add(CosmeticStrategy strategy) {
strategies.add(strategy);
}
@Override
public void load(ConfigurationSection section) {
List<CosmeticStrategy> disabled = new ArrayList<>();
for(CosmeticStrategy strategy : strategies) {
val obj = section.get(strategy.tag());
if(obj == null) disabled.add(strategy);
else strategy.loadYaml(obj);
}
strategies.removeAll(disabled);
}
}
@@ -0,0 +1,12 @@
package me.redtea.nodropx.service.event;
import me.redtea.nodropx.api.event.NoDropItemThrownEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
public interface EventService {
void callThrownEvent(Player who, ItemStack item, NoDropItemThrownEvent.Reason reason, Optional<Cancellable> rootEvent);
}
@@ -0,0 +1,29 @@
package me.redtea.nodropx.service.event.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.api.event.NoDropItemThrownEvent;
import me.redtea.nodropx.service.event.EventService;
import me.redtea.nodropx.service.nodrop.NoDropService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.inventory.ItemStack;
import java.util.Optional;
@RequiredArgsConstructor
public class EventServiceImpl implements EventService {
private final NoDropService noDropService;
@Override
public void callThrownEvent(Player who, ItemStack item, NoDropItemThrownEvent.Reason reason, Optional<Cancellable> rootEvent) {
if(!noDropService.isNoDrop(item)) return;
NoDropItemThrownEvent callingEvent = new NoDropItemThrownEvent(
who,
reason,
item
);
Bukkit.getPluginManager().callEvent(callingEvent);
rootEvent.ifPresent(cancellable -> cancellable.setCancelled(callingEvent.isCancelled()));
}
}
@@ -0,0 +1,26 @@
package me.redtea.nodropx.service.nbt;
import org.bukkit.inventory.ItemStack;
/**
* Service for working with NBT tags
*/
public interface NBTService {
/**
* Sets the NBT tag "isNoDrop", which indicates that the item is no drop
* @param item editable item
* @param isNoDrop tag value
* @since 1.0.0
* @author redtea
*/
void setNoDropTag(ItemStack item, boolean isNoDrop);
/**
* Gets the value of the "isNoDrop" tag, which indicates that the item is no drop
* @param item checking item
* @return tag value
* @since 1.0.0
* @author redtea
*/
boolean getNoDropTag(ItemStack item);
}
@@ -0,0 +1,22 @@
package me.redtea.nodropx.service.nbt.impl;
import de.tr7zw.changeme.nbtapi.NBTItem;
import me.redtea.nodropx.service.nbt.NBTService;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
public class Tr7zwNBTServiceImpl implements NBTService {
@Override
public void setNoDropTag(ItemStack item, boolean isNoDrop) {
NBTItem nbtItem = new NBTItem(item);
nbtItem.setBoolean("isNoDrop", true);
nbtItem.applyNBT(item);
}
@Override
public boolean getNoDropTag(ItemStack item) {
if(item == null || item.getType() == Material.AIR) return false;
NBTItem nbtItem = new NBTItem(item);
return nbtItem.hasTag("isNoDrop") && nbtItem.getBoolean("isNoDrop");
}
}
@@ -0,0 +1,26 @@
package me.redtea.nodropx.service.nodrop;
import org.bukkit.inventory.ItemStack;
/**
* Service for changing and checking no drop status of item
*/
public interface NoDropService {
/**
* Set no drop to item
* @param item the item who's no drop property are changing
* @param isNoDrop will the item can drop from inventory on death?
* @author redtea
* @since 1.0.0
*/
void setNoDrop(ItemStack item, boolean isNoDrop);
/**
* Check if item is no drop item
* @param item the item being checked
* @return true if item can not drop from inventory on death
* @author redtea
* @since 1.0.0
*/
boolean isNoDrop(ItemStack item);
}
@@ -0,0 +1,25 @@
package me.redtea.nodropx.service.nodrop.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.service.cosmetic.CosmeticService;
import me.redtea.nodropx.service.nbt.NBTService;
import me.redtea.nodropx.service.nodrop.NoDropService;
import org.bukkit.inventory.ItemStack;
@RequiredArgsConstructor
public class NoDropServiceImpl implements NoDropService {
private final NBTService nbtService;
private final CosmeticService cosmeticService;
@Override
public void setNoDrop(ItemStack item, boolean isNoDrop) {
nbtService.setNoDropTag(item, isNoDrop);
cosmeticService.applyAll(item);
}
@Override
public boolean isNoDrop(ItemStack item) {
if(item == null) return false;
return nbtService.getNoDropTag(item);
}
}
@@ -0,0 +1,9 @@
package me.redtea.nodropx.service.respawn;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
public interface RespawnService {
void giveSavedItems(Player player);
void saveItemsBeforeDeath(PlayerDeathEvent event);
}
@@ -0,0 +1,70 @@
package me.redtea.nodropx.service.respawn.impl;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.api.event.NoDropItemDropOnDeathEvent;
import me.redtea.nodropx.libs.carcadex.repo.Repo;
import me.redtea.nodropx.service.nodrop.NoDropService;
import me.redtea.nodropx.service.respawn.RespawnService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
@RequiredArgsConstructor
public class RespawnServiceImpl implements RespawnService {
private final NoDropService noDropService;
private final Map<String, Pair[]> toRespawn = new HashMap<>();
private final Repo<String, List<Integer>> capacity;
@Override
public void giveSavedItems(Player player) {
if(toRespawn.containsKey(player.getName())) {
Pair[] pairs = toRespawn.get(player.getName());
for(Pair pair : pairs) player.getInventory().setItem(pair.slot, pair.item);
}
}
@Override
public void saveItemsBeforeDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
Set<Integer> noDropSlots = new HashSet<>();
for(int i = 0; i < player.getInventory().getSize(); ++i) {
ItemStack item = player.getInventory().getItem(i);
if(!noDropService.isNoDrop(item)) continue;
NoDropItemDropOnDeathEvent callingEvent = new NoDropItemDropOnDeathEvent(
player,
item
);
Bukkit.getPluginManager().callEvent(callingEvent);
if(!callingEvent.isWillDropped()) {
noDropSlots.add(i);
noDropSlots.addAll(getProtectedSlots(item));
}
}
List<Pair> toSave = new ArrayList<>();
for(int slot : noDropSlots) {
ItemStack item = player.getInventory().getItem(slot);
toSave.add(new Pair(slot, item));
event.getDrops().remove(item);
}
if(toSave.isEmpty()) return;
toRespawn.put(player.getName(), toSave.toArray(new Pair[0]));
}
@AllArgsConstructor
static class Pair {
int slot;
ItemStack item;
}
private List<Integer> getProtectedSlots(ItemStack i) {
return capacity.get(i.getType().name()).orElse(new ArrayList<>());
}
}
@@ -0,0 +1,12 @@
package me.redtea.nodropx.service.storage;
import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.UUID;
public interface StorageService {
void addItem(UUID playerId, ItemStack item);
void removeItem(UUID playerId, ItemStack item);
List<ItemStack> getItems(UUID playerId);
}
@@ -0,0 +1,43 @@
package me.redtea.nodropx.service.storage.impl;
import lombok.RequiredArgsConstructor;
import me.redtea.nodropx.gui.facade.GuiFacade;
import me.redtea.nodropx.libs.carcadex.repo.MutableRepo;
import me.redtea.nodropx.service.nodrop.NoDropService;
import me.redtea.nodropx.service.storage.StorageService;
import org.bukkit.Bukkit;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
public class StorageServiceImpl implements StorageService {
private final GuiFacade guiFacade;
private final MutableRepo<UUID, List<ItemStack>> pagesRepo;
private final NoDropService noDropService;
@Override
public void addItem(UUID playerId, ItemStack item) {
List<ItemStack> items = pagesRepo.get(playerId).orElse(new ArrayList<>());
if(!noDropService.isNoDrop(item)) throw new IllegalArgumentException("Item must be no drop!");
items.add(item);
pagesRepo.update(playerId, items);
guiFacade.clearCache(Bukkit.getPlayer(playerId));
}
@Override
public void removeItem(UUID playerId, ItemStack item) {
List<ItemStack> items = pagesRepo.get(playerId).orElse(null);
if(items == null) return;
items.remove(item);
pagesRepo.update(playerId, items);
guiFacade.clearCache(Bukkit.getPlayer(playerId));
}
@Override
public List<ItemStack> getItems(UUID playerId) {
return pagesRepo.get(playerId).orElse(new ArrayList<>());
}
}
@@ -0,0 +1,26 @@
package me.redtea.nodropx.util;
import net.md_5.bungee.api.ChatColor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static net.md_5.bungee.api.ChatColor.COLOR_CHAR;
public class MD5ColorUtils {
private static final Pattern HEX_PATTERN = Pattern.compile("&#([a-f0-9]{6})");
public static String translateHexColorCodes(String message) {
Matcher matcher = HEX_PATTERN.matcher(message);
StringBuffer buffer = new StringBuffer(message.length() + 4 * 8);
while (matcher.find()) {
String group = matcher.group(1);
matcher.appendReplacement(buffer, COLOR_CHAR + "x"
+ COLOR_CHAR + group.charAt(0) + COLOR_CHAR + group.charAt(1)
+ COLOR_CHAR + group.charAt(2) + COLOR_CHAR + group.charAt(3)
+ COLOR_CHAR + group.charAt(4) + COLOR_CHAR + group.charAt(5)
);
}
return ChatColor.translateAlternateColorCodes('&', matcher.appendTail(buffer).toString());
}
}
@@ -0,0 +1,50 @@
package me.redtea.nodropx.util;
import lombok.val;
import org.bukkit.Material;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
public class MaterialUtils {
@NotNull
public static Material getMaterialFromString(String s) {
if(s == null || s.equals("")) return Material.AIR;
Material material = Material.matchMaterial(s);
if(material != null) return material;
String[] spl = s.split(":");
if (spl[0].equals("minecraft")) {
material = Material.matchMaterial(spl[1]);
} else {
if(spl.length < 2) {
material = Material.matchMaterial(spl[0]);
} else {
material = Material.matchMaterial(spl[0]+"_"+spl[1]);;
}
}
return material == null ? Material.AIR : material;
}
public void remove(Inventory inventory, Material type, int amount) {
if (amount <= 0) return;
int size = inventory.getSize();
for (int slot = 0; slot < size; slot++) {
ItemStack is = inventory.getItem(slot);
if (is == null) continue;
if (type == is.getType()) {
int newAmount = is.getAmount() - amount;
if (newAmount > 0) {
is.setAmount(newAmount);
break;
} else {
inventory.clear(slot);
amount = -newAmount;
if (amount == 0) break;
}
}
}
}
}
+1
View File
@@ -0,0 +1 @@
CHAINMAIL_HELMET: [ 9, 10, 11, 12, 13, 14, 15, 16, 17 ]
+2
View File
@@ -0,0 +1,2 @@
forceUseLegacy: false
forceUseMD5Colors: false
+16
View File
@@ -0,0 +1,16 @@
name: NoDropX
version: '${version}'
main: me.redtea.nodropx.NoDropX
api-version: '1.13'
description: Set no drop status to your items
author: itzRedTea
authors: [itzRedTea]
permissions:
nodropx.give:
description: "Allows give no drop items"
nodropx.reload:
description: "Allows reload the plugin"
nodropx.gui:
escription: "Allows open no drop gui"
default: true
@@ -0,0 +1,3 @@
#name: "No drop"
lore:
- "&7[&a✔&7] &aNo drop item!"
@@ -0,0 +1,23 @@
usage:
- "&7[ &aUsage of command &e/nodrop &7]"
- "&7| &a/give [player] [item] (amount) &e- give no drop item"
give:
fail: "&4[!] &cFail! Check item type for valid."
success: "&2&l[!] &aGived &e%item%&a to &e%player%&a amount &e%amount%&a!"
reload: "&aPlugin successfully reloaded at %time% ms"
noPerms: "&4&l[!] &cNo perms for this!"
noConsole: "&4&l[!] &cOnly players can use it!"
noExists: "&4&l[!] &cThis player doesnt exists!"
gui:
title: "&lPersonal storage"
frame:
name: "&7"
material: GLASS_PANE
prev:
name: "&cPrevious"
material: ARROW
next:
name: "&cNext"
material: ARROW
@@ -0,0 +1,3 @@
#name: "No drop"
lore:
- "&7[&a✔&7] &#68cf0cN&#6cd41fo &#6fd932d&#73dd46r&#77e259o&#7be76cp &#7eec7fi&#82f192t&#86f5a6e&#89fab9m&#8dffcc!!"
+23
View File
@@ -0,0 +1,23 @@
usage:
- "&7[ &aUsage of command &e/nodrop &7]"
- "&7| &a/give [player] [item] (amount) &e- give no drop item"
give:
fail: "&4[!] &cFail! Check item type for valid."
success: "&2&l[!] &aGived &e%item%&a to &e%player%&a amount &e%amount%&a!"
reload: "&aPlugin successfully reloaded at %time% ms"
noPerms: "&4&l[!] &cNo perms for this!"
noConsole: "&4&l[!] &cOnly players can use it!"
noExists: "&4&l[!] &cThis player doesnt exists!"
gui:
title: "&lPersonal storage"
frame:
name: "&7"
material: GLASS_PANE
prev:
name: "&cPrevious"
material: ARROW
next:
name: "&cNext"
material: ARROW
+3
View File
@@ -0,0 +1,3 @@
#name: "No drop"
lore:
- "<gray>[<green>✔<gray>] <gradient:#68CF0C:#8DFFCC>No drop item!"
+24
View File
@@ -0,0 +1,24 @@
usage:
- "<gray>[ <green>Usage of command <yellow>/nodrop <gray>]"
- "<gray>| <green>/give [player] [item] (amount) <yellow>- give no drop item"
give:
fail: "<dark_red><bold>[!] <red>Fail! Check item type for valid."
success: "<green><bold>[!] <reset><green>Gived <yellow>%item%<green> to <yellow>%player%<green> amount <yellow>%amount%<green>!"
reload: "<green>Plugin successfully reloaded at %time% ms"
noPerms: "<dark_red><bold>[!] <red>No perms for this!"
noConsole: "<dark_red><bold>[!] <red>Only players can use it!"
noExists: "<dark_red><bold>[!] <red>This player doesnt exists!"
gui:
title: "<bold>Personal storage"
frame:
name: "&7"
material: GLASS_PANE
prev:
name: "<red>Previous"
material: ARROW
next:
name: "<red>Next"
material: ARROW