diff --git a/.gitignore b/.gitignore index a5b1113..ec61117 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ gradle-app.setting .project # JDT-specific (Eclipse Java Development Tools) .classpath + +run +.idea \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7986f4e --- /dev/null +++ b/build.gradle @@ -0,0 +1,84 @@ +plugins { + id 'java' + id("com.github.johnrengelman.shadow") version "7.0.0" + id("xyz.jpenilla.run-paper") version "2.1.0" +} + +group = 'me.redtea' +version = '1.0.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { + name = "spigotmc-repo" + url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/" + } + maven { + name = "sonatype" + url = "https://oss.sonatype.org/content/groups/public/" + } + maven { + name = "CodeMC" + url = uri("https://repo.codemc.io/repository/maven-public/") + } + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } +} + +dependencies { + implementation 'org.jetbrains:annotations:24.0.0' + compileOnly 'org.projectlombok:lombok:1.18.26' + annotationProcessor 'org.projectlombok:lombok:1.18.26' + compileOnly "com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT" + implementation "me.mattstudios.utils:matt-framework:1.4" + implementation 'de.tr7zw:item-nbt-api:2.11.3' + implementation "net.kyori:adventure-api:4.14.0" + implementation "net.kyori:adventure-platform-bukkit:4.3.0" + implementation files("libs/MiniMessages.jar") + compileOnly files("libs/nodropxforgebridge-1.1.jar") + implementation "dev.triumphteam:triumph-gui:3.1.2" +} + +def targetJavaVersion = 8 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release = targetJavaVersion + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} + + +shadowJar { + relocate 'de.tr7zw.changeme.nbtapi', 'me.redtea.nodropx.libs.tr7zw.nbt' + relocate 'de.tr7zw.annotations', 'me.redtea.nodropx.libs.tr7zw.annotations' + relocate 'me.mattstudios.mf', 'me.redtea.nodropx.libs.commands' + relocate 'dev.triumphteam', 'me.redtea.nodropx.libs.gui' + relocate 'org.checkerframework', 'me.redtea.nodropx.libs.message.oskarsmc.libs.checker' + relocate 'org.intellij', 'me.redtea.nodropx.libs.intellij' + relocate 'org.jetbrains', 'me.redtea.nodropx.libs.jetbrains'} + + +tasks { + runServer { + minecraftVersion("1.16.5") + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c1962a7 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 0000000..37aef8d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 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% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/MiniMessages.jar b/libs/MiniMessages.jar new file mode 100644 index 0000000..7bdeb9e Binary files /dev/null and b/libs/MiniMessages.jar differ diff --git a/libs/nodropxforgebridge-1.1.jar b/libs/nodropxforgebridge-1.1.jar new file mode 100644 index 0000000..c7ed856 Binary files /dev/null and b/libs/nodropxforgebridge-1.1.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..6be36e9 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'NoDropX' +include 'api' +include 'bukkit' + diff --git a/src/main/java/me/redtea/nodropx/NoDropX.java b/src/main/java/me/redtea/nodropx/NoDropX.java new file mode 100644 index 0000000..4c13f7e --- /dev/null +++ b/src/main/java/me/redtea/nodropx/NoDropX.java @@ -0,0 +1,176 @@ +package me.redtea.nodropx; + +import lombok.Getter; +import me.mattstudios.mf.base.CommandManager; +import me.redtea.nodropx.api.facade.NoDropAPI; +import me.redtea.nodropx.api.facade.impl.NoDropXFacade; +import me.redtea.nodropx.command.NoDropCommand; +import me.redtea.nodropx.factory.item.NoDropItemFactory; +import me.redtea.nodropx.factory.item.impl.NoDropItemFactoryImpl; +import me.redtea.nodropx.factory.text.TextContext; +import me.redtea.nodropx.factory.text.TextServicesFactory; +import me.redtea.nodropx.factory.text.impl.TextServicesFactoryImpl; +import me.redtea.nodropx.gui.facade.GuiFacade; +import me.redtea.nodropx.gui.facade.impl.GuiFacadeImpl; +import me.redtea.nodropx.gui.pages.StorageGui; +import me.redtea.nodropx.libs.bstats.Metrics; +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; +import me.redtea.nodropx.libs.carcadex.repo.Repo; +import me.redtea.nodropx.libs.carcadex.repo.impl.yaml.YamlRepo; +import me.redtea.nodropx.libs.message.container.Messages; +import me.redtea.nodropx.listener.InventoryHandler; +import me.redtea.nodropx.listener.PlayerHandler; +import me.redtea.nodropx.model.cosmetic.impl.DisplayNameCosmetic; +import me.redtea.nodropx.schema.ItemStackListSchema; +import me.redtea.nodropx.service.cosmetic.CosmeticService; +import me.redtea.nodropx.service.cosmetic.impl.CosmeticServiceImpl; +import me.redtea.nodropx.service.event.EventService; +import me.redtea.nodropx.service.event.impl.EventServiceImpl; +import me.redtea.nodropx.service.nbt.NBTService; +import me.redtea.nodropx.service.nbt.impl.Tr7zwNBTServiceImpl; +import me.redtea.nodropx.service.nodrop.NoDropService; +import me.redtea.nodropx.service.nodrop.impl.NoDropServiceImpl; +import me.redtea.nodropx.service.respawn.RespawnService; +import me.redtea.nodropx.service.respawn.impl.RespawnServiceImpl; +import me.redtea.nodropx.service.storage.StorageService; +import me.redtea.nodropx.service.storage.impl.StorageServiceImpl; +import me.redtea.nodropxforgebridge.ApiProvider; +import me.redtea.nodropxforgebridge.Nodropxforgebridge; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.ServicePriority; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +public final class NoDropX extends JavaPlugin { + private static NoDropAPI noDropAPIInstance; + @Getter + private static NoDropX instance; + + private Messages messages; + private CosmeticService cosmeticService; + @Getter + private boolean supportHEX; + @Getter + private boolean supportKyoriLore; + private MutableRepo> pagesRepo; + private Repo> capacity; + + + @Override + public void onEnable() { + instance = this; + saveDefaultConfig(); + + TextServicesFactory textServicesFactory = new TextServicesFactoryImpl(); + TextContext textContext = textServicesFactory.create(this); + supportHEX = textContext.isSupportHex(); + supportKyoriLore = textContext.isSupportKyori(); + messages = supportKyoriLore ? Messages.of(textContext.getMessages()) : Messages.legacy(textContext.getMessages()); + + cosmeticService = new CosmeticServiceImpl(); + cosmeticService.add(new DisplayNameCosmetic()); + cosmeticService.add(textContext.getLoreStrategy()); + cosmeticService.load(YamlConfiguration.loadConfiguration(textContext.getCosmetics())); + + capacity = new YamlRepo<>( + new File(getDataFolder(), "capacity.yml").toPath(), + this, + conf -> conf.getKeys(false).stream().collect(Collectors.toMap( + value -> value, + conf::getIntegerList)) + ); + + NBTService nbtService = new Tr7zwNBTServiceImpl(); + NoDropService noDropService = new NoDropServiceImpl(nbtService, cosmeticService); + NoDropItemFactory noDropItemFactory = new NoDropItemFactoryImpl(noDropService); + EventService eventService = new EventServiceImpl(noDropService); + RespawnService respawnService = new RespawnServiceImpl(noDropService, capacity); + pagesRepo = Repo.>builder() + .autoSave(1000 * 60 * 15L) + .schema(new ItemStackListSchema(new File(getDataFolder(), "storage"))) + .plugin(this) + .build(); + + + GuiFacade guiFacade = new GuiFacadeImpl( + new StorageGui(messages, pagesRepo, noDropService, this) + ); + StorageService storageService = new StorageServiceImpl(guiFacade, pagesRepo, noDropService); + + NoDropAPI noDropAPI = new NoDropXFacade(noDropService, noDropItemFactory, storageService, capacity); + noDropAPIInstance = noDropAPI; + + + Bukkit.getPluginManager().registerEvents( + new InventoryHandler(eventService), + this + ); + Bukkit.getPluginManager().registerEvents( + new PlayerHandler(respawnService, eventService, guiFacade), + this + ); + + Bukkit.getServicesManager().register(NoDropAPI.class, + noDropAPI, this, ServicePriority.Normal); + + CommandManager commandManager = new CommandManager(this); + commandManager.getMessageHandler().register("cmd.no.permission", sender -> messages.get("noPerms").send(sender)); + commandManager.getMessageHandler().register("cmd.no.console", sender -> messages.get("noConsole").send(sender)); + commandManager.getMessageHandler().register("cmd.no.exists", sender -> messages.get("noExists").send(sender)); + commandManager.getMessageHandler().register("cmd.wrong.usage", sender -> messages.get("usage").send(sender)); + commandManager.getCompletionHandler().register("#materials", input -> Arrays.stream(Material.values()).map(Enum::name).collect(Collectors.toList())); + commandManager.register(new NoDropCommand(messages, noDropAPI, guiFacade, this)); + bStatsInit(); + printCredits(); + + mod(); + } + + //todo remove + private void mod() { + Nodropxforgebridge.noDropAPI = s -> getAPI().getCapacitySlots(s); + } + + + private void bStatsInit() { + int pluginId = 19090; + Metrics metrics = new Metrics(this, pluginId); + } + + + private void printCredits() { + Bukkit.getLogger().info(ChatColor.GREEN + "************************************************************"); + Bukkit.getLogger().info(ChatColor.GREEN + "* " + ChatColor.YELLOW + "NoDropX version " + ChatColor.GOLD + getDescription().getVersion() + ChatColor.GRAY + " (uses CarcadeX v. 1.0.7)"); + Bukkit.getLogger().info(ChatColor.GREEN + "* " + ChatColor.YELLOW + "By " + ChatColor.RED + "itzRedTea"); + Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "Detected bukkit version " + ChatColor.GOLD + Bukkit.getBukkitVersion()); + Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "support HEX colors " + (supportHEX ? ChatColor.DARK_GREEN + "YES" : ChatColor.DARK_RED + "NO")); + Bukkit.getLogger().info(ChatColor.GREEN + "* "+ ChatColor.YELLOW + "support Kyori as lore " + (supportKyoriLore ? ChatColor.DARK_GREEN + "YES" : ChatColor.DARK_RED + "NO")); + Bukkit.getLogger().info(ChatColor.GREEN + "************************************************************"); + } + + public void onReload() { + pagesRepo.saveAll(); + capacity.reload(); + messages.reload(YamlConfiguration.loadConfiguration(new File(getDataFolder(), "messages.yml"))); + cosmeticService.load(YamlConfiguration.loadConfiguration(new File(getDataFolder(), "cosmetics.yml"))); + } + + @Override + public void onDisable() { + pagesRepo.saveAll(); + } + + public static NoDropAPI getAPI() { + return noDropAPIInstance; + } +} diff --git a/src/main/java/me/redtea/nodropx/api/event/ChangeNoDropEvent.java b/src/main/java/me/redtea/nodropx/api/event/ChangeNoDropEvent.java new file mode 100644 index 0000000..c5c45bc --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/event/ChangeNoDropEvent.java @@ -0,0 +1,51 @@ +package me.redtea.nodropx.api.event; + +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when item changes no drop status + */ +public class ChangeNoDropEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + @Getter + private final boolean newValue; + + @Getter + @NotNull + private final ItemStack item; + + private boolean isCancelled; + + public ChangeNoDropEvent(@NotNull Player who, boolean newValue, @NotNull ItemStack item) { + super(who); + this.newValue = newValue; + this.item = item; + } + + @Override + public boolean isCancelled() { + return isCancelled ; + } + + @Override + public void setCancelled(boolean cancel) { + isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/me/redtea/nodropx/api/event/NoDropItemDropOnDeathEvent.java b/src/main/java/me/redtea/nodropx/api/event/NoDropItemDropOnDeathEvent.java new file mode 100644 index 0000000..4111312 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/event/NoDropItemDropOnDeathEvent.java @@ -0,0 +1,39 @@ +package me.redtea.nodropx.api.event; + +import lombok.Getter; +import lombok.Setter; +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when player that has no drop item dead + */ +public class NoDropItemDropOnDeathEvent extends PlayerEvent { + private static final HandlerList handlers = new HandlerList(); + + @Getter + @NotNull + private final ItemStack item; + + @Getter + @Setter + private boolean willDropped = false; + + public NoDropItemDropOnDeathEvent(Player who, @NotNull ItemStack item) { + super(who); + this.item = item; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + +} diff --git a/src/main/java/me/redtea/nodropx/api/event/NoDropItemThrownEvent.java b/src/main/java/me/redtea/nodropx/api/event/NoDropItemThrownEvent.java new file mode 100644 index 0000000..b2ae71a --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/event/NoDropItemThrownEvent.java @@ -0,0 +1,55 @@ +package me.redtea.nodropx.api.event; + +import lombok.Getter; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.HandlerList; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * This event is called when no drop item is moved from the player's inventory + */ +public class NoDropItemThrownEvent extends PlayerEvent implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + @Getter + @NotNull + private final Reason reason; + + @Getter + @NotNull + private final ItemStack item; + + private boolean isCancelled; + + public NoDropItemThrownEvent(Player who, @NotNull Reason reason, @NotNull ItemStack item) { + super(who); + this.reason = reason; + this.item = item; + } + + @Override + public boolean isCancelled() { + return isCancelled ; + } + + @Override + public void setCancelled(boolean cancel) { + isCancelled = cancel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + public enum Reason { + DROP_BY_PLAYER, MOVING_TO_OTHER_INVENTORY + } +} diff --git a/src/main/java/me/redtea/nodropx/api/facade/NoDropAPI.java b/src/main/java/me/redtea/nodropx/api/facade/NoDropAPI.java new file mode 100644 index 0000000..276ceae --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/facade/NoDropAPI.java @@ -0,0 +1,130 @@ +package me.redtea.nodropx.api.facade; + +import me.redtea.nodropx.api.facade.manipulator.StorageManipulator; +import me.redtea.nodropx.util.MaterialUtils; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.UUID; + +/** + * Facade for all plugin functions + */ +public interface NoDropAPI { + /** + * Set no drop to item + * @param item the item who's no drop property are changing + * @param isNoDrop will the item can drop from inventory on death? + * @author redtea + * @since 1.0.0 + */ + void setNoDrop(@NotNull ItemStack item, boolean isNoDrop); + + /** + * Check if item is no drop item + * @param item the item being checked + * @return true if item can not drop from inventory on death + * @author redtea + * @since 1.0.0 + */ + boolean isNoDrop(@NotNull ItemStack item); + + /** + * Generate new no drop item by material and amount + * @param material material of item + * @param amount count of item + * @return no drop item + * @author redtea + * @since 1.0.0 + */ + ItemStack getNoDrop(@NotNull Material material, int amount); + + + /** + * Gives the entity no drop item + * @param entity who will get no drop item + * @param material type of item + * @param amount amount of item + * @author redtea + * @since 1.0.0 + */ + void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material, int amount); + + /** + * returns an object for accessing the storage of a player with UUID playerUniqueId + * @param playerUniqueId UUID of player + * @return object for accessing the storage + * @author redtea + * @since 1.0.0 + */ + @NotNull StorageManipulator getStorageManipulator(@NotNull UUID playerUniqueId); + + /** + * returns the slot numbers that an no drop item with this material saves at death + * @param material material to check + * @return numbers of saved slots + * @author redtea + * @since 1.0.0 + */ + @NotNull Collection getCapacitySlots(Material material); + + /** + * @see NoDropAPI#getCapacitySlots(Material) + */ + default @NotNull Collection getCapacitySlots(String material) { + return getCapacitySlots(MaterialUtils.getMaterialFromString(material)); + } + + /** + * @see NoDropAPI#getStorageManipulator(UUID) + */ + default @NotNull StorageManipulator getStorageManipulator(OfflinePlayer offlinePlayer) { + return getStorageManipulator(offlinePlayer.getUniqueId()); + } + + /** + * @see NoDropAPI#giveNoDrop(HumanEntity, Material, int) + */ + default void giveNoDrop(@NotNull HumanEntity entity, @NotNull String material, int amount) { + giveNoDrop(entity, MaterialUtils.getMaterialFromString(material), amount); + } + + /** + * @see NoDropAPI#giveNoDrop(HumanEntity, Material, int) + */ + default void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material) { + giveNoDrop(entity, material, 1); + } + + /** + * @see NoDropAPI#giveNoDrop(HumanEntity, Material, int) + */ + default void giveNoDrop(@NotNull HumanEntity entity, @NotNull String material) { + giveNoDrop(entity, material, 1); + } + + /** + * @see NoDropAPI#getNoDrop(Material, int) + */ + default @NotNull ItemStack getNoDrop(@NotNull String material, int amount) { + return getNoDrop(MaterialUtils.getMaterialFromString(material), amount); + } + + /** + * @see NoDropAPI#getNoDrop(Material, int) + */ + default @NotNull ItemStack getNoDrop(@NotNull Material material) { + return getNoDrop(material, 1); + } + + /** + * @see NoDropAPI#getNoDrop(Material, int) + */ + default @NotNull ItemStack getNoDrop(@NotNull String material) { + return getNoDrop(material, 1); + } +} diff --git a/src/main/java/me/redtea/nodropx/api/facade/impl/NoDropXFacade.java b/src/main/java/me/redtea/nodropx/api/facade/impl/NoDropXFacade.java new file mode 100644 index 0000000..40811bc --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/facade/impl/NoDropXFacade.java @@ -0,0 +1,73 @@ +package me.redtea.nodropx.api.facade.impl; + +import me.redtea.nodropx.api.facade.NoDropAPI; +import me.redtea.nodropx.api.facade.manipulator.StorageManipulator; +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.factory.item.NoDropItemFactory; +import me.redtea.nodropx.libs.carcadex.repo.Repo; +import me.redtea.nodropx.service.nodrop.NoDropService; +import me.redtea.nodropx.service.storage.StorageService; +import org.bukkit.Material; +import org.bukkit.entity.HumanEntity; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +public class NoDropXFacade implements NoDropAPI { + + private final NoDropService noDropService; + private final NoDropItemFactory itemFactory; + private final StorageService storageService; + private final Repo> capacity; + + @Override + public void setNoDrop(@NotNull ItemStack item, boolean isNoDrop) { + noDropService.setNoDrop(item, isNoDrop); + } + + @Override + public boolean isNoDrop(@NotNull ItemStack item) { + return noDropService.isNoDrop(item); + } + + @Override + @NotNull + public ItemStack getNoDrop(@NotNull Material material, int amount) { + return itemFactory.createNoDrop(material, amount); + } + + @Override + public void giveNoDrop(@NotNull HumanEntity entity, @NotNull Material material, int amount) { + entity.getInventory().addItem(getNoDrop(material, amount)); + } + + @Override + public @NotNull StorageManipulator getStorageManipulator(@NotNull UUID playerUniqueId) { + return new StorageManipulator() { + @Override + public void add(@NotNull ItemStack item) { + storageService.addItem(playerUniqueId, item); + } + + @Override + public void remove(@NotNull ItemStack item) { + storageService.removeItem(playerUniqueId, item); + } + + @Override + public @NotNull List all() { + return storageService.getItems(playerUniqueId); + } + }; + } + + @Override + public @NotNull Collection getCapacitySlots(Material material) { + return capacity.get(material.name()).orElse(new ArrayList<>()); + } +} diff --git a/src/main/java/me/redtea/nodropx/api/facade/manipulator/StorageManipulator.java b/src/main/java/me/redtea/nodropx/api/facade/manipulator/StorageManipulator.java new file mode 100644 index 0000000..408a73e --- /dev/null +++ b/src/main/java/me/redtea/nodropx/api/facade/manipulator/StorageManipulator.java @@ -0,0 +1,30 @@ +package me.redtea.nodropx.api.facade.manipulator; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public interface StorageManipulator { + /** + * Adds no drop item to player storage + * @param item - item that will add + * @author redtea + * @since 1.0.0 + */ + void add(@NotNull ItemStack item); + /** + * Remove no drop item to player storage + * @param item - item that will remove + * @author redtea + * @since 1.0.0 + */ + void remove(@NotNull ItemStack item); + + /** + * @return items in storage + * @author redtea + * @since 1.0.0 + */ + @NotNull List all(); +} diff --git a/src/main/java/me/redtea/nodropx/command/NoDropCommand.java b/src/main/java/me/redtea/nodropx/command/NoDropCommand.java new file mode 100644 index 0000000..2ccf562 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/command/NoDropCommand.java @@ -0,0 +1,66 @@ +package me.redtea.nodropx.command; + +import lombok.RequiredArgsConstructor; +import me.mattstudios.mf.annotations.*; +import me.mattstudios.mf.base.CommandBase; +import me.redtea.nodropx.NoDropX; +import me.redtea.nodropx.api.facade.NoDropAPI; +import me.redtea.nodropx.gui.facade.GuiFacade; +import me.redtea.nodropx.libs.message.container.Messages; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Date; + +/** + * Main command of plugin + *

/nodrop - usage

+ *

/nodrop give [Player] [Material] [Amount (optional)]

+ */ +@Command("nodrop") +@RequiredArgsConstructor +public class NoDropCommand extends CommandBase { + private final Messages messages; + private final NoDropAPI noDropAPI; + private final GuiFacade guiFacade; + private final NoDropX plugin; + + @Default + public void onDefault(final CommandSender sender) { + messages.get("usage").send(sender); + } + + @SubCommand("give") + @Permission("nodropx.give") + @Completion({"#players", "#materials", "#range:1-64"}) + public void onGive(final CommandSender sender, + final Player player, + final String material, + @Optional final Integer amount) { + try { + noDropAPI.giveNoDrop(player, material, amount == null ? 1 : amount); + messages.get("give.success") + .replaceAll("%item%", material) + .replaceAll("%player%", player.getName()) + .replaceAll("%amount%", amount == null ? "0" : amount.toString()) + .send(sender); + } catch (Throwable e) { + messages.get("give.fail").send(sender); + } + + } + + @SubCommand("reload") + @Permission("nodropx.reload") + public void onReload(final CommandSender sender) { + long time = new Date().getTime(); + plugin.onReload(); + messages.get("reload").replaceAll("%time%", String.valueOf(new Date().getTime() - time)).send(sender); + } + + @SubCommand("gui") + @Permission("nodropx.gui") + public void onGui(final Player sender) { + guiFacade.openNoDropStorage(sender); + } +} diff --git a/src/main/java/me/redtea/nodropx/factory/item/NoDropItemFactory.java b/src/main/java/me/redtea/nodropx/factory/item/NoDropItemFactory.java new file mode 100644 index 0000000..07b37be --- /dev/null +++ b/src/main/java/me/redtea/nodropx/factory/item/NoDropItemFactory.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.factory.item; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public interface NoDropItemFactory { + ItemStack createNoDrop(@NotNull Material material, int amount); +} diff --git a/src/main/java/me/redtea/nodropx/factory/item/impl/NoDropItemFactoryImpl.java b/src/main/java/me/redtea/nodropx/factory/item/impl/NoDropItemFactoryImpl.java new file mode 100644 index 0000000..5f54e10 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/factory/item/impl/NoDropItemFactoryImpl.java @@ -0,0 +1,21 @@ +package me.redtea.nodropx.factory.item.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.factory.item.NoDropItemFactory; +import me.redtea.nodropx.service.nodrop.NoDropService; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +@RequiredArgsConstructor +public class NoDropItemFactoryImpl implements NoDropItemFactory { + private final NoDropService noDropService; + + @Override + @NotNull + public ItemStack createNoDrop(@NotNull Material material, int amount) { + ItemStack result = new ItemStack(material, amount); + noDropService.setNoDrop(result, true); + return result; + } +} diff --git a/src/main/java/me/redtea/nodropx/factory/text/TextContext.java b/src/main/java/me/redtea/nodropx/factory/text/TextContext.java new file mode 100644 index 0000000..7b35c01 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/factory/text/TextContext.java @@ -0,0 +1,15 @@ +package me.redtea.nodropx.factory.text; + +import lombok.Data; +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; + +import java.io.File; + +@Data +public class TextContext { + private final CosmeticStrategy loreStrategy; + private final File messages; + private final File cosmetics; + private final boolean supportHex; + private final boolean supportKyori; +} \ No newline at end of file diff --git a/src/main/java/me/redtea/nodropx/factory/text/TextServicesFactory.java b/src/main/java/me/redtea/nodropx/factory/text/TextServicesFactory.java new file mode 100644 index 0000000..47c5531 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/factory/text/TextServicesFactory.java @@ -0,0 +1,7 @@ +package me.redtea.nodropx.factory.text; + +import me.redtea.nodropx.NoDropX; + +public interface TextServicesFactory { + TextContext create(NoDropX plugin); +} diff --git a/src/main/java/me/redtea/nodropx/factory/text/impl/TextServicesFactoryImpl.java b/src/main/java/me/redtea/nodropx/factory/text/impl/TextServicesFactoryImpl.java new file mode 100644 index 0000000..037f090 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/factory/text/impl/TextServicesFactoryImpl.java @@ -0,0 +1,76 @@ +package me.redtea.nodropx.factory.text.impl; + +import lombok.SneakyThrows; +import me.redtea.nodropx.NoDropX; +import me.redtea.nodropx.factory.text.TextContext; +import me.redtea.nodropx.factory.text.TextServicesFactory; +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import me.redtea.nodropx.model.cosmetic.impl.LoreKyoriCosmetic; +import me.redtea.nodropx.model.cosmetic.impl.LoreLegacyCosmetic; +import me.redtea.nodropx.model.cosmetic.impl.LoreMD5Cosmetics; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; + +public class TextServicesFactoryImpl implements TextServicesFactory { + private CosmeticStrategy cosmeticStrategy; + private boolean isSupportKyoriLore; + private boolean isSupportHEX = Integer.parseInt(Bukkit.getVersion().split("\\.")[1]) >= 16; + + + public TextContext create(NoDropX plugin) { + + try { + Class clazz = Class.forName("com.destroystokyo.paper.utils.PaperPluginLogger"); + isSupportKyoriLore = isSupportHEX; + } catch (ClassNotFoundException e) { + isSupportKyoriLore = false; + } + + if(plugin.getConfig().getBoolean("forceUseLegacy")) { + isSupportHEX = false; + isSupportKyoriLore = false; + } else if(plugin.getConfig().getBoolean("forceUseMD5Colors")) { + isSupportKyoriLore = false; + } + + + String folder = "legacy"; + if(isSupportKyoriLore) folder = "mm"; + else if(isSupportHEX) folder = "md5"; + + File messages = loadResource(plugin, folder, "messages.yml"); + File cosmetics = loadResource(plugin, folder, "cosmetics.yml"); + + if(!isSupportHEX) { + plugin.getLogger().warning("This minecraft version did not support HEX"); + plugin.getLogger().warning("Using legacy message serializer"); + cosmeticStrategy = new LoreLegacyCosmetic(); + } else if(!isSupportKyoriLore) { + plugin.getLogger().warning("This minecraft version did not support Kyori"); + plugin.getLogger().warning("Using legacy MD5 message serializer"); + cosmeticStrategy = new LoreMD5Cosmetics(); + } else cosmeticStrategy = new LoreKyoriCosmetic(); + + return new TextContext(cosmeticStrategy, messages, cosmetics, isSupportHEX, isSupportKyoriLore); + } + + + + + + @SneakyThrows + private File loadResource(Plugin plugin, String folder, String resourceName) { + File file = new File(plugin.getDataFolder(), resourceName); + if(!file.exists()) { + InputStream in = plugin.getResource("text/" + folder + "/" + resourceName); + Files.copy(in, file.toPath()); + } + return file; + } + + +} diff --git a/src/main/java/me/redtea/nodropx/gui/facade/GuiFacade.java b/src/main/java/me/redtea/nodropx/gui/facade/GuiFacade.java new file mode 100644 index 0000000..5df3794 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/gui/facade/GuiFacade.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.gui.facade; + +import org.bukkit.entity.Player; + +public interface GuiFacade { + void openNoDropStorage(Player player); + void clearCache(Player player); + void clearAllCache(); +} diff --git a/src/main/java/me/redtea/nodropx/gui/facade/impl/GuiFacadeImpl.java b/src/main/java/me/redtea/nodropx/gui/facade/impl/GuiFacadeImpl.java new file mode 100644 index 0000000..6321b4a --- /dev/null +++ b/src/main/java/me/redtea/nodropx/gui/facade/impl/GuiFacadeImpl.java @@ -0,0 +1,26 @@ +package me.redtea.nodropx.gui.facade.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.gui.facade.GuiFacade; +import me.redtea.nodropx.gui.pages.StorageGui; +import org.bukkit.entity.Player; + +@RequiredArgsConstructor +public class GuiFacadeImpl implements GuiFacade { + private final StorageGui storageGui; + + @Override + public void openNoDropStorage(Player player) { + storageGui.open(player); + } + + @Override + public void clearCache(Player player) { + storageGui.clearCache(player); + } + + @Override + public void clearAllCache() { + storageGui.clearAllCache(); + } +} diff --git a/src/main/java/me/redtea/nodropx/gui/pages/StorageGui.java b/src/main/java/me/redtea/nodropx/gui/pages/StorageGui.java new file mode 100644 index 0000000..7516f1e --- /dev/null +++ b/src/main/java/me/redtea/nodropx/gui/pages/StorageGui.java @@ -0,0 +1,139 @@ +package me.redtea.nodropx.gui.pages; + +import dev.triumphteam.gui.builder.item.ItemBuilder; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.GuiItem; +import dev.triumphteam.gui.guis.ScrollingGui; +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; +import me.redtea.nodropx.libs.message.container.Messages; +import me.redtea.nodropx.service.nodrop.NoDropService; +import me.redtea.nodropx.util.MaterialUtils; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class StorageGui { + private final Map cache = new HashMap<>(); + private final Messages messages; + private final MutableRepo> pagesRepo; + private final NoDropService noDropService; + private final Plugin plugin; + + public void open(Player player) { + if(cache.containsKey(player.getUniqueId())) { + cache.get(player.getUniqueId()).open(player); + return; + } + + ScrollingGui gui = Gui.scrolling() + .title(messages.get("gui.title").asComponent()) + .rows(6) + .pageSize(45) + .disableAllInteractions() + .create(); + setFrame(gui); + setButtons(gui); + setAction(gui); + setOnClose(gui); + for(ItemStack item : pagesRepo.get(player.getUniqueId()).orElse(new ArrayList<>())) { + gui.addItem(ItemBuilder.from(item).asGuiItem()); + } + + gui.open(player); + cache.put(player.getUniqueId(), gui); + } + + private void setOnClose(ScrollingGui gui) { + gui.setCloseGuiAction(event -> pagesRepo.update(event.getPlayer().getUniqueId(), gui.getPageItems().stream() + .map(GuiItem::getItemStack) + .collect(Collectors.toList()))); + } + + private void setAction(ScrollingGui gui) { + gui.setDefaultClickAction(event -> { + if((event.getSlot() >= 45 && event.getSlot() <= 53) || event.getClickedInventory() instanceof PlayerInventory) { + return; + } + if(event.getAction() == InventoryAction.PLACE_ALL && !event.getClick().isShiftClick()) { + if(!noDropService.isNoDrop(event.getCursor())) { + return; + } + ItemStack is = event.getCursor(); + event.setCursor(new ItemStack(Material.AIR)); + new BukkitRunnable() { + @Override + public void run() { + gui.addItem(ItemBuilder.from(is).asGuiItem()); + gui.update(); + } + }.runTask(plugin); + } else if(event.getAction() == InventoryAction.SWAP_WITH_CURSOR && !event.getClick().isShiftClick()) { + if(!noDropService.isNoDrop(event.getCursor())) { + return; + } + ItemStack is = event.getCursor(); + new BukkitRunnable() { + @Override + public void run() { + gui.addItem(ItemBuilder.from(is).asGuiItem()); + gui.update(); + } + }.runTask(plugin); + event.setCursor(new ItemStack(Material.AIR)); + } else if(event.getAction() == InventoryAction.PICKUP_ALL) { + event.setCursor(event.getCurrentItem()); + new BukkitRunnable() { + @Override + public void run() { + gui.removePageItem(event.getCurrentItem()); + gui.update(); + } + }.runTask(plugin); + + } + + }); + } + + + private void setFrame(ScrollingGui gui) { + for(int i = 1; i < 10; ++i) { + gui.setItem(6, i, ItemBuilder.from( + MaterialUtils.getMaterialFromString(messages.get("gui.frame.material").asUnparsedString()) + ).name(messages.get("gui.frame.name").asComponent()).asGuiItem(event -> event.setCancelled(true))); + } + } + + private void setButtons(ScrollingGui gui) { + gui.setItem(6, 3, ItemBuilder.from( + MaterialUtils.getMaterialFromString(messages.get("gui.prev.material").asUnparsedString()) + ).name(messages.get("gui.prev.name").asComponent()).asGuiItem(event -> { + gui.previous(); + event.setCancelled(true); + })); + gui.setItem(6, 7, ItemBuilder.from( + MaterialUtils.getMaterialFromString(messages.get("gui.next.material").asUnparsedString()) + ).name(messages.get("gui.next.name").asComponent()).asGuiItem(event -> { + gui.next(); + event.setCancelled(true); + })); + } + + public void clearCache(Player player) { + cache.remove(player.getUniqueId()); + } + + public void clearAllCache() { + cache.clear(); + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/bstats/Metrics.java b/src/main/java/me/redtea/nodropx/libs/bstats/Metrics.java new file mode 100644 index 0000000..9d13810 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/bstats/Metrics.java @@ -0,0 +1,881 @@ +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Disallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfuscator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package me.redtea.nodropx.libs.bstats; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.java.JavaPlugin; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my plugin id? + */ + public Metrics(JavaPlugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + metricsBase = + new MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText); + } + + /** Shuts down the underlying scheduler service. */ + public void shutdown() { + metricsBase.shutdown(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.0.2"; + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final ScheduledExecutorService scheduler; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be + * used to delegate the data collection to a another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. + * @param errorLogger A consumer that accepts log message and an error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be logged. + * @param logResponseStatusText Whether or not the response status text should be logged. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText) { + ScheduledThreadPoolExecutor scheduler = + new ScheduledThreadPoolExecutor(1, task -> new Thread(task, "bStats-Metrics")); + // We want delayed tasks (non-periodic) that will execute in the future to be + // cancelled when the scheduler is shutdown. + // Otherwise, we risk preventing the server from shutting down even when + // MetricsBase#shutdown() is called + scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.scheduler = scheduler; + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + checkRelocation(); + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from + // bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + public void shutdown() { + scheduler.shutdown(); + } + + private void startSubmitting() { + final Runnable submitTask = + () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven + // distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into + // the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the + // submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just + // don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = + customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" ... :D + final String defaultPackage = + new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); + final String examplePackage = + new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure no one just copy & pastes the example and uses the wrong + // package names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

While this class is neither feature-rich nor the most performant one, it's sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = + Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not + * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/reload/Reloadable.java b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/Reloadable.java new file mode 100644 index 0000000..8823162 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/Reloadable.java @@ -0,0 +1,16 @@ +package me.redtea.nodropx.libs.carcadex.reload; + +public interface Reloadable { + void init(); + + void close(); + + default void reload() { + close(); + init(); + } + + default int priority() { + return 0; + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/reload/exception/SimpledReloaderException.java b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/exception/SimpledReloaderException.java new file mode 100644 index 0000000..a0654d9 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/exception/SimpledReloaderException.java @@ -0,0 +1,7 @@ +package me.redtea.nodropx.libs.carcadex.reload.exception; + +public class SimpledReloaderException extends RuntimeException { + public SimpledReloaderException() { + super("Simpled reloader is not implements this operation. Please use classic reloader"); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/ParameterizedReloadable.java b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/ParameterizedReloadable.java new file mode 100644 index 0000000..a6fe716 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/ParameterizedReloadable.java @@ -0,0 +1,15 @@ +package me.redtea.nodropx.libs.carcadex.reload.parameterized; + +import me.redtea.nodropx.libs.carcadex.reload.Reloadable; +import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer; +import org.apache.commons.lang.NotImplementedException; +import org.jetbrains.annotations.Nullable; + +public abstract class ParameterizedReloadable implements Reloadable { + public abstract void init(@Nullable ReloadContainer container); + @Override + public void init() { + throw new NotImplementedException("This class can be initialized only with init(ReloadContainer) method"); + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/ReloadContainer.java b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/ReloadContainer.java new file mode 100644 index 0000000..c6fe9b4 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/ReloadContainer.java @@ -0,0 +1,21 @@ +package me.redtea.nodropx.libs.carcadex.reload.parameterized.container; + +import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.impl.MapReloadContainer; + +import java.util.HashMap; +import java.util.Map; + +public interface ReloadContainer { + T get(String key); + void set(String key, T t); + boolean contains(String key); + static ReloadContainer of(Object... objects) { + Map map = new HashMap<>(); + for(int i = 0; i < objects.length; i += 2) { + String key = (String) objects[i]; + Object value = objects[i+1]; + map.put(key, value); + } + return new MapReloadContainer(map); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/impl/MapReloadContainer.java b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/impl/MapReloadContainer.java new file mode 100644 index 0000000..3cea827 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/reload/parameterized/container/impl/MapReloadContainer.java @@ -0,0 +1,25 @@ +package me.redtea.nodropx.libs.carcadex.reload.parameterized.container.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer; + +import java.util.Map; + +@RequiredArgsConstructor +public class MapReloadContainer implements ReloadContainer { + private final Map params; + @Override + public T get(String key) { + return (T) params.get(key); + } + + @Override + public void set(String key, T t) { + params.put(key, t); + } + + @Override + public boolean contains(String key) { + return params.containsKey(key); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/MutableRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/MutableRepo.java new file mode 100644 index 0000000..caee5db --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/MutableRepo.java @@ -0,0 +1,12 @@ +package me.redtea.nodropx.libs.carcadex.repo; + +import me.redtea.nodropx.libs.carcadex.reload.Reloadable; + +public interface MutableRepo extends Repo, Reloadable { + + V update(K key, V value); + + V remove(K key); + + void saveAll(); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/Repo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/Repo.java new file mode 100644 index 0000000..d80999a --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/Repo.java @@ -0,0 +1,17 @@ +package me.redtea.nodropx.libs.carcadex.repo; + +import me.redtea.nodropx.libs.carcadex.reload.Reloadable; +import me.redtea.nodropx.libs.carcadex.repo.builder.RepoBuilder; + +import java.util.Collection; +import java.util.Optional; + +public interface Repo extends Reloadable { + Collection all(); + + Optional get(K key); + + static RepoBuilder builder() { + return RepoBuilder.get(); + } +} \ No newline at end of file diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/RepoBuilder.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/RepoBuilder.java new file mode 100644 index 0000000..7d8d571 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/RepoBuilder.java @@ -0,0 +1,38 @@ +package me.redtea.nodropx.libs.carcadex.repo.builder; + +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; +import me.redtea.nodropx.libs.carcadex.repo.builder.impl.RepoBuilderImpl; +import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy; +import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer; +import org.bukkit.plugin.Plugin; +import org.slf4j.Logger; + +import java.nio.file.Path; + +public interface RepoBuilder { + RepoBuilder serializer(CommonSerializer serializer); + RepoBuilder binary(); //requires that V extends Serializable + RepoBuilder plugin(Plugin plugin); + RepoBuilder autoSave(long period); //in milisec + RepoBuilder schema(SchemaStrategy schemaStrategy); + RepoBuilder dir(Path dir); + RepoBuilder dir(String filename); //requires plugin. didnt need if dir set + RepoBuilder logging(); + RepoBuilder debugLogging(); + RepoBuilder logging(Logger logger); + RepoBuilder debugLogging(Logger logger); + RepoBuilder threadSafe(); + MutableRepo build(); + + static RepoBuilder get() { + return new RepoBuilderImpl<>(); + } + + static RepoBuilder of(Path dir) { + return RepoBuilder.get().dir(dir); + } + + static RepoBuilder of(Plugin plugin) { + return RepoBuilder.get().plugin(plugin); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/impl/RepoBuilderImpl.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/impl/RepoBuilderImpl.java new file mode 100644 index 0000000..0d7f768 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/builder/impl/RepoBuilderImpl.java @@ -0,0 +1,159 @@ +package me.redtea.nodropx.libs.carcadex.repo.builder.impl; + +import lombok.SneakyThrows; +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; +import me.redtea.nodropx.libs.carcadex.repo.builder.RepoBuilder; +import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.AutoSaveDecorator; +import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.CopyOnWriteDecorator; +import me.redtea.nodropx.libs.carcadex.repo.decorator.impl.LoggingDecorator; +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; +import me.redtea.nodropx.libs.carcadex.repo.impl.common.CommonRepo; +import me.redtea.nodropx.libs.carcadex.repo.impl.schema.SchemaRepo; +import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy; +import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer; +import org.bukkit.plugin.Plugin; +import org.slf4j.Logger; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class RepoBuilderImpl implements RepoBuilder { + private CommonSerializer serializer; //null if binary + private boolean autoSave; + private long period; + private Plugin plugin; + private Path dir; + private String filename; + private SchemaStrategy schemaStrategy; + private Logger logger = null; + private boolean logging = false; + private boolean debugLogger = false; + private boolean threadSafe = false; + + @Override + public RepoBuilder serializer(CommonSerializer serializer) { + this.serializer = serializer; + return this; + } + + @Override + public RepoBuilder binary() { + serializer = null; + return this; + } + + @Override + public RepoBuilder plugin(Plugin plugin) { + this.plugin = plugin; + return this; + } + + @Override + public RepoBuilder autoSave(long period) { + autoSave = true; + this.period = period; + return this; + } + + @Override + public RepoBuilder schema(SchemaStrategy schemaStrategy) { + this.schemaStrategy = schemaStrategy; + return this; + } + + @Override + public RepoBuilder dir(Path path) { + this.dir = path; + return this; + } + + @Override + public RepoBuilder dir(String filename) { + this.filename = filename; + return this; + } + + @Override + public RepoBuilder logging() { + logging = true; + debugLogger = false; + return this; + } + + @Override + public RepoBuilder debugLogging() { + logging = true; + debugLogger = true; + return this; + } + + @Override + public RepoBuilder logging(Logger logger) { + logging = true; + debugLogger = false; + this.logger = logger; + return this; + } + + @Override + public RepoBuilder debugLogging(Logger logger) { + logging = true; + debugLogger = true; + this.logger = logger; + return this; + } + + @Override + public RepoBuilder threadSafe() { + threadSafe = true; + return this; + } + + @Override + public MutableRepo build() { + if(dir == null) { + checkDir(); + } + + CacheRepo result = null; + + if(serializer != null) { + result = new CommonRepo<>(dir, serializer); + } + + if(schemaStrategy != null) { + result = new SchemaRepo<>(schemaStrategy); + } + + if(result == null) { + result = new CommonRepo<>(dir); + } + + if(autoSave) {result = new AutoSaveDecorator<>(result, 0, period); + } + + if(logging) { + if(logger != null) result = new LoggingDecorator<>(result, logger, debugLogger); + else result = new LoggingDecorator<>(result, debugLogger); + } + + if(threadSafe) { + result = new CopyOnWriteDecorator<>(result); + } + + result.init(); + return result; + } + + @SneakyThrows + private void checkDir() { + if(plugin == null) { + if(filename == null) { + dir = Paths.get("saved-data-carcadex"); + } else dir = Paths.get(filename); + } else { + dir = new File(plugin.getDataFolder(), filename == null ? "saved-data-carcadex" : filename).toPath(); + } + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/CacheRepoDecorator.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/CacheRepoDecorator.java new file mode 100644 index 0000000..0e30740 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/CacheRepoDecorator.java @@ -0,0 +1,58 @@ +package me.redtea.nodropx.libs.carcadex.repo.decorator; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; + +import java.util.Collection; +import java.util.Optional; + +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +public abstract class CacheRepoDecorator implements CacheRepo { + protected final CacheRepo repo; + + @Override + public void init() { + repo.init(); + } + + @Override + public void close() { + repo.close(); + } + + @Override + public V update(K key, V value) { + return repo.update(key, value); + } + + @Override + public V remove(K key) { + return repo.remove(key); + } + + @Override + public void saveAll() { + repo.saveAll(); + } + + @Override + public Collection all() { + return repo.all(); + } + + @Override + public Optional get(K key) { + return repo.get(key); + } + + @Override + public void loadToCache(K k) { + repo.loadToCache(k); + } + + @Override + public void removeFromCache(K k) { + repo.remove(k); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/AutoSaveDecorator.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/AutoSaveDecorator.java new file mode 100644 index 0000000..aa0e4b0 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/AutoSaveDecorator.java @@ -0,0 +1,19 @@ +package me.redtea.nodropx.libs.carcadex.repo.decorator.impl; + +import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator; +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; + +import java.util.Timer; +import java.util.TimerTask; + +public class AutoSaveDecorator extends CacheRepoDecorator { + public AutoSaveDecorator(CacheRepo repo, long delay /*in milisec*/, long period /*in milisec*/) { + super(repo); + new Timer().scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + saveAll(); + } + }, delay, period); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/CopyOnWriteDecorator.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/CopyOnWriteDecorator.java new file mode 100644 index 0000000..14f84d5 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/CopyOnWriteDecorator.java @@ -0,0 +1,105 @@ +package me.redtea.nodropx.libs.carcadex.repo.decorator.impl; + +import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator; +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; + +import java.util.Collection; +import java.util.Optional; +import java.util.concurrent.locks.ReentrantLock; + + +public class CopyOnWriteDecorator extends CacheRepoDecorator { + private final CacheRepo repo; + + public CopyOnWriteDecorator(CacheRepo repo) { + super(repo); + this.repo = repo; + } + + final transient ReentrantLock lock = new ReentrantLock(); + + @Override + public void loadToCache(K k) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + super.loadToCache(k); + } finally { + lock.unlock(); + } + } + + @Override + public void removeFromCache(K k) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + super.removeFromCache(k); + } finally { + lock.unlock(); + } + } + + @Override + public Collection all() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return super.all(); + } finally { + lock.unlock(); + } + } + + @Override + public Optional get(K key) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + return super.get(key); + } finally { + lock.unlock(); + } + } + @Override + public void saveAll() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + super.saveAll(); + } finally { + lock.unlock(); + } + } + + @Override + public V update(K key, V value) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + V oldValue = repo.get(key).orElse(null); + if (oldValue != null && !oldValue.equals(value)) { + repo.update(key, value); + } + return value; + } finally { + lock.unlock(); + } + } + + public V remove(K key) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + V oldValue = repo.get(key).orElse(null); + if (oldValue != null) { + repo.remove(key); + } + return oldValue; + } finally { + lock.unlock(); + } + } + + +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/LoggingDecorator.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/LoggingDecorator.java new file mode 100644 index 0000000..e1323ac --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/decorator/impl/LoggingDecorator.java @@ -0,0 +1,143 @@ +package me.redtea.nodropx.libs.carcadex.repo.decorator.impl; + +import me.redtea.nodropx.libs.carcadex.repo.decorator.CacheRepoDecorator; +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Optional; + +public class LoggingDecorator extends CacheRepoDecorator { + private final Logger logger; + + private final boolean onlyOnDebug; + + public LoggingDecorator(CacheRepo repo, Logger logger, boolean onlyOnDebug) { + super(repo); + this.logger = logger; + this.onlyOnDebug = onlyOnDebug; + } + + public LoggingDecorator(CacheRepo repo, boolean onlyOnDebug) { + super(repo); + this.logger = LoggerFactory.getLogger(this.getClass()); + this.onlyOnDebug = onlyOnDebug; + } + + public LoggingDecorator(CacheRepo repo) { + super(repo); + this.logger = LoggerFactory.getLogger(this.getClass()); + this.onlyOnDebug = false; + } + + @Override + public void init() { + try { + log("Start of repository initialization..."); + super.init(); + } catch (Throwable e) { + logger.error("Error on initialization repository! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public void close() { + try { + log("Closing the repository..."); + super.close(); + } catch (Throwable e) { + logger.error("Error on closing repository! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public V update(K key, V value) { + try { + log("Updating the repository..."); + log("Key: " + key + " Value: " + value); + return repo.update(key, value); + } catch (Throwable e) { + logger.error("Error on updating repository! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public V remove(K key) { + try { + log("Removing from repository..."); + log("Key: " + key); + return repo.remove(key); + } catch (Throwable e) { + logger.error("Error on removing from repository! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public void saveAll() { + try { + log("Saving to storage from repository..."); + repo.saveAll(); + } catch (Throwable e) { + logger.error("Error on saving to storage from repository! " + e.getLocalizedMessage()); + throw e; + } + + } + + @Override + public Collection all() { + try { + log("Getting all values from memory..."); + return repo.all(); + } catch (Throwable e) { + logger.error("Error on getting all values from memory! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public Optional get(K key) { + try { + log("Getting value from repository..."); + log("Key: " + key); + return repo.get(key); + } catch (Throwable e) { + logger.error("Error on getting value from repository! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public void loadToCache(K k) { + try { + log("Load to cache value..."); + log("Key: " + k); + repo.loadToCache(k); + } catch (Throwable e) { + logger.error("Error on load to cache value! " + e.getLocalizedMessage()); + throw e; + } + } + + @Override + public void removeFromCache(K k) { + try { + log("Remove from cache value..."); + repo.remove(k); + } catch (Throwable e) { + logger.error("Error on remove from cache value! " + e.getLocalizedMessage()); + throw e; + } + } + + private void log(String log) { + if(onlyOnDebug) logger.debug(log); + else logger.info(log); + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/CacheRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/CacheRepo.java new file mode 100644 index 0000000..f97dbbb --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/CacheRepo.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl; + +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; + +public interface CacheRepo extends MutableRepo { + void loadToCache(K k); + + void removeFromCache(K k); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/common/CommonRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/common/CommonRepo.java new file mode 100644 index 0000000..f4e626c --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/common/CommonRepo.java @@ -0,0 +1,18 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl.common; + +import me.redtea.nodropx.libs.carcadex.repo.impl.schema.SchemaRepo; +import me.redtea.nodropx.libs.carcadex.schema.impl.binary.BinarySchemaStrategy; +import me.redtea.nodropx.libs.carcadex.schema.impl.serialize.SerializeSchemaStrategy; +import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer; + +import java.nio.file.Path; + +public class CommonRepo extends SchemaRepo { + public CommonRepo(Path dir, CommonSerializer serializer) { + super(new SerializeSchemaStrategy<>(dir, serializer)); + } + + public CommonRepo(Path dir) { + super(new BinarySchemaStrategy<>(dir)); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/map/MapRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/map/MapRepo.java new file mode 100644 index 0000000..5ce5f93 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/map/MapRepo.java @@ -0,0 +1,40 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl.map; + +import me.redtea.nodropx.libs.carcadex.reload.parameterized.ParameterizedReloadable; +import me.redtea.nodropx.libs.carcadex.reload.parameterized.container.ReloadContainer; +import me.redtea.nodropx.libs.carcadex.repo.Repo; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Reload container: + * data - Map.class + * @param + * @param + */ +public abstract class MapRepo extends ParameterizedReloadable implements Repo { + protected Map data = new HashMap<>(); + + @Override + public Collection all() { + return data.values(); + } + + @Override + public Optional get(K key) { + return Optional.ofNullable(data.get(key)); + } + + @Override + public void init(ReloadContainer container) { + if(container != null) data = container.get("data"); + } + + @Override + public void close() { + data.clear(); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/schema/SchemaRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/schema/SchemaRepo.java new file mode 100644 index 0000000..40973e2 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/schema/SchemaRepo.java @@ -0,0 +1,84 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl.schema; + +import me.redtea.nodropx.libs.carcadex.repo.impl.CacheRepo; +import me.redtea.nodropx.libs.carcadex.repo.impl.map.MapRepo; +import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy; + +import java.util.*; + +public class SchemaRepo extends MapRepo implements CacheRepo { + protected final Set toRemove = new HashSet<>(); + + protected final SchemaStrategy schemaStrategy; + + public SchemaRepo(SchemaStrategy schemaStrategy) { + this.schemaStrategy = schemaStrategy; + } + + @Override + public V update(K key, V value) { + return data.put(key, value); + } + + @Override + public Collection all() { + return schemaStrategy.all(); + } + + @Override + public Optional get(K key) { + if(toRemove.contains(key)) return Optional.ofNullable(null); + V result = data.get(key); + if(result == null) try { + loadToCache(key); + result = data.get(key); + } catch (Throwable ignored) {} + return Optional.ofNullable(result); + } + + @Override + public V remove(K key) { + if(!get(key).isPresent()) throw new NoSuchElementException(); + toRemove.add(key); + return data.remove(key); + } + + @Override + public void saveAll() { + toRemove.forEach(schemaStrategy::remove); + data.forEach(schemaStrategy::insert); + } + + @Override + public void loadToCache(K key) { + data.put(key, schemaStrategy.get(key)); + } + + @Override + public void removeFromCache(K key) { + data.remove(key); + } + + @Override + public void init() { + schemaStrategy.init(); + } + + @Override + public void close() { + saveAll(); + data.clear(); + toRemove.clear(); + schemaStrategy.close(); + } + + @Override + public void reload() { + saveAll(); + data.clear(); + schemaStrategy.reload(); + init(); + } + + +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/YamlRepo.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/YamlRepo.java new file mode 100644 index 0000000..fc03357 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/YamlRepo.java @@ -0,0 +1,37 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl.yaml; + +import me.redtea.nodropx.libs.carcadex.repo.impl.map.MapRepo; +import me.redtea.nodropx.libs.carcadex.repo.impl.yaml.strategy.ParseStrategy; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class YamlRepo extends MapRepo { + protected final Path file; + + protected final Plugin plugin; + + protected final ParseStrategy parseStrategy; + + public YamlRepo(Path file, Plugin plugin, ParseStrategy parseStrategy) { + this.file = file; + this.plugin = plugin; + this.parseStrategy = parseStrategy; + init(); + } + + + protected FileConfiguration initFile() { + if (Files.notExists(file)) plugin.saveResource(file.toFile().getName(), false); + return YamlConfiguration.loadConfiguration(file.toFile()); + } + + + @Override + public void init() { + data.putAll(parseStrategy.fromYaml(initFile())); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/strategy/ParseStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/strategy/ParseStrategy.java new file mode 100644 index 0000000..2f7aa56 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/repo/impl/yaml/strategy/ParseStrategy.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.libs.carcadex.repo.impl.yaml.strategy; + +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.Map; + +public interface ParseStrategy { + Map fromYaml(FileConfiguration fileConfiguration); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/AbstractFileSchemaStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/AbstractFileSchemaStrategy.java new file mode 100644 index 0000000..30fb077 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/AbstractFileSchemaStrategy.java @@ -0,0 +1,36 @@ +package me.redtea.nodropx.libs.carcadex.schema; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public abstract class AbstractFileSchemaStrategy implements SchemaStrategy { + protected final Path dir; + + public AbstractFileSchemaStrategy(Path dir) { + this.dir = dir; + init(); + } + + @Override + public void init() { + if(Files.notExists(dir)) { + try { + Files.createDirectory(dir); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void close() { + } + + @Override + public void remove(K key) { + File file = new File(dir.toFile(), String.valueOf(key)); + if(file.exists()) file.delete(); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/SchemaStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/SchemaStrategy.java new file mode 100644 index 0000000..0e8dce8 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/SchemaStrategy.java @@ -0,0 +1,15 @@ +package me.redtea.nodropx.libs.carcadex.schema; + +import me.redtea.nodropx.libs.carcadex.reload.Reloadable; + +import java.util.Collection; + +public interface SchemaStrategy extends Reloadable { + Collection all(); + + V get(K key); + + void insert(K key, V value); + + void remove(K key); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/binary/BinarySchemaStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/binary/BinarySchemaStrategy.java new file mode 100644 index 0000000..b42c714 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/binary/BinarySchemaStrategy.java @@ -0,0 +1,62 @@ +package me.redtea.nodropx.libs.carcadex.schema.impl.binary; + +import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy; + +import java.io.*; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +public class BinarySchemaStrategy extends AbstractFileSchemaStrategy { + + public BinarySchemaStrategy(Path dir) { + super(dir); + } + + + @Override + public Collection all() { + if(dir.toFile().listFiles() != null) + return Arrays.stream(dir.toFile().listFiles()).map(this::read).collect(Collectors.toList()); + else return new ArrayList<>(); + } + + @Override + public V get(K key) { + File file = new File(dir.toFile(), key+".bin"); + return read(file); + } + + private V read(File file) { + try { + if(!file.exists()) file.createNewFile(); + FileInputStream fileInputStream = new FileInputStream(file); + ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); + V result = (V) objectInputStream.readObject(); + objectInputStream.close(); + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + @Override + public void insert(K key, V value) { + try { + File file = new File(dir.toFile(), key+".bin"); + if(!file.exists()) file.createNewFile(); + FileOutputStream fileOutputStream = new FileOutputStream(file); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); + objectOutputStream.writeObject(value); + objectOutputStream.flush(); + objectOutputStream.close(); + fileOutputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/FileSectionSchemaStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/FileSectionSchemaStrategy.java new file mode 100644 index 0000000..d92ff8d --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/FileSectionSchemaStrategy.java @@ -0,0 +1,43 @@ +package me.redtea.nodropx.libs.carcadex.schema.impl.filesection; + +import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy; +import me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.SectionParser; +import org.bukkit.configuration.ConfigurationSection; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +public abstract class FileSectionSchemaStrategy extends AbstractFileSchemaStrategy { + private final SectionParser sectionParser; + + public FileSectionSchemaStrategy(Path dir, SectionParser sectionParser) { + super(dir); + this.sectionParser = sectionParser; + } + + @Override + public Collection all() { + if(dir.toFile().listFiles() == null) return new ArrayList<>(); + return Arrays.stream(dir.toFile().listFiles()).map(it -> fromSection(sectionParser.parse(it))).collect(Collectors.toList()); + } + + @Override + public V get(K key) { + return fromSection(sectionParser.parse(Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key))) + .findFirst().get())); + } + + @Override + public void insert(K key, V value) { + File file = Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key))) + .findFirst().get(); + sectionParser.save(saveToSection(value, sectionParser.parse(file)), file); + } + + abstract V fromSection(ConfigurationSection section); + abstract ConfigurationSection saveToSection(V v, ConfigurationSection section); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/SectionParser.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/SectionParser.java new file mode 100644 index 0000000..21f4984 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/SectionParser.java @@ -0,0 +1,10 @@ +package me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser; + +import org.bukkit.configuration.ConfigurationSection; + +import java.io.File; + +public interface SectionParser { + ConfigurationSection parse(File file); + void save(ConfigurationSection section, File file); +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/impl/YamlSectionParser.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/impl/YamlSectionParser.java new file mode 100644 index 0000000..2a1405e --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/filesection/parser/impl/YamlSectionParser.java @@ -0,0 +1,25 @@ +package me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.impl; + +import me.redtea.nodropx.libs.carcadex.schema.impl.filesection.parser.SectionParser; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; + +public class YamlSectionParser implements SectionParser { + @Override + public ConfigurationSection parse(File file) { + return YamlConfiguration.loadConfiguration(file); + } + + @Override + public void save(ConfigurationSection section, File file) { + try { + ((FileConfiguration) section).save(file); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/serialize/SerializeSchemaStrategy.java b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/serialize/SerializeSchemaStrategy.java new file mode 100644 index 0000000..5a02760 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/schema/impl/serialize/SerializeSchemaStrategy.java @@ -0,0 +1,67 @@ +package me.redtea.nodropx.libs.carcadex.schema.impl.serialize; + +import me.redtea.nodropx.libs.carcadex.schema.AbstractFileSchemaStrategy; +import me.redtea.nodropx.libs.carcadex.serializer.CommonSerializer; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collection; +import java.util.stream.Collectors; + +public class SerializeSchemaStrategy extends AbstractFileSchemaStrategy { + + private final CommonSerializer serializer; + + public SerializeSchemaStrategy(Path dir, CommonSerializer serializer) { + super(dir); + this.serializer = serializer; + } + + + @Override + public void close() { + } + + @Override + public Collection all() { + return Arrays.stream(dir.toFile().listFiles()).map(file -> { + try { + return serializer.deserialize(String.join("\n", Files.readAllLines(file.toPath()))); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + } + + @Override + public V get(K key) { + try { + return serializer.deserialize( + String.join("\n", Files.readAllLines( + Arrays.stream(dir.toFile().listFiles()).filter(it -> it.getName().equals(String.valueOf(key))) + .findFirst().get().toPath() + ))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void insert(K key, V value) { + File file = new File(dir.toFile(), String.valueOf(key)); + try { + if(!file.exists()) file.createNewFile(); + BufferedWriter writer; + writer = new BufferedWriter(new FileWriter(file)); + writer.write(serializer.serialize(value)); + writer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/carcadex/serializer/CommonSerializer.java b/src/main/java/me/redtea/nodropx/libs/carcadex/serializer/CommonSerializer.java new file mode 100644 index 0000000..de16368 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/carcadex/serializer/CommonSerializer.java @@ -0,0 +1,6 @@ +package me.redtea.nodropx.libs.carcadex.serializer; + +public interface CommonSerializer { + String serialize(T t); + T deserialize(String s); +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/container/Messages.java b/src/main/java/me/redtea/nodropx/libs/message/container/Messages.java new file mode 100644 index 0000000..3047b52 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/container/Messages.java @@ -0,0 +1,242 @@ +package me.redtea.nodropx.libs.message.container; + +import me.redtea.nodropx.libs.message.container.impl.MessagesImpl; +import me.redtea.nodropx.libs.message.factory.MessageFactory; +import me.redtea.nodropx.libs.message.factory.impl.LegacyFactoryImpl; +import me.redtea.nodropx.libs.message.model.Message; +import me.redtea.nodropx.libs.message.model.impl.NullMessage; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.nio.file.Path; + +/** + *

Container of messages

+ *

Create container by plugin with file messages.yml

+ *

Step 1. Create file messages.yml in resources folder (in jar of plugin)

+ *

Write in file your messages in YAML format. Use MiniMessages format for color messages.
+ * See https://docs.advntr.dev/minimessage/format.html

+ *

For example I will be use this content:

+ *
+ * {@code
+ * error: "Error!"
+ * someRoot:
+ *      message1: "Message"
+ *      message2:
+ *          - "Message start!"
+ *          - "Message end!"
+ *
+ * }
+ * 
+ *

Step 2. Load Messages container in your onEnable method

+ *
+ * {@code
+ * Messages messages = Messages.of(this);
+ * }
+ * 
+ *

Step 3. Done! Now you can use the messages variable to manage messages.

+ *

Create container by plugin with custom file

+ *

Everything is the same, the same, only in jar create a file with the name you need (or several such files) + * and change OnEnable like this:

+ *
+ * {@code
+ * Messages messages = Messages.of(this, "YOURFILENAME.yml");
+ * }
+ * 
+ *

Create container by ConfigurationSection

+ *

Here are a couple of examples of how this can be done:

+ *
+ * {@code
+ * Messages messages = Messages.of(getConfig().getConfigurationSection("messages"));
+ *
+ * //or so
+ *
+ * ConfigurationSection section = ...;
+ * Messages messages = Messages.of(section);
+ * }
+ * 
+ * @since 1.0.0 + * @author itzRedTea + */ +public interface Messages { + /** + *

Get message from container by key/yaml path

+ * + *

For example

+ *

Yaml structure:

+ *
+     * {@code
+     * error: "Error!"
+     * someRoot:
+     *      message1: "Message"
+     *      message2:
+     *          - "Message start!"
+     *          - "Message end!"
+     *
+     * }
+     * 
+ * + *

Getting messages in code:

+ *
+     * {@code
+     * Messages messages = ...; //Init Messages
+     * CommandSender sender = ...; //Getting CommandSender
+     * messages.get("error").send(sender); //Getting error message
+     * message.get("someRoot.message1").send(sender); //Getting someRoot.message1 message
+     * message.get("someRoot.message2").send(sender); //Getting someRoot.message2 message
+     * }
+     * 
+ * + * @param key key of message + * @return Message + * @see Message + */ + Message get(String key); + + /** + *

Put message to container

+ * @param key key/yaml path to message + * @param message message to mut + * @return putted message + * @see Message + */ + Message put(String key, Message message); + + /** + * @param key key/yaml path to message + * @return is there a message with such a key in the container? + */ + boolean has(String key); + + /** + *

Reload container with messages in section from args.

+ * @param section - ConfigurationSection with messages + */ + void reload(ConfigurationSection section); + + /** + *

Sets custom message parse factory.

+ */ + void factory(MessageFactory messageFactory); + + /** + *

Create container using configuration section

+ * Create container using messages from configuration section. + *
+     * {@code
+     * ConfigurationSection section = ...;
+     * Messages messages = Messages.of(section);
+     * }
+     * 
+ * @param section - configuration section with messages + * @return container with messages + */ + static Messages of(ConfigurationSection section) { + return new MessagesImpl(section); + } + + /** + *

Create container using current plugin and custom filename

+ * Create container using file with your name from jar entry by default. + * A new file in plugin folder will be created with content from your file. + *
+     * {@code
+     * Messages messages = Messages.of(this, "YOURFILENAME.yml");
+     * }
+     * 
+ * @param plugin - plugin that creates a container + * @return container with messages + */ + static Messages of(Plugin plugin, String fileName) { + File file = new File(plugin.getDataFolder(), fileName); + if(!file.exists()) plugin.saveResource(fileName, false); + return of(YamlConfiguration.loadConfiguration(file)); + } + + /** + *

Create container using current plugin

+ * Create container using messages.yml file by default from jar entry. + * A new file in plugin folder will be created with content from messages.yml + *
+     * {@code
+     * Messages messages = Messages.of(this);
+     * }
+     * 
+ * @param plugin - plugin that creates a container + * @return container with messages + */ + static Messages of(Plugin plugin) { + return of(plugin, "text/legacy/messages.yml"); + } + + /** + *

Create container using file

+ *
+     * {@code
+     * File file = new File("msg_file.yml");
+     * Messages messages = Messages.of(file);
+     * }
+     * 
+ * @param file - file with messages + * @return container with messages + */ + static Messages of(File file) { + return of(YamlConfiguration.loadConfiguration(file)); + } + + /** + *

Create container using path

+ * + * @param path - file with messages + * @return container with messages + * @see Messages#of(File) + */ + static Messages of(Path path) { + return of(path.toFile()); + } + + /** + * Only legacy color codes. For versions < 1.18 + * @return container with messages + */ + static Messages legacy(ConfigurationSection section) { + return asLegacy(of(section)); + } + + /** @see Messages#legacy(ConfigurationSection) */ + static Messages legacy(Plugin plugin, String fileName) { + return asLegacy(of(plugin, fileName)); + } + + /** @see Messages#legacy(ConfigurationSection) */ + static Messages legacy(Plugin plugin) { + return asLegacy(of(plugin)); + } + + /** @see Messages#legacy(ConfigurationSection) */ + static Messages legacy(File file) { + return asLegacy(of(file)); + } + + /** @see Messages#legacy(ConfigurationSection) */ + static Messages asLegacy(Messages messages) { + messages.factory(new LegacyFactoryImpl()); + return messages; + } + + /** + * @return empty messages container + * Use this with Reloader + */ + static Messages empty() { + return new MessagesImpl(); + } + + /** + * Singleton NULL_MESSAGE + * @see NullMessage + */ + Message NULL_MESSAGE = new NullMessage(); +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/container/impl/MessagesImpl.java b/src/main/java/me/redtea/nodropx/libs/message/container/impl/MessagesImpl.java new file mode 100644 index 0000000..0a4c464 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/container/impl/MessagesImpl.java @@ -0,0 +1,85 @@ +package me.redtea.nodropx.libs.message.container.impl; + +import me.redtea.nodropx.libs.message.container.Messages; +import me.redtea.nodropx.libs.message.factory.MessageFactory; +import me.redtea.nodropx.libs.message.model.Message; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class MessagesImpl implements Messages { + private Map messages; + + private MessageFactory factory = MessageFactory.instance; + + private final Message NULL_MESSAGE = factory.nullMessage(); + + public MessagesImpl() { + } + + public MessagesImpl(@NotNull ConfigurationSection section) { + messages = fromConfigurationToMap(section); + } + + public MessagesImpl(@NotNull File file) { + this(YamlConfiguration.loadConfiguration(file)); + } + + @Override + public Message get(String key) { + return messages.get(key); + } + + @Override + public Message put(String key, Message message) { + if(!has(key)) return NULL_MESSAGE; + return messages.put(key, message); + } + + @Override + public boolean has(String key) { + return messages.containsKey(key); + } + + @Override + public void reload(ConfigurationSection section) { + messages = fromConfigurationToMap(section); + } + + @Override + public void factory(MessageFactory messageFactory) { + this.factory = messageFactory; + } + + private Map fromConfigurationToMap(@NotNull ConfigurationSection section) { + Map data = new HashMap<>(); + + section.getKeys(false).forEach(key -> { + if (section.isConfigurationSection(key)) { + Map newMessages = fromConfigurationToMap(Objects. + requireNonNull(section.getConfigurationSection(key))); + newMessages.forEach((keyMessage, message) -> data.put(key + "." + keyMessage, message)); + } else { + Message message = null; + if (section.isString(key)) { + message = factory.message(section.getString(key)); + } else if (section.isList(key)) { + message = factory.message(section.getStringList(key)); + } + + if (message != null) { + data.put(key, message); + } + } + }); + + return data; + } + + +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/factory/MessageFactory.java b/src/main/java/me/redtea/nodropx/libs/message/factory/MessageFactory.java new file mode 100644 index 0000000..d2d2223 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/factory/MessageFactory.java @@ -0,0 +1,21 @@ +package me.redtea.nodropx.libs.message.factory; + +import me.redtea.nodropx.libs.message.factory.impl.MessageFactoryImpl; +import me.redtea.nodropx.libs.message.model.Message; + +import java.util.List; + +import static me.redtea.nodropx.libs.message.container.Messages.NULL_MESSAGE; + +public interface MessageFactory { + Message message(List unparsed); + Message message(String msg); + Message legacyMessage(List unparsed); + Message legacyMessage(String msg); + + default Message nullMessage() { + return NULL_MESSAGE; + } + + MessageFactory instance = new MessageFactoryImpl(); +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/factory/impl/LegacyFactoryImpl.java b/src/main/java/me/redtea/nodropx/libs/message/factory/impl/LegacyFactoryImpl.java new file mode 100644 index 0000000..9d8f4b3 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/factory/impl/LegacyFactoryImpl.java @@ -0,0 +1,44 @@ +package me.redtea.nodropx.libs.message.factory.impl; + +import com.google.common.collect.Lists; +import me.redtea.nodropx.libs.message.factory.MessageFactory; +import me.redtea.nodropx.libs.message.model.Message; +import me.redtea.nodropx.libs.message.model.impl.serialized.impl.LegacyMessage; +import me.redtea.nodropx.util.MD5ColorUtils; +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +import static net.md_5.bungee.api.ChatColor.COLOR_CHAR; + +public class LegacyFactoryImpl implements MessageFactory { + @Override + public Message message(List unparsed) { + if(unparsed == null || unparsed.isEmpty()) return nullMessage(); + if(unparsed.stream().anyMatch(it -> it.contains("&"))) unparsed = + unparsed.stream() + .map(MD5ColorUtils::translateHexColorCodes) + .collect(Collectors.toList()); + return new LegacyMessage(unparsed); + } + + @Override + public Message message(String msg) { + if(msg == null) return nullMessage(); + if(msg.contains("&")) msg = MD5ColorUtils.translateHexColorCodes(msg); + return new LegacyMessage(Lists.newArrayList(msg)); + } + + @Override + public Message legacyMessage(List unparsed) { + return message(unparsed); + } + + @Override + public Message legacyMessage(String msg) { + return message(Lists.newArrayList(msg)); + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/factory/impl/MessageFactoryImpl.java b/src/main/java/me/redtea/nodropx/libs/message/factory/impl/MessageFactoryImpl.java new file mode 100644 index 0000000..35892f2 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/factory/impl/MessageFactoryImpl.java @@ -0,0 +1,33 @@ +package me.redtea.nodropx.libs.message.factory.impl; + +import com.google.common.collect.Lists; +import me.redtea.nodropx.libs.message.factory.MessageFactory; +import me.redtea.nodropx.libs.message.model.Message; +import me.redtea.nodropx.libs.message.model.impl.ComponentMessage; +import me.redtea.nodropx.libs.message.model.impl.serialized.impl.LegacyMessage; + +import java.util.List; + +public class MessageFactoryImpl implements MessageFactory { + @Override + public Message message(List unparsed) { + if(unparsed == null || unparsed.isEmpty()) return nullMessage(); + return new ComponentMessage(unparsed); + } + + @Override + public Message message(String msg) { + return message(Lists.newArrayList(msg)); + } + + @Override + public Message legacyMessage(List unparsed) { + if(unparsed == null || unparsed.isEmpty()) return nullMessage(); + return new LegacyMessage(unparsed); + } + + @Override + public Message legacyMessage(String msg) { + return legacyMessage(Lists.newArrayList(msg)); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/AbstractMessage.java b/src/main/java/me/redtea/nodropx/libs/message/model/AbstractMessage.java new file mode 100644 index 0000000..cfcc7e8 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/AbstractMessage.java @@ -0,0 +1,86 @@ +package me.redtea.nodropx.libs.message.model; + +import me.redtea.nodropx.NoDropX; +import me.redtea.nodropx.libs.message.model.impl.ComponentMessage; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.platform.bukkit.BukkitAudiences; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class AbstractMessage implements Message { + protected final List unparsed; + + protected List parsed = null; + + protected final BukkitAudiences adventure; + + public AbstractMessage(List unparsed) { + this.unparsed = unparsed; + this.adventure = BukkitAudiences.create(NoDropX.getInstance()); + } + + protected abstract Component parse(String unparsed); + + private void parse() { + parsed = new ArrayList<>(); + for(String s : unparsed) { + parsed.add(parse(s)); + } + } + + @Override + public void send(@NotNull CommandSender sender) { + parse(); + Audience audience = adventure.sender(sender); + parsed.forEach(audience::sendMessage); + + } + + private Component cacheComponent = null; + + @Override + @NotNull + public List asComponentList() { + parse(); + return parsed; + } + + @Override + @NotNull + public Message replaceAll(@NotNull String from, @NotNull String to) { + return new ComponentMessage(unparsed.stream().map(it -> it.replaceAll(from, to)).collect(Collectors.toList())); + } + + @Override + public @NotNull Component asComponent() { + if(cacheComponent == null) { + parse(); + Component first = parsed.get(0); + for(int i = 1; i < parsed.size(); i++) { + first = first.append(parsed.get(i)).append(Component.newline()); + } + cacheComponent = first; + } + return cacheComponent; + } + + @Override + @NotNull + public List asUnparsedStringList() { + return unparsed; + } + + private String cacheString = null; + @Override + @NotNull + public String asUnparsedString(@NotNull String newLine) { + if(cacheString == null) cacheString = String.join(newLine, unparsed); + return cacheString; + } + +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/Message.java b/src/main/java/me/redtea/nodropx/libs/message/model/Message.java new file mode 100644 index 0000000..dee72c7 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/Message.java @@ -0,0 +1,63 @@ +package me.redtea.nodropx.libs.message.model; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + *

Message

+ * This class provides message management methods. You can also convert it to Component. + * @since 1.0.0 + * @author itzRedTea + */ +public interface Message extends ComponentLike { + /** + * Send message to CommandSender + * @param sender recipient of the message + */ + void send(@NotNull CommandSender sender); + + /** + * Replaces all matches in unparsed message + * @param from what you want to replace + * @param to what do you want to replace + * @return copy of the Message with the changes applied to it + */ + @NotNull + Message replaceAll(@NotNull String from, @NotNull String to); + + /** + * Convert message to list of components + * @return list of components + */ + @NotNull + List asComponentList(); + /** + * Get unparsed value of message + */ + @NotNull + List asUnparsedStringList(); + /** + * Get unparsed value of message joined to one string + */ + @NotNull + String asUnparsedString(@NotNull String newLine); + + /** + * @see Message#asUnparsedString(String) () + */ + default String asUnparsedString() { + return asUnparsedString("\n"); + } + + /** + * @see Message#replaceAll(String, String) + */ + @NotNull + default Message replaceAll(@NotNull Object from, @NotNull Object to) { + return replaceAll(String.valueOf(from), String.valueOf(to)); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/impl/ComponentMessage.java b/src/main/java/me/redtea/nodropx/libs/message/model/impl/ComponentMessage.java new file mode 100644 index 0000000..5add462 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/impl/ComponentMessage.java @@ -0,0 +1,20 @@ +package me.redtea.nodropx.libs.message.model.impl; + +import me.redtea.nodropx.libs.message.model.AbstractMessage; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; + +import java.util.List; + +public class ComponentMessage extends AbstractMessage { + private final MiniMessage mm = MiniMessage.get(); + + public ComponentMessage(List unparsed) { + super(unparsed); + } + + @Override + protected Component parse(String unparsed) { + return mm.deserialize(unparsed); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/impl/NullMessage.java b/src/main/java/me/redtea/nodropx/libs/message/model/impl/NullMessage.java new file mode 100644 index 0000000..ac654a7 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/impl/NullMessage.java @@ -0,0 +1,41 @@ +package me.redtea.nodropx.libs.message.model.impl; + +import me.redtea.nodropx.libs.message.model.Message; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public final class NullMessage implements Message { + @Override + public void send(@NotNull CommandSender sender) { + + } + + @Override + public @NotNull Message replaceAll(@NotNull String from, @NotNull String to) { + return this; + } + + @Override + public @NotNull List asComponentList() { + return new ArrayList<>(); + } + + @Override + public @NotNull List asUnparsedStringList() { + return new ArrayList<>(); + } + + @Override + public @NotNull String asUnparsedString(@NotNull String newLine) { + return ""; + } + + @Override + public @NotNull Component asComponent() { + return Component.text(""); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/SerializedMessage.java b/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/SerializedMessage.java new file mode 100644 index 0000000..6a1bf36 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/SerializedMessage.java @@ -0,0 +1,22 @@ +package me.redtea.nodropx.libs.message.model.impl.serialized; + +import me.redtea.nodropx.libs.message.model.AbstractMessage; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.serializer.ComponentSerializer; + +import java.util.List; + +public class SerializedMessage extends AbstractMessage { + private final ComponentSerializer serializer; + + public SerializedMessage(List unparsed, ComponentSerializer serializer) { + super(unparsed); + this.serializer = serializer; + } + + @Override + protected Component parse(String unparsed) { + return serializer.deserialize(unparsed); + } +} diff --git a/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/impl/LegacyMessage.java b/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/impl/LegacyMessage.java new file mode 100644 index 0000000..1e64994 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/libs/message/model/impl/serialized/impl/LegacyMessage.java @@ -0,0 +1,16 @@ +package me.redtea.nodropx.libs.message.model.impl.serialized.impl; + +import me.redtea.nodropx.libs.message.model.impl.serialized.SerializedMessage; +import me.redtea.nodropx.util.MD5ColorUtils; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.ChatColor; + +import java.util.List; +import java.util.stream.Collectors; + +public class LegacyMessage extends SerializedMessage { + public LegacyMessage(List unparsed) { + super(unparsed.stream().map(MD5ColorUtils::translateHexColorCodes).collect(Collectors.toList()), + LegacyComponentSerializer.builder().build()); + } +} diff --git a/src/main/java/me/redtea/nodropx/listener/InventoryHandler.java b/src/main/java/me/redtea/nodropx/listener/InventoryHandler.java new file mode 100644 index 0000000..acb5d69 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/listener/InventoryHandler.java @@ -0,0 +1,39 @@ +package me.redtea.nodropx.listener; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.api.event.NoDropItemThrownEvent; +import me.redtea.nodropx.service.event.EventService; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; + +@RequiredArgsConstructor +public class InventoryHandler implements Listener { + private final EventService eventService; + + @EventHandler + public void onMove(InventoryClickEvent event) { + if(event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + call(event, event.getCurrentItem()); + } else if(event.getAction().equals(InventoryAction.PLACE_ALL) + || event.getAction().equals(InventoryAction.PLACE_ONE) + || event.getAction().equals(InventoryAction.PLACE_SOME)) { + call(event, event.getCursor()); + } + + } + + private void call(InventoryClickEvent event, ItemStack item) { + eventService.callThrownEvent( + (Player)event.getWhoClicked(), + item, + NoDropItemThrownEvent.Reason.MOVING_TO_OTHER_INVENTORY, + Optional.of(event) + ); + } +} diff --git a/src/main/java/me/redtea/nodropx/listener/PlayerHandler.java b/src/main/java/me/redtea/nodropx/listener/PlayerHandler.java new file mode 100644 index 0000000..e4b5af1 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/listener/PlayerHandler.java @@ -0,0 +1,50 @@ +package me.redtea.nodropx.listener; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.api.event.NoDropItemThrownEvent; +import me.redtea.nodropx.gui.facade.GuiFacade; +import me.redtea.nodropx.service.event.EventService; +import me.redtea.nodropx.service.respawn.RespawnService; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerDropItemEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerRespawnEvent; + +import java.util.Optional; + +@RequiredArgsConstructor +public class PlayerHandler implements Listener { + private final RespawnService respawnService; + private final EventService eventService; + private final GuiFacade guiFacade; + + //TODO check for armor + @EventHandler + public void onDrop(PlayerDropItemEvent event) { + eventService.callThrownEvent( + event.getPlayer(), + event.getItemDrop().getItemStack(), + NoDropItemThrownEvent.Reason.DROP_BY_PLAYER, + Optional.of(event) + ); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void onDeath(PlayerDeathEvent event) { + respawnService.saveItemsBeforeDeath(event); + } + + @EventHandler + public void onRespawn(PlayerRespawnEvent event) { + respawnService.giveSavedItems(event.getPlayer()); + } + + @EventHandler + public void onQuit(PlayerQuitEvent event) { + guiFacade.clearCache(event.getPlayer()); + } + +} diff --git a/src/main/java/me/redtea/nodropx/model/cosmetic/CosmeticStrategy.java b/src/main/java/me/redtea/nodropx/model/cosmetic/CosmeticStrategy.java new file mode 100644 index 0000000..311b4b3 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/model/cosmetic/CosmeticStrategy.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.model.cosmetic; + +import org.bukkit.inventory.ItemStack; + +public interface CosmeticStrategy { + void apply(ItemStack itemStack); + void loadYaml(Object fromYaml); + String tag(); +} diff --git a/src/main/java/me/redtea/nodropx/model/cosmetic/impl/DisplayNameCosmetic.java b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/DisplayNameCosmetic.java new file mode 100644 index 0000000..2819e78 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/DisplayNameCosmetic.java @@ -0,0 +1,26 @@ +package me.redtea.nodropx.model.cosmetic.impl; + +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public class DisplayNameCosmetic implements CosmeticStrategy { + private String displayName; + + @Override + public void apply(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + meta.setDisplayName(displayName); + itemStack.setItemMeta(meta); + } + + @Override + public void loadYaml(Object fromYaml) { + displayName = (String) fromYaml; + } + + @Override + public String tag() { + return "name"; + } +} diff --git a/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreKyoriCosmetic.java b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreKyoriCosmetic.java new file mode 100644 index 0000000..c6b97bb --- /dev/null +++ b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreKyoriCosmetic.java @@ -0,0 +1,37 @@ +package me.redtea.nodropx.model.cosmetic.impl; + +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class LoreKyoriCosmetic implements CosmeticStrategy { + private List lore; + private final MiniMessage mm = MiniMessage.get(); + @Override + public void apply(ItemStack itemStack) { + List lore = itemStack.lore(); + if(lore == null) lore = new ArrayList<>(); + lore.addAll(this.lore); + itemStack.lore(lore); + } + + @Override + public void loadYaml(Object fromYaml) { + List list = (List) fromYaml; + lore = list.stream().map(mm::deserialize).map(it -> Component + .empty() + .decoration(TextDecoration.ITALIC, false) + .append(it)).collect(Collectors.toList()); + } + + @Override + public String tag() { + return "lore"; + } +} diff --git a/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreLegacyCosmetic.java b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreLegacyCosmetic.java new file mode 100644 index 0000000..df4f140 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreLegacyCosmetic.java @@ -0,0 +1,37 @@ +package me.redtea.nodropx.model.cosmetic.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.NoDropX; +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import org.bukkit.ChatColor; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +public class LoreLegacyCosmetic implements CosmeticStrategy { + private List lore; + @Override + public void apply(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + List lore = meta.getLore(); + if(lore == null) lore = new ArrayList<>(); + lore.addAll(this.lore); + meta.setLore(lore); + itemStack.setItemMeta(meta); + } + + @Override + public void loadYaml(Object fromYaml) { + List list = (List) fromYaml; + lore = list.stream().map(it -> ChatColor.translateAlternateColorCodes('&', it)).collect(Collectors.toList()); + } + + @Override + public String tag() { + return "lore"; + } +} diff --git a/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreMD5Cosmetics.java b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreMD5Cosmetics.java new file mode 100644 index 0000000..1c521b9 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/model/cosmetic/impl/LoreMD5Cosmetics.java @@ -0,0 +1,43 @@ +package me.redtea.nodropx.model.cosmetic.impl; + +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import me.redtea.nodropx.util.MD5ColorUtils; +import net.md_5.bungee.api.ChatColor; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static net.md_5.bungee.api.ChatColor.COLOR_CHAR; + +public class LoreMD5Cosmetics implements CosmeticStrategy { + + + private List lore; + @Override + public void apply(ItemStack itemStack) { + ItemMeta meta = itemStack.getItemMeta(); + List lore = meta.getLore(); + if(lore == null) lore = new ArrayList<>(); + lore.addAll(this.lore); + meta.setLore(lore); + itemStack.setItemMeta(meta); + } + + @Override + public void loadYaml(Object fromYaml) { + List list = (List) fromYaml; + lore = list.stream().map(MD5ColorUtils::translateHexColorCodes).collect(Collectors.toList()); + } + + @Override + public String tag() { + return "lore"; + } + + +} diff --git a/src/main/java/me/redtea/nodropx/schema/ItemStackListSchema.java b/src/main/java/me/redtea/nodropx/schema/ItemStackListSchema.java new file mode 100644 index 0000000..dd148eb --- /dev/null +++ b/src/main/java/me/redtea/nodropx/schema/ItemStackListSchema.java @@ -0,0 +1,73 @@ +package me.redtea.nodropx.schema; + +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; +import me.redtea.nodropx.libs.carcadex.schema.SchemaStrategy; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +public class ItemStackListSchema implements SchemaStrategy> { + private final File dir; + + @Override + public Collection> all() { + val res = new ArrayList>(); + for(File file : dir.listFiles()) { + res.add(get(UUID.fromString(file.getName().substring(file.getName().length()-4)))); + } + return res; + } + + @Override + public List get(UUID key) { + File file = new File(dir, key.toString() + ".yml"); + if(!file.exists()) return new ArrayList<>(); + val conf = YamlConfiguration.loadConfiguration(file); + val res = new ArrayList(); + for(String k : conf.getKeys(false)) { + res.add(conf.getItemStack(k)); + } + return res; + } + + @Override + @SneakyThrows + public void insert(UUID key, List value) { + File file = new File(dir, key.toString() + ".yml"); + if(!file.exists()) file.createNewFile(); + val conf = YamlConfiguration.loadConfiguration(file); + for(int i = 0; i < value.size(); ++i) { + conf.set(String.valueOf(i), value.get(i)); + } + conf.save(file); + } + + @Override + public void remove(UUID key) { + File file = new File(dir, key.toString() + ".yml"); + if(file.exists()) file.delete(); + } + + @Override + @SneakyThrows + public void init() { + if(!dir.exists()) { + Files.createDirectories(dir.toPath()); + } + } + + @Override + public void close() { + + } + +} diff --git a/src/main/java/me/redtea/nodropx/service/cosmetic/CosmeticService.java b/src/main/java/me/redtea/nodropx/service/cosmetic/CosmeticService.java new file mode 100644 index 0000000..a1c60c7 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/cosmetic/CosmeticService.java @@ -0,0 +1,11 @@ +package me.redtea.nodropx.service.cosmetic; + +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; + +public interface CosmeticService { + void applyAll(ItemStack item); + void add(CosmeticStrategy strategy); + void load(ConfigurationSection section); +} diff --git a/src/main/java/me/redtea/nodropx/service/cosmetic/impl/CosmeticServiceImpl.java b/src/main/java/me/redtea/nodropx/service/cosmetic/impl/CosmeticServiceImpl.java new file mode 100644 index 0000000..50905ca --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/cosmetic/impl/CosmeticServiceImpl.java @@ -0,0 +1,43 @@ +package me.redtea.nodropx.service.cosmetic.impl; + +import lombok.val; +import me.redtea.nodropx.model.cosmetic.CosmeticStrategy; +import me.redtea.nodropx.service.cosmetic.CosmeticService; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; + +public class CosmeticServiceImpl implements CosmeticService { + private final List strategies = new ArrayList<>(); + + @Override + public void applyAll(ItemStack item) { + strategies.forEach(it -> { + try { + it.apply(item); + } catch (Throwable e) { + Bukkit.getLogger().severe("[NoDropX Cosmetic] Failed to apply cosmetic with tag " + it.tag()); + e.printStackTrace(); + } + }); + } + + @Override + public void add(CosmeticStrategy strategy) { + strategies.add(strategy); + } + + @Override + public void load(ConfigurationSection section) { + List disabled = new ArrayList<>(); + for(CosmeticStrategy strategy : strategies) { + val obj = section.get(strategy.tag()); + if(obj == null) disabled.add(strategy); + else strategy.loadYaml(obj); + } + strategies.removeAll(disabled); + } +} diff --git a/src/main/java/me/redtea/nodropx/service/event/EventService.java b/src/main/java/me/redtea/nodropx/service/event/EventService.java new file mode 100644 index 0000000..80a83a8 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/event/EventService.java @@ -0,0 +1,12 @@ +package me.redtea.nodropx.service.event; + +import me.redtea.nodropx.api.event.NoDropItemThrownEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; + +public interface EventService { + void callThrownEvent(Player who, ItemStack item, NoDropItemThrownEvent.Reason reason, Optional rootEvent); +} diff --git a/src/main/java/me/redtea/nodropx/service/event/impl/EventServiceImpl.java b/src/main/java/me/redtea/nodropx/service/event/impl/EventServiceImpl.java new file mode 100644 index 0000000..34bcde9 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/event/impl/EventServiceImpl.java @@ -0,0 +1,29 @@ +package me.redtea.nodropx.service.event.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.api.event.NoDropItemThrownEvent; +import me.redtea.nodropx.service.event.EventService; +import me.redtea.nodropx.service.nodrop.NoDropService; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.inventory.ItemStack; + +import java.util.Optional; + +@RequiredArgsConstructor +public class EventServiceImpl implements EventService { + private final NoDropService noDropService; + + @Override + public void callThrownEvent(Player who, ItemStack item, NoDropItemThrownEvent.Reason reason, Optional rootEvent) { + if(!noDropService.isNoDrop(item)) return; + NoDropItemThrownEvent callingEvent = new NoDropItemThrownEvent( + who, + reason, + item + ); + Bukkit.getPluginManager().callEvent(callingEvent); + rootEvent.ifPresent(cancellable -> cancellable.setCancelled(callingEvent.isCancelled())); + } +} diff --git a/src/main/java/me/redtea/nodropx/service/nbt/NBTService.java b/src/main/java/me/redtea/nodropx/service/nbt/NBTService.java new file mode 100644 index 0000000..db94323 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/nbt/NBTService.java @@ -0,0 +1,26 @@ +package me.redtea.nodropx.service.nbt; + +import org.bukkit.inventory.ItemStack; + +/** + * Service for working with NBT tags + */ +public interface NBTService { + /** + * Sets the NBT tag "isNoDrop", which indicates that the item is no drop + * @param item editable item + * @param isNoDrop tag value + * @since 1.0.0 + * @author redtea + */ + void setNoDropTag(ItemStack item, boolean isNoDrop); + + /** + * Gets the value of the "isNoDrop" tag, which indicates that the item is no drop + * @param item checking item + * @return tag value + * @since 1.0.0 + * @author redtea + */ + boolean getNoDropTag(ItemStack item); +} diff --git a/src/main/java/me/redtea/nodropx/service/nbt/impl/Tr7zwNBTServiceImpl.java b/src/main/java/me/redtea/nodropx/service/nbt/impl/Tr7zwNBTServiceImpl.java new file mode 100644 index 0000000..b37e382 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/nbt/impl/Tr7zwNBTServiceImpl.java @@ -0,0 +1,22 @@ +package me.redtea.nodropx.service.nbt.impl; + +import de.tr7zw.changeme.nbtapi.NBTItem; +import me.redtea.nodropx.service.nbt.NBTService; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +public class Tr7zwNBTServiceImpl implements NBTService { + @Override + public void setNoDropTag(ItemStack item, boolean isNoDrop) { + NBTItem nbtItem = new NBTItem(item); + nbtItem.setBoolean("isNoDrop", true); + nbtItem.applyNBT(item); + } + + @Override + public boolean getNoDropTag(ItemStack item) { + if(item == null || item.getType() == Material.AIR) return false; + NBTItem nbtItem = new NBTItem(item); + return nbtItem.hasTag("isNoDrop") && nbtItem.getBoolean("isNoDrop"); + } +} diff --git a/src/main/java/me/redtea/nodropx/service/nodrop/NoDropService.java b/src/main/java/me/redtea/nodropx/service/nodrop/NoDropService.java new file mode 100644 index 0000000..e35bb44 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/nodrop/NoDropService.java @@ -0,0 +1,26 @@ +package me.redtea.nodropx.service.nodrop; + +import org.bukkit.inventory.ItemStack; + +/** + * Service for changing and checking no drop status of item + */ +public interface NoDropService { + /** + * Set no drop to item + * @param item the item who's no drop property are changing + * @param isNoDrop will the item can drop from inventory on death? + * @author redtea + * @since 1.0.0 + */ + void setNoDrop(ItemStack item, boolean isNoDrop); + + /** + * Check if item is no drop item + * @param item the item being checked + * @return true if item can not drop from inventory on death + * @author redtea + * @since 1.0.0 + */ + boolean isNoDrop(ItemStack item); +} diff --git a/src/main/java/me/redtea/nodropx/service/nodrop/impl/NoDropServiceImpl.java b/src/main/java/me/redtea/nodropx/service/nodrop/impl/NoDropServiceImpl.java new file mode 100644 index 0000000..0d1dab0 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/nodrop/impl/NoDropServiceImpl.java @@ -0,0 +1,25 @@ +package me.redtea.nodropx.service.nodrop.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.service.cosmetic.CosmeticService; +import me.redtea.nodropx.service.nbt.NBTService; +import me.redtea.nodropx.service.nodrop.NoDropService; +import org.bukkit.inventory.ItemStack; + +@RequiredArgsConstructor +public class NoDropServiceImpl implements NoDropService { + private final NBTService nbtService; + private final CosmeticService cosmeticService; + + @Override + public void setNoDrop(ItemStack item, boolean isNoDrop) { + nbtService.setNoDropTag(item, isNoDrop); + cosmeticService.applyAll(item); + } + + @Override + public boolean isNoDrop(ItemStack item) { + if(item == null) return false; + return nbtService.getNoDropTag(item); + } +} diff --git a/src/main/java/me/redtea/nodropx/service/respawn/RespawnService.java b/src/main/java/me/redtea/nodropx/service/respawn/RespawnService.java new file mode 100644 index 0000000..e1e58b2 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/respawn/RespawnService.java @@ -0,0 +1,9 @@ +package me.redtea.nodropx.service.respawn; + +import org.bukkit.entity.Player; +import org.bukkit.event.entity.PlayerDeathEvent; + +public interface RespawnService { + void giveSavedItems(Player player); + void saveItemsBeforeDeath(PlayerDeathEvent event); +} diff --git a/src/main/java/me/redtea/nodropx/service/respawn/impl/RespawnServiceImpl.java b/src/main/java/me/redtea/nodropx/service/respawn/impl/RespawnServiceImpl.java new file mode 100644 index 0000000..d5ae146 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/respawn/impl/RespawnServiceImpl.java @@ -0,0 +1,70 @@ +package me.redtea.nodropx.service.respawn.impl; + +import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.api.event.NoDropItemDropOnDeathEvent; +import me.redtea.nodropx.libs.carcadex.repo.Repo; +import me.redtea.nodropx.service.nodrop.NoDropService; +import me.redtea.nodropx.service.respawn.RespawnService; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.inventory.ItemStack; + +import java.util.*; + +@RequiredArgsConstructor +public class RespawnServiceImpl implements RespawnService { + private final NoDropService noDropService; + private final Map toRespawn = new HashMap<>(); + private final Repo> capacity; + + + @Override + public void giveSavedItems(Player player) { + if(toRespawn.containsKey(player.getName())) { + Pair[] pairs = toRespawn.get(player.getName()); + for(Pair pair : pairs) player.getInventory().setItem(pair.slot, pair.item); + } + } + + @Override + public void saveItemsBeforeDeath(PlayerDeathEvent event) { + Player player = event.getEntity(); + Set noDropSlots = new HashSet<>(); + for(int i = 0; i < player.getInventory().getSize(); ++i) { + ItemStack item = player.getInventory().getItem(i); + if(!noDropService.isNoDrop(item)) continue; + NoDropItemDropOnDeathEvent callingEvent = new NoDropItemDropOnDeathEvent( + player, + item + ); + Bukkit.getPluginManager().callEvent(callingEvent); + + if(!callingEvent.isWillDropped()) { + noDropSlots.add(i); + noDropSlots.addAll(getProtectedSlots(item)); + } + } + + List toSave = new ArrayList<>(); + for(int slot : noDropSlots) { + ItemStack item = player.getInventory().getItem(slot); + toSave.add(new Pair(slot, item)); + event.getDrops().remove(item); + } + + if(toSave.isEmpty()) return; + toRespawn.put(player.getName(), toSave.toArray(new Pair[0])); + } + + @AllArgsConstructor + static class Pair { + int slot; + ItemStack item; + } + + private List getProtectedSlots(ItemStack i) { + return capacity.get(i.getType().name()).orElse(new ArrayList<>()); + } +} diff --git a/src/main/java/me/redtea/nodropx/service/storage/StorageService.java b/src/main/java/me/redtea/nodropx/service/storage/StorageService.java new file mode 100644 index 0000000..1c460a3 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/storage/StorageService.java @@ -0,0 +1,12 @@ +package me.redtea.nodropx.service.storage; + +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.UUID; + +public interface StorageService { + void addItem(UUID playerId, ItemStack item); + void removeItem(UUID playerId, ItemStack item); + List getItems(UUID playerId); +} diff --git a/src/main/java/me/redtea/nodropx/service/storage/impl/StorageServiceImpl.java b/src/main/java/me/redtea/nodropx/service/storage/impl/StorageServiceImpl.java new file mode 100644 index 0000000..956743f --- /dev/null +++ b/src/main/java/me/redtea/nodropx/service/storage/impl/StorageServiceImpl.java @@ -0,0 +1,43 @@ +package me.redtea.nodropx.service.storage.impl; + +import lombok.RequiredArgsConstructor; +import me.redtea.nodropx.gui.facade.GuiFacade; +import me.redtea.nodropx.libs.carcadex.repo.MutableRepo; +import me.redtea.nodropx.service.nodrop.NoDropService; +import me.redtea.nodropx.service.storage.StorageService; +import org.bukkit.Bukkit; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@RequiredArgsConstructor +public class StorageServiceImpl implements StorageService { + private final GuiFacade guiFacade; + private final MutableRepo> pagesRepo; + private final NoDropService noDropService; + + @Override + public void addItem(UUID playerId, ItemStack item) { + List items = pagesRepo.get(playerId).orElse(new ArrayList<>()); + if(!noDropService.isNoDrop(item)) throw new IllegalArgumentException("Item must be no drop!"); + items.add(item); + pagesRepo.update(playerId, items); + guiFacade.clearCache(Bukkit.getPlayer(playerId)); + } + + @Override + public void removeItem(UUID playerId, ItemStack item) { + List items = pagesRepo.get(playerId).orElse(null); + if(items == null) return; + items.remove(item); + pagesRepo.update(playerId, items); + guiFacade.clearCache(Bukkit.getPlayer(playerId)); + } + + @Override + public List getItems(UUID playerId) { + return pagesRepo.get(playerId).orElse(new ArrayList<>()); + } +} diff --git a/src/main/java/me/redtea/nodropx/util/MD5ColorUtils.java b/src/main/java/me/redtea/nodropx/util/MD5ColorUtils.java new file mode 100644 index 0000000..497ad9f --- /dev/null +++ b/src/main/java/me/redtea/nodropx/util/MD5ColorUtils.java @@ -0,0 +1,26 @@ +package me.redtea.nodropx.util; + +import net.md_5.bungee.api.ChatColor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static net.md_5.bungee.api.ChatColor.COLOR_CHAR; + +public class MD5ColorUtils { + private static final Pattern HEX_PATTERN = Pattern.compile("&#([a-f0-9]{6})"); + + public static String translateHexColorCodes(String message) { + Matcher matcher = HEX_PATTERN.matcher(message); + StringBuffer buffer = new StringBuffer(message.length() + 4 * 8); + while (matcher.find()) { + String group = matcher.group(1); + matcher.appendReplacement(buffer, COLOR_CHAR + "x" + + COLOR_CHAR + group.charAt(0) + COLOR_CHAR + group.charAt(1) + + COLOR_CHAR + group.charAt(2) + COLOR_CHAR + group.charAt(3) + + COLOR_CHAR + group.charAt(4) + COLOR_CHAR + group.charAt(5) + ); + } + return ChatColor.translateAlternateColorCodes('&', matcher.appendTail(buffer).toString()); + } +} diff --git a/src/main/java/me/redtea/nodropx/util/MaterialUtils.java b/src/main/java/me/redtea/nodropx/util/MaterialUtils.java new file mode 100644 index 0000000..2a15873 --- /dev/null +++ b/src/main/java/me/redtea/nodropx/util/MaterialUtils.java @@ -0,0 +1,50 @@ +package me.redtea.nodropx.util; + +import lombok.val; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +public class MaterialUtils { + @NotNull + public static Material getMaterialFromString(String s) { + if(s == null || s.equals("")) return Material.AIR; + Material material = Material.matchMaterial(s); + if(material != null) return material; + + String[] spl = s.split(":"); + + if (spl[0].equals("minecraft")) { + material = Material.matchMaterial(spl[1]); + } else { + if(spl.length < 2) { + material = Material.matchMaterial(spl[0]); + } else { + material = Material.matchMaterial(spl[0]+"_"+spl[1]);; + } + } + + return material == null ? Material.AIR : material; + } + + public void remove(Inventory inventory, Material type, int amount) { + if (amount <= 0) return; + int size = inventory.getSize(); + for (int slot = 0; slot < size; slot++) { + ItemStack is = inventory.getItem(slot); + if (is == null) continue; + if (type == is.getType()) { + int newAmount = is.getAmount() - amount; + if (newAmount > 0) { + is.setAmount(newAmount); + break; + } else { + inventory.clear(slot); + amount = -newAmount; + if (amount == 0) break; + } + } + } + } +} diff --git a/src/main/resources/capacity.yml b/src/main/resources/capacity.yml new file mode 100644 index 0000000..1b2b149 --- /dev/null +++ b/src/main/resources/capacity.yml @@ -0,0 +1 @@ +CHAINMAIL_HELMET: [ 9, 10, 11, 12, 13, 14, 15, 16, 17 ] \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..15f7d0a --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,2 @@ +forceUseLegacy: false +forceUseMD5Colors: false \ 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 0000000..dea5558 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,16 @@ +name: NoDropX +version: '${version}' +main: me.redtea.nodropx.NoDropX +api-version: '1.13' +description: Set no drop status to your items +author: itzRedTea +authors: [itzRedTea] + +permissions: + nodropx.give: + description: "Allows give no drop items" + nodropx.reload: + description: "Allows reload the plugin" + nodropx.gui: + escription: "Allows open no drop gui" + default: true \ No newline at end of file diff --git a/src/main/resources/text/legacy/cosmetics.yml b/src/main/resources/text/legacy/cosmetics.yml new file mode 100644 index 0000000..47c1ccb --- /dev/null +++ b/src/main/resources/text/legacy/cosmetics.yml @@ -0,0 +1,3 @@ +#name: "No drop" +lore: + - "&7[&a✔&7] &aNo drop item!" \ No newline at end of file diff --git a/src/main/resources/text/legacy/messages.yml b/src/main/resources/text/legacy/messages.yml new file mode 100644 index 0000000..edf28a7 --- /dev/null +++ b/src/main/resources/text/legacy/messages.yml @@ -0,0 +1,23 @@ +usage: + - "&7[ &aUsage of command &e/nodrop &7]" + - "&7| &a/give [player] [item] (amount) &e- give no drop item" +give: + fail: "&4[!] &cFail! Check item type for valid." + success: "&2&l[!] &aGived &e%item%&a to &e%player%&a amount &e%amount%&a!" +reload: "&aPlugin successfully reloaded at %time% ms" + +noPerms: "&4&l[!] &cNo perms for this!" +noConsole: "&4&l[!] &cOnly players can use it!" +noExists: "&4&l[!] &cThis player doesnt exists!" + +gui: + title: "&lPersonal storage" + frame: + name: "&7" + material: GLASS_PANE + prev: + name: "&cPrevious" + material: ARROW + next: + name: "&cNext" + material: ARROW \ No newline at end of file diff --git a/src/main/resources/text/md5/cosmetics.yml b/src/main/resources/text/md5/cosmetics.yml new file mode 100644 index 0000000..51bf120 --- /dev/null +++ b/src/main/resources/text/md5/cosmetics.yml @@ -0,0 +1,3 @@ +#name: "No drop" +lore: + - "&7[&a✔&7] Dcf0cNcd41fo fd932dIdd46rMe259obe76cp eec7fiRf192tVf5a6eYfab9mdffcc!!" \ No newline at end of file diff --git a/src/main/resources/text/md5/messages.yml b/src/main/resources/text/md5/messages.yml new file mode 100644 index 0000000..edf28a7 --- /dev/null +++ b/src/main/resources/text/md5/messages.yml @@ -0,0 +1,23 @@ +usage: + - "&7[ &aUsage of command &e/nodrop &7]" + - "&7| &a/give [player] [item] (amount) &e- give no drop item" +give: + fail: "&4[!] &cFail! Check item type for valid." + success: "&2&l[!] &aGived &e%item%&a to &e%player%&a amount &e%amount%&a!" +reload: "&aPlugin successfully reloaded at %time% ms" + +noPerms: "&4&l[!] &cNo perms for this!" +noConsole: "&4&l[!] &cOnly players can use it!" +noExists: "&4&l[!] &cThis player doesnt exists!" + +gui: + title: "&lPersonal storage" + frame: + name: "&7" + material: GLASS_PANE + prev: + name: "&cPrevious" + material: ARROW + next: + name: "&cNext" + material: ARROW \ No newline at end of file diff --git a/src/main/resources/text/mm/cosmetics.yml b/src/main/resources/text/mm/cosmetics.yml new file mode 100644 index 0000000..b9d0365 --- /dev/null +++ b/src/main/resources/text/mm/cosmetics.yml @@ -0,0 +1,3 @@ +#name: "No drop" +lore: + - "[✔] No drop item!" \ No newline at end of file diff --git a/src/main/resources/text/mm/messages.yml b/src/main/resources/text/mm/messages.yml new file mode 100644 index 0000000..8362d99 --- /dev/null +++ b/src/main/resources/text/mm/messages.yml @@ -0,0 +1,24 @@ +usage: + - "[ Usage of command /nodrop ]" + - "| /give [player] [item] (amount) - give no drop item" +give: + fail: "[!] Fail! Check item type for valid." + success: "[!] Gived %item% to %player% amount %amount%!" +reload: "Plugin successfully reloaded at %time% ms" + + +noPerms: "[!] No perms for this!" +noConsole: "[!] Only players can use it!" +noExists: "[!] This player doesnt exists!" + +gui: + title: "Personal storage" + frame: + name: "&7" + material: GLASS_PANE + prev: + name: "Previous" + material: ARROW + next: + name: "Next" + material: ARROW \ No newline at end of file