├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── games647 │ └── scoreboardstats │ └── config │ ├── CommentedYaml.java │ ├── ConfigNode.java │ ├── Settings.java │ ├── SidebarConfig.java │ └── VariableItem.java ├── defaults ├── lib │ └── BukkitGames-2.3.2.jar ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── games647 │ └── scoreboardstats │ └── defaults │ ├── ASkyBlockVariables.java │ ├── BukkitGamesVariables.java │ ├── BukkitGlobalVariables.java │ ├── BukkitVariables.java │ ├── BungeeCordVariables.java │ ├── CraftconomyVariables.java │ ├── FactionsVariables.java │ ├── GeneralVariables.java │ ├── GriefPreventionVariables.java │ ├── HeroesVariables.java │ ├── McCombatLevelVariables.java │ ├── McPrisonVariables.java │ ├── McmmoVariables.java │ ├── MyPetVariables.java │ ├── PlaceHolderVariables.java │ ├── PlayerPingVariable.java │ ├── PlayerPointsVariables.java │ ├── SimpleClansVariables.java │ ├── SkyblockVariables.java │ ├── TicksPerSecondTask.java │ └── VaultVariables.java ├── plugin ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── games647 │ │ │ └── scoreboardstats │ │ │ ├── PlayerListener.java │ │ │ ├── RefreshTask.java │ │ │ ├── SbManager.java │ │ │ ├── ScoreboardStats.java │ │ │ ├── commands │ │ │ ├── CommandHandler.java │ │ │ ├── SidebarCommands.java │ │ │ └── ToggleCommand.java │ │ │ └── scoreboard │ │ │ ├── Objective.java │ │ │ ├── PacketListener.java │ │ │ ├── PacketManager.java │ │ │ ├── PlayerScoreboard.java │ │ │ ├── SlotTransformer.java │ │ │ ├── State.java │ │ │ ├── Team.java │ │ │ └── TeamMode.java │ └── resources │ │ ├── config.yml │ │ ├── plugin.yml │ │ └── sql.yml │ └── test │ └── java │ └── com │ └── github │ └── games647 │ └── scoreboardstats │ └── variables │ └── VersionTest.java ├── pom.xml ├── pvp ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── github │ └── games647 │ └── scoreboardstats │ └── pvp │ ├── Database.java │ ├── DatabaseConfiguration.java │ ├── PlayerStats.java │ ├── SignListener.java │ ├── StatsListener.java │ └── StatsLoader.java └── variables ├── pom.xml └── src ├── main └── java │ └── com │ └── github │ └── games647 │ └── scoreboardstats │ ├── BoardManager.java │ ├── Version.java │ ├── scoreboard │ └── Score.java │ └── variables │ ├── DefaultReplacer.java │ ├── DefaultReplacers.java │ ├── EventReplacer.java │ ├── PluginListener.java │ ├── ReplaceManager.java │ ├── Replacer.java │ ├── ReplacerAPI.java │ ├── ReplacerException.java │ ├── UnknownVariableException.java │ └── UnsupportedPluginException.java └── test └── java └── com └── github └── games647 └── scoreboardstats └── variables └── ReplaceManagerTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse stuff 2 | /.classpath 3 | /.project 4 | /.settings 5 | 6 | # NetBeans 7 | */nbproject 8 | nb-configuration.xml 9 | 10 | # maven 11 | */target 12 | 13 | # vim 14 | .*.sw[a-p] 15 | 16 | # virtual machine crash logs, see https://www.java.com/en/download/help/error_hotspot.xml 17 | hs_err_pid* 18 | 19 | # various other potential build files 20 | */build/ 21 | /bin 22 | /dist 23 | /manifest.mf 24 | *.log 25 | 26 | # Mac filesystem dust 27 | .DS_Store 28 | 29 | # IntelliJ 30 | *.iml 31 | *.ipr 32 | *.iws 33 | .idea/ 34 | 35 | # Gradle 36 | .gradle 37 | 38 | # Ignore Gradle GUI config 39 | gradle-app.setting 40 | 41 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 42 | !gradle-wrapper.jar 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use https://travis-ci.org/ for automatic testing 2 | 3 | # speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/ 4 | sudo: false 5 | 6 | # This is a java project 7 | language: java 8 | 9 | script: mvn test -B 10 | 11 | jdk: 12 | - oraclejdk8 13 | - oraclejdk9 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 games647 and contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScoreboardStats 2 | 3 | ScoreboardStats is a [Bukkit](https://github.com/Bukkit/Bukkit) plugin 4 | that uses the sidebar from scoreboard feature which was introduced into 5 | [Minecraft](https://minecraft.net) since version 6 | [1.5](https://mcupdate.tumblr.com/post/45267771887/minecraft-1-5). 7 | This plugin simplifies, adds many new features and possible ways to improve 8 | the use of it. 9 | 10 | Project page: 11 | 12 | https://dev.bukkit.org/bukkit-mods/scoreboardstats/ 13 | 14 | https://www.curse.com/bukkit-plugins/minecraft/scoreboardstats 15 | 16 | ### For Plugin Developers 17 | 18 | https://github.com/games647/ScoreboardStats/wiki 19 | 20 | Feel free to contribute there too. 21 | Just click the edit button after you login into your Github account 22 | 23 | ### Building 24 | 25 | ScoreboardStats uses Maven 3 to manage building configurations, 26 | general project details and dependencies. 27 | You can compile this project yourself by using Maven. 28 | 29 | 30 | * Just import the project from [Github](https://github.com/). 31 | Your IDE would detect the Maven project. 32 | * If not: Download it from [here](https://maven.apache.org/download.cgi) 33 | You will find executable in the bin folder. 34 | * Run (using IDE, console or something else) 35 | -------------------------------------------------------------------------------- /config/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.games647 7 | scoreboardstats-parent 8 | 1.0.0 9 | 10 | 11 | scoreboardstats-config 12 | jar 13 | 14 | -------------------------------------------------------------------------------- /config/src/main/java/com/github/games647/scoreboardstats/config/CommentedYaml.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.config; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.base.Strings; 5 | import com.google.common.io.CharStreams; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.Modifier; 12 | import java.nio.file.Files; 13 | import java.nio.file.Path; 14 | import java.util.Collections; 15 | import java.util.List; 16 | 17 | import org.bukkit.ChatColor; 18 | import org.bukkit.configuration.Configuration; 19 | import org.bukkit.configuration.InvalidConfigurationException; 20 | import org.bukkit.configuration.file.FileConfiguration; 21 | import org.bukkit.configuration.file.YamlConfiguration; 22 | import org.slf4j.Logger; 23 | 24 | /** 25 | * Represents an easy way to load and save to yaml configs. Furthermore support 26 | * this class UTF-8 even before Bukkit introduced it and will also support comments 27 | * soon. 28 | */ 29 | class CommentedYaml { 30 | 31 | private static final String COMMENT_PREFIX = "# "; 32 | private static final String FILE_NAME = "config.yml"; 33 | 34 | protected final transient Logger logger; 35 | protected final transient Path dataFolder; 36 | protected transient FileConfiguration config; 37 | 38 | CommentedYaml(Logger logger, Path dataFolder) { 39 | this.logger = logger; 40 | this.dataFolder = dataFolder; 41 | } 42 | 43 | /** 44 | * Gets the YAML file configuration from the disk while loading it 45 | * explicit with UTF-8. This allows umlauts and other UTF-8 characters 46 | * for all Bukkit versions. 47 | *

