This commit is contained in:
okx-code
2018-08-23 17:40:24 +01:00
parent 044a8419d0
commit 32dc06cf62
31 changed files with 2795 additions and 0 deletions
+926
View File
@@ -0,0 +1,926 @@
package sh.okx.rankup;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import javax.net.ssl.HttpsURLConnection;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
* <p>
* Check out https://bStats.org/ to learn more about bStats!
*/
public class Metrics {
// The version of this bStats class
public static final int B_STATS_VERSION = 1;
// The url to which the data is sent
private static final String URL = "https://bStats.org/submitData";
// Should failed requests be logged?
private static boolean logFailedRequests;
// The uuid of the server
private static String serverUUID;
// The plugin
private final JavaPlugin plugin;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
/**
* Class constructor.
*
* @param plugin The plugin which stats should be submitted.
*/
public Metrics(JavaPlugin plugin) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null!");
}
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
// Check if the config file exists
if (!config.isSet("serverUuid")) {
// Add default values
config.addDefault("enabled", true);
// Every server gets it's unique random id.
config.addDefault("serverUuid", UUID.randomUUID().toString());
// Should failed request be logged?
config.addDefault("logFailedRequests", false);
// Inform the server owners about bStats
config.options().header(
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
"To honor their work, you should not disable it.\n" +
"This has nearly no effect on the server performance!\n" +
"Check out https://bStats.org/ to learn more :)"
).copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) {
}
}
// Load the data
serverUUID = config.getString("serverUuid");
logFailedRequests = config.getBoolean("logFailedRequests", false);
if (config.getBoolean("enabled", true)) {
boolean found = false;
// Search for all other bStats Metrics classes to see if we are the first one
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
found = true; // We aren't the first
break;
} catch (NoSuchFieldException ignored) {
}
}
// Register our service
Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
if (!found) {
// We are the first!
startSubmitting();
}
}
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
if (chart == null) {
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
}
/**
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (!plugin.isEnabled()) { // Plugin was disabled
timer.cancel();
return;
}
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
Bukkit.getScheduler().runTask(plugin, new Runnable() {
@Override
public void run() {
submitData();
}
});
}
}, 1000 * 60 * 5, 1000 * 60 * 30);
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
// WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!
}
/**
* Gets the plugin specific data.
* This method is called using Reflection.
*
* @return The plugin specific data.
*/
public JSONObject getPluginData() {
JSONObject data = new JSONObject();
String pluginName = plugin.getDescription().getName();
String pluginVersion = plugin.getDescription().getVersion();
data.put("pluginName", pluginName); // Append the name of the plugin
data.put("pluginVersion", pluginVersion); // Append the version of the plugin
JSONArray customCharts = new JSONArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
JSONObject chart = customChart.getRequestJsonObject();
if (chart == null) { // If the chart is null, we skip it
continue;
}
customCharts.add(chart);
}
data.put("customCharts", customCharts);
return data;
}
/**
* Gets the server specific data.
*
* @return The server specific data.
*/
private JSONObject getServerData() {
// Minecraft specific data
int playerAmount = Bukkit.getOnlinePlayers().size();
int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
String bukkitVersion = org.bukkit.Bukkit.getVersion();
bukkitVersion = bukkitVersion.substring(bukkitVersion.indexOf("MC: ") + 4, bukkitVersion.length() - 1);
// OS/Java specific data
String javaVersion = System.getProperty("java.version");
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String osVersion = System.getProperty("os.version");
int coreCount = Runtime.getRuntime().availableProcessors();
JSONObject data = new JSONObject();
data.put("serverUUID", serverUUID);
data.put("playerAmount", playerAmount);
data.put("onlineMode", onlineMode);
data.put("bukkitVersion", bukkitVersion);
data.put("javaVersion", javaVersion);
data.put("osName", osName);
data.put("osArch", osArch);
data.put("osVersion", osVersion);
data.put("coreCount", coreCount);
return data;
}
/**
* Collects the data and sends it afterwards.
*/
private void submitData() {
final JSONObject data = getServerData();
JSONArray pluginData = new JSONArray();
// Search for all other bStats Metrics classes to get their plugin data
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
} catch (NoSuchFieldException ignored) {
continue; // Continue "searching"
}
// Found one!
try {
pluginData.add(service.getMethod("getPluginData").invoke(Bukkit.getServicesManager().load(service)));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
}
}
data.put("plugins", pluginData);
// Create a new thread for the connection to the bStats server
new Thread(new Runnable() {
@Override
public void run() {
try {
// Send the data
sendData(data);
} catch (Exception e) {
// Something went wrong! :(
if (logFailedRequests) {
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
}
}
}
}).start();
}
/**
* Sends the data to the bStats server.
*
* @param data The data to send.
* @throws Exception If the request failed.
*/
private static void sendData(JSONObject data) throws Exception {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null!");
}
if (Bukkit.isPrimaryThread()) {
throw new IllegalAccessException("This method must not be called from the main thread!");
}
HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
// Add headers
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
// Send data
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.write(compressedData);
outputStream.flush();
outputStream.close();
connection.getInputStream().close(); // We don't care about the response - Just send our data :)
}
/**
* Gzips the given String.
*
* @param str The string to gzip.
* @return The gzipped String.
* @throws IOException If the compression failed.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
gzip.write(str.getBytes("UTF-8"));
gzip.close();
return outputStream.toByteArray();
}
/**
* Represents a custom chart.
*/
public static abstract class CustomChart {
// The id of the chart
protected final String chartId;
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public CustomChart(String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
protected JSONObject getRequestJsonObject() {
JSONObject chart = new JSONObject();
chart.put("chartId", chartId);
try {
JSONObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
chart.put("data", data);
} catch (Throwable t) {
if (logFailedRequests) {
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return chart;
}
protected abstract JSONObject getChartData();
}
/**
* Represents a custom simple pie.
*/
public static abstract class SimplePie extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public SimplePie(String chartId) {
super(chartId);
}
/**
* Gets the value of the pie.
*
* @return The value of the pie.
*/
public abstract String getValue();
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
String value = getValue();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
/**
* Represents a custom advanced pie.
*/
public static abstract class AdvancedPie extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public AdvancedPie(String chartId) {
super(chartId);
}
/**
* Gets the values of the pie.
*
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
* You don't have to create a map yourself!
* @return The values of the pie.
*/
public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.put(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom single line chart.
*/
public static abstract class SingleLineChart extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public SingleLineChart(String chartId) {
super(chartId);
}
/**
* Gets the value of the chart.
*
* @return The value of the chart.
*/
public abstract int getValue();
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
int value = getValue();
if (value == 0) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
/**
* Represents a custom multi line chart.
*/
public static abstract class MultiLineChart extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public MultiLineChart(String chartId) {
super(chartId);
}
/**
* Gets the values of the chart.
*
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
* You don't have to create a map yourself!
* @return The values of the chart.
*/
public abstract HashMap<String, Integer> getValues(HashMap<String, Integer> valueMap);
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
HashMap<String, Integer> map = getValues(new HashMap<String, Integer>());
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.put(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom simple map chart.
*/
public static abstract class SimpleMapChart extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public SimpleMapChart(String chartId) {
super(chartId);
}
/**
* Gets the value of the chart.
*
* @return The value of the chart.
*/
public abstract Country getValue();
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
Country value = getValue();
if (value == null) {
// Null = skip the chart
return null;
}
data.put("value", value.getCountryIsoTag());
return data;
}
}
/**
* Represents a custom advanced map chart.
*/
public static abstract class AdvancedMapChart extends CustomChart {
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
public AdvancedMapChart(String chartId) {
super(chartId);
}
/**
* Gets the value of the chart.
*
* @param valueMap Just an empty map. The only reason it exists is to make your life easier.
* You don't have to create a map yourself!
* @return The value of the chart.
*/
public abstract HashMap<Country, Integer> getValues(HashMap<Country, Integer> valueMap);
@Override
protected JSONObject getChartData() {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
HashMap<Country, Integer> map = getValues(new HashMap<Country, Integer>());
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<Country, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.put(entry.getKey().getCountryIsoTag(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* A enum which is used for custom maps.
*/
public enum Country {
/**
* bStats will use the country of the server.
*/
AUTO_DETECT("AUTO", "Auto Detected"),
ANDORRA("AD", "Andorra"),
UNITED_ARAB_EMIRATES("AE", "United Arab Emirates"),
AFGHANISTAN("AF", "Afghanistan"),
ANTIGUA_AND_BARBUDA("AG", "Antigua and Barbuda"),
ANGUILLA("AI", "Anguilla"),
ALBANIA("AL", "Albania"),
ARMENIA("AM", "Armenia"),
NETHERLANDS_ANTILLES("AN", "Netherlands Antilles"),
ANGOLA("AO", "Angola"),
ANTARCTICA("AQ", "Antarctica"),
ARGENTINA("AR", "Argentina"),
AMERICAN_SAMOA("AS", "American Samoa"),
AUSTRIA("AT", "Austria"),
AUSTRALIA("AU", "Australia"),
ARUBA("AW", "Aruba"),
ÅLAND_ISLANDS("AX", "Åland Islands"),
AZERBAIJAN("AZ", "Azerbaijan"),
BOSNIA_AND_HERZEGOVINA("BA", "Bosnia and Herzegovina"),
BARBADOS("BB", "Barbados"),
BANGLADESH("BD", "Bangladesh"),
BELGIUM("BE", "Belgium"),
BURKINA_FASO("BF", "Burkina Faso"),
BULGARIA("BG", "Bulgaria"),
BAHRAIN("BH", "Bahrain"),
BURUNDI("BI", "Burundi"),
BENIN("BJ", "Benin"),
SAINT_BARTHÉLEMY("BL", "Saint Barthélemy"),
BERMUDA("BM", "Bermuda"),
BRUNEI("BN", "Brunei"),
BOLIVIA("BO", "Bolivia"),
BONAIRE_SINT_EUSTATIUS_AND_SABA("BQ", "Bonaire, Sint Eustatius and Saba"),
BRAZIL("BR", "Brazil"),
BAHAMAS("BS", "Bahamas"),
BHUTAN("BT", "Bhutan"),
BOUVET_ISLAND("BV", "Bouvet Island"),
BOTSWANA("BW", "Botswana"),
BELARUS("BY", "Belarus"),
BELIZE("BZ", "Belize"),
CANADA("CA", "Canada"),
COCOS_ISLANDS("CC", "Cocos Islands"),
THE_DEMOCRATIC_REPUBLIC_OF_CONGO("CD", "The Democratic Republic Of Congo"),
CENTRAL_AFRICAN_REPUBLIC("CF", "Central African Republic"),
CONGO("CG", "Congo"),
SWITZERLAND("CH", "Switzerland"),
CÔTE_D_IVOIRE("CI", "Côte d'Ivoire"),
COOK_ISLANDS("CK", "Cook Islands"),
CHILE("CL", "Chile"),
CAMEROON("CM", "Cameroon"),
CHINA("CN", "China"),
COLOMBIA("CO", "Colombia"),
COSTA_RICA("CR", "Costa Rica"),
CUBA("CU", "Cuba"),
CAPE_VERDE("CV", "Cape Verde"),
CURAÇAO("CW", "Curaçao"),
CHRISTMAS_ISLAND("CX", "Christmas Island"),
CYPRUS("CY", "Cyprus"),
CZECH_REPUBLIC("CZ", "Czech Republic"),
GERMANY("DE", "Germany"),
DJIBOUTI("DJ", "Djibouti"),
DENMARK("DK", "Denmark"),
DOMINICA("DM", "Dominica"),
DOMINICAN_REPUBLIC("DO", "Dominican Republic"),
ALGERIA("DZ", "Algeria"),
ECUADOR("EC", "Ecuador"),
ESTONIA("EE", "Estonia"),
EGYPT("EG", "Egypt"),
WESTERN_SAHARA("EH", "Western Sahara"),
ERITREA("ER", "Eritrea"),
SPAIN("ES", "Spain"),
ETHIOPIA("ET", "Ethiopia"),
FINLAND("FI", "Finland"),
FIJI("FJ", "Fiji"),
FALKLAND_ISLANDS("FK", "Falkland Islands"),
MICRONESIA("FM", "Micronesia"),
FAROE_ISLANDS("FO", "Faroe Islands"),
FRANCE("FR", "France"),
GABON("GA", "Gabon"),
UNITED_KINGDOM("GB", "United Kingdom"),
GRENADA("GD", "Grenada"),
GEORGIA("GE", "Georgia"),
FRENCH_GUIANA("GF", "French Guiana"),
GUERNSEY("GG", "Guernsey"),
GHANA("GH", "Ghana"),
GIBRALTAR("GI", "Gibraltar"),
GREENLAND("GL", "Greenland"),
GAMBIA("GM", "Gambia"),
GUINEA("GN", "Guinea"),
GUADELOUPE("GP", "Guadeloupe"),
EQUATORIAL_GUINEA("GQ", "Equatorial Guinea"),
GREECE("GR", "Greece"),
SOUTH_GEORGIA_AND_THE_SOUTH_SANDWICH_ISLANDS("GS", "South Georgia And The South Sandwich Islands"),
GUATEMALA("GT", "Guatemala"),
GUAM("GU", "Guam"),
GUINEA_BISSAU("GW", "Guinea-Bissau"),
GUYANA("GY", "Guyana"),
HONG_KONG("HK", "Hong Kong"),
HEARD_ISLAND_AND_MCDONALD_ISLANDS("HM", "Heard Island And McDonald Islands"),
HONDURAS("HN", "Honduras"),
CROATIA("HR", "Croatia"),
HAITI("HT", "Haiti"),
HUNGARY("HU", "Hungary"),
INDONESIA("ID", "Indonesia"),
IRELAND("IE", "Ireland"),
ISRAEL("IL", "Israel"),
ISLE_OF_MAN("IM", "Isle Of Man"),
INDIA("IN", "India"),
BRITISH_INDIAN_OCEAN_TERRITORY("IO", "British Indian Ocean Territory"),
IRAQ("IQ", "Iraq"),
IRAN("IR", "Iran"),
ICELAND("IS", "Iceland"),
ITALY("IT", "Italy"),
JERSEY("JE", "Jersey"),
JAMAICA("JM", "Jamaica"),
JORDAN("JO", "Jordan"),
JAPAN("JP", "Japan"),
KENYA("KE", "Kenya"),
KYRGYZSTAN("KG", "Kyrgyzstan"),
CAMBODIA("KH", "Cambodia"),
KIRIBATI("KI", "Kiribati"),
COMOROS("KM", "Comoros"),
SAINT_KITTS_AND_NEVIS("KN", "Saint Kitts And Nevis"),
NORTH_KOREA("KP", "North Korea"),
SOUTH_KOREA("KR", "South Korea"),
KUWAIT("KW", "Kuwait"),
CAYMAN_ISLANDS("KY", "Cayman Islands"),
KAZAKHSTAN("KZ", "Kazakhstan"),
LAOS("LA", "Laos"),
LEBANON("LB", "Lebanon"),
SAINT_LUCIA("LC", "Saint Lucia"),
LIECHTENSTEIN("LI", "Liechtenstein"),
SRI_LANKA("LK", "Sri Lanka"),
LIBERIA("LR", "Liberia"),
LESOTHO("LS", "Lesotho"),
LITHUANIA("LT", "Lithuania"),
LUXEMBOURG("LU", "Luxembourg"),
LATVIA("LV", "Latvia"),
LIBYA("LY", "Libya"),
MOROCCO("MA", "Morocco"),
MONACO("MC", "Monaco"),
MOLDOVA("MD", "Moldova"),
MONTENEGRO("ME", "Montenegro"),
SAINT_MARTIN("MF", "Saint Martin"),
MADAGASCAR("MG", "Madagascar"),
MARSHALL_ISLANDS("MH", "Marshall Islands"),
MACEDONIA("MK", "Macedonia"),
MALI("ML", "Mali"),
MYANMAR("MM", "Myanmar"),
MONGOLIA("MN", "Mongolia"),
MACAO("MO", "Macao"),
NORTHERN_MARIANA_ISLANDS("MP", "Northern Mariana Islands"),
MARTINIQUE("MQ", "Martinique"),
MAURITANIA("MR", "Mauritania"),
MONTSERRAT("MS", "Montserrat"),
MALTA("MT", "Malta"),
MAURITIUS("MU", "Mauritius"),
MALDIVES("MV", "Maldives"),
MALAWI("MW", "Malawi"),
MEXICO("MX", "Mexico"),
MALAYSIA("MY", "Malaysia"),
MOZAMBIQUE("MZ", "Mozambique"),
NAMIBIA("NA", "Namibia"),
NEW_CALEDONIA("NC", "New Caledonia"),
NIGER("NE", "Niger"),
NORFOLK_ISLAND("NF", "Norfolk Island"),
NIGERIA("NG", "Nigeria"),
NICARAGUA("NI", "Nicaragua"),
NETHERLANDS("NL", "Netherlands"),
NORWAY("NO", "Norway"),
NEPAL("NP", "Nepal"),
NAURU("NR", "Nauru"),
NIUE("NU", "Niue"),
NEW_ZEALAND("NZ", "New Zealand"),
OMAN("OM", "Oman"),
PANAMA("PA", "Panama"),
PERU("PE", "Peru"),
FRENCH_POLYNESIA("PF", "French Polynesia"),
PAPUA_NEW_GUINEA("PG", "Papua New Guinea"),
PHILIPPINES("PH", "Philippines"),
PAKISTAN("PK", "Pakistan"),
POLAND("PL", "Poland"),
SAINT_PIERRE_AND_MIQUELON("PM", "Saint Pierre And Miquelon"),
PITCAIRN("PN", "Pitcairn"),
PUERTO_RICO("PR", "Puerto Rico"),
PALESTINE("PS", "Palestine"),
PORTUGAL("PT", "Portugal"),
PALAU("PW", "Palau"),
PARAGUAY("PY", "Paraguay"),
QATAR("QA", "Qatar"),
REUNION("RE", "Reunion"),
ROMANIA("RO", "Romania"),
SERBIA("RS", "Serbia"),
RUSSIA("RU", "Russia"),
RWANDA("RW", "Rwanda"),
SAUDI_ARABIA("SA", "Saudi Arabia"),
SOLOMON_ISLANDS("SB", "Solomon Islands"),
SEYCHELLES("SC", "Seychelles"),
SUDAN("SD", "Sudan"),
SWEDEN("SE", "Sweden"),
SINGAPORE("SG", "Singapore"),
SAINT_HELENA("SH", "Saint Helena"),
SLOVENIA("SI", "Slovenia"),
SVALBARD_AND_JAN_MAYEN("SJ", "Svalbard And Jan Mayen"),
SLOVAKIA("SK", "Slovakia"),
SIERRA_LEONE("SL", "Sierra Leone"),
SAN_MARINO("SM", "San Marino"),
SENEGAL("SN", "Senegal"),
SOMALIA("SO", "Somalia"),
SURINAME("SR", "Suriname"),
SOUTH_SUDAN("SS", "South Sudan"),
SAO_TOME_AND_PRINCIPE("ST", "Sao Tome And Principe"),
EL_SALVADOR("SV", "El Salvador"),
SINT_MAARTEN_DUTCH_PART("SX", "Sint Maarten (Dutch part)"),
SYRIA("SY", "Syria"),
SWAZILAND("SZ", "Swaziland"),
TURKS_AND_CAICOS_ISLANDS("TC", "Turks And Caicos Islands"),
CHAD("TD", "Chad"),
FRENCH_SOUTHERN_TERRITORIES("TF", "French Southern Territories"),
TOGO("TG", "Togo"),
THAILAND("TH", "Thailand"),
TAJIKISTAN("TJ", "Tajikistan"),
TOKELAU("TK", "Tokelau"),
TIMOR_LESTE("TL", "Timor-Leste"),
TURKMENISTAN("TM", "Turkmenistan"),
TUNISIA("TN", "Tunisia"),
TONGA("TO", "Tonga"),
TURKEY("TR", "Turkey"),
TRINIDAD_AND_TOBAGO("TT", "Trinidad and Tobago"),
TUVALU("TV", "Tuvalu"),
TAIWAN("TW", "Taiwan"),
TANZANIA("TZ", "Tanzania"),
UKRAINE("UA", "Ukraine"),
UGANDA("UG", "Uganda"),
UNITED_STATES_MINOR_OUTLYING_ISLANDS("UM", "United States Minor Outlying Islands"),
UNITED_STATES("US", "United States"),
URUGUAY("UY", "Uruguay"),
UZBEKISTAN("UZ", "Uzbekistan"),
VATICAN("VA", "Vatican"),
SAINT_VINCENT_AND_THE_GRENADINES("VC", "Saint Vincent And The Grenadines"),
VENEZUELA("VE", "Venezuela"),
BRITISH_VIRGIN_ISLANDS("VG", "British Virgin Islands"),
U_S__VIRGIN_ISLANDS("VI", "U.S. Virgin Islands"),
VIETNAM("VN", "Vietnam"),
VANUATU("VU", "Vanuatu"),
WALLIS_AND_FUTUNA("WF", "Wallis And Futuna"),
SAMOA("WS", "Samoa"),
YEMEN("YE", "Yemen"),
MAYOTTE("YT", "Mayotte"),
SOUTH_AFRICA("ZA", "South Africa"),
ZAMBIA("ZM", "Zambia"),
ZIMBABWE("ZW", "Zimbabwe");
private String isoTag;
private String name;
Country(String isoTag, String name) {
this.isoTag = isoTag;
this.name = name;
}
/**
* Gets the name of the country.
*
* @return The name of the country.
*/
public String getCountryName() {
return name;
}
/**
* Gets the iso tag of the country.
*
* @return The iso tag of the country.
*/
public String getCountryIsoTag() {
return isoTag;
}
/**
* Gets a country by it's iso tag.
*
* @param isoTag The iso tag of the county.
* @return The country with the given iso tag or <code>null</code> if unknown.
*/
public static Country byIsoTag(String isoTag) {
for (Country country : Country.values()) {
if (country.getCountryIsoTag().equals(isoTag)) {
return country;
}
}
return null;
}
/**
* Gets a country by a locale.
*
* @param locale The locale.
* @return The country from the giben locale or <code>null</code> if unknown country or
* if the locale does not contain a country.
*/
public static Country byLocale(Locale locale) {
return byIsoTag(locale.getCountry());
}
}
}
+220
View File
@@ -0,0 +1,220 @@
package sh.okx.rankup;
import lombok.Getter;
import me.clip.placeholderapi.PlaceholderAPI;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.InventoryView;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import sh.okx.rankup.commands.InfoCommand;
import sh.okx.rankup.commands.RankListCommand;
import sh.okx.rankup.commands.RankupCommand;
import sh.okx.rankup.gui.Gui;
import sh.okx.rankup.gui.GuiListener;
import sh.okx.rankup.messages.Message;
import sh.okx.rankup.messages.MessageBuilder;
import sh.okx.rankup.messages.Variable;
import sh.okx.rankup.placeholders.Placeholders;
import sh.okx.rankup.ranks.Rank;
import sh.okx.rankup.ranks.Rankups;
import sh.okx.rankup.ranks.requirements.PlaytimeHoursRequirement;
import sh.okx.rankup.ranks.requirements.XpLevelRequirement;
import sh.okx.rankup.ranks.requirements.MoneyRequirement;
import sh.okx.rankup.ranks.requirements.RequirementRegistry;
import java.io.File;
public class Rankup extends JavaPlugin {
@Getter
private Permission permissions;
@Getter
private Economy economy;
/**
* The registry for listing the requirements to /rankup.
*/
@Getter
private RequirementRegistry requirementRegistry = new RequirementRegistry();
@Getter
private FileConfiguration messages;
@Getter
private FileConfiguration config;
@Getter
private Rankups rankups;
@Getter
private Placeholders placeholders;
@Override
public void onEnable() {
registerRequirements();
setupPermissions();
setupEconomy();
reload();
Metrics metrics = new Metrics(this);
metrics.addCustomChart(new Metrics.SimplePie("confirmation") {
@Override
public String getValue() {
return getConfig().getString("confirmation.type");
}
});
getCommand("rankup").setExecutor(new RankupCommand(this));
getCommand("rankup3").setExecutor(new InfoCommand(this));
getCommand("ranks").setExecutor(new RankListCommand(this));
getServer().getPluginManager().registerEvents(new GuiListener(this), this);
}
@Override
public void onDisable() {
closeInventories();
PlaceholderAPI.unregisterExpansion(placeholders);
}
public void reload() {
closeInventories();
loadConfigs();
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
placeholders = new Placeholders(this);
placeholders.register();
}
if(config.getInt("version") != YamlConfiguration.loadConfiguration(getTextResource("config.yml")).getInt("version")) {
getLogger().severe("You are using an outdated config!");
getLogger().severe("This means that some things might not work!");
getLogger().severe("To update, please rename your config files (or the folder they are in),");
getLogger().severe("and run /rankup3 reload to generate a new config file.");
getLogger().severe("If that does not work, restart your server.");
getLogger().severe("You may then copy in your config values from the old config.");
getLogger().severe("Check the changelog on the Rankup spigot page to see the changes.");
}
}
/**
* Closes all rankup inventories on disable
* so players cannot grab items from the inventory
* on a plugin reload.
*/
private void closeInventories() {
for(Player player : Bukkit.getOnlinePlayers()) {
InventoryView view = player.getOpenInventory();
if(view.getType() == InventoryType.CHEST
&& view.getTopInventory().getHolder() instanceof Gui) {
player.closeInventory();
}
}
}
private void loadConfigs() {
messages = loadConfig("messages.yml");
config = loadConfig("config.yml");
rankups = new Rankups(this, loadConfig("rankups.yml"));
}
private FileConfiguration loadConfig(String name) {
File file = new File(getDataFolder(), name);
if (!file.exists()) {
saveResource(name, false);
}
return YamlConfiguration.loadConfiguration(file);
}
private void registerRequirements() {
requirementRegistry.addRequirement(new MoneyRequirement(this, "money"));
requirementRegistry.addRequirement(new XpLevelRequirement(this, "xp-level"));
requirementRegistry.addRequirement(new PlaytimeHoursRequirement(this, "playtime-hours"));
}
private void setupPermissions() {
RegisteredServiceProvider<Permission> rsp = getServer().getServicesManager().getRegistration(Permission.class);
permissions = rsp.getProvider();
}
private void setupEconomy() {
RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
if (rsp != null) {
economy = rsp.getProvider();
} else {
getLogger().warning("No economy found.");
}
}
public MessageBuilder getMessage(Rank rank, Message message) {
ConfigurationSection messages = rankups.getConfig()
.getConfigurationSection(rank.getName());
if(messages == null || !messages.isSet(message.getName())) {
messages = this.messages;
}
return MessageBuilder.of(messages, message);
}
public MessageBuilder getMessage(Message message) {
return MessageBuilder.of(messages, message);
}
public void rankup(Player player) {
if(!checkRankup(player)) {
return;
}
Rank oldRank = rankups.getRank(player);
Rank rank = rankups.nextRank(oldRank);
oldRank.applyRequirements(player);
permissions.playerRemoveGroup(null, player, oldRank.getRank());
permissions.playerAddGroup(null, player, rank.getRank());
getMessage(oldRank, Message.SUCCESS_PUBLIC)
.failIfEmpty()
.replaceAll(player, oldRank, rank)
.broadcast();
getMessage(oldRank, Message.SUCCESS_PRIVATE)
.failIfEmpty()
.replaceAll(player, oldRank, rank)
.send(player);
oldRank.runCommands(player, rank);
}
/**
* Checks if a player can rankup,
* and if they can't, sends the player a message and returns false
* @param player the player to check if they can rankup
* @return true if the player can rankup, false otherwise
*/
public boolean checkRankup(Player player) {
Rank rank = rankups.getRank(player);
if (rank == null) { // check if in ladder
getMessage(Message.NOT_IN_LADDER)
.replace(Variable.PLAYER, player.getName())
.send(player);
return false;
} else if (rank.isLastRank()) { // check if they are at the highest rank
getMessage(rank, Message.NO_RANKUP)
.replaceAll(player, rank)
.send(player);
return false;
} else if (!rank.checkRequirements(player)) { // check if they can afford it
MessageBuilder builder =
getMessage(rank, Message.REQUIREMENTS_NOT_MET)
.replaceAll(player, rank);
if (economy != null) {
double balance = economy.getBalance(player);
builder = builder
.replace(Variable.MONEY, balance)
.replace(Variable.MONEY_NEEDED, rank.getRequirement("money").getAmount() - balance);
}
builder.send(player);
return false;
}
return true;
}
}
@@ -0,0 +1,72 @@
package sh.okx.rankup.commands;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.PluginDescriptionFile;
import sh.okx.rankup.Rankup;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
@RequiredArgsConstructor
public class InfoCommand implements CommandExecutor {
private String versionMessage;
private final Rankup plugin;
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(args.length > 0) {
if(args[0].equalsIgnoreCase("reload") && sender.hasPermission("rankup.reload")) {
plugin.reload();
sender.sendMessage(ChatColor.GREEN + "" + ChatColor.BOLD + "Rankup " + ChatColor.YELLOW + "Reloaded configuration files.");
return true;
}
}
PluginDescriptionFile description = plugin.getDescription();
sender.sendMessage(
ChatColor.GREEN + "" + ChatColor.BOLD + description.getName() + " " + description.getVersion() +
ChatColor.YELLOW + " by " + ChatColor.BLUE + ChatColor.BOLD + String.join(", ", description.getAuthors()));
if(sender.hasPermission("rankup.reload")) {
sender.sendMessage(ChatColor.GREEN + "/" + label + " reload " + ChatColor.YELLOW + "Reloads configuration files.");
}
if(sender.hasPermission("rankup.checkversion")) {
if(versionMessage == null) {
sender.sendMessage(ChatColor.YELLOW + "Checking version...");
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
String message;
try {
String latest = getLatestVersion();
if (description.getVersion().equals(latest)) {
message = ChatColor.GREEN + "You are on the latest version.";
} else {
message = ChatColor.YELLOW + "A new version is available: " + ChatColor.GOLD + latest
+ "\nhttps://www.spigotmc.org/resources/rankup.17933/";
}
} catch (IOException e) {
message = ChatColor.RED + "Error while checking version.";
}
versionMessage = message;
Bukkit.getScheduler().runTask(plugin, () -> sender.sendMessage(versionMessage));
});
} else {
sender.sendMessage(versionMessage);
}
}
return true;
}
public String getLatestVersion() throws IOException {
URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=17933");
String result = CharStreams.toString(new InputStreamReader(url.openStream(), Charsets.UTF_8));
return result;
}
}
@@ -0,0 +1,95 @@
package sh.okx.rankup.commands;
import lombok.RequiredArgsConstructor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
import sh.okx.rankup.messages.Message;
import sh.okx.rankup.messages.MessageBuilder;
import sh.okx.rankup.messages.Variable;
import sh.okx.rankup.ranks.Rank;
import sh.okx.rankup.ranks.Rankups;
import sh.okx.rankup.ranks.requirements.Requirement;
import java.text.DecimalFormat;
@RequiredArgsConstructor
public class RankListCommand implements CommandExecutor {
private final Rankup plugin;
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
Rankups rankups = plugin.getRankups();
Rank playerRank = null;
if(sender instanceof Player) {
playerRank = rankups.getRank((Player) sender);
}
sendHeaderFooter(sender, playerRank, Message.RANKS_HEADER);
int state = playerRank == null ? 2 : 0;
Rank rank = rankups.getFirstRank();
do {
Rank next = rankups.nextRank(rank);
if(rank.equals(playerRank)) {
sendMessage(sender, 1, rank, next);
state = 2;
} else {
sendMessage(sender, state, rank, next);
}
rank = next;
} while(!rank.isLastRank());
sendHeaderFooter(sender, playerRank, Message.RANKS_FOOTER);
return true;
}
private void sendHeaderFooter(CommandSender sender, Rank rank, Message type) {
MessageBuilder builder = plugin.getMessage(type)
.failIfEmpty();
if(rank == null) {
builder.replace(Variable.PLAYER, sender.getName());
} else {
builder.replaceAll(sender, rank);
}
builder.send(sender);
}
private void sendMessage(CommandSender player, int state, Rank oldRank, Rank rank) {
if(state == 0) {
replaceCost(plugin.getMessage(oldRank, Message.RANKS_COMPLETE)
.replaceAll(player, oldRank, rank), player, oldRank)
.send(player);
} else if(state == 1) {
replaceCost(plugin.getMessage(oldRank, Message.RANKS_CURRENT)
.replaceAll(player, oldRank, rank), player, oldRank)
.send(player);
} else if(state == 2) {
replaceCost(plugin.getMessage(oldRank, Message.RANKS_INCOMPLETE)
.replaceAll(player, oldRank, rank), player, oldRank)
.send(player);
}
}
private MessageBuilder replaceCost(MessageBuilder builder, CommandSender sender, Rank rank) {
Requirement money = rank.getRequirement("money");
if(money == null || plugin.getEconomy() == null) {
return builder;
}
double amount;
if(sender instanceof Player && rank.isInRank((Player) sender)) {
amount = money.getRemaining((Player) sender);
} else {
amount = money.getAmount();
}
DecimalFormat moneyFormat = plugin.getPlaceholders().getMoneyFormat();
DecimalFormat percentFormat = plugin.getPlaceholders().getPercentFormat();
return builder
.replace(Variable.MONEY_NEEDED, moneyFormat.format(amount))
.replace(Variable.PERCENT_LEFT, percentFormat.format((amount / money.getAmount()) * 100))
.replace(Variable.PERCENT_DONE, percentFormat.format((1-(amount / money.getAmount())) * 100))
.replace(Variable.MONEY, moneyFormat.format(money.getAmount()));
}
}
@@ -0,0 +1,72 @@
package sh.okx.rankup.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
import sh.okx.rankup.gui.Gui;
import sh.okx.rankup.messages.Message;
import sh.okx.rankup.messages.Variable;
import sh.okx.rankup.ranks.Rank;
import sh.okx.rankup.ranks.Rankups;
import java.util.Map;
import java.util.WeakHashMap;
public class RankupCommand implements CommandExecutor {
private final Map<Player, Long> confirming = new WeakHashMap<>();
private final Rankup plugin;
public RankupCommand(Rankup plugin) {
this.plugin = plugin;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
// check if player
if (!(sender instanceof Player)) {
return false;
}
Player player = (Player) sender;
Rankups rankups = plugin.getRankups();
Rank rank = rankups.getRank(player);
if (!plugin.checkRankup(player)) {
return true;
}
FileConfiguration config = plugin.getConfig();
String confirmationType = config.getString("confirmation-type").toLowerCase();
// if they are on text confirming, rank them up
if (confirmationType.equals("text") && confirming.containsKey(player)) {
long time = System.currentTimeMillis() - confirming.remove(player);
if (time < config.getInt("text.timeout") * 1000) {
plugin.rankup(player);
return true;
}
}
switch (confirmationType) {
case "text":
confirming.put(player, System.currentTimeMillis());
plugin.getMessage(rank, Message.CONFIRMATION)
.replace(Variable.PLAYER, player.getName())
.replace(Variable.RANK, rank.getRank())
.replace(Variable.RANK_NAME, rank.getName())
.send(player);
break;
case "gui":
Gui.of(player, rank, rankups.nextRank(rank), plugin).open(player);
break;
case "none":
plugin.rankup(player);
break;
default:
throw new IllegalArgumentException("Invalid confirmation type " + confirmationType);
}
return true;
}
}
+121
View File
@@ -0,0 +1,121 @@
package sh.okx.rankup.gui;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import sh.okx.rankup.Rankup;
import sh.okx.rankup.messages.Message;
import sh.okx.rankup.messages.MessageBuilder;
import sh.okx.rankup.messages.Variable;
import sh.okx.rankup.ranks.Rank;
import java.util.Arrays;
import java.util.stream.Collectors;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Gui implements InventoryHolder {
@Getter
private Inventory inventory;
@Getter
private ItemStack rankup;
@Getter
private ItemStack cancel;
public void open(Player player) {
player.openInventory(inventory);
}
public static Gui of(Player player, Rank oldRank, Rank rank, Rankup plugin) {
ConfigurationSection config = plugin.getConfig().getConfigurationSection("gui");
ItemStack[] items = new ItemStack[config.getInt("rows") * 9];
addItem(items, config.getConfigurationSection("rankup"), player, oldRank, rank);
addItem(items, config.getConfigurationSection("cancel"), player, oldRank, rank);
addItem(items, config.getConfigurationSection("fill"), player, oldRank, rank);
Gui gui = new Gui();
gui.rankup = getItem(config.getConfigurationSection("rankup"), player, oldRank, rank);
gui.cancel = getItem(config.getConfigurationSection("cancel"), player, oldRank, rank);
Inventory inventory = Bukkit.createInventory(gui,
items.length,
plugin.getMessage(rank, Message.TITLE)
.replaceAll(player, oldRank, rank)
.toString());
inventory.setContents(items);
gui.inventory = inventory;
return gui;
}
private static ItemStack getItem(ConfigurationSection section, Player player, Rank oldRank, Rank rank) {
boolean legacy = !Bukkit.getVersion().contains("1.13");
String materialName = section.getString("material").toUpperCase();
// handle default material correctly on older versions
if (legacy && materialName.equals("BLACK_STAINED_GLASS_PANE")) {
materialName = "STAINED_GLASS_PANE:15";
}
ItemStack item;
if (legacy) {
String[] parts = materialName.split(":");
Material material = Material.valueOf(parts[0]);
short type = parts.length > 1 ? Short.parseShort(parts[1]) : 0;
item = new ItemStack(material, 1, type);
} else {
Material material = Material.valueOf(materialName);
item = new ItemStack(material);
}
ItemMeta meta = item.getItemMeta();
if (section.contains("lore")) {
meta.setLore(Arrays.stream(format(section.getString("lore"), player, oldRank, rank).split("\n"))
.map(string -> ChatColor.RESET + string)
.collect(Collectors.toList()));
}
if (section.contains("name")) {
meta.setDisplayName(ChatColor.RESET + format(section.getString("name"), player, oldRank, rank));
}
item.setItemMeta(meta);
return item;
}
private static String format(String message, Player player, Rank oldRank, Rank rank) {
return new MessageBuilder(ChatColor.translateAlternateColorCodes('&', message))
.replaceAll(player, oldRank, rank)
.toString();
}
private static void addItem(ItemStack[] items, ConfigurationSection section, Player player, Rank oldRank, Rank rank) {
ItemStack item = getItem(section, player, oldRank, rank);
if (section.getName().equalsIgnoreCase("fill")) {
for (int i = 0; i < items.length; i++) {
if (items[i] == null) {
items[i] = item;
}
}
return;
}
String[] locations = section.getString("index").split(" ");
for (String location : locations) {
String[] parts = location.split("-");
if (parts.length == 1) {
items[Integer.parseInt(parts[0])] = item;
} else {
for (int i = Integer.parseInt(parts[0]); i <= Integer.parseInt(parts[1]); i++) {
items[i] = item;
}
}
}
}
}
@@ -0,0 +1,36 @@
package sh.okx.rankup.gui;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import sh.okx.rankup.Rankup;
@RequiredArgsConstructor
public class GuiListener implements Listener {
private final Rankup plugin;
@EventHandler
public void on(InventoryClickEvent e) {
Inventory inventory = e.getInventory();
if (inventory == null
|| !(inventory.getHolder() instanceof Gui)
|| !e.getInventory().equals(e.getClickedInventory())) {
return;
}
e.setCancelled(true);
Player player = (Player) e.getWhoClicked();
Gui gui = (Gui) inventory.getHolder();
if (gui.getRankup().isSimilar(e.getCurrentItem())) {
Bukkit.getScheduler().runTask(plugin, player::closeInventory);
plugin.rankup(player);
} else if (gui.getCancel().isSimilar(e.getCurrentItem())) {
Bukkit.getScheduler().runTask(plugin, player::closeInventory);
}
}
}
@@ -0,0 +1,32 @@
package sh.okx.rankup.messages;
import org.bukkit.command.CommandSender;
class EmptyMessageBuilder extends MessageBuilder {
EmptyMessageBuilder() {
super(null);
}
@Override
public MessageBuilder failIfEmpty() {
throw new UnsupportedOperationException();
}
@Override
public MessageBuilder replace(Variable variable, Object value) {
return this;
}
@Override
public void send(CommandSender sender) {
}
@Override
public void broadcast() {
}
@Override
public String toString() {
return null;
}
}
@@ -0,0 +1,25 @@
package sh.okx.rankup.messages;
import lombok.Getter;
public enum Message {
NOT_IN_LADDER("not-in-ladder"),
REQUIREMENTS_NOT_MET("rankup.requirements-not-met"),
NO_RANKUP("rankup.no-rankup"),
SUCCESS_PUBLIC("rankup.success-public"),
SUCCESS_PRIVATE("rankup.success-private"),
CONFIRMATION("rankup.confirmation"),
TITLE("rankup.title"),
RANKS_HEADER("ranks.header"),
RANKS_FOOTER("ranks.footer"),
RANKS_COMPLETE("rankup.ranks.complete"),
RANKS_CURRENT("rankup.ranks.current"),
RANKS_INCOMPLETE("rankup.ranks.incomplete");
@Getter
private final String name;
Message(String name) {
this.name = name;
}
}
@@ -0,0 +1,103 @@
package sh.okx.rankup.messages;
import lombok.AllArgsConstructor;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import sh.okx.rankup.ranks.Rank;
import sh.okx.rankup.ranks.requirements.Requirement;
import java.text.DecimalFormat;
public class MessageBuilder {
private String message;
public MessageBuilder(String message) {
this.message = message;
}
public static MessageBuilder of(ConfigurationSection config, Message message) {
return new MessageBuilder(ChatColor.translateAlternateColorCodes('&', config.getString(message.getName())));
}
public MessageBuilder replace(Variable variable, Object value) {
this.message = variable.replace(message, String.valueOf(value));
return this;
}
public MessageBuilder replaceAll(CommandSender player, Rank rank) {
replace(Variable.PLAYER, player.getName());
replaceAll(rank);
return this;
}
public MessageBuilder replaceAll(CommandSender player, Rank oldRank, Rank rank) {
replace(Variable.PLAYER, player.getName());
replaceAll(oldRank, rank);
return this;
}
public MessageBuilder replaceAll(Rank rank) {
replace(Variable.RANK, rank.getRank());
replace(Variable.RANK_NAME, rank.getName());
return this;
}
public MessageBuilder replaceAll(Rank oldRank, Rank rank) {
replace(Variable.RANK, rank.getRank());
replace(Variable.RANK_NAME, rank.getName());
replace(Variable.OLD_RANK, oldRank.getRank());
replace(Variable.OLD_RANK_NAME, oldRank.getName());
return this;
}
public MessageBuilder replaceCost(CommandSender sender, Economy economy, Rank rank) {
Requirement money = rank.getRequirement("money");
if(money == null || economy == null) {
return this;
}
replace(Variable.MONEY, money.getAmount());
if(sender instanceof Player && rank.isInRank((Player) sender)) {
replace(Variable.MONEY_NEEDED, money.getRemaining((Player) sender));
} else {
replace(Variable.MONEY_NEEDED, money.getAmount());
}
return this;
}
/**
* Fails the MessageBuilder if the message is empty.
* if this fails, all subsequent calls to that MessageBuilder will do nothing
* @return an EmptyMessageBuilder if the message is empty, itself otherwise
*/
public MessageBuilder failIfEmpty() {
if(message.isEmpty()) {
return new EmptyMessageBuilder();
} else {
return this;
}
}
public void send(CommandSender sender) {
sender.sendMessage(message);
}
/**
* Sends the message to all players
* ie, calls MessageBuilder#send(Player) for all players online, and sends the message in the console.
*/
public void broadcast() {
for(Player player : Bukkit.getOnlinePlayers()) {
send(player);
}
send(Bukkit.getConsoleSender());
}
@Override
public String toString() {
return message;
}
}
@@ -0,0 +1,31 @@
package sh.okx.rankup.messages;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public enum Variable {
PLAYER,
OLD_RANK,
OLD_RANK_NAME,
RANK,
RANK_NAME,
MONEY,
MONEY_NEEDED,
PERCENT_DONE,
PERCENT_LEFT;
public static Variable getVariable(String name) {
for(Variable variable : values()) {
if(variable.toString().equalsIgnoreCase(name)) {
return variable;
}
}
return null;
}
public String replace(String message, String value) {
Pattern pattern = Pattern.compile("\\{" + this + "}", Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(message);
return matcher.replaceAll(value);
}
}
@@ -0,0 +1,167 @@
package sh.okx.rankup.placeholders;
import lombok.Getter;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
import sh.okx.rankup.ranks.Rank;
import sh.okx.rankup.ranks.Rankups;
import sh.okx.rankup.ranks.requirements.Requirement;
import java.text.DecimalFormat;
import java.util.function.Function;
public class Placeholders extends PlaceholderExpansion {
private final Rankup plugin;
@Getter
private final DecimalFormat moneyFormat;
@Getter
private final DecimalFormat percentFormat;
@Getter
private final DecimalFormat simpleFormat;
public Placeholders(Rankup plugin) {
this.plugin = plugin;
this.moneyFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.money-format"));
this.percentFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.percent-format"));
this.simpleFormat = new DecimalFormat(plugin.getConfig().getString("placeholders.simple-format"));
}
@Override
public String onPlaceholderRequest(Player player, String params) {
if (player == null) {
return "";
}
params = params.toLowerCase();
Rankups rankups = plugin.getRankups();
Rank rank = rankups.getRank(player);
Rank next = null;
if (rank != null) {
next = rankups.nextRank(rank);
}
if(params.startsWith("requirement_")) {
String[] parts = params.split("_", 3);
return getPlaceholderRequirement(player, rank,
parts[1], parts.length > 2 ? parts[2] : "");
}
switch (params) {
case "current_rank":
return orElsePlaceholder(rank, Rank::getRank, "not-in-ladder");
case "current_rank_name":
return orElsePlaceholder(rank, Rank::getRank, "not-in-ladder");
case "current_rank_money":
return orElsePlaceholder(rank, r -> simplify(r.getRequirement("money").getAmount()), 0);
case "current_rank_money_formatted":
return moneyFormat.format(orElsePlaceholder(rank, r -> r.getRequirement("money").getAmount(), 0));
case "next_rank":
if (rank == null) {
return getPlaceholder("not-in-ladder");
} else if (next == null) {
return getPlaceholder("highest-rank");
} else {
return next.getRank();
}
case "next_rank_name":
if (rank == null) {
return getPlaceholder("not-in-ladder");
} else if (next == null) {
return getPlaceholder("highest-rank");
} else {
return next.getName();
}
case "next_rank_money":
return orElsePlaceholder(next, r -> simplify(r.getRequirement("money").getAmount()), 0);
case "next_rank_money_formatted":
return moneyFormat.format(orElsePlaceholder(next, r -> r.getRequirement("money").getAmount(), 0));
case "next_rank_money_left":
return orElsePlaceholder(next, r -> simplify(plugin.getEconomy().getBalance(player) - r.getRequirement("money").getAmount()), 0);
case "next_rank_money_left_formatted":
return moneyFormat.format(orElsePlaceholder(next, r -> plugin.getEconomy().getBalance(player) - r.getRequirement("money").getAmount(), 0));
case "next_rank_percent_left":
return orElsePlaceholder(next, r -> (1-(plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount())) * 100, 0);
case "next_rank_percent_left_formatted":
return percentFormat.format(orElsePlaceholder(next, r -> (1-(plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount())) * 100, 0));
case "next_rank_percent_done":
return orElsePlaceholder(next, r -> (plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount()) * 100, 0);
case "next_rank_percent_done_formatted":
return percentFormat.format(orElsePlaceholder(next, r -> (plugin.getEconomy().getBalance(player) / r.getRequirement("money").getAmount()) * 100, 0));
default:
return null;
}
}
private String getPlaceholderRequirement(Player player, Rank rank, String requirementName, String params) {
if(rank == null) {
return "";
}
Requirement requirement = rank.getRequirement(requirementName);
switch(params) {
case "":
return simpleFormat.format(orElse(requirement, Requirement::getAmount, 0));
case "left":
return simpleFormat.format(orElse(requirement, r -> r.getRemaining(player), 0));
case "percent_left":
return percentFormat.format(orElse(requirement, r -> (r.getRemaining(player) / r.getAmount()) * 100, 0));
case "percent_done":
return percentFormat.format(orElse(requirement, r -> (1-(r.getRemaining(player) / r.getAmount())) * 100, 100));
default:
return null;
}
}
private Number simplify(Number number) {
if (number instanceof Float) {
return (float) number % 1 == 0 ? (int) number : number;
} else if (number instanceof Double) {
return (double) number % 1 == 0 ? (long) number : number;
} else {
return number;
}
}
private <T> String orElsePlaceholder(T t, Function<T, Object> value, Object fallback) {
if (t == null) {
return getPlaceholder(String.valueOf(fallback));
}
try {
return String.valueOf(value.apply(t));
} catch (NullPointerException ex) {
return getPlaceholder(String.valueOf(fallback));
}
}
private <T, R> R orElse(T t, Function<T, R> value, R fallback) {
if (t == null) {
return fallback;
}
try {
return value.apply(t);
} catch (NullPointerException ex) {
return fallback;
}
}
private String getPlaceholder(String name) {
return plugin.getConfig().getString("placeholders." + name);
}
@Override
public String getIdentifier() {
return "rankup";
}
@Override
public String getAuthor() {
return String.join(", ", plugin.getDescription().getAuthors());
}
@Override
public String getVersion() {
return plugin.getDescription().getVersion();
}
}
@@ -0,0 +1,4 @@
package sh.okx.rankup.ranks;
public class Prestige {
}
+138
View File
@@ -0,0 +1,138 @@
package sh.okx.rankup.ranks;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
import sh.okx.rankup.messages.MessageBuilder;
import sh.okx.rankup.messages.Variable;
import sh.okx.rankup.ranks.requirements.Requirement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BinaryOperator;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class Rank {
private final Rankup plugin;
@Getter
private final String name;
@Getter
private final String next;
@Getter
private final String rank;
private final Set<Requirement> requirements;
private final BinaryOperator<Boolean> reducer;
private final List<String> commands;
public static Rank deserialize(Rankup plugin, ConfigurationSection section) {
String rank = section.getString("rank");
Set<Requirement> requirements = new HashSet<>();
BinaryOperator<Boolean> reducer = null;
ConfigurationSection requirementsSection = section.getConfigurationSection("requirements");
if(requirementsSection != null) {
for (Map.Entry<String, Object> entry : requirementsSection.getValues(false).entrySet()) {
String name = entry.getKey();
double amount = Double.parseDouble(String.valueOf(entry.getValue()));
Requirement requirement = plugin.getRequirementRegistry().newRequirement(name, amount);
if (requirement == null) {
plugin.getLogger().warning("Unknown requirement " + name);
} else {
requirements.add(requirement);
}
}
String operation = section.getString("operation");
if (operation == null) {
operation = "and";
}
switch (operation) {
case "and":
reducer = (a, b) -> a && b;
break;
case "or":
reducer = (a, b) -> a || b;
break;
case "xor":
reducer = (a, b) -> (a && !b) || (b && !a);
break;
case "none":
reducer = (a, b) -> !a && !b;
break;
default:
throw new IllegalArgumentException("Invalid operation type for rank " + rank);
}
}
return new Rank(plugin,
section.getName(),
section.getString("next"),
rank,
requirements,
reducer,
section.getStringList("commands"));
}
public boolean checkRequirements(Player player) {
return requirements.stream()
.map(requirement -> requirement.check(player))
.reduce(reducer)
.orElse(true);
}
public boolean isInRank(Player player) {
String[] groups = plugin.getPermissions().getPlayerGroups(player);
for (String group : groups) {
if(group.equalsIgnoreCase(rank)) {
return true;
}
}
return false;
}
public boolean isLastRank() {
return next == null;
}
public Requirement getRequirement(String name) {
for(Requirement requirement : requirements) {
if(requirement.getName().equalsIgnoreCase(name)) {
return requirement;
}
}
return null;
}
public void applyRequirements(Player player) {
for(Requirement requirement : requirements) {
requirement.apply(player);
}
}
public void runCommands(Player player, Rank nextRank) {
for (String command : commands) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), new MessageBuilder(command)
.replace(Variable.PLAYER, player.getName())
.replace(Variable.OLD_RANK, rank)
.replace(Variable.OLD_RANK_NAME, name)
.replace(Variable.RANK, nextRank.rank)
.replace(Variable.RANK_NAME, nextRank.name)
.toString());
}
}
@Override
public boolean equals(Object o) {
if(!(o instanceof Rank)) {
return false;
}
return ((Rank) o).name.equals(name);
}
}
@@ -0,0 +1,80 @@
package sh.okx.rankup.ranks;
import lombok.Getter;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class Rankups {
@Getter
private final FileConfiguration config;
private final Set<Rank> ranks = new HashSet<>();
public Rankups(Rankup plugin, FileConfiguration config) {
this.config = config;
for (Map.Entry<String, Object> entry : config.getValues(false).entrySet()) {
ConfigurationSection rankSection = (ConfigurationSection) entry.getValue();
ranks.add(Rank.deserialize(plugin, rankSection));
}
}
public Rank getFirstRank() {
OUTER:
for(Rank rank : ranks) {
// see if anything ranks up to this
for(Rank rank0 : ranks) {
if(!rank0.isLastRank() && rank0.getNext().equals(rank.getName())) {
continue OUTER;
}
}
// nothing ranks up to this
return rank;
}
return null;
}
public Rank getRank(String name) {
for(Rank rank : ranks) {
if(rank.getName().equals(name)) {
return rank;
}
}
return null;
}
public Rank getRank(Player player) {
return ranks.stream()
.filter(rank -> rank.isInRank(player))
.findFirst()
.orElse(null);
}
public Rank nextRank(Rank rank) {
if(rank.isLastRank()) {
return null;
}
for(Rank nextRank : ranks) {
if (rank.getNext().equalsIgnoreCase(nextRank.getName())) {
return nextRank;
}
}
// this shouldn't happen but whatever
return null;
}
// public boolean hasNext(Rank start, Rank rank) {
// while(!start.isLastRank()) {
// start = nextRank(rank);
// if(start.equals(rank)) {
// return true;
// }
// }
// return false;
// }
}
@@ -0,0 +1,38 @@
package sh.okx.rankup.ranks.requirements;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
public class MoneyRequirement extends Requirement {
public MoneyRequirement(Rankup plugin, String name) {
super(plugin, name);
}
protected MoneyRequirement(Requirement clone) {
super(clone);
}
@Override
public boolean check(Player player) {
Economy economy = plugin.getEconomy();
double balance = economy.getBalance(player);
return balance >= amount;
}
@Override
public void apply(Player player) {
Economy economy = plugin.getEconomy();
economy.withdrawPlayer(player, amount);
}
@Override
public double getRemaining(Player player) {
return Math.max(0, amount - plugin.getEconomy().getBalance(player));
}
@Override
public Requirement clone() {
return new MoneyRequirement(this);
}
}
@@ -0,0 +1,37 @@
package sh.okx.rankup.ranks.requirements;
import org.bukkit.Statistic;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
public class PlaytimeHoursRequirement extends Requirement {
private static final int TICKS_PER_HOUR = 20 * 60 * 60;
public PlaytimeHoursRequirement(Rankup plugin, String name) {
super(plugin, name);
}
protected PlaytimeHoursRequirement(Requirement clone) {
super(clone);
}
@Override
public boolean check(Player player) {
return player.getStatistic(Statistic.PLAY_ONE_MINUTE) * TICKS_PER_HOUR >= amount;
}
@Override
public void apply(Player player) {
// well, we can't really take hours of playtime away, can we?
}
@Override
public double getRemaining(Player player) {
return amount - (player.getStatistic(Statistic.PLAY_ONE_MINUTE) * TICKS_PER_HOUR);
}
@Override
public Requirement clone() {
return new PlaytimeHoursRequirement(this);
}
}
@@ -0,0 +1,55 @@
package sh.okx.rankup.ranks.requirements;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
public abstract class Requirement implements Cloneable {
protected Rankup plugin;
@Getter
protected String name;
@Getter
@Setter
protected double amount;
public Requirement(Rankup plugin, String name) {
this.plugin = plugin;
this.name = name;
}
protected Requirement(Requirement clone) {
if(clone != null) {
this.plugin = clone.plugin;
this.name = clone.name;
this.amount = clone.amount;
}
}
/**
* Check if a player meets this requirement
* @param player the player to check
* @return true if they meet the requirement, false otherwise
*/
public abstract boolean check(Player player);
/**
* Apply the effect of this requirement to the player.
* For money, this could be taking money away from the player.
* You can assume that <code>Requirement#check(Player)</code> has been called,
* and has returned true immediately prior to this.
* @param player the player to take from
*/
public abstract void apply(Player player);
/**
* Get the remaining amount needed for <code>Requirement#check(Player)</code> to yield true.
* This is not required and is only used in placeholders.
* @param player the player to find the remaining amount of
* @return the remaining amount needed. Should be non-negative.
*/
public double getRemaining(Player player) {
return amount;
}
public abstract Requirement clone();
}
@@ -0,0 +1,23 @@
package sh.okx.rankup.ranks.requirements;
import java.util.HashSet;
import java.util.Set;
public class RequirementRegistry {
private Set<Requirement> requirements = new HashSet<>();
public void addRequirement(Requirement requirement) {
requirements.add(requirement);
}
public Requirement newRequirement(String name, double amount) {
for(Requirement requirement : requirements) {
if(requirement.getName().equalsIgnoreCase(name)) {
Requirement newRequirement = requirement.clone();
newRequirement.setAmount(amount);
return newRequirement;
}
}
return null;
}
}
@@ -0,0 +1,40 @@
package sh.okx.rankup.ranks.requirements;
import org.bukkit.entity.Player;
import sh.okx.rankup.Rankup;
public class XpLevelRequirement extends Requirement {
public XpLevelRequirement(Rankup plugin, String name) {
super(plugin, name);
}
protected XpLevelRequirement(Requirement clone) {
super(clone);
}
@Override
public void setAmount(double amount) {
// experience level should be a whole number
super.setAmount(Math.round(amount));
}
@Override
public boolean check(Player player) {
return player.getLevel() >= amount;
}
@Override
public void apply(Player player) {
player.setLevel(player.getLevel() - (int) amount);
}
@Override
public double getRemaining(Player player) {
return Math.max(0, amount - player.getLevel());
}
@Override
public Requirement clone() {
return new XpLevelRequirement(this);
}
}
+45
View File
@@ -0,0 +1,45 @@
# this is used for letting you know that you need to update/change your config file
version: 0
# how people should confirm ranking up
# options are: gui, text or none
confirmation-type: 'gui'
gui:
rows: 1
rankup:
material: EMERALD_BLOCK
# index can be separated by spaces to show in multiple ways
# ie, 0-3 9-12 18-21
# you can also just use a single number instead of a range.
index: 0-3
name: '&a&lConfirm'
# lore is optional
lore: '&6Rankup to &b{RANK}'
cancel:
material: REDSTONE_BLOCK
index: 5-8
name: '&c&lCancel'
fill:
name: ' '
# if you are using a 1.8-1.12 and you want to change this
# you can use MATERIAL:data, for example STAINED_GLASS_PANE:8
# this works for both the rankup and cancel blocks as well
material: BLACK_STAINED_GLASS_PANE
text:
# the time in seconds for a player to
# confirm ranking up by typing the command again
timeout: 10
placeholders:
# format for money. for more information, see
# https://docs.oracle.com/javase/8/docs/api/java/text/DecimalFormat.html
money-format: "#,##0.##"
percent-format: "0.##"
# the format used for requirements
simple-format: "#.##"
# used for current_rank and next_rank placeholders when a player not in anything in rankups.yml
not-in-ladder: "None"
# used in next_rank placeholders when there is no rankup
highest-rank: "None"
+35
View File
@@ -0,0 +1,35 @@
# the messages in this section can be customised for each rankup in rankups.yml.
rankup:
# NOTE: if you are using requirements for your ranks that are NOT money,
# you will want to change this for each rank!
requirements-not-met: "&cYou need {MONEY} money to rankup."
no-rankup: "&eYou are at the highest rank."
# set to an empty string, ie: success-public: ""
# to hide that message.
success-public: "&a{PLAYER} &ehas ranked up to: &d{RANK}"
success-private: "&aYou have ranked up to: &d{RANK}"
# used for the text confirmation
confirmation: |-
&eAre you sure you want to rankup to &a{RANK}&e?
&eType &c/rankup &eagain to confirm.
# used for the GUI confirmation
title: "Rankup to {RANK}"
# It is HIGHLY RECOMMENDED you override these in rankups.yml
# to show the specific requirements for each rank.
# however if you are just using money, you can use {MONEY} or {MONEY_NEEDED} or {PERCENT_DONE} or {PERCENT_LEFT}
# for example:
#ranks:
# complete: "&7{OLD_RANK} &8\xbb &7{RANK} &efor &7${MONEY}"
# current: "&c{OLD_RANK} &e\xbb &c{RANK} &efor &a${MONEY} &e{PERCENT_DONE}%"
# incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &efor &a${MONEY}"
ranks:
complete: "&7{OLD_RANK} &8\xbb &7{RANK}"
current: "&c{OLD_RANK} &e\xbb &c{RANK}"
incomplete: "&r{OLD_RANK} &e\xbb &r{RANK}"
ranks:
# an empty string disables the header/footer
header: ''
footer: ''
not-in-ladder: "&cSorry, but we could not find any rankups for the group(s) you are in."
+41
View File
@@ -0,0 +1,41 @@
name: Rankup
version: 3.0-alpha
main: sh.okx.rankup.Rankup
author: Okx
depend: [Vault]
softdepend: [PlaceholderAPI]
api-version: 1.13
commands:
rankup:
permission: rankup.rankup
description: Rankup.
rankup3:
permission: rankup3.info
description: View Rankup version and perform some administrative commands.
# support the old command
aliases: [pru]
ranks:
permission: rankup.ranks
description: List all the ranks.
permissions:
rankup.*:
children:
rankup.info: true
rankup.rankup: true
rankup.checkversion: true
rankup.ranks: true
rankup.reload: true
rankup.ranks: true
rankup.info:
default: true
rankup.rankup:
default: true
rankup.checkversion:
default: op
rankup.ranks:
default: true
rankup.reload:
default: op
rankup.ranks:
default: true
+49
View File
@@ -0,0 +1,49 @@
Aexample:
# the name of the rank in your permissions plugin
rank: 'A'
# the next rank a player can rank up to.
# this must be the name of the configuration section.
# for example, the name of this configuration section is "Aexample".
# this is not required.
next: 'Bexample'
# List of requirements to go to the next rank
# (ie, this example will charge 1000 money to rankup from A to B)
# money: money from the server economy
# xp-level: amount of experience levels
# playtime-hours: hours a player has played
# custom requirements can also be added by other plugins.
requirements:
money: 1000
# What requirements players need to match to /rankup.
# this is optional - if you don't use it, it defaults to "and"
# n.b. if there are no requirements players will always be able to /rankup.
# and: all requirements
# or: at least one requirement
# xor: only one requirement
# none: no requirements
operation: and
# the console will run these commands when a player ranks up
#commands:
# this will run when a player ranks up from A to B.
#- 'say {PLAYER} well done for ranking up from {OLD_RANK} to {RANK}!'
Bexample:
rank: 'B'
next: 'Cexample'
requirements:
money: 2500
Cexample:
rank: 'C'
next: 'Dexample'
requirements:
money: 5000
xp-level: 2
# you can have a custom messages too.
# you can use this to list the requirements needed.
rankup:
requirements-not-met: '&cYou need 5000 money and 2 levels of XP to rankup to D.'
ranks:
complete: "&7{OLD_RANK} &8\xbb &7{RANK} &e(5000 money, 2 XP levels)"
current: "&c{OLD_RANK} &e\xbb &c{RANK} &e(5000 money, 2 XP levels)"
incomplete: "&r{OLD_RANK} &e\xbb &r{RANK} &e(5000 money, 2 XP levels)"
Dexample:
rank: 'D'
@@ -0,0 +1,9 @@
package sh.okx.rankup;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class RankupTest {
}