├── src └── main │ ├── resources │ ├── transaction.log │ ├── plugin.yml │ └── config.yml │ └── java │ └── net │ └── mackenziemolloy │ └── shopguiplus │ └── sellgui │ ├── objects │ └── ShopItemPriceValue.java │ ├── utility │ ├── LogFormatter.java │ ├── FileUtils.java │ ├── sirblobman │ │ ├── Validate.java │ │ ├── VersionUtility.java │ │ ├── HexColorUtility.java │ │ └── MessageUtility.java │ ├── UpdateChecker.java │ ├── Hastebin.java │ ├── StringFormatter.java │ ├── ShopHandler.java │ ├── PlayerHandler.java │ └── CommentedConfiguration.java │ ├── SellGUI.java │ └── command │ └── CommandSellGUI.java ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.MD ├── .gitignore ├── gradlew.bat └── gradlew /src/main/resources/transaction.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ShopGUIPlus-SellGUI' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MackenzieMolloy/ShopGUIPlus-SellGUI/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/objects/ShopItemPriceValue.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.objects; 2 | 3 | import net.brcdev.shopgui.economy.EconomyType; 4 | 5 | public class ShopItemPriceValue { 6 | 7 | private final EconomyType economyType; 8 | private final double sellPrice; 9 | 10 | public ShopItemPriceValue(EconomyType economyType, double sellPrice) { 11 | this.economyType = economyType; 12 | this.sellPrice = sellPrice; 13 | } 14 | 15 | public EconomyType getEconomyType() { 16 | return economyType; 17 | } 18 | 19 | public double getSellPrice() { 20 | return sellPrice; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: "ShopGUIPlus-SellGUI" 2 | prefix: "ShopGUIPlus Sell GUI" 3 | description: "An addon for ShopGUIPlus that enables players to easily sell items in seconds, without having to go searching through the shops for them." 4 | website: "https://www.spigotmc.org/resources/85170/" 5 | 6 | main: "net.mackenziemolloy.shopguiplus.sellgui.SellGUI" 7 | version: "1.1.8" 8 | api-version: "1.13" 9 | folia-supported: true 10 | 11 | authors: 12 | - "Mackenzie Molloy" 13 | - "SirBlobman" 14 | 15 | depend: 16 | - "ShopGUIPlus" 17 | 18 | commands: 19 | sellgui: 20 | description: "Sell Command" 21 | aliases: 22 | - "sg" 23 | - "sellg" 24 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/LogFormatter.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 4 | 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.Objects; 9 | import java.util.logging.Formatter; 10 | import java.util.logging.LogRecord; 11 | 12 | public class LogFormatter extends Formatter { 13 | 14 | private final DateFormat dateFormat = new SimpleDateFormat(Objects.requireNonNull(SellGUI.getInstance().getConfiguration().getString("options.transaction_log.date_format"))); 15 | 16 | @Override 17 | public String format(LogRecord logRecord) { 18 | return getDateAndTime() + " " + logRecord.getMessage() + "\n"; 19 | } 20 | 21 | private String getDateAndTime() { 22 | Date date = new Date(); 23 | return dateFormat.format(date); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | ![](https://i.imgur.com/kKx5gUu.png) 2 | 3 | An addon for [ShopGUIPlus](https://www.spigotmc.org/resources/shopgui-1-7-1-16.6515/) that enables players to easily sell items in seconds, without having to go searching through the shops for them. 4 | 5 | ![](https://i.imgur.com/P6mPXDK.gif) 6 | 7 | ![](https://i.imgur.com/h7Ruzj8.png) 8 | Supports all currencies supported by ShopGUIPlus 9 | 10 | Supports all hooks (HeadDatabase, etc) supported by ShopGUIPlus 11 | 12 | Supports from 1.8, to 1.20 13 | 14 | Fully customisable messages 15 | 16 | Action bar, and title messages 17 | 18 | (Cool lookin') receipts 19 | 20 | ![](https://i.imgur.com/NZ9sc2w.png) 21 | 22 | The permissions are as follows; 23 | 24 | sellgui.use grants the user access to use the SellGUI 25 | 26 | sellgui.dump grants the user access to the dump debug command 27 | 28 | sellgui.reload grants the user access to reload the configuration 29 | 30 | ![](https://i.imgur.com/u2IFwlB.png)![](https://i.imgur.com/mXnsSK9.png)![](https://i.imgur.com/SK5ulCR.png) 31 | 32 | 33 | ## Information 34 | - [Installation](https://github.com/MackenzieMolloy/ShopGUIPlus-SellGUI/wiki/Installation) 35 | - [Configuration](https://github.com/MackenzieMolloy/ShopGUIPlus-SellGUI/wiki/Configuration) 36 | - [Getting Support](https://github.com/MackenzieMolloy/ShopGUIPlus-SellGUI/wiki/Getting-Support) 37 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/FileUtils.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 9 | 10 | public class FileUtils { 11 | 12 | public static void copy(InputStream in, File file) { 13 | try { 14 | OutputStream out = new FileOutputStream(file); 15 | byte[] buffer = new byte[1024]; 16 | int length; 17 | while ((length = in.read(buffer)) > 0) { 18 | out.write(buffer, 0, length); 19 | } 20 | out.close(); 21 | in.close(); 22 | } catch (IOException ex) { 23 | ex.printStackTrace(); 24 | } 25 | } 26 | 27 | public static void mkdir(File file) { 28 | try { 29 | file.mkdir(); 30 | } catch (Exception ex) { 31 | ex.printStackTrace(); 32 | } 33 | } 34 | 35 | public static File loadFile(String name) { 36 | if (!SellGUI.getInstance().getDataFolder().exists()) { 37 | FileUtils.mkdir(SellGUI.getInstance().getDataFolder()); 38 | } 39 | 40 | File f = new File(SellGUI.getInstance().getDataFolder(), name); 41 | if (!f.exists()) { 42 | FileUtils.copy(SellGUI.getInstance().getResource(name), f); 43 | } 44 | 45 | return f; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/sirblobman/Validate.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | public final class Validate { 10 | 11 | public static @NotNull O notNull(@Nullable O value, @NotNull String message) { 12 | if (value == null) { 13 | throw new IllegalArgumentException(message); 14 | } 15 | 16 | return value; 17 | } 18 | 19 | public static @NotNull String notEmpty(@Nullable String value, @NotNull String message) { 20 | notNull(value, message); 21 | 22 | if (value.isEmpty()) { 23 | throw new IllegalArgumentException(message); 24 | } 25 | 26 | return value; 27 | } 28 | 29 | public static > @NotNull C notEmpty(@Nullable C value, @NotNull String message) { 30 | notNull(value, message); 31 | 32 | if (value.isEmpty()) { 33 | throw new IllegalArgumentException(message); 34 | } 35 | 36 | return value; 37 | } 38 | 39 | public static > @NotNull M notEmpty(@Nullable M value, @NotNull String message) { 40 | notNull(value, message); 41 | 42 | if (value.isEmpty()) { 43 | throw new IllegalArgumentException(message); 44 | } 45 | 46 | return value; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################## 2 | ## Java 3 | ############################## 4 | .mtj.tmp/ 5 | *.class 6 | *.jar 7 | *.war 8 | *.ear 9 | *.nar 10 | hs_err_pid* 11 | 12 | ############################## 13 | ## Maven 14 | ############################## 15 | target/ 16 | pom.xml.tag 17 | pom.xml.releaseBackup 18 | pom.xml.versionsBackup 19 | pom.xml.next 20 | pom.xml.bak 21 | release.properties 22 | dependency-reduced-pom.xml 23 | buildNumber.properties 24 | .mvn/timing.properties 25 | .mvn/wrapper/maven-wrapper.jar 26 | 27 | ############################## 28 | ## Gradle 29 | ############################## 30 | bin/ 31 | build/ 32 | .gradle 33 | .gradletasknamecache 34 | gradle-app.setting 35 | !gradle-wrapper.jar 36 | 37 | ############################## 38 | ## IntelliJ 39 | ############################## 40 | out/ 41 | .idea/ 42 | .idea_modules/ 43 | *.iml 44 | *.ipr 45 | *.iws 46 | 47 | ############################## 48 | ## Eclipse 49 | ############################## 50 | .settings/ 51 | bin/ 52 | tmp/ 53 | .metadata 54 | .classpath 55 | .project 56 | *.tmp 57 | *.bak 58 | *.swp 59 | *~.nib 60 | local.properties 61 | .loadpath 62 | .factorypath 63 | 64 | ############################## 65 | ## NetBeans 66 | ############################## 67 | nbproject/private/ 68 | build/ 69 | nbbuild/ 70 | dist/ 71 | nbdist/ 72 | nbactions.xml 73 | nb-configuration.xml 74 | 75 | ############################## 76 | ## Visual Studio Code 77 | ############################## 78 | .vscode/ 79 | .code-workspace 80 | 81 | ############################## 82 | ## OS X 83 | ############################## 84 | .DS_Store 85 | ._* 86 | 87 | ############################## 88 | ## Windows 89 | ############################## 90 | ~$* 91 | *.tmp 92 | /libs/ 93 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/UpdateChecker.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.URL; 6 | import java.util.Locale; 7 | import java.util.Scanner; 8 | import java.util.function.Consumer; 9 | import java.util.logging.Level; 10 | import java.util.logging.Logger; 11 | 12 | import com.tcoded.folialib.impl.PlatformScheduler; 13 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 14 | import org.bukkit.plugin.java.JavaPlugin; 15 | 16 | public class UpdateChecker { 17 | 18 | private final JavaPlugin plugin; 19 | private final PlatformScheduler scheduler; 20 | private final int resourceId; 21 | 22 | public UpdateChecker(JavaPlugin plugin, int resourceId) { 23 | this.plugin = plugin; 24 | this.scheduler = SellGUI.scheduler(); 25 | this.resourceId = resourceId; 26 | } 27 | 28 | public void getVersion(final Consumer consumer) { 29 | scheduler.runAsync(task -> getVersionInternal(consumer)); 30 | } 31 | 32 | private void getVersionInternal(Consumer consumer) { 33 | String updateUrlFormat = ("https://api.spigotmc.org/legacy/update.php?resource=%s"); 34 | String updateUrl = String.format(Locale.US, updateUrlFormat, this.resourceId); 35 | 36 | try ( 37 | InputStream inputStream = new URL(updateUrl).openStream(); 38 | Scanner scanner = new Scanner(inputStream)) { 39 | if (scanner.hasNext()) { 40 | consumer.accept(scanner.next()); 41 | } 42 | } catch (IOException ex) { 43 | Logger logger = this.plugin.getLogger(); 44 | logger.log(Level.INFO, "Failed to check for updates because an error occurred:", ex); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/Hastebin.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.io.InputStreamReader; 9 | import java.net.URL; 10 | import java.nio.charset.StandardCharsets; 11 | import javax.net.ssl.HttpsURLConnection; 12 | 13 | public class Hastebin { 14 | 15 | private static final String BASE_URL = "https://paste.helpch.at/"; 16 | private static final String API_URL = "https://paste.helpch.at/documents"; 17 | 18 | public String post(String text, boolean raw) throws IOException { 19 | byte[] postData = text.getBytes(StandardCharsets.UTF_8); 20 | HttpsURLConnection conn = getHttpsURLConnection(postData); 21 | 22 | String response = ""; 23 | DataOutputStream wr; 24 | 25 | try { 26 | wr = new DataOutputStream(conn.getOutputStream()); 27 | wr.write(postData); 28 | BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); 29 | String readLine = reader.readLine(); 30 | response = readLine.contains("key") ? BASE_URL + readLine.substring(readLine.indexOf(":") + 2, readLine.length() - 2) : readLine; 31 | } catch (IOException e) { 32 | e.printStackTrace(); 33 | } 34 | 35 | return response; 36 | } 37 | 38 | private static @NotNull HttpsURLConnection getHttpsURLConnection(byte[] postData) throws IOException { 39 | URL url = new URL(API_URL); 40 | HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 41 | 42 | conn.setDoOutput(true); 43 | conn.setInstanceFollowRedirects(false); 44 | conn.setRequestMethod("POST"); 45 | conn.setRequestProperty("User-Agent", "Hastebin Java API"); 46 | conn.setRequestProperty("Content-Length", Integer.toString(postData.length)); 47 | conn.setUseCaches(false); 48 | 49 | return conn; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/StringFormatter.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | import java.text.DecimalFormat; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | 10 | public class StringFormatter { 11 | 12 | public static ArrayList quantityCodes = new ArrayList<>(Arrays.asList("K", "M", "B", "T", "Q", "Qa", "Sx", "Sp", "Oc", "No", "De", "Un", "Du", "Tr", "Qu", "Qi", "Se", "Sev", "Oc", "Nov", "Vg", "C")); 13 | 14 | public static String capitalize(String string) { 15 | return string.substring(0, 1).toUpperCase() + string.substring(1); 16 | } 17 | 18 | public static String abbreviateQuantity(double count) { 19 | if (count < 1000) { 20 | return String.valueOf(count); 21 | } 22 | 23 | int exp = (int) (Math.log(count) / Math.log(1000)); 24 | 25 | DecimalFormat format = new DecimalFormat("0.##"); 26 | String val = format.format(count / Math.pow(1000, exp)); 27 | 28 | return String.format("%s%s", val, quantityCodes.get(exp-1)); 29 | } 30 | 31 | public static String getFormattedNumber(Double numberToFormat) { 32 | SellGUI plugin = JavaPlugin.getPlugin(SellGUI.class); 33 | CommentedConfiguration configuration = plugin.getConfiguration(); 34 | String numberToReturn = numberToFormat.toString(); 35 | 36 | if (configuration.getBoolean("options.rounded_pricing")) { 37 | final DecimalFormat formatToApply = new DecimalFormat("#,##0.00"); 38 | numberToReturn = formatToApply.format(numberToFormat); 39 | } 40 | 41 | if (configuration.getBoolean("options.remove_trailing_zeros")) { 42 | if (numberToReturn.split("\\.")[1] != null) { 43 | if (Double.parseDouble(numberToReturn.split("\\.")[1]) == 0) { 44 | numberToReturn = numberToReturn.split("\\.")[0]; 45 | } 46 | } 47 | } 48 | 49 | return configuration.getBoolean("options.abbreviate_numbers") && !configuration.getBoolean("options.rounded_pricing") ? abbreviateQuantity(Double.parseDouble(numberToReturn)) : numberToReturn; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/ShopHandler.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import java.text.DecimalFormat; 4 | import java.util.Locale; 5 | 6 | import org.bukkit.entity.Player; 7 | import org.bukkit.inventory.ItemStack; 8 | import org.bukkit.plugin.java.JavaPlugin; 9 | 10 | import net.brcdev.shopgui.ShopGuiPlusApi; 11 | import net.brcdev.shopgui.economy.EconomyManager; 12 | import net.brcdev.shopgui.economy.EconomyType; 13 | import net.brcdev.shopgui.provider.economy.EconomyProvider; 14 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 15 | import org.jetbrains.annotations.NotNull; 16 | 17 | public class ShopHandler { 18 | 19 | @NotNull 20 | public static EconomyType getEconomyType(ItemStack material) { 21 | EconomyType economyType = ShopGuiPlusApi.getItemStackShop(material).getEconomyType(); 22 | if (economyType != null) { 23 | return economyType; 24 | } 25 | 26 | EconomyManager economyManager = ShopGuiPlusApi.getPlugin().getEconomyManager(); 27 | EconomyProvider defaultEconomyProvider = economyManager.getDefaultEconomyProvider(); 28 | if (defaultEconomyProvider != null) { 29 | String defaultEconomyTypeName = defaultEconomyProvider.getName().toUpperCase(Locale.US); 30 | try { 31 | return EconomyType.valueOf(defaultEconomyTypeName); 32 | } catch (IllegalArgumentException ex) { 33 | return EconomyType.CUSTOM; 34 | } 35 | } 36 | 37 | return EconomyType.CUSTOM; 38 | } 39 | 40 | public static Double getItemSellPrice(ItemStack material, Player player) { 41 | return ShopGuiPlusApi.getItemStackPriceSell(player, material); 42 | } 43 | 44 | public static String getFormattedPrice(Double priceToFormat, EconomyType economyType) { 45 | SellGUI plugin = JavaPlugin.getPlugin(SellGUI.class); 46 | CommentedConfiguration configuration = plugin.getConfiguration(); 47 | String priceToReturn = priceToFormat.toString(); 48 | 49 | if (configuration.getBoolean("options.rounded_pricing")) { 50 | DecimalFormat formatToApplyRaw = new DecimalFormat("0.00"); 51 | priceToReturn = formatToApplyRaw.format(priceToFormat); 52 | } 53 | 54 | if (configuration.getBoolean("options.remove_trailing_zeros")) { 55 | if (Double.valueOf(priceToReturn.split("\\.")[1]) == 0) { 56 | priceToReturn = priceToReturn.split("\\.")[0]; 57 | } 58 | } 59 | 60 | return priceToReturn; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/sirblobman/VersionUtility.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman; 2 | 3 | import java.util.logging.Logger; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.Server; 9 | 10 | public final class VersionUtility { 11 | 12 | static { 13 | String bukkitVersion = Bukkit.getBukkitVersion(); 14 | if (bukkitVersion.contains("-pre") || bukkitVersion.contains("-rc")) { 15 | Logger logger = Bukkit.getLogger(); 16 | logger.warning("[ShopGUIPlus-SellGUI] You are using a '-pre' or '-rc' version of spigot."); 17 | logger.warning("[ShopGUIPlus-SellGUI] Bugs may occur when using a preview version."); 18 | } 19 | } 20 | 21 | /** 22 | * @return The current Minecraft version of the server (Example: 1.16.5) 23 | */ 24 | public static @NotNull String getMinecraftVersion() { 25 | String bukkitVersion = Bukkit.getBukkitVersion(); 26 | int firstDash = bukkitVersion.indexOf('-'); 27 | return bukkitVersion.substring(0, firstDash); 28 | } 29 | 30 | /** 31 | * @return The current NMS version of the server (Example: 1_16_R3) 32 | */ 33 | public static @NotNull String getNetMinecraftServerVersion() { 34 | Server server = Bukkit.getServer(); 35 | Class serverClass = server.getClass(); 36 | Package serverPackage = serverClass.getPackage(); 37 | String serverPackageName = serverPackage.getName(); 38 | 39 | int lastPeriodIndex = serverPackageName.lastIndexOf('.'); 40 | int nextIndex = (lastPeriodIndex + 2); 41 | return serverPackageName.substring(nextIndex); 42 | } 43 | 44 | /** 45 | * @return The current major. Minor version of the server (Example: 1.16) 46 | */ 47 | public static @NotNull String getMajorMinorVersion() { 48 | String minecraftVersion = getMinecraftVersion(); 49 | int lastPeriodIndex = minecraftVersion.lastIndexOf('.'); 50 | return (lastPeriodIndex < 2 ? minecraftVersion : minecraftVersion.substring(0, lastPeriodIndex)); 51 | } 52 | 53 | /** 54 | * @return The current major version of the server as an integer (Example: {@code 1}) 55 | */ 56 | public static int getMajorVersion() { 57 | String majorMinorVersion = getMajorMinorVersion(); 58 | int periodIndex = majorMinorVersion.indexOf('.'); 59 | 60 | String majorString = majorMinorVersion.substring(0, periodIndex); 61 | return Integer.parseInt(majorString); 62 | } 63 | 64 | /** 65 | * @return The current minor version of the server as an integer (Example: {@code 16}) 66 | */ 67 | public static int getMinorVersion() { 68 | String majorMinorVersion = getMajorMinorVersion(); 69 | int periodIndex = majorMinorVersion.indexOf('.'); 70 | int nextIndex = (periodIndex + 1); 71 | 72 | String minorString = majorMinorVersion.substring(nextIndex); 73 | return Integer.parseInt(minorString); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/PlayerHandler.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | import java.util.Locale; 6 | 7 | import net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman.VersionUtility; 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.ChatColor; 10 | import org.bukkit.Location; 11 | import org.bukkit.Sound; 12 | import org.bukkit.command.CommandSender; 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.plugin.java.JavaPlugin; 15 | 16 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 17 | 18 | public class PlayerHandler { 19 | 20 | private static Sound getSound(String event) { 21 | SellGUI plugin = JavaPlugin.getPlugin(SellGUI.class); 22 | CommentedConfiguration configuration = plugin.getConfiguration(); 23 | String soundPath = String.format(Locale.US, "options.sounds.events.%s", event); 24 | 25 | String soundValue = configuration.getString(soundPath); 26 | 27 | try { 28 | // Minecraft 1.12 has some older sound class 29 | if (VersionUtility.getMinorVersion() < 13) { 30 | // Dynamically resolve org.bukkit.Sound 31 | Class soundClass = Class.forName("org.bukkit.Sound"); 32 | 33 | // Use reflection to call valueOf(String) 34 | Method valueOfMethod = soundClass.getMethod("valueOf", String.class); 35 | Object soundEnum = valueOfMethod.invoke(null, soundValue); 36 | 37 | return (Sound) soundEnum; 38 | } 39 | 40 | return Sound.valueOf(soundValue); 41 | } catch (IllegalAccessException | InvocationTargetException | ClassNotFoundException | IllegalArgumentException | NoSuchMethodException ex) { 42 | if (configuration.getBoolean("options.sounds.error_notification")) { 43 | CommandSender console = Bukkit.getConsoleSender(); 44 | console.sendMessage(ChatColor.DARK_RED + "[ShopGUIPlus-SellGUI] Invalid Sound for version " 45 | + plugin.getVersion() + " => '" + configuration.getString("options.sounds.events." 46 | + event) + "' (failed) "); 47 | } 48 | 49 | return null; 50 | } 51 | } 52 | 53 | public static void playSound(Player player, String event) { 54 | SellGUI plugin = JavaPlugin.getPlugin(SellGUI.class); 55 | CommentedConfiguration configuration = plugin.getConfiguration(); 56 | if (!configuration.getBoolean("options.sounds.enabled")) { 57 | return; 58 | } 59 | 60 | float volume = 1.0F; 61 | float pitch = 1.0F; 62 | 63 | if (configuration.isDouble("options.sounds.pitch") || configuration.isInt("options.sounds.pitch")) { 64 | pitch = (float) configuration.getDouble("options.sounds.pitch"); 65 | } 66 | 67 | if (configuration.isDouble("options.sounds.volume") || configuration.isInt("options.sounds.volume")) { 68 | volume = (float) configuration.getDouble("options.sounds.volume"); 69 | } 70 | 71 | Location location = player.getLocation(); 72 | Sound sound = getSound(event); 73 | if (sound == null) return; 74 | 75 | player.playSound(location, sound, volume, pitch); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/sirblobman/HexColorUtility.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | import net.md_5.bungee.api.ChatColor; 11 | import net.kyori.adventure.text.Component; 12 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 13 | 14 | public final class HexColorUtility { 15 | 16 | private static final Map KNOWN_PATTERN_MAP = new ConcurrentHashMap<>(); 17 | 18 | /** 19 | * @param colorChar The character that will be used for color codes (usually {@code '&'}) 20 | * @param string The {@link String} that will have its values replaced. 21 | * @return A new {@link String} with the hex color codes being replaced. 22 | */ 23 | public static @NotNull String replaceHexColors(char colorChar, @NotNull String string) { 24 | Pattern pattern = getReplaceAllRgbPattern(colorChar); 25 | Matcher matcher = pattern.matcher(string); 26 | 27 | StringBuilder buffer = new StringBuilder(); 28 | while (matcher.find()) { 29 | if (matcher.group(1) != null) { 30 | matcher.appendReplacement(buffer, colorChar + "#$2"); 31 | continue; 32 | } 33 | 34 | try { 35 | String hexCodeString = matcher.group(2); 36 | String hexCode = parseHexColor(hexCodeString); 37 | matcher.appendReplacement(buffer, hexCode); 38 | } catch (NumberFormatException ignored) { 39 | // Ignored Exception 40 | } 41 | } 42 | 43 | matcher.appendTail(buffer); 44 | 45 | // Normalize Adventure legacy serializers so that colors reset styles (bold/italic/etc.) 46 | Component component = LegacyComponentSerializer.legacyAmpersand().deserialize(buffer.toString()); 47 | return LegacyComponentSerializer.legacySection().serialize(component); 48 | } 49 | 50 | private static Pattern getReplaceAllRgbPattern(char colorChar) { 51 | if (KNOWN_PATTERN_MAP.containsKey(colorChar)) { 52 | return KNOWN_PATTERN_MAP.get(colorChar); 53 | } 54 | 55 | String colorCharString = Character.toString(colorChar); 56 | String colorCharPattern = Pattern.quote(colorCharString); 57 | 58 | String patternString = ("(" + colorCharPattern + ")?" + colorCharPattern + "#([0-9a-fA-F]{6})"); 59 | Pattern pattern = Pattern.compile(patternString); 60 | KNOWN_PATTERN_MAP.put(colorChar, pattern); 61 | return pattern; 62 | } 63 | 64 | private static @NotNull String parseHexColor(@NotNull String string) throws NumberFormatException { 65 | if (string.startsWith("#")) { 66 | string = string.substring(1); 67 | } 68 | 69 | if (string.length() != 6) { 70 | throw new NumberFormatException("Invalid hex length!"); 71 | } 72 | 73 | int colorInt = Integer.decode("#" + string); 74 | if (colorInt < 0x000000 || colorInt > 0xFFFFFF) { 75 | throw new NumberFormatException("Invalid hex color value!"); 76 | } 77 | 78 | StringBuilder assembled = new StringBuilder(); 79 | assembled.append(ChatColor.COLOR_CHAR); 80 | assembled.append("x"); 81 | 82 | char[] charArray = string.toCharArray(); 83 | for (char character : charArray) { 84 | assembled.append(ChatColor.COLOR_CHAR); 85 | assembled.append(character); 86 | } 87 | 88 | return assembled.toString(); 89 | } 90 | 91 | public static String purgeAllColor(String input) { 92 | // Remove Minecraft color codes (e.g., &7) 93 | String noMinecraftColorCodes = input.replaceAll("[&§][0-9a-fA-FkKlLnNoOrR]", ""); 94 | 95 | // Remove hex color codes (e.g., &#FFFF0) 96 | return noMinecraftColorCodes.replaceAll("[&§]#([0-9a-fA-F]){6}", ""); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/sirblobman/MessageUtility.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public final class MessageUtility { 10 | 11 | /** 12 | * @param message The message that will be colored 13 | * @return A new string containing {@code message} but with the color codes replaced, 14 | * or an empty string if {@code message} was {@code null}. 15 | * @see org.bukkit.ChatColor#translateAlternateColorCodes(char, String) 16 | * @see net.md_5.bungee.api.ChatColor#translateAlternateColorCodes(char, String) 17 | * @see HexColorUtility#replaceHexColors(char, String) 18 | */ 19 | public static @NotNull String color(@NotNull String message) { 20 | try { 21 | Class.forName("net.md_5.bungee.api.ChatColor"); 22 | return net.md_5.bungee.api.ChatColor.translateAlternateColorCodes('&', message); 23 | } catch (ReflectiveOperationException ex) { 24 | return org.bukkit.ChatColor.translateAlternateColorCodes('&', message); 25 | } 26 | } 27 | 28 | /** 29 | * @param messageArray The array of messages that will be colored 30 | * @return A new array containing every message in the input array, but with color codes replaced. 31 | */ 32 | public static String @NotNull [] colorArray(String @NotNull ... messageArray) { 33 | int messageArrayLength = messageArray.length; 34 | String[] colorMessageArray = new String[messageArrayLength]; 35 | 36 | for (int i = 0; i < messageArrayLength; i++) { 37 | String message = messageArray[i]; 38 | colorMessageArray[i] = color(message); 39 | } 40 | 41 | return colorMessageArray; 42 | } 43 | 44 | /** 45 | * @param messageList The iterable of messages that will be colored 46 | * @return A {@code List} containing every message in the input iterable, but with color codes replaced. 47 | * @see List 48 | * @see java.util.Collection 49 | */ 50 | public static @NotNull List colorList(@NotNull Iterable messageList) { 51 | List colorList = new ArrayList<>(); 52 | for (String message : messageList) { 53 | String color = color(message); 54 | colorList.add(color); 55 | } 56 | 57 | return colorList; 58 | } 59 | 60 | /** 61 | * @param messageArray The array of messages that will be colored 62 | * @return A {@code List} containing every message in the input array, but with color codes replaced. 63 | */ 64 | public static @NotNull List colorList(String @NotNull ... messageArray) { 65 | List messageList = Arrays.asList(messageArray); 66 | return colorList(messageList); 67 | } 68 | 69 | /** 70 | * Copies all elements from the iterable collection of originals to a 71 | * new {@link List} 72 | * 73 | * @param token String to search for 74 | * @param originals An iterable collection of strings to filter. 75 | * @return the list of all matches. 76 | * @throws IllegalArgumentException if any parameter is null 77 | * @throws IllegalArgumentException if originals contains a null element. 78 | */ 79 | public static @NotNull List getMatches(@NotNull String token, @NotNull Iterable originals) { 80 | List collection = new ArrayList<>(); 81 | for (String string : originals) { 82 | if (startsWithIgnoreCase(string, token)) { 83 | collection.add(string); 84 | } 85 | } 86 | 87 | return collection; 88 | } 89 | 90 | /** 91 | * This method uses a region to check case-insensitive equality. This 92 | * means the internal array does not need to be copied like a 93 | * toLowerCase() call would. 94 | * 95 | * @param string String to check 96 | * @param prefix Prefix of string to compare 97 | * @return true if provided string starts with, an ignoring case, the prefix 98 | * provided 99 | * @throws NullPointerException if the prefix is null 100 | * @throws IllegalArgumentException if string is null 101 | */ 102 | public static boolean startsWithIgnoreCase(@NotNull String string, @NotNull String prefix) { 103 | if (string.length() < prefix.length()) { 104 | return false; 105 | } 106 | 107 | return string.regionMatches(true, 0, prefix, 0, prefix.length()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # 2 | # ____ _____ _ _ ____ _ _ ___ 3 | # / ___|| ____| | | | / ___| | | |_ _| 4 | # \___ \| _| | | | | | | _| | | || | 5 | # ___) | |___| |___| |__| |_| | |_| || | 6 | # |____/|_____|_____|_____\____|\___/|___| 7 | # ( By Mackenzie Molloy ) 8 | 9 | # Please contact me if you have any issues with the plugin - happy to help. 10 | # - Support Discord | https://mackenziemolloy.net/discord 11 | 12 | # 13 | # Permissions: 14 | # - sellgui.use | Access to /sellgui (opens the sellgui) 15 | # - sellgui.reload | Access to /sellgui reload (reloads configuration) 16 | # - sellgui.debug | Access to /sellgui dump (dumps server information to a paste) 17 | # 18 | 19 | options: 20 | # 21 | # 0 - None 22 | # 1 - Hover message (Adds "receipt_text" to the end of the message) 23 | # 24 | receipt_type: 1 25 | sell_titles: true 26 | 27 | # 28 | # This is the feature where all sell transactions made via 29 | # the SellGUI Sell Menu are logged to a file. 30 | # 31 | transaction_log: 32 | # Should the Transaction logging feature be enabled? 33 | enabled: true 34 | # Date format of transaction logging (http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html) 35 | date_format: "yyyy/MM/dd HH:mm:ss" 36 | 37 | # 38 | # Whether prices should be rounded to 2 decimal places, or not. 39 | # This also formats prices with comma separation. IE: 1800 -> 1,800 40 | # 41 | rounded_pricing: false 42 | 43 | # 44 | # Whether trailing zero decimals should be removed (28.0, 28.00, etc --> 28) 45 | # 46 | remove_trailing_zeros: false 47 | 48 | # 49 | # Should units be abbreviated? E.g.: 1,259 -> 1.25k 50 | # Requires "rounded_pricing" to be false 51 | # 52 | abbreviate_numbers: false 53 | 54 | # 55 | # Sounds that are played during certain events 56 | # 57 | sounds: 58 | enabled: true 59 | error_notification: true # Whether there should be a console output notifying you that a sound is invalid 60 | pitch: 1 61 | volume: 1 62 | events: 63 | # If you don't want a certain event to have a sound, just set to an invalid value 64 | # NOTE: THESE DEFAULT VALUES ARE FOR 1.13+ AND MUST USE _ (underscore) INSTEAD OF . (dot) 65 | open: BLOCK_CHEST_OPEN 66 | success: ENTITY_VILLAGER_CELEBRATE 67 | failed: ENTITY_VILLAGER_HURT 68 | 69 | # 70 | # Action Bar Messages only work on versions 1.9+ 71 | # 72 | action_bar_msgs: true 73 | 74 | # 75 | # Only relevent in versions prior to 1.13, basically do you want trialing ":(NUMBER)" on item lists (IE: "STONE:2") 76 | # 77 | show_item_damage: true 78 | 79 | # 80 | # How many rows the GUI will have 81 | # Min: 1 - Max: 6 82 | # 83 | rows: 6 84 | 85 | # 86 | # Similar to the SGP Item format: (EXAMPLE) 87 | # 1: 88 | # type: dummy <--- Not required, but may become applicable in future updates I make 89 | # item: 90 | # material: STONE 91 | # damage: 1 <--- Only use if you're running 1.12 or below 92 | # quantity: 1 93 | # name: "&7Cool name" 94 | # lore: 95 | # - "&7Lore Line 1" 96 | # - "&9Lore line 2" 97 | # ...and so on 98 | # customModelData: 1 99 | # commandsOnClick: (or commandsOnClickConsole) 100 | # - "say %PLAYER%" 101 | # slot: 1 102 | # 103 | decorations: [] 104 | 105 | messages: 106 | no_permission: "&cYou do not have permission" 107 | sellgui_title: "&0Sell GUI" 108 | reloaded_config: "&c[SellGUI] Reloaded configuration files." 109 | no_items_sold: "&aShop > &fYou didn't have anything sellable." 110 | no_items_in_gui: "&aShop > &fYou didn't place any items in the GUI." 111 | # 112 | # {list} - returns a list of sold items 113 | # {earning} - returns the total amount made 114 | # {amount} - return number of items sold 115 | # 116 | items_sold: "&aShop > &fYou sold &a{list}&f for &a{earning}&f." 117 | receipt_text: "&7(RECEIPT)" 118 | receipt_title: "&f&nReceipt for sell\n\n" 119 | # 120 | # {amount} - return number of item 121 | # {item} - returns item name 122 | # {price} - returns the selling price of the item 123 | # 124 | receipt_item_layout: "&a{amount} x {item} &ffor &a{price}" 125 | # 126 | # {amount} - return number of items sold 127 | # {earning} - returns the total amount made 128 | # 129 | sell_title: "&a&l+{earning}" 130 | sell_subtitle: "&7You sold &a&n{amount}&7 items in this batch" 131 | gamemode_not_allowed: "&aShop > &fYou are not allowed to use the Sell GUI in &a{gamemode} mode&f." 132 | inventory_full: "&aShop > &fYour inventory is currently full, so excess items have been dropped on the ground." 133 | # 134 | # {amount} - return number of items sold 135 | # {earning} - returns the total amount made 136 | # 137 | action_bar_items_sold: "&aShop > &fYou sold &a{amount} &fitems in this batch." 138 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/SellGUI.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui; 2 | 3 | import com.tcoded.folialib.FoliaLib; 4 | import com.tcoded.folialib.impl.PlatformScheduler; 5 | import net.mackenziemolloy.shopguiplus.sellgui.command.CommandSellGUI; 6 | import net.mackenziemolloy.shopguiplus.sellgui.utility.CommentedConfiguration; 7 | import net.mackenziemolloy.shopguiplus.sellgui.utility.FileUtils; 8 | import net.mackenziemolloy.shopguiplus.sellgui.utility.LogFormatter; 9 | import net.mackenziemolloy.shopguiplus.sellgui.utility.UpdateChecker; 10 | import net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman.VersionUtility; 11 | import org.bstats.bukkit.Metrics; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.ChatColor; 14 | import org.bukkit.command.CommandSender; 15 | import org.bukkit.configuration.InvalidConfigurationException; 16 | import org.bukkit.plugin.PluginDescriptionFile; 17 | import org.bukkit.plugin.java.JavaPlugin; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.util.logging.FileHandler; 23 | import java.util.logging.Handler; 24 | import java.util.logging.Level; 25 | import java.util.logging.Logger; 26 | 27 | public final class SellGUI extends JavaPlugin { 28 | 29 | private final CommentedConfiguration configuration; 30 | private static SellGUI instance; 31 | private static PlatformScheduler scheduler; 32 | public Logger fileLogger; 33 | private FileHandler handler; 34 | private String version; 35 | 36 | public boolean compatible = false; 37 | 38 | public SellGUI() { 39 | this.configuration = new CommentedConfiguration(); 40 | this.version = null; 41 | } 42 | 43 | @Override 44 | public void onEnable() { 45 | instance = this; 46 | 47 | FoliaLib foliaLib = new FoliaLib(this); 48 | scheduler = foliaLib.getScheduler(); 49 | 50 | new CommandSellGUI(this).register(); 51 | Logger logger = getLogger(); 52 | 53 | checkCompatibility(); 54 | 55 | this.version = VersionUtility.getNetMinecraftServerVersion(); 56 | logger.info("Your server is running version '" + this.version + "'."); 57 | 58 | generateFiles(); 59 | setupMetrics(); 60 | setupUpdates(); 61 | initLogger(); 62 | 63 | logger.info("*-*"); 64 | logger.info("ShopGUIPlus SellGUI"); 65 | logger.info("Made by Mackenzie Molloy"); 66 | logger.info("*-*"); 67 | } 68 | 69 | public void checkCompatibility() { 70 | try { 71 | Class.forName("net.brcdev.shopgui.shop.item.ShopItem"); 72 | compatible = true; 73 | } catch (ReflectiveOperationException ex) { 74 | compatible = false; 75 | } 76 | } 77 | 78 | public void initLogger() { 79 | if (!configuration.getBoolean("options.transaction_log.enabled", false)) { 80 | return; 81 | } 82 | 83 | try { 84 | File logFile = FileUtils.loadFile("transaction.log"); 85 | FileHandler handler = new FileHandler(logFile.getAbsolutePath(), true); 86 | handler.setFormatter(new LogFormatter()); 87 | this.handler = handler; 88 | 89 | Logger logger = Logger.getLogger("SellGUIFileLogger"); 90 | logger.setUseParentHandlers(false); 91 | logger.addHandler(handler); 92 | this.fileLogger = logger; 93 | } catch (IOException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | 98 | public void closeLogger() { 99 | if (fileLogger == null) { 100 | return; 101 | } 102 | 103 | for (Handler handler : fileLogger.getHandlers()) { 104 | handler.close(); 105 | fileLogger.removeHandler(handler); 106 | } 107 | 108 | fileLogger = null; 109 | } 110 | 111 | public CommentedConfiguration getConfiguration() { 112 | return this.configuration; 113 | } 114 | 115 | public String getVersion() { 116 | return this.version; 117 | } 118 | 119 | private void setupMetrics() { 120 | new Metrics(this, 9356); 121 | } 122 | 123 | private void setupUpdates() { 124 | PluginDescriptionFile description = getDescription(); 125 | String localVersion = description.getVersion(); 126 | String pluginPrefix = description.getPrefix(); 127 | 128 | UpdateChecker updateChecker = new UpdateChecker(this, 85170); 129 | updateChecker.getVersion(updateVersion -> { 130 | CommandSender console = Bukkit.getConsoleSender(); 131 | if (localVersion.contains("dev")) { 132 | String message = (ChatColor.DARK_RED + "[" + pluginPrefix + "] You are running a DEVELOPMENT " + 133 | "build. This may contain bugs."); 134 | console.sendMessage(message); 135 | return; 136 | } 137 | 138 | if (localVersion.equalsIgnoreCase(updateVersion)) { 139 | String message = (ChatColor.GREEN + "[" + pluginPrefix + "] You are running the LATEST release."); 140 | console.sendMessage(message); 141 | return; 142 | } 143 | 144 | String message = (ChatColor.DARK_RED + "[" + pluginPrefix + "] There is a new update available." + 145 | " Please update ASAP. Download: https://www.spigotmc.org/resources/85170/"); 146 | console.sendMessage(message); 147 | }); 148 | } 149 | 150 | public void generateFiles() { 151 | saveDefaultConfig(); 152 | File pluginFolder = getDataFolder(); 153 | File configFile = new File(pluginFolder, "config.yml"); 154 | 155 | try { 156 | this.configuration.load(configFile); 157 | 158 | InputStream jarConfig = getResource("config.yml"); 159 | this.configuration.syncWithConfig(configFile, jarConfig, "stupid_option"); 160 | } catch (IOException | InvalidConfigurationException ex) { 161 | Logger logger = getLogger(); 162 | logger.log(Level.SEVERE, "Failed to load the 'config.yml' file due to an error:", ex); 163 | } 164 | } 165 | 166 | public static SellGUI getInstance() { 167 | return instance; 168 | } 169 | 170 | public static PlatformScheduler scheduler() { 171 | return scheduler; 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | 118 | 119 | # Determine the Java command to use to start the JVM. 120 | if [ -n "$JAVA_HOME" ] ; then 121 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 122 | # IBM's JDK on AIX uses strange locations for the executables 123 | JAVACMD=$JAVA_HOME/jre/sh/java 124 | else 125 | JAVACMD=$JAVA_HOME/bin/java 126 | fi 127 | if [ ! -x "$JAVACMD" ] ; then 128 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 129 | 130 | Please set the JAVA_HOME variable in your environment to match the 131 | location of your Java installation." 132 | fi 133 | else 134 | JAVACMD=java 135 | if ! command -v java >/dev/null 2>&1 136 | then 137 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 138 | 139 | Please set the JAVA_HOME variable in your environment to match the 140 | location of your Java installation." 141 | fi 142 | fi 143 | 144 | # Increase the maximum file descriptors if we can. 145 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 146 | case $MAX_FD in #( 147 | max*) 148 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 149 | # shellcheck disable=SC2039,SC3045 150 | MAX_FD=$( ulimit -H -n ) || 151 | warn "Could not query maximum file descriptor limit" 152 | esac 153 | case $MAX_FD in #( 154 | '' | soft) :;; #( 155 | *) 156 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 157 | # shellcheck disable=SC2039,SC3045 158 | ulimit -n "$MAX_FD" || 159 | warn "Could not set maximum file descriptor limit to $MAX_FD" 160 | esac 161 | fi 162 | 163 | # Collect all arguments for the java command, stacking in reverse order: 164 | # * args from the command line 165 | # * the main class name 166 | # * -classpath 167 | # * -D...appname settings 168 | # * --module-path (only if needed) 169 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 170 | 171 | # For Cygwin or MSYS, switch paths to Windows format before running java 172 | if "$cygwin" || "$msys" ; then 173 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 214 | "$@" 215 | 216 | # Stop when "xargs" is not available. 217 | if ! command -v xargs >/dev/null 2>&1 218 | then 219 | die "xargs is not available" 220 | fi 221 | 222 | # Use "xargs" to parse quoted args. 223 | # 224 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 225 | # 226 | # In Bash we could simply go: 227 | # 228 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 229 | # set -- "${ARGS[@]}" "$@" 230 | # 231 | # but POSIX shell has neither arrays nor command substitution, so instead we 232 | # post-process each arg (as a line of input to sed) to backslash-escape any 233 | # character that might be a shell metacharacter, then use eval to reverse 234 | # that process (while maintaining the separation between arguments), and wrap 235 | # the whole thing up as a single "set" statement. 236 | # 237 | # This will of course break if any of these variables contains a newline or 238 | # an unmatched quote. 239 | # 240 | 241 | eval "set -- $( 242 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 243 | xargs -n1 | 244 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 245 | tr '\n' ' ' 246 | )" '"$@"' 247 | 248 | exec "$JAVACMD" "$@" 249 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/utility/CommentedConfiguration.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * CommentedConfiguration 4 | * Developed by Ome_R 5 | * 6 | * You may use the resource and/or modify it - but not 7 | * claiming it as your own work. You are not allowed 8 | * to remove this message, unless being permitted by 9 | * the developer of the resource. 10 | * 11 | * Spigot: https://www.spigotmc.org/resources/authors/ome_r.25629/ 12 | * MC-Market: https://www.mc-market.org/resources/authors/40228/ 13 | * GitHub: https://github.com/OmerBenGera?tab=repositories 14 | * Website: https://bg-software.com/ 15 | * 16 | *******************************************************************************/ 17 | 18 | package net.mackenziemolloy.shopguiplus.sellgui.utility; 19 | 20 | import org.bukkit.Bukkit; 21 | import org.bukkit.configuration.ConfigurationSection; 22 | import org.bukkit.configuration.InvalidConfigurationException; 23 | import org.bukkit.configuration.file.YamlConfiguration; 24 | import org.jetbrains.annotations.NotNull; 25 | 26 | import java.io.BufferedReader; 27 | import java.io.File; 28 | import java.io.FileInputStream; 29 | import java.io.FileNotFoundException; 30 | import java.io.IOException; 31 | import java.io.InputStream; 32 | import java.io.InputStreamReader; 33 | import java.io.Reader; 34 | import java.nio.charset.StandardCharsets; 35 | import java.util.ArrayList; 36 | import java.util.Arrays; 37 | import java.util.HashMap; 38 | import java.util.List; 39 | import java.util.Map; 40 | 41 | public final class CommentedConfiguration extends YamlConfiguration { 42 | 43 | /** 44 | * Holds all comments for the config. 45 | * Can be accessed by using the methods that are provided 46 | * by the class. 47 | */ 48 | private final Map configComments = new HashMap<>(); 49 | 50 | /** 51 | * Flag to determine if the config failed to load. 52 | * When this flag is true, syncWithConfig will not reset the config. 53 | */ 54 | private boolean creationFailure = false; 55 | 56 | public CommentedConfiguration() { 57 | try { 58 | // We don't want the YAML to parse comments at all. 59 | this.options().parseComments(false); 60 | } catch (Throwable ignored) { 61 | 62 | } 63 | } 64 | 65 | /** 66 | * Sync the config with another resource. 67 | * This method can be used as an auto updater for your config files. 68 | * @param file The file to save changes into if there are any. 69 | * @param resource The resource to sync with. Can be provided by JavaPlugin#getResource 70 | * @param ignoredSections An array of sections that will be ignored, and won't get updated 71 | * if they are already exist in the config. If they are not in the 72 | * config, they will be synced with the resource's config. 73 | */ 74 | public void syncWithConfig(File file, InputStream resource, String... ignoredSections) throws IOException{ 75 | if(creationFailure) return; 76 | 77 | CommentedConfiguration cfg = loadConfiguration(resource); 78 | if (syncConfigurationSection(cfg, cfg.getConfigurationSection(""), Arrays.asList(ignoredSections)) && file != null) { 79 | save(file); 80 | } 81 | } 82 | 83 | /** 84 | * Set a new comment to a path. 85 | * You can remove comments by providing a null as a comment argument. 86 | * @param path The path to set the comment to. 87 | * @param comment The comment to set. Supports multiple lines (use \n as a spacer). 88 | */ 89 | public void setComment(String path, String comment) { 90 | if (comment == null) { 91 | configComments.remove(path); 92 | } else { 93 | configComments.put(path, comment); 94 | } 95 | } 96 | 97 | /** 98 | * Get a comment of a path. 99 | * @param path The path to get the comment of. 100 | * @return Returns a string that contains the comment. If no comment exists, null will be returned. 101 | */ 102 | public String getComment(String path) { 103 | return getComment(path, null); 104 | } 105 | 106 | /** 107 | * Get a comment of a path with a default value if no comment exists. 108 | * @param path The path to get the comment of. 109 | * @param def A default comment that will be returned if no comment exists for the path. 110 | * @return Returns a string that contains the comment. If no comment exists, the def value will be returned. 111 | */ 112 | public String getComment(String path, String def) { 113 | return configComments.getOrDefault(path, def); 114 | } 115 | 116 | /** 117 | * Checks whether a path has a comment or not. 118 | * @param path The path to check. 119 | * @return Returns true if there's an existing comment, otherwise false. 120 | */ 121 | public boolean containsComment(String path) { 122 | return getComment(path) != null; 123 | } 124 | 125 | /** 126 | * Check if the config has failed to load. 127 | */ 128 | public boolean hasFailed() { 129 | return creationFailure; 130 | } 131 | 132 | /** 133 | * Load all data related to the config file - keys, values and comments. 134 | * @param contents The contents of the file. 135 | * @throws InvalidConfigurationException if the contents are invalid. 136 | */ 137 | @Override 138 | public void loadFromString(@NotNull String contents) throws InvalidConfigurationException { 139 | //Load data of the base yaml (keys and values). 140 | super.loadFromString(contents); 141 | 142 | //Parse the contents into lines. 143 | String[] lines = contents.split("\n"); 144 | int currentIndex = 0; 145 | 146 | //Variables that are used to track progress. 147 | StringBuilder comments = new StringBuilder(); 148 | String currentSection = ""; 149 | 150 | while (currentIndex < lines.length) { 151 | //Checking if the current line is a comment. 152 | if (isComment(lines[currentIndex])) { 153 | //Adding the comment to the builder with a new line at the end. 154 | comments.append(lines[currentIndex]).append("\n"); 155 | } else if (isNewSection(lines[currentIndex])) { //Checking if the current line is a valid new section. 156 | //Parsing the line into a full-path. 157 | currentSection = getSectionPath(this, lines[currentIndex], currentSection); 158 | 159 | //If there is a valid comment for the section. 160 | if (comments.length() > 1) { 161 | //Adding the comment. 162 | setComment(currentSection, comments.substring(0, comments.length() - 1)); 163 | } 164 | 165 | //Resetting the comment variable for further usage. 166 | comments = new StringBuilder(); 167 | } 168 | 169 | //Skipping to the next line. 170 | currentIndex++; 171 | } 172 | } 173 | 174 | /** 175 | * Parsing all the data (keys, values and comments) into a valid string, that will be written into a file later. 176 | * @return A string that contains all the data, ready to be written into a file. 177 | */ 178 | @Override 179 | public String saveToString() { 180 | //First, we set headers to null - as we will handle all comments, including headers, in this method. 181 | this.options().header(null); 182 | 183 | //Get the string of the data (keys and values) and parse it into an array of lines. 184 | List lines = new ArrayList<>(Arrays.asList(super.saveToString().split("\n"))); 185 | 186 | //Variables that are used to track progress. 187 | int currentIndex = 0; 188 | String currentSection = ""; 189 | 190 | while (currentIndex < lines.size()) { 191 | String line = lines.get(currentIndex); 192 | 193 | //Checking if the line is a new section. 194 | if (isNewSection(line)) { 195 | //Parsing the line into a full-path. 196 | currentSection = getSectionPath(this, line, currentSection); 197 | //Checking if that path contains a comment 198 | if (containsComment(currentSection)) { 199 | //Adding the comment to the lines array at that index (adding it one line before the actual line) 200 | lines.add(currentIndex, getComment(currentSection)); 201 | //Skipping one line so the pointer will point to the line we checked again. 202 | currentIndex++; 203 | } 204 | } 205 | 206 | //Skipping to the next line. 207 | currentIndex++; 208 | } 209 | 210 | //Parsing the array of lines into a one single string. 211 | StringBuilder contents = new StringBuilder(); 212 | for (String line : lines) { 213 | contents.append("\n").append(line); 214 | } 215 | 216 | return contents.isEmpty() ? "" : contents.substring(1); 217 | } 218 | 219 | /** 220 | * Sync a specific configuration section with another one, recursively. 221 | * @param commentedConfig The config that contains the data we need to sync with. 222 | * @param section The current section that we sync. 223 | * @param ignoredSections A list of ignored sections that won't be synced (unless not found in the file). 224 | * @return Returns true if there were any changes, otherwise false. 225 | */ 226 | private boolean syncConfigurationSection(CommentedConfiguration commentedConfig, ConfigurationSection section, List ignoredSections) { 227 | //Variables that are used to track progress. 228 | boolean changed = false; 229 | 230 | //Going through all the keys of the section. 231 | for (String key : section.getKeys(false)) { 232 | //Parsing the key into a full-path. 233 | String path = section.getCurrentPath().isEmpty() ? key : section.getCurrentPath() + "." + key; 234 | 235 | //Checking if the key is also a section. 236 | if (section.isConfigurationSection(key)) { 237 | //Checking if the section is ignored. 238 | boolean isIgnored = ignoredSections.stream().anyMatch(path::contains); 239 | //Checking if the config contains the section. 240 | boolean containsSection = contains(path); 241 | //If the config doesn't contain the section, or it's not ignored - we will sync data. 242 | if (!containsSection || !isIgnored) { 243 | //Syncing data and updating the changed variable. 244 | changed = syncConfigurationSection(commentedConfig, section.getConfigurationSection(key), ignoredSections) || changed; 245 | } 246 | } else if (!contains(path)) { //Checking if the config contains the path (not a section). 247 | //Saving the value of the key into the config. 248 | set(path, section.get(key)); 249 | //Updating variables. 250 | changed = true; 251 | } 252 | 253 | //Checking if there is a valid comment for the path, and also making sure the comments are not the same. 254 | if (commentedConfig.containsComment(path) && !commentedConfig.getComment(path).equals(getComment(path))) { 255 | //Saving the comment of the key into the config. 256 | setComment(path, commentedConfig.getComment(path)); 257 | //Updating variable. 258 | changed = true; 259 | } 260 | } 261 | 262 | /*Keys cannot be ordered easily, so we need to do some tricks to make sure 263 | all of them are ordered correctly (and the new config will look the same 264 | as the resource that was provided).*/ 265 | 266 | //Checking if there was a value that had been added into the config 267 | if (changed) { 268 | correctIndexes(section, getConfigurationSection(section.getCurrentPath())); 269 | } 270 | 271 | return changed; 272 | } 273 | 274 | /** 275 | * Flag this config as failed to load. 276 | */ 277 | private CommentedConfiguration flagAsFailed() { 278 | creationFailure = true; 279 | return this; 280 | } 281 | 282 | /** 283 | * Load a config from a file. 284 | * @param file The file to load the config from. 285 | * @return A new instance of CommentedConfiguration contains all the data (keys, values and comments). 286 | */ 287 | public static CommentedConfiguration loadConfiguration(@NotNull File file) { 288 | try { 289 | FileInputStream stream = new FileInputStream(file); 290 | return loadConfiguration(new InputStreamReader(stream, StandardCharsets.UTF_8)); 291 | } catch (FileNotFoundException ex) { 292 | Bukkit.getLogger().warning("File " + file.getName() + " doesn't exist."); 293 | return new CommentedConfiguration().flagAsFailed(); 294 | } 295 | } 296 | 297 | /** 298 | * Load a config from an input-stream, which is used for resources that are obtained using JavaPlugin#getResource. 299 | * @param inputStream The input-stream to load the config from. 300 | * @return A new instance of CommentedConfiguration contains all the data (keys, values and comments). 301 | */ 302 | public static CommentedConfiguration loadConfiguration(InputStream inputStream) { 303 | return loadConfiguration(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); 304 | } 305 | 306 | /** 307 | * Load a config from a reader (used for files and streams together). 308 | * @param reader The reader to load the config from. 309 | * @return A new instance of CommentedConfiguration contains all the data (keys, values and comments). 310 | */ 311 | public static CommentedConfiguration loadConfiguration(@NotNull Reader reader) { 312 | //Creating a blank instance of the config. 313 | CommentedConfiguration config = new CommentedConfiguration(); 314 | 315 | //Parsing the reader into a BufferedReader for an easy reading of it. 316 | try (BufferedReader bufferedReader = reader instanceof BufferedReader ? (BufferedReader)reader : new BufferedReader(reader)) { 317 | StringBuilder contents = new StringBuilder(); 318 | 319 | String line; 320 | while((line = bufferedReader.readLine()) != null) { 321 | contents.append(line).append('\n'); 322 | } 323 | 324 | config.loadFromString(contents.toString()); 325 | } catch (IOException | InvalidConfigurationException ex) { 326 | config.flagAsFailed(); 327 | ex.printStackTrace(); 328 | } 329 | 330 | return config; 331 | } 332 | 333 | /** 334 | * Checks if a line is a new section or not. 335 | * Sadly, that's not possible to use ":" as a spacer between key and value, and ": " must be used. 336 | * @param line The line to check. 337 | * @return True if the line is a new section, otherwise false. 338 | */ 339 | private static boolean isNewSection(String line) { 340 | String trimLine = line.trim(); 341 | return trimLine.contains(": ") || trimLine.endsWith(":"); 342 | } 343 | 344 | /** 345 | * Creates a full path of a line. 346 | * @param commentedConfig The config to get the path from. 347 | * @param line The line to get the path of. 348 | * @param currentSection The last known section. 349 | * @return The full path of the line. 350 | */ 351 | private static String getSectionPath(CommentedConfiguration commentedConfig, String line, String currentSection) { 352 | //Removing all spaces and getting the name of the section. 353 | String newSection = line.trim().split(": ")[0]; 354 | 355 | //Parsing some formats to make sure having a plain name. 356 | if (newSection.endsWith(":")) { 357 | newSection = newSection.substring(0, newSection.length() - 1); 358 | } 359 | if (newSection.startsWith(".")) { 360 | newSection = newSection.substring(1); 361 | } 362 | if (newSection.startsWith("'") && newSection.endsWith("'")) { 363 | newSection = newSection.substring(1, newSection.length() - 1); 364 | } 365 | 366 | //Checking if the new section is a child-section of the currentSection. 367 | if (!currentSection.isEmpty() && commentedConfig.contains(currentSection + "." + newSection)) { 368 | newSection = currentSection + "." + newSection; 369 | } else { // Looking for the parent of the new section. 370 | String parentSection = currentSection; 371 | 372 | /*Getting the parent of the new section. The loop will stop in one of the following situations: 373 | 1) The parent is empty - which means we have nowhere to go, as that's the root section. 374 | 2) The config contains a valid path that was built with ..*/ 375 | while (!parentSection.isEmpty() && 376 | !commentedConfig.contains((parentSection = getParentPath(parentSection)) + "." + newSection)); 377 | 378 | //Parsing and building the new full path. 379 | newSection = parentSection.trim().isEmpty() ? newSection : parentSection + "." + newSection; 380 | } 381 | 382 | return newSection; 383 | } 384 | 385 | /** 386 | * Checks if a line represents a comment or not. 387 | * @param line The line to check. 388 | * @return True if the line is a comment (stars with # or it's empty), otherwise false. 389 | */ 390 | private static boolean isComment(String line) { 391 | String trimLine = line.trim(); 392 | return trimLine.startsWith("#") || trimLine.isEmpty(); 393 | } 394 | 395 | /** 396 | * Get the parent path of the provided path, by removing the last '.' from the path. 397 | * @param path The path to check. 398 | * @return The parent path of the provided path. 399 | */ 400 | private static String getParentPath(String path) { 401 | return path.contains(".") ? path.substring(0, path.lastIndexOf('.')) : ""; 402 | } 403 | 404 | /** 405 | * Convert key-indexes of a section into the same key-indexes that another section has. 406 | * @param section The section that will be used as a way to get the correct indexes. 407 | * @param target The target section that will be ordered again. 408 | */ 409 | private static void correctIndexes(ConfigurationSection section, ConfigurationSection target) { 410 | //Parsing the sections into ArrayLists with their keys and values. 411 | List> sectionMap = getSectionMap(section), correctOrder = new ArrayList<>(); 412 | 413 | //Going through the sectionMap, which is in the correct order. 414 | for (Pair entry : sectionMap) { 415 | //Adding the entry into a new list with the correct value from the target section. 416 | correctOrder.add(new Pair<>(entry.key, target.get(entry.key))); 417 | } 418 | 419 | /*The only way to change key-indexes is to add them one-by-one again, in the correct order. 420 | In order to do so, the section needs to be cleared so the indexes will be reset.*/ 421 | 422 | //Clearing the configuration. 423 | clearConfiguration(target); 424 | 425 | //Adding the entries again in the correct order. 426 | for (Pair entry : correctOrder) { 427 | target.set(entry.key, entry.value); 428 | } 429 | } 430 | 431 | /** 432 | * Parsing a section into a list that contains all of its keys and their values without changing their order. 433 | * @param section The section to convert. 434 | * @return A list that contains all the keys and their values. 435 | */ 436 | private static List> getSectionMap(ConfigurationSection section) { 437 | //Creating an empty ArrayList. 438 | List> list = new ArrayList<>(); 439 | 440 | //Going through all the keys and adding them in the same order. 441 | for (String key : section.getKeys(false)) { 442 | list.add(new Pair<>(key, section.get(key))); 443 | } 444 | 445 | return list; 446 | } 447 | 448 | /** 449 | * Clear a configuration section from all of its keys. 450 | * This can be done by setting all the keys' values to null. 451 | * @param section The section to clear. 452 | */ 453 | private static void clearConfiguration(ConfigurationSection section) { 454 | for (String key : section.getKeys(false)) { 455 | section.set(key, null); 456 | } 457 | } 458 | 459 | /** 460 | * A class that is used as a way of representing a map's entry (which is not implemented). 461 | */ 462 | private static class Pair { 463 | 464 | private final K key; 465 | private final V value; 466 | 467 | Pair(K key, V value) { 468 | this.key = key; 469 | this.value = value; 470 | } 471 | 472 | @Override 473 | public boolean equals(Object obj) { 474 | return obj instanceof Pair && key.equals(((Pair) obj).key) && value.equals(((Pair) obj).value); 475 | } 476 | 477 | @Override 478 | public int hashCode() { 479 | return key.hashCode(); 480 | } 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /src/main/java/net/mackenziemolloy/shopguiplus/sellgui/command/CommandSellGUI.java: -------------------------------------------------------------------------------- 1 | package net.mackenziemolloy.shopguiplus.sellgui.command; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.EnumMap; 8 | import java.util.HashMap; 9 | import java.util.HashSet; 10 | import java.util.LinkedList; 11 | import java.util.List; 12 | import java.util.Locale; 13 | import java.util.Map; 14 | import java.util.Map.Entry; 15 | import java.util.Objects; 16 | import java.util.Set; 17 | import java.util.concurrent.CompletableFuture; 18 | import java.util.function.Function; 19 | import java.util.logging.Logger; 20 | 21 | import com.tcoded.folialib.impl.PlatformScheduler; 22 | import dev.triumphteam.gui.guis.Gui; 23 | import dev.triumphteam.gui.guis.GuiItem; 24 | import net.brcdev.shopgui.ShopGuiPlusApi; 25 | import net.brcdev.shopgui.ShopGuiPlugin; 26 | import net.brcdev.shopgui.economy.EconomyType; 27 | import net.brcdev.shopgui.provider.economy.EconomyProvider; 28 | import net.mackenziemolloy.shopguiplus.sellgui.SellGUI; 29 | import net.mackenziemolloy.shopguiplus.sellgui.objects.ShopItemPriceValue; 30 | import net.mackenziemolloy.shopguiplus.sellgui.utility.*; 31 | import net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman.HexColorUtility; 32 | import net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman.MessageUtility; 33 | import net.mackenziemolloy.shopguiplus.sellgui.utility.sirblobman.VersionUtility; 34 | import net.md_5.bungee.api.ChatMessageType; 35 | import net.md_5.bungee.api.chat.BaseComponent; 36 | import net.md_5.bungee.api.chat.HoverEvent; 37 | import net.md_5.bungee.api.chat.HoverEvent.Action; 38 | import net.md_5.bungee.api.chat.TextComponent; 39 | import org.bukkit.Bukkit; 40 | import org.bukkit.ChatColor; 41 | import org.bukkit.GameMode; 42 | import org.bukkit.Location; 43 | import org.bukkit.Material; 44 | import org.bukkit.World; 45 | import org.bukkit.command.Command; 46 | import org.bukkit.command.CommandSender; 47 | import org.bukkit.command.PluginCommand; 48 | import org.bukkit.command.TabExecutor; 49 | import org.bukkit.configuration.ConfigurationSection; 50 | import org.bukkit.configuration.file.FileConfiguration; 51 | import org.bukkit.entity.HumanEntity; 52 | import org.bukkit.entity.Player; 53 | import org.bukkit.event.inventory.InventoryCloseEvent; 54 | import org.bukkit.inventory.Inventory; 55 | import org.bukkit.inventory.ItemStack; 56 | import org.bukkit.inventory.meta.Damageable; 57 | import org.bukkit.inventory.meta.ItemMeta; 58 | import org.bukkit.plugin.Plugin; 59 | import org.bukkit.plugin.PluginManager; 60 | import org.bukkit.util.StringUtil; 61 | import org.jetbrains.annotations.NotNull; 62 | import org.jetbrains.annotations.Nullable; 63 | import net.kyori.adventure.text.Component; 64 | import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; 65 | 66 | @SuppressWarnings("deprecation") 67 | public final class CommandSellGUI implements TabExecutor { 68 | 69 | private final SellGUI plugin; 70 | private final PlatformScheduler scheduler; 71 | 72 | public CommandSellGUI(SellGUI plugin) { 73 | this.plugin = Objects.requireNonNull(plugin, "The plugin must not be null!"); 74 | this.scheduler = SellGUI.scheduler(); 75 | } 76 | 77 | public void register() { 78 | PluginCommand pluginCommand = this.plugin.getCommand("sellgui"); 79 | if (pluginCommand != null) { 80 | pluginCommand.setExecutor(this); 81 | pluginCommand.setTabCompleter(this); 82 | } 83 | } 84 | 85 | @Override 86 | public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { 87 | if (args.length == 1) { 88 | List valueSet = Arrays.asList("rl", "reload", "debug", "dump"); 89 | return StringUtil.copyPartialMatches(args[0], valueSet, new ArrayList<>()); 90 | } 91 | 92 | return Collections.emptyList(); 93 | } 94 | 95 | @Override 96 | public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String @NotNull [] args) { 97 | if (!plugin.compatible) { 98 | String message = MessageUtility.color("&7\n&7\n&a&lUPDATE REQUIRED \n&7\n&7Unfortunately &fSellGUI &7will not work until you update &cShopGUIPlus&7 to version &c1.78.0&7 or above.\n&7\n&eDownload: https://spigotmc.org/resources/6515/\n&7\n&7"); 99 | sender.sendMessage(message); 100 | return false; 101 | } 102 | 103 | if (args.length == 0) { 104 | return commandBase(sender); 105 | } 106 | 107 | String sub = args[0].toLowerCase(Locale.US); 108 | return switch (sub) { 109 | case "rl", "reload" -> commandReload(sender); 110 | case "debug", "dump" -> commandDebug(sender); 111 | default -> false; 112 | }; 113 | } 114 | 115 | private boolean commandReload(CommandSender sender) { 116 | if (!sender.hasPermission("sellgui.reload")) { 117 | sendMessage(sender, "no_permission"); 118 | return true; 119 | } 120 | 121 | CompletableFuture.runAsync(this.plugin::generateFiles).whenComplete((success, error) -> { 122 | if (error != null) { 123 | sender.sendMessage(ChatColor.RED + "An error occurred, please check the server console."); 124 | error.printStackTrace(); 125 | return; 126 | } 127 | 128 | if (!this.plugin.getConfiguration().getBoolean("options.transaction_log.enabled", false)) this.plugin.closeLogger(); 129 | else if (this.plugin.fileLogger == null) this.plugin.initLogger(); 130 | 131 | sendMessage(sender, "reloaded_config"); 132 | if (sender instanceof Player player) { 133 | PlayerHandler.playSound(player, "success"); 134 | } 135 | }); 136 | return true; 137 | } 138 | 139 | private boolean commandDebug(CommandSender sender) { 140 | if (!sender.hasPermission("sellgui.dump")) { 141 | sendMessage(sender, "no_permission"); 142 | return true; 143 | } 144 | 145 | CommentedConfiguration configuration = this.plugin.getConfiguration(); 146 | PluginManager pluginManager = Bukkit.getPluginManager(); 147 | Plugin[] pluginArray = pluginManager.getPlugins(); 148 | List pluginInfoList = new ArrayList<>(); 149 | 150 | for (Plugin plugin : pluginArray) { 151 | String pluginName = plugin.getName(); 152 | String pluginVersion = plugin.getDescription().getVersion(); 153 | String pluginAuthorList = String.join(", ", plugin.getDescription().getAuthors()); 154 | 155 | String pluginInfo = String.format(Locale.US, "- %s v%s by %s", pluginName, pluginVersion, 156 | pluginAuthorList); 157 | pluginInfoList.add(pluginInfo); 158 | } 159 | 160 | String pasteRaw = "| System Information\n\n" + "- OS Type: " + System.getProperty("os.name") + "\n" + 161 | "- OS Version: " + System.getProperty("os.version") + 162 | " (" + System.getProperty("os.arch") + ")\n" + 163 | "- Processor: " + System.getenv("PROCESSOR_IDENTIFIER") + 164 | "\n\n| Server Information\n\n" + 165 | "- Version: " + Bukkit.getBukkitVersion() + "\n" + 166 | "- Online Mode: " + Bukkit.getOnlineMode() + "\n" + 167 | "- Memory Usage: " + getMemoryUsage() + 168 | "\n\n| Plugins\n" + String.join("\n", pluginInfoList) + 169 | "\n\n| Plugin Configuration\n\n" + configuration.saveToString(); 170 | try { 171 | String pasteUrl = new Hastebin().post(pasteRaw, true); 172 | String pastedDumpMsg = ChatColor.translateAlternateColorCodes('&', 173 | "&c[ShopGUIPlus-SellGUI] Successfully dumped server information here: %s."); 174 | 175 | String message = String.format(Locale.US, pastedDumpMsg, pasteUrl); 176 | Bukkit.getConsoleSender().sendMessage(message); 177 | if (sender instanceof Player) sender.sendMessage(message); 178 | 179 | if (sender instanceof Player player) { 180 | PlayerHandler.playSound(player, "success"); 181 | } 182 | } catch (IOException ex) { 183 | sender.sendMessage(ChatColor.RED + "An error occurred, please check the console:"); 184 | ex.printStackTrace(); 185 | } 186 | 187 | return true; 188 | } 189 | 190 | private boolean commandBase(CommandSender sender) { 191 | if (!(sender instanceof Player player)) { 192 | sender.sendMessage(ChatColor.RED + "Only players can execute this command."); 193 | return true; 194 | } 195 | 196 | if (!player.hasPermission("sellgui.use")) { 197 | sendMessage(player, "no_permission"); 198 | return true; 199 | } 200 | 201 | if (!checkGameMode(player)) { 202 | return true; 203 | } 204 | 205 | CommentedConfiguration configuration = this.plugin.getConfiguration(); 206 | int guiSize = configuration.getInt("options.rows", 6); 207 | if (guiSize > 6 || guiSize < 1) { 208 | guiSize = 6; 209 | } 210 | 211 | String sellGuiTitle = getMessage("sellgui_title", null); 212 | Component sellGuiTitleComponent = LegacyComponentSerializer.legacySection().deserialize(sellGuiTitle); 213 | Gui gui = Gui.gui().title(sellGuiTitleComponent).rows(guiSize).create(); 214 | PlayerHandler.playSound(player, "open"); 215 | 216 | Set ignoredSlotSet = new HashSet<>(); 217 | setDecorationItems(configuration, gui, ignoredSlotSet); 218 | gui.setCloseGuiAction(event -> scheduler.runAtEntity(player, task -> onGuiClose(player, event, ignoredSlotSet))); 219 | 220 | scheduler.runAtEntity(player, task -> gui.open(player)); 221 | return true; 222 | } 223 | 224 | private boolean checkGameMode(Player player) { 225 | ShopGuiPlugin shopGui = ShopGuiPlusApi.getPlugin(); 226 | FileConfiguration configuration = shopGui.getConfigMain().getConfig(); 227 | List disabledGameModeList = configuration.getStringList("disableShopsInGamemodes"); 228 | 229 | GameMode gameMode = player.getGameMode(); 230 | String gameModeName = gameMode.name(); 231 | if (disabledGameModeList.contains(gameModeName)) { 232 | String gameModeFormatted = StringFormatter.capitalize(gameModeName); 233 | sendMessage(player, "gamemode_not_allowed", message -> 234 | message.replace("{gamemode}", gameModeFormatted)); 235 | return false; 236 | } 237 | 238 | return true; 239 | } 240 | 241 | private void setDecorationItems(ConfigurationSection configuration, Gui gui, Set ignoredSlotSet) { 242 | ConfigurationSection sectionDecorations = configuration.getConfigurationSection("options.decorations"); 243 | if (sectionDecorations == null) { 244 | return; 245 | } 246 | 247 | Set sectionDecorationsKeys = sectionDecorations.getKeys(false); 248 | for (String key : sectionDecorationsKeys) { 249 | ConfigurationSection section = sectionDecorations.getConfigurationSection(key); 250 | if (section == null) { 251 | continue; 252 | } 253 | 254 | Material material; 255 | String materialName = section.getString("item.material"); 256 | if (materialName == null || (material = Material.matchMaterial(materialName)) == null 257 | || !section.isInt("slot") || section.getInt("slot") > ((gui.getRows() * 9) - 1) 258 | || section.getInt("slot") < 0) { 259 | Logger logger = this.plugin.getLogger(); 260 | logger.warning("Failed to load decoration item with id '" + key + "'."); 261 | continue; 262 | } 263 | 264 | ItemStack item = new ItemStack(material); 265 | setItemDamage(item, section.getInt("item.damage", 0)); 266 | item.setAmount(section.getInt("item.quantity", 1)); 267 | 268 | ItemMeta itemMeta = item.getItemMeta(); 269 | if (itemMeta != null) { 270 | String displayName = section.getString("item.name"); 271 | if (displayName != null) { 272 | displayName = MessageUtility.color(displayName); 273 | itemMeta.setDisplayName(displayName); 274 | } 275 | 276 | List loreList = section.getStringList("item.lore"); 277 | if (!loreList.isEmpty()) { 278 | List processedLore = new ArrayList<>(loreList.size()); 279 | for (String line : loreList) { 280 | processedLore.add(MessageUtility.color(HexColorUtility.replaceHexColors('&', line))); 281 | } 282 | 283 | itemMeta.setLore(processedLore); 284 | } 285 | 286 | int customModelData = section.getInt("item.customModelData"); 287 | if (customModelData != 0) { 288 | itemMeta.setCustomModelData(customModelData); 289 | } 290 | 291 | item.setItemMeta(itemMeta); 292 | } 293 | 294 | List consoleCommandList = section.getStringList("commandsOnClickConsole"); 295 | List playerCommandList = section.getStringList("commandsOnClick"); 296 | 297 | GuiItem guiItem = new GuiItem(item, e -> { 298 | e.setCancelled(true); 299 | HumanEntity human = e.getWhoClicked(); 300 | String humanName = human.getName(); 301 | 302 | for (String consoleCommand : consoleCommandList) { 303 | String command = consoleCommand.replace("%PLAYER%", humanName); 304 | scheduler.runNextTick(task -> Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command)); 305 | } 306 | 307 | for (String playerCommand : playerCommandList) { 308 | String command = playerCommand.replace("%PLAYER%", humanName); 309 | scheduler.runAtEntity(human, task -> Bukkit.dispatchCommand(human, command)); 310 | } 311 | 312 | if (section.getBoolean("item.sellinventory")) { 313 | scheduler.runAtEntity(human, task -> human.closeInventory()); 314 | commandBase(Bukkit.getPlayer(humanName)); 315 | } 316 | }); 317 | 318 | int slot = section.getInt("slot"); 319 | gui.setItem(slot, guiItem); 320 | 321 | ignoredSlotSet.add(slot); 322 | } 323 | } 324 | 325 | private String getMemoryUsage() { 326 | Runtime runtime = Runtime.getRuntime(); 327 | long totalMemory = runtime.totalMemory(); 328 | long freeMemory = runtime.freeMemory(); 329 | long maxMemory = runtime.maxMemory(); 330 | 331 | long usedMemory = (totalMemory - freeMemory); 332 | long usedMemoryMB = (usedMemory / 1_048_576L); 333 | long maxMemoryMB = (maxMemory / 1_048_576L); 334 | 335 | return String.format(Locale.US, "%s / %s MiB", usedMemoryMB, maxMemoryMB); 336 | } 337 | 338 | private void setItemDamage(ItemStack item, int damage) { 339 | int minorVersion = VersionUtility.getMinorVersion(); 340 | if (minorVersion < 13) { 341 | short durability = (short) damage; 342 | item.setDurability(durability); 343 | return; 344 | } 345 | 346 | ItemMeta itemMeta = item.getItemMeta(); 347 | if (itemMeta instanceof Damageable) { 348 | ((Damageable) itemMeta).setDamage(damage); 349 | item.setItemMeta(itemMeta); 350 | } 351 | } 352 | 353 | private void onGuiClose(Player player, InventoryCloseEvent event, Set ignoredSlotSet) { 354 | int minorVersion = VersionUtility.getMinorVersion(); 355 | CommentedConfiguration configuration = this.plugin.getConfiguration(); 356 | 357 | // ItemStack is a stack size of 0, Integer is Price 358 | Map itemStackSellPriceCache = new HashMap<>(); 359 | 360 | Map> soldMap2 = new HashMap<>(); 361 | Map moneyMap = new EnumMap<>(EconomyType.class); 362 | 363 | double totalPrice = 0; 364 | int itemAmount = 0; 365 | boolean excessItems = false; 366 | boolean itemsPlacedInGui = false; 367 | 368 | Inventory inventory = event.getInventory(); 369 | for (int a = 0; a < inventory.getSize(); a++) { 370 | ItemStack i = inventory.getItem(a); 371 | 372 | // Quick check for invalid items: null or ignored item 373 | if (i == null || ignoredSlotSet.contains(a)) { 374 | continue; 375 | } 376 | 377 | itemsPlacedInGui = true; 378 | 379 | ItemStack singleItem = new ItemStack(i); 380 | singleItem.setAmount(1); 381 | 382 | if (itemStackSellPriceCache.getOrDefault(singleItem, new ShopItemPriceValue(null, 0.0)).getSellPrice() > 0 || ShopGuiPlusApi.getItemStackPriceSell(player, i) > 0) { 383 | itemAmount += i.getAmount(); 384 | 385 | @Deprecated 386 | short materialDamage = i.getDurability(); 387 | int amount = i.getAmount(); 388 | 389 | double itemSellPrice = itemStackSellPriceCache.containsKey(singleItem) ? itemStackSellPriceCache.get(singleItem).getSellPrice() * amount : ShopGuiPlusApi.getItemStackPriceSell(player, i); 390 | 391 | totalPrice += itemSellPrice; 392 | 393 | EconomyType itemEconomyType = ShopHandler.getEconomyType(i); 394 | 395 | ItemStack SingleItemStack = new ItemStack(i); 396 | SingleItemStack.setAmount(1); 397 | 398 | itemStackSellPriceCache.putIfAbsent(SingleItemStack, new ShopItemPriceValue(itemEconomyType, itemSellPrice/amount)); 399 | 400 | Map totalSold = soldMap2.getOrDefault(SingleItemStack, new HashMap<>()); 401 | int totalSoldCount = totalSold.getOrDefault(materialDamage, 0); 402 | int amountSold = (totalSoldCount + amount); 403 | 404 | totalSold.put(materialDamage, amountSold); 405 | soldMap2.put(SingleItemStack, totalSold); 406 | 407 | double totalSold2 = moneyMap.getOrDefault(itemEconomyType, 0.0); 408 | double amountSold2 = (totalSold2 + itemSellPrice); 409 | moneyMap.put(itemEconomyType, amountSold2); 410 | } else { 411 | excessItems = true; 412 | 413 | Location location = player.getLocation().add(0.0D, 0.5D, 0.0D); 414 | Map fallenItems = event.getPlayer().getInventory().addItem(i); 415 | scheduler.runAtLocation(location, task -> { 416 | World world = player.getWorld(); 417 | fallenItems.values().forEach(item -> world.dropItemNaturally(location, item)); 418 | }); 419 | } 420 | } 421 | 422 | if (excessItems) { 423 | sendMessage(player, "inventory_full"); 424 | } 425 | 426 | if (totalPrice == 0) { 427 | PlayerHandler.playSound(player, "failed"); 428 | sendMessage(player, itemsPlacedInGui ? "no_items_sold" : "no_items_in_gui"); 429 | return; 430 | } 431 | 432 | PlayerHandler.playSound(player, "success"); 433 | StringBuilder formattedPricing = new StringBuilder(); 434 | for (Entry entry : moneyMap.entrySet()) { 435 | EconomyProvider economyProvider = ShopGuiPlusApi.getPlugin().getEconomyManager() 436 | .getEconomyProvider(entry.getKey()); 437 | economyProvider.deposit(player, entry.getValue()); 438 | formattedPricing.append(economyProvider.getCurrencyPrefix()).append(StringFormatter 439 | .getFormattedNumber(entry.getValue())).append(economyProvider.getCurrencySuffix()) 440 | .append(", "); 441 | } 442 | 443 | if (formattedPricing.toString().endsWith(", ")) { 444 | formattedPricing = new StringBuilder(formattedPricing.substring(0, 445 | formattedPricing.length() - 2)); 446 | } 447 | 448 | List receiptList = new LinkedList<>(); 449 | List itemList = new LinkedList<>(); 450 | 451 | if (configuration.getInt("options.receipt_type", 0) == 1 452 | || configuration.getString("messages.items_sold", "").contains("{list}")) { 453 | for (Entry> entry : soldMap2.entrySet()) { 454 | for (Entry damageEntry : entry.getValue().entrySet()) { 455 | @Deprecated 456 | ItemStack materialItemStack = entry.getKey(); 457 | 458 | double profits = ShopGuiPlusApi.getItemStackPriceSell(player, materialItemStack) 459 | * damageEntry.getValue(); 460 | String profitsFormatted = ShopGuiPlusApi.getPlugin().getEconomyManager() 461 | .getEconomyProvider(ShopHandler.getEconomyType(materialItemStack)) 462 | .getCurrencyPrefix() + StringFormatter.getFormattedNumber(profits) 463 | + ShopGuiPlusApi.getPlugin().getEconomyManager().getEconomyProvider( 464 | ShopHandler.getEconomyType(materialItemStack)) 465 | .getCurrencySuffix(); 466 | 467 | String itemNameFormatted = StringFormatter.capitalize(materialItemStack.getType() 468 | .name().replace("AETHER_LEGACY_", "") 469 | .replace("LOST_AETHER_", "") 470 | .replace("_", " ").toLowerCase()); 471 | 472 | ItemMeta itemMeta = materialItemStack.getItemMeta(); 473 | if (itemMeta != null && itemMeta.hasDisplayName()) { 474 | String displayName = itemMeta.getDisplayName(); 475 | if (!displayName.isEmpty()) { 476 | itemNameFormatted = materialItemStack.getItemMeta().getDisplayName(); 477 | } 478 | } 479 | 480 | if (minorVersion <= 12 && !configuration.getBoolean("options.show_item_damage", false)) { 481 | itemNameFormatted += (":" + damageEntry.getKey()); 482 | } 483 | 484 | String finalItemNameFormatted = itemNameFormatted; 485 | String itemLine = getMessage("receipt_item_layout", message -> message 486 | .replace("{amount}", String.valueOf(damageEntry.getValue())) 487 | .replace("{item}", finalItemNameFormatted) 488 | .replace("{price}", profitsFormatted)); 489 | 490 | receiptList.add(itemLine); 491 | itemList.add(itemNameFormatted); 492 | } 493 | } 494 | } 495 | 496 | String itemAmountFormatted = StringFormatter.getFormattedNumber((double) itemAmount); 497 | if (configuration.getInt("options.receipt_type", 0) == 1) { 498 | int finalItemAmount = itemAmount; 499 | StringBuilder finalFormattedPricing1 = formattedPricing; 500 | 501 | TextComponent itemsSoldComponent = getTextComponentMessage("items_sold", message -> message 502 | .replace("{earning}", finalFormattedPricing1) 503 | .replace("{receipt}", "") 504 | .replace("{list}", String.join(", ", itemList)) 505 | .replace("{amount}", String.valueOf(finalItemAmount))); 506 | itemsSoldComponent.addExtra(" "); 507 | 508 | String receiptHoverMessage = (getMessage("receipt_title", null) + ChatColor.RESET + String.join("\n", receiptList) + ChatColor.RESET); 509 | 510 | TextComponent receiptNameComponent = getTextComponentMessage("receipt_text", null); 511 | BaseComponent[] hoverEventComponents = TextComponent.fromLegacyText(receiptHoverMessage); 512 | 513 | HoverEvent hoverEvent = new HoverEvent(Action.SHOW_TEXT, hoverEventComponents); 514 | receiptNameComponent.setHoverEvent(hoverEvent); 515 | 516 | sendMessage(player, Arrays.asList(itemsSoldComponent, receiptNameComponent)); 517 | } else { 518 | StringBuilder finalFormattedPricing = formattedPricing; 519 | sendMessage(player, "items_sold", message -> message.replace("{earning}", 520 | finalFormattedPricing) 521 | .replace("{receipt}", "") 522 | .replace("{list}", String.join(", ", itemList)) 523 | .replace("{amount}", itemAmountFormatted)); 524 | } 525 | 526 | /* Subject to deprecation */ 527 | if (plugin.fileLogger != null) { 528 | plugin.fileLogger.info(player.getName() + " (" + player.getUniqueId() + ") sold: {" + HexColorUtility.purgeAllColor(String.join(", ", receiptList)) + "}"); 529 | } 530 | 531 | if (configuration.getBoolean("options.sell_titles", false)) { 532 | sendSellTitles(player, formattedPricing, itemAmountFormatted); 533 | } 534 | 535 | if (configuration.getBoolean("options.action_bar_msgs", false) && minorVersion >= 9) { 536 | sendActionBar(player, formattedPricing, itemAmountFormatted); 537 | } 538 | } 539 | 540 | private String getMessage(String path, @Nullable Function replacer) { 541 | CommentedConfiguration configuration = this.plugin.getConfiguration(); 542 | String message = configuration.getString("messages." + path, ""); 543 | if (message.isEmpty()) { 544 | return ""; 545 | } 546 | 547 | if (replacer != null) { 548 | message = replacer.apply(message); 549 | } 550 | 551 | return MessageUtility.color(HexColorUtility.replaceHexColors('&', message)); 552 | } 553 | 554 | private TextComponent getTextComponentMessage(String path, @Nullable Function replacer) { 555 | String message = getMessage(path, replacer); 556 | if (message.isEmpty()) { 557 | return new TextComponent(""); 558 | } else { 559 | BaseComponent[] components = TextComponent.fromLegacyText(message); 560 | TextComponent root = new TextComponent(""); 561 | for (BaseComponent component : components) { 562 | root.addExtra(component); 563 | } 564 | 565 | return root; 566 | } 567 | } 568 | 569 | private void sendMessage(CommandSender sender, String path) { 570 | sendMessage(sender, path, null); 571 | } 572 | 573 | private void sendMessage(CommandSender sender, String path, @Nullable Function replacer) { 574 | String message = getMessage(path, replacer); 575 | if (message.isEmpty()) { 576 | return; 577 | } 578 | 579 | if (sender instanceof Player player) { 580 | BaseComponent[] components = TextComponent.fromLegacyText(message); 581 | player.spigot().sendMessage(components); 582 | } else { 583 | sender.sendMessage(message); 584 | } 585 | } 586 | 587 | private void sendMessage(Player player, List textComponents) { 588 | boolean isTextPresent = textComponents.stream() 589 | .anyMatch(component -> component.getText() != null && !component.getText().isEmpty() 590 | || component.getExtra() != null && !component.getExtra().isEmpty()); 591 | 592 | if (!isTextPresent) { 593 | return; 594 | } 595 | 596 | player.spigot().sendMessage(textComponents.toArray(new BaseComponent[0])); 597 | } 598 | 599 | private void sendSellTitles(Player player, CharSequence price, String amount) { 600 | Function replacer = message -> message.replace("{earning}", price) 601 | .replace("{amount}", amount); 602 | 603 | String title = getMessage("sell_title", replacer); 604 | String subtitle = getMessage("sell_subtitle", replacer); 605 | player.sendTitle(title, subtitle); 606 | } 607 | 608 | private void sendActionBar(Player player, CharSequence price, String amount) { 609 | Function replacer = message -> message.replace("{earning}", price) 610 | .replace("{amount}", amount); 611 | 612 | TextComponent message = getTextComponentMessage("action_bar_items_sold", replacer); 613 | player.spigot().sendMessage(ChatMessageType.ACTION_BAR, message); 614 | } 615 | } 616 | --------------------------------------------------------------------------------