48 | * Bukkit add also this feature since 49 | * https://github.com/Bukkit/Bukkit/commit/24883a61704f78a952e948c429f63c4a2ab39912 50 | * To be allow the same feature for all Bukkit version, this method was 51 | * created. 52 | * 53 | * @return the loaded file configuration 54 | */ 55 | public FileConfiguration getConfigFromDisk() { 56 | Path file = dataFolder.resolve(FILE_NAME); 57 | 58 | YamlConfiguration newConf = new YamlConfiguration(); 59 | newConf.setDefaults(getDefaults()); 60 | try { 61 | //UTF-8 should be available on all java running systems 62 | List lines = Files.readAllLines(file); 63 | load(lines, newConf); 64 | } catch (InvalidConfigurationException | IOException ex) { 65 | logger.error("Couldn't load the configuration", ex); 66 | } 67 | 68 | return newConf; 69 | } 70 | 71 | protected void loadConfig() { 72 | config = getConfigFromDisk(); 73 | 74 | for (Field field : getClass().getDeclaredFields()) { 75 | if (Modifier.isTransient(field.getModifiers()) 76 | || !field.getType().isPrimitive() && field.getType() != String.class) { 77 | continue; 78 | } 79 | 80 | ConfigNode node = field.getAnnotation(ConfigNode.class); 81 | String path = field.getName(); 82 | if (node != null && !Strings.isNullOrEmpty(path)) { 83 | path = node.path(); 84 | } 85 | 86 | field.setAccessible(true); 87 | setField(path, field); 88 | } 89 | } 90 | 91 | protected void saveConfig() { 92 | if (config == null) { 93 | return; 94 | } 95 | 96 | //todo add comment support 97 | try { 98 | List lines = Collections.singletonList(config.saveToString()); 99 | Files.write(dataFolder.resolve(FILE_NAME), lines); 100 | } catch (IOException ex) { 101 | logger.error("Failed to save config", ex); 102 | } 103 | } 104 | 105 | private void setField(String path, Field field) throws IllegalArgumentException { 106 | if (config.isSet(path)) { 107 | try { 108 | if (config.isString(path)) { 109 | field.set(this, ChatColor.translateAlternateColorCodes('&', config.getString(path))); 110 | } else { 111 | field.set(this, config.get(path)); 112 | } 113 | } catch (IllegalAccessException ex) { 114 | logger.error("Error setting field", ex); 115 | } 116 | } else { 117 | logger.error("Path not fond {}", path); 118 | } 119 | } 120 | 121 | private void load(Iterable lines, YamlConfiguration newConf) throws InvalidConfigurationException { 122 | StringBuilder builder = new StringBuilder(); 123 | for (String line : lines) { 124 | //remove the silly tab error from yaml 125 | builder.append(line.replace("\t", " ")); 126 | //indicates a new line 127 | builder.append('\n'); 128 | } 129 | 130 | newConf.loadFromString(builder.toString()); 131 | } 132 | 133 | /** 134 | * Get the default configuration located in the plugin jar 135 | * 136 | * @return the default configuration 137 | */ 138 | private Configuration getDefaults() { 139 | YamlConfiguration defaults = new YamlConfiguration(); 140 | 141 | try (InputStream defConfigStream = getClass().getResourceAsStream('/' + FILE_NAME)) { 142 | try { 143 | Readable reader = new InputStreamReader(defConfigStream, Charsets.UTF_8); 144 | //stream will be closed in this method 145 | List lines = CharStreams.readLines(reader); 146 | load(lines, defaults); 147 | return defaults; 148 | } catch (InvalidConfigurationException ex) { 149 | logger.error("Invalid Configuration", ex); 150 | } catch (IOException ex) { 151 | logger.error("Couldn't load the configuration", ex); 152 | } 153 | } catch (IOException ioEx) { 154 | logger.error("Failed to find default", ioEx); 155 | } 156 | 157 | return defaults; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /config/src/main/java/com/github/games647/scoreboardstats/config/ConfigNode.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.config; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Specializes a non-default path for config node 11 | */ 12 | @Target(ElementType.FIELD) 13 | @Documented 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @interface ConfigNode { 16 | 17 | /** 18 | * Defines the path to the node if it has another as the variable name. 19 | * Every indention is separated with an dot ('.') 20 | * 21 | * @return the path to the node 22 | */ 23 | String path() default ""; 24 | } 25 | -------------------------------------------------------------------------------- /config/src/main/java/com/github/games647/scoreboardstats/config/Settings.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.config; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | 5 | import java.util.Set; 6 | 7 | import org.bukkit.configuration.ConfigurationSection; 8 | import org.bukkit.plugin.Plugin; 9 | import org.slf4j.Logger; 10 | 11 | /** 12 | * Managing all general configurations of this plugin. 13 | */ 14 | public class Settings extends CommentedYaml { 15 | 16 | private static final String MIN_ITEMS = "A scoreboard have to display min. 1 item ({})"; 17 | private static final String TOO_MANY_ITEMS = "Scoreboard can't have more than 15 items"; 18 | 19 | @ConfigNode(path = "enable-pvpstats") 20 | private static boolean pvpStats; 21 | 22 | @ConfigNode(path = "Temp-Scoreboard-enabled") 23 | private static boolean tempScoreboard; 24 | 25 | private static SidebarConfig mainScoreboard; 26 | 27 | @ConfigNode(path = "Temp-Scoreboard.Title") 28 | private static String tempTitle; 29 | 30 | @ConfigNode(path = "Temp-Scoreboard.Color") 31 | private static String tempColor; 32 | 33 | @ConfigNode(path = "Temp-Scoreboard.Type") 34 | private static String topType; 35 | 36 | @ConfigNode(path = "Scoreboard.Update-delay") 37 | private static int interval; 38 | 39 | @ConfigNode(path = "Temp-Scoreboard.Items") 40 | private static int topItems; 41 | 42 | @ConfigNode(path = "Temp-Scoreboard.Interval-show") 43 | private static int tempShow; 44 | 45 | @ConfigNode(path = "Temp-Scoreboard.Interval-Disappear") 46 | private static int tempDisappear; 47 | 48 | private static Set disabledWorlds; 49 | 50 | public Settings(Plugin plugin, Logger logger) { 51 | super(logger, plugin.getDataFolder().toPath()); 52 | 53 | plugin.saveDefaultConfig(); 54 | } 55 | 56 | /** 57 | * Check if a world is from ScoreboardStats active 58 | * 59 | * @param worldName the checked world 60 | * @return if the world is disabled 61 | */ 62 | public static boolean isActiveWorld(String worldName) { 63 | return !disabledWorlds.contains(worldName); 64 | } 65 | 66 | /** 67 | * Check whether tracking of players stats is enabled 68 | * 69 | * @return whether tracking of players stats is enabled 70 | */ 71 | public static boolean isPvpStats() { 72 | return pvpStats; 73 | } 74 | 75 | /** 76 | * Check if the temp-scoreboard is enabled 77 | * 78 | * @return if the temp-scoreboard is enabled 79 | */ 80 | public static boolean isTempScoreboard() { 81 | return tempScoreboard; 82 | } 83 | 84 | public static SidebarConfig getMainScoreboard() { 85 | return mainScoreboard; 86 | } 87 | 88 | public static String getTempTitle() { 89 | return tempTitle; 90 | } 91 | 92 | public static String getTempColor() { 93 | return tempColor; 94 | } 95 | 96 | /** 97 | * Get the type what the temp-scoreboard should display 98 | * 99 | * @return what the temp-scoreboard should display 100 | */ 101 | public static String getTopType() { 102 | return topType; 103 | } 104 | 105 | /** 106 | * Get the interval in which the items being refreshed. 107 | * 108 | * @return the interval in which the items being refreshed. 109 | */ 110 | public static int getInterval() { 111 | return interval; 112 | } 113 | 114 | /** 115 | * Get how many items the temp-scoreboard should have 116 | * 117 | * @return how many items the temp-scoreboard should have 118 | */ 119 | public static int getTopitems() { 120 | return topItems; 121 | } 122 | 123 | /** 124 | * Get the seconds after the temp-scoreboard should appear. 125 | * 126 | * @return the seconds after the temp-scoreboard should appear 127 | */ 128 | public static int getTempAppear() { 129 | return tempShow; 130 | } 131 | 132 | /** 133 | * Get the seconds after the temp-scoreboard should disappear. 134 | * 135 | * @return the seconds after the temp-scoreboard should disappear 136 | */ 137 | public static int getTempDisappear() { 138 | return tempDisappear; 139 | } 140 | 141 | /** 142 | * Load the configuration file in memory and convert it into simple variables 143 | */ 144 | @Override 145 | public void loadConfig() { 146 | super.loadConfig(); 147 | 148 | //This set only changes after another call to loadConfig so this set can be immutable 149 | disabledWorlds = ImmutableSet.copyOf(config.getStringList("disabled-disabledWorlds")); 150 | 151 | tempTitle = trimLength(tempTitle, 32); 152 | 153 | String title = config.getString("Scoreboard.Title"); 154 | mainScoreboard = new SidebarConfig(trimLength(title, 32)); 155 | //Load all normal scoreboard variables 156 | loadItems(config.getConfigurationSection("Scoreboard.Items")); 157 | 158 | //temp-scoreboard 159 | tempScoreboard = tempScoreboard && pvpStats; 160 | topItems = checkItems(topItems); 161 | topType = topType.replace("%", ""); 162 | } 163 | 164 | private String trimLength(String check, int limit) { 165 | //Check if the string is longer, so we don't end up with a IndexOutOfBoundEx 166 | if (check.length() > limit) { 167 | //If the string check is longer cut it down 168 | String cut = check.substring(0, limit); 169 | logger.warn("{} was longer than {} characters. We remove the overlapping characters", cut, limit); 170 | 171 | return cut; 172 | } 173 | 174 | return check; 175 | } 176 | 177 | private int checkItems(int input) { 178 | if (input >= 16) { 179 | //Only 15 items per sidebar objective are allowed 180 | logger.warn(TOO_MANY_ITEMS); 181 | return 16 - 1; 182 | } 183 | 184 | if (input <= 0) { 185 | logger.warn(MIN_ITEMS, "tempscoreboard"); 186 | return 5; 187 | } 188 | 189 | return input; 190 | } 191 | 192 | private void loadItems(ConfigurationSection config) { 193 | //clear all existing items 194 | mainScoreboard.clear(); 195 | 196 | //not implemented yet in compatibility mode 197 | int maxLength = 40; 198 | for (String key : config.getKeys(false)) { 199 | if (mainScoreboard.size() == 15) { 200 | //Only 15 items per sidebar objective are allowed 201 | logger.warn(TOO_MANY_ITEMS); 202 | break; 203 | } 204 | 205 | String displayName = trimLength(key, maxLength); 206 | String value = config.getString(key); 207 | if (displayName.contains("%")) { 208 | String variable = ""; 209 | // mainScoreboard.addVariableItem(true, variable, displayName, value); 210 | } else if (value.charAt(0) == '%' && value.charAt(value.length() - 1) == '%') { 211 | //Prevent case-sensitive mistakes 212 | String variable = value.replace("%", "").toLowerCase(); 213 | mainScoreboard.addVariableItem(false, variable, displayName, 0); 214 | } else { 215 | try { 216 | int score = Integer.parseInt(value); 217 | mainScoreboard.addItem(displayName, score); 218 | } catch (NumberFormatException numberFormatException) { 219 | //Prevent user mistakes 220 | logger.info("Replacer {} has to contain % at the beginning and at the end", displayName); 221 | } 222 | } 223 | } 224 | 225 | if (mainScoreboard.size() == 0) { 226 | //It won't show up if there isn't at least one item 227 | logger.info(MIN_ITEMS, "scoreboard"); 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /config/src/main/java/com/github/games647/scoreboardstats/config/SidebarConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.config; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.Map; 6 | 7 | import org.bukkit.ChatColor; 8 | 9 | public class SidebarConfig { 10 | 11 | private final Map itemsByName = Maps.newHashMapWithExpectedSize(15); 12 | private final Map itemsByVariable = Maps.newHashMapWithExpectedSize(15); 13 | private String displayName; 14 | 15 | public SidebarConfig(String displayName) { 16 | this.displayName = ChatColor.translateAlternateColorCodes('&', displayName); 17 | } 18 | 19 | public String getTitle() { 20 | return displayName; 21 | } 22 | 23 | public void setTitle(String displayName) { 24 | this.displayName = ChatColor.translateAlternateColorCodes('&', displayName); 25 | } 26 | 27 | public void addItem(String displayName, int score) { 28 | String colorName = ChatColor.translateAlternateColorCodes('&', displayName); 29 | 30 | VariableItem variableItem = new VariableItem(false, null, colorName, score); 31 | itemsByName.put(colorName, variableItem); 32 | } 33 | 34 | public void addVariableItem(boolean textVariable, String variable, String displayText, int defaultScore) { 35 | String coloredDisplay = ChatColor.translateAlternateColorCodes('&', displayText); 36 | 37 | VariableItem variableItem = new VariableItem(textVariable, variable, coloredDisplay, defaultScore); 38 | itemsByName.put(coloredDisplay, variableItem); 39 | itemsByVariable.put(variable, variableItem); 40 | } 41 | 42 | public void remove(VariableItem variableItem) { 43 | itemsByName.remove(variableItem.getDisplayText()); 44 | itemsByVariable.remove(variableItem.getVariable()); 45 | } 46 | 47 | public Map getItemsByName() { 48 | return itemsByName; 49 | } 50 | 51 | public Map getItemsByVariable() { 52 | return itemsByVariable; 53 | } 54 | 55 | public int size() { 56 | return itemsByName.size(); 57 | } 58 | 59 | public void clear() { 60 | itemsByVariable.clear(); 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return "SidebarConfig{" + "displayName=" 66 | + displayName + ", itemsByVariable=" 67 | + itemsByVariable 68 | + '}'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /config/src/main/java/com/github/games647/scoreboardstats/config/VariableItem.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.config; 2 | 3 | public class VariableItem { 4 | 5 | private final boolean textVariable; 6 | private final String variable; 7 | 8 | private String displayText; 9 | private int score; 10 | 11 | public VariableItem(boolean textVariable, String variable, String displayText, int defaultScore) { 12 | this(textVariable, variable, displayText); 13 | 14 | this.score = defaultScore; 15 | } 16 | 17 | public VariableItem(boolean textVariable, String variable, String displayText) { 18 | this.textVariable = textVariable; 19 | this.variable = variable; 20 | this.displayText = displayText; 21 | } 22 | 23 | public String getDisplayText() { 24 | return displayText; 25 | } 26 | 27 | public void setDisplayText(String displayText) { 28 | this.displayText = displayText; 29 | } 30 | 31 | public int getScore() { 32 | return score; 33 | } 34 | 35 | public void setScore(int score) { 36 | this.score = score; 37 | } 38 | 39 | public boolean isTextVariable() { 40 | return textVariable; 41 | } 42 | 43 | public String getVariable() { 44 | return variable; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "VariableItem{" 50 | + "textVariable=" + textVariable 51 | + ", variable=" + variable 52 | + ", displayText=" + displayText 53 | + ", score=" + score 54 | + '}'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /defaults/lib/BukkitGames-2.3.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TuxCoding/ScoreboardStats/HEAD/defaults/lib/BukkitGames-2.3.2.jar -------------------------------------------------------------------------------- /defaults/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.games647 7 | scoreboardstats-parent 8 | 1.0.0 9 | 10 | 11 | scoreboardstats-defaults 12 | jar 13 | 14 | 15 | 16 | 17 | md_5-releases 18 | https://repo.md-5.net/content/groups/public/ 19 | 20 | 21 | 22 | 23 | vault-repo 24 | http://nexus.hc.to/content/repositories/pub_releases/ 25 | 26 | 27 | 28 | heroes-repo 29 | http://nexus.hc.to/content/repositories/pub_snapshots/ 30 | 31 | 32 | 33 | sonatype-repo 34 | https://oss.sonatype.org/content/repositories/snapshots/ 35 | 36 | 37 | 38 | xephi-repo 39 | https://ci.xephi.fr/plugin/repository/everything/ 40 | 41 | 42 | 43 | 44 | Dakani 45 | Dakani Nexus Repo 46 | https://repo.dakanilabs.com/repository/public/ 47 | 48 | 49 | 50 | 51 | simpleclans-repo 52 | http://104.236.246.108:8081/artifactory/plugins-release-local 53 | 54 | 55 | 56 | uSkyBlock-mvn-repo 57 | https://raw.github.com/rlf/uSkyBlock/mvn-repo/ 58 | 59 | 60 | 61 | 62 | jitpack.io 63 | https://jitpack.io 64 | 65 | 66 | 67 | 68 | bintray-tastybento-maven-repo 69 | https://dl.bintray.com/tastybento/maven-repo 70 | 71 | 72 | 73 | 74 | mypet-repo 75 | http://repo.keyle.de/ 76 | 77 | 78 | 79 | 80 | extendedclip-repo 81 | http://repo.extendedclip.com/content/repositories/placeholderapi/ 82 | 83 | 84 | 85 | 86 | dustplanet-repo 87 | https://repo.dustplanet.de/artifactory/ext-release-local/ 88 | 89 | 90 | 91 | 92 | enderzone-repo 93 | https://ci.ender.zone/plugin/repository/everything/ 94 | 95 | 96 | 97 | 98 | 99 | com.github.games647 100 | scoreboardstats-variables 101 | 1.0.0 102 | 103 | 104 | 105 | com.wasteofplastic 106 | askyblock 107 | 3.0.7 108 | 109 | 110 | * 111 | * 112 | 113 | 114 | 115 | 116 | 117 | me.clip 118 | placeholderapi 119 | 2.8.2 120 | 121 | 122 | * 123 | * 124 | 125 | 126 | 127 | 128 | 129 | de.keyle 130 | mypet 131 | 2.3.2 132 | 133 | 134 | * 135 | * 136 | 137 | 138 | 139 | 140 | 141 | 142 | net.milkbowl.vault 143 | VaultAPI 144 | 1.6 145 | 146 | 147 | * 148 | * 149 | 150 | 151 | true 152 | 153 | 154 | 155 | net.sacredlabyrinth.phaed.simpleclans 156 | SimpleClans 157 | 2.7.5 158 | 159 | 160 | * 161 | * 162 | 163 | 164 | true 165 | 166 | 167 | 168 | org.black_ixx 169 | PlayerPoints 170 | 2.1.5 171 | 172 | 173 | * 174 | * 175 | 176 | 177 | true 178 | 179 | 180 | 181 | com.greatmancode 182 | craftconomy3 183 | 3.3.1 184 | 185 | 186 | * 187 | * 188 | 189 | 190 | true 191 | 192 | 193 | 194 | com.herocraftonline.heroes 195 | Heroes 196 | 1.5.5.7 197 | 198 | 199 | * 200 | * 201 | 202 | 203 | true 204 | 205 | 206 | 207 | com.gmail.nossr50.mcMMO 208 | mcMMO 209 | 1.5.00 210 | 211 | 212 | * 213 | * 214 | 215 | 216 | true 217 | 218 | 219 | 220 | uSkyBlock 221 | uSkyBlock 222 | 2.4.5 223 | true 224 | 225 | 226 | * 227 | * 228 | 229 | 230 | 231 | 232 | 233 | com.github.SirFaizdat 234 | Prison 235 | v2.4.1 236 | 237 | 238 | * 239 | * 240 | 241 | 242 | true 243 | 244 | 245 | 246 | com.github.TechFortress 247 | GriefPrevention 248 | 16.7.1 249 | 250 | 251 | * 252 | * 253 | 254 | 255 | 256 | 257 | 258 | com.github.games647 259 | McCombatLevel 260 | 3ab946cf26 261 | 262 | 263 | * 264 | * 265 | 266 | 267 | 268 | 269 | 270 | 271 | BukkitGames 272 | BukkitGames 273 | 2.3.2 274 | system 275 | ${project.basedir}/lib/BukkitGames-2.3.2.jar 276 | true 277 | 278 | 279 | * 280 | * 281 | 282 | 283 | 284 | 285 | 286 | 287 | com.massivecraft 288 | 289 | Factions 290 | 1.6.9.5-U0.2.2 291 | 292 | 293 | * 294 | * 295 | 296 | 297 | true 298 | 299 | 300 | 301 | com.massivecraft 302 | factions 303 | 2.12.0 304 | 305 | 306 | * 307 | * 308 | 309 | 310 | 311 | 312 | 313 | 314 | com.massivecraft 315 | massivecore 316 | 2.12.0 317 | 318 | 319 | * 320 | * 321 | 322 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/ASkyBlockVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.wasteofplastic.askyblock.ASkyBlockAPI; 7 | import com.wasteofplastic.askyblock.events.ChallengeCompleteEvent; 8 | import com.wasteofplastic.askyblock.events.IslandPostLevelEvent; 9 | 10 | import java.util.function.ToIntFunction; 11 | import java.util.stream.Stream; 12 | 13 | import org.bukkit.entity.Player; 14 | import org.bukkit.plugin.Plugin; 15 | 16 | @DefaultReplacer(plugin = "ASkyBlock") 17 | public class ASkyBlockVariables extends DefaultReplacers { 18 | 19 | private final ASkyBlockAPI skyBlockAPI = ASkyBlockAPI.getInstance(); 20 | 21 | public ASkyBlockVariables(ReplacerAPI replaceManager, Plugin plugin) { 22 | super(replaceManager, plugin); 23 | } 24 | 25 | @Override 26 | public void register() { 27 | register("island-level") 28 | .scoreSupply(player -> (int) skyBlockAPI.getLongIslandLevel(player.getUniqueId())) 29 | .eventScore(IslandPostLevelEvent.class, event -> (int) event.getLongLevel()); 30 | 31 | register("challenge_done") 32 | .scoreSupply(player -> (int) skyBlockAPI.getLongIslandLevel(player.getUniqueId())) 33 | .eventScore(ChallengeCompleteEvent.class, event -> getDoneChallenge(event.getPlayer())); 34 | 35 | register("challenge_incomplete") 36 | .scoreSupply(player -> (int) skyBlockAPI.getLongIslandLevel(player.getUniqueId())) 37 | .eventScore(ChallengeCompleteEvent.class, event -> getIncompleteChallenge(event.getPlayer())); 38 | 39 | register("challenge_total") 40 | .scoreSupply(player -> (int) skyBlockAPI.getLongIslandLevel(player.getUniqueId())); 41 | 42 | register("challenge_unique") 43 | .scoreSupply(player -> (int) skyBlockAPI.getLongIslandLevel(player.getUniqueId())); 44 | } 45 | 46 | private int getDoneChallenge(Player player) { 47 | return mapChallenges(player, challenges -> (int) challenges 48 | .filter(complete -> complete) 49 | .count()); 50 | } 51 | 52 | private int getIncompleteChallenge(Player player) { 53 | return mapChallenges(player, challenges -> (int) challenges 54 | .filter(complete -> !complete) 55 | .count()); 56 | } 57 | 58 | private int getTotalChallenge(Player player) { 59 | return mapTimes(player, lst -> (int) lst 60 | .filter(times -> times > 0) 61 | .count()); 62 | } 63 | 64 | private int getUniqueChallenge(Player player) { 65 | return mapTimes(player, lst -> lst 66 | .mapToInt(time -> time) 67 | .sum()); 68 | } 69 | 70 | private int mapTimes(Player player, ToIntFunction> fct) { 71 | return fct.applyAsInt(skyBlockAPI.getChallengeTimes(player.getUniqueId()).values().stream()); 72 | } 73 | 74 | private int mapChallenges(Player player, ToIntFunction> fct) { 75 | return fct.applyAsInt(skyBlockAPI.getChallengeStatus(player.getUniqueId()).values().stream()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/BukkitGamesVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import de.ftbastler.bukkitgames.api.BukkitGamesAPI; 8 | import de.ftbastler.bukkitgames.api.PlayerBuyKitEvent; 9 | 10 | import org.bukkit.plugin.Plugin; 11 | 12 | @DefaultReplacer(plugin = "BukkitGames") 13 | public class BukkitGamesVariables extends DefaultReplacers { 14 | 15 | private final BukkitGamesAPI bukkitGamesAPI = BukkitGamesAPI.getApi(); 16 | 17 | public BukkitGamesVariables(ReplacerAPI replaceManager, Plugin plugin) { 18 | super(replaceManager, plugin); 19 | } 20 | 21 | @Override 22 | public void register() { 23 | register("coins") 24 | .scoreSupply(bukkitGamesAPI::getPlayerBalance) 25 | .eventScore(PlayerBuyKitEvent.class, event -> { 26 | int oldBalance = bukkitGamesAPI.getPlayerBalance(event.getPlayer()); 27 | return oldBalance - event.getKitCost(); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/BukkitGlobalVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.event.player.PlayerJoinEvent; 9 | import org.bukkit.event.player.PlayerQuitEvent; 10 | import org.bukkit.plugin.Plugin; 11 | import org.bukkit.util.NumberConversions; 12 | 13 | /** 14 | * Replace all Bukkit variables which are the same for players. Currently 15 | * there is no good way to mark variables as global 16 | */ 17 | @DefaultReplacer 18 | public class BukkitGlobalVariables extends DefaultReplacers { 19 | 20 | public BukkitGlobalVariables(ReplacerAPI replaceManager, Plugin plugin) { 21 | super(replaceManager, plugin); 22 | } 23 | 24 | @Override 25 | public void register() { 26 | register("tps") 27 | .scoreSupply(() -> NumberConversions.round(TicksPerSecondTask.getLastTicks())); 28 | 29 | register("online") 30 | .scoreSupply(() -> Bukkit.getOnlinePlayers().size()) 31 | .eventScore(PlayerJoinEvent.class, () -> Bukkit.getOnlinePlayers().size()) 32 | .eventScore(PlayerQuitEvent.class, () -> Bukkit.getOnlinePlayers().size() - 1); 33 | 34 | register("max_player") 35 | .scoreSupply(Bukkit::getMaxPlayers) 36 | .constant(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/BukkitVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.inventory.ItemStack; 9 | import org.bukkit.plugin.Plugin; 10 | import org.bukkit.util.NumberConversions; 11 | 12 | /** 13 | * Replace all Bukkit variables 14 | */ 15 | @DefaultReplacer 16 | public class BukkitVariables extends DefaultReplacers { 17 | 18 | private static final int MINUTE_TO_SECOND = 60; 19 | 20 | public BukkitVariables(ReplacerAPI replaceManager, Plugin plugin) { 21 | super(replaceManager, plugin); 22 | } 23 | 24 | @Override 25 | public void register() { 26 | register("health").scoreSupply(player -> NumberConversions.round(player.getHealth())); 27 | register("lifetime").scoreSupply(player -> player.getTicksLived() / (20 * MINUTE_TO_SECOND)); 28 | register("no_damage_ticks").scoreSupply(player -> player.getNoDamageTicks() / (20 * MINUTE_TO_SECOND)); 29 | register("last_damage").scoreSupply(player -> (int) (player.getLastDamage())); 30 | 31 | register("exp").scoreSupply(Player::getTotalExperience); 32 | register("xp_to_level").scoreSupply(Player::getExpToLevel); 33 | 34 | 35 | register("helmet").scoreSupply(player -> calculateDurability(player.getInventory().getHelmet())); 36 | register("boots").scoreSupply(player -> calculateDurability(player.getInventory().getBoots())); 37 | register("leggings").scoreSupply(player -> calculateDurability(player.getInventory().getLeggings())); 38 | register("chestplate").scoreSupply(player -> calculateDurability(player.getInventory().getChestplate())); 39 | 40 | register("chestplate").scoreSupply(player -> (int) player.getWorld().getTime()); 41 | } 42 | 43 | private int calculateDurability(ItemStack item) { 44 | //Check if the user have an item on the slot and if the item isn't a stone block or something 45 | if (item == null || item.getType().getMaxDurability() == 0) { 46 | return 0; 47 | } else { 48 | //calculate in percent 49 | return item.getDurability() * 100 / item.getType().getMaxDurability(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/BungeeCordVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.google.common.collect.Iterables; 7 | import com.google.common.io.ByteArrayDataInput; 8 | import com.google.common.io.ByteArrayDataOutput; 9 | import com.google.common.io.ByteStreams; 10 | 11 | import org.bukkit.Bukkit; 12 | import org.bukkit.entity.Player; 13 | import org.bukkit.plugin.Plugin; 14 | import org.bukkit.plugin.messaging.Messenger; 15 | import org.bukkit.plugin.messaging.PluginMessageListener; 16 | 17 | @DefaultReplacer 18 | public class BungeeCordVariables extends DefaultReplacers implements PluginMessageListener, Runnable { 19 | 20 | private static final int UPDATE_INTERVAL = 20 * 30; 21 | private static final String BUNGEE_CHANNEL = "BungeeCord"; 22 | 23 | private int onlinePlayers; 24 | 25 | public BungeeCordVariables(ReplacerAPI replaceManager, Plugin plugin) { 26 | super(replaceManager, plugin); 27 | } 28 | 29 | @Override 30 | public void register() { 31 | Messenger messenger = Bukkit.getMessenger(); 32 | messenger.registerOutgoingPluginChannel(plugin, BUNGEE_CHANNEL); 33 | messenger.registerIncomingPluginChannel(plugin, BUNGEE_CHANNEL, this); 34 | 35 | Bukkit.getScheduler().runTaskTimer(plugin, this, UPDATE_INTERVAL, UPDATE_INTERVAL); 36 | 37 | register("bungee-online").scoreSupply(() -> onlinePlayers); 38 | } 39 | 40 | @Override 41 | public void onPluginMessageReceived(String channel, Player player, byte[] message) { 42 | if (!channel.equals(BUNGEE_CHANNEL)) { 43 | return; 44 | } 45 | 46 | ByteArrayDataInput in = ByteStreams.newDataInput(message); 47 | String subChannel = in.readUTF(); 48 | if ("PlayerCount".equals(subChannel)) { 49 | try { 50 | String server = in.readUTF(); 51 | int count = in.readInt(); 52 | 53 | if ("ALL".equals(server)) { 54 | onlinePlayers = count; 55 | } 56 | } catch (Exception eofException) { 57 | //happens if bungeecord doesn't know the server 58 | //ignore the admin should be notified by seeing the -1 59 | } 60 | } 61 | } 62 | 63 | @Override 64 | public void run() { 65 | Player sender = Iterables.getFirst(Bukkit.getOnlinePlayers(), null); 66 | if (sender != null) { 67 | ByteArrayDataOutput out = ByteStreams.newDataOutput(); 68 | out.writeUTF("PlayerCount"); 69 | out.writeUTF("ALL"); 70 | 71 | sender.sendPluginMessage(plugin, BUNGEE_CHANNEL, out.toByteArray()); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/CraftconomyVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.greatmancode.craftconomy3.Common; 7 | import com.greatmancode.craftconomy3.account.AccountManager; 8 | import com.greatmancode.craftconomy3.currency.Currency; 9 | import com.greatmancode.craftconomy3.currency.CurrencyManager; 10 | 11 | import org.bukkit.plugin.Plugin; 12 | import org.bukkit.util.NumberConversions; 13 | 14 | @DefaultReplacer(plugin = "CraftConomy3") 15 | public class CraftconomyVariables extends DefaultReplacers { 16 | 17 | private final AccountManager accountManager = Common.getInstance().getAccountManager(); 18 | private final CurrencyManager currencyManager = Common.getInstance().getCurrencyManager(); 19 | 20 | public CraftconomyVariables(ReplacerAPI replaceManager, Plugin plugin) { 21 | super(replaceManager, plugin); 22 | } 23 | 24 | @Override 25 | public void register() { 26 | if (Common.isInitialized()) { 27 | for (String currencyName : currencyManager.getCurrencyNames()) { 28 | register("money_" + currencyName) 29 | .scoreSupply(player -> { 30 | Currency currency = currencyManager.getCurrency(currencyName); 31 | double balance = accountManager.getAccount(player.getWorld().getName(), false) 32 | .getBalance(player.getWorld().getName(), currencyName); 33 | return NumberConversions.round(balance); 34 | }); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/FactionsVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.Version; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 5 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 6 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 7 | import com.github.games647.scoreboardstats.variables.UnsupportedPluginException; 8 | import com.massivecraft.factions.FPlayer; 9 | import com.massivecraft.factions.FPlayers; 10 | import com.massivecraft.factions.entity.MPlayer; 11 | 12 | import java.util.Collection; 13 | import java.util.Optional; 14 | 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.plugin.Plugin; 17 | 18 | /** 19 | * Replace all variables that are associated with the faction plugin 20 | *

21 | * https://dev.bukkit.org/bukkit-plugins/factions/ 22 | */ 23 | @DefaultReplacer(plugin = "Factions") 24 | public class FactionsVariables extends DefaultReplacers { 25 | 26 | private final boolean newVersion; 27 | 28 | public FactionsVariables(ReplacerAPI replaceManager, Plugin plugin) throws UnsupportedPluginException { 29 | super(replaceManager, plugin); 30 | 31 | String version = plugin.getDescription().getVersion(); 32 | newVersion = Version.compare("2", version) >= 0; 33 | 34 | //Version is between 2.0 and 2.7 35 | if (newVersion && Version.compare("2.7", version) < 0) { 36 | throw new UnsupportedPluginException("Due the newest changes from " 37 | + "Factions, you have to upgrade your version to a version above 2.7. " 38 | + "If explicitly want to use this version. Create a ticket on " 39 | + "the project page of this plugin"); 40 | } 41 | } 42 | 43 | //faction 2.7+ 44 | private void registerNewFactions() { 45 | register("power") 46 | .scoreSupply(player -> getMPlayer(player) 47 | .map(MPlayer::getPowerRounded) 48 | .orElse(-1)); 49 | 50 | register("f_power") 51 | .scoreSupply(player -> getNewFaction(player) 52 | .map(com.massivecraft.factions.entity.Faction::getPowerRounded) 53 | .orElse(-1)); 54 | 55 | register("members") 56 | .scoreSupply(player -> getNewFaction(player) 57 | .map(com.massivecraft.factions.entity.Faction::getMPlayers) 58 | .map(Collection::size) 59 | .orElse(-1)); 60 | 61 | register("members_online") 62 | .scoreSupply(player -> getNewFaction(player) 63 | .map(com.massivecraft.factions.entity.Faction::getMPlayers) 64 | .map(Collection::size) 65 | .orElse(-1)); 66 | } 67 | 68 | //factions 1.6.9 and 1.8.2 69 | private void registerOldFactions() { 70 | register("power") 71 | .scoreSupply(player -> getFPlayer(player) 72 | .map(FPlayer::getPowerRounded) 73 | .orElse(-1)); 74 | 75 | register("f_power") 76 | .scoreSupply(player -> getOldFaction(player) 77 | .map(com.massivecraft.factions.Faction::getPowerRounded) 78 | .orElse(-1)); 79 | 80 | register("members") 81 | .scoreSupply(player -> getOldFaction(player) 82 | .map(com.massivecraft.factions.Faction::getFPlayers) 83 | .map(Collection::size) 84 | .orElse(-1)); 85 | 86 | register("members_online") 87 | .scoreSupply(player -> getOldFaction(player) 88 | .map(com.massivecraft.factions.Faction::getFPlayers) 89 | .map(Collection::size) 90 | .orElse(-1)); 91 | } 92 | 93 | @Override 94 | public void register() { 95 | if (newVersion) { 96 | registerNewFactions(); 97 | } else { 98 | registerOldFactions(); 99 | } 100 | } 101 | 102 | private Optional getMPlayer(Player player) { 103 | return Optional.ofNullable(MPlayer.get(player)); 104 | } 105 | 106 | private Optional getNewFaction(Player player) { 107 | return getMPlayer(player).map(MPlayer::getFaction); 108 | } 109 | 110 | private Optional getFPlayer(Player player) { 111 | return Optional.ofNullable(FPlayers.getInstance().getByPlayer(player)); 112 | } 113 | 114 | private Optional getOldFaction(Player player) { 115 | return getFPlayer(player).map(FPlayer::getFaction); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/GeneralVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import java.util.Calendar; 8 | 9 | import org.bukkit.plugin.Plugin; 10 | 11 | /** 12 | * Represents a replacer for non Minecraft related variables 13 | */ 14 | @DefaultReplacer 15 | public class GeneralVariables extends DefaultReplacers { 16 | 17 | //From bytes to mega bytes 18 | private static final int MB_CONVERSION = 1_024 * 1_024; 19 | 20 | private final Runtime runtime = Runtime.getRuntime(); 21 | 22 | public GeneralVariables(ReplacerAPI replaceManager, Plugin plugin) { 23 | super(replaceManager, plugin); 24 | } 25 | 26 | @Override 27 | public void register() { 28 | register("free_ram").scoreSupply(() -> (int) (runtime.freeMemory() / MB_CONVERSION)); 29 | 30 | register("max_ram").scoreSupply(() -> (int) (runtime.maxMemory() - runtime.freeMemory() / MB_CONVERSION)); 31 | 32 | register("used_ram") 33 | .scoreSupply(() -> (int) ((runtime.maxMemory() - runtime.freeMemory()) * 100 / runtime.maxMemory())); 34 | 35 | register("date").scoreSupply(() -> Calendar.getInstance().get(Calendar.DAY_OF_MONTH)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/GriefPreventionVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import me.ryanhamshire.GriefPrevention.GriefPrevention; 8 | import me.ryanhamshire.GriefPrevention.PlayerData; 9 | 10 | import org.bukkit.entity.Player; 11 | 12 | @DefaultReplacer(plugin = "GriefPrevention") 13 | public class GriefPreventionVariables extends DefaultReplacers { 14 | 15 | public GriefPreventionVariables(ReplacerAPI replaceManager, GriefPrevention plugin) { 16 | super(replaceManager, plugin); 17 | } 18 | 19 | @Override 20 | public void register() { 21 | register("accrued_claim_block") 22 | .scoreSupply(player -> getData(player).getAccruedClaimBlocks()); 23 | 24 | register("bonus_claim_blocks") 25 | .scoreSupply(player -> getData(player).getBonusClaimBlocks()); 26 | 27 | register("remaining_blocks") 28 | .scoreSupply(player -> getData(player).getRemainingClaimBlocks()); 29 | 30 | register("total_blocks") 31 | .scoreSupply(player -> { 32 | PlayerData playerData = getData(player); 33 | return plugin.dataStore.getGroupBonusBlocks(player.getUniqueId()) 34 | + playerData.getAccruedClaimBlocks() 35 | + playerData.getRemainingClaimBlocks() 36 | + playerData.getBonusClaimBlocks(); 37 | }); 38 | 39 | register("group_bonus_blocks") 40 | .scoreSupply(player -> plugin.dataStore.getGroupBonusBlocks(player.getUniqueId())); 41 | } 42 | 43 | private PlayerData getData(Player player) { 44 | return plugin.dataStore.getPlayerData(player.getUniqueId()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/HeroesVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.herocraftonline.heroes.Heroes; 7 | import com.herocraftonline.heroes.api.events.ClassChangeEvent; 8 | import com.herocraftonline.heroes.api.events.HeroChangeLevelEvent; 9 | import com.herocraftonline.heroes.api.events.HeroRegainManaEvent; 10 | import com.herocraftonline.heroes.api.events.SkillUseEvent; 11 | import com.herocraftonline.heroes.characters.CharacterManager; 12 | import com.herocraftonline.heroes.characters.Hero; 13 | 14 | import org.bukkit.entity.Player; 15 | 16 | /** 17 | * Replace all variables that are associated with the heroes plugin 18 | *

19 | * https://dev.bukkit.org/bukkit-plugins/heroes/ 20 | */ 21 | @DefaultReplacer(plugin = "Heroes") 22 | public class HeroesVariables extends DefaultReplacers { 23 | 24 | private final CharacterManager characterManager; 25 | 26 | public HeroesVariables(ReplacerAPI replaceManager, Heroes plugin) { 27 | super(replaceManager, plugin); 28 | 29 | this.characterManager = plugin.getCharacterManager(); 30 | } 31 | 32 | @Override 33 | public void register() { 34 | register("mana").scoreSupply(player -> getHero(player).getMana()) 35 | .eventScore(SkillUseEvent.class, event -> event.getHero().getMana() - event.getManaCost()) 36 | .eventScore(HeroRegainManaEvent.class, event -> event.getHero().getMana()); 37 | 38 | register("max_mana").scoreSupply(player -> getHero(player).getMaxMana()) 39 | .eventScore(ClassChangeEvent.class, event -> event.getHero().getMaxMana()); 40 | 41 | register("level").scoreSupply(player -> getHero(player).getLevel()) 42 | .eventScore(HeroChangeLevelEvent.class, HeroChangeLevelEvent::getTo); 43 | } 44 | 45 | private Hero getHero(Player player) { 46 | return characterManager.getHero(player); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/McCombatLevelVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.gmail.mrphpfan.mccombatlevel.McCombatLevel; 7 | import com.gmail.mrphpfan.mccombatlevel.PlayerCombatLevelChangeEvent; 8 | 9 | @DefaultReplacer(plugin = "mcCombatLevel") 10 | public class McCombatLevelVariables extends DefaultReplacers { 11 | 12 | public McCombatLevelVariables(ReplacerAPI replaceManager, McCombatLevel plugin) { 13 | super(replaceManager, plugin); 14 | } 15 | 16 | @Override 17 | public void register() { 18 | register("mc_combat_level") 19 | .scoreSupply(plugin::getCombatLevel) 20 | .eventScore(PlayerCombatLevelChangeEvent.class, PlayerCombatLevelChangeEvent::getNewLevel); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/McPrisonVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.github.games647.scoreboardstats.variables.UnsupportedPluginException; 7 | 8 | import me.sirfaizdat.prison.ranks.Ranks; 9 | import me.sirfaizdat.prison.ranks.UserInfo; 10 | import me.sirfaizdat.prison.ranks.events.BalanceChangeEvent; 11 | import me.sirfaizdat.prison.ranks.events.DemoteEvent; 12 | import me.sirfaizdat.prison.ranks.events.RankupEvent; 13 | 14 | import net.milkbowl.vault.economy.Economy; 15 | 16 | import org.bukkit.Bukkit; 17 | import org.bukkit.entity.Player; 18 | import org.bukkit.plugin.Plugin; 19 | import org.bukkit.plugin.RegisteredServiceProvider; 20 | 21 | /** 22 | * Replace all variables that are associated with the prison plugin 23 | *

24 | * https://dev.bukkit.org/bukkit-plugins/mcprison/ 25 | */ 26 | @DefaultReplacer(plugin = "Prison") 27 | public class McPrisonVariables extends DefaultReplacers { 28 | 29 | private final Economy eco; 30 | 31 | public McPrisonVariables(ReplacerAPI replaceManager, Plugin plugin) throws UnsupportedPluginException { 32 | super(replaceManager, plugin); 33 | 34 | RegisteredServiceProvider economyProvider = Bukkit.getServicesManager().getRegistration(Economy.class); 35 | if (economyProvider == null) { 36 | throw new UnsupportedPluginException("Couldn't find an economy plugin"); 37 | } else { 38 | eco = economyProvider.getProvider(); 39 | } 40 | } 41 | 42 | @Override 43 | public void register() { 44 | register("moneyNeeded").scoreSupply(this::getMoneyNeeded) 45 | .eventScore(RankupEvent.class, event -> getMoneyNeeded(event.getPlayer())) 46 | .eventScore(BalanceChangeEvent.class, event -> getMoneyNeeded(event.getPlayer())) 47 | .eventScore(DemoteEvent.class, event -> getMoneyNeeded(event.getPlayer())); 48 | } 49 | 50 | private int getMoneyNeeded(Player player) { 51 | UserInfo userInfo = Ranks.i.getUserInfo(player.getName()); 52 | return (int) (userInfo.getNextRank().getPrice() - eco.getBalance(player)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/McmmoVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.gmail.nossr50.api.ExperienceAPI; 7 | import com.gmail.nossr50.datatypes.skills.SkillType; 8 | import com.gmail.nossr50.events.experience.McMMOPlayerLevelChangeEvent; 9 | import com.gmail.nossr50.events.experience.McMMOPlayerLevelDownEvent; 10 | import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent; 11 | 12 | import java.util.stream.Stream; 13 | 14 | import org.bukkit.plugin.Plugin; 15 | 16 | /** 17 | * Replace all variables that are associated with the mcMMO plugin 18 | *

19 | * https://dev.bukkit.org/bukkit-plugins/mcmmo/ 20 | */ 21 | @DefaultReplacer(plugin = "mcMMO") 22 | public class McmmoVariables extends DefaultReplacers { 23 | 24 | public McmmoVariables(ReplacerAPI replaceManager, Plugin plugin) { 25 | super(replaceManager, plugin); 26 | } 27 | 28 | @Override 29 | public void register() { 30 | register("powlvl").scoreSupply(ExperienceAPI::getPowerLevel) 31 | .eventScore(McMMOPlayerLevelUpEvent.class, this::getPowerLevel) 32 | .eventScore(McMMOPlayerLevelDownEvent.class, this::getPowerLevel); 33 | 34 | Stream.of(SkillType.values()) 35 | .map(SkillType::name) 36 | .map(String::toUpperCase) 37 | .forEach(skill -> register(skill) 38 | .scoreSupply(player -> ExperienceAPI.getLevel(player, skill)) 39 | .eventScore(McMMOPlayerLevelUpEvent.class, event -> ExperienceAPI 40 | .getLevel(event.getPlayer(), skill)) 41 | .eventScore(McMMOPlayerLevelDownEvent.class, event -> ExperienceAPI 42 | .getLevel(event.getPlayer(), skill))); 43 | } 44 | 45 | private int getPowerLevel(McMMOPlayerLevelChangeEvent changeEvent) { 46 | return ExperienceAPI.getPowerLevel(changeEvent.getPlayer()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/MyPetVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import de.Keyle.MyPet.MyPetApi; 8 | import de.Keyle.MyPet.api.event.MyPetLevelUpEvent; 9 | import de.Keyle.MyPet.api.player.MyPetPlayer; 10 | 11 | import org.bukkit.plugin.Plugin; 12 | 13 | @DefaultReplacer(plugin = "MyPet") 14 | public class MyPetVariables extends DefaultReplacers { 15 | 16 | public MyPetVariables(ReplacerAPI replaceManager, Plugin plugin) { 17 | super(replaceManager, plugin); 18 | } 19 | 20 | @Override 21 | public void register() { 22 | register("pet_level").scoreSupply(player -> { 23 | MyPetPlayer myPetPlayer = MyPetApi.getPlayerManager().getMyPetPlayer(player); 24 | return myPetPlayer.getMyPet().getExperience().getLevel(); 25 | }).eventScore(MyPetLevelUpEvent.class, MyPetLevelUpEvent::getLevel); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/PlaceHolderVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.google.common.collect.Sets; 7 | 8 | import java.util.Collection; 9 | import java.util.Set; 10 | 11 | import me.clip.placeholderapi.PlaceholderAPI; 12 | import me.clip.placeholderapi.PlaceholderHook; 13 | import me.clip.placeholderapi.expansion.PlaceholderExpansion; 14 | import me.clip.placeholderapi.external.EZPlaceholderHook; 15 | 16 | import org.bukkit.plugin.Plugin; 17 | 18 | @DefaultReplacer(plugin = "PlaceholderAPI") 19 | public class PlaceHolderVariables extends DefaultReplacers { 20 | 21 | public PlaceHolderVariables(ReplacerAPI replaceManager, Plugin plugin) { 22 | super(replaceManager, plugin); 23 | } 24 | 25 | @Override 26 | public void register() { 27 | Set variables = Sets.newHashSet(); 28 | 29 | Collection hooks = PlaceholderAPI.getPlaceholders().values(); 30 | for (PlaceholderHook hook : hooks) { 31 | String variablePrefix = null; 32 | if (hook instanceof EZPlaceholderHook) { 33 | variablePrefix = ((EZPlaceholderHook) hook).getPlaceholderName(); 34 | } else if (hook instanceof PlaceholderExpansion) { 35 | variablePrefix = ((PlaceholderExpansion) hook).getIdentifier(); 36 | } 37 | 38 | if (variablePrefix != null) { 39 | variables.add(variablePrefix + "_*"); 40 | } 41 | } 42 | 43 | for (String variable : variables) { 44 | register(variable).supply(player -> PlaceholderAPI.setPlaceholders(player, '%' + variable + '%')); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/PlayerPingVariable.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import java.lang.reflect.Field; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.plugin.Plugin; 13 | 14 | /** 15 | * Replace the ping variable. 16 | */ 17 | @DefaultReplacer 18 | public class PlayerPingVariable extends DefaultReplacers { 19 | 20 | private Method getHandleMethod; 21 | private Field pingField; 22 | 23 | public PlayerPingVariable(ReplacerAPI replaceManager, Plugin plugin) { 24 | super(replaceManager, plugin); 25 | } 26 | 27 | @Override 28 | public void register() { 29 | register("ping").scoreSupply(this::getReflectionPing); 30 | } 31 | 32 | private int getReflectionPing(Player player) { 33 | try { 34 | if (getHandleMethod == null) { 35 | getHandleMethod = player.getClass().getDeclaredMethod("getHandle"); 36 | //disable java security check. This will speed it a little 37 | getHandleMethod.setAccessible(true); 38 | } 39 | 40 | Object entityPlayer = getHandleMethod.invoke(player); 41 | 42 | if (pingField == null) { 43 | pingField = entityPlayer.getClass().getDeclaredField("ping"); 44 | //disable java security check. This will speed it a little 45 | pingField.setAccessible(true); 46 | } 47 | 48 | //returns the found int value 49 | return pingField.getInt(entityPlayer); 50 | } catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InvocationTargetException ex) { 51 | //Forward the exception to replaceManager 52 | throw new RuntimeException(ex); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/PlayerPointsVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import org.black_ixx.playerpoints.PlayerPoints; 8 | import org.black_ixx.playerpoints.event.PlayerPointsChangeEvent; 9 | import org.black_ixx.playerpoints.event.PlayerPointsResetEvent; 10 | 11 | /** 12 | * Represents a replacer for the Plugin PlayerPoints 13 | *

14 | * https://dev.bukkit.org/bukkit-plugins/playerpoints/ 15 | */ 16 | @DefaultReplacer(plugin = "PlayerPoints") 17 | public class PlayerPointsVariables extends DefaultReplacers { 18 | 19 | public PlayerPointsVariables(ReplacerAPI replaceManager, PlayerPoints plugin) { 20 | super(replaceManager, plugin); 21 | } 22 | 23 | @Override 24 | public void register() { 25 | register("points") 26 | .scoreSupply(player -> plugin.getAPI().look(player.getUniqueId())) 27 | .eventScore(PlayerPointsResetEvent.class, () -> 0) 28 | .eventScore(PlayerPointsChangeEvent.class, event -> { 29 | int lastBal = plugin.getAPI().look(event.getPlayerId()); 30 | return lastBal + event.getChange(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/SimpleClansVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | 7 | import java.util.Collection; 8 | import java.util.Optional; 9 | 10 | import net.sacredlabyrinth.phaed.simpleclans.Clan; 11 | import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; 12 | import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; 13 | import net.sacredlabyrinth.phaed.simpleclans.managers.ClanManager; 14 | 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.util.NumberConversions; 17 | 18 | /** 19 | * Replace all variables that are associated with the SimpleClans plugin 20 | *

21 | * https://dev.bukkit.org/bukkit-plugins/simpleclans/ 22 | * 23 | * @see SimpleClans 24 | * @see ClanPlayer 25 | * @see Clan 26 | * @see ClanManager 27 | */ 28 | @DefaultReplacer(plugin = "SimpleClans") 29 | public class SimpleClansVariables extends DefaultReplacers { 30 | 31 | private final ClanManager clanManager; 32 | 33 | public SimpleClansVariables(ReplacerAPI replaceManager, SimpleClans plugin) { 34 | super(replaceManager, plugin); 35 | 36 | clanManager = plugin.getClanManager(); 37 | } 38 | 39 | @Override 40 | public void register() { 41 | register("kills").scoreSupply(player -> getClanPlayer(player) 42 | .map(clanPlayer -> clanPlayer.getCivilianKills() 43 | + clanPlayer.getNeutralKills() 44 | + clanPlayer.getRivalKills()) 45 | .orElse(-1)); 46 | 47 | register("deaths").scoreSupply(player -> getClanPlayer(player) 48 | .map(ClanPlayer::getDeaths) 49 | .orElse(-1)); 50 | 51 | register("kdr").scoreSupply(player -> getClanPlayer(player) 52 | .map(ClanPlayer::getKDR) 53 | .map(NumberConversions::round) 54 | .orElse(-1)); 55 | 56 | register("members").scoreSupply(player -> getClan(player) 57 | .map(Clan::getSize) 58 | .orElse(-1)); 59 | 60 | register("clan_kdr").scoreSupply(player -> getClan(player) 61 | .map(Clan::getTotalKDR) 62 | .map(NumberConversions::round) 63 | .orElse(-1)); 64 | 65 | register("clan_money").scoreSupply(player -> getClan(player) 66 | .map(Clan::getBalance) 67 | .map(NumberConversions::round) 68 | .orElse(-1)); 69 | 70 | register("rivals").scoreSupply(player -> getClan(player) 71 | .map(Clan::getRivals) 72 | .map(Collection::size) 73 | .orElse(-1)); 74 | 75 | register("allies").scoreSupply(player -> getClan(player) 76 | .map(Clan::getAllies) 77 | .map(Collection::size) 78 | .orElse(-1)); 79 | 80 | register("members_online").scoreSupply(player -> getClan(player) 81 | .map(Clan::getOnlineMembers) 82 | .map(Collection::size) 83 | .orElse(-1)); 84 | 85 | register("allies_total").scoreSupply(player -> getClan(player) 86 | .map(Clan::getAllAllyMembers) 87 | .map(Collection::size) 88 | .orElse(-1)); 89 | 90 | register("clan_kills").scoreSupply(player -> getClan(player) 91 | .map(clan -> clan.getTotalCivilian() 92 | + clan.getTotalNeutral() 93 | + clan.getTotalRival()) 94 | .orElse(-1)); 95 | } 96 | 97 | private Optional getClanPlayer(Player player) { 98 | return Optional.ofNullable(clanManager.getClanPlayer(player)); 99 | } 100 | 101 | private Optional getClan(Player player) { 102 | return getClanPlayer(player).map(ClanPlayer::getClan); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/SkyblockVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.github.games647.scoreboardstats.variables.UnsupportedPluginException; 7 | 8 | import org.bukkit.plugin.Plugin; 9 | import org.bukkit.util.NumberConversions; 10 | import us.talabrek.ultimateskyblock.api.event.uSkyBlockScoreChangedEvent; 11 | import us.talabrek.ultimateskyblock.api.uSkyBlockAPI; 12 | 13 | /** 14 | * Replace all variables that are associated with the uSkyBlock plugin 15 | *

16 | * https://dev.bukkit.org/bukkit-plugins/uskyblock/ 17 | */ 18 | @DefaultReplacer(plugin = "uSkyblock") 19 | public class SkyblockVariables extends DefaultReplacers { 20 | 21 | public SkyblockVariables(ReplacerAPI replaceManager, uSkyBlockAPI plugin) { 22 | super(replaceManager, plugin); 23 | } 24 | 25 | private static uSkyBlockAPI getCheckVersion(Plugin plugin) throws UnsupportedPluginException { 26 | if (plugin instanceof uSkyBlockAPI) { 27 | return (uSkyBlockAPI) plugin; 28 | } else { 29 | throw new UnsupportedPluginException("Your uSkyBlock version is outdated"); 30 | } 31 | } 32 | 33 | @Override 34 | public void register() { 35 | register("island_level") 36 | .scoreSupply(player -> NumberConversions.round(plugin.getIslandLevel(player))) 37 | .eventScore(uSkyBlockScoreChangedEvent.class, this::getNewScore); 38 | } 39 | 40 | private int getNewScore(uSkyBlockScoreChangedEvent changedEvent) { 41 | return NumberConversions.round(changedEvent.getScore().getScore()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/TicksPerSecondTask.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | /** 4 | * Tracks how many ticks are passed per second. 5 | */ 6 | public class TicksPerSecondTask implements Runnable { 7 | 8 | private static float lastTicks = 20.0F; 9 | //the last time we updated the ticks 10 | private long lastCheck; 11 | 12 | /** 13 | * Get the ticks count of the last check. 20 Ticks should pass per second 14 | * 15 | * @return the ticks count of the last check 16 | */ 17 | public static float getLastTicks() { 18 | return lastTicks; 19 | } 20 | 21 | @Override 22 | public void run() { 23 | //nanoTime is more accurate 24 | long currentTime = System.nanoTime(); 25 | long timeSpent = currentTime - lastCheck; 26 | //update the last check 27 | lastCheck = currentTime; 28 | 29 | //how many ticks passed since the last check * 1000 to convert to seconds 30 | float tps = 3 * 20 * 1000.0F / (timeSpent / (1_000 * 1_000)); 31 | if (tps >= 0.0F && tps < 20.0F) { 32 | //Prevent all invalid values 33 | lastTicks = tps; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /defaults/src/main/java/com/github/games647/scoreboardstats/defaults/VaultVariables.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.defaults; 2 | 3 | import com.github.games647.scoreboardstats.variables.DefaultReplacer; 4 | import com.github.games647.scoreboardstats.variables.DefaultReplacers; 5 | import com.github.games647.scoreboardstats.variables.ReplacerAPI; 6 | import com.github.games647.scoreboardstats.variables.UnsupportedPluginException; 7 | 8 | import net.milkbowl.vault.economy.Economy; 9 | 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.plugin.Plugin; 13 | import org.bukkit.plugin.RegisteredServiceProvider; 14 | import org.bukkit.util.NumberConversions; 15 | 16 | /** 17 | * Replace the economy variable with Vault. 18 | *

19 | * https://dev.bukkit.org/bukkit-plugins/vault/ 20 | * 21 | * @see Economy 22 | */ 23 | @DefaultReplacer(plugin = "Vault") 24 | public class VaultVariables extends DefaultReplacers { 25 | 26 | private final Economy economy; 27 | 28 | public VaultVariables(ReplacerAPI replaceManager, Plugin plugin) throws UnsupportedPluginException { 29 | super(replaceManager, plugin); 30 | 31 | RegisteredServiceProvider economyProvider = Bukkit.getServicesManager().getRegistration(Economy.class); 32 | if (economyProvider == null) { 33 | //check if an economy plugin is installed otherwise it would throw a exception if the want to replace 34 | throw new UnsupportedPluginException("Cannot find an economy plugin"); 35 | } else { 36 | economy = economyProvider.getProvider(); 37 | } 38 | } 39 | 40 | @Override 41 | public void register() { 42 | register("money").scoreSupply(this::getBalance); 43 | } 44 | 45 | private int getBalance(Player player) { 46 | return NumberConversions.round(economy.getBalance(player, player.getWorld().getName())); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.games647 7 | scoreboardstats-parent 8 | 1.0.0 9 | 10 | 11 | scoreboardstats-plugin 12 | jar 13 | 14 | 15 | 16 | ${project.name} 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-shade-plugin 22 | 3.1.0 23 | 24 | false 25 | false 26 | 27 | 28 | com.github.games647:* 29 | 30 | com.zaxxer:HikariCP 31 | 32 | org.slf4j:slf4j-jdk14 33 | org.slf4j:slf4j-api 34 | 35 | 36 | 37 | 38 | 39 | package 40 | 41 | shade 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | src/main/resources 51 | 52 | true 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | md_5-releases 61 | https://repo.md-5.net/content/groups/public/ 62 | 63 | 64 | 65 | 66 | shadowvolt-repo 67 | http://repo.dmulloy2.net/content/groups/public/ 68 | 69 | 70 | 71 | 72 | 73 | com.github.games647 74 | scoreboardstats-variables 75 | 1.0.0 76 | 77 | 78 | 79 | com.github.games647 80 | scoreboardstats-defaults 81 | 1.0.0 82 | 83 | 84 | 85 | com.github.games647 86 | scoreboardstats-config 87 | 1.0.0 88 | 89 | 90 | 91 | com.github.games647 92 | scoreboardstats-pvp 93 | 1.0.0 94 | 95 | 96 | 99 | 100 | com.comphenix.protocol 101 | ProtocolLib 102 | 4.4.0-SNAPSHOT 103 | true 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/PlayerListener.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import com.github.games647.scoreboardstats.config.Settings; 4 | 5 | import org.bukkit.entity.Player; 6 | import org.bukkit.event.EventHandler; 7 | import org.bukkit.event.EventPriority; 8 | import org.bukkit.event.Listener; 9 | import org.bukkit.event.player.PlayerChangedWorldEvent; 10 | import org.bukkit.event.player.PlayerJoinEvent; 11 | import org.bukkit.event.player.PlayerQuitEvent; 12 | 13 | /** 14 | * Listening to players events. 15 | */ 16 | class PlayerListener implements Listener { 17 | 18 | private final ScoreboardStats plugin; 19 | 20 | /** 21 | * Creates a new player listener 22 | * 23 | * @param plugin ScoreboardStats plugin 24 | */ 25 | public PlayerListener(ScoreboardStats plugin) { 26 | this.plugin = plugin; 27 | } 28 | 29 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) 30 | public void onPlayerJoin(PlayerJoinEvent joinEvent) { 31 | Player player = joinEvent.getPlayer(); 32 | if (Settings.isActiveWorld(player.getWorld().getName())) { 33 | //add it to the refresh queue 34 | plugin.getRefreshTask().addToQueue(joinEvent.getPlayer()); 35 | } 36 | } 37 | 38 | @EventHandler(priority = EventPriority.LOWEST) 39 | public void onPlayerQuit(PlayerQuitEvent quitEvent) { 40 | Player player = quitEvent.getPlayer(); 41 | plugin.getRefreshTask().remove(player); 42 | plugin.getScoreboardManager().unregister(player); 43 | } 44 | 45 | /** 46 | * Check if the player moves in a scoreboard disabled world 47 | * 48 | * @param worldChange the teleport event 49 | * @see com.github.games647.scoreboardstats.RefreshTask 50 | */ 51 | //ignore cancelled events 52 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) 53 | public void onWorldChange(PlayerChangedWorldEvent worldChange) { 54 | Player player = worldChange.getPlayer(); 55 | //new world 56 | if (Settings.isActiveWorld(player.getWorld().getName())) { 57 | //old world 58 | if (!Settings.isActiveWorld(worldChange.getFrom().getName())) { 59 | //Activate the scoreboard if it was disabled 60 | plugin.getRefreshTask().addToQueue(player); 61 | } 62 | } else { 63 | //Disable the scoreboard if the player goes into a disabled world 64 | plugin.getRefreshTask().remove(player); 65 | plugin.getScoreboardManager().unregister(player); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/RefreshTask.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import com.github.games647.scoreboardstats.config.Settings; 4 | import com.google.common.collect.Maps; 5 | 6 | import java.util.Map; 7 | 8 | import org.apache.commons.lang.mutable.MutableInt; 9 | import org.bukkit.entity.Player; 10 | 11 | /** 12 | * Handling all updates for a player in a performance optimized variant. This 13 | * class split the updates over the ticks much smoother. 14 | * 15 | * @see SbManager 16 | * @see com.github.games647.scoreboardstats.variables.ReplaceManager 17 | */ 18 | public class RefreshTask implements Runnable { 19 | 20 | private final ScoreboardStats plugin; 21 | 22 | //Prevent duplicate entries and is faster than the delay queue 23 | private final Map queue = Maps.newHashMapWithExpectedSize(100); 24 | 25 | private int nextGlobalUpdate = 20 * Settings.getInterval(); 26 | 27 | /** 28 | * Initialize refresh task 29 | * 30 | * @param instance ScoreboardStats instance 31 | */ 32 | public RefreshTask(ScoreboardStats instance) { 33 | this.plugin = instance; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | //let the players update smoother 39 | int remainingUpdates = getNextUpdates(); 40 | for (Map.Entry entry : queue.entrySet()) { 41 | Player player = entry.getKey(); 42 | MutableInt remainingTicks = entry.getValue(); 43 | if (remainingTicks.intValue() == 0) { 44 | if (remainingUpdates != 0) { 45 | //Smoother refreshing; limit the updates 46 | plugin.getScoreboardManager().onUpdate(player); 47 | remainingTicks.setValue(20 * Settings.getInterval()); 48 | remainingUpdates--; 49 | } 50 | } else { 51 | remainingTicks.decrement(); 52 | } 53 | } 54 | 55 | nextGlobalUpdate--; 56 | if (nextGlobalUpdate == 0) { 57 | nextGlobalUpdate = 20 * Settings.getInterval(); 58 | //update globals 59 | plugin.getReplaceManager().updateGlobals(); 60 | } 61 | } 62 | 63 | /** 64 | * Add a player to the queue for updating him. 65 | * 66 | * @param request the player that should be added. 67 | */ 68 | public void addToQueue(Player request) { 69 | if (queue.containsKey(request)) { 70 | //check if it isn't already in the queue 71 | queue.put(request, new MutableInt(20 * Settings.getInterval())); 72 | } 73 | } 74 | 75 | /** 76 | * Checks if the player is in the refresh queue. 77 | * 78 | * @param request player instance 79 | * @return true if the player is in the refresh queue. 80 | */ 81 | public boolean contains(Player request) { 82 | return queue.containsKey(request); 83 | } 84 | 85 | /** 86 | * Explicit removes the player from the refresh queue. 87 | * 88 | * @param request player who should be removed 89 | * @return if the last entry exists 90 | */ 91 | public boolean remove(Player request) { 92 | return queue.remove(request) != null; 93 | } 94 | 95 | /** 96 | * Clears the complete queue. 97 | */ 98 | public void clear() { 99 | queue.clear(); 100 | } 101 | 102 | private int getNextUpdates() { 103 | int nextUpdates = queue.size() / 20; 104 | if (nextUpdates <= 0) { 105 | //just update minimum one player per tick. Otherwise servers with not much players 106 | //won't receive any updates 107 | return 1; 108 | } 109 | 110 | return nextUpdates; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/SbManager.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import com.github.games647.scoreboardstats.config.Settings; 4 | import com.github.games647.scoreboardstats.config.VariableItem; 5 | 6 | import java.util.UUID; 7 | 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | 11 | /** 12 | * Manage the scoreboard access. 13 | */ 14 | public abstract class SbManager implements BoardManager { 15 | 16 | protected static final String UNKNOWN_VARIABLE = "Cannot find variable with name: ({}) Maybe you misspelled it " + 17 | "or the replacer isn't available yet"; 18 | 19 | protected static final String SB_NAME = "Stats"; 20 | protected static final String TEMP_SB_NAME = SB_NAME + 'T'; 21 | 22 | private static final int MAX_ITEM_LENGTH = 16; 23 | 24 | protected final ScoreboardStats plugin; 25 | 26 | private final String permission; 27 | 28 | protected SbManager(ScoreboardStats plugin) { 29 | this.plugin = plugin; 30 | this.permission = plugin.getName().toLowerCase() + ".use"; 31 | } 32 | 33 | @Override 34 | public void registerAll() { 35 | boolean isPvpStats = Settings.isPvpStats(); 36 | //maybe batch this 37 | Bukkit.getOnlinePlayers().stream().filter(Player::isOnline).forEach(player -> { 38 | if (isPvpStats) { 39 | //maybe batch this 40 | player.removeMetadata("player_stats", plugin); 41 | plugin.getStatsDatabase().loadAccountAsync(player); 42 | } 43 | 44 | plugin.getRefreshTask().addToQueue(player); 45 | }); 46 | } 47 | 48 | @Override 49 | public void unregisterAll() { 50 | Bukkit.getOnlinePlayers().forEach(this::unregister); 51 | } 52 | 53 | @Override 54 | public void updateVariable(Player player, String variable, int newScore) { 55 | VariableItem variableItem = Settings.getMainScoreboard().getItemsByVariable().get(variable); 56 | if (variableItem != null) { 57 | update(player, variableItem.getDisplayText(), newScore); 58 | } 59 | } 60 | 61 | @Override 62 | public void updateVariable(Player player, String variable, String newScore) { 63 | VariableItem variableItem = Settings.getMainScoreboard().getItemsByName().get(variable); 64 | if (variableItem != null) { 65 | // update(player, variableItem.getDisplayText(), newScore); 66 | } 67 | } 68 | 69 | protected abstract void update(Player player, String title, int newScore); 70 | 71 | protected void scheduleShowTask(Player player, boolean action) { 72 | if (!Settings.isTempScoreboard()) { 73 | return; 74 | } 75 | 76 | int interval = Settings.getTempDisappear(); 77 | if (action) { 78 | interval = Settings.getTempAppear(); 79 | } 80 | 81 | UUID uuid = player.getUniqueId(); 82 | Bukkit.getScheduler().runTaskLater(plugin, () -> { 83 | Player localPlayer = Bukkit.getPlayer(uuid); 84 | if (localPlayer == null) { 85 | return; 86 | } 87 | 88 | if (action) { 89 | createTopListScoreboard(player); 90 | } else { 91 | createScoreboard(player); 92 | } 93 | }, interval * 20L); 94 | } 95 | 96 | protected String stripLength(String check) { 97 | if (check.length() > MAX_ITEM_LENGTH) { 98 | return check.substring(0, MAX_ITEM_LENGTH); 99 | } 100 | 101 | return check; 102 | } 103 | 104 | protected boolean isAllowed(Player player) { 105 | return player.hasPermission(permission) && Settings.isActiveWorld(player.getWorld().getName()); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/ScoreboardStats.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import com.github.games647.scoreboardstats.commands.SidebarCommands; 4 | import com.github.games647.scoreboardstats.config.Settings; 5 | import com.github.games647.scoreboardstats.defaults.TicksPerSecondTask; 6 | import com.github.games647.scoreboardstats.pvp.Database; 7 | import com.github.games647.scoreboardstats.scoreboard.PacketManager; 8 | import com.github.games647.scoreboardstats.variables.ReplaceManager; 9 | 10 | import java.lang.reflect.Constructor; 11 | import java.util.logging.Level; 12 | 13 | import org.bukkit.plugin.java.JavaPlugin; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.slf4j.impl.JDK14LoggerAdapter; 17 | 18 | /** 19 | * Represents the main class of this plugin. 20 | */ 21 | public class ScoreboardStats extends JavaPlugin { 22 | 23 | private final Logger logger = createLoggerFromJDK(getLogger()); 24 | 25 | //don't create instances here that accesses the bukkit API - it will be incompatible with older mc versions 26 | private RefreshTask refreshTask; 27 | private Settings settings; 28 | private SbManager scoreboardManager; 29 | private Database database; 30 | 31 | private ReplaceManager replaceManager; 32 | 33 | private static Logger createLoggerFromJDK(java.util.logging.Logger parent) { 34 | try { 35 | parent.setLevel(Level.ALL); 36 | 37 | Class adapterClass = JDK14LoggerAdapter.class; 38 | Constructor cons = adapterClass.getDeclaredConstructor(java.util.logging.Logger.class); 39 | cons.setAccessible(true); 40 | return cons.newInstance(parent); 41 | } catch (ReflectiveOperationException reflectEx) { 42 | parent.log(Level.WARNING, "Cannot create slf4j logging adapter", reflectEx); 43 | parent.log(Level.WARNING, "Creating logger instance manually..."); 44 | return LoggerFactory.getLogger(parent.getName()); 45 | } 46 | } 47 | 48 | /** 49 | * Get the scoreboard manager. 50 | * 51 | * @return the manager 52 | */ 53 | public BoardManager getScoreboardManager() { 54 | return scoreboardManager; 55 | } 56 | 57 | /** 58 | * Get the replace manager. 59 | * 60 | * @return the manager 61 | */ 62 | public ReplaceManager getReplaceManager() { 63 | return replaceManager; 64 | } 65 | 66 | /** 67 | * Get the refresh task for updating the scoreboard 68 | * 69 | * @return the refresh task instance 70 | */ 71 | public RefreshTask getRefreshTask() { 72 | return refreshTask; 73 | } 74 | 75 | /** 76 | * The database manager for pvp stats 77 | * 78 | * @return pvp stats database manager 79 | */ 80 | public Database getStatsDatabase() { 81 | return database; 82 | } 83 | 84 | public Logger getLog() { 85 | return logger; 86 | } 87 | 88 | /** 89 | * Enable the plugin 90 | */ 91 | @Override 92 | public void onEnable() { 93 | //Load the config + needs to be initialised to get the configured value for update-checking 94 | settings = new Settings(this, logger); 95 | settings.loadConfig(); 96 | 97 | refreshTask = new RefreshTask(this); 98 | 99 | //Register all events 100 | getServer().getPluginManager().registerEvents(new PlayerListener(this), this); 101 | 102 | //register all commands based on the root command of this plugin 103 | getCommand(getName().toLowerCase()).setExecutor(new SidebarCommands(this)); 104 | 105 | //start tracking the ticks 106 | getServer().getScheduler().runTaskTimer(this, new TicksPerSecondTask(), 5 * 20L, 3 * 20L); 107 | //Start the refresh task; it should run on every tick, because it's smoothly update the variables with limit 108 | getServer().getScheduler().runTaskTimer(this, refreshTask, 5 * 20L, 1L); 109 | 110 | scoreboardManager = new PacketManager(this); 111 | replaceManager = new ReplaceManager(scoreboardManager, this, logger); 112 | 113 | if (Settings.isPvpStats()) { 114 | database = new Database(this, logger); 115 | database.setupDatabase(); 116 | } 117 | 118 | //creates scoreboards for every player that is online 119 | scoreboardManager.registerAll(); 120 | } 121 | 122 | /** 123 | * Disable the plugin 124 | */ 125 | @Override 126 | public void onDisable() { 127 | if (scoreboardManager != null) { 128 | //Clear all scoreboards 129 | scoreboardManager.unregisterAll(); 130 | } 131 | 132 | if (replaceManager != null) { 133 | replaceManager.close(); 134 | } 135 | 136 | if (database != null) { 137 | //flush the cache to the database 138 | database.saveAll(); 139 | } 140 | } 141 | 142 | /** 143 | * Reload the plugin 144 | */ 145 | public void onReload() { 146 | if (settings != null) { 147 | settings.loadConfig(); 148 | } 149 | 150 | if (refreshTask != null) { 151 | refreshTask.clear(); 152 | } 153 | 154 | if (scoreboardManager != null) { 155 | scoreboardManager.unregisterAll(); 156 | } 157 | 158 | scoreboardManager = new PacketManager(this); 159 | if (database == null) { 160 | database = new Database(this, logger); 161 | database.setupDatabase(); 162 | } else { 163 | database.setupDatabase(); 164 | } 165 | 166 | scoreboardManager.registerAll(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/commands/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.commands; 2 | 3 | import com.github.games647.scoreboardstats.ScoreboardStats; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import org.bukkit.ChatColor; 9 | import org.bukkit.command.CommandSender; 10 | 11 | /** 12 | * Represents a handler for sub commands 13 | */ 14 | abstract class CommandHandler { 15 | 16 | protected final ScoreboardStats plugin; 17 | 18 | private final String permission; 19 | private final String description; 20 | private final String subCommand; 21 | 22 | private final List aliases; 23 | 24 | CommandHandler(String command, ScoreboardStats plugin, String... aliases) { 25 | this(command, "&cNo description", plugin, aliases); 26 | } 27 | 28 | CommandHandler(String command, String description, ScoreboardStats plugin, String... aliases) { 29 | this.plugin = plugin; 30 | this.description = ChatColor.translateAlternateColorCodes('&', description); 31 | this.permission = plugin.getName().toLowerCase() + ".command." + command; 32 | this.subCommand = command; 33 | 34 | this.aliases = Arrays.asList(aliases); 35 | } 36 | 37 | /** 38 | * Get the main sub command 39 | * 40 | * @return the main sub command 41 | */ 42 | public String getSubCommand() { 43 | return subCommand; 44 | } 45 | 46 | /** 47 | * Get all aliases of this command. All strings in this list can invoke this 48 | * command 49 | * 50 | * @return all aliases 51 | */ 52 | public Iterable getAliases() { 53 | return aliases; 54 | } 55 | 56 | /** 57 | * Get the description of this command 58 | * 59 | * @return the description 60 | */ 61 | public String getDescription() { 62 | return description; 63 | } 64 | 65 | /** 66 | * Get the permission of this command 67 | * 68 | * @return the permission 69 | */ 70 | public String getPermission() { 71 | return permission; 72 | } 73 | 74 | /** 75 | * Check if the player has enough permissions to execute this command. It 76 | * will send a no permission if he doesn't have it. 77 | * 78 | * @param sender the executor 79 | * @return whether the sender has enough permissions 80 | */ 81 | public boolean hasPermission(CommandSender sender) { 82 | if (sender.hasPermission(permission)) { 83 | return true; 84 | } 85 | 86 | sender.sendMessage("§4 You don't have enough permissions to do that"); 87 | return false; 88 | } 89 | 90 | /** 91 | * Executes the given subcommand 92 | * 93 | * @param sender Source of the command 94 | * @param subCommand Command which was executed 95 | * @param args The arguments passed to the command, including final partial argument to be completed 96 | */ 97 | public abstract void onCommand(CommandSender sender, String subCommand, String... args); 98 | 99 | /** 100 | * Requests a list of possible completions for a command argument. 101 | * 102 | * @param sender Source of the command 103 | * @param subCommand Command which was executed 104 | * @param args The arguments passed to the command, including final partial argument to be completed 105 | * @return A List of possible completions for the final argument, or null to default to the command executor 106 | */ 107 | public List onTabComplete(CommandSender sender, String subCommand, String... args) { 108 | //default null -> bukkit will handle this with a list of only players 109 | return null; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/commands/SidebarCommands.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.commands; 2 | 3 | import com.github.games647.scoreboardstats.ScoreboardStats; 4 | import com.google.common.collect.ImmutableList; 5 | import com.google.common.collect.Lists; 6 | import com.google.common.collect.Maps; 7 | 8 | import java.util.Arrays; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | import org.bukkit.ChatColor; 14 | import org.bukkit.command.Command; 15 | import org.bukkit.command.CommandSender; 16 | import org.bukkit.command.TabExecutor; 17 | import org.bukkit.util.StringUtil; 18 | 19 | /** 20 | * This class forward all commands to the user commands for a better access 21 | */ 22 | public class SidebarCommands implements TabExecutor { 23 | 24 | private final ScoreboardStats plugin; 25 | 26 | private final Map commands = Maps.newHashMap(); 27 | private final List subCommands = Lists.newArrayList(); 28 | 29 | public SidebarCommands(ScoreboardStats plugin) { 30 | this.plugin = plugin; 31 | 32 | registerSubCommands(); 33 | } 34 | 35 | @Override 36 | public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { 37 | //default subcommand 38 | String subCommand = "toggle"; 39 | String[] newArgs = args; 40 | if (args.length != 0) { 41 | subCommand = args[0]; 42 | //remove the subcommand from the args list 43 | newArgs = Arrays.copyOfRange(args, 1, args.length); 44 | } 45 | 46 | CommandHandler commandHandler = commands.get(subCommand); 47 | if (commandHandler == null) { 48 | sender.sendMessage(ChatColor.DARK_RED + "Command not found"); 49 | } else { 50 | if (commandHandler.hasPermission(sender)) { 51 | commandHandler.onCommand(sender, subCommand, newArgs); 52 | } 53 | } 54 | 55 | return true; 56 | } 57 | 58 | @Override 59 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 60 | if (args.length == 0) { 61 | return ImmutableList.of(); 62 | } 63 | 64 | String lastWord = args[args.length - 1]; 65 | List suggestion; 66 | if (args.length == 1) { 67 | return subCommands.stream() 68 | .filter(subCommand -> StringUtil.startsWithIgnoreCase(subCommand, lastWord)) 69 | .sorted(String.CASE_INSENSITIVE_ORDER) 70 | .collect(Collectors.toList()); 71 | } 72 | 73 | String subCommand = args[0]; 74 | CommandHandler commandHandler = commands.get(subCommand); 75 | if (commandHandler != null) { 76 | //remove the subcommand from the args list 77 | suggestion = commandHandler.onTabComplete(sender, subCommand, Arrays.copyOfRange(args, 1, args.length)); 78 | if (suggestion != null) { 79 | //Prevent NPEs and the usage of this method in nearly every handler 80 | suggestion.sort(String.CASE_INSENSITIVE_ORDER); 81 | } 82 | 83 | return suggestion; 84 | } 85 | 86 | return null; 87 | } 88 | 89 | private void registerSubCommands() { 90 | register(new ToggleCommand(plugin)); 91 | } 92 | 93 | private void register(CommandHandler handler) { 94 | for (String alias : handler.getAliases()) { 95 | commands.put(alias, handler); 96 | subCommands.add(alias); 97 | } 98 | 99 | String subCommand = handler.getSubCommand(); 100 | commands.put(subCommand, handler); 101 | subCommands.add(subCommand); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/commands/ToggleCommand.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.commands; 2 | 3 | import com.github.games647.scoreboardstats.RefreshTask; 4 | import com.github.games647.scoreboardstats.ScoreboardStats; 5 | 6 | import org.bukkit.command.CommandSender; 7 | import org.bukkit.entity.Player; 8 | import org.bukkit.scoreboard.DisplaySlot; 9 | 10 | /** 11 | * Change the visibility of the scoreboard 12 | */ 13 | public class ToggleCommand extends CommandHandler { 14 | 15 | private static final String TOGGLE_MSG = "§2Toggling the scoreboard"; 16 | 17 | public ToggleCommand(ScoreboardStats plugin) { 18 | super("toggle", "&aToggles the sidebar", plugin, "hide", "show"); 19 | } 20 | 21 | @Override 22 | public void onCommand(CommandSender sender, String subCommand, String... args) { 23 | if (!(sender instanceof Player)) { 24 | //the console cannot have a scoreboard 25 | sender.sendMessage("§4This command can only be executed by Players"); 26 | return; 27 | } 28 | 29 | //We checked that it can only be players 30 | Player player = (Player) sender; 31 | RefreshTask refreshTask = plugin.getRefreshTask(); 32 | if (refreshTask.contains(player)) { 33 | if ("hide".equals(subCommand) || "toggle".equals(subCommand)) { 34 | refreshTask.remove(player); 35 | player.getScoreboard().clearSlot(DisplaySlot.SIDEBAR); 36 | player.sendMessage(TOGGLE_MSG); 37 | } 38 | } else if ("show".equals(subCommand) || "toggle".equals(subCommand)) { 39 | player.sendMessage(TOGGLE_MSG); 40 | refreshTask.addToQueue(player); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/Objective.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import com.comphenix.protocol.events.PacketContainer; 4 | import com.comphenix.protocol.wrappers.EnumWrappers.ScoreboardAction; 5 | import com.google.common.collect.Lists; 6 | import com.google.common.collect.Maps; 7 | 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | import java.util.OptionalInt; 12 | 13 | import org.apache.commons.lang.builder.ToStringBuilder; 14 | 15 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_DISPLAY_OBJECTIVE; 16 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_OBJECTIVE; 17 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_SCORE; 18 | 19 | /** 20 | * Represents a sidebar objective 21 | */ 22 | public class Objective { 23 | 24 | private static final int MAX_ITEM_SIZE = 15; 25 | private static final int SIDEBAR_SLOT = 1; 26 | 27 | //A scoreboard can only hold < 16 scores 28 | final Map scores = Maps.newHashMapWithExpectedSize(MAX_ITEM_SIZE); 29 | 30 | private final PlayerScoreboard scoreboard; 31 | private final String objectiveId; 32 | String displayName; 33 | 34 | Objective(PlayerScoreboard scoreboard, String objectiveId, String displayName) { 35 | this.scoreboard = scoreboard; 36 | 37 | this.objectiveId = objectiveId; 38 | this.displayName = displayName; 39 | } 40 | 41 | public boolean isShown() { 42 | //Prevents NPE with this ordering 43 | return scoreboard.getSidebarObjective().map(sidebar -> sidebar.equals(this)).orElse(false); 44 | } 45 | 46 | public boolean exists() { 47 | return scoreboard.getObjective(objectiveId).isPresent(); 48 | } 49 | 50 | public String getId() { 51 | return objectiveId; 52 | } 53 | 54 | public String getDisplayName() { 55 | return displayName; 56 | } 57 | 58 | public void setDisplayName(String displayName) { 59 | if (this.displayName.equals(displayName)) { 60 | return; 61 | } 62 | 63 | this.displayName = displayName; 64 | sendObjectivePacket(State.UPDATE_DISPLAY_NAME); 65 | } 66 | 67 | public OptionalInt getScore(String name) { 68 | Integer score = scores.get(name); 69 | if (score == null) { 70 | return OptionalInt.empty(); 71 | } 72 | 73 | return OptionalInt.of(score); 74 | } 75 | 76 | public int getScore(String name, int def) { 77 | Integer score = scores.get(name); 78 | if (score == null) { 79 | setScores(name, def); 80 | return def; 81 | } 82 | 83 | return score; 84 | } 85 | 86 | public boolean hasScore(String name) { 87 | return scores.containsKey(name); 88 | } 89 | 90 | public void setScores(String name, int value) { 91 | Integer oldVal = scores.put(name, value); 92 | if (oldVal != null && oldVal == value) { 93 | return; 94 | } 95 | 96 | sendScorePacket(name, value, ScoreboardAction.CHANGE); 97 | } 98 | 99 | public List> getScores() { 100 | List> values = Lists.newArrayListWithExpectedSize(scores.size()); 101 | values.addAll(scores.entrySet()); 102 | values.sort((score1, score2) -> Integer.compare(score2.getValue(), score1.getValue())); 103 | return values; 104 | } 105 | 106 | public void removeScore(String name) { 107 | scores.remove(name); 108 | sendScorePacket(name, 0, ScoreboardAction.REMOVE); 109 | } 110 | 111 | public void clear() { 112 | scores.keySet().forEach(this::removeScore); 113 | } 114 | 115 | public PlayerScoreboard getScoreboard() { 116 | return scoreboard; 117 | } 118 | 119 | @Override 120 | public int hashCode() { 121 | return Objects.hash(objectiveId); 122 | } 123 | 124 | @Override 125 | public boolean equals(Object obj) { 126 | //ignores also null 127 | return obj instanceof Objective && Objects.equals(objectiveId, ((Objective) obj).objectiveId); 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return ToStringBuilder.reflectionToString(this); 133 | } 134 | 135 | private void sendScorePacket(String name, int score, ScoreboardAction action) { 136 | PacketContainer packet = new PacketContainer(SCOREBOARD_SCORE); 137 | packet.getStrings().write(0, name); 138 | packet.getStrings().write(1, objectiveId); 139 | 140 | packet.getIntegers().write(0, score); 141 | 142 | packet.getScoreboardActions().write(0, action); 143 | 144 | scoreboard.sendPacket(packet); 145 | } 146 | 147 | void sendObjectivePacket(State state) { 148 | PacketContainer packet = new PacketContainer(SCOREBOARD_OBJECTIVE); 149 | packet.getStrings().write(0, objectiveId); 150 | 151 | if (state != State.REMOVE) { 152 | packet.getStrings().write(1, displayName); 153 | packet.getStrings().write(2, "integer"); 154 | } 155 | 156 | scoreboard.sendPacket(packet); 157 | } 158 | 159 | void sendShowPacket() { 160 | PacketContainer packet = new PacketContainer(SCOREBOARD_DISPLAY_OBJECTIVE); 161 | packet.getStrings().write(0, objectiveId); 162 | 163 | packet.getIntegers().write(0, SIDEBAR_SLOT); 164 | scoreboard.sendPacket(packet); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/PacketListener.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import com.comphenix.net.sf.cglib.proxy.Factory; 4 | import com.comphenix.protocol.PacketType; 5 | import com.comphenix.protocol.events.PacketAdapter; 6 | import com.comphenix.protocol.events.PacketContainer; 7 | import com.comphenix.protocol.events.PacketEvent; 8 | import com.comphenix.protocol.wrappers.EnumWrappers.ScoreboardAction; 9 | 10 | import java.util.Collection; 11 | import java.util.Optional; 12 | import java.util.UUID; 13 | 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.plugin.Plugin; 17 | import org.bukkit.scoreboard.DisplaySlot; 18 | 19 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_DISPLAY_OBJECTIVE; 20 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_OBJECTIVE; 21 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_SCORE; 22 | import static com.comphenix.protocol.PacketType.Play.Server.SCOREBOARD_TEAM; 23 | 24 | /** 25 | * Listening all outgoing packets and check + handle for possibly client crash cases. This Listener should only read and 26 | * listen to relevant packets. 27 | *

28 | * Protocol specifications can be found here http://wiki.vg/Protocol 29 | */ 30 | class PacketListener extends PacketAdapter { 31 | 32 | private final PacketManager manager; 33 | 34 | PacketListener(Plugin plugin, PacketManager manager) { 35 | super(plugin, SCOREBOARD_DISPLAY_OBJECTIVE, SCOREBOARD_OBJECTIVE, SCOREBOARD_SCORE, SCOREBOARD_TEAM); 36 | 37 | this.manager = manager; 38 | } 39 | 40 | @Override 41 | public void onPacketSending(PacketEvent packetEvent) { 42 | Player player = packetEvent.getPlayer(); 43 | if (packetEvent.isCancelled() || player instanceof Factory) { 44 | return; 45 | } 46 | 47 | PacketContainer packet = packetEvent.getPacket(); 48 | if (packet.hasMetadata("ScoreboardStats")) { 49 | //it's our own packet 50 | return; 51 | } 52 | 53 | UUID playerUUID = player.getUniqueId(); 54 | 55 | //handle async packets by other plugins 56 | if (Bukkit.isPrimaryThread()) { 57 | ensureMainThread(playerUUID, packet); 58 | } else { 59 | PacketContainer clone = packet.deepClone(); 60 | Bukkit.getScheduler().runTask(plugin, () -> ensureMainThread(playerUUID, clone)); 61 | } 62 | } 63 | 64 | private void ensureMainThread(UUID uuid, PacketContainer packet) { 65 | Player player = Bukkit.getPlayer(uuid); 66 | if (player == null) { 67 | return; 68 | } 69 | 70 | PacketType packetType = packet.getType(); 71 | if (packetType.equals(SCOREBOARD_SCORE)) { 72 | handleScorePacket(player, packet); 73 | } else if (packetType.equals(SCOREBOARD_OBJECTIVE)) { 74 | handleObjectivePacket(player, packet); 75 | } else if (packetType.equals(SCOREBOARD_DISPLAY_OBJECTIVE)) { 76 | handleDisplayPacket(player, packet); 77 | } else if (packetType.equals(SCOREBOARD_TEAM)) { 78 | handleTeamPacket(player, packet); 79 | } 80 | } 81 | 82 | private void handleScorePacket(Player player, PacketContainer packet) { 83 | String scoreName = packet.getStrings().read(0); 84 | String parent = packet.getStrings().read(1); 85 | int score = packet.getIntegers().read(0); 86 | 87 | //state id 88 | ScoreboardAction action = packet.getScoreboardActions().read(0); 89 | 90 | //Packet receiving validation 91 | if (parent.length() > 16) { 92 | //Invalid packet 93 | return; 94 | } 95 | 96 | PlayerScoreboard scoreboard = manager.getScoreboard(player); 97 | //scores actually only have two state id, because these 98 | if (action == ScoreboardAction.CHANGE) { 99 | scoreboard.getObjective(parent).ifPresent(objective -> objective.scores.put(scoreName, score)); 100 | } else if (action == ScoreboardAction.REMOVE) { 101 | scoreboard.getObjective(parent).ifPresent(objective -> objective.scores.remove(scoreName, score)); 102 | } 103 | } 104 | 105 | private void handleObjectivePacket(Player player, PacketContainer packet) { 106 | String objectiveId = packet.getStrings().read(0); 107 | //Can be empty 108 | String displayName = packet.getStrings().read(1); 109 | State action = State.fromId(packet.getIntegers().read(0)); 110 | 111 | //Packet receiving validation 112 | if (objectiveId.length() > 16 || displayName.length() > 32) { 113 | //Invalid packet 114 | return; 115 | } 116 | 117 | PlayerScoreboard scoreboard = manager.getScoreboard(player); 118 | if (action == State.CREATE) { 119 | Objective objective = new Objective(scoreboard, objectiveId, displayName); 120 | scoreboard.objectivesByName.put(objectiveId, objective); 121 | } else { 122 | if (action == State.REMOVE) { 123 | scoreboard.objectivesByName.remove(objectiveId); 124 | } else { 125 | scoreboard.getObjective(objectiveId).ifPresent(obj -> obj.displayName = displayName); 126 | } 127 | } 128 | } 129 | 130 | private void handleDisplayPacket(Player player, PacketContainer packet) { 131 | //Can be empty; if so it would just clear the slot 132 | String objectiveId = packet.getStrings().read(0); 133 | Optional slot = SlotTransformer.fromId(packet.getIntegers().read(0)); 134 | 135 | //Packet receiving validation 136 | if (!slot.isPresent() || objectiveId.length() > 16) { 137 | return; 138 | } 139 | 140 | PlayerScoreboard scoreboard = manager.getScoreboard(player); 141 | if (slot.get() == DisplaySlot.SIDEBAR) { 142 | scoreboard.getObjective(objectiveId).ifPresent(obj -> scoreboard.sidebarObjective = obj); 143 | } else { 144 | scoreboard.getSidebarObjective().filter(objective -> objective.getId().equals(objectiveId)) 145 | .ifPresent(objective -> scoreboard.sidebarObjective = null); 146 | } 147 | } 148 | 149 | private void handleTeamPacket(Player player, PacketContainer packet) { 150 | String teamId = packet.getStrings().read(0); 151 | Optional optMode = TeamMode.getMode(packet.getIntegers().read(0)); 152 | 153 | if (!optMode.isPresent() || teamId.length() > 16) { 154 | return; 155 | } 156 | 157 | TeamMode mode = optMode.get(); 158 | 159 | PlayerScoreboard scoreboard = manager.getScoreboard(player); 160 | if (mode == TeamMode.CREATE) { 161 | Collection members = packet.getSpecificModifier(Collection.class).read(0); 162 | scoreboard.teamsByName.put(teamId, new Team(scoreboard, teamId, members)); 163 | } else if (mode == TeamMode.REMOVE) { 164 | scoreboard.teamsByName.remove(teamId); 165 | } else if (mode == TeamMode.ADD_MEMBER) { 166 | Collection members = packet.getSpecificModifier(Collection.class).read(0); 167 | scoreboard.getTeam(teamId).ifPresent(team -> team.members.addAll(members)); 168 | } else if (mode == TeamMode.REMOVE_MEMBER) { 169 | Collection members = packet.getSpecificModifier(Collection.class).read(0); 170 | scoreboard.getTeam(teamId).ifPresent(team -> team.members.removeAll(members)); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/PacketManager.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import com.comphenix.protocol.ProtocolLibrary; 4 | import com.github.games647.scoreboardstats.SbManager; 5 | import com.github.games647.scoreboardstats.ScoreboardStats; 6 | import com.github.games647.scoreboardstats.config.Settings; 7 | import com.github.games647.scoreboardstats.config.VariableItem; 8 | import com.github.games647.scoreboardstats.variables.ReplaceManager; 9 | import com.github.games647.scoreboardstats.variables.ReplacerException; 10 | import com.github.games647.scoreboardstats.variables.UnknownVariableException; 11 | import com.google.common.collect.Maps; 12 | 13 | import java.util.Iterator; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.OptionalInt; 17 | import java.util.UUID; 18 | 19 | import org.bukkit.entity.Player; 20 | 21 | /** 22 | * Manage the scoreboards with packet-use 23 | */ 24 | public class PacketManager extends SbManager { 25 | 26 | private final Map scoreboards = Maps.newHashMapWithExpectedSize(50); 27 | 28 | /** 29 | * Creates a new scoreboard manager for the packet system. 30 | * 31 | * @param plugin ScoreboardStats instance 32 | */ 33 | public PacketManager(ScoreboardStats plugin) { 34 | super(plugin); 35 | 36 | ProtocolLibrary.getProtocolManager().addPacketListener(new PacketListener(plugin, this)); 37 | } 38 | 39 | public PlayerScoreboard getScoreboard(Player player) { 40 | return scoreboards 41 | .computeIfAbsent(player.getUniqueId(), key -> new PlayerScoreboard(plugin, player)); 42 | } 43 | 44 | @Override 45 | public void onUpdate(Player player) { 46 | if (getScoreboard(player).getSidebarObjective().isPresent()) { 47 | sendUpdate(player); 48 | } else { 49 | createScoreboard(player); 50 | } 51 | } 52 | 53 | @Override 54 | public void unregisterAll() { 55 | super.unregisterAll(); 56 | 57 | scoreboards.clear(); 58 | } 59 | 60 | @Override 61 | public void unregister(Player player) { 62 | PlayerScoreboard scoreboard = scoreboards.remove(player.getUniqueId()); 63 | if (scoreboard != null) { 64 | scoreboard.getObjectives().stream() 65 | .filter(obj -> obj.getId().startsWith(SB_NAME)) 66 | .map(Objective::getId) 67 | .forEach(scoreboard::removeObjective); 68 | } 69 | } 70 | 71 | @Override 72 | public void createScoreboard(Player player) { 73 | PlayerScoreboard scoreboard = getScoreboard(player); 74 | Optional oldObjective = scoreboard.getSidebarObjective(); 75 | if (!isAllowed(player) || oldObjective.map(Objective::getId).map(TEMP_SB_NAME::equals).orElse(false)) { 76 | //Check if another scoreboard is showing 77 | return; 78 | } 79 | 80 | Objective objective = scoreboard.getOrCreateObjective(SB_NAME); 81 | objective.setDisplayName(Settings.getTempTitle()); 82 | 83 | updateVariables(objective, player, true); 84 | 85 | //Schedule the next temp scoreboard show 86 | scheduleShowTask(player, true); 87 | } 88 | 89 | @Override 90 | public void createTopListScoreboard(Player player) { 91 | PlayerScoreboard scoreboard = getScoreboard(player); 92 | Optional oldObjective = scoreboard.getSidebarObjective(); 93 | if (!isAllowed(player) || oldObjective.map(Objective::getId).map(SB_NAME::equals).orElse(false)) { 94 | //Check if another scoreboard is showing 95 | return; 96 | } 97 | 98 | //Unregister objective instead of sending 15 remove score packets 99 | scoreboard.getObjective(TEMP_SB_NAME).map(Objective::getId).ifPresent(scoreboard::removeObjective); 100 | 101 | //We are checking if another object is shown. If it's our scoreboard the code will continue to this 102 | //were the force the replacement, because the scoreboard management in minecraft right now is sync, 103 | //so we don't expect any crashes by other plugins. 104 | Objective objective = scoreboard.getOrCreateObjective(TEMP_SB_NAME); 105 | objective.setDisplayName(Settings.getTempTitle()); 106 | 107 | //Colorize and send all elements 108 | for (Map.Entry entry : plugin.getStatsDatabase().getTop()) { 109 | String scoreName = stripLength(Settings.getTempColor() + entry.getKey()); 110 | objective.setScores(scoreName, entry.getValue()); 111 | } 112 | 113 | //schedule the next normal scoreboard show 114 | scheduleShowTask(player, false); 115 | } 116 | 117 | @Override 118 | public void update(Player player, String title, int newScore) { 119 | getScoreboard(player).getObjective(SB_NAME).ifPresent(objective -> objective.setScores(title, newScore)); 120 | } 121 | 122 | @Override 123 | public void sendUpdate(Player player) { 124 | Optional sidebar = getScoreboard(player).getSidebarObjective(); 125 | if (sidebar.filter(objective -> SB_NAME.equals(objective.getId())).isPresent()) { 126 | updateVariables(sidebar.get(), player, false); 127 | } 128 | } 129 | 130 | private void updateVariables(Objective objective, Player player, boolean complete) { 131 | Iterator iter = Settings.getMainScoreboard().getItemsByVariable().values().iterator(); 132 | while (iter.hasNext()) { 133 | VariableItem variableItem = iter.next(); 134 | 135 | String variable = variableItem.getVariable(); 136 | String displayText = variableItem.getDisplayText(); 137 | int score = variableItem.getScore(); 138 | 139 | try { 140 | ReplaceManager replaceManager = plugin.getReplaceManager(); 141 | OptionalInt newScore = replaceManager.scoreReplace(player, variable, false); 142 | if (newScore.isPresent()) { 143 | objective.setScores(displayText, newScore.getAsInt()); 144 | } 145 | } catch (UnknownVariableException ex) { 146 | //Remove the variable because we can't replace it 147 | iter.remove(); 148 | Settings.getMainScoreboard().getItemsByName().remove(variableItem.getDisplayText()); 149 | 150 | plugin.getLog().info(UNKNOWN_VARIABLE, variableItem); 151 | } catch (ReplacerException e) { 152 | iter.remove(); 153 | plugin.getLog().error("Error on replace", e); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/PlayerScoreboard.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import com.comphenix.protocol.ProtocolLibrary; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.github.games647.scoreboardstats.ScoreboardStats; 6 | import com.google.common.collect.Maps; 7 | 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.util.Collection; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | 13 | import org.bukkit.entity.Player; 14 | 15 | /** 16 | * Represents the scoreboard overview in a server-side perspective. 17 | */ 18 | public class PlayerScoreboard { 19 | 20 | private final ScoreboardStats plugin; 21 | private final Player player; 22 | 23 | final Map objectivesByName = Maps.newHashMap(); 24 | final Map teamsByName = Maps.newHashMap(); 25 | 26 | Objective sidebarObjective; 27 | 28 | public PlayerScoreboard(ScoreboardStats plugin, Player player) { 29 | this.plugin = plugin; 30 | this.player = player; 31 | } 32 | 33 | public Objective getOrCreateObjective(String objectiveId) { 34 | return objectivesByName.computeIfAbsent(objectiveId, this::addObjective); 35 | } 36 | 37 | public Optional getObjective(String objectiveId) { 38 | return Optional.ofNullable(objectivesByName.get(objectiveId)); 39 | } 40 | 41 | public Objective addObjective(String objectiveId) { 42 | return addObjective(objectiveId, objectiveId); 43 | } 44 | 45 | public Objective addObjective(String objectiveId, String display) { 46 | Objective objective = new Objective(this, objectiveId, display); 47 | sidebarObjective = objective; 48 | objectivesByName.put(objectiveId, objective); 49 | 50 | objective.sendObjectivePacket(State.CREATE); 51 | objective.sendShowPacket(); 52 | return objective; 53 | } 54 | 55 | public Collection getObjectives() { 56 | return objectivesByName.values(); 57 | } 58 | 59 | public Optional getSidebarObjective() { 60 | return Optional.ofNullable(sidebarObjective); 61 | } 62 | 63 | public void removeObjective(String objectiveId) { 64 | Objective objective = objectivesByName.remove(objectiveId); 65 | if (objective != null) { 66 | objective.sendObjectivePacket(State.REMOVE); 67 | } 68 | } 69 | 70 | public Optional getTeam(String teamId) { 71 | return Optional.ofNullable(teamsByName.get(teamId)); 72 | } 73 | 74 | public Team addTeam(String teamId) { 75 | Team team = new Team(this, teamId); 76 | teamsByName.put(teamId, team); 77 | 78 | team.sendCreatePacket(); 79 | return team; 80 | } 81 | 82 | public Collection getTeams() { 83 | return teamsByName.values(); 84 | } 85 | 86 | public void removeTeam(String teamId) { 87 | Team team = teamsByName.remove(teamId); 88 | if (team != null) { 89 | team.sendRemovePacket(); 90 | } 91 | } 92 | 93 | public Player getOwner() { 94 | return player; 95 | } 96 | 97 | void sendPacket(PacketContainer packet) { 98 | //add metadata that we ignore our packets on the listener 99 | packet.addMetadata("ScoreboardStats", true); 100 | 101 | try { 102 | //false so we don't listen to our own packets 103 | ProtocolLibrary.getProtocolManager().sendServerPacket(player, packet); 104 | } catch (InvocationTargetException ex) { 105 | //just log it for now. 106 | plugin.getLog().info("Failed to send packet", ex); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/SlotTransformer.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.bukkit.scoreboard.DisplaySlot; 6 | 7 | /** 8 | * Represent the three different sides a scoreboard objective can have 9 | *

10 | * Protocol specifications can be found here http://wiki.vg/Protocol 11 | */ 12 | class SlotTransformer { 13 | 14 | private SlotTransformer() { 15 | //singleton 16 | } 17 | 18 | /** 19 | * Get the enum from his id 20 | * 21 | * @param slotId the id 22 | * @return the representing enum or null if the id isn't valid 23 | */ 24 | public static Optional fromId(int slotId) { 25 | switch (slotId) { 26 | case 0: 27 | return Optional.of(DisplaySlot.PLAYER_LIST); 28 | case 1: 29 | return Optional.of(DisplaySlot.SIDEBAR); 30 | case 2: 31 | return Optional.of(DisplaySlot.BELOW_NAME); 32 | default: 33 | return Optional.empty(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/State.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | /** 4 | * Represents the state of a scoreboard objective packet 5 | *

6 | * Protocol specifications can be found here http://wiki.vg/Protocol 7 | */ 8 | public enum State { 9 | 10 | /** 11 | * The objective was created 12 | */ 13 | CREATE, 14 | 15 | /** 16 | * The objective was removed 17 | */ 18 | REMOVE, 19 | 20 | /** 21 | * The display name of the objective was changed 22 | */ 23 | UPDATE_DISPLAY_NAME; 24 | 25 | /** 26 | * Get the enum from his id 27 | * 28 | * @param stateId the id 29 | * @return the representing enum or null if the id not valid 30 | */ 31 | public static State fromId(int stateId) { 32 | return State.values()[stateId]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/Team.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import com.comphenix.protocol.PacketType.Play.Server; 4 | import com.comphenix.protocol.events.PacketContainer; 5 | import com.google.common.collect.ImmutableSet; 6 | import com.google.common.collect.Sets; 7 | 8 | import java.util.Collection; 9 | import java.util.Set; 10 | 11 | public class Team { 12 | 13 | private final PlayerScoreboard scoreboard; 14 | private final String teamId; 15 | 16 | final Set members = Sets.newHashSet(); 17 | String prefix; 18 | String suffix; 19 | 20 | public Team(PlayerScoreboard scoreboard, String teamId) { 21 | this.scoreboard = scoreboard; 22 | this.teamId = teamId; 23 | } 24 | 25 | public Team(PlayerScoreboard scoreboard, String teamId, Collection members) { 26 | this(scoreboard, teamId); 27 | 28 | this.members.addAll(members); 29 | } 30 | 31 | public String getId() { 32 | return teamId; 33 | } 34 | 35 | public boolean hasMember(String member) { 36 | return members.contains(member); 37 | } 38 | 39 | public boolean addMember(String member) { 40 | return members.add(member); 41 | } 42 | 43 | public boolean removeMember(String member) { 44 | return members.remove(member); 45 | } 46 | 47 | public Set getMembers() { 48 | return ImmutableSet.copyOf(members); 49 | } 50 | 51 | public String getPrefix() { 52 | return prefix; 53 | } 54 | 55 | public void setPrefix(String prefix) { 56 | this.prefix = prefix; 57 | } 58 | 59 | public String getSuffix() { 60 | return suffix; 61 | } 62 | 63 | public void setSuffix(String suffix) { 64 | this.suffix = suffix; 65 | } 66 | 67 | void sendCreatePacket() { 68 | PacketContainer packet = newPacket(TeamMode.CREATE); 69 | packet.getSpecificModifier(Collection.class).write(0, ImmutableSet.copyOf(members)); 70 | scoreboard.sendPacket(packet); 71 | } 72 | 73 | void sendMemberUpdatePacket(boolean add) { 74 | PacketContainer packet = newPacket(add ? TeamMode.ADD_MEMBER : TeamMode.REMOVE_MEMBER); 75 | packet.getSpecificModifier(Collection.class).write(0, ImmutableSet.copyOf(members)); 76 | scoreboard.sendPacket(packet); 77 | } 78 | 79 | void sendRemovePacket() { 80 | PacketContainer packet = newPacket(TeamMode.REMOVE); 81 | scoreboard.sendPacket(packet); 82 | } 83 | 84 | private PacketContainer newPacket(TeamMode mode) { 85 | PacketContainer packet = new PacketContainer(Server.SCOREBOARD_TEAM); 86 | packet.getStrings().write(0, teamId); 87 | 88 | packet.getIntegers().write(1, mode.ordinal()); 89 | return packet; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/github/games647/scoreboardstats/scoreboard/TeamMode.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | import java.util.Optional; 4 | 5 | public enum TeamMode { 6 | 7 | CREATE, 8 | 9 | REMOVE, 10 | 11 | UPDATE, 12 | 13 | ADD_MEMBER, 14 | 15 | REMOVE_MEMBER; 16 | 17 | public static Optional getMode(int id) { 18 | TeamMode[] values = TeamMode.values(); 19 | if (id < 0 || id >= values.length) { 20 | return Optional.empty(); 21 | } 22 | 23 | return Optional.of(values[id]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugin/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # ${project.name} configuration 2 | 3 | disabled-worlds: 4 | - city 5 | 6 | Scoreboard: 7 | Title: '&a&lStats' 8 | # seconds 9 | # For instant updates you can or 1 and it will update every second 10 | Update-delay: 2 11 | Items: 12 | # The Title must have under 48 characters 13 | # Title: Type 14 | '&9Online': '%online%' 15 | '&9Money': '%money%' 16 | # Your can choose your custom score here 17 | '&aHello World': 1337 18 | 19 | # Let ScoreboardStats track stats (kills, deaths, mobkills, killstreak) You need no plugin for this 20 | enable-pvpstats: false 21 | 22 | Temp-Scoreboard-enabled: false 23 | 24 | Temp-Scoreboard: 25 | Title: '&a&lTop Kills' 26 | # %mob% | %kills% | %killstreak% 27 | Type: '%kills%' 28 | Color: '&9' 29 | # How many Players would be displayed 30 | Items: 5 31 | Interval-show: 300 32 | Interval-disappear: 300 33 | -------------------------------------------------------------------------------- /plugin/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | # project data for Bukkit in order to register our plugin with all it components 2 | # ${project.name} are variables from Maven (pom.xml) which will be replaced after the build 3 | name: ${project.name} 4 | version: ${project.version} 5 | main: ${project.groupId}.${project.parent.artifactId}.${project.name} 6 | 7 | # meta data for plugin managers 8 | website: ${project.url} 9 | dev-url: ${project.url} 10 | description: | 11 | ${project.description} 12 | 13 | # Required for the packets 14 | depend: [ProtocolLib] 15 | 16 | # depending on them - load after them to make sure they are initialized 17 | softdepend: 18 | - InSigns 19 | # Replacer dependencies 20 | - PlaceholderAPI 21 | - mcMMO 22 | - Vault 23 | - SimpleClans 24 | - Factions 25 | - Heroes 26 | - uSkyBlock 27 | - PlayerPoints 28 | - Craftconomy3 29 | - ASkyBlock 30 | 31 | # Root commands to register automatically to Bukkit 32 | commands: 33 | # choose a unique name in order to register it successfully 34 | ${project.artifactId}: 35 | description: 'Root command for all commands in ${project.name}' 36 | aliases: [side, ss, sb, board, sidebar] 37 | 38 | # Permission management 39 | permissions: 40 | ${project.artifactId}.admin: 41 | children: 42 | ${project.artifactId}.reload: true 43 | ${project.artifactId}.sign: true 44 | ${project.artifactId}.use: true 45 | ${project.artifactId}.hide: true 46 | ${project.artifactId}.member: 47 | default: true 48 | children: 49 | ${project.artifactId}.use: true 50 | ${project.artifactId}.hide: true 51 | ${project.artifactId}.use: 52 | description: 'Sender is allowed to see the scoreboard' 53 | ${project.artifactId}.sign: 54 | description: 'Sender can make signs with variables on it' 55 | ${project.artifactId}.reload: 56 | description: 'Sender can perform a plugin reload' 57 | ${project.artifactId}.hide: 58 | description: 'Sender can toggle the scoreboard' 59 | -------------------------------------------------------------------------------- /plugin/src/main/resources/sql.yml: -------------------------------------------------------------------------------- 1 | # These settings aren't required if you disable pvpstats. 2 | # Here can you configure your sql setting. 3 | # If you don't have or want to use a mysql database you can also use a sqlite database 4 | # MySQL example 5 | # The default port for a mysql database is 3306 6 | # Username: 'bukkit' 7 | # Password: 'xyz' 8 | # Isolation: 'SERIALIZABLE' 9 | # Driver: 'com.mysql.jdbc.Driver' 10 | # Url: 'jdbc:mysql://IP:PORT/databaseName' 11 | # Timeout: 1000 12 | 13 | SQL-Settings: 14 | Username: 'bukkit' 15 | Password: 'walrus' 16 | Driver: 'org.sqlite.JDBC' 17 | # If you use mysql, delete org.sqlite.JDBC, comment out 18 | # Driver: 'com.mysql.jdbc.Driver' 19 | Url: 'jdbc:sqlite:{DIR}{NAME}.db' 20 | tablePrefix: '' 21 | -------------------------------------------------------------------------------- /plugin/src/test/java/com/github/games647/scoreboardstats/variables/VersionTest.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import com.github.games647.scoreboardstats.Version; 4 | 5 | import org.bukkit.Bukkit; 6 | import org.bukkit.Server; 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mockito; 11 | import org.powermock.api.mockito.PowerMockito; 12 | import org.powermock.core.classloader.annotations.PrepareForTest; 13 | import org.powermock.modules.junit4.PowerMockRunner; 14 | 15 | /** 16 | * Version Test class 17 | * 18 | * @see Version 19 | */ 20 | @PrepareForTest(Bukkit.class) 21 | @RunWith(PowerMockRunner.class) 22 | public class VersionTest { 23 | 24 | private static final String DEFAULT_VERSION = "git-Bukkit-1.5.2-R1.0-1-gf46bd58-b2793jnks (MC: 1.7.9)"; 25 | 26 | /** 27 | * Test version parsing 28 | */ 29 | @Test 30 | public void testParsing() { 31 | //Bukkit version parsing. Can be found here: META-INF 32 | PowerMockito.mockStatic(Bukkit.class); 33 | Server server = Mockito.mock(Server.class); 34 | Mockito.when(Bukkit.getServer()).thenReturn(server); 35 | 36 | Mockito.when(Bukkit.getVersion()).thenReturn(DEFAULT_VERSION); 37 | 38 | //Plugin Parsing of FactionsUUID; shouldn't fail 39 | Version.parse("1.6.9.5-U0.1.12-SNAPSHOT"); 40 | } 41 | 42 | /** 43 | * Test of compareTo method, of class Version. 44 | */ 45 | @Test 46 | public void testComparison() { 47 | Version low = new Version(1, 5, 4); 48 | Version high = new Version(1, 8, 5); 49 | 50 | Assert.assertSame("Higher Compare: " + high + ' ' + low, 1, high.compareTo(low)); 51 | Assert.assertSame("Lower Compare: " + low + ' ' + high, -1, low.compareTo(high)); 52 | 53 | Version higher = new Version(1, 5, 5); 54 | Assert.assertSame("Higher Compare: " + higher + ' ' + low, 1, higher.compareTo(low)); 55 | Assert.assertSame("Lower Compare: " + low + ' ' + higher, -1, low.compareTo(higher)); 56 | 57 | Version equal = new Version(1, 2, 3); 58 | Version equal1 = new Version(1, 2, 3); 59 | Assert.assertSame("Equal Compare: " + equal + ' ' + equal1, 0, equal.compareTo(equal1)); 60 | Assert.assertSame("Equal Compare: " + equal1 + ' ' + equal, 0, equal1.compareTo(equal1)); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.github.games647 6 | 7 | scoreboardstats-parent 8 | pom 9 | 10 | ScoreboardStats 11 | 12 | Show the Scoreboard with many custom variables 13 | 1.0.0 14 | https://dev.bukkit.org/bukkit-plugins/scoreboardstats 15 | 16 | 17 | UTF-8 18 | 19 | 2.0.0-beta.5 20 | 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | plugin 27 | variables 28 | pvp 29 | config 30 | defaults 31 | 32 | 33 | 34 | 35 | 36 | spigot-repo 37 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 38 | 39 | 40 | 41 | 42 | 43 | org.spigotmc 44 | spigot-api 45 | 1.12.2-R0.1-SNAPSHOT 46 | provided 47 | 48 | 49 | 50 | 51 | org.slf4j 52 | slf4j-jdk14 53 | 1.7.25 54 | 55 | 56 | 57 | 58 | org.powermock 59 | powermock-core 60 | ${powermock.version} 61 | test 62 | 63 | 64 | org.powermock 65 | powermock-module-junit4 66 | ${powermock.version} 67 | test 68 | 69 | 70 | org.powermock 71 | powermock-api-mockito2 72 | ${powermock.version} 73 | test 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /pvp/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.games647 7 | scoreboardstats-parent 8 | 1.0.0 9 | 10 | 11 | scoreboardstats-pvp 12 | jar 13 | 14 | 15 | 16 | 17 | jitpack.io 18 | https://jitpack.io 19 | 20 | 21 | 22 | 23 | 24 | com.github.games647 25 | scoreboardstats-variables 26 | 1.0.0 27 | 28 | 29 | 30 | com.github.games647 31 | scoreboardstats-config 32 | 1.0.0 33 | 34 | 35 | 36 | 37 | com.zaxxer 38 | HikariCP 39 | 2.7.1 40 | 41 | 42 | 43 | 44 | com.github.blablubbabc 45 | IndividualSigns 46 | 91ea396307 47 | 48 | 49 | * 50 | * 51 | 52 | 53 | true 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/Database.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import com.github.games647.scoreboardstats.config.Settings; 4 | import com.github.games647.scoreboardstats.variables.ReplaceManager; 5 | import com.github.games647.scoreboardstats.variables.Replacer; 6 | import com.google.common.collect.ImmutableMap; 7 | import com.google.common.collect.Lists; 8 | import com.google.common.collect.Maps; 9 | import com.zaxxer.hikari.HikariDataSource; 10 | 11 | import java.sql.Connection; 12 | import java.sql.PreparedStatement; 13 | import java.sql.ResultSet; 14 | import java.sql.SQLException; 15 | import java.sql.Statement; 16 | import java.time.Instant; 17 | import java.util.Collection; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Map.Entry; 22 | import java.util.Objects; 23 | import java.util.Optional; 24 | import java.util.UUID; 25 | import java.util.concurrent.CancellationException; 26 | import java.util.concurrent.Future; 27 | import java.util.function.Function; 28 | import java.util.stream.Collectors; 29 | 30 | import org.bukkit.Bukkit; 31 | import org.bukkit.entity.Player; 32 | import org.bukkit.metadata.MetadataValue; 33 | import org.bukkit.plugin.Plugin; 34 | import org.slf4j.Logger; 35 | 36 | /** 37 | * This represents a handler for saving player stats. 38 | */ 39 | public class Database { 40 | 41 | private static final String METAKEY = "player_stats"; 42 | 43 | private final Plugin plugin; 44 | private final Logger logger; 45 | 46 | private final Map toplist; 47 | private final DatabaseConfiguration dbConfig; 48 | private HikariDataSource dataSource; 49 | 50 | public Database(Plugin plugin, Logger logger) { 51 | this.plugin = plugin; 52 | this.logger = logger; 53 | 54 | this.dbConfig = new DatabaseConfiguration(plugin); 55 | this.toplist = Maps.newHashMapWithExpectedSize(Settings.getTopitems()); 56 | } 57 | 58 | /** 59 | * Get the cache player stats if they exists and the arguments are valid. 60 | * 61 | * @param request the associated player 62 | * @return the stats if they are in the cache 63 | */ 64 | @Deprecated 65 | public PlayerStats getCachedStats(Player request) { 66 | return getStats(request).orElse(null); 67 | } 68 | 69 | public Optional getStats(Player request) { 70 | if (request != null) { 71 | for (MetadataValue metadata : request.getMetadata(METAKEY)) { 72 | if (metadata.value() instanceof PlayerStats) { 73 | return Optional.of((PlayerStats) metadata.value()); 74 | } 75 | } 76 | } 77 | 78 | return Optional.empty(); 79 | } 80 | 81 | /** 82 | * Starts loading the stats for a specific player in an external thread. 83 | * 84 | * @param player the associated player 85 | */ 86 | public void loadAccountAsync(Player player) { 87 | if (dataSource != null && !getStats(player).isPresent()) { 88 | Bukkit.getScheduler().runTaskAsynchronously(plugin, new StatsLoader(plugin, player, this)); 89 | } 90 | } 91 | 92 | /** 93 | * Starts loading the stats for a specific player sync 94 | * 95 | * @param uniqueId the associated playername or uuid 96 | * @return the loaded stats 97 | */ 98 | public Optional loadAccount(UUID uniqueId) { 99 | if (dataSource == null) { 100 | return Optional.empty(); 101 | } else { 102 | try (Connection conn = dataSource.getConnection(); 103 | PreparedStatement stmt = conn.prepareStatement("SELECT * FROM player_stats WHERE uuid=?")) { 104 | 105 | stmt.setString(1, uniqueId.toString()); 106 | try (ResultSet resultSet = stmt.executeQuery()) { 107 | return Optional.of(extractPlayerStats(resultSet)); 108 | } 109 | } catch (SQLException ex) { 110 | logger.error("Error loading player profile", ex); 111 | } 112 | 113 | return Optional.empty(); 114 | } 115 | } 116 | 117 | private PlayerStats extractPlayerStats(ResultSet resultSet) throws SQLException { 118 | int id = resultSet.getInt(1); 119 | 120 | String rawUUID = resultSet.getString(2); 121 | UUID uuid = null; 122 | if (rawUUID != null) { 123 | uuid = UUID.fromString(rawUUID); 124 | } 125 | 126 | String playerName = resultSet.getString(3); 127 | 128 | int kills = resultSet.getInt(4); 129 | int deaths = resultSet.getInt(5); 130 | int mobkills = resultSet.getInt(6); 131 | int killstreak = resultSet.getInt(7); 132 | 133 | Instant lastOnline = resultSet.getTimestamp(8).toInstant(); 134 | return new PlayerStats(id, uuid, playerName, kills, deaths, mobkills, killstreak, lastOnline); 135 | } 136 | 137 | /** 138 | * Starts loading the stats for a specific player sync 139 | * 140 | * @param player the associated player 141 | * @return the loaded stats 142 | */ 143 | public Optional loadAccount(Player player) { 144 | return loadAccount(player.getUniqueId()); 145 | } 146 | 147 | /** 148 | * Save PlayerStats async. 149 | * 150 | * @param stats PlayerStats data 151 | */ 152 | public void saveAsync(PlayerStats stats) { 153 | Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> save(Lists.newArrayList(stats))); 154 | } 155 | 156 | /** 157 | * Save the PlayerStats on the current Thread. 158 | * 159 | * @param stats PlayerStats data 160 | */ 161 | public void save(Collection stats) { 162 | if (stats != null && dataSource != null) { 163 | update(stats.stream() 164 | .filter(Objects::nonNull) 165 | .filter(stat -> !stat.isNew()) 166 | .collect(Collectors.toList())); 167 | 168 | insert(stats.stream() 169 | .filter(Objects::nonNull) 170 | .filter(PlayerStats::isNew) 171 | .collect(Collectors.toList())); 172 | } 173 | } 174 | 175 | private void update(Collection stats) { 176 | if (stats.isEmpty()) { 177 | return; 178 | } 179 | 180 | //Save the stats to the database 181 | try (Connection conn = dataSource.getConnection(); 182 | PreparedStatement stmt = conn.prepareStatement("UPDATE player_stats " 183 | + "SET kills=?, deaths=?, killstreak=?, mobkills=?, last_online=CURRENT_TIMESTAMP, playername=? " 184 | + "WHERE id=?")) { 185 | conn.setAutoCommit(false); 186 | for (PlayerStats stat : stats) { 187 | stmt.setInt(1, stat.getKills()); 188 | stmt.setInt(2, stat.getDeaths()); 189 | stmt.setInt(3, stat.getKillstreak()); 190 | stmt.setInt(4, stat.getMobkills()); 191 | 192 | stmt.setString(5, stat.getPlayername()); 193 | 194 | stmt.setInt(6, stat.getId()); 195 | stmt.addBatch(); 196 | } 197 | 198 | stmt.executeBatch(); 199 | conn.commit(); 200 | } catch (Exception ex) { 201 | logger.error("Error updating profiles", ex); 202 | } 203 | } 204 | 205 | private void insert(Collection stats) { 206 | if (stats.isEmpty()) { 207 | return; 208 | } 209 | 210 | try (Connection conn = dataSource.getConnection(); 211 | PreparedStatement stmt = conn.prepareStatement("INSERT INTO player_stats " 212 | + "(uuid, playername, kills, deaths, killstreak, mobkills, last_online) VALUES " 213 | + "(?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)", Statement.RETURN_GENERATED_KEYS)) { 214 | conn.setAutoCommit(false); 215 | for (PlayerStats stat : stats) { 216 | stmt.setString(1, stat.getUuid().toString()); 217 | stmt.setString(2, stat.getPlayername()); 218 | 219 | stmt.setInt(3, stat.getKills()); 220 | stmt.setInt(4, stat.getDeaths()); 221 | stmt.setInt(5, stat.getKillstreak()); 222 | stmt.setInt(6, stat.getMobkills()); 223 | 224 | stmt.addBatch(); 225 | } 226 | 227 | stmt.executeBatch(); 228 | conn.commit(); 229 | 230 | try (ResultSet generatedKeys = stmt.getGeneratedKeys()) { 231 | for (PlayerStats stat : stats) { 232 | if (!generatedKeys.next()) { 233 | break; 234 | } 235 | 236 | stat.setId(generatedKeys.getInt(1)); 237 | } 238 | } 239 | } catch (Exception ex) { 240 | logger.error("Error inserting profiles", ex); 241 | } 242 | } 243 | 244 | /** 245 | * Starts saving all cache player stats and then clears the cache. 246 | */ 247 | public void saveAll() { 248 | try { 249 | logger.info("Now saving the stats to the database. This could take a while."); 250 | 251 | //If pvpstats are enabled save all stats that are in the cache 252 | List toSave = Bukkit.getOnlinePlayers().stream() 253 | .map(this::getStats) 254 | .filter(Optional::isPresent) 255 | .map(Optional::get) 256 | .collect(Collectors.toList()); 257 | 258 | if (!toSave.isEmpty()) { 259 | save(toSave); 260 | } 261 | 262 | dataSource.close(); 263 | } finally { 264 | //Make rally sure we remove all even on error 265 | Bukkit.getOnlinePlayers() 266 | .forEach(player -> player.removeMetadata(METAKEY, plugin)); 267 | } 268 | } 269 | 270 | /** 271 | * Initialize a components and checking for an existing database 272 | */ 273 | public void setupDatabase() { 274 | //Check if pvpstats should be enabled 275 | dbConfig.loadConfiguration(); 276 | dataSource = new HikariDataSource(dbConfig.getServerConfig()); 277 | 278 | try (Connection conn = dataSource.getConnection(); 279 | Statement stmt = conn.createStatement()) { 280 | String createTableQuery = "CREATE TABLE IF NOT EXISTS " + dbConfig.getTablePrefix() + "player_stats ( " 281 | + "id integer PRIMARY KEY AUTO_INCREMENT, " 282 | + "uuid varchar(40) NOT NULL, " 283 | + "playername varchar(16) NOT NULL, " 284 | + "kills integer NOT NULL, " 285 | + "deaths integer NOT NULL, " 286 | + "mobkills integer NOT NULL, " 287 | + "killstreak integer NOT NULL, " 288 | + "last_online timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP )"; 289 | 290 | if (dbConfig.getServerConfig().getDriverClassName().contains("sqlite")) { 291 | createTableQuery = createTableQuery.replace("AUTO_INCREMENT", ""); 292 | dataSource.setMaximumPoolSize(1); 293 | } 294 | 295 | stmt.execute(createTableQuery); 296 | } catch (Exception ex) { 297 | logger.error("Error creating database ", ex); 298 | return; 299 | } 300 | 301 | Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, this::updateTopList, 20 * 60 * 5, 0); 302 | Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> { 303 | Future> syncPlayers = Bukkit.getScheduler() 304 | .callSyncMethod(plugin, Bukkit::getOnlinePlayers); 305 | 306 | try { 307 | Collection onlinePlayers = syncPlayers.get(); 308 | 309 | List toSave = onlinePlayers.stream() 310 | .map(this::getStats) 311 | .filter(Optional::isPresent) 312 | .map(Optional::get) 313 | .collect(Collectors.toList()); 314 | 315 | if (!toSave.isEmpty()) { 316 | save(toSave); 317 | } 318 | } catch (CancellationException cancelEx) { 319 | //ignore it on shutdown 320 | } catch (Exception ex) { 321 | logger.error("Error fetching top list", ex); 322 | } 323 | }, 20 * 60, 20 * 60 * 5); 324 | 325 | registerEvents(); 326 | } 327 | 328 | /** 329 | * Get the a map of the best players for a specific category. 330 | * 331 | * @return a iterable of the entries 332 | */ 333 | public Iterable> getTop() { 334 | synchronized (toplist) { 335 | return ImmutableMap.copyOf(toplist).entrySet(); 336 | } 337 | } 338 | 339 | /** 340 | * Updates the toplist 341 | */ 342 | private void updateTopList() { 343 | String type = Settings.getTopType(); 344 | Map newToplist; 345 | switch (type) { 346 | case "killstreak": 347 | newToplist = getTopList("killstreak", PlayerStats::getKillstreak); 348 | break; 349 | case "mob": 350 | newToplist = getTopList("mobkills", PlayerStats::getMobkills); 351 | break; 352 | default: 353 | newToplist = getTopList("kills", PlayerStats::getKills); 354 | break; 355 | } 356 | 357 | synchronized (toplist) { 358 | //set it after fetching so it's only blocking for a short time 359 | toplist.clear(); 360 | toplist.putAll(newToplist); 361 | } 362 | } 363 | 364 | private Map getTopList(String type, Function valueMapper) { 365 | if (dataSource == null) { 366 | return Collections.emptyMap(); 367 | } 368 | 369 | try (Connection conn = dataSource.getConnection(); 370 | Statement stmt = conn.createStatement()) { 371 | try (ResultSet resultSet = stmt.executeQuery("SELECT * FROM player_stats ORDER BY " + type + " desc" 372 | + " LIMIT " + Settings.getTopitems())) { 373 | Map result = Maps.newHashMap(); 374 | for (int i = 0; i < Settings.getTopitems(); i++) { 375 | if (!resultSet.next()) { 376 | return result; 377 | } 378 | 379 | PlayerStats stats = extractPlayerStats(resultSet); 380 | if (!stats.isNew()) { 381 | String entry = (i + 1) + ". " + stats.getPlayername(); 382 | result.put(entry, valueMapper.apply(stats)); 383 | } 384 | } 385 | 386 | return result; 387 | } 388 | } catch (SQLException ex) { 389 | logger.error("Error loading top list", ex); 390 | } 391 | 392 | return Collections.emptyMap(); 393 | } 394 | 395 | private void registerEvents() { 396 | if (Bukkit.getPluginManager().isPluginEnabled("InSigns")) { 397 | //Register this listener if InSigns is available 398 | Bukkit.getPluginManager().registerEvents(new SignListener(plugin, this), plugin); 399 | } 400 | 401 | ReplaceManager replaceManager = ReplaceManager.getInstance(); 402 | replaceManager.register(newVariable("kills", PlayerStats::getKills)); 403 | replaceManager.register(newVariable("deaths", PlayerStats::getDeaths)); 404 | replaceManager.register(newVariable("kdr", PlayerStats::getKdr)); 405 | replaceManager.register(newVariable("mob-kills", PlayerStats::getMobkills)); 406 | replaceManager.register(newVariable("killstreak", PlayerStats::getKillstreak)); 407 | replaceManager.register(newVariable("current-streak", PlayerStats::getCurrentStreak)); 408 | 409 | Bukkit.getPluginManager().registerEvents(new StatsListener(plugin, this), plugin); 410 | } 411 | 412 | private Replacer newVariable(String variable, Function fct) { 413 | return new Replacer(plugin, "kills") 414 | .scoreSupply(player -> getStats(player) 415 | .map(fct) 416 | .orElse(-1)); 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/DatabaseConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import com.zaxxer.hikari.HikariConfig; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | import org.bukkit.configuration.ConfigurationSection; 9 | import org.bukkit.configuration.file.FileConfiguration; 10 | import org.bukkit.configuration.file.YamlConfiguration; 11 | import org.bukkit.plugin.Plugin; 12 | 13 | /** 14 | * Configuration for the SQL database. 15 | * 16 | * @see Database 17 | */ 18 | class DatabaseConfiguration { 19 | 20 | private final Plugin plugin; 21 | 22 | private HikariConfig serverConfig; 23 | private String tablePrefix; 24 | 25 | DatabaseConfiguration(Plugin instance) { 26 | plugin = instance; 27 | } 28 | 29 | /** 30 | * Get the SQL configuration 31 | * 32 | * @return the server configuration 33 | */ 34 | public HikariConfig getServerConfig() { 35 | return serverConfig; 36 | } 37 | 38 | public String getTablePrefix() { 39 | return tablePrefix; 40 | } 41 | 42 | /** 43 | * Loads the configuration 44 | */ 45 | public void loadConfiguration() { 46 | serverConfig = new HikariConfig(); 47 | 48 | Path file = plugin.getDataFolder().toPath().resolve("sql.yml"); 49 | //Check if the file exists. If so load the settings form there 50 | if (Files.notExists(file)) { 51 | //Create a new configuration based on the default settings form bukkit.yml 52 | plugin.saveResource("sql.yml", false); 53 | } 54 | 55 | FileConfiguration sqlConfig = YamlConfiguration.loadConfiguration(file.toFile()); 56 | 57 | ConfigurationSection sqlSettingSection = sqlConfig.getConfigurationSection("SQL-Settings"); 58 | serverConfig.setUsername(sqlSettingSection.getString("Username")); 59 | serverConfig.setPassword(sqlSettingSection.getString("Password")); 60 | serverConfig.setDriverClassName(sqlSettingSection.getString("Driver")); 61 | serverConfig.setJdbcUrl(replaceUrlString(sqlSettingSection.getString("Url"))); 62 | if (serverConfig.getDriverClassName().contains("sqlite")) { 63 | serverConfig.setConnectionTestQuery("SELECT 1"); 64 | } 65 | 66 | tablePrefix = sqlSettingSection.getString("tablePrefix", ""); 67 | } 68 | 69 | private String replaceUrlString(String input) { 70 | //Replace the windows separators ('\') with a '/'; \\ escape regEx --> \\\\ escape java 71 | String result = input.replaceAll("\\{DIR\\}", plugin.getDataFolder().getPath().replaceAll("\\\\", "/") + '/'); 72 | result = result.replaceAll("\\{NAME\\}", plugin.getDescription().getName().replaceAll("[^\\w-]", "")); 73 | 74 | return result; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/PlayerStats.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import java.time.Instant; 4 | import java.util.Objects; 5 | import java.util.UUID; 6 | 7 | import org.apache.commons.lang.builder.ToStringBuilder; 8 | import org.bukkit.util.NumberConversions; 9 | 10 | /** 11 | * Represents the stats from a player. The stats are kills, deaths, mobkills and killstreak. All stats are annotated to 12 | * be validated on runtime to be not invalid. 13 | *

14 | * Maybe these stats also have to be validated to be not null, so we can prevent some special cases while using it 15 | * especially for SQL database, but this can also occurs on using it as file database due incorrect format or unexpected 16 | * user modifications. 17 | */ 18 | public class PlayerStats { 19 | 20 | private final UUID uuid; 21 | private int id = -1; 22 | private String playername; 23 | 24 | //You can't have negative stats 25 | private int kills; 26 | private int deaths; 27 | private int mobkills; 28 | private int killstreak; 29 | 30 | private Instant lastOnline; 31 | 32 | private int currentStreak; 33 | 34 | public PlayerStats(int id, UUID uuid, String playername, 35 | int kills, int deaths, int mobkills, int killstreak, Instant lastOnline) { 36 | this(uuid, playername); 37 | 38 | this.id = id; 39 | this.kills = kills; 40 | this.deaths = deaths; 41 | this.mobkills = mobkills; 42 | this.killstreak = killstreak; 43 | this.lastOnline = lastOnline; 44 | } 45 | 46 | public PlayerStats(UUID uuid, String playername) { 47 | this.uuid = uuid; 48 | this.playername = playername; 49 | } 50 | 51 | /** 52 | * Get the auto incrementing id 53 | * 54 | * @return the auto incrementing id 55 | */ 56 | public int getId() { 57 | return id; 58 | } 59 | 60 | public void setId(int id) { 61 | this.id = id; 62 | } 63 | 64 | /** 65 | * Get the UUID of this player. The database represents this one as string 66 | * 67 | * @return the UUID of the player 68 | */ 69 | public UUID getUuid() { 70 | return uuid; 71 | } 72 | 73 | /** 74 | * Get the player name of these stats 75 | * 76 | * @return the player name of these stats 77 | */ 78 | public String getPlayername() { 79 | return playername; 80 | } 81 | 82 | /** 83 | * Set the player name of these stats 84 | * 85 | * @param playername the player name of these stats 86 | */ 87 | public void setPlayername(String playername) { 88 | this.playername = playername; 89 | } 90 | 91 | /** 92 | * Get the player kills 93 | * 94 | * @return the player kills 95 | */ 96 | public int getKills() { 97 | return kills; 98 | } 99 | 100 | /** 101 | * Get the deaths 102 | * 103 | * @return the deaths 104 | */ 105 | public int getDeaths() { 106 | return deaths; 107 | } 108 | 109 | /** 110 | * Get the mob kills 111 | * 112 | * @return the mob kills 113 | */ 114 | public int getMobkills() { 115 | return mobkills; 116 | } 117 | 118 | /** 119 | * Get the highest killstreak 120 | * 121 | * @return the highest killstreak 122 | */ 123 | public int getKillstreak() { 124 | return killstreak; 125 | } 126 | 127 | /** 128 | * Get the current killstreak 129 | * 130 | * @return current killstreak 131 | */ 132 | public int getCurrentStreak() { 133 | return currentStreak; 134 | } 135 | 136 | /** 137 | * Get the current kill-death-ratio rounded 138 | * 139 | * @return the kill death ratio rounded to an integer 140 | */ 141 | public int getKdr() { 142 | //We can't divide by zero 143 | if (deaths == 0) { 144 | return kills; 145 | } else { 146 | //Triggers float division to have decimals 147 | return NumberConversions.round(kills / (float) deaths); 148 | } 149 | } 150 | 151 | /** 152 | * Get the UNIX timestamp where this entry was last updated. 153 | * 154 | * @return the timestamp this was last saved; can be null 155 | */ 156 | @Deprecated 157 | public long getLastOnline() { 158 | return lastOnline.toEpochMilli(); 159 | } 160 | 161 | public void setLastOnline(Instant lastOnline) { 162 | this.lastOnline = lastOnline; 163 | } 164 | 165 | /** 166 | * Set the update timestamp value. This value is updated on every save. 167 | * 168 | * @param lastOnline the player was online; can be null 169 | */ 170 | @Deprecated 171 | public void setLastOnline(long lastOnline) { 172 | this.lastOnline = Instant.ofEpochMilli(lastOnline); 173 | } 174 | 175 | public Instant getLastOnlineDate() { 176 | return lastOnline; 177 | } 178 | 179 | /** 180 | * Increment the kills 181 | */ 182 | public void onKill() { 183 | kills++; 184 | 185 | currentStreak++; 186 | if (currentStreak > killstreak) { 187 | killstreak = currentStreak; 188 | } 189 | } 190 | 191 | /** 192 | * Increment the mob kills 193 | */ 194 | public void onMobKill() { 195 | mobkills++; 196 | } 197 | 198 | /** 199 | * Increment the deaths 200 | */ 201 | public void onDeath() { 202 | currentStreak = 0; 203 | deaths++; 204 | } 205 | 206 | public boolean isNew() { 207 | return id == -1; 208 | } 209 | 210 | @Override 211 | public int hashCode() { 212 | return Objects.hash(id, uuid, playername); 213 | } 214 | 215 | @Override 216 | public boolean equals(Object obj) { 217 | //ignores also null 218 | if (obj instanceof PlayerStats) { 219 | PlayerStats other = (PlayerStats) obj; 220 | return id == other.id 221 | && Objects.equals(uuid, other.uuid) 222 | && Objects.equals(playername, other.playername); 223 | } 224 | 225 | return false; 226 | } 227 | 228 | @Override 229 | public String toString() { 230 | return ToStringBuilder.reflectionToString(this); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/SignListener.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | 5 | import de.blablubbabc.insigns.SignSendEvent; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.function.Function; 10 | 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.event.EventHandler; 13 | import org.bukkit.event.EventPriority; 14 | import org.bukkit.event.Listener; 15 | import org.bukkit.plugin.Plugin; 16 | 17 | /** 18 | * Replace some variables on signs with the player individual stats. 19 | * The variables will be replaced dynamically 20 | * 21 | * @see Database 22 | */ 23 | public class SignListener implements Listener { 24 | 25 | private static final char OPENING_TAG = '['; 26 | private static final char CLOSING_TAG = ']'; 27 | 28 | private final Database database; 29 | private final Map> variables = ImmutableMap. 30 | >builder() 31 | .put(OPENING_TAG + "Kill" + CLOSING_TAG, PlayerStats::getKills) 32 | .put(OPENING_TAG + "Death" + CLOSING_TAG, PlayerStats::getDeaths) 33 | .put(OPENING_TAG + "KDR" + CLOSING_TAG, PlayerStats::getKdr) 34 | .put(OPENING_TAG + "Streak" + CLOSING_TAG, PlayerStats::getKillstreak) 35 | .build(); 36 | 37 | public SignListener(Plugin plugin, Database statsDatabase) { 38 | this.database = statsDatabase; 39 | } 40 | 41 | @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) 42 | public void onSignSend(SignSendEvent event) { 43 | for (int i = 0; i < 4; ++i) { 44 | String line = event.getLine(i); 45 | Player player = event.getPlayer(); 46 | 47 | Optional replaced = replace(player, line); 48 | if (replaced.isPresent()) { 49 | event.setLine(i, replaced.get()); 50 | } 51 | } 52 | } 53 | 54 | private Optional replace(Player player, String line) { 55 | for (Map.Entry> entry : variables.entrySet()) { 56 | if (line.contains(entry.getKey())) { 57 | Function fct = entry.getValue(); 58 | return Optional.of(database.getStats(player) 59 | .map(fct) 60 | .map(value -> Integer.toString(value)) 61 | .orElse("Not loaded")); 62 | } 63 | } 64 | 65 | return Optional.empty(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/StatsListener.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import com.github.games647.scoreboardstats.config.Settings; 4 | import com.github.games647.scoreboardstats.variables.ReplaceManager; 5 | 6 | import org.bukkit.entity.EntityType; 7 | import org.bukkit.entity.LivingEntity; 8 | import org.bukkit.entity.Player; 9 | import org.bukkit.event.EventHandler; 10 | import org.bukkit.event.EventPriority; 11 | import org.bukkit.event.Listener; 12 | import org.bukkit.event.entity.EntityDeathEvent; 13 | import org.bukkit.event.entity.PlayerDeathEvent; 14 | import org.bukkit.event.player.PlayerJoinEvent; 15 | import org.bukkit.event.player.PlayerQuitEvent; 16 | import org.bukkit.plugin.Plugin; 17 | 18 | /** 19 | * If enabled this class counts the kills. 20 | */ 21 | public class StatsListener implements Listener { 22 | 23 | private final Plugin plugin; 24 | private final Database database; 25 | 26 | public StatsListener(Plugin plugin, Database database) { 27 | this.plugin = plugin; 28 | this.database = database; 29 | } 30 | 31 | /** 32 | * Add the player account from the database in the cache. 33 | * 34 | * @param joinEvent the join event 35 | * @see Database 36 | */ 37 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) 38 | public void onJoin(PlayerJoinEvent joinEvent) { 39 | Player player = joinEvent.getPlayer(); 40 | //removing old metadata which wasn't removed (which can lead to memory leaks) 41 | player.removeMetadata("player_stats", plugin); 42 | 43 | //load the pvpstats if activated 44 | database.loadAccountAsync(player); 45 | } 46 | 47 | /** 48 | * Saves the stats to database if the player leaves 49 | * 50 | * @param quitEvent leave event 51 | * @see Database 52 | */ 53 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) 54 | public void onQuit(PlayerQuitEvent quitEvent) { 55 | Player player = quitEvent.getPlayer(); 56 | 57 | database.getStats(player).ifPresent(database::saveAsync); 58 | 59 | //just remove our metadata to prevent memory leaks 60 | player.removeMetadata("player_stats", plugin); 61 | } 62 | 63 | /** 64 | * Tracks the mob kills. 65 | * 66 | * @param event the death event 67 | */ 68 | @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGH) 69 | public void onMobDeath(EntityDeathEvent event) { 70 | LivingEntity entity = event.getEntity(); 71 | //killer is null if it's not a player 72 | Player killer = entity.getKiller(); 73 | 74 | //Check if it's not player because we are already handling it 75 | if (entity.getType() != EntityType.PLAYER && Settings.isActiveWorld(entity.getWorld().getName())) { 76 | database.getStats(killer).ifPresent(stats -> { 77 | //If the cache entry is loaded and the player isn't null, increase the mob kills 78 | stats.onMobKill(); 79 | 80 | ReplaceManager.getInstance().forceUpdate(killer, "mob", stats.getMobkills()); 81 | }); 82 | } 83 | } 84 | 85 | /** 86 | * Tracks player deaths and kills 87 | * 88 | * @param deathEvent the death event. 89 | */ 90 | @EventHandler 91 | public void onDeath(PlayerDeathEvent deathEvent) { 92 | Player killed = deathEvent.getEntity(); 93 | //killer is null if it's not a player 94 | Player killer = killed.getKiller(); 95 | if (killed.equals(killer)) { 96 | return; 97 | } 98 | 99 | if (Settings.isActiveWorld(killed.getWorld().getName())) { 100 | ReplaceManager replaceManager = ReplaceManager.getInstance(); 101 | 102 | database.getStats(killed).ifPresent(killedCache -> { 103 | killedCache.onDeath(); 104 | replaceManager.forceUpdate(killed, "deaths", killedCache.getDeaths()); 105 | replaceManager.forceUpdate(killed, "kdr", killedCache.getKdr()); 106 | //will reset 107 | replaceManager.forceUpdate(killed, "current_streak", killedCache.getCurrentStreak()); 108 | }); 109 | 110 | 111 | database.getStats(killer).ifPresent(killercache -> { 112 | killercache.onKill(); 113 | replaceManager.forceUpdate(killer, "kills", killercache.getKills()); 114 | replaceManager.forceUpdate(killer, "kdr", killercache.getKdr()); 115 | //maybe the player reaches a new high score 116 | replaceManager.forceUpdate(killer, "killstreak", killercache.getKillstreak()); 117 | replaceManager.forceUpdate(killer, "current_streak", killercache.getCurrentStreak()); 118 | }); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /pvp/src/main/java/com/github/games647/scoreboardstats/pvp/StatsLoader.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.pvp; 2 | 3 | import com.github.games647.scoreboardstats.variables.ReplaceManager; 4 | 5 | import java.lang.ref.WeakReference; 6 | import java.time.Instant; 7 | 8 | import org.apache.commons.lang.builder.ToStringBuilder; 9 | import org.bukkit.Bukkit; 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.metadata.FixedMetadataValue; 12 | import org.bukkit.plugin.Plugin; 13 | 14 | /** 15 | * This class is used for loading the player stats. 16 | */ 17 | public class StatsLoader implements Runnable { 18 | 19 | private final Plugin plugin; 20 | 21 | private final WeakReference weakPlayer; 22 | private final WeakReference weakDatabase; 23 | 24 | /** 25 | * Creates a new loader for a specific player 26 | * 27 | * @param plugin the owning plugin to reschedule 28 | * @param player player instance 29 | * @param statsDatabase the pvp database 30 | */ 31 | public StatsLoader(Plugin plugin, Player player, Database statsDatabase) { 32 | this.plugin = plugin; 33 | 34 | //don't prevent the garbage collection of this player if he logs out 35 | this.weakPlayer = new WeakReference<>(player); 36 | this.weakDatabase = new WeakReference<>(statsDatabase); 37 | } 38 | 39 | @Override 40 | public void run() { 41 | final Player player = weakPlayer.get(); 42 | Database statsDatabase = weakDatabase.get(); 43 | if (player != null && statsDatabase != null) { 44 | PlayerStats stats = statsDatabase.loadAccount(player) 45 | .orElse(new PlayerStats(player.getUniqueId(), player.getName())); 46 | 47 | //update player name on every load, because it's changeable 48 | stats.setPlayername(player.getName()); 49 | stats.setLastOnline(Instant.now()); 50 | 51 | Bukkit.getScheduler().runTask(plugin, () -> { 52 | //possible not thread-safe, so reschedule it while setMetadata is thread-safe 53 | if (player.isOnline()) { 54 | //sets it only if the player is only 55 | player.setMetadata("player_stats", new FixedMetadataValue(plugin, stats)); 56 | ReplaceManager.getInstance().forceUpdate(player, "deaths", stats.getDeaths()); 57 | ReplaceManager.getInstance().forceUpdate(player, "kdr", stats.getKdr()); 58 | ReplaceManager.getInstance().forceUpdate(player, "kills", stats.getKills()); 59 | ReplaceManager.getInstance().forceUpdate(player, "killstreak", stats.getKillstreak()); 60 | ReplaceManager.getInstance().forceUpdate(player, "current_streak", stats.getCurrentStreak()); 61 | ReplaceManager.getInstance().forceUpdate(player, "mobkills", stats.getMobkills()); 62 | } 63 | }); 64 | } 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return ToStringBuilder.reflectionToString(this); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /variables/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | com.github.games647 7 | scoreboardstats-parent 8 | 1.0.0 9 | 10 | 11 | scoreboardstats-variables 12 | jar 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-javadoc-plugin 19 | 3.0.0-M1 20 | 21 | 22 | https://hub.spigotmc.org/javadocs/spigot/ 23 | https://docs.oracle.com/javase/9/docs/api/ 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/BoardManager.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import org.bukkit.entity.Player; 4 | 5 | public interface BoardManager { 6 | 7 | /** 8 | * Adding all players to the refresh queue and loading the player stats if enabled 9 | */ 10 | void registerAll(); 11 | 12 | /** 13 | * Clear the scoreboard for all players 14 | */ 15 | void unregisterAll(); 16 | 17 | /** 18 | * Creates a new scoreboard based on the configuration. 19 | * 20 | * @param player for who should the scoreboard be set. 21 | */ 22 | void createScoreboard(Player player); 23 | 24 | void createTopListScoreboard(Player player); 25 | 26 | void onUpdate(Player player); 27 | 28 | void updateVariable(Player player, String variable, int newScore); 29 | 30 | void updateVariable(Player player, String variable, String newScore); 31 | 32 | /** 33 | * Unregister ScoreboardStats from the player 34 | * 35 | * @param player who owns the scoreboard 36 | */ 37 | void unregister(Player player); 38 | 39 | /** 40 | * Called if the scoreboard should be updated. 41 | * 42 | * @param player for who should the scoreboard be set. 43 | */ 44 | void sendUpdate(Player player); 45 | } 46 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/Version.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats; 2 | 3 | import com.google.common.collect.ComparisonChain; 4 | 5 | import java.util.Objects; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | import org.apache.commons.lang.builder.ToStringBuilder; 10 | 11 | /** 12 | * Version class for comparing and detecting Minecraft and other versions 13 | */ 14 | public class Version implements Comparable { 15 | 16 | //thanks to the author of ProtocolLib aadnk 17 | private static final String VERSION_REGEX = ".*\\(.*MC.\\s*([a-zA-z0-9\\-\\.]+)\\s*\\)"; 18 | private final int major; 19 | private final int minor; 20 | private final int build; 21 | 22 | /** 23 | * Creates a new version based on this string. 24 | * 25 | * @param version the version string 26 | * @throws IllegalArgumentException if the string doesn't match a version format 27 | */ 28 | public Version(String version) throws IllegalArgumentException { 29 | int[] versionParts = parse(version); 30 | 31 | major = versionParts[0]; 32 | minor = versionParts[1]; 33 | build = versionParts[2]; 34 | } 35 | 36 | /** 37 | * Creates a new version based on these values. 38 | * 39 | * @param major the major version 40 | * @param minor the minor version 41 | * @param build the build version 42 | */ 43 | public Version(int major, int minor, int build) { 44 | this.major = major; 45 | this.minor = minor; 46 | this.build = build; 47 | } 48 | 49 | /** 50 | * Compares the version with checking the first three numbers 51 | * 52 | * @param expected the object to be compared. 53 | * @param version the object to be compared. 54 | * @return 1 version is higher; 0 both are equal; -1 version is lower
55 | * a negative integer, zero, or a positive integer as this object 56 | * is less than, equal to, or greater than the specified object. 57 | */ 58 | public static int compare(String expected, String version) { 59 | int[] expectedParts = parse(stripVersionDetails(version)); 60 | int[] versionParts = parse(stripVersionDetails(expected)); 61 | 62 | return ComparisonChain.start() 63 | .compare(expectedParts[0], versionParts[0]) 64 | .compare(expectedParts[1], versionParts[1]) 65 | .compare(expectedParts[2], versionParts[2]) 66 | .result(); 67 | } 68 | 69 | /** 70 | * Separate the version into major, minor, build integers 71 | * 72 | * @param version the version that should be parsed 73 | * @return the version parts 74 | * @throws IllegalArgumentException if the version doesn't contains only positive numbers separated by max. 5 dots. 75 | */ 76 | public static int[] parse(String version) throws IllegalArgumentException { 77 | //excludes spaces which could be added by mistake and exclude build suffixes 78 | String trimmedVersion = version.trim().split("(\\-|[a-zA-Z])")[0]; 79 | if (!trimmedVersion.matches("\\d+(\\.\\d+){0,5}")) { 80 | //check if it's a format like '1.5' 81 | throw new IllegalArgumentException("Invalid format: " + version); 82 | } 83 | 84 | int[] versionParts = new int[3]; 85 | 86 | //escape regEx and split by dots 87 | String[] split = trimmedVersion.split("\\."); 88 | //We check if the length has min 1 entry. 89 | for (int i = 0; i < split.length && i < versionParts.length; i++) { 90 | versionParts[i] = Integer.parseInt(split[i]); 91 | } 92 | 93 | return versionParts; 94 | } 95 | 96 | private static String getVersionStringFromServer(String versionString) { 97 | Pattern versionPattern = Pattern.compile(VERSION_REGEX); 98 | Matcher versionMatcher = versionPattern.matcher(versionString); 99 | 100 | if (versionMatcher.matches() && versionMatcher.group(1) != null) { 101 | return versionMatcher.group(1); 102 | } 103 | 104 | //Couldn't extract the version 105 | throw new IllegalStateException("Cannot parse version String '" + versionString); 106 | } 107 | 108 | private static String stripVersionDetails(String version) { 109 | int end = version.indexOf('-'); 110 | if (end == -1) { 111 | end = version.length(); 112 | } 113 | 114 | return version.substring(0, end); 115 | } 116 | 117 | /** 118 | * Gets the major value 119 | * 120 | * @return the major value 121 | */ 122 | public int getMajor() { 123 | return major; 124 | } 125 | 126 | /** 127 | * Gets the minor value 128 | * 129 | * @return the minor value 130 | */ 131 | public int getMinor() { 132 | return minor; 133 | } 134 | 135 | /** 136 | * Gets the build value 137 | * 138 | * @return the build value 139 | */ 140 | public int getBuild() { 141 | return build; 142 | } 143 | 144 | @Override 145 | public int compareTo(Version other) { 146 | return ComparisonChain.start() 147 | .compare(major, other.major) 148 | .compare(minor, other.minor) 149 | .compare(build, other.build) 150 | .result(); 151 | } 152 | 153 | @Override 154 | public int hashCode() { 155 | return Objects.hash(major, minor, build); 156 | } 157 | 158 | @Override 159 | public boolean equals(Object obj) { 160 | //ignores also null 161 | if (obj instanceof Version) { 162 | Version other = (Version) obj; 163 | return major == other.major && minor == other.minor && build == other.build; 164 | } 165 | 166 | return false; 167 | } 168 | 169 | @Override 170 | public String toString() { 171 | return ToStringBuilder.reflectionToString(this); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/scoreboard/Score.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.scoreboard; 2 | 3 | /** 4 | * Represents a scoreboard score item or "line" 5 | */ 6 | public interface Score { 7 | 8 | /** 9 | * Get the current display name for the scoreboard score 10 | * 11 | * @return display name 12 | */ 13 | String getName(); 14 | 15 | /** 16 | * Score value that is displayed next to the item 17 | * 18 | * @return the current score 19 | */ 20 | int getScore(); 21 | } 22 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/DefaultReplacer.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * Represents that a replacer that is added to this plugin by this plugin itself. 11 | */ 12 | @Documented 13 | @Target(ElementType.TYPE) 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface DefaultReplacer { 16 | 17 | /** 18 | * Returns the required plugin that is needed to activate this variables. It defaults to 19 | * this scoreboard plugin itself. 20 | * 21 | * @return the required plugin dependency for this variables 22 | */ 23 | String plugin() default "ScoreboardStats"; 24 | 25 | /** 26 | * Minimum required version to activate the replacers that will be registered. The version format 27 | * have to be in semVer format major.minor.fix with 28 | *
29 | * 1.2.3 is higher than 1.1.3 30 | *
31 | * 2.5 higher than 1.0.0 32 | * 33 | * @return required version or empty for no required version 34 | */ 35 | String requiredVersion() default ""; 36 | } 37 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/DefaultReplacers.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import org.bukkit.plugin.Plugin; 4 | 5 | /** 6 | * Represents a default replacers instance that will register default variables. 7 | * 8 | * @param plugin class for easier access to the plugin field without casting 9 | */ 10 | public abstract class DefaultReplacers { 11 | 12 | protected final ReplacerAPI replaceManager; 13 | protected final T plugin; 14 | 15 | public DefaultReplacers(ReplacerAPI replaceManager, T plugin) { 16 | this.replaceManager = replaceManager; 17 | this.plugin = plugin; 18 | } 19 | 20 | /** 21 | * Register all variables that this class can manage 22 | */ 23 | public abstract void register(); 24 | 25 | /** 26 | * Shortcut method to register the variables without the boilerplate of adding the plugin instance and registering 27 | * it to the manager. 28 | * 29 | * @param variable variable name like "online" 30 | * @return the replacer responsible for this single variable 31 | */ 32 | protected Replacer register(String variable) { 33 | Replacer replacer = new Replacer(plugin, variable); 34 | replaceManager.register(replacer); 35 | return replacer; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/EventReplacer.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import com.google.common.collect.Sets; 4 | 5 | import java.util.Set; 6 | import java.util.function.Function; 7 | 8 | import org.bukkit.Bukkit; 9 | import org.bukkit.entity.Player; 10 | import org.bukkit.event.Event; 11 | import org.bukkit.event.player.PlayerEvent; 12 | 13 | /** 14 | * Manages the variable updating if an a specific event is fired. 15 | * 16 | * @param event class - this have to the exact type 17 | */ 18 | class EventReplacer { 19 | 20 | private final Replacer replacer; 21 | private final Class eventClass; 22 | private final Set> functions = Sets.newHashSet(); 23 | private final Set> scoreFunctions = Sets.newHashSet(); 24 | 25 | EventReplacer(Replacer replacer, Class eventClass) { 26 | this.replacer = replacer; 27 | this.eventClass = eventClass; 28 | } 29 | 30 | void addFct(Function fct) { 31 | functions.add(fct); 32 | } 33 | 34 | void addScoreFct(Function fct) { 35 | scoreFunctions.add(fct); 36 | } 37 | 38 | public Set> getFunctions() { 39 | return functions; 40 | } 41 | 42 | public Set> getScoreFunctions() { 43 | return scoreFunctions; 44 | } 45 | 46 | void execute(ReplacerAPI replaceManager, Event event) { 47 | executeUnsafe(replaceManager, eventClass.cast(event)); 48 | } 49 | 50 | private void executeUnsafe(ReplacerAPI replaceManager, T event) { 51 | String variable = replacer.getVariable(); 52 | for (Function function : scoreFunctions) { 53 | int newScore = function.apply(event); 54 | if (replacer.isGlobal()) { 55 | for (Player player : Bukkit.getOnlinePlayers()) { 56 | replaceManager.forceUpdate(player, variable, newScore); 57 | } 58 | } else if (event instanceof PlayerEvent) { 59 | replaceManager.forceUpdate(((PlayerEvent) event).getPlayer(), variable, newScore); 60 | } 61 | } 62 | } 63 | 64 | Class getEventClass() { 65 | return eventClass; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/PluginListener.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import org.bukkit.event.EventHandler; 4 | import org.bukkit.event.Listener; 5 | import org.bukkit.event.server.PluginDisableEvent; 6 | import org.bukkit.plugin.Plugin; 7 | 8 | /** 9 | * Keeps track of plugin disables and enables. It will register default replacers 10 | * back again or removes replacers of disabled plugins. 11 | */ 12 | class PluginListener implements Listener { 13 | 14 | private final ReplaceManager replaceManager; 15 | 16 | public PluginListener(ReplaceManager replaceManager) { 17 | this.replaceManager = replaceManager; 18 | } 19 | 20 | /** 21 | * Check for disabled plugin to remove the associated replacer 22 | * 23 | * @param disableEvent the disable event 24 | */ 25 | @EventHandler 26 | public void onPluginDisable(PluginDisableEvent disableEvent) { 27 | //Remove the listener if the associated plugin was disabled 28 | Plugin disablePlugin = disableEvent.getPlugin(); 29 | 30 | replaceManager.unregisterAll(disablePlugin); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/ReplaceManager.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import com.github.games647.scoreboardstats.BoardManager; 4 | import com.github.games647.scoreboardstats.Version; 5 | import com.google.common.collect.Sets; 6 | import com.google.common.reflect.ClassPath; 7 | import com.google.common.reflect.ClassPath.ClassInfo; 8 | 9 | import java.io.IOException; 10 | import java.lang.reflect.Constructor; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | import org.bukkit.Bukkit; 15 | import org.bukkit.entity.Player; 16 | import org.bukkit.plugin.Plugin; 17 | import org.slf4j.Logger; 18 | 19 | /** 20 | * Handling the replace management 21 | */ 22 | public class ReplaceManager extends ReplacerAPI { 23 | 24 | private static final String UNSUPPORTED_VERSION = "The Replacer: {} cannot be registered -" + 25 | " the plugin version isn't supported {} {}"; 26 | 27 | //todo: only temporarily 28 | @Deprecated 29 | private static ReplaceManager instance; 30 | private final Plugin plugin; 31 | private final BoardManager boardManager; 32 | 33 | /** 34 | * Creates a new replace manager 35 | * 36 | * @param scoreboardManager to manage the scoreboards 37 | * @param plugin ScoreboardStats plugin 38 | */ 39 | public ReplaceManager(BoardManager scoreboardManager, Plugin plugin, Logger logger) { 40 | super(logger); 41 | 42 | instance = this; 43 | 44 | this.plugin = plugin; 45 | this.boardManager = scoreboardManager; 46 | 47 | Bukkit.getPluginManager().registerEvents(new PluginListener(this), plugin); 48 | addDefaultReplacers(); 49 | } 50 | 51 | @Deprecated 52 | public static ReplaceManager getInstance() { 53 | return instance; 54 | } 55 | 56 | public void close() { 57 | instance = null; 58 | } 59 | 60 | @Override 61 | public void forceUpdate(Player player, String variable, int score) { 62 | boardManager.updateVariable(player, variable, score); 63 | } 64 | 65 | @Override 66 | public void forceUpdate(Player player, String variable, String value) { 67 | boardManager.updateVariable(player, variable, value); 68 | } 69 | 70 | public void updateGlobals() { 71 | replacers.values() 72 | .stream() 73 | .filter(Replacer::isGlobal) 74 | .filter(replacer -> !replacer.isEventVariable()) 75 | .forEach(replacer -> { 76 | int score = replacer.scoreReplace(null); 77 | String variable = replacer.getVariable(); 78 | Bukkit.getOnlinePlayers().forEach(player -> boardManager.updateVariable(player, variable, score)); 79 | }); 80 | } 81 | 82 | private void addDefaultReplacers() { 83 | Set defaultReplacers = Sets.newHashSet(); 84 | try { 85 | defaultReplacers = ClassPath.from(getClass().getClassLoader()) 86 | .getTopLevelClasses("com.github.games647.scoreboardstats.defaults") 87 | .stream() 88 | .map(ClassInfo::load) 89 | .filter(DefaultReplacers.class::isAssignableFrom) 90 | .map(clazz -> (Class>) clazz) 91 | .filter(this::registerDefault) 92 | .map(Class::getSimpleName) 93 | .collect(Collectors.toSet()); 94 | } catch (IOException ioEx) { 95 | logger.error("Failed to register replacers", ioEx); 96 | } 97 | 98 | logger.info("Registered default replacers: {}", defaultReplacers); 99 | } 100 | 101 | private boolean registerDefault(Class> replacerClass) { 102 | try { 103 | DefaultReplacer annotation = replacerClass.getAnnotation(DefaultReplacer.class); 104 | 105 | String replacerPluginName = annotation.plugin(); 106 | if (replacerPluginName.isEmpty()) { 107 | replacerPluginName = plugin.getName(); 108 | } 109 | 110 | Plugin replacerPlugin = Bukkit.getPluginManager().getPlugin(replacerPluginName); 111 | if (replacerPlugin != null) { 112 | String required = annotation.requiredVersion(); 113 | String version = replacerPlugin.getDescription().getVersion(); 114 | if (!required.isEmpty() && new Version(version).compareTo(new Version(required)) >= 0) { 115 | logger.info(UNSUPPORTED_VERSION, replacerClass.getSimpleName(), version, required); 116 | return false; 117 | } 118 | 119 | Constructor> cons = replacerClass.getConstructor(ReplacerAPI.class, Plugin.class); 120 | cons.newInstance(this, replacerPlugin).register(); 121 | } 122 | 123 | return true; 124 | } catch (Exception | LinkageError replacerException) { 125 | //only catch this throwable, because they could probably happened 126 | logger.warn("Cannot register replacer", replacerException); 127 | } 128 | 129 | return false; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/Replacer.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import com.google.common.base.Converter; 4 | import com.google.common.collect.Maps; 5 | import com.google.common.primitives.Ints; 6 | 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.function.Function; 10 | import java.util.function.IntSupplier; 11 | import java.util.function.Supplier; 12 | 13 | import org.bukkit.ChatColor; 14 | import org.bukkit.entity.Player; 15 | import org.bukkit.event.Event; 16 | import org.bukkit.plugin.Plugin; 17 | 18 | public class Replacer { 19 | 20 | private final Plugin plugin; 21 | private final String variable; 22 | 23 | private final Converter stringConverter = Ints.stringConverter(); 24 | private final Converter intConverter = stringConverter.reverse(); 25 | 26 | private final Map> eventsReplacers = Maps.newHashMap(); 27 | 28 | private boolean async; 29 | private boolean constant; 30 | private String description; 31 | private boolean global; 32 | 33 | private Function scoreSupplier; 34 | private Function supplier; 35 | 36 | public Replacer(Plugin plugin, String variable) { 37 | this.plugin = plugin; 38 | this.variable = variable; 39 | } 40 | 41 | public Replacer async() { 42 | this.async = true; 43 | return this; 44 | } 45 | 46 | public Replacer constant() { 47 | this.constant = true; 48 | return this; 49 | } 50 | 51 | public Replacer description(String description) { 52 | this.description = ChatColor.translateAlternateColorCodes('&', description); 53 | return this; 54 | } 55 | 56 | public Replacer supply(Supplier supplier) { 57 | this.global = true; 58 | supply(player -> supplier.get()); 59 | return this; 60 | } 61 | 62 | public Replacer scoreSupply(IntSupplier supplier) { 63 | this.global = true; 64 | scoreSupply(player -> supplier.getAsInt()); 65 | return this; 66 | } 67 | 68 | public Replacer supply(Function supplier) { 69 | this.supplier = supplier; 70 | this.scoreSupplier = player -> stringConverter.convert(supplier.apply(player)); 71 | 72 | return this; 73 | } 74 | 75 | public Replacer scoreSupply(Function supplier) { 76 | this.scoreSupplier = supplier; 77 | this.supplier = player -> intConverter.convert(supplier.apply(player)); 78 | 79 | return this; 80 | } 81 | 82 | public Replacer event(Class eventClass, Supplier supplier) { 83 | event(eventClass, event -> supplier.get()); 84 | return this; 85 | } 86 | 87 | public Replacer eventScore(Class eventClass, IntSupplier supplier) { 88 | eventScore(eventClass, event -> supplier.getAsInt()); 89 | return this; 90 | } 91 | 92 | public Replacer event(Class eventClass, Function function) { 93 | EventReplacer checkedReplacer = getEventReplacer(eventClass); 94 | checkedReplacer.addFct(function); 95 | return this; 96 | } 97 | 98 | public Replacer eventScore(Class eventClass, Function function) { 99 | EventReplacer checkedReplacer = getEventReplacer(eventClass); 100 | checkedReplacer.addScoreFct(function); 101 | return this; 102 | } 103 | 104 | private EventReplacer getEventReplacer(Class eventClass) { 105 | String eventName = eventClass.getCanonicalName(); 106 | EventReplacer replacer = eventsReplacers.get(eventName); 107 | 108 | EventReplacer checkedReplacer; 109 | if (replacer == null) { 110 | return new EventReplacer<>(this, eventClass); 111 | } 112 | 113 | return ((EventReplacer) replacer); 114 | } 115 | 116 | public String replace(Player player) { 117 | return supplier.apply(player); 118 | } 119 | 120 | public int scoreReplace(Player player) { 121 | return scoreSupplier.apply(player); 122 | } 123 | 124 | public String getVariable() { 125 | return variable; 126 | } 127 | 128 | public boolean isAsync() { 129 | return async; 130 | } 131 | 132 | public boolean isGlobal() { 133 | return global; 134 | } 135 | 136 | public boolean isConstant() { 137 | return constant; 138 | } 139 | 140 | public Plugin getPlugin() { 141 | return plugin; 142 | } 143 | 144 | public boolean isEventVariable() { 145 | return !eventsReplacers.isEmpty(); 146 | } 147 | 148 | public Optional getDescription() { 149 | return Optional.ofNullable(description); 150 | } 151 | 152 | protected Function getScoreSupplier() { 153 | return scoreSupplier; 154 | } 155 | 156 | protected Function getSupplier() { 157 | return supplier; 158 | } 159 | 160 | protected Map> getEventsReplacers() { 161 | return eventsReplacers; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/ReplacerAPI.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import com.google.common.collect.Maps; 4 | 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.OptionalInt; 9 | 10 | import org.bukkit.Bukkit; 11 | import org.bukkit.entity.Player; 12 | import org.bukkit.event.Event; 13 | import org.bukkit.event.Listener; 14 | import org.bukkit.plugin.EventExecutor; 15 | import org.bukkit.plugin.Plugin; 16 | import org.bukkit.plugin.PluginManager; 17 | import org.slf4j.Logger; 18 | 19 | import static org.bukkit.event.EventPriority.HIGHEST; 20 | 21 | public abstract class ReplacerAPI { 22 | 23 | protected final Logger logger; 24 | protected final Map replacers = Maps.newHashMap(); 25 | 26 | public ReplacerAPI(Logger logger) { 27 | this.logger = logger; 28 | } 29 | 30 | public void register(Replacer replacer) { 31 | replacers.put(replacer.getVariable(), replacer); 32 | 33 | for (EventReplacer eventReplacer : replacer.getEventsReplacers().values()) { 34 | Class eventClass = eventReplacer.getEventClass(); 35 | Plugin plugin = replacer.getPlugin(); 36 | 37 | EventExecutor executor = (listener, event) -> eventReplacer.execute(this, event); 38 | 39 | Listener listener = new Listener() {}; 40 | 41 | PluginManager pluginManager = Bukkit.getPluginManager(); 42 | pluginManager.registerEvent(eventClass, listener, HIGHEST, executor, plugin, true); 43 | } 44 | } 45 | 46 | public void unregister(String variable) { 47 | replacers.remove(variable); 48 | } 49 | 50 | public void unregisterAll(Plugin disablePlugin) { 51 | Iterator iterator = replacers.values().iterator(); 52 | while (iterator.hasNext()) { 53 | Plugin plugin = iterator.next().getPlugin(); 54 | if (plugin == disablePlugin) { 55 | iterator.remove(); 56 | } 57 | } 58 | } 59 | 60 | public void forceUpdate(String variable, int score) { 61 | Bukkit.getOnlinePlayers().forEach(player -> forceUpdate(player, variable, score)); 62 | } 63 | 64 | public void forceUpdate(String variable, String value) { 65 | Bukkit.getOnlinePlayers().forEach(player -> forceUpdate(player, variable, value)); 66 | } 67 | 68 | public abstract void forceUpdate(Player player, String variable, int score); 69 | 70 | public abstract void forceUpdate(Player player, String variable, String value); 71 | 72 | public Optional replace(Player player, String variable, boolean complete) throws ReplacerException { 73 | Replacer replacer = getReplacer(variable); 74 | if (!complete && replacer.isGlobal() || replacer.isEventVariable() || replacer.isConstant()) { 75 | return Optional.empty(); 76 | } 77 | 78 | try { 79 | return Optional.of(replacer.replace(player)); 80 | } catch (Exception ex) { 81 | throw new ReplacerException(ex); 82 | } 83 | } 84 | 85 | public OptionalInt scoreReplace(Player player, String variable, boolean complete) throws ReplacerException { 86 | Replacer replacer = getReplacer(variable); 87 | if (!complete && replacer.isGlobal() || replacer.isEventVariable() || replacer.isConstant()) { 88 | return OptionalInt.empty(); 89 | } 90 | 91 | try { 92 | return OptionalInt.of(replacer.scoreReplace(player)); 93 | } catch (Exception ex) { 94 | throw new ReplacerException(ex); 95 | } 96 | } 97 | 98 | private Replacer getReplacer(String variable) throws UnknownVariableException { 99 | Replacer replacer = this.replacers.get(variable); 100 | if (replacer == null) { 101 | throw new UnknownVariableException(variable); 102 | } 103 | 104 | return replacer; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/ReplacerException.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | public class ReplacerException extends Exception { 4 | 5 | public ReplacerException(Throwable cause) { 6 | super(cause); 7 | } 8 | 9 | public ReplacerException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/UnknownVariableException.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | /** 4 | * Represents an exception if a used variable can't be replaced by a replacer 5 | * So if no replacer know this variable 6 | */ 7 | public class UnknownVariableException extends ReplacerException { 8 | 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * Creates a new exception if the variable couldn't be replaced. 13 | * 14 | * @param variable variable 15 | */ 16 | public UnknownVariableException(String variable) { 17 | super("Unknown variable " + variable); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /variables/src/main/java/com/github/games647/scoreboardstats/variables/UnsupportedPluginException.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | public class UnsupportedPluginException extends ReplacerException { 4 | 5 | public UnsupportedPluginException(String pluginName, String expectedVersion, String currentVersion) { 6 | super(String.format("The version %s of plugin %s version isn't supported. We require at least %s", 7 | currentVersion, pluginName, expectedVersion)); 8 | } 9 | 10 | public UnsupportedPluginException(String message) { 11 | super(message); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /variables/src/test/java/com/github/games647/scoreboardstats/variables/ReplaceManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.github.games647.scoreboardstats.variables; 2 | 3 | import org.bukkit.Bukkit; 4 | import org.bukkit.plugin.Plugin; 5 | import org.bukkit.plugin.SimplePluginManager; 6 | import org.bukkit.plugin.messaging.StandardMessenger; 7 | import org.bukkit.scheduler.BukkitScheduler; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mockito; 11 | import org.powermock.api.mockito.PowerMockito; 12 | import org.powermock.core.classloader.annotations.PrepareForTest; 13 | import org.powermock.modules.junit4.PowerMockRunner; 14 | import org.slf4j.LoggerFactory; 15 | 16 | @PrepareForTest({Bukkit.class, SimplePluginManager.class, Plugin.class}) 17 | @RunWith(PowerMockRunner.class) 18 | public class ReplaceManagerTest { 19 | 20 | private static final String SAMPLE_VARIABLE = "sample"; 21 | 22 | @Test 23 | public void testUnregister() throws Exception { 24 | PowerMockito.mockStatic(Bukkit.class); 25 | Mockito.when(Bukkit.getPluginManager()).thenReturn(PowerMockito.mock(SimplePluginManager.class)); 26 | Mockito.when(Bukkit.getMessenger()).thenReturn(PowerMockito.mock(StandardMessenger.class)); 27 | Mockito.when(Bukkit.getScheduler()).thenReturn(PowerMockito.mock(BukkitScheduler.class)); 28 | 29 | Plugin plugin = PowerMockito.mock(Plugin.class); 30 | 31 | ReplaceManager replaceManager = new ReplaceManager(null, plugin, LoggerFactory.getLogger("test")); 32 | replaceManager.register(new Replacer(plugin, "test").scoreSupply(() -> 1)); 33 | } 34 | } 35 | --------------------------------------------------------------------------------