commit af2189e1b2f28344a21d697ebd92f2a296048d1d Author: HeroBrineGoat <76707404+MasterOfTheFish@users.noreply.github.com> Date: Mon Nov 8 17:00:01 2021 -0500 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8a0a60d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Project exclude paths +/.gradle/ +/build/ +/build/classes/java/main/ \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..659bf431 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 00000000..30bab2ab --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..ba1ec5c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..12511d1b --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..6680db93 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..797acea5 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 00000000..e96534fb --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..2a4ce6ae --- /dev/null +++ b/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'java' + id 'com.github.johnrengelman.shadow' version '6.1.0' +} + +group 'io.github.fisher2911' +version '1.0.0' + +repositories { + mavenCentral() + mavenLocal() + maven { url 'https://papermc.io/repo/repository/maven-public/' } + maven { url = 'https://repo.mattstudios.me/artifactory/public/' } + maven { url = 'https://jitpack.io' } + maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } + maven { url = 'https://repo.leonardobishop.com/releases/' } + maven { url = 'https://jitpack.io' } +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + compileOnly 'io.papermc.paper:paper:1.17.1-R0.1-SNAPSHOT' + compileOnly 'org.jetbrains:annotations:22.0.0' + compileOnly 'net.kyori:adventure-api:4.9.3' + implementation 'net.kyori:adventure-text-minimessage:4.2.0-SNAPSHOT' + implementation 'net.kyori:adventure-platform-bukkit:4.0.0' + implementation 'dev.triumphteam:triumph-gui:3.0.3' + implementation 'me.mattstudios.utils:matt-framework:1.4.6' + implementation 'org.spongepowered:configurate-yaml:4.1.2' +} + +test { + useJUnitPlatform() +} + +shadowJar { + relocate 'dev.triumphteam.gui', 'io.github.fisher2911.hmccosmetics.gui' + relocate 'me.mattstudios.mf', 'io.github.fisher2911.hmccosmetics.mf' + relocate 'net.kyori.adventure.text.minimessage', 'io.github.fisher2911.hmccosmetics.adventure.minimessage' + relocate 'net.kyori.adventure.platform', 'io.github.fisher2911.hmccosmetics.adventure.platform' + relocate 'org.spongepowered.configurate', 'io.github.fisher2911.hmccosmetics.configurate' +} + +shadowJar { + archiveBaseName.set('BackpackCosmetics') + archiveClassifier.set('') + archiveVersion.set('') +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7454180f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..69a97150 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..744e882e --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MSYS* | MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..f7e048dd --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'HMCCosmetics' + diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/HMCCosmetics.java b/src/main/java/io/github/fisher2911/hmccosmetics/HMCCosmetics.java new file mode 100644 index 00000000..11c562a6 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/HMCCosmetics.java @@ -0,0 +1,72 @@ +package io.github.fisher2911.hmccosmetics; + +import io.github.fisher2911.hmccosmetics.command.CosmeticsCommand; +import io.github.fisher2911.hmccosmetics.gui.CosmeticsMenu; +import io.github.fisher2911.hmccosmetics.listener.JoinListener; +import io.github.fisher2911.hmccosmetics.message.MessageHandler; +import io.github.fisher2911.hmccosmetics.message.Messages; +import io.github.fisher2911.hmccosmetics.user.UserManager; +import me.mattstudios.mf.base.CommandManager; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.List; + +public class HMCCosmetics extends JavaPlugin { + + private UserManager userManager; + private MessageHandler messageHandler; + private CosmeticsMenu cosmeticsMenu; + private CommandManager commandManager; + + @Override + public void onEnable() { + this.messageHandler = new MessageHandler(this); + this.userManager = new UserManager(this); + this.cosmeticsMenu = new CosmeticsMenu(this); + this.messageHandler.load(); + this.cosmeticsMenu.load(); + this.registerCommands(); + this.registerListeners(); + + this.userManager.startTeleportTask(); + } + + @Override + public void onDisable() { + this.messageHandler.close(); + this.userManager.cancelTeleportTask(); + this.userManager.removeAll(); + } + + private void registerListeners() { + List.of(new JoinListener(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.register(new CosmeticsCommand(this)); + } + + public MessageHandler getMessageHandler() { + return messageHandler; + } + + public UserManager getUserManager() { + return userManager; + } + + public CosmeticsMenu getCosmeticsMenu() { + return cosmeticsMenu; + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/command/CosmeticsCommand.java b/src/main/java/io/github/fisher2911/hmccosmetics/command/CosmeticsCommand.java new file mode 100644 index 00000000..624d1ba8 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/command/CosmeticsCommand.java @@ -0,0 +1,31 @@ +package io.github.fisher2911.hmccosmetics.command; + +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.gui.CosmeticsMenu; +import io.github.fisher2911.hmccosmetics.message.MessageHandler; +import me.mattstudios.mf.annotations.Command; +import me.mattstudios.mf.annotations.Default; +import me.mattstudios.mf.annotations.Permission; +import me.mattstudios.mf.base.CommandBase; +import org.bukkit.entity.Player; + +@Command("cosmetics") +public class CosmeticsCommand extends CommandBase { + + private final HMCCosmetics plugin; + private final MessageHandler messageHandler; + private final CosmeticsMenu cosmeticsMenu; + + public CosmeticsCommand(final HMCCosmetics plugin) { + this.plugin = plugin; + 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); + } + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/config/GuiSerializer.java b/src/main/java/io/github/fisher2911/hmccosmetics/config/GuiSerializer.java new file mode 100644 index 00000000..d0c9a3fd --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/config/GuiSerializer.java @@ -0,0 +1,69 @@ +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 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.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class GuiSerializer implements TypeSerializer { + + private static final HMCCosmetics plugin; + + static { + plugin = HMCCosmetics.getPlugin(HMCCosmetics.class); + } + + public static final GuiSerializer INSTANCE = new GuiSerializer(); + + private GuiSerializer() {} + + private static final String TITLE = "title"; + private static final String ROWS = "rows"; + private static final String ITEMS = "items"; + + 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 = source.node(ITEMS).childrenMap(); + + final Map 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); + } + + return new CosmeticGui(plugin, titleNode.getString(), rowsNode.getInt(), guiItemMap); + } + + @Override + public void serialize(final Type type, @Nullable final CosmeticGui obj, final ConfigurationNode node) throws SerializationException { + + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/config/ItemSerializer.java b/src/main/java/io/github/fisher2911/hmccosmetics/config/ItemSerializer.java new file mode 100644 index 00000000..2a30f395 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/config/ItemSerializer.java @@ -0,0 +1,205 @@ +package io.github.fisher2911.hmccosmetics.config; + +import dev.triumphteam.gui.guis.GuiItem; +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.gui.ArmorItem; +import io.github.fisher2911.hmccosmetics.util.StringUtils; +import io.github.fisher2911.hmccosmetics.util.Utils; +import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder; +import io.github.fisher2911.hmccosmetics.util.builder.LeatherArmorBuilder; +import io.github.fisher2911.hmccosmetics.util.builder.SkullBuilder; +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.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; + +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; + +public class ItemSerializer implements TypeSerializer { + + public static final ItemSerializer INSTANCE = new ItemSerializer(); + + 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 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 OPEN_MENU = "open-menu"; + private static final String ID = "id"; + + 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 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 openMenuNode = source.node(OPEN_MENU); + final ConfigurationNode idNode = source.node(ID); + + + final Material material = Utils.stringToEnum(Utils.replaceIfNull(materialNode.getString(), ""), + Material.class, Material.AIR); + final int amount = amountNode.getInt(); + final String name = StringUtils.parseStringToString(Utils.replaceIfNull(nameNode.getString(), "")); + + final boolean unbreakable = unbreakableNode.getBoolean(); + final boolean glowing = glowingNode.getBoolean(); + final List lore = Utils.replaceIfNull(loreNode.getList(String.class), new ArrayList()). + stream().map(StringUtils::parseStringToString).collect(Collectors.toList()); + final int modelData = modelDataNode.getInt(); + final Set itemFlags = Utils.replaceIfNull(itemFlagsNode.getList(String.class), new ArrayList()). + 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 Color color = Color.fromBGR(redNode.getInt(), greenNode.getInt(), blueNode.getInt()); + + final Map enchantments = + Utils.replaceIfNull(enchantsNode.getList(String.class), + new ArrayList()). + 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 (material == 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 (LeatherArmorBuilder.isLeatherArmor(material)) { + itemBuilder = LeatherArmorBuilder.from(material); + if (color != null) { + ((LeatherArmorBuilder) itemBuilder).color(color); + } + } else { + itemBuilder = ItemBuilder.from(material); + } + + final ItemStack itemStack = itemBuilder. + amount(amount). + name(name). + unbreakable(unbreakable). + glow(glowing). + lore(lore). + modelData(modelData). + enchants(enchantments, true). + itemFlags(itemFlags). + build(); + + try { + final ArmorItem.Type cosmeticType = ArmorItem.Type.valueOf( + Utils.replaceIfNull( + typeNode.getString(), "" + ).toUpperCase(Locale.ROOT) + ); + + final String permission = permissionNode.getString(); + + return new ArmorItem( + itemStack, + Utils.replaceIfNull(idNode.getString(), ""), + permission, + cosmeticType); + + } catch (final IllegalArgumentException exception) { + final String openMenu = openMenuNode.getString( + Utils.replaceIfNull(OPEN_MENU, "")); + + return dev.triumphteam.gui.builder.item.ItemBuilder.from( + itemStack). + asGuiItem(event -> { + final HMCCosmetics plugin = HMCCosmetics.getPlugin(HMCCosmetics.class); + plugin.getCosmeticsMenu().openMenu(openMenu, event.getWhoClicked()); + }); + } + } + + @Override + public void serialize(final Type type, @Nullable final GuiItem obj, final ConfigurationNode node) throws SerializationException { + + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/gui/ArmorItem.java b/src/main/java/io/github/fisher2911/hmccosmetics/gui/ArmorItem.java new file mode 100644 index 00000000..be36b68b --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/gui/ArmorItem.java @@ -0,0 +1,81 @@ +package io.github.fisher2911.hmccosmetics.gui; + +import dev.triumphteam.gui.components.GuiAction; +import dev.triumphteam.gui.guis.GuiItem; +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 String permission; + private final Type type; + + public ArmorItem( + @NotNull final ItemStack itemStack, + final GuiAction action, + final String id, final String permission, + final Type type) { + super(itemStack, action); + this.id = id; + this.permission = permission; + this.type = type; + } + + public ArmorItem( + @NotNull final ItemStack itemStack, + final String id, + final String permission, + final Type type) { + super(itemStack); + this.id = id; + this.permission = permission; + this.type = type; + } + + public ArmorItem( + @NotNull final Material material, + final String id, + final String permission, + final Type type) { + super(material); + this.id = id; + this.permission = permission; + this.type = type; + } + + public ArmorItem( + @NotNull final Material material, + @Nullable final GuiAction action, + final String id, + final String permission, + final Type type) { + super(material, action); + this.id = id; + this.permission = permission; + this.type = type; + } + + public String getId() { + return id; + } + + public String getPermission() { + return permission; + } + + public Type getType() { + return type; + } + + public enum Type { + + HAT, + + BACKPACK + + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticGui.java b/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticGui.java new file mode 100644 index 00000000..444eb88a --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticGui.java @@ -0,0 +1,157 @@ +package io.github.fisher2911.hmccosmetics.gui; + +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.user.UserManager; +import io.github.fisher2911.hmccosmetics.util.builder.ItemBuilder; +import org.bukkit.Bukkit; +import org.bukkit.entity.HumanEntity; +import org.bukkit.entity.Player; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; + +public class CosmeticGui { + + private final HMCCosmetics plugin; + private final MessageHandler messageHandler; + private final String title; + private final int rows; + private final Map guiItemMap; + private Gui gui; + + public CosmeticGui( + final HMCCosmetics plugin, + final String title, + final int rows, + final Map guiItemMap) { + this.plugin = plugin; + this.messageHandler = this.plugin.getMessageHandler(); + this.title = title; + this.rows = rows; + this.guiItemMap = guiItemMap; + } + + 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 = entry.getValue(); + + if (guiItem instanceof final ArmorItem armorItem) { + + final Map placeholders = new HashMap<>(); + + final PlayerArmor playerArmor = user.getPlayerArmor(); + + final ArmorItem hat = playerArmor.getHat(); + final ArmorItem backpack = playerArmor.getBackpack(); + + final ArmorItem.Type type = armorItem.getType(); + + final String id = switch (type) { + case HAT -> hat.getId(); + case BACKPACK -> backpack.getId(); + }; + + placeholders.put( + Placeholder.ENABLED, + String.valueOf(id.equals(armorItem.getId())). + toLowerCase(Locale.ROOT)); + + placeholders.put( + Placeholder.ALLOWED, + String.valueOf( + player.hasPermission(armorItem.getPermission())). + toUpperCase(Locale.ROOT) + ); + + this.gui.setItem(slot, + new GuiItem( + ItemBuilder.from( + armorItem.getItemStack() + ).namePlaceholders(placeholders). + lorePlaceholders(placeholders). + build(), + event -> { + final String permission = armorItem.getPermission(); + + if (permission != null && + !permission.isBlank() && + !player.hasPermission(armorItem.getPermission())) { + this.messageHandler.sendMessage( + player, + Messages.NO_PERMISSION + ); + return; + } + + this.setUserArmor(player, user, armorItem); + } + ) + ); + + continue; + } + + this.gui.setItem(slot, guiItem); + } + } + + private void setUserArmor( + final HumanEntity player, + final User user, + final ArmorItem armorItem) { + + if (player == null) { + return; + } + + final ArmorItem.Type type = armorItem.getType(); + + switch (type) { + case HAT -> user.setOrUnsetHat(armorItem); + case BACKPACK -> user.setOrUnsetBackpack(armorItem); + } + } + + public void open(final HumanEntity humanEntity) { + final Optional optionalUser = this.plugin.getUserManager().get(humanEntity.getUniqueId()); + + if (optionalUser.isEmpty()) { + return; + } + + final User user = optionalUser.get(); + + this.gui = Gui.gui(). + title(Adventure.MINI_MESSAGE.parse(this.title)). + rows(this.rows). + create(); + + this.gui.setDefaultClickAction(event -> { + event.setCancelled(true); + this.setItems(user); + this.gui.update(); + }); + this.setItems(user); + + this.gui.open(humanEntity); + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticsMenu.java b/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticsMenu.java new file mode 100644 index 00000000..50b77d59 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/gui/CosmeticsMenu.java @@ -0,0 +1,89 @@ +package io.github.fisher2911.hmccosmetics.gui; + +import dev.triumphteam.gui.guis.GuiItem; +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.config.GuiSerializer; +import io.github.fisher2911.hmccosmetics.config.ItemSerializer; +import org.bukkit.entity.HumanEntity; +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; + +public class CosmeticsMenu { + + public static final String MAIN_MENU = "main"; + + private final HMCCosmetics plugin; + + private final Map guiMap = new HashMap<>(); + + public CosmeticsMenu(final HMCCosmetics plugin) { + this.plugin = plugin; + } + + public void openMenu(final String id, final HumanEntity humanEntity) { + final CosmeticGui cosmeticGui = this.guiMap.get(id); + + if (cosmeticGui != null) { + cosmeticGui.open(humanEntity); + } + } + + public void openDefault(final HumanEntity humanEntity) { + this.openMenu(MAIN_MENU, humanEntity); + } + + public void load() { + final File file = Path.of(this.plugin.getDataFolder().getPath(), + "menus").toFile(); + + if (!Path.of(this.plugin.getDataFolder().getPath(), + "menus", + "main").toFile().exists()) { + this.plugin.saveResource( + new File("menus", "main.yml").getPath(), + false + ); + } + + if (!file.exists() || + !file.isDirectory()) { + return; + } + + final File[] files = file.listFiles(); + + if (files == 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(); + + try { + final ConfigurationNode source = loader.load(); + + this.guiMap.put(id, source.get(CosmeticGui.class)); + + } catch (final ConfigurateException exception) { + exception.printStackTrace(); + } + + } + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/inventory/PlayerArmor.java b/src/main/java/io/github/fisher2911/hmccosmetics/inventory/PlayerArmor.java new file mode 100644 index 00000000..0f85a06a --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/inventory/PlayerArmor.java @@ -0,0 +1,48 @@ +package io.github.fisher2911.hmccosmetics.inventory; + +import io.github.fisher2911.hmccosmetics.gui.ArmorItem; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +public class PlayerArmor { + + private ArmorItem hat; + private ArmorItem backpack; + + public PlayerArmor(final ArmorItem hat, final ArmorItem backpack) { + this.hat = hat; + this.backpack = backpack; + } + + public static PlayerArmor empty() { + return new PlayerArmor( + new ArmorItem( + new ItemStack(Material.AIR), + "", + "", + ArmorItem.Type.HAT + ), + new ArmorItem( + new ItemStack(Material.AIR), + "", + "", + ArmorItem.Type.BACKPACK + )); + } + + public ArmorItem getHat() { + return hat; + } + + public void setHat(final ArmorItem hat) { + this.hat = hat; + } + + public ArmorItem getBackpack() { + return backpack; + } + + public void setBackpack(final ArmorItem backpack) { + this.backpack = backpack; + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/listener/JoinListener.java b/src/main/java/io/github/fisher2911/hmccosmetics/listener/JoinListener.java new file mode 100644 index 00000000..f1c81910 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/listener/JoinListener.java @@ -0,0 +1,29 @@ +package io.github.fisher2911.hmccosmetics.listener; + +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.user.UserManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +public class JoinListener implements Listener { + + private final HMCCosmetics plugin; + private final UserManager userManager; + + public JoinListener(final HMCCosmetics plugin) { + this.plugin = plugin; + this.userManager = this.plugin.getUserManager(); + } + + @EventHandler + public void onJoin(final PlayerJoinEvent event) { + this.userManager.add(event.getPlayer()); + } + + @EventHandler + public void onQuit(final PlayerQuitEvent event) { + this.userManager.remove(event.getPlayer().getUniqueId()); + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/Adventure.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/Adventure.java new file mode 100644 index 00000000..df99386e --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/Adventure.java @@ -0,0 +1,24 @@ +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(); + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/ErrorMessages.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/ErrorMessages.java new file mode 100644 index 00000000..c5f06aac --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/ErrorMessages.java @@ -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"; + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/Message.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/Message.java new file mode 100644 index 00000000..6b4efc55 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/Message.java @@ -0,0 +1,57 @@ +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 + + } +} \ No newline at end of file diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/MessageHandler.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/MessageHandler.java new file mode 100644 index 00000000..b635fe46 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/MessageHandler.java @@ -0,0 +1,158 @@ +package io.github.fisher2911.hmccosmetics.message; + +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.util.StringUtils; +import io.github.fisher2911.hmccosmetics.util.Utils; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.title.Title; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +public class MessageHandler { + + private final HMCCosmetics plugin; + private final Logger logger; + private final BukkitAudiences adventure; + private final Map 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 placeholders) { + final String message = StringUtils.applyPlaceholders(this.getMessage(key), placeholders); + final Component component = Adventure.MINI_MESSAGE.parse(message); + this.adventure.sender(sender).sendMessage(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 placeholders) { + final String message = StringUtils.applyPlaceholders(this.getMessage(key), placeholders); + Component component = Adventure.MINI_MESSAGE.parse(message); + this.adventure.player(player).sendActionBar(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 placeholders) { + final String message = StringUtils.applyPlaceholders(this.getMessage(key), placeholders); + Component component = Adventure.MINI_MESSAGE.parse(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)); + } + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/Messages.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/Messages.java new file mode 100644 index 00000000..f5255e62 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/Messages.java @@ -0,0 +1,17 @@ +package io.github.fisher2911.hmccosmetics.message; + +public class Messages { + + public static final Message NO_PERMISSION = + new Message("no-permission", "You do not have permission for this!"); + 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 MUST_BE_PLAYER = + new Message("must-be-player", "You must be a player to do this!"); +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/Permission.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/Permission.java new file mode 100644 index 00000000..498e9722 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/Permission.java @@ -0,0 +1,7 @@ +package io.github.fisher2911.hmccosmetics.message; + +public class Permission { + + public static final String DEFAULT_COMMAND = "hmccosmetics.cmd.default"; + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/message/Placeholder.java b/src/main/java/io/github/fisher2911/hmccosmetics/message/Placeholder.java new file mode 100644 index 00000000..b314a18b --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/message/Placeholder.java @@ -0,0 +1,12 @@ +package io.github.fisher2911.hmccosmetics.message; + +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 ENABLED = "%enabled%"; + public static final String ALLOWED = "%allowed%"; +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/user/User.java b/src/main/java/io/github/fisher2911/hmccosmetics/user/User.java new file mode 100644 index 00000000..2ec08ec4 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/user/User.java @@ -0,0 +1,124 @@ +package io.github.fisher2911.hmccosmetics.user; + +import io.github.fisher2911.hmccosmetics.gui.ArmorItem; +import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.ArmorStand; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; + +public class User { + + private final UUID uuid; + private final PlayerArmor playerArmor; + private ArmorStand attached; + + public User(final UUID uuid, final PlayerArmor playerArmor) { + this.uuid = uuid; + this.playerArmor = playerArmor; + } + + public @Nullable Player getPlayer() { + return Bukkit.getPlayer(this.uuid); + } + + public UUID getUuid() { + return uuid; + } + + public PlayerArmor getPlayerArmor() { + return playerArmor; + } + + public void setBackpack(final ArmorItem backpack) { + this.playerArmor.setBackpack(backpack); + } + + public void setOrUnsetBackpack(final ArmorItem backpack) { + if (backpack.getId().equals(this.playerArmor.getBackpack().getId())) { + this.setBackpack(new ArmorItem( + new ItemStack(Material.AIR), + "", + "", + ArmorItem.Type.BACKPACK + )); + return; + } + + this.setBackpack(backpack); + } + + + public void setHat(final ArmorItem hat) { + this.playerArmor.setHat(hat); + this.getPlayer().getEquipment().setHelmet(this.playerArmor.getHat().getItemStack()); + } + + public void setOrUnsetHat(final ArmorItem hat) { + if (hat.getId().equals(this.playerArmor.getHat().getId())) { + this.setHat(new ArmorItem( + new ItemStack(Material.AIR), + "", + "", + ArmorItem.Type.HAT + )); + return; + } + + this.setHat(hat); + } + + public void detach() { + if (this.attached != null) { + this.attached.remove(); + } + } + + // teleports armor stand to the correct position + // todo change to packets + public void updateArmorStand() { + final ArmorItem backpackArmorItem = this.playerArmor.getBackpack(); + if (backpackArmorItem == null || backpackArmorItem.getItemStack().getType() == Material.AIR) { + return; + } + + final ItemStack backpackItem = backpackArmorItem.getItemStack(); + + + final Player player = this.getPlayer(); + + if (player == null) { + return; + } + + if (this.attached == null) { + this.attached = player.getWorld().spawn(player.getLocation(), + ArmorStand.class, + armorStand -> { + armorStand.setVisible(false); + player.addPassenger(armorStand); + }); + } + + if (!player.getPassengers().contains(this.attached)) { + player.addPassenger(this.attached); + } + + final EntityEquipment equipment = this.attached.getEquipment(); + + if (!backpackItem.equals(equipment.getChestplate())) { + equipment.setChestplate(backpackItem); + } + + this.attached. + setRotation( + player.getLocation().getYaw(), + player.getLocation().getPitch()); + } + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/user/UserManager.java b/src/main/java/io/github/fisher2911/hmccosmetics/user/UserManager.java new file mode 100644 index 00000000..333f252d --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/user/UserManager.java @@ -0,0 +1,67 @@ +package io.github.fisher2911.hmccosmetics.user; + +import io.github.fisher2911.hmccosmetics.HMCCosmetics; +import io.github.fisher2911.hmccosmetics.gui.ArmorItem; +import io.github.fisher2911.hmccosmetics.inventory.PlayerArmor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; + +public class UserManager { + + private final HMCCosmetics plugin; + + private final Map userMap = new HashMap<>(); + + private BukkitTask teleportTask; + + public UserManager(final HMCCosmetics plugin) { + this.plugin = plugin; + } + + public void add(final Player player) { + final UUID uuid = player.getUniqueId(); + this.userMap.put(uuid, new User(uuid, PlayerArmor.empty())); + } + + public Optional get(final UUID uuid) { + return Optional.ofNullable(this.userMap.get(uuid)); + } + + public void remove(final UUID uuid) { + this.get(uuid).ifPresent(User::detach); + this.userMap.remove(uuid); + } + + public void startTeleportTask() { + this.teleportTask = Bukkit.getScheduler().runTaskTimer( + this.plugin, + () -> this.userMap.values().forEach( + User::updateArmorStand + ), + 1, + 1 + ); + } + + public void removeAll() { + for (final var user : this.userMap.values()) { + user.detach(); + } + + this.userMap.clear(); + } + + public void cancelTeleportTask() { + this.teleportTask.cancel(); + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/util/StringUtils.java b/src/main/java/io/github/fisher2911/hmccosmetics/util/StringUtils.java new file mode 100644 index 00000000..8d343297 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/util/StringUtils.java @@ -0,0 +1,38 @@ +package io.github.fisher2911.hmccosmetics.util; + +import io.github.fisher2911.hmccosmetics.message.Adventure; +import net.kyori.adventure.text.Component; + +import java.util.Map; + +public class StringUtils { + + /** + * + * @param message message being translated + * @param placeholders placeholders applied + * @return message with placeholders applied + */ + + public static String applyPlaceholders(String message, final Map placeholders) { + for (final Map.Entry entry : placeholders.entrySet()) { + message = message.replace(entry.getKey(), entry.getValue()); + } + return message; + } + + + /** + * + * @param parsed message to be parsed + * @return MiniMessage parsed string + */ + + public static Component parse(final String parsed) { + return Adventure.MINI_MESSAGE.parse(parsed); + } + + public static String parseStringToString(final String parsed) { + return Adventure.SERIALIZER.serialize(Adventure.MINI_MESSAGE.parse(parsed)); + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/util/Utils.java b/src/main/java/io/github/fisher2911/hmccosmetics/util/Utils.java new file mode 100644 index 00000000..2aa29606 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/util/Utils.java @@ -0,0 +1,105 @@ +package io.github.fisher2911.hmccosmetics.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; + +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 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 replaceIfNull(final @Nullable T original, final T replacement, final @NotNull Consumer 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 type + */ + + public static void doIfNotNull(final @Nullable T t, final @NotNull Consumer consumer) { + if (t == null) { + return; + } + consumer.accept(t); + } + + /** + * + * @param t object being checked + * @param function applied if t is not null + * @param type + * @return + */ + + public static Optional returnIfNotNull(final @Nullable T t, final @NotNull Function 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 stringToEnum(final @NotNull String enumAsString, + final @NotNull Class 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 stringToEnum(final @NotNull String enumAsString, + @NotNull final Class enumClass, + final E defaultEnum, + final @NotNull Consumer consumer) { + try { + final E value = Enum.valueOf(enumClass, enumAsString); + consumer.accept(value); + return value; + } catch (final IllegalArgumentException exception) { + consumer.accept(defaultEnum); + return defaultEnum; + } + } + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/ItemBuilder.java b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/ItemBuilder.java new file mode 100644 index 00000000..c0a27427 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/ItemBuilder.java @@ -0,0 +1,189 @@ +package io.github.fisher2911.hmccosmetics.util.builder; + +import io.github.fisher2911.hmccosmetics.message.Adventure; +import io.github.fisher2911.hmccosmetics.util.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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) { + this.itemMeta.setDisplayName(name); + return this; + } + + /** + * Sets placeholders to the item's name + * + * @param placeholders placeholders + */ + + public ItemBuilder namePlaceholders(final Map placeholders) { + final String name = StringUtils. + applyPlaceholders(this.itemMeta.getDisplayName(), placeholders); + this.itemMeta.displayName( + Adventure.MINI_MESSAGE.parse( + name)); + return this; + } + + /** + * @param lore ItemStack lore + * @return this + */ + + public ItemBuilder lore(final List lore) { + this.itemMeta.setLore(lore); + return this; + } + + /** + * Sets placeholders to the item's lore + * + * @param placeholders placeholders + */ + + + public ItemBuilder lorePlaceholders(final Map placeholders) { + final List lore = new ArrayList<>(); + + final List previousLore = this.itemMeta.getLore(); + + if (previousLore == null) { + return this; + } + + for (final String line : previousLore) { + lore.add(StringUtils.applyPlaceholders( + line, placeholders + )); + } + + this.itemMeta.setLore(lore); + return this; + } + + /** + * @param unbreakable whether the ItemStack is unbreakable + * @return this + */ + + public ItemBuilder unbreakable(final boolean unbreakable) { + this.itemMeta.setUnbreakable(unbreakable); + return this; + } + + public ItemBuilder glow(final boolean glow) { + 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 enchantments, boolean ignoreLeveLRestrictions) { + enchantments.forEach((enchantment, level) -> this.itemMeta.addEnchant(enchantment, level, ignoreLeveLRestrictions)); + return this; + } + + /** + * @param itemFlags ItemStack ItemFlags + * @return this + */ + + public ItemBuilder itemFlags(final Set itemFlags) { + this.itemMeta.addItemFlags(itemFlags.toArray(new ItemFlag[0])); + return this; + } + + /** + * @param modelData ItemStack modelData + * @return this + */ + + public ItemBuilder modelData(final int modelData) { + 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; + } + +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/LeatherArmorBuilder.java b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/LeatherArmorBuilder.java new file mode 100644 index 00000000..62021832 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/LeatherArmorBuilder.java @@ -0,0 +1,86 @@ +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.LeatherArmorMeta; + +import java.util.EnumSet; +import java.util.Set; + +public class LeatherArmorBuilder extends ItemBuilder{ + + private static final Set VALID_ARMOR = EnumSet.of(Material.LEATHER_BOOTS, + Material.LEATHER_LEGGINGS, Material.LEATHER_CHESTPLATE, Material.LEATHER_HELMET); + + /** + * + * @param material ItemStack material + */ + + LeatherArmorBuilder(final Material material) { + super(material); + } + + /** + * + * @param itemStack ItemStack + */ + + LeatherArmorBuilder(final ItemStack itemStack) { + super(itemStack); + } + + /** + * + * @param material ItemStack material + * @return this + * @throws IllegalArgumentException thrown if material is not leather armor + */ + + public static LeatherArmorBuilder from(final Material material) throws IllegalArgumentException { + if (!VALID_ARMOR.contains(material)) { + throw new IllegalArgumentException(material.name() + " is not leather armor!"); + } + return new LeatherArmorBuilder(material); + } + + /** + * + * @param itemStack ItemStack + * @return this + * @throws IllegalArgumentException thrown if itemStack's type is not leather armor + */ + + public static LeatherArmorBuilder from(final ItemStack itemStack) throws IllegalArgumentException { + final Material material = itemStack.getType(); + if (!VALID_ARMOR.contains(material)) { + throw new IllegalArgumentException(material.name() + " is not leather armor!"); + } + return new LeatherArmorBuilder(itemStack); + } + + /** + * + * @param color armor color + * @return this + */ + + public LeatherArmorBuilder color(final Color color) { + if (itemMeta instanceof final LeatherArmorMeta meta) { + meta.setColor(color); + this.itemMeta = meta; + } + return this; + } + + /** + * + * @param material checked material + * @return true if is leather armor, else false + */ + + public static boolean isLeatherArmor(final Material material) { + return VALID_ARMOR.contains(material); + } +} diff --git a/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/SkullBuilder.java b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/SkullBuilder.java new file mode 100644 index 00000000..1745e346 --- /dev/null +++ b/src/main/java/io/github/fisher2911/hmccosmetics/util/builder/SkullBuilder.java @@ -0,0 +1,95 @@ +package io.github.fisher2911.hmccosmetics.util.builder; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Field; +import java.util.UUID; + +/** + * 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; + } + +} diff --git a/src/main/resources/menus/main.yml b/src/main/resources/menus/main.yml new file mode 100644 index 00000000..5faddfba --- /dev/null +++ b/src/main/resources/menus/main.yml @@ -0,0 +1,47 @@ +title: "Main" +rows: 3 +items: + 10: + material: NETHERITE_CHESTPLATE + name: "Backpack" + lore: + - "" + - "Enabled: %enabled%" + - "Allowed: %allowed%" + amount: 1 + type: BACKPACK + permission: "" + id: netherite_backpack + 11: + material: NETHERITE_HELMET + name: "Hat" + lore: + - "" + - "Enabled: %enabled%" + - "Allowed: %allowed%" + amount: 1 + type: HAT + permission: "" + id: netherite_hat + 12: + material: DIAMOND_CHESTPLATE + name: "Backpack" + lore: + - "" + - "Enabled: %enabled%" + - "Allowed: %allowed%" + amount: 1 + type: BACKPACK + permission: "" + id: diamond_backpack + 13: + material: DIAMOND_HELMET + name: "Hat " + lore: + - "" + - "Enabled: %enabled%" + - "Allowed: %allowed%" + amount: 1 + type: HAT + permission: "" + id: diamond_hat diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 00000000..d8f6fa31 --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,6 @@ +no-permission: "No Permission!" +set-hat: "Set Hat!" +removed-hat: "Removed Hat" +set-backpack: "Set Backpack!" +removed-backpack: "Removed Backpack" +must-be-player: "You must be a player to do this!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 00000000..3b181880 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,8 @@ +name: HMCCosmetics +main: io.github.fisher2911.hmccosmetics.HMCCosmetics +version: 1.0.0 +api-version: 1.17 +permissions: + hmccosmetics.cmd.default: + default: op + description: Permission to execute the default command \ No newline at end of file