9
0
mirror of https://github.com/HibiscusMC/HMCCosmetics.git synced 2025-12-31 04:46:42 +00:00

Added wardrobe (Despawning and entity skin not functional)

This commit is contained in:
HeroBrineGoat
2022-02-02 18:28:45 -05:00
parent 39ac2713b7
commit 72f92be6f6
86 changed files with 589 additions and 544 deletions

View File

@@ -0,0 +1,193 @@
package io.github.fisher2911.hmccosmetics;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import io.github.fisher2911.hmccosmetics.command.CosmeticsCommand;
import io.github.fisher2911.hmccosmetics.concurrent.Threads;
import io.github.fisher2911.hmccosmetics.config.Settings;
import io.github.fisher2911.hmccosmetics.cosmetic.CosmeticManager;
import io.github.fisher2911.hmccosmetics.database.Database;
import io.github.fisher2911.hmccosmetics.database.DatabaseFactory;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.gui.CosmeticsMenu;
import io.github.fisher2911.hmccosmetics.hook.HookManager;
import io.github.fisher2911.hmccosmetics.hook.item.ItemsAdderHook;
import io.github.fisher2911.hmccosmetics.listener.ClickListener;
import io.github.fisher2911.hmccosmetics.listener.CosmeticFixListener;
import io.github.fisher2911.hmccosmetics.listener.JoinListener;
import io.github.fisher2911.hmccosmetics.listener.PlayerShiftListener;
import io.github.fisher2911.hmccosmetics.listener.RespawnListener;
import io.github.fisher2911.hmccosmetics.listener.TeleportListener;
import io.github.fisher2911.hmccosmetics.message.MessageHandler;
import io.github.fisher2911.hmccosmetics.message.Messages;
import io.github.fisher2911.hmccosmetics.message.Translation;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import me.mattstudios.mf.base.CommandManager;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
public class HMCCosmetics extends JavaPlugin {
public static final Path PLUGIN_FOLDER = Paths.get("plugins", "HMCCosmetics");
private ProtocolManager protocolManager;
private Settings settings;
private UserManager userManager;
private CosmeticManager cosmeticManager;
private MessageHandler messageHandler;
private CosmeticsMenu cosmeticsMenu;
private CommandManager commandManager;
private Database database;
private BukkitTask saveTask;
@Override
public void onEnable() {
final int pluginId = 13873;
final Metrics metrics = new Metrics(this, pluginId);
protocolManager = ProtocolLibrary.getProtocolManager();
this.settings = new Settings(this);
this.messageHandler = new MessageHandler(this);
this.userManager = new UserManager(this);
this.cosmeticManager = new CosmeticManager(new HashMap<>());
this.cosmeticsMenu = new CosmeticsMenu(this);
this.userManager.startTeleportTask();
try {
this.database = DatabaseFactory.create(this);
} catch (final SQLException exception) {
exception.printStackTrace();
}
this.registerCommands();
this.registerListeners();
if (!HookManager.getInstance().isEnabled(ItemsAdderHook.class)) {
this.load();
}
HookManager.getInstance().registerListeners(this);
this.saveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
this,
() -> Threads.getInstance().execute(
() -> this.database.saveAll()
),
20 * 60,
20 * 60
);
}
@Override
public void onDisable() {
this.saveTask.cancel();
this.database.saveAll();
this.messageHandler.close();
this.userManager.cancelTeleportTask();
this.userManager.removeAll();
Threads.getInstance().onDisable();
this.database.close();
}
private void registerListeners() {
List.of(
new JoinListener(this),
new ClickListener(this),
new TeleportListener(this),
new RespawnListener(this),
new CosmeticFixListener(this),
new PlayerShiftListener(this)
).
forEach(
listener -> this.getServer().getPluginManager()
.registerEvents(listener, this)
);
}
private void registerCommands() {
this.commandManager = new CommandManager(this, true);
this.commandManager.getMessageHandler().register(
"cmd.no.console", player ->
this.messageHandler.sendMessage(
player,
Messages.MUST_BE_PLAYER
)
);
this.commandManager.getCompletionHandler().register("#types",
resolver ->
Arrays.stream(ArmorItem.Type.
values()).
map(ArmorItem.Type::toString).
collect(Collectors.toList())
);
this.commandManager.getCompletionHandler().register("#ids",
resolver ->
this.cosmeticManager.getAll().stream().map(ArmorItem::getId)
.collect(Collectors.toList()));
this.commandManager.register(new CosmeticsCommand(this));
}
public void load() {
Bukkit.getScheduler().runTaskAsynchronously(this,
() -> {
this.settings.load();
this.messageHandler.load();
this.cosmeticsMenu.load();
Translation.getInstance().load();
this.database.load();
});
}
public void reload() {
Bukkit.getScheduler().runTaskAsynchronously(this,
() -> {
this.settings.load();
this.messageHandler.load();
this.cosmeticsMenu.reload();
Translation.getInstance().load();
});
}
public Settings getSettings() {
return settings;
}
public MessageHandler getMessageHandler() {
return messageHandler;
}
public UserManager getUserManager() {
return userManager;
}
public CosmeticManager getCosmeticManager() {
return cosmeticManager;
}
public CosmeticsMenu getCosmeticsMenu() {
return cosmeticsMenu;
}
public ProtocolManager getProtocolManager() {
return protocolManager;
}
public Database getDatabase() {
return database;
}
}

View File

@@ -0,0 +1,94 @@
package io.github.fisher2911.hmccosmetics.api;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import java.util.ArrayList;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
/**
* Wrapper for ArmorItem used internally for convenience and safety
*/
public class CosmeticItem {
private final ArmorItem armorItem;
public CosmeticItem(final ArmorItem armorItem) {
this.armorItem = armorItem;
}
/**
* @param itemStack the {@link org.bukkit.inventory.ItemStack} display item
* @param id the id of the item
* @param type the cosmetic item type
* @param dyeable whether the item can be dyed
* @param rgb from Bukkit's {@link Color#asRGB()}
*/
public CosmeticItem(final ItemStack itemStack, final String id, final ArmorItem.Type type,
final boolean dyeable, final int rgb) {
this.armorItem = new ArmorItem(itemStack, id, new ArrayList<>(), "", type, dyeable, rgb);
}
/**
* @param material the {@link org.bukkit.Material} display item
* @param id the id of the item
* @param type the cosmetic item type
* @param dyeable whether the item can be dyed
* @param rgb from Bukkit's {@link Color#asRGB()}
*/
public CosmeticItem(final Material material, final String id, final ArmorItem.Type type,
final boolean dyeable, final int rgb) {
this.armorItem = new ArmorItem(material, id, new ArrayList<>(), "", type, dyeable, rgb);
}
/**
* @param itemStack the {@link org.bukkit.inventory.ItemStack} display item
* @param id the id of the item
* @param type the cosmetic item type
*/
public CosmeticItem(final ItemStack itemStack, final String id, final ArmorItem.Type type) {
this(itemStack, id, type, false, -1);
}
/**
* @param material the {@link org.bukkit.Material} display item
* @param id the id of the item
* @param type the cosmetic item type
*/
public CosmeticItem(final Material material, final String id, final ArmorItem.Type type) {
this(material, id, type, false, -1);
}
public ItemStack getColored() {
return this.armorItem.getColored();
}
public ItemStack getItemStack() {
return this.armorItem.getItemStack();
}
public String getId() {
return this.armorItem.getId();
}
public ArmorItem.Type getType() {
return this.armorItem.getType();
}
public boolean isDyeable() {
return this.armorItem.isDyeable();
}
public int getColor() {
return this.armorItem.getDye();
}
public ArmorItem getArmorItem() {
return this.armorItem;
}
}

View File

@@ -0,0 +1,79 @@
package io.github.fisher2911.hmccosmetics.api;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import java.util.Optional;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
public class HMCCosmeticsAPI {
private static final HMCCosmetics plugin;
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
/**
* This will attempt to get the {@link io.github.fisher2911.hmccosmetics.api.CosmeticItem} that
* the user is wearing. It returns an empty {@link io.github.fisher2911.hmccosmetics.api.CosmeticItem}
* if the user is not found, or if the user is not wearing a cosmetic
*
* @param uuid the uuid of the user
* @param type the type of cosmetic being retrieved
* @return the current cosmetic of the player
*/
public static CosmeticItem getUserCurrentItem(final UUID uuid, final ArmorItem.Type type) {
final Optional<User> userOptional = plugin.getUserManager().get(uuid);
if (userOptional.isEmpty()) {
return new CosmeticItem(ArmorItem.empty(type));
}
return new CosmeticItem(userOptional.get().getPlayerArmor().getItem(type));
}
/**
* @param uuid the uuid of the user whose cosmetic is being set
* @param cosmeticItem the cosmetic being set
* @return true if the cosmetic was set, or else false
*/
public static boolean setCosmeticItem(final UUID uuid, final CosmeticItem cosmeticItem) {
final UserManager userManager = plugin.getUserManager();
final Optional<User> userOptional = userManager.get(uuid);
if (userOptional.isEmpty()) {
return false;
}
userManager.setItem(userOptional.get(), cosmeticItem.getArmorItem());
return true;
}
/**
* @param id the id of the cosmetic item being retrieved
* @return null if the cosmetic was not found, or a copy of the cosmetic item
*/
@Nullable
public static CosmeticItem getCosmeticFromId(final String id) {
final ArmorItem armorItem = plugin.getCosmeticManager().getArmorItem(id);
if (armorItem == null) {
return null;
}
return new CosmeticItem(new ArmorItem(armorItem));
}
/**
* @param uuid the uuid of the user whose armor stand id is being retrieved
* @return the armor stand id, or -1 if the user is not found
*/
public static int getUserArmorStandId(final UUID uuid) {
final Optional<User> userOptional = plugin.getUserManager().get(uuid);
if (userOptional.isEmpty()) {
return -1;
}
return userOptional.get().getArmorStandId();
}
}

View File

@@ -0,0 +1,33 @@
package io.github.fisher2911.hmccosmetics.api.event;
import io.github.fisher2911.hmccosmetics.api.CosmeticItem;
import io.github.fisher2911.hmccosmetics.user.User;
/**
* Called when a user changes their equipped cosmetic
*/
public class CosmeticChangeEvent extends CosmeticItemEvent {
private final User user;
private CosmeticItem removed;
public CosmeticChangeEvent(final CosmeticItem cosmeticItem, final CosmeticItem removed,
final User user) {
super(cosmeticItem);
this.removed = removed;
this.user = user;
}
public User getUser() {
return user;
}
public CosmeticItem getRemoved() {
return removed;
}
public void setRemoved(final CosmeticItem removed) {
this.removed = removed;
}
}

View File

@@ -0,0 +1,47 @@
package io.github.fisher2911.hmccosmetics.api.event;
import io.github.fisher2911.hmccosmetics.api.CosmeticItem;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public abstract class CosmeticItemEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private CosmeticItem cosmeticItem;
private boolean cancelled;
public CosmeticItemEvent(final CosmeticItem cosmeticItem) {
this.cosmeticItem = cosmeticItem;
this.cancelled = false;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
public CosmeticItem getCosmeticItem() {
return this.cosmeticItem;
}
public void setCosmeticItem(final CosmeticItem cosmeticItem) {
this.cosmeticItem = cosmeticItem;
}
@Override
public boolean isCancelled() {
return this.cancelled;
}
@Override
public void setCancelled(final boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
}

View File

@@ -0,0 +1,233 @@
package io.github.fisher2911.hmccosmetics.command;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.gui.CosmeticsMenu;
import io.github.fisher2911.hmccosmetics.message.Message;
import io.github.fisher2911.hmccosmetics.message.MessageHandler;
import io.github.fisher2911.hmccosmetics.message.Messages;
import io.github.fisher2911.hmccosmetics.message.Placeholder;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import io.github.fisher2911.hmccosmetics.util.StringUtils;
import java.util.Map;
import java.util.Optional;
import me.mattstudios.mf.annotations.Command;
import me.mattstudios.mf.annotations.Completion;
import me.mattstudios.mf.annotations.Default;
import me.mattstudios.mf.annotations.Permission;
import me.mattstudios.mf.annotations.SubCommand;
import me.mattstudios.mf.base.CommandBase;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@Command("cosmetics")
public class CosmeticsCommand extends CommandBase {
private final HMCCosmetics plugin;
private final UserManager userManager;
private final MessageHandler messageHandler;
private final CosmeticsMenu cosmeticsMenu;
public CosmeticsCommand(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
this.messageHandler = this.plugin.getMessageHandler();
this.cosmeticsMenu = this.plugin.getCosmeticsMenu();
}
@Default
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.DEFAULT_COMMAND)
public void defaultCommand(final Player player) {
this.cosmeticsMenu.openDefault(player);
}
@SubCommand("reload")
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.RELOAD_COMMAND)
public void reloadCommand(final CommandSender sender) {
Bukkit.getScheduler().runTaskAsynchronously(
this.plugin,
() -> {
this.plugin.reload();
this.messageHandler.sendMessage(
sender,
Messages.RELOADED
);
}
);
}
@SubCommand("dye")
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.DYE_COMMAND)
public void dyeArmor(final Player player, @Completion("#types") String typeString,
final @me.mattstudios.mf.annotations.Optional String dyeColor) {
final Optional<User> optionalUser = this.userManager.get(player.getUniqueId());
if (optionalUser.isEmpty()) {
return;
}
try {
final ArmorItem.Type type = ArmorItem.Type.valueOf(typeString.toUpperCase());
final User user = optionalUser.get();
if (dyeColor == null) {
this.cosmeticsMenu.openDyeSelectorGui(user, type);
return;
}
final ArmorItem armorItem = user.getPlayerArmor().getItem(type);
if (dyeColor != null) {
this.setDyeColor(dyeColor, armorItem, player);
}
this.userManager.setItem(user, armorItem);
this.messageHandler.sendMessage(
player,
Messages.SET_DYE_COLOR,
Map.of(Placeholder.ITEM, StringUtils.formatArmorItemType(typeString))
);
} catch (final IllegalArgumentException exception) {
this.messageHandler.sendMessage(
player,
Messages.INVALID_TYPE);
}
}
@SubCommand("help") // WORK IN PROGRESS (WIP)
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.HELP_COMMAND)
public void helpCommand(final CommandSender sender) {
this.messageHandler.sendMessage(
sender,
Messages.HELP_COMMAND
);
}
@SubCommand("add")
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.SET_COSMETIC_COMMAND)
public void setCommand(
final CommandSender sender,
@Completion("#players") final Player player,
@Completion("#ids") final String id,
final @me.mattstudios.mf.annotations.Optional String dyeColor) {
final Optional<User> userOptional = this.userManager.get(player.getUniqueId());
if (userOptional.isEmpty()) {
this.messageHandler.sendMessage(
sender,
Messages.INVALID_USER
);
return;
}
final User user = userOptional.get();
final ArmorItem armorItem = this.plugin.getCosmeticManager().getArmorItem(id);
if (armorItem == null) {
this.messageHandler.sendMessage(
sender,
Messages.ITEM_NOT_FOUND
);
return;
}
if (dyeColor != null) {
this.setDyeColor(dyeColor, armorItem, player);
}
final Message setMessage = Messages.getSetMessage(armorItem.getType());
final Message setOtherMessage = Messages.getSetOtherMessage(armorItem.getType());
this.userManager.setItem(user, armorItem);
this.messageHandler.sendMessage(
player,
setMessage
);
this.messageHandler.sendMessage(
sender,
setOtherMessage,
Map.of(Placeholder.PLAYER, player.getName(),
Placeholder.TYPE, id)
);
}
@SubCommand("remove")
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.SET_COSMETIC_COMMAND)
public void removeCommand(final CommandSender sender,
@Completion("#players") final Player player, @Completion("#types") String typeString) {
final Optional<User> userOptional = this.userManager.get(player.getUniqueId());
if (userOptional.isEmpty()) {
this.messageHandler.sendMessage(
sender,
Messages.INVALID_USER
);
return;
}
final User user = userOptional.get();
try {
final ArmorItem.Type type = ArmorItem.Type.valueOf(typeString.toUpperCase());
final Message setOtherMessage = Messages.getSetOtherMessage(type);
this.userManager.removeItem(user, type);
this.messageHandler.sendMessage(
sender,
setOtherMessage,
Map.of(Placeholder.PLAYER, player.getName(),
Placeholder.TYPE, "none")
);
} catch (final IllegalArgumentException exception) {
this.messageHandler.sendMessage(player, Messages.INVALID_TYPE);
}
}
@SubCommand("wardrobe")
@Permission(io.github.fisher2911.hmccosmetics.message.Permission.VIEW_WARDROBE)
public void openWardrobe(final Player player) {
final Optional<User> optionalUser = this.plugin.getUserManager().get(player.getUniqueId());
if (optionalUser.isEmpty()) return;
final User user = optionalUser.get();
final Wardrobe wardrobe = user.getWardrobe();
if (wardrobe.isActive()) {
this.messageHandler.sendMessage(
player,
Messages.WARDROBE_ALREADY_OPEN
);
return;
}
wardrobe.setActive(true);
Bukkit.getScheduler().runTaskAsynchronously(
this.plugin,
() -> wardrobe.spawnFakePlayer(player, this.plugin.getSettings())
);
this.cosmeticsMenu.openDefault(player);
}
private void setDyeColor(final String dyeColor, final ArmorItem armorItem, final CommandSender sender) {
try {
final java.awt.Color awtColor = java.awt.Color.decode(dyeColor);
Color color = Color.fromRGB(awtColor.getRed(), awtColor.getGreen(), awtColor.getBlue());
armorItem.setDye(color.asRGB());
} catch (final NumberFormatException exception) {
this.messageHandler.sendMessage(
sender,
Messages.INVALID_COLOR,
Map.of(Placeholder.ITEM, dyeColor)
);
}
}
}

View File

@@ -0,0 +1,32 @@
package io.github.fisher2911.hmccosmetics.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Threads {
private static final Threads INSTANCE;
static {
INSTANCE = new Threads();
}
private final ExecutorService service;
private Threads() {
this.service = Executors.newFixedThreadPool(1);
}
public static Threads getInstance() {
return INSTANCE;
}
public void execute(final Runnable runnable) {
this.service.execute(runnable);
}
public void onDisable() {
this.service.shutdownNow().forEach(Runnable::run);
}
}

View File

@@ -0,0 +1,121 @@
package io.github.fisher2911.hmccosmetics.config;
import com.comphenix.protocol.wrappers.EnumWrappers;
import dev.triumphteam.gui.components.GuiAction;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.message.Message;
import io.github.fisher2911.hmccosmetics.util.Utils;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.function.Consumer;
public class ActionSerializer implements TypeSerializer<GuiAction<InventoryClickEvent>> {
public static final ActionSerializer INSTANCE = new ActionSerializer();
private static final HMCCosmetics plugin;
private static final String OPEN_MENU = "open-menu";
private static final String SEND_MESSAGE = "send-message";
private static final String SOUND = "sound";
private static final String SOUND_NAME = "name";
private static final String SOUND_VOLUME = "volume";
private static final String SOUND_PITCH = "pitch";
private static final String SOUND_CATEGORY = "category";
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
private ActionSerializer() {
}
private ConfigurationNode nonVirtualNode(final ConfigurationNode source, final Object... path)
throws SerializationException {
if (!source.hasChild(path)) {
throw new SerializationException(
"Required field " + Arrays.toString(path) + " was not present in node");
}
return source.node(path);
}
@Override
public GuiAction<InventoryClickEvent> deserialize(final Type type, final ConfigurationNode source) {
final var children = source.childrenMap();
final List<Consumer<InventoryClickEvent>> consumers = new ArrayList<>();
for (final var entry : children.entrySet()) {
final String clickType = entry.getKey().toString();
if (clickType == null) continue;
consumers.add(this.parseAction(entry.getValue(), clickType.toUpperCase(Locale.ROOT)));
}
return event -> {
for (final Consumer<InventoryClickEvent> consumer : consumers) {
consumer.accept(event);
}
};
}
private Consumer<InventoryClickEvent> parseAction(final ConfigurationNode node, final String clickType) {
final ConfigurationNode openMenuNode = node.node(OPEN_MENU);
final ConfigurationNode sendMessageNode = node.node(SEND_MESSAGE);
final ConfigurationNode soundNode = node.node(SOUND);
final ConfigurationNode soundNameNode = soundNode.node(SOUND_NAME);
final ConfigurationNode volumeNode = soundNode.node(SOUND_VOLUME);
final ConfigurationNode pitchNode = soundNode.node(SOUND_PITCH);
final ConfigurationNode categoryNode = soundNode.node(SOUND_CATEGORY);
final String openMenu = openMenuNode.getString();
final String sendMessage = sendMessageNode.getString();
final SoundData soundData;
final String soundName = soundNameNode.getString();
final String category = categoryNode.getString();
final int volume = volumeNode.getInt();
final int pitch = pitchNode.getInt();
if (soundName == null || category == null) {
soundData = null;
} else {
soundData = new SoundData(
soundName,
EnumWrappers.SoundCategory.valueOf(category),
volume,
pitch
);
}
final ClickType click = Utils.stringToEnum(clickType, ClickType.class, ClickType.UNKNOWN);
return event -> {
if (click != ClickType.UNKNOWN && event.getClick() != click) return;
if (!(event.getWhoClicked() instanceof final Player player)) return;
if (soundData != null) {
soundData.play(player);
}
if (sendMessage != null) plugin.getMessageHandler().sendMessage(
player,
new Message("", sendMessage)
);
if (openMenu != null) plugin.getCosmeticsMenu().openMenu(openMenu, event.getWhoClicked());
};
}
@Override
public void serialize(final Type type, @Nullable final GuiAction<InventoryClickEvent> obj, final ConfigurationNode node) throws SerializationException {
}
}

View File

@@ -0,0 +1,52 @@
package io.github.fisher2911.hmccosmetics.config;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.inventory.EquipmentSlot;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public class CosmeticSettings {
private static final transient String COSMETIC_SETTINGS_PATH = "cosmetic-settings";
private static final transient String REQUIRE_EMPTY_HELMET_PATH = "require-empty-helmet";
private static final transient String REQUIRE_EMPTY_OFF_HAND_PATH = "require-empty-off-hand";
private static final transient String LOOK_DOWN_PITCH_PATH = "look-down-backpack-remove";
private boolean requireEmptyHelmet;
private boolean requireEmptyOffHand;
private int lookDownPitch;
public void load(final FileConfiguration config) {
this.requireEmptyHelmet = config.getBoolean(COSMETIC_SETTINGS_PATH + "." + REQUIRE_EMPTY_HELMET_PATH);
this.requireEmptyOffHand = config.getBoolean(COSMETIC_SETTINGS_PATH + "." + REQUIRE_EMPTY_OFF_HAND_PATH);
this.lookDownPitch = config.getInt(COSMETIC_SETTINGS_PATH + "." + LOOK_DOWN_PITCH_PATH);
}
public boolean isRequireEmptyHelmet() {
return requireEmptyHelmet;
}
public boolean isRequireEmptyOffHand() {
return requireEmptyOffHand;
}
public int getLookDownPitch() {
return lookDownPitch;
}
public void setRequireEmptyHelmet(final boolean requireEmptyHelmet) {
this.requireEmptyHelmet = requireEmptyHelmet;
}
public void setRequireEmptyOffHand(final boolean requireEmptyOffHand) {
this.requireEmptyOffHand = requireEmptyOffHand;
}
public boolean requireEmpty(final EquipmentSlot slot) {
return switch (slot) {
case OFF_HAND -> this.isRequireEmptyOffHand();
case HEAD -> this.isRequireEmptyHelmet();
default -> false;
};
}
}

View File

@@ -0,0 +1,132 @@
package io.github.fisher2911.hmccosmetics.config;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.gui.ColorItem;
import io.github.fisher2911.hmccosmetics.gui.DyeSelectorGui;
import io.github.fisher2911.hmccosmetics.message.Adventure;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Color;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
public class DyeGuiSerializer implements TypeSerializer<DyeSelectorGui> {
public static final DyeGuiSerializer INSTANCE = new DyeGuiSerializer();
private static final HMCCosmetics plugin;
private static final String TITLE = "title";
private static final String ROWS = "rows";
private static final String ITEMS = "items";
private static final String COSMETICS_SLOTS = "cosmetics-slots";
private static final String SET_COLOR = "set-color";
private static final String RED = "red";
private static final String GREEN = "green";
private static final String BLUE = "blue";
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
private DyeGuiSerializer() {
}
private ConfigurationNode nonVirtualNode(final ConfigurationNode source, final Object... path)
throws SerializationException {
if (!source.hasChild(path)) {
throw new SerializationException(
"Required field " + Arrays.toString(path) + " was not present in node");
}
return source.node(path);
}
@Override
public DyeSelectorGui deserialize(final Type type, final ConfigurationNode source)
throws SerializationException {
final ConfigurationNode titleNode = this.nonVirtualNode(source, TITLE);
final ConfigurationNode rowsNode = this.nonVirtualNode(source, ROWS);
final ConfigurationNode itemsNode = source.node(ITEMS);
final ConfigurationNode cosmeticSlotsNode = source.node(COSMETICS_SLOTS);
final Map<Integer, GuiItem> guiItemMap = new HashMap<>();
final var itemMap = itemsNode.childrenMap();
final var cosmeticSlotsMap = cosmeticSlotsNode.childrenMap();
for (final var entry : itemMap.entrySet()) {
if (!(entry.getKey() instanceof final Integer slot)) {
continue;
}
final var node = entry.getValue();
final GuiItem guiItem = ItemSerializer.INSTANCE.deserialize(
GuiItem.class,
node
);
final ConfigurationNode colorNode = node.node(SET_COLOR);
if (colorNode.virtual()) {
guiItemMap.put(slot, guiItem);
continue;
}
final int red = colorNode.node(RED).getInt();
final int green = colorNode.node(GREEN).getInt();
final int blue = colorNode.node(BLUE).getInt();
guiItemMap.put(slot,
new ColorItem(guiItem.getItemStack(), Color.fromRGB(red, green, blue)));
}
final BiMap<Integer, ArmorItem.Type> cosmeticSlots = HashBiMap.create();
int selectedCosmetic = -1;
for (final var entry : cosmeticSlotsMap.entrySet()) {
if (!(entry.getKey() instanceof final Integer slot)) {
continue;
}
selectedCosmetic = selectedCosmetic == -1 ? slot : selectedCosmetic;
final var node = entry.getValue();
final String typeStr = node.getString();
try {
final ArmorItem.Type itemType = ArmorItem.Type.valueOf(typeStr);
cosmeticSlots.put(slot, itemType);
} catch (final IllegalArgumentException | NullPointerException exception) {
plugin.getLogger().severe(typeStr + " is not a valid ArmorItem type in DyeGui!");
}
}
String title = titleNode.getString();
if (title == null) {
title = "";
}
return new DyeSelectorGui(
plugin,
Adventure.SERIALIZER.serialize(
Adventure.MINI_MESSAGE.deserialize(title)),
rowsNode.getInt(),
guiItemMap,
cosmeticSlots,
selectedCosmetic);
}
@Override
public void serialize(final Type type, @Nullable final DyeSelectorGui obj, final ConfigurationNode node) throws SerializationException {
}
}

View File

@@ -0,0 +1,82 @@
package io.github.fisher2911.hmccosmetics.config;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.CosmeticGui;
import io.github.fisher2911.hmccosmetics.message.Adventure;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
public class GuiSerializer implements TypeSerializer<CosmeticGui> {
public static final GuiSerializer INSTANCE = new GuiSerializer();
private static final HMCCosmetics plugin;
private static final String TITLE = "title";
private static final String ROWS = "rows";
private static final String ITEMS = "items";
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
private GuiSerializer() {
}
private ConfigurationNode nonVirtualNode(final ConfigurationNode source, final Object... path)
throws SerializationException {
if (!source.hasChild(path)) {
throw new SerializationException(
"Required field " + Arrays.toString(path) + " was not present in node");
}
return source.node(path);
}
@Override
public CosmeticGui deserialize(final Type type, final ConfigurationNode source)
throws SerializationException {
final ConfigurationNode titleNode = this.nonVirtualNode(source, TITLE);
final ConfigurationNode rowsNode = this.nonVirtualNode(source, ROWS);
final ConfigurationNode itemsNode = source.node(ITEMS);
final var childrenMap = itemsNode.childrenMap();
final Map<Integer, GuiItem> guiItemMap = new HashMap<>();
for (final var entry : childrenMap.entrySet()) {
if (!(entry.getKey() instanceof final Integer slot)) {
continue;
}
final GuiItem guiItem = ItemSerializer.INSTANCE.deserialize(
GuiItem.class,
entry.getValue()
);
guiItemMap.put(slot, guiItem);
}
String title = titleNode.getString();
if (title == null) {
title = "";
}
return new CosmeticGui(plugin,
Adventure.SERIALIZER.serialize(
Adventure.MINI_MESSAGE.deserialize(title)),
rowsNode.getInt(),
guiItemMap);
}
@Override
public void serialize(final Type type, @Nullable final CosmeticGui obj, final ConfigurationNode node) throws SerializationException {
}
}

View File

@@ -0,0 +1,253 @@
package io.github.fisher2911.hmccosmetics.config;
import dev.triumphteam.gui.components.GuiAction;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.hook.HookManager;
import io.github.fisher2911.hmccosmetics.util.Keys;
import io.github.fisher2911.hmccosmetics.util.StringUtils;
import io.github.fisher2911.hmccosmetics.util.Utils;
import io.github.fisher2911.hmccosmetics.util.builder.ColorBuilder;
import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder;
import io.github.fisher2911.hmccosmetics.util.builder.SkullBuilder;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.OfflinePlayer;
import org.bukkit.Registry;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer;
public class ItemSerializer implements TypeSerializer<GuiItem> {
public static final ItemSerializer INSTANCE = new ItemSerializer();
private static final HMCCosmetics plugin;
private static final String MATERIAL = "material";
private static final String AMOUNT = "amount";
private static final String NAME = "name";
private static final String UNBREAKABLE = "unbreakable";
private static final String GLOWING = "glowing";
private static final String LORE = "lore";
private static final String LOCKED_LORE = "locked-lore";
private static final String MODEL_DATA = "model-data";
private static final String ENCHANTS = "enchants";
private static final String ITEM_FLAGS = "item-flags";
private static final String TEXTURE = "texture";
private static final String OWNER = "owner";
private static final String COLOR = "color";
private static final String RED = "red";
private static final String GREEN = "green";
private static final String BLUE = "blue";
private static final String PERMISSION = "permission";
private static final String TYPE = "type";
private static final String ACTION = "action";
private static final String ID = "id";
private static final String DYEABLE = "dyeable";
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
private ItemSerializer() {
}
private ConfigurationNode nonVirtualNode(final ConfigurationNode source, final Object... path)
throws SerializationException {
if (!source.hasChild(path)) {
throw new SerializationException(
"Required field " + Arrays.toString(path) + " was not present in node");
}
return source.node(path);
}
@Override
public GuiItem deserialize(final Type type, final ConfigurationNode source)
throws SerializationException {
final ConfigurationNode materialNode = this.nonVirtualNode(source, MATERIAL);
final ConfigurationNode amountNode = source.node(AMOUNT);
final ConfigurationNode nameNode = source.node(NAME);
final ConfigurationNode unbreakableNode = source.node(UNBREAKABLE);
final ConfigurationNode glowingNode = source.node(GLOWING);
final ConfigurationNode loreNode = source.node(LORE);
final ConfigurationNode lockedLoreNode = source.node(LOCKED_LORE);
final ConfigurationNode modelDataNode = source.node(MODEL_DATA);
final ConfigurationNode enchantsNode = source.node(ENCHANTS);
final ConfigurationNode itemFlagsNode = source.node(ITEM_FLAGS);
final ConfigurationNode textureNode = source.node(TEXTURE);
final ConfigurationNode ownerNode = source.node(OWNER);
final ConfigurationNode colorNode = source.node(COLOR);
final ConfigurationNode redNode = colorNode.node(RED);
final ConfigurationNode greenNode = colorNode.node(GREEN);
final ConfigurationNode blueNode = colorNode.node(BLUE);
final ConfigurationNode permissionNode = source.node(PERMISSION);
final ConfigurationNode typeNode = source.node(TYPE);
final ConfigurationNode actionNode = source.node(ACTION);
final ConfigurationNode idNode = source.node(ID);
final ConfigurationNode dyeableNode = source.node(DYEABLE);
final String materialString = Utils.replaceIfNull(materialNode.getString(), "");
final int amount = amountNode.getInt();
ItemStack itemStack;
try {
itemStack = new ItemStack(Material.valueOf(materialString), amount);
} catch (final IllegalArgumentException exception) {
itemStack = HookManager.getInstance().getItemHooks().getItemStack(materialString);
if (itemStack == null) {
itemStack = new ItemStack(Material.AIR);
}
}
final String name = StringUtils.parseStringToString(nameNode.getString());
final boolean unbreakable = unbreakableNode.getBoolean();
final boolean glowing = glowingNode.getBoolean();
final List<String> lore = Utils.replaceIfNull(loreNode.getList(String.class),
new ArrayList<String>()).
stream().map(StringUtils::parseStringToString).collect(Collectors.toList());
final List<String> lockedLore = Utils.replaceIfNull(lockedLoreNode.getList(String.class),
new ArrayList<String>()).
stream().map(StringUtils::parseStringToString).collect(Collectors.toList());
final int modelData = modelDataNode.getInt();
final Set<ItemFlag> itemFlags = Utils.replaceIfNull(itemFlagsNode.getList(String.class),
new ArrayList<String>()).
stream().map(flag -> {
try {
return ItemFlag.valueOf(flag.toUpperCase());
} catch (final Exception ignored) {
return null;
}
}).collect(Collectors.toSet());
final String texture = textureNode.getString();
final String owner = ownerNode.getString();
final boolean dyeable = dyeableNode.getBoolean();
final Color color;
if (colorNode.virtual()) {
color = null;
} else {
color = Color.fromRGB(redNode.getInt(), greenNode.getInt(), blueNode.getInt());
}
final Map<Enchantment, Integer> enchantments =
Utils.replaceIfNull(enchantsNode.getList(String.class),
new ArrayList<String>()).
stream().
collect(Collectors.toMap(enchantmentString -> {
if (!enchantmentString.contains(":")) {
return null;
}
final NamespacedKey namespacedKey = NamespacedKey.minecraft(
enchantmentString.
split(":")[0].
toLowerCase());
return Registry.ENCHANTMENT.get(namespacedKey);
}, enchantmentString -> {
if (!enchantmentString.contains(":")) {
return 0;
}
try {
return Integer.parseInt(enchantmentString.split(":")[1]);
} catch (final NumberFormatException exception) {
return 0;
}
}));
final ItemBuilder itemBuilder;
if (itemStack.getType() == Material.PLAYER_HEAD) {
itemBuilder = SkullBuilder.create();
if (texture != null) {
((SkullBuilder) itemBuilder).texture(texture);
} else if (owner != null) {
final OfflinePlayer player = Bukkit.getOfflinePlayer(owner);
((SkullBuilder) itemBuilder).owner(player);
}
} else if (ColorBuilder.canBeColored(itemStack)) {
itemBuilder = ColorBuilder.from(itemStack);
if (color != null) {
((ColorBuilder) itemBuilder).color(color);
}
} else {
itemBuilder = ItemBuilder.from(itemStack);
}
if (itemStack.getItemMeta() != null && !itemStack.getItemMeta().hasCustomModelData()) {
itemBuilder.modelData(modelData);
}
itemStack = itemBuilder.
amount(amount).
name(name).
unbreakable(unbreakable).
glow(glowing).
lore(lore).
enchants(enchantments, true).
itemFlags(itemFlags).
build();
final GuiAction<InventoryClickEvent> action = ActionSerializer.INSTANCE.deserialize(GuiAction.class, actionNode);
Keys.setKey(itemStack);
try {
final ArmorItem.Type cosmeticType = ArmorItem.Type.valueOf(
Utils.replaceIfNull(
typeNode.getString(), ""
).toUpperCase(Locale.ROOT)
);
final String permission = permissionNode.getString();
return new ArmorItem(
itemStack,
action,
Utils.replaceIfNull(idNode.getString(), ""),
lockedLore,
permission,
cosmeticType,
dyeable,
-1
);
} catch (final IllegalArgumentException exception) {
final GuiItem guiItem = dev.triumphteam.gui.builder.item.ItemBuilder.from(itemStack).asGuiItem();
guiItem.setAction(action);
return guiItem;
}
}
@Override
public void serialize(final Type type, @Nullable final GuiItem obj, final ConfigurationNode node) throws SerializationException {
}
}

View File

@@ -0,0 +1,25 @@
package io.github.fisher2911.hmccosmetics.config;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
public class Settings {
private final HMCCosmetics plugin;
private final CosmeticSettings settings;
public Settings(final HMCCosmetics plugin) {
this.plugin = plugin;
this.settings = new CosmeticSettings();
}
public void load() {
this.plugin.saveDefaultConfig();
this.plugin.reloadConfig();
this.settings.load(this.plugin.getConfig());
}
public CosmeticSettings getCosmeticSettings() {
return settings;
}
}

View File

@@ -0,0 +1,61 @@
package io.github.fisher2911.hmccosmetics.config;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.MinecraftKey;
import io.github.fisher2911.hmccosmetics.packet.PacketManager;
import org.bukkit.entity.Player;
public class SoundData {
private final String name;
private final EnumWrappers.SoundCategory soundCategory;
private final float volume;
private final float pitch;
public SoundData(final String name, final EnumWrappers.SoundCategory soundCategory, final float volume, final float pitch) {
this.name = name;
this.soundCategory = soundCategory;
this.volume = volume;
this.pitch = pitch;
}
public String getName() {
return name;
}
public float getVolume() {
return volume;
}
public float getPitch() {
return pitch;
}
public EnumWrappers.SoundCategory getSoundCategory() {
return soundCategory;
}
public void play(final Player player) {
final PacketContainer soundPacket = PacketManager.getSoundPacket(
player,
player.getLocation(),
this.getKey(this.name),
this.volume,
this.pitch,
this.soundCategory
);
PacketManager.sendPacket(player, soundPacket);
}
private MinecraftKey getKey(final String string) {
if (!string.contains(":")) {
return new MinecraftKey(string);
}
final String[] parts = string.split(":");
return new MinecraftKey(parts[0], parts[1]);
}
}

View File

@@ -0,0 +1,33 @@
package io.github.fisher2911.hmccosmetics.cosmetic;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import java.util.Collection;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
public class CosmeticManager {
private final Map<String, ArmorItem> armorItemMap;
public CosmeticManager(final Map<String, ArmorItem> armorItemMap) {
this.armorItemMap = armorItemMap;
}
@Nullable
public ArmorItem getArmorItem(final String id) {
return this.armorItemMap.get(id);
}
public void addArmorItem(final ArmorItem armorItem) {
this.armorItemMap.put(armorItem.getId(), armorItem);
}
public Collection<ArmorItem> getAll() {
return this.armorItemMap.values();
}
public Map<String, ArmorItem> getArmorItemMap() {
return armorItemMap;
}
}

View File

@@ -0,0 +1,169 @@
package io.github.fisher2911.hmccosmetics.database;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.DaoManager;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.concurrent.Threads;
import io.github.fisher2911.hmccosmetics.database.dao.ArmorItemDAO;
import io.github.fisher2911.hmccosmetics.database.dao.UserDAO;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.user.User;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import org.bukkit.Bukkit;
public class Database {
protected final HMCCosmetics plugin;
final Dao<UserDAO, UUID> userDao;
final Dao<ArmorItemDAO, UUID> armorItemDao;
private final ConnectionSource dataSource;
private final DatabaseType databaseType;
AtomicInteger FAKE_ENTITY_ID = new AtomicInteger(Integer.MAX_VALUE);
String TABLE_NAME = "user";
String PLAYER_UUID_COLUMN = "uuid";
String BACKPACK_COLUMN = "backpack";
String HAT_COLUMN = "hat";
String DYE_COLOR_COLUMN = "dye";
String CREATE_TABLE_STATEMENT =
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
PLAYER_UUID_COLUMN + " CHAR(36), " +
BACKPACK_COLUMN + " CHAR(50), " +
HAT_COLUMN + " CHAR(50), " +
DYE_COLOR_COLUMN + " INT, " +
"UNIQUE (" +
PLAYER_UUID_COLUMN +
"))";
public Database(
final HMCCosmetics plugin,
final ConnectionSource dataSource,
final DatabaseType databaseType) throws SQLException {
this.plugin = plugin;
this.dataSource = dataSource;
this.userDao = DaoManager.createDao(this.dataSource, UserDAO.class);
this.armorItemDao = DaoManager.createDao(this.dataSource, ArmorItemDAO.class);
this.databaseType = databaseType;
}
public void load() {
Threads.getInstance().execute(() -> new DatabaseConverter(this.plugin, this).convert());
}
protected void createTables() {
try {
TableUtils.createTableIfNotExists(this.dataSource, ArmorItemDAO.class);
TableUtils.createTableIfNotExists(this.dataSource, UserDAO.class);
} catch (final SQLException exception) {
exception.printStackTrace();
}
}
public void loadUser(final UUID uuid, final Consumer<User> onComplete) {
final int armorStandId = FAKE_ENTITY_ID.getAndDecrement();
final Wardrobe wardrobe = this.createNewWardrobe(uuid);
Threads.getInstance().execute(
() -> {
try {
UserDAO user = this.userDao.queryForId(uuid);
if (user == null) {
user = this.userDao.createIfNotExists(new UserDAO(uuid));
}
final List<ArmorItemDAO> armorItems = this.armorItemDao.queryForEq("uuid",
uuid.toString());
final User actualUser = user.toUser(
this.plugin.getCosmeticManager(),
armorItems,
wardrobe,
armorStandId);
Bukkit.getScheduler().runTask(this.plugin,
() -> {
this.plugin.getUserManager().add(
actualUser
);
onComplete.accept(actualUser);
}
);
} catch (final SQLException exception) {
exception.printStackTrace();
}
});
final User user = new User(uuid, PlayerArmor.empty(), wardrobe, armorStandId);
this.plugin.getUserManager().add(user);
onComplete.accept(user);
}
public void saveUser(final User user) {
try {
final UserDAO userDAO = new UserDAO(user.getUuid());
this.userDao.createOrUpdate(userDAO);
final String uuid = user.getUuid().toString();
for (final ArmorItem armorItem : user.getPlayerArmor().getArmorItems()) {
final ArmorItemDAO dao = ArmorItemDAO.fromArmorItem(armorItem);
dao.setUuid(uuid);
this.armorItemDao.createOrUpdate(dao);
}
} catch (final SQLException exception) {
exception.printStackTrace();
}
}
public void saveAll() {
for (final User user : this.plugin.getUserManager().getAll()) {
this.saveUser(user);
}
}
public void close() {
try {
this.dataSource.close();
} catch (final Exception exception) {
exception.printStackTrace();
}
}
protected ConnectionSource getDataSource() {
return dataSource;
}
public DatabaseType getDatabaseType() {
return databaseType;
}
public Dao<UserDAO, UUID> getUserDao() {
return userDao;
}
public Dao<ArmorItemDAO, UUID> getArmorItemDao() {
return armorItemDao;
}
public Wardrobe createNewWardrobe(final UUID ownerUUID) {
return new Wardrobe(
UUID.randomUUID(),
ownerUUID,
PlayerArmor.empty(),
FAKE_ENTITY_ID.getAndDecrement(),
FAKE_ENTITY_ID.getAndDecrement(),
false
);
}
}

View File

@@ -0,0 +1,129 @@
package io.github.fisher2911.hmccosmetics.database;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.user.User;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.bukkit.configuration.file.YamlConfiguration;
public class DatabaseConverter {
private static final int CURRENT_VERSION = 2;
private static final String FILE_NAME = "info.yml";
private final HMCCosmetics plugin;
private final Database database;
public DatabaseConverter(final HMCCosmetics plugin, final Database database) {
this.database = database;
this.plugin = plugin;
}
public void convert() {
final File folder = new File(this.plugin.getDataFolder(), "database");
final File file = Path.of(
folder.getPath(),
FILE_NAME
).toFile();
final boolean fileExists = file.exists();
if (!file.exists()) {
this.plugin.saveResource("database" + File.separator + FILE_NAME, true);
}
final YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
final int version = fileExists ? config.getInt("version") : 1;
final Set<User> users = new HashSet<>();
this.convert(version, users::add);
try {
config.set("version", CURRENT_VERSION);
config.save(file);
} catch (final IOException exception) {
exception.printStackTrace();
}
this.database.createTables();
for (final User user : users) {
database.saveUser(user);
}
}
private void convert(final int version, final Consumer<User> consumer) {
switch (version) {
case 1 -> this.convertVersionOne(consumer);
}
}
private void convertVersionOne(final Consumer<User> consumer) {
final String query = "SELECT * from user";
try (final PreparedStatement statement = this.database.getDataSource()
.getReadOnlyConnection("user").
getUnderlyingConnection().prepareStatement(query)) {
final ResultSet results = statement.executeQuery();
try {
final Map<String, ArmorItem> armorItems = new ConcurrentHashMap<>(
this.plugin.getCosmeticManager().getArmorItemMap());
while (results.next()) {
final PlayerArmor playerArmor = PlayerArmor.empty();
final UUID uuid = UUID.fromString(results.getString(1));
final User user = new User(
uuid,
playerArmor,
this.database.createNewWardrobe(uuid),
this.database.FAKE_ENTITY_ID.getAndDecrement()
);
final String backpackId = results.getString(2);
final String hatId = results.getString(3);
final int hatDye = results.getInt(4);
final ArmorItem backpack = armorItems.get(backpackId);
final ArmorItem hat = armorItems.get(hatId);
if (backpack != null) {
playerArmor.setItem(backpack);
}
if (hat != null) {
hat.setDye(hatDye);
playerArmor.setItem(hat);
}
consumer.accept(user);
}
} catch (final SQLException exception) {
exception.printStackTrace();
}
} catch (final SQLException exception) {
exception.printStackTrace();
}
try (final PreparedStatement dropStatement = this.database.getDataSource()
.getReadWriteConnection("user").
getUnderlyingConnection().prepareStatement("DROP TABLE user")) {
dropStatement.executeUpdate();
} catch (final SQLException exception) {
exception.printStackTrace();
}
}
}

View File

@@ -0,0 +1,91 @@
package io.github.fisher2911.hmccosmetics.database;
import com.j256.ormlite.jdbc.DataSourceConnectionSource;
import com.j256.ormlite.jdbc.JdbcPooledConnectionSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
public class DatabaseFactory {
private static final Path FILE_PATH = HMCCosmetics.PLUGIN_FOLDER.resolve("database.yml");
private static final String TYPE_PATH = "type";
private static final String NAME_PATH = "name";
private static final String USERNAME_PATH = "username";
private static final String PASSWORD_PATH = "password";
private static final String IP_PATH = "ip";
private static final String PORT_PATH = "port";
public static Database create(final HMCCosmetics plugin) throws SQLException {
if (!Files.exists(FILE_PATH)) {
plugin.saveResource(FILE_PATH.getFileName().toString(), false);
}
final FileConfiguration config = YamlConfiguration.loadConfiguration(FILE_PATH.toFile());
final String type = config.getString(TYPE_PATH);
final Logger logger = plugin.getLogger();
if (type == null) {
logger.severe("Database type was null, disabling plugin.");
Bukkit.getPluginManager().disablePlugin(plugin);
throw new NullPointerException();
}
final Database database = switch (type.toLowerCase()) {
case "mysql" -> {
final String name = config.getString(NAME_PATH);
final String username = config.getString(USERNAME_PATH);
final String password = config.getString(PASSWORD_PATH);
final String ip = config.getString(IP_PATH);
final String port = config.getString(PORT_PATH);
final HikariConfig hikari = new HikariConfig();
final String jdbcUrl = "jdbc:mysql://" + ip + ":" + port + "/" + name;
hikari.setJdbcUrl(jdbcUrl);
hikari.setUsername(username);
hikari.setPassword(password);
hikari.setConnectionTimeout(1000000000);
final HikariDataSource source = new HikariDataSource(hikari);
yield new Database(
plugin,
new DataSourceConnectionSource(source, jdbcUrl),
DatabaseType.MYSQL
);
}
case "sqlite" -> {
final File folder = new File(plugin.getDataFolder().getPath(), "database");
folder.mkdirs();
yield new Database(plugin, new JdbcPooledConnectionSource("jdbc:sqlite:" + new File(
folder.getPath(),
"users.db"
).getPath()),
DatabaseType.SQLITE
);
}
default -> null;
};
if (database == null) {
logger.severe(
"Error loading database, type " + type + " is invalid! Disabling plugin.");
Bukkit.getPluginManager().disablePlugin(plugin);
}
return database;
}
}

View File

@@ -0,0 +1,8 @@
package io.github.fisher2911.hmccosmetics.database;
public enum DatabaseType {
MYSQL,
SQLITE
}

View File

@@ -0,0 +1,123 @@
package io.github.fisher2911.hmccosmetics.database.dao;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import io.github.fisher2911.hmccosmetics.cosmetic.CosmeticManager;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import java.util.Objects;
import org.jetbrains.annotations.Nullable;
@DatabaseTable(tableName = "armor_items")
public class ArmorItemDAO {
@DatabaseField(columnName = "uuid", useGetSet = true, uniqueCombo = true)
private String uuid;
@DatabaseField
private String id;
@DatabaseField(id = true, useGetSet = true, columnName = "artificial_id")
private String artificialId;
@DatabaseField(uniqueCombo = true)
private String type;
@DatabaseField(columnName = "color")
private int rgbDye;
public ArmorItemDAO(final String id, final String type, final int rgbDye) {
this.id = id;
this.artificialId = this.getArtificialId();
this.type = type;
this.rgbDye = rgbDye;
}
public ArmorItemDAO() {
}
public static ArmorItemDAO fromArmorItem(final ArmorItem armorItem) {
return new ArmorItemDAO(armorItem.getId(), armorItem.getType().toString(),
armorItem.getDye());
}
@Nullable
public ArmorItem toArmorItem(final CosmeticManager cosmeticManager) {
final ArmorItem armorItem = cosmeticManager.getArmorItem(this.id);
if (armorItem == null) {
return null;
}
final ArmorItem copy = new ArmorItem(armorItem);
copy.setDye(this.rgbDye);
return copy;
}
public String getUuid() {
return uuid;
}
public void setUuid(final String uuid) {
this.uuid = uuid;
}
/**
* ORMLite does not allow more than one primary key (WHYYYY???????????)
*
* @return
*/
public String getArtificialId() {
return this.uuid + "-" + this.type;
}
public void setArtificialId(final String artificialId) {
this.artificialId = artificialId;
}
public void setId(final String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(final String type) {
this.type = type;
}
public int getRgbDye() {
return rgbDye;
}
public void setRgbDye(final int rgbDye) {
this.rgbDye = rgbDye;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final ArmorItemDAO that = (ArmorItemDAO) o;
return Objects.equals(getUuid(), that.getUuid()) && Objects.equals(getType(),
that.getType());
}
@Override
public int hashCode() {
return Objects.hash(getUuid(), getType());
}
@Override
public String toString() {
return "ArmorItemDAO{" +
"uuid='" + uuid + '\'' +
", id='" + id + '\'' +
", type='" + type + '\'' +
", rgbDye=" + rgbDye +
'}';
}
}

View File

@@ -0,0 +1,74 @@
package io.github.fisher2911.hmccosmetics.database.dao;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import io.github.fisher2911.hmccosmetics.cosmetic.CosmeticManager;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
@DatabaseTable(tableName = "user")
public class UserDAO {
@DatabaseField(id = true)
private UUID uuid;
public UserDAO() {
}
public UserDAO(final UUID uuid) {
this.uuid = uuid;
}
public void setUuid(final UUID uuid) {
this.uuid = uuid;
}
public User toUser(
final CosmeticManager cosmeticManager,
final List<ArmorItemDAO> armorItems,
final Wardrobe wardrobe,
final int armorStandId) {
final PlayerArmor playerArmor = PlayerArmor.empty();
for (final ArmorItemDAO armorItemDao : armorItems) {
final ArmorItem armorItem = armorItemDao.toArmorItem(cosmeticManager);
if (armorItem == null) {
continue;
}
playerArmor.setItem(armorItem);
}
return new User(this.uuid, playerArmor, wardrobe, armorStandId);
}
@Override
public String toString() {
return "UserDAO{" +
"uuid=" + uuid +
'}';
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final UserDAO userDAO = (UserDAO) o;
return Objects.equals(uuid, userDAO.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
}

View File

@@ -0,0 +1,273 @@
package io.github.fisher2911.hmccosmetics.gui;
import dev.triumphteam.gui.components.GuiAction;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.util.builder.ColorBuilder;
import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ArmorItem extends GuiItem {
private final String id;
private final List<String> lockedLore;
private final String permission;
private final Type type;
private GuiAction<InventoryClickEvent> action;
private boolean dyeable;
private int dye;
public ArmorItem(
@NotNull final ItemStack itemStack,
final GuiAction<InventoryClickEvent> action,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final int dye) {
super(itemStack, action);
this.id = id;
this.lockedLore = lockedLore;
this.action = action;
this.permission = permission;
this.type = type;
this.dye = dye;
}
public ArmorItem(
@NotNull final ItemStack itemStack,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final int dye) {
super(itemStack);
this.id = id;
this.lockedLore = lockedLore;
this.action = null;
this.permission = permission;
this.type = type;
this.dye = dye;
}
public ArmorItem(
@NotNull final Material material,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final int dye) {
super(material);
this.id = id;
this.lockedLore = lockedLore;
this.action = null;
this.permission = permission;
this.type = type;
this.dye = dye;
}
public ArmorItem(
@NotNull final Material material,
@Nullable final GuiAction<InventoryClickEvent> action,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final int dye) {
super(material, action);
this.id = id;
this.lockedLore = lockedLore;
this.action = action;
this.permission = permission;
this.type = type;
this.dye = dye;
}
public ArmorItem(
@NotNull final ItemStack itemStack,
final GuiAction<InventoryClickEvent> action,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final boolean dyeable,
final int dye) {
super(itemStack, action);
this.id = id;
this.lockedLore = lockedLore;
this.action = action;
this.permission = permission;
this.type = type;
this.dyeable = dyeable;
this.dye = dye;
}
public ArmorItem(
@NotNull final ItemStack itemStack,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final boolean dyeable,
final int dye) {
super(itemStack);
this.id = id;
this.lockedLore = lockedLore;
this.action = null;
this.permission = permission;
this.type = type;
this.dyeable = dyeable;
this.dye = dye;
}
public ArmorItem(
@NotNull final Material material,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final boolean dyeable,
final int dye) {
super(material);
this.id = id;
this.lockedLore = lockedLore;
this.action = null;
this.permission = permission;
this.type = type;
this.dyeable = dyeable;
this.dye = dye;
}
public ArmorItem(
@NotNull final Material material,
@Nullable final GuiAction<InventoryClickEvent> action,
final String id,
final List<String> lockedLore,
final String permission,
final Type type,
final boolean dyeable,
final int dye) {
super(material, action);
this.id = id;
this.lockedLore = lockedLore;
this.action = action;
this.permission = permission;
this.type = type;
this.dyeable = dyeable;
this.dye = dye;
}
public ArmorItem(final ArmorItem armorItem) {
super(armorItem.getItemStack(), armorItem.getAction());
this.id = armorItem.getId();
this.lockedLore = new ArrayList<>();
Collections.copy(armorItem.getLockedLore(), this.lockedLore);
this.action = armorItem.getAction();
this.permission = armorItem.getPermission();
this.type = armorItem.getType();
this.dyeable = armorItem.isDyeable();
this.dye = armorItem.getDye();
}
public static ArmorItem empty(final Type type) {
return new ArmorItem(
new ItemStack(Material.AIR),
"",
new ArrayList<>(),
"",
type,
-1
);
}
public String getId() {
return id;
}
public List<String> getLockedLore() {
return lockedLore;
}
public GuiAction<InventoryClickEvent> getAction() {
return this.action;
}
@Override
public void setAction(final GuiAction<InventoryClickEvent> action) {
super.setAction(action);
this.action = action;
}
public String getPermission() {
return permission;
}
public Type getType() {
return type;
}
public boolean isDyeable() {
return dyeable;
}
public int getDye() {
return dye;
}
public void setDye(final int dye) {
this.dye = dye;
}
public ItemStack getColored() {
return this.color(super.getItemStack());
}
public ItemStack getItemStack(final boolean allowed) {
final ItemStack itemStack;
if (allowed) {
itemStack = super.getItemStack();
} else {
itemStack = ItemBuilder.from(this.getItemStack()).
lore(this.lockedLore).
build();
}
return this.color(itemStack);
}
private ItemStack color(final ItemStack itemStack) {
if (this.dye == -1 || !ColorBuilder.canBeColored(itemStack)) {
return itemStack;
}
return ColorBuilder.from(itemStack).
color(Color.fromRGB(this.dye)).
build();
}
public boolean isEmpty() {
return this.getItemStack().getType() == Material.AIR;
}
public ArmorItem copy() {
return new ArmorItem(this);
}
public enum Type {
HAT,
BACKPACK,
OFF_HAND
}
}

View File

@@ -0,0 +1,43 @@
package io.github.fisher2911.hmccosmetics.gui;
import dev.triumphteam.gui.components.GuiAction;
import dev.triumphteam.gui.guis.GuiItem;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ColorItem extends GuiItem {
private final Color color;
public ColorItem(final @NotNull ItemStack itemStack,
final GuiAction<InventoryClickEvent> action,
final Color color) {
super(itemStack, action);
this.color = color;
}
public ColorItem(final @NotNull ItemStack itemStack, final Color color) {
super(itemStack);
this.color = color;
}
public ColorItem(final @NotNull Material material, final Color color) {
super(material);
this.color = color;
}
public ColorItem(final @NotNull Material material,
final @Nullable GuiAction<InventoryClickEvent> action, final Color color) {
super(material, action);
this.color = color;
}
public Color getColor() {
return color;
}
}

View File

@@ -0,0 +1,221 @@
package io.github.fisher2911.hmccosmetics.gui;
import dev.triumphteam.gui.components.GuiAction;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.message.Adventure;
import io.github.fisher2911.hmccosmetics.message.MessageHandler;
import io.github.fisher2911.hmccosmetics.message.Messages;
import io.github.fisher2911.hmccosmetics.message.Placeholder;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public class CosmeticGui {
private static final float COOL_DOWN = 0.5f;
protected final HMCCosmetics plugin;
protected final MessageHandler messageHandler;
protected final String title;
protected final int rows;
protected final Map<Integer, ItemStack> itemStackMap;
protected final Map<Integer, GuiItem> guiItemMap;
protected Gui gui;
private long lastClicked;
public CosmeticGui(
final HMCCosmetics plugin,
final String title,
final int rows,
final Map<Integer, GuiItem> guiItemMap) {
this.plugin = plugin;
this.messageHandler = this.plugin.getMessageHandler();
this.title = title;
this.rows = rows;
this.guiItemMap = guiItemMap;
this.itemStackMap = new HashMap<>();
this.guiItemMap.forEach((key, value) -> itemStackMap.put(key, value.getItemStack()));
}
private void setItems(final User user) {
final Player player = user.getPlayer();
if (player == null) {
return;
}
for (final var entry : guiItemMap.entrySet()) {
final int slot = entry.getKey();
final GuiItem guiItem = this.getGuiItem(user, player, slot);
if (guiItem == null) {
continue;
}
this.gui.setItem(slot, guiItem);
}
}
private void setUserArmor(
final HumanEntity human,
final User user,
final ArmorItem armorItem,
final InventoryClickEvent event,
final GuiAction<InventoryClickEvent> actionIfSet) {
final long current = System.currentTimeMillis();
if ((current - this.lastClicked) / 1000. < COOL_DOWN) {
return;
}
this.lastClicked = current;
if (!(human instanceof final Player player)) {
return;
}
final ArmorItem.Type type = armorItem.getType();
final ArmorItem setTo = this.plugin.getUserManager().setOrUnset(
user,
armorItem,
Messages.getRemovedMessage(type),
Messages.getSetMessage(type)
);
if (!setTo.isEmpty()) {
actionIfSet.execute(event);
}
final int slot = event.getSlot();
final GuiItem guiItem = this.getGuiItem(user, player, slot);
if (guiItem == null) {
return;
}
this.gui.updateItem(slot, guiItem);
}
public void open(final User user) {
final Player player = user.getPlayer();
if (player == null) return;
this.gui = Gui.gui().
title(Adventure.MINI_MESSAGE.deserialize(
Placeholder.applyPapiPlaceholders(user.getPlayer(), this.title))).
rows(this.rows).
create();
this.gui.setDefaultClickAction(event -> event.setCancelled(true));
this.setItems(user);
this.gui.open(player);
}
@Nullable
private GuiItem getGuiItem(final User user, final Player player, final int slot) {
final GuiItem guiItem = this.guiItemMap.get(slot);
if (guiItem == null) {
return null;
}
final ItemStack itemStack = this.itemStackMap.get(slot);
if (itemStack == null) {
return null;
}
if (guiItem instanceof final ArmorItem armorItem) {
final String permission =
armorItem.getPermission() == null ? "" : armorItem.getPermission();
final boolean hasPermission = permission.isBlank() || player.hasPermission(permission);
return new GuiItem(
this.applyPlaceholders(user, player, armorItem, hasPermission),
event -> {
if (!hasPermission) {
this.messageHandler.sendMessage(
player,
Messages.NO_COSMETIC_PERMISSION
);
return;
}
final ArmorItem cosmeticItem = this.plugin.getCosmeticManager()
.getArmorItem(armorItem.getId());
if (cosmeticItem == null) {
return;
}
this.setUserArmor(player, user, cosmeticItem, event, armorItem.getAction());
}
);
}
guiItem.setItemStack(
ItemBuilder.from(itemStack.clone()).papiPlaceholders(player).build()
);
return guiItem;
}
protected ItemStack applyPlaceholders(final User user, final Player player,
final ArmorItem armorItem, final boolean hasPermission) {
final Map<String, String> placeholders = new HashMap<>();
final PlayerArmor playerArmor = user.getPlayerArmor();
final ArmorItem.Type type = armorItem.getType();
final String id = playerArmor.getItem(type).getId();
placeholders.put(
Placeholder.ENABLED,
String.valueOf(id.equals(armorItem.getId())).
toLowerCase(Locale.ROOT));
placeholders.put(
Placeholder.ALLOWED,
String.valueOf(hasPermission).
toLowerCase(Locale.ROOT));
final ItemStack itemStack;
if (!hasPermission) {
itemStack = armorItem.getItemStack(false);
} else {
itemStack = armorItem.getColored();
}
return ItemBuilder.from(
itemStack
).namePlaceholders(placeholders).
lorePlaceholders(placeholders).
papiPlaceholders(player).
build();
}
public CosmeticGui copy() {
return new CosmeticGui(
this.plugin,
this.title,
this.rows,
new HashMap<>(this.guiItemMap)
);
}
}

View File

@@ -0,0 +1,194 @@
package io.github.fisher2911.hmccosmetics.gui;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.config.DyeGuiSerializer;
import io.github.fisher2911.hmccosmetics.config.GuiSerializer;
import io.github.fisher2911.hmccosmetics.config.ItemSerializer;
import io.github.fisher2911.hmccosmetics.cosmetic.CosmeticManager;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permission;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.io.File;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CosmeticsMenu {
public static final String DEFAULT_MAIN_MENU = "main";
public static final String DEFAULT_DYE_MENU = "dye-menu";
private final HMCCosmetics plugin;
private final CosmeticManager cosmeticManager;
private final Map<String, CosmeticGui> guiMap = new HashMap<>();
public CosmeticsMenu(final HMCCosmetics plugin) {
this.plugin = plugin;
this.cosmeticManager = this.plugin.getCosmeticManager();
}
public void openMenu(final String id, final HumanEntity humanEntity) {
final CosmeticGui cosmeticGui = this.getGui(id);
final Optional<User> optionalUser = this.plugin.getUserManager().get(humanEntity.getUniqueId());
if (optionalUser.isEmpty()) return;
User user = optionalUser.get();
final Wardrobe wardrobe = user.getWardrobe();
if (wardrobe.isActive()) user = wardrobe;
if (cosmeticGui instanceof final DyeSelectorGui dyeSelectorGui) {
dyeSelectorGui.getGui(user, user.getLastSetItem().getType()).open(humanEntity);
return;
}
if (cosmeticGui != null) {
cosmeticGui.open(user);
}
}
public void openDefault(final HumanEntity humanEntity) {
this.openMenu(DEFAULT_MAIN_MENU, humanEntity);
}
public void reload() {
for (final ArmorItem armorItem : this.cosmeticManager.getAll()) {
Bukkit.getPluginManager().removePermission(new Permission(armorItem.getPermission()));
}
this.load();
}
public void openDyeSelectorGui(
User user,
final ArmorItem.Type type) {
final Player player = user.getPlayer();
if (player == null) {
return;
}
final Wardrobe wardrobe = user.getWardrobe();
if (wardrobe.isActive()) user = wardrobe;
final CosmeticGui gui = this.getGui(DEFAULT_DYE_MENU);
if (gui instanceof final DyeSelectorGui dyeSelectorGui) {
dyeSelectorGui.getGui(user, type).open(player);
}
}
@Nullable
private CosmeticGui getGui(final String id) {
final CosmeticGui gui = this.guiMap.get(id);
if (gui == null) return null;
return gui.copy();
}
private static final String GUI_TYPE = "gui-type";
private static final String DYE_TYPE = "dye";
public void load() {
this.guiMap.clear();
final File file = Path.of(this.plugin.getDataFolder().getPath(),
"menus").toFile();
if (!Path.of(this.plugin.getDataFolder().getPath(),
"menus",
DEFAULT_MAIN_MENU + ".yml").toFile().exists()) {
this.plugin.saveResource(
new File("menus", DEFAULT_MAIN_MENU + ".yml").getPath(),
false
);
}
if (!Path.of(this.plugin.getDataFolder().getPath(),
"menus",
DEFAULT_DYE_MENU + ".yml").toFile().exists()) {
this.plugin.saveResource(
new File("menus", DEFAULT_DYE_MENU + ".yml").getPath(),
false
);
}
if (!file.exists() ||
!file.isDirectory()) {
this.plugin.getLogger().severe("No directory found");
return;
}
final File[] files = file.listFiles();
if (files == null) {
this.plugin.getLogger().severe("Files are null");
return;
}
for (final File guiFile : files) {
final String id = guiFile.getName().replace(".yml", "");
final YamlConfigurationLoader loader = YamlConfigurationLoader.
builder().
path(Path.of(guiFile.getPath())).
defaultOptions(opts ->
opts.serializers(build -> {
build.register(GuiItem.class, ItemSerializer.INSTANCE);
build.register(CosmeticGui.class, GuiSerializer.INSTANCE);
build.register(DyeSelectorGui.class, DyeGuiSerializer.INSTANCE);
}))
.build();
try {
final ConfigurationNode source = loader.load();
final ConfigurationNode typeNode = source.node(GUI_TYPE);
final String type;
if (typeNode != null) {
type = typeNode.getString();
} else {
type = "";
}
if (id.equals(DEFAULT_DYE_MENU) || DYE_TYPE.equals(type)) {
this.guiMap.put(id, DyeGuiSerializer.INSTANCE.deserialize(DyeSelectorGui.class, source));
this.plugin.getLogger().info("Loaded dye gui: " + id);
continue;
}
final CosmeticGui gui = source.get(CosmeticGui.class);
if (gui == null) continue;
for (final GuiItem guiItem : gui.guiItemMap.values()) {
if (guiItem instanceof final ArmorItem item) {
final ArmorItem copy = new ArmorItem(item);
copy.setAction(null);
this.cosmeticManager.addArmorItem(copy);
if (copy.getPermission().isBlank()) continue;
Bukkit.getPluginManager().addPermission(new Permission(copy.getPermission()));
}
}
this.guiMap.put(id, source.get(CosmeticGui.class));
this.plugin.getLogger().info("Loaded gui: " + id);
} catch (final ConfigurateException exception) {
exception.printStackTrace();
}
}
}
}

View File

@@ -0,0 +1,201 @@
package io.github.fisher2911.hmccosmetics.gui;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.message.Placeholder;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public class DyeSelectorGui extends CosmeticGui {
private final BiMap<Integer, ArmorItem.Type> cosmeticsSlots;
private int selectedCosmetic;
public DyeSelectorGui(
final HMCCosmetics plugin,
final String title,
final int rows,
final Map<Integer, GuiItem> guiItemMap,
final BiMap<Integer, ArmorItem.Type> cosmeticsSlots,
final int selectedCosmetic
) {
super(plugin, title, rows, guiItemMap);
this.cosmeticsSlots = cosmeticsSlots;
this.selectedCosmetic = selectedCosmetic;
}
public Gui getGui(final User user) {
return this.getGui(user, null);
}
public Gui getGui(final User user, @Nullable final ArmorItem.Type type) {
this.gui = Gui.gui().
title(Component.text(
Placeholder.applyPapiPlaceholders(user.getPlayer(), this.title))).
rows(rows).
create();
final Player player = user.getPlayer();
if (type != null) {
final Integer selected = this.cosmeticsSlots.inverse().get(type);
this.selectedCosmetic = selected == null ? this.selectedCosmetic : selected;
}
for (final var entry : this.cosmeticsSlots.entrySet()) {
gui.setItem(
entry.getKey(),
new GuiItem(
this.applyPlaceholders(
user,
player,
user.getPlayerArmor().getItem(entry.getValue()),
true
)
)
);
}
for (final var entry : this.guiItemMap.entrySet()) {
final GuiItem guiItem = entry.getValue();
final ItemStack itemStack = this.itemStackMap.get(entry.getKey());
if (itemStack == null) {
continue;
}
guiItem.setItemStack(
ItemBuilder.from(itemStack.clone()).papiPlaceholders(player).build()
);
gui.setItem(entry.getKey(), guiItem);
}
final PlayerArmor playerArmor = user.getPlayerArmor();
this.select(this.selectedCosmetic, user, player);
gui.setDefaultClickAction(event -> {
event.setCancelled(true);
final ArmorItem armorItem = playerArmor.getItem(
this.cosmeticsSlots.get(this.selectedCosmetic)
);
if (armorItem == null) {
return;
}
final ItemStack itemStack = playerArmor.getItem(type).getItemStack();
if (itemStack == null) {
return;
}
final int slot = event.getSlot();
final ArmorItem.Type clickedType = this.cosmeticsSlots.get(slot);
if (clickedType != null) {
this.select(slot, user, player);
return;
}
if (!armorItem.isDyeable()) {
return;
}
final GuiItem guiItem = this.guiItemMap.get(slot);
if (!(guiItem instanceof ColorItem colorItem)) {
return;
}
armorItem.setDye(colorItem.getColor().asRGB());
this.plugin.getUserManager().setItem(user, armorItem);
this.updateSelected(user, player);
});
return gui;
}
private void select(final int slot, final User user, final Player player) {
final PlayerArmor playerArmor = user.getPlayerArmor();
final ItemStack previous = this.applyPlaceholders(
user,
player,
playerArmor.getItem(this.cosmeticsSlots.get(this.selectedCosmetic)),
true
);
if (previous != null && previous.getType() != Material.AIR) {
final ItemStack previousItem = dev.triumphteam.gui.builder.item.ItemBuilder.from(
previous
).glow(false).build();
this.gui.updateItem(this.selectedCosmetic, previousItem);
}
this.selectedCosmetic = slot;
this.updateSelected(user, player);
}
private void updateSelected(final User user, final Player player) {
final ArmorItem.Type type = this.cosmeticsSlots.get(this.selectedCosmetic);
if (type == null) {
return;
}
this.gui.updateItem(this.selectedCosmetic,
ItemBuilder.from(
this.applyPlaceholders(
user, player, user.getPlayerArmor().getItem(type), true
)
).glow(true).build());
}
@Override
public void open(final User user) {
final Player player = user.getPlayer();
if (player == null) return;
this.getGui(user, user.getLastSetItem().getType()).open(player);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public DyeSelectorGui copy() {
return new DyeSelectorGui(
this.plugin,
super.title,
super.rows,
new HashMap<>(super.guiItemMap),
HashBiMap.create(this.cosmeticsSlots),
this.selectedCosmetic
);
}
}

View File

@@ -0,0 +1,7 @@
package io.github.fisher2911.hmccosmetics.hook;
public interface Hook {
String getId();
}

View File

@@ -0,0 +1,85 @@
package io.github.fisher2911.hmccosmetics.hook;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.hook.item.ItemHook;
import io.github.fisher2911.hmccosmetics.hook.item.ItemHooks;
import io.github.fisher2911.hmccosmetics.hook.item.ItemsAdderHook;
import io.github.fisher2911.hmccosmetics.hook.item.OraxenHook;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.Nullable;
public class HookManager {
private static final HookManager INSTANCE;
static {
INSTANCE = new HookManager(HMCCosmetics.getPlugin(HMCCosmetics.class));
}
private final HMCCosmetics plugin;
private final ItemHooks itemHooks;
private final PAPIHook papiHook;
private final Set<Class<? extends Hook>> registeredHooks;
private final Set<Listener> listeners;
private HookManager(final HMCCosmetics plugin) {
this.plugin = plugin;
this.registeredHooks = new HashSet<>();
this.listeners = new HashSet<>();
final PluginManager pluginManager = Bukkit.getPluginManager();
if (pluginManager.getPlugin("PlaceholderApi") != null) {
this.registeredHooks.add(PAPIHook.class);
this.papiHook = new PAPIHook();
} else {
this.papiHook = null;
}
final Map<String, ItemHook> itemHookMap = new HashMap<>();
final OraxenHook oraxenHook = new OraxenHook();
final ItemsAdderHook itemsAdderHook = new ItemsAdderHook();
if (pluginManager.getPlugin("Oraxen") != null) {
itemHookMap.put(oraxenHook.getIdentifier(), oraxenHook);
}
if (pluginManager.getPlugin("ItemsAdder") != null) {
itemHookMap.put(itemsAdderHook.getIdentifier(), itemsAdderHook);
this.listeners.add(itemsAdderHook);
}
this.itemHooks = new ItemHooks(itemHookMap);
itemHookMap.values().forEach(hook -> this.registerHook(hook.getClass()));
}
public static HookManager getInstance() {
return INSTANCE;
}
protected void registerHook(final Class<? extends Hook> hook) {
this.registeredHooks.add(hook);
}
public boolean isEnabled(final Class<? extends Hook> hook) {
return this.registeredHooks.contains(hook);
}
public void registerListeners(final HMCCosmetics plugin) {
for (final Listener listener : this.listeners) {
plugin.getServer().getPluginManager().registerEvents(listener, plugin);
}
}
@Nullable
public PAPIHook getPapiHook() {
return papiHook;
}
public ItemHooks getItemHooks() {
return itemHooks;
}
}

View File

@@ -0,0 +1,19 @@
package io.github.fisher2911.hmccosmetics.hook;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.entity.Player;
public class PAPIHook implements Hook {
private static final String ID = "PAPI";
@Override
public String getId() {
return ID;
}
public String parse(final Player player, final String string) {
return PlaceholderAPI.setPlaceholders(player, string);
}
}

View File

@@ -0,0 +1,11 @@
package io.github.fisher2911.hmccosmetics.hook.item;
import io.github.fisher2911.hmccosmetics.hook.Hook;
import org.bukkit.inventory.ItemStack;
public interface ItemHook extends Hook {
String getIdentifier();
ItemStack getItem(final String id);
}

View File

@@ -0,0 +1,42 @@
package io.github.fisher2911.hmccosmetics.hook.item;
import java.util.Map;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
public class ItemHooks {
private final Map<String, ItemHook> itemHookMap;
public ItemHooks(final Map<String, ItemHook> itemHookMap) {
this.itemHookMap = itemHookMap;
}
@Nullable
public ItemStack getItemStack(final String item) {
final String[] parts = item.split(":");
if (parts.length < 2) {
return null;
}
final String identifier = parts[0];
final StringBuilder itemId = new StringBuilder();
for (int i = 1; i < parts.length; i++) {
itemId.append(parts[i]);
if (i < parts.length - 1) {
itemId.append(":");
}
}
final ItemHook hook = this.itemHookMap.get(identifier);
if (hook == null) {
return null;
}
return hook.getItem(itemId.toString());
}
}

View File

@@ -0,0 +1,41 @@
package io.github.fisher2911.hmccosmetics.hook.item;
import dev.lone.itemsadder.api.CustomStack;
import dev.lone.itemsadder.api.Events.ItemsAdderLoadDataEvent;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
public class ItemsAdderHook implements ItemHook, Listener {
public static final String ID = "ITEM_ADDER";
private static final String IDENTIFIER = "itemsadder";
@EventHandler
public void onItemsAdderLoad(final ItemsAdderLoadDataEvent event) {
final HMCCosmetics plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
Bukkit.getScheduler().runTaskAsynchronously(plugin, plugin::load);
}
@Override
public String getId() {
return ID;
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public ItemStack getItem(final String id) {
final CustomStack stack = CustomStack.getInstance(id);
if (stack == null) {
return null;
}
return stack.getItemStack().clone();
}
}

View File

@@ -0,0 +1,31 @@
package io.github.fisher2911.hmccosmetics.hook.item;
import io.th0rgal.oraxen.items.ItemBuilder;
import io.th0rgal.oraxen.items.OraxenItems;
import org.bukkit.inventory.ItemStack;
public class OraxenHook implements ItemHook {
public static final String ID = "ORAXEN";
private static final String IDENTIFIER = "oraxen";
@Override
public String getId() {
return ID;
}
@Override
public String getIdentifier() {
return IDENTIFIER;
}
@Override
public ItemStack getItem(final String id) {
final ItemBuilder itemBuilder = OraxenItems.getItemById(id);
if (itemBuilder == null) {
return null;
}
return itemBuilder.build();
}
}

View File

@@ -0,0 +1,65 @@
package io.github.fisher2911.hmccosmetics.inventory;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
public class PlayerArmor {
private final Map<ArmorItem.Type, ArmorItem> armorItems;
public PlayerArmor(ArmorItem hat, final ArmorItem backpack, final ArmorItem offHand) {
this.armorItems = new EnumMap<>(ArmorItem.Type.class);
this.armorItems.put(hat.getType(), hat);
this.armorItems.put(backpack.getType(), hat);
this.armorItems.put(offHand.getType(), offHand);
}
public PlayerArmor(final Map<ArmorItem.Type, ArmorItem> armorItems) {
this.armorItems = armorItems;
}
public static PlayerArmor empty() {
return new PlayerArmor(
ArmorItem.empty(ArmorItem.Type.HAT),
ArmorItem.empty(ArmorItem.Type.BACKPACK),
ArmorItem.empty(ArmorItem.Type.OFF_HAND)
);
}
public ArmorItem getHat() {
return this.getItem(ArmorItem.Type.HAT);
}
public ArmorItem getBackpack() {
return this.getItem(ArmorItem.Type.BACKPACK);
}
public ArmorItem getOffHand() {
return this.getItem(ArmorItem.Type.OFF_HAND);
}
public ArmorItem getItem(final ArmorItem.Type type) {
ArmorItem armorItem = this.armorItems.get(type);
if (armorItem == null) {
armorItem = ArmorItem.empty(type);
this.armorItems.put(type, armorItem);
}
return armorItem;
}
public ArmorItem setItem(final ArmorItem armorItem) {
return this.armorItems.put(armorItem.getType(), armorItem);
}
public Collection<ArmorItem> getArmorItems() {
return this.armorItems.values();
}
public PlayerArmor copy() {
return new PlayerArmor(new HashMap<>(this.armorItems));
}
}

View File

@@ -0,0 +1,67 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryDragEvent;
public class ClickListener implements Listener {
private final HMCCosmetics plugin;
private final UserManager userManager;
public ClickListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
}
@EventHandler
public void onCosmeticClick(final InventoryClickEvent event) {
final HumanEntity player = event.getWhoClicked();
if (!(player instanceof Player)) {
return;
}
this.fixInventory((Player) player);
}
@EventHandler
public void onCosmeticClick(final InventoryDragEvent event) {
final HumanEntity player = event.getWhoClicked();
if (!(player instanceof Player)) {
return;
}
this.fixInventory((Player) player);
}
@EventHandler
public void onInventoryClose(final InventoryCloseEvent event) {
final HumanEntity player = event.getPlayer();
this.userManager.get(player.getUniqueId()).ifPresent(this::doRunnable);
}
private void fixInventory(final Player player) {
final Optional<User> optionalUser = this.userManager.get(player.getUniqueId());
if (optionalUser.isEmpty()) {
return;
}
this.doRunnable(optionalUser.get());
}
private void doRunnable(final User user) {
Bukkit.getScheduler().runTaskLaterAsynchronously(
this.plugin, () -> this.userManager.updateCosmetics(user),
1);
}
}

View File

@@ -0,0 +1,80 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.spigotmc.event.entity.EntityMountEvent;
public class CosmeticFixListener implements Listener {
private final HMCCosmetics plugin;
private final UserManager userManager;
public CosmeticFixListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
}
@EventHandler
public void onEntityMount(final EntityMountEvent event) {
if (!(event.getEntity() instanceof final Player player)) {
return;
}
this.fixCosmetics(player);
}
@EventHandler
public void onOffhandSwap(final PlayerSwapHandItemsEvent event) {
final ItemStack offHand = event.getOffHandItem();
if (offHand != null && offHand.getType() != Material.AIR) {
return;
}
this.fixCosmetics(event.getPlayer());
}
@EventHandler
public void onRightClick(final PlayerInteractEvent event) {
if (event.getAction() != Action.RIGHT_CLICK_BLOCK
&& event.getHand() != EquipmentSlot.OFF_HAND) {
return;
}
final Player player = event.getPlayer();
final ItemStack mainHand = event.getPlayer().getInventory().getItemInMainHand();
if (mainHand.getType().isBlock() && mainHand.getAmount() > 0) {
return;
}
this.userManager.updateCosmetics(player.getUniqueId(), true);
}
@EventHandler(ignoreCancelled = true)
public void onBlockPlace(final BlockPlaceEvent event) {
if (event.getHand() != EquipmentSlot.OFF_HAND) {
return;
}
final ItemStack itemStack = event.getItemInHand();
if (itemStack.getAmount() > 1) {
return;
}
this.fixCosmetics(event.getPlayer());
}
private void fixCosmetics(final Player player) {
Bukkit.getScheduler().runTaskLaterAsynchronously(this.plugin,
() -> this.userManager.updateCosmetics(player.getUniqueId(), true), 2);
}
}

View File

@@ -0,0 +1,54 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.database.Database;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional;
public class JoinListener implements Listener {
private final HMCCosmetics plugin;
private final Database database;
private final UserManager userManager;
public JoinListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.database = this.plugin.getDatabase();
this.userManager = this.plugin.getUserManager();
}
@EventHandler
public void onJoin(final PlayerJoinEvent event) {
final Player player = event.getPlayer();
this.database.loadUser(player.getUniqueId(),
user -> Bukkit.getScheduler().runTaskAsynchronously(this.plugin,
() -> this.userManager.resendCosmetics(player)));
}
@EventHandler
public void onQuit(final PlayerQuitEvent event) {
final Player player = event.getPlayer();
final Optional<User> optionalUser = this.userManager.get(player.getUniqueId());
optionalUser.ifPresent(user -> {
final Wardrobe wardrobe = user.getWardrobe();
if (wardrobe.isActive()) {
Bukkit.getScheduler().runTaskAsynchronously(
this.plugin,
() -> wardrobe.despawnFakePlayer(player)
);
}
});
this.userManager.remove(player.getUniqueId());
}
}

View File

@@ -0,0 +1,43 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.message.Messages;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import io.github.fisher2911.hmccosmetics.user.Wardrobe;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import java.util.Optional;
public class PlayerShiftListener implements Listener {
private final HMCCosmetics plugin;
private final UserManager userManager;
public PlayerShiftListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
}
@EventHandler
public void onPlayerShift(final PlayerToggleSneakEvent event) {
final Player player = event.getPlayer();
final Optional<User> userOptional = this.userManager.get(player.getUniqueId());
if (userOptional.isEmpty()) return;
final User user = userOptional.get();
final Wardrobe wardrobe = user.getWardrobe();
if (!wardrobe.isActive()) return;
wardrobe.despawnFakePlayer(player);
this.plugin.getMessageHandler().sendMessage(
player,
Messages.CLOSED_WARDROBE
);
}
}

View File

@@ -0,0 +1,36 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import java.util.Optional;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
public class RespawnListener implements Listener {
private final HMCCosmetics plugin;
private final UserManager userManager;
public RespawnListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
}
@EventHandler
public void onPlayerRespawn(final PlayerRespawnEvent event) {
Bukkit.getScheduler().runTaskLater(this.plugin, () -> {
final Player player = event.getPlayer();
final Optional<User> optionalUser = this.userManager.get(player.getUniqueId());
optionalUser.ifPresent(user -> {
user.despawnAttached();
this.userManager.updateCosmetics(user);
this.userManager.setItem(user, user.getPlayerArmor().getHat());
});
}, 1);
}
}

View File

@@ -0,0 +1,29 @@
package io.github.fisher2911.hmccosmetics.listener;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.user.User;
import io.github.fisher2911.hmccosmetics.user.UserManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerTeleportEvent;
public class TeleportListener implements Listener {
private final HMCCosmetics plugin;
private final UserManager userManager;
public TeleportListener(final HMCCosmetics plugin) {
this.plugin = plugin;
this.userManager = this.plugin.getUserManager();
}
@EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
public void onPlayerTeleport(final PlayerTeleportEvent event) {
final Player player = event.getPlayer();
this.userManager.get(player.getUniqueId()).ifPresent(User::despawnAttached);
}
}

View File

@@ -0,0 +1,23 @@
package io.github.fisher2911.hmccosmetics.message;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.transformation.TransformationRegistry;
import net.kyori.adventure.text.minimessage.transformation.TransformationType;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public class Adventure {
public static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.hexColors()
.useUnusualXRepeatedCharacterHexFormat()
.build();
public static final MiniMessage MINI_MESSAGE = MiniMessage.builder()
.transformations(TransformationRegistry.
builder().
add(TransformationType.CLICK_EVENT,
TransformationType.DECORATION,
TransformationType.COLOR
).build())
.build();
}

View File

@@ -0,0 +1,8 @@
package io.github.fisher2911.hmccosmetics.message;
public class ErrorMessages {
public static final String INVALID_ITEM = "%s is not a valid %s in file %s";
public static final String ITEM_NOT_FOUND = "%s was not found in file %s";
}

View File

@@ -0,0 +1,60 @@
package io.github.fisher2911.hmccosmetics.message;
import java.util.Objects;
public class Message {
private final String key;
private final String message;
private final Type type;
public Message(final String key, final String message, final Type type) {
this.key = key;
this.message = message;
this.type = type;
}
public Message(final String key, final String message) {
this.message = message;
this.key = key;
this.type = Type.MESSAGE;
}
public String getKey() {
return key;
}
public String getMessage() {
return this.message;
}
public Type getType() {
return type;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Message message = (Message) o;
return Objects.equals(key, message.key);
}
@Override
public int hashCode() {
return Objects.hash(message);
}
public enum Type {
MESSAGE,
ACTION_BAR,
TITLE
}
}

View File

@@ -0,0 +1,171 @@
package io.github.fisher2911.hmccosmetics.message;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.util.Utils;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.bungeecord.BungeeComponentSerializer;
import net.kyori.adventure.title.Title;
import net.md_5.bungee.api.ChatMessageType;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
public class MessageHandler {
private final HMCCosmetics plugin;
private final Logger logger;
private final BukkitAudiences adventure;
private final Map<String, Message> messageMap = new HashMap<>();
public MessageHandler(final HMCCosmetics plugin) {
this.plugin = plugin;
this.logger = this.plugin.getLogger();
this.adventure = BukkitAudiences.create(this.plugin);
}
/**
* Closes adventure
*/
public void close() {
adventure.close();
}
/**
* @param sender receiver of message
* @param key message key
* @param placeholders placeholders
*/
public void sendMessage(final CommandSender sender, final Message key,
final Map<String, String> placeholders) {
final String message = this.getPapiPlaceholders(
sender,
Placeholder.applyPlaceholders(this.getMessage(key), placeholders)
);
final Component component = Adventure.MINI_MESSAGE.deserialize(message);
sender.spigot().sendMessage(BungeeComponentSerializer.get().serialize(component));
}
/**
* @param sender receiver of message
* @param key message key
*/
public void sendMessage(final CommandSender sender, final Message key) {
this.sendMessage(sender, key, Collections.emptyMap());
}
/**
* @param player receiver of message
* @param key message key
* @param placeholders placeholders
*/
public void sendActionBar(final Player player, final Message key,
final Map<String, String> placeholders) {
final String message = this.getPapiPlaceholders(
player,
Placeholder.applyPlaceholders(this.getMessage(key), placeholders)
);
Component component = Adventure.MINI_MESSAGE.deserialize(message);
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, BungeeComponentSerializer.get().serialize(component));
}
/**
* @param player receiver of message
* @param key message key
*/
public void sendActionBar(final Player player, final Message key) {
this.sendActionBar(player, key, Collections.emptyMap());
}
/**
* @param player receiver of message
* @param key message key
* @param placeholders placeholders
*/
public void sendTitle(final Player player, final Message key,
final Map<String, String> placeholders) {
final String message = this.getPapiPlaceholders(
player,
Placeholder.applyPlaceholders(this.getMessage(key), placeholders)
);
Component component = Adventure.MINI_MESSAGE.deserialize(message);
this.adventure.player(player).showTitle(Title.title(component, Component.empty()));
}
/**
* @param player receiver of message
* @param key message key
*/
public void sendTitle(final Player player, final Message key) {
this.sendTitle(player, key, Collections.emptyMap());
}
/**
* @param key message key
* @return message, or empty string if message not found
*/
public String getMessage(final Message key) {
return this.messageMap.getOrDefault(key.getKey(), key).getMessage();
}
/**
* Loads all messages from messages.yml
*/
public void load() {
final String fileName = "messages.yml";
final File file = new File(this.plugin.getDataFolder(), fileName);
if (!file.exists()) {
this.plugin.saveResource(fileName, false);
}
final FileConfiguration config = YamlConfiguration.loadConfiguration(file);
String prefix = config.getString("prefix");
if (prefix == null) {
prefix = "";
}
for (final String key : config.getKeys(false)) {
final String message = Utils.replaceIfNull(config.getString(key), "", value -> {
if (value == null) {
this.logger.warning(
String.format(ErrorMessages.ITEM_NOT_FOUND, "message", fileName));
}
}).replace(Placeholder.PREFIX, prefix);
final Message.Type messageType = Utils.stringToEnum(
Utils.replaceIfNull(config.getString("type"), ""), Message.Type.class,
Message.Type.MESSAGE
);
this.messageMap.put(key, new Message(key, message, messageType));
}
}
private String getPapiPlaceholders(final CommandSender sender, final String message) {
if (sender instanceof final Player player) {
return Placeholder.applyPapiPlaceholders(player, message);
}
return Placeholder.applyPapiPlaceholders(null, message);
}
}

View File

@@ -0,0 +1,97 @@
package io.github.fisher2911.hmccosmetics.message;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import org.bukkit.ChatColor;
public class Messages {
public static final Message INVALID_COLOR = new Message(
"","<red>" + Placeholder.ITEM + " is an invalid color.");
public static final Message NO_PERMISSION =
new Message("no-permission", "You do not have permission for this!");
public static final Message NO_COSMETIC_PERMISSION =
new Message("no-cosmetic-permission", "You do not have permission for this cosmetic!");
public static final Message SET_HAT =
new Message("set-hat", "Set hat");
public static final Message REMOVED_HAT =
new Message("removed-hat", "Removed hat");
public static final Message SET_BACKPACK =
new Message("set-backpack", "Set backpack");
public static final Message REMOVED_BACKPACK =
new Message("removed-backpack", "Removed backpack");
public static final Message SET_OFF_HAND =
new Message("set-off-hand", "Set off hand");
public static final Message REMOVED_OFF_HAND =
new Message("removed-off-hand", "Removed off hand");
public static final Message SET_DYE_COLOR =
new Message("set-dye-color", "Set dye color of " + Placeholder.ITEM);
public static final Message MUST_BE_PLAYER =
new Message("must-be-player", "You must be a player to do this!");
public static final Message RELOADED =
new Message("reloaded", "Config reloaded");
public static final Message INVALID_TYPE =
new Message("invalid-type", "Invalid type");
public static final Message INVALID_USER =
new Message("invalid-user", ChatColor.RED + "That user's data cannot be found!");
public static final Message ITEM_NOT_FOUND =
new Message("item-not-found", ChatColor.RED + "That item could not be found!");
public static final Message HELP_COMMAND =
new Message("help-command",
"""
<#6D9DC5><st> </st> <gradient:#40B7D6:#6D9DC5>HMCCosmetics - Help</gradient><#6D9DC5> <st> </st>
<#5AE4B5>• <#40B7D6>/cosmetics - <#6D9DC5>Opens cosmetics GUI.
<#5AE4B5>• <#40B7D6>/cosmetics dye <gray><BACKPACK/HAT></gray> - <#6D9DC5>Opens dye menu for specified cosmetic.
<#5AE4B5>• <#40B7D6>/cosmetics help - <#6D9DC5>Opens this menu.
<st> </st>""");
public static final Message OPENED_WARDROBE =
new Message("opened-wardrobe", ChatColor.GREEN + "Viewing wardrobe!");
public static final Message CLOSED_WARDROBE =
new Message("closed-wardrobe", ChatColor.GREEN + "Closing wardrobe!");
public static final Message WARDROBE_ALREADY_OPEN =
new Message("wardrobe-already-open", ChatColor.RED + "The wardrobe is already open!");
public static final Message SET_OTHER_BACKPACK = new Message(
"set-other-backpack", ChatColor.GREEN + "You have set the backpack of " +
Placeholder.PLAYER + " to " + Placeholder.TYPE + "."
);
public static final Message SET_OTHER_HAT = new Message(
"set-other-backpack", ChatColor.GREEN + "You have set the helmet of " +
Placeholder.PLAYER + " to " + Placeholder.TYPE + "."
);
public static final Message SET_OTHER_OFF_HAND = new Message(
"set-other-off-hand", ChatColor.GREEN + "You have set the off hand of " +
Placeholder.PLAYER + " to " + Placeholder.TYPE + "."
);
public static Message getSetMessage(final ArmorItem.Type type) {
return switch (type) {
case HAT -> Messages.SET_HAT;
case BACKPACK -> Messages.SET_BACKPACK;
case OFF_HAND -> Messages.SET_OFF_HAND;
};
}
public static Message getRemovedMessage(final ArmorItem.Type type) {
return switch (type) {
case HAT -> Messages.REMOVED_HAT;
case BACKPACK -> Messages.REMOVED_BACKPACK;
case OFF_HAND -> Messages.REMOVED_OFF_HAND;
};
}
public static Message getSetOtherMessage(final ArmorItem.Type type) {
return switch (type) {
case HAT -> Messages.SET_OTHER_HAT;
case BACKPACK -> Messages.SET_OTHER_BACKPACK;
case OFF_HAND -> Messages.SET_OTHER_OFF_HAND;
};
}
}

View File

@@ -0,0 +1,12 @@
package io.github.fisher2911.hmccosmetics.message;
public class Permission {
public static final String DEFAULT_COMMAND = "hmccosmetics.cmd.default";
public static final String DYE_COMMAND = "hmccosmetics.cmd.dye";
public static final String RELOAD_COMMAND = "hmccosmetics.cmd.reload";
public static final String HELP_COMMAND = "hmccosmetics.cmd.help";
public static final String SET_COSMETIC_COMMAND = "hmccosmetics.cmd.set";
public static final String VIEW_WARDROBE = "hmccosmetics.cmd.wardrobe";
}

View File

@@ -0,0 +1,42 @@
package io.github.fisher2911.hmccosmetics.message;
import io.github.fisher2911.hmccosmetics.hook.HookManager;
import io.github.fisher2911.hmccosmetics.hook.PAPIHook;
import java.util.Map;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
public class Placeholder {
public static final String PREFIX = "%prefix%";
public static final String TYPE = "%type%";
public static final String ITEM = "%item%";
public static final String FILE = "%file%";
public static final String PLAYER = "%player%";
public static final String ENABLED = "%enabled%";
public static final String ALLOWED = "%allowed%";
/**
* @param message message being translated
* @param placeholders placeholders applied
* @return message with placeholders applied
*/
public static String applyPlaceholders(String message, final Map<String, String> placeholders) {
for (final Map.Entry<String, String> entry : placeholders.entrySet()) {
message = message.replace(entry.getKey(), Translation.translate(entry.getValue()));
}
return message;
}
public static String applyPapiPlaceholders(@Nullable final Player player,
final String message) {
if (HookManager.getInstance().isEnabled(PAPIHook.class)) {
return HookManager.getInstance().getPapiHook().parse(player, message);
}
return message;
}
}

View File

@@ -0,0 +1,56 @@
package io.github.fisher2911.hmccosmetics.message;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
public class Translation {
private static final Translation INSTANCE;
private static final String FILE_NAME = "translations.yml";
private static final String TRANSLATION_PATH = "translations";
static {
INSTANCE = new Translation(HMCCosmetics.getPlugin(HMCCosmetics.class));
}
private final HMCCosmetics plugin;
private final Map<String, String> translations;
public Translation(final HMCCosmetics plugin) {
this.translations = new HashMap<>();
this.plugin = plugin;
}
public static Translation getInstance() {
return INSTANCE;
}
public static String translate(final String key) {
return INSTANCE.translations.getOrDefault(key, key);
}
public void load() {
final File file = new File(this.plugin.getDataFolder(), FILE_NAME);
if (!file.exists()) {
this.plugin.saveResource(FILE_NAME, false);
}
final FileConfiguration config = YamlConfiguration.loadConfiguration(file);
final ConfigurationSection section = config.getConfigurationSection(TRANSLATION_PATH);
if (section == null) {
return;
}
for (final String key : section.getKeys(false)) {
this.translations.put(key, section.getString(key));
}
}
}

View File

@@ -0,0 +1,170 @@
package io.github.fisher2911.hmccosmetics.packet;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.Pair;
import io.github.fisher2911.nms.playerpackets.PlayerPackets;
import io.github.fisher2911.nms.PlayerPackets_1_17_R1;
import io.github.fisher2911.nms.PlayerPackets_1_18_R1;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.IntArrayList;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.UUID;
public class PacketManager {
private static final PlayerPackets playerPackets;
static {
final String version = Bukkit.getVersion();
System.out.println("Version: " + Bukkit.getVersion());
if (version.contains("1.17")) {
playerPackets = new PlayerPackets_1_17_R1();
} else if (version.contains("1.18")) {
playerPackets = new PlayerPackets_1_18_R1();
} else {
playerPackets = null;
}
}
public static PacketContainer getEntitySpawnPacket(final Location location, final int entityId,
final EntityType entityType) {
final PacketContainer packet = new PacketContainer(PacketType.Play.Server.SPAWN_ENTITY);
// Entity ID
packet.getIntegers().write(0, entityId);
// Entity Type
// packet.getIntegers().write(6, 78);
// Set yaw pitch
packet.getIntegers().write(4, (int) location.getPitch());
packet.getIntegers().write(5, (int) location.getYaw());
// Set location
packet.getDoubles().write(0, location.getX());
packet.getDoubles().write(1, location.getY());
packet.getDoubles().write(2, location.getZ());
// Set UUID
packet.getUUIDs().write(0, UUID.randomUUID());
packet.getEntityTypeModifier().write(0, entityType);
return packet;
}
public static PacketContainer getEquipmentPacket(
final List<Pair<EnumWrappers.ItemSlot, ItemStack>> equipmentList,
final int entityId
) {
final PacketContainer armorPacket = new PacketContainer(
PacketType.Play.Server.ENTITY_EQUIPMENT);
armorPacket.getIntegers().write(0, entityId);
armorPacket.getSlotStackPairLists().write(0, equipmentList);
return armorPacket;
}
public static PacketContainer getRotationPacket(final int entityId, final Location location) {
final PacketContainer rotationPacket = new PacketContainer(
PacketType.Play.Server.ENTITY_HEAD_ROTATION);
rotationPacket.getIntegers().write(0, entityId);
rotationPacket.getBytes().write(0, (byte) (location.getYaw() * 256 / 360));
return rotationPacket;
}
public static PacketContainer getRidingPacket(final int mountId, final int passengerId) {
final PacketContainer ridingPacket = new PacketContainer(PacketType.Play.Server.MOUNT);
ridingPacket.
getIntegers().
write(0, mountId);
ridingPacket.getIntegerArrays().write(0, new int[]{passengerId});
return ridingPacket;
}
public static PacketContainer getEntityDestroyPacket(final int entityId) {
final PacketContainer destroyPacket = new PacketContainer(
PacketType.Play.Server.ENTITY_DESTROY);
destroyPacket.getModifier().write(0, new IntArrayList(new int[]{entityId}));
return destroyPacket;
}
public static PacketContainer getSoundPacket(
final Player player,
final Location location,
final MinecraftKey name,
final float volume,
final float pitch,
final EnumWrappers.SoundCategory soundCategory
) {
final var manager = ProtocolLibrary.getProtocolManager();
final var packet = manager.createPacket(PacketType.Play.Server.CUSTOM_SOUND_EFFECT);
packet.getMinecraftKeys()
.write(
0,
name
);
packet.getSoundCategories()
.write(0, EnumWrappers.SoundCategory.valueOf(soundCategory.name()));
packet.getIntegers()
.write(0, location.getBlockX() * 8)
.write(
1, location.getBlockY() * 8
)
.write(2, location.getBlockZ() * 8);
packet.getFloat()
.write(0, volume)
.write(1, pitch);
return packet;
}
public static PacketContainer getFakePlayerSpawnPacket(final Location location, final UUID uuid, final int entityId) throws IllegalStateException {
if (playerPackets == null) throw new IllegalStateException("This cannot be used in version: " + Bukkit.getVersion());
return playerPackets.getSpawnPacket(location, uuid, entityId);
}
public static PacketContainer getFakePlayerInfoPacket(final Player player, final UUID uuid) throws IllegalStateException {
if (playerPackets == null) throw new IllegalStateException("This cannot be used in version: " + Bukkit.getVersion());
return playerPackets.getPlayerInfoPacket(player, uuid);
}
public static PacketContainer getRemovePlayerPacket(final Player player, final UUID uuid, final int entityId) {
if (playerPackets == null) throw new IllegalStateException("This cannot be used in version: " + Bukkit.getVersion());
return playerPackets.getRemovePacket(player, uuid, entityId);
}
public static void sendPacket(final Player to, final PacketContainer... packets) {
final ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
try {
for (final PacketContainer packet : packets) {
protocolManager.sendServerPacket(to, packet);
}
} catch (final InvocationTargetException exception) {
exception.printStackTrace();
}
}
public static void sendPacketToOnline(final PacketContainer... packets) {
for (final Player player : Bukkit.getOnlinePlayers()) {
sendPacket(player, packets);
}
}
}

View File

@@ -0,0 +1,35 @@
package io.github.fisher2911.hmccosmetics.user;
import org.bukkit.inventory.EntityEquipment;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.EnumMap;
import java.util.Map;
public class Equipment {
private static final EquipmentSlot[] VALUES = EquipmentSlot.values();
private final Map<EquipmentSlot, ItemStack> equipment = new EnumMap<>(EquipmentSlot.class);
public Equipment() {
}
public static Equipment fromEntityEquipment(final EntityEquipment entityEquipment) {
final Equipment equipment = new Equipment();
for (final EquipmentSlot slot : VALUES) {
equipment.setItem(slot, entityEquipment.getItem(slot));
}
return equipment;
}
@Nullable
public ItemStack getItem(final EquipmentSlot slot) {
return this.equipment.get(slot);
}
public void setItem(final EquipmentSlot slot, @Nullable final ItemStack itemStack) {
this.equipment.put(slot, itemStack);
}
}

View File

@@ -0,0 +1,197 @@
package io.github.fisher2911.hmccosmetics.user;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.Serializer;
import io.github.fisher2911.hmccosmetics.config.Settings;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.packet.PacketManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class User {
private final UUID uuid;
private final PlayerArmor playerArmor;
protected Wardrobe wardrobe;
private ArmorItem lastSetItem = ArmorItem.empty(ArmorItem.Type.HAT);
private boolean hasArmorStand;
private final int armorStandId;
public User(final UUID uuid, final PlayerArmor playerArmor, final Wardrobe wardrobe, final int armorStandId) {
this.uuid = uuid;
this.playerArmor = playerArmor;
this.wardrobe = wardrobe;
this.armorStandId = armorStandId;
}
protected User(final UUID uuid, final PlayerArmor playerArmor, final int armorStandId) {
this.uuid = uuid;
this.playerArmor = playerArmor;
this.armorStandId = armorStandId;
}
public @Nullable Player getPlayer() {
return Bukkit.getPlayer(this.uuid);
}
public UUID getUuid() {
return this.uuid;
}
public PlayerArmor getPlayerArmor() {
return playerArmor;
}
public Wardrobe getWardrobe() {
return wardrobe;
}
protected void setPlayerArmor(final PlayerArmor playerArmor) {
for (final ArmorItem armorItem : playerArmor.getArmorItems()) {
this.playerArmor.setItem(armorItem);
}
}
protected void removeAllCosmetics() {
for (final ArmorItem.Type type : ArmorItem.Type.values()) {
this.removeItem(type);
}
}
public int getArmorStandId() {
return armorStandId;
}
protected ArmorItem setItem(final ArmorItem armorItem) {
this.lastSetItem = armorItem;
return this.playerArmor.setItem(armorItem);
}
protected ArmorItem removeItem(final ArmorItem.Type type) {
return this.setItem(ArmorItem.empty(type));
}
public void spawnArmorStand(final Player other) {
final Player player = this.getPlayer();
if (player == null) return;
final Location location = player.getLocation();
final PacketContainer packet = PacketManager.getEntitySpawnPacket(location, this.armorStandId, EntityType.ARMOR_STAND);
PacketManager.sendPacket(other, packet);
}
public void spawnArmorStand(final Settings settings) {
if (this.hasArmorStand) {
this.updateArmorStand(settings);
return;
}
for (final Player p : Bukkit.getOnlinePlayers()) {
this.spawnArmorStand(p);
}
this.hasArmorStand = true;
}
public void updateArmorStand(final Settings settings) {
if (!this.hasArmorStand) {
this.spawnArmorStand(settings);
}
for (final Player player : Bukkit.getOnlinePlayers()) {
this.updateArmorStand(player, settings);
}
}
public void updateArmorStand(final Player other, final Settings settings) {
final Player player = this.getPlayer();
if (player == null) return;
this.updateArmorStand(other, settings, player.getLocation());
}
public void updateArmorStand(final Player other, final Settings settings, final Location location) {
final List<Pair<EnumWrappers.ItemSlot, ItemStack>> equipmentList = new ArrayList<>();
equipmentList.add(new Pair<>(EnumWrappers.ItemSlot.HEAD,
this.playerArmor.getBackpack().getColored()
));
final PacketContainer armorPacket = PacketManager.getEquipmentPacket(equipmentList, this.armorStandId);
final PacketContainer rotationPacket = PacketManager.getRotationPacket(this.armorStandId, location);
final PacketContainer ridingPacket = PacketManager.getRidingPacket(this.getEntityId(), this.armorStandId);
final PacketContainer metaContainer = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
WrappedDataWatcher metaData = new WrappedDataWatcher();
final Serializer byteSerializer = WrappedDataWatcher.Registry.get(Byte.class);
metaData.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(0, byteSerializer), (byte) (0x20));
metaData.setObject(new WrappedDataWatcher.WrappedDataWatcherObject(15, byteSerializer), (byte) (0x10));
metaContainer.getIntegers().write(0, this.armorStandId);
metaContainer.getWatchableCollectionModifier().write(0, metaData.getWatchableObjects());
PacketManager.sendPacket(other, armorPacket, metaContainer, rotationPacket, ridingPacket);
final int lookDownPitch = settings.getCosmeticSettings().getLookDownPitch();
if (lookDownPitch != -1 &&
this.isFacingDown(location, lookDownPitch)) {
equipmentList.set(0, new Pair<>(EnumWrappers.ItemSlot.HEAD,
new ItemStack(Material.AIR)
));
PacketManager.sendPacket(other, PacketManager.getEquipmentPacket(equipmentList, this.armorStandId));
}
}
private boolean isFacingDown(final Location location, final int pitchLimit) {
return location.getPitch() > pitchLimit;
}
public void despawnAttached() {
PacketManager.sendPacketToOnline(PacketManager.getEntityDestroyPacket(this.armorStandId));
this.hasArmorStand = false;
}
public boolean hasArmorStand() {
return hasArmorStand;
}
public ArmorItem getLastSetItem() {
return lastSetItem;
}
public int getEntityId() {
final Player player = this.getPlayer();
if (player == null) return -1;
return player.getEntityId();
}
public boolean hasPermissionToUse(final ArmorItem armorItem) {
final Player player = this.getPlayer();
if (player == null) return false;
return player.hasPermission(armorItem.getPermission());
}
}

View File

@@ -0,0 +1,267 @@
package io.github.fisher2911.hmccosmetics.user;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.api.CosmeticItem;
import io.github.fisher2911.hmccosmetics.api.event.CosmeticChangeEvent;
import io.github.fisher2911.hmccosmetics.concurrent.Threads;
import io.github.fisher2911.hmccosmetics.config.CosmeticSettings;
import io.github.fisher2911.hmccosmetics.config.Settings;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.message.Message;
import io.github.fisher2911.hmccosmetics.message.MessageHandler;
import io.github.fisher2911.hmccosmetics.message.Placeholder;
import io.github.fisher2911.hmccosmetics.packet.PacketManager;
import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import org.bukkit.scheduler.BukkitTask;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
public class UserManager {
private final HMCCosmetics plugin;
private final Settings settings;
private final MessageHandler messageHandler;
private final Map<UUID, User> userMap = new HashMap<>();
private final Map<Integer, User> armorStandIdMap = new HashMap<>();
private BukkitTask teleportTask;
public UserManager(final HMCCosmetics plugin) {
this.plugin = plugin;
this.settings = this.plugin.getSettings();
this.messageHandler = this.plugin.getMessageHandler();
}
public void add(final User user) {
this.userMap.put(user.getUuid(), user);
this.armorStandIdMap.put(user.getArmorStandId(), user);
this.updateCosmetics(user);
}
public Optional<User> get(final UUID uuid) {
return Optional.ofNullable(this.userMap.get(uuid));
}
public Collection<User> getAll() {
return this.userMap.values();
}
public void remove(final UUID uuid) {
final User user = this.userMap.remove(uuid);
if (user == null) return;
this.armorStandIdMap.remove(user.getArmorStandId());
final PlayerArmor copy = user.getPlayerArmor().copy();
user.removeAllCosmetics();
this.updateCosmetics(user);
user.despawnAttached();
user.setPlayerArmor(copy);
Threads.getInstance().execute(() -> this.plugin.getDatabase().saveUser(user));
}
public void startTeleportTask() {
// throws an error on first load of registry if this isn't here
WrappedDataWatcher.Registry.get(Byte.class);
this.teleportTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
this.plugin,
() -> {
for (final User user : this.userMap.values()) {
user.updateArmorStand(this.plugin.getSettings());
}
},
1,
1
);
}
public void resendCosmetics(final Player player) {
for (final User user : this.userMap.values()) {
user.spawnArmorStand(player);
this.updateCosmetics(user, false, player);
}
}
public void updateCosmetics(final UUID uuid, final boolean ignoreRestrictions) {
this.get(uuid).ifPresent(user -> this.updateCosmetics(user, ignoreRestrictions));
}
public void updateCosmetics(final UUID uuid) {
this.updateCosmetics(uuid, false);
}
public void updateCosmetics(final User user) {
this.updateCosmetics(user, false);
}
public void updateCosmetics(final User user, final boolean ignoreRestrictions) {
for (final Player player : Bukkit.getOnlinePlayers()) {
this.updateCosmetics(user, ignoreRestrictions, player);
}
}
public void updateCosmetics(final User user, final boolean ignoreRestrictions, final Player other) {
final Player player = user.getPlayer();
final Equipment equipment;
if (player == null) {
equipment = new Equipment();
} else {
equipment = Equipment.fromEntityEquipment(player.getEquipment());
}
final PlayerArmor playerArmor = user.getPlayerArmor();
final List<Pair<EnumWrappers.ItemSlot, ItemStack>> equipmentList = new ArrayList<>();
equipmentList.add(
new Pair<>(EnumWrappers.ItemSlot.HEAD, this.getCosmeticItem(equipment, playerArmor.getHat(), EquipmentSlot.HEAD, ignoreRestrictions))
);
equipmentList.add(
new Pair<>(EnumWrappers.ItemSlot.OFFHAND, this.getCosmeticItem(equipment, playerArmor.getOffHand(), EquipmentSlot.OFF_HAND, ignoreRestrictions))
);
PacketManager.sendPacket(
other,
PacketManager.getEquipmentPacket(
equipmentList,
user.getEntityId()
)
);
}
private ItemStack getCosmeticItem(
final Equipment equipment,
final ArmorItem armorItem,
final EquipmentSlot slot,
final boolean ignoreRestrictions) {
final CosmeticSettings cosmeticSettings = this.settings.getCosmeticSettings();
final Map<String, String> placeholders = Map.of(Placeholder.ALLOWED, "true",
Placeholder.ENABLED, "true");
ItemStack itemStack = ItemBuilder.from(armorItem.getColored()).
namePlaceholders(placeholders).
lorePlaceholders(placeholders).
build();
final boolean isAir = itemStack.getType().isAir();
final boolean requireEmpty = cosmeticSettings.requireEmpty(slot);
if (!isAir && (!requireEmpty || ignoreRestrictions)) return itemStack;
if (equipment == null) return itemStack;
final ItemStack equipped = equipment.getItem(slot);
if (equipped != null && equipped.getType() != Material.AIR) return equipped;
return itemStack;
}
public void setItem(final User user, final ArmorItem armorItem) {
final Wardrobe wardrobe = user.getWardrobe();
final User setUser;
if (wardrobe.isActive()) {
setUser = wardrobe;
} else {
setUser = user;
}
ArmorItem previous = setUser.getPlayerArmor().getItem(armorItem.getType());
final CosmeticChangeEvent event =
new CosmeticChangeEvent(new CosmeticItem(armorItem.copy()), new CosmeticItem(previous.copy()), setUser);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) return;
setUser.setItem(event.getCosmeticItem().getArmorItem());
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
switch (armorItem.getType()) {
case HAT, OFF_HAND -> this.updateCosmetics(setUser);
case BACKPACK -> {
if (wardrobe.isActive()) setUser.updateArmorStand(settings);
}
}
});
}
public void removeItem(final User user, final ArmorItem.Type type) {
this.setItem(user, ArmorItem.empty(type));
}
/**
* @param user
* @param armorItem
* @param removeMessage
* @param setMessage
* @return the item that was set
*/
public ArmorItem setOrUnset(
final User user,
final ArmorItem armorItem,
final Message removeMessage,
final Message setMessage) {
final Player player = user.getPlayer();
final ArmorItem.Type type = armorItem.getType();
final ArmorItem empty = ArmorItem.empty(type);
if (player == null) {
return empty;
}
final ArmorItem check = user.getPlayerArmor().getItem(type);
if (armorItem.getId().equals(check.getId())) {
this.setItem(user, ArmorItem.empty(type));
messageHandler.sendMessage(
player,
removeMessage
);
return empty;
}
this.setItem(user, armorItem);
messageHandler.sendMessage(
player,
setMessage
);
return armorItem;
}
public void removeAll() {
for (final var user : this.userMap.values()) {
user.despawnAttached();
}
this.userMap.clear();
}
public void cancelTeleportTask() {
this.teleportTask.cancel();
}
}

View File

@@ -0,0 +1,84 @@
package io.github.fisher2911.hmccosmetics.user;
import com.comphenix.protocol.events.PacketContainer;
import io.github.fisher2911.hmccosmetics.config.Settings;
import io.github.fisher2911.hmccosmetics.gui.ArmorItem;
import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor;
import io.github.fisher2911.hmccosmetics.packet.PacketManager;
import net.minecraft.network.protocol.Packet;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import java.util.UUID;
public class Wardrobe extends User {
private final UUID ownerUUID;
private final int entityId;
private boolean active;
private Location currentLocation;
public Wardrobe(
final UUID uuid,
final UUID ownerUUID,
final PlayerArmor playerArmor,
final int armorStandId,
final int entityId,
final boolean active) {
super(uuid, playerArmor, armorStandId);
this.ownerUUID = ownerUUID;
this.entityId = entityId;
this.active = active;
this.wardrobe = this;
}
public void spawnFakePlayer(final Player viewer, final Settings settings) {
this.currentLocation = viewer.getLocation().clone();
this.currentLocation.setPitch(0);
this.currentLocation.setYaw(0);
final PacketContainer playerSpawnPacket = PacketManager.getFakePlayerSpawnPacket(
this.currentLocation,
this.getUuid(),
this.entityId
);
final PacketContainer playerInfoPacket = PacketManager.getFakePlayerInfoPacket(
viewer,
this.getUuid()
);
PacketManager.sendPacket(viewer, playerInfoPacket, playerSpawnPacket);
this.spawnArmorStand(viewer);
this.updateArmorStand(viewer, settings, this.currentLocation);
}
public void despawnFakePlayer(final Player viewer) {
PacketManager.sendPacket(viewer, PacketManager.getRemovePlayerPacket(viewer, this.getUuid(), this.getEntityId()));
this.despawnAttached();
this.active = false;
}
@Override
public int getEntityId() {
return this.entityId;
}
@Override
public boolean hasPermissionToUse(final ArmorItem armorItem) {
return true;
}
public boolean isActive() {
return active;
}
public void setActive(final boolean active) {
this.active = active;
}
@Override
public Player getPlayer() {
return Bukkit.getPlayer(this.ownerUUID);
}
}

View File

@@ -0,0 +1,37 @@
package io.github.fisher2911.hmccosmetics.util;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType;
public class Keys {
static HMCCosmetics plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
public static final NamespacedKey ITEM_KEY = new NamespacedKey(plugin, "cosmetic");
public static final NamespacedKey ARMOR_STAND_KEY = new NamespacedKey(plugin, "armor-stand");
public static void setKey(final ItemStack itemStack) {
final ItemMeta itemMeta = itemStack.getItemMeta();
if (itemMeta == null) {
return;
}
itemMeta.getPersistentDataContainer().set(ITEM_KEY, PersistentDataType.BYTE, (byte) 1);
itemStack.setItemMeta(itemMeta);
}
public static boolean hasKey(final ItemStack itemStack) {
final ItemMeta itemMeta = itemStack.getItemMeta();
if (itemMeta == null) {
return false;
}
return itemMeta.getPersistentDataContainer().has(ITEM_KEY, PersistentDataType.BYTE);
}
}

View File

@@ -0,0 +1,41 @@
package io.github.fisher2911.hmccosmetics.util;
import io.github.fisher2911.hmccosmetics.HMCCosmetics;
import io.github.fisher2911.hmccosmetics.message.Adventure;
import net.kyori.adventure.text.Component;
public class StringUtils {
private static final HMCCosmetics plugin;
static {
plugin = HMCCosmetics.getPlugin(HMCCosmetics.class);
}
/**
* @param parsed message to be parsed
* @return MiniMessage parsed string
*/
public static Component parse(final String parsed) {
return Adventure.MINI_MESSAGE.deserialize(parsed);
}
public static String parseStringToString(final String parsed) {
return Adventure.SERIALIZER.serialize(Adventure.MINI_MESSAGE.deserialize(parsed));
}
public static String formatArmorItemType(String type) {
type = type.toLowerCase();
final String[] parts = type.split(" ");
final String firstPart = parts[0].substring(0, 1).toUpperCase() + parts[0].substring(1);
if (parts.length == 1) {
return firstPart;
}
return firstPart + parts[1].substring(0, 1).toUpperCase() + parts[1].substring(1);
}
}

View File

@@ -0,0 +1,103 @@
package io.github.fisher2911.hmccosmetics.util;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Utils {
/**
* @param original Object to be checked if null
* @param replacement Object returned if original is null
* @return original if not null, otherwise replacement
*/
public static <T> T replaceIfNull(final @Nullable T original, final @NotNull T replacement) {
return replaceIfNull(original, replacement, t -> {
});
}
/**
* @param original Object to be checked if null
* @param replacement Object returned if original is null
* @param consumer accepts the original object, can be used for logging
* @return original if not null, otherwise replacement
*/
public static <T> T replaceIfNull(final @Nullable T original, final T replacement,
final @NotNull Consumer<T> consumer) {
if (original == null) {
consumer.accept(replacement);
return replacement;
}
consumer.accept(original);
return original;
}
/**
* @param t object being checked
* @param consumer accepted if t is not null
* @param <T> type
*/
public static <T> void doIfNotNull(final @Nullable T t, final @NotNull Consumer<T> consumer) {
if (t == null) {
return;
}
consumer.accept(t);
}
/**
* @param t object being checked
* @param function applied if t is not null
* @param <T> type
* @return
*/
public static <T> Optional<T> returnIfNotNull(final @Nullable T t,
final @NotNull Function<T, T> function) {
if (t == null) {
return Optional.empty();
}
return Optional.of(function.apply(t));
}
/**
* @param enumAsString Enum value as a string to be parsed
* @param enumClass enum type enumAsString is to be converted to
* @param defaultEnum default value to be returned
* @return enumAsString as an enum, or default enum if it could not be parsed
*/
public static <E extends Enum<E>> E stringToEnum(final @NotNull String enumAsString,
final @NotNull Class<E> enumClass,
E defaultEnum) {
return stringToEnum(enumAsString, enumClass, defaultEnum, e -> {
});
}
/**
* @param enumAsString Enum value as a string to be parsed
* @param enumClass enum type enumAsString is to be converted to
* @param defaultEnum default value to be returned
* @param consumer accepts the returned enum, can be used for logging
* @return enumAsString as an enum, or default enum if it could not be parsed
*/
public static <E extends Enum<E>> E stringToEnum(final @NotNull String enumAsString,
@NotNull final Class<E> enumClass,
final E defaultEnum,
final @NotNull Consumer<E> consumer) {
try {
final E value = Enum.valueOf(enumClass, enumAsString);
consumer.accept(value);
return value;
} catch (final IllegalArgumentException exception) {
consumer.accept(defaultEnum);
return defaultEnum;
}
}
}

View File

@@ -0,0 +1,81 @@
package io.github.fisher2911.hmccosmetics.util.builder;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.LeatherArmorMeta;
import org.bukkit.inventory.meta.PotionMeta;
public class ColorBuilder extends ItemBuilder {
/**
* @param material ItemStack material
*/
ColorBuilder(final Material material) {
super(material);
}
/**
* @param itemStack ItemStack
*/
ColorBuilder(final ItemStack itemStack) {
super(itemStack);
}
/**
* @param material ItemStack material
* @return this
* @throws IllegalArgumentException thrown if itemStack's type can not change color
*/
public static ColorBuilder from(final Material material) throws IllegalArgumentException {
if (!canBeColored(material)) {
throw new IllegalArgumentException(material.name() + " is not leather armor!");
}
return new ColorBuilder(material);
}
/**
* @param itemStack ItemStack
* @return this
* @throws IllegalArgumentException thrown if itemStack's type can not change color
*/
public static ColorBuilder from(final ItemStack itemStack) throws IllegalArgumentException {
final Material material = itemStack.getType();
if (!canBeColored(itemStack)) {
throw new IllegalArgumentException(material.name() + " is not leather armor!");
}
return new ColorBuilder(itemStack);
}
public static boolean canBeColored(final Material material) {
return canBeColored(new ItemStack(material));
}
public static boolean canBeColored(final ItemStack itemStack) {
final ItemMeta itemMeta = itemStack.getItemMeta();
return (itemMeta instanceof LeatherArmorMeta ||
itemMeta instanceof PotionMeta);
}
/**
* @param color armor color
* @return this
*/
public ColorBuilder color(final Color color) {
if (this.itemMeta instanceof final PotionMeta meta) {
meta.setColor(color);
}
if (this.itemMeta instanceof final LeatherArmorMeta meta) {
meta.setColor(color);
}
return this;
}
}

View File

@@ -0,0 +1,255 @@
package io.github.fisher2911.hmccosmetics.util.builder;
import io.github.fisher2911.hmccosmetics.message.Placeholder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
public class ItemBuilder {
protected Material material;
protected int amount;
protected ItemMeta itemMeta;
/**
* @param material builder material
*/
ItemBuilder(final Material material) {
this.material = material;
this.itemMeta = Bukkit.getItemFactory().getItemMeta(material);
}
/**
* @param itemStack builder ItemStack
*/
ItemBuilder(final ItemStack itemStack) {
this.material = itemStack.getType();
this.itemMeta = itemStack.hasItemMeta() ? itemStack.getItemMeta()
: Bukkit.getItemFactory().getItemMeta(this.material);
}
/**
* @param material builder material
* @return
*/
public static ItemBuilder from(final Material material) {
return new ItemBuilder(material);
}
/**
* @param itemStack builder ItemStack
* @return
*/
public static ItemBuilder from(final ItemStack itemStack) {
return new ItemBuilder(itemStack);
}
/**
* @param amount ItemStack amount
* @return this
*/
public ItemBuilder amount(final int amount) {
this.amount = Math.min(Math.max(1, amount), 64);
return this;
}
/**
* @param name ItemStack name
* @return this
*/
public ItemBuilder name(final String name) {
if (this.itemMeta == null) {
return this;
}
this.itemMeta.setDisplayName(name);
return this;
}
/**
* Sets placeholders to the item's name
*
* @param placeholders placeholders
*/
public ItemBuilder namePlaceholders(final Map<String, String> placeholders) {
if (this.itemMeta == null) {
return this;
}
final String name = Placeholder.
applyPlaceholders(this.itemMeta.getDisplayName(), placeholders);
this.itemMeta.setDisplayName(name);
return this;
}
/**
* @param lore ItemStack lore
* @return this
*/
public ItemBuilder lore(final List<String> lore) {
if (this.itemMeta == null) {
return this;
}
this.itemMeta.setLore(lore);
return this;
}
/**
* Sets placeholders to the item's lore
*
* @param placeholders placeholders
*/
public ItemBuilder lorePlaceholders(final Map<String, String> placeholders) {
if (this.itemMeta == null) {
return this;
}
final List<String> lore = new ArrayList<>();
final List<String> previousLore = this.itemMeta.getLore();
if (previousLore == null) {
return this;
}
for (final String line : previousLore) {
lore.add(Placeholder.applyPlaceholders(
line, placeholders
));
}
this.itemMeta.setLore(lore);
return this;
}
public ItemBuilder papiPlaceholders(final Player player) {
this.lorePapiPlaceholders(player);
this.namePapiPlaceholders(player);
return this;
}
private void lorePapiPlaceholders(final Player player) {
if (this.itemMeta == null) {
return;
}
final List<String> newLore = new ArrayList<>();
final List<String> lore = this.itemMeta.getLore();
if (lore == null) {
return;
}
for (final String line : this.itemMeta.getLore()) {
newLore.add(Placeholder.applyPapiPlaceholders(player, line));
}
this.itemMeta.setLore(newLore);
}
private void namePapiPlaceholders(final Player player) {
if (this.itemMeta == null) {
return;
}
this.itemMeta.setDisplayName(
Placeholder.applyPapiPlaceholders(
player,
this.itemMeta.getDisplayName()
)
);
}
/**
* @param unbreakable whether the ItemStack is unbreakable
* @return this
*/
public ItemBuilder unbreakable(final boolean unbreakable) {
if (this.itemMeta == null) {
return this;
}
this.itemMeta.setUnbreakable(unbreakable);
return this;
}
public ItemBuilder glow(final boolean glow) {
if (this.itemMeta == null) {
return this;
}
if (glow) {
this.itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
this.itemMeta.addEnchant(Enchantment.LUCK, 1, true);
}
return this;
}
/**
* @param enchantments enchants to be added to the ItemStack
* @param ignoreLeveLRestrictions whether to ignore enchantment level restrictions
* @return this
*/
public ItemBuilder enchants(final Map<Enchantment, Integer> enchantments,
boolean ignoreLeveLRestrictions) {
if (this.itemMeta == null) {
return this;
}
enchantments.forEach((enchantment, level) -> this.itemMeta.addEnchant(enchantment, level,
ignoreLeveLRestrictions));
return this;
}
/**
* @param itemFlags ItemStack ItemFlags
* @return this
*/
public ItemBuilder itemFlags(final Set<ItemFlag> itemFlags) {
if (this.itemMeta == null) {
return this;
}
this.itemMeta.addItemFlags(itemFlags.toArray(new ItemFlag[0]));
return this;
}
/**
* @param modelData ItemStack modelData
* @return this
*/
public ItemBuilder modelData(final int modelData) {
if (this.itemMeta == null) {
return this;
}
this.itemMeta.setCustomModelData(modelData);
return this;
}
/**
* @return built ItemStack
*/
public ItemStack build() {
final ItemStack itemStack = new ItemStack(this.material, Math.max(this.amount, 1));
itemStack.setItemMeta(itemMeta);
return itemStack;
}
}

View File

@@ -0,0 +1,93 @@
package io.github.fisher2911.hmccosmetics.util.builder;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import java.lang.reflect.Field;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;
/**
* Some parts taken from https://github.com/TriumphTeam/triumph-gui/blob/master/core/src/main/java/dev/triumphteam/gui/builder/item/SkullBuilder.java
*/
public class SkullBuilder extends ItemBuilder {
private static final Field PROFILE_FIELD;
static {
Field field;
try {
final SkullMeta skullMeta = (SkullMeta) new ItemStack(
Material.PLAYER_HEAD).getItemMeta();
field = skullMeta.getClass().getDeclaredField("profile");
field.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
field = null;
}
PROFILE_FIELD = field;
}
/**
* @param material The material
*/
SkullBuilder(final Material material) {
super(material);
}
/**
* Creates a new SkullBuilder instance
*
* @return this
*/
public static SkullBuilder create() {
return new SkullBuilder(Material.PLAYER_HEAD);
}
/**
* @param player skull owner
* @return this
*/
public SkullBuilder owner(final OfflinePlayer player) {
if (this.itemMeta instanceof final SkullMeta skullMeta) {
skullMeta.setOwningPlayer(player);
this.itemMeta = skullMeta;
}
return this;
}
/**
* @param texture skull texture
* @return this
*/
public SkullBuilder texture(@NotNull final String texture) {
if (PROFILE_FIELD == null) {
return this;
}
final SkullMeta skullMeta = (SkullMeta) this.itemMeta;
final GameProfile profile = new GameProfile(UUID.randomUUID(), null);
profile.getProperties().put("textures", new Property("textures", texture));
try {
PROFILE_FIELD.set(skullMeta, profile);
} catch (IllegalArgumentException | IllegalAccessException ex) {
ex.printStackTrace();
}
this.itemMeta = skullMeta;
return this;
}
}