diff --git a/Plan/build.gradle b/Plan/build.gradle index accbb25ae..2cf6a8d6f 100644 --- a/Plan/build.gradle +++ b/Plan/build.gradle @@ -78,6 +78,7 @@ subprojects { ext.gsonVersion = "2.8.5" ext.guavaVersion = "28.0-jre" ext.bstatsVersion = "1.4" + ext.placeholderapiVersion = "2.9.2" repositories { mavenCentral() diff --git a/Plan/bukkit/build.gradle b/Plan/bukkit/build.gradle index 654966f0e..ef98b864b 100644 --- a/Plan/bukkit/build.gradle +++ b/Plan/bukkit/build.gradle @@ -1,16 +1,18 @@ +repositories { + maven { // Placeholder API repository + url = "http://repo.extendedclip.com/content/repositories/placeholderapi/" + } +} + dependencies { compile project(path: ":common", configuration: 'shadow') compileOnly project(":api") compile "com.djrapitops:AbstractPluginFramework-bukkit:$abstractPluginFrameworkVersion" compile "org.bstats:bstats-bukkit:$bstatsVersion" + compileOnly "me.clip:placeholderapi:$placeholderapiVersion" -// compileOnly "org.spigotmc:spigot-api:$spigotVersion" -// compileOnly "org.bukkit:bukkit:$bukkitVersion" compileOnly "com.destroystokyo.paper:paper-api:$paperVersion" - -// testCompile "org.spigotmc:spigot-api:$spigotVersion" -// testCompile "org.bukkit:bukkit:$bukkitVersion" testCompile "com.destroystokyo.paper:paper-api:$paperVersion" testCompile project(path: ":common", configuration: 'testArtifacts') diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java index 7631164ee..faeb02dcd 100644 --- a/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/Plan.java @@ -16,6 +16,7 @@ */ package com.djrapitops.plan; +import com.djrapitops.plan.addons.placeholderapi.PlaceholderRegistrar; import com.djrapitops.plan.commands.PlanCommand; import com.djrapitops.plan.exceptions.EnableException; import com.djrapitops.plan.gathering.ServerShutdownSave; @@ -26,6 +27,7 @@ import com.djrapitops.plugin.BukkitPlugin; import com.djrapitops.plugin.benchmarking.Benchmark; import com.djrapitops.plugin.command.ColorScheme; import com.djrapitops.plugin.task.AbsRunnable; +import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import java.util.logging.Level; @@ -53,6 +55,7 @@ public class Plan extends BukkitPlugin implements PlanPlugin { system.enable(); registerMetrics(); + registerPlaceholderAPIExtension(); logger.debug("Verbose debug messages are enabled."); String benchTime = " (" + timings.end("Enable").map(Benchmark::toDurationString).orElse("-") + ")"; @@ -79,6 +82,16 @@ public class Plan extends BukkitPlugin implements PlanPlugin { } } + private void registerPlaceholderAPIExtension() { + try { + if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + PlaceholderRegistrar.register(system, errorHandler); + } + } catch (Exception | NoClassDefFoundError | NoSuchMethodError failed) { + logger.warn("Failed to register PlaceholderAPI placeholders: " + failed.toString()); + } + } + private void registerMetrics() { Plan plugin = this; // Spigot 1.14 requires Sync events to be fired from a server thread. diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderRegistrar.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderRegistrar.java new file mode 100644 index 000000000..170f86999 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlaceholderRegistrar.java @@ -0,0 +1,36 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi; + +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plugin.logging.error.ErrorHandler; + +/** + * Additional wrapper to register PlaceholderAPI placeholders. + * + * @author Rsl1122 + */ +public class PlaceholderRegistrar { + + private PlaceholderRegistrar() { + } + + public static void register(PlanSystem system, ErrorHandler errorHandler) { + new PlanPlaceHolders(system, errorHandler).register(); + } + +} \ No newline at end of file diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceHolders.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceHolders.java new file mode 100644 index 000000000..3104d8dad --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/PlanPlaceHolders.java @@ -0,0 +1,125 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi; + +import com.djrapitops.plan.PlanSystem; +import com.djrapitops.plan.addons.placeholderapi.placeholders.*; +import com.djrapitops.plan.delivery.domain.keys.ServerKeys; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.version.VersionCheckSystem; +import com.djrapitops.plugin.logging.L; +import com.djrapitops.plugin.logging.error.ErrorHandler; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Placeholder expansion used to provide data from Plan. + * + *

+ * Current used services for placeholders: + *

+ * + * @author aidn5 + */ +public class PlanPlaceHolders extends PlaceholderExpansion { + public final ErrorHandler errorHandler; + + private final Collection placeholders = new ArrayList<>(); + private final VersionCheckSystem versionCheckSystem; + + public PlanPlaceHolders( + PlanSystem system, + ErrorHandler errorHandler + ) { + this.versionCheckSystem = system.getVersionCheckSystem(); + this.errorHandler = errorHandler; + + PlanConfig config = system.getConfigSystem().getConfig(); + DBSystem databaseSystem = system.getDatabaseSystem(); + ServerInfo serverInfo = system.getServerInfo(); + Formatters formatters = system.getDeliveryUtilities().getFormatters(); + + placeholders.add(new ServerPlaceHolders(databaseSystem, serverInfo, formatters)); + placeholders.add(new OperatorPlaceholders(databaseSystem, serverInfo)); + placeholders.add(new WorldTimePlaceHolder(databaseSystem, serverInfo, formatters)); + placeholders.add(new SessionPlaceHolder(config, databaseSystem, serverInfo, formatters)); + placeholders.add(new PlayerPlaceHolder(databaseSystem, serverInfo, formatters)); + } + + @Override + public boolean persist() { + return true; + } + + @Override + public boolean canRegister() { + return true; + } + + @Override + public String getIdentifier() { + return "plan"; + } + + @Override + public String getPlugin() { + return "Plan"; + } + + @Override + public String getAuthor() { + return "Rsl1122"; + } + + @Override + public String getVersion() { + return versionCheckSystem.getCurrentVersion(); + } + + @Override + public String onPlaceholderRequest(Player p, String params) { + try { + for (AbstractPlanPlaceHolder placeholder : placeholders) { + String value = placeholder.onPlaceholderRequest(p, params); + if (value == null) continue; + + return value; + + } + } catch (Exception e) { + errorHandler.log(L.WARN, getClass(), e); + e.printStackTrace(); + } + + return null; + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/AbstractPlanPlaceHolder.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/AbstractPlanPlaceHolder.java new file mode 100644 index 000000000..04000f0ef --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/AbstractPlanPlaceHolder.java @@ -0,0 +1,75 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.addons.placeholderapi.PlanPlaceHolders; +import com.djrapitops.plan.identification.ServerInfo; +import org.bukkit.entity.Player; + +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * abstract class used for plan placeholders list. This class contains most used + * methods (static methods and non-static one). It is also used as a universe + * interface for the Plan-placeholders instances + * {@link #onPlaceholderRequest(Player, String)}. Check {@link PlanPlaceHolders} + * to learn more how it is used. + * + * @author aidn5 + * @see PlanPlaceHolders + */ +public abstract class AbstractPlanPlaceHolder { + + protected final ServerInfo serverInfo; + + AbstractPlanPlaceHolder(ServerInfo serverInfo) { + this.serverInfo = serverInfo; + } + + static long now() { + return System.currentTimeMillis(); + } + + static long dayAgo() { + return now() - TimeUnit.DAYS.toMillis(1L); + } + + static long weekAgo() { + return now() - (TimeUnit.DAYS.toMillis(7L)); + } + + static long monthAgo() { + return now() - (TimeUnit.DAYS.toMillis(30L)); + } + + UUID serverUUID() { + return serverInfo.getServerUUID(); + } + + /** + * Look up the placeholder and check if it is registered in this instance. + * + * @param p the player who is viewing the placeholder + * @param params the placeholder to look up to. + * @return the value of the placeholder if found, or empty {@link String} if no + * value found but the placeholder is registered, + * otherwise {@code null} + * @throws Exception if any error occurs + */ + public abstract String onPlaceholderRequest(Player p, String params) throws Exception; +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/OperatorPlaceholders.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/OperatorPlaceholders.java new file mode 100644 index 000000000..c2b6898be --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/OperatorPlaceholders.java @@ -0,0 +1,46 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries; +import org.bukkit.entity.Player; + +/** + * Placeholders about operators. + * + * @author aidn5, Rsl1122 + */ +public class OperatorPlaceholders extends AbstractPlanPlaceHolder { + + private final DBSystem dbSystem; + + public OperatorPlaceholders(DBSystem dbSystem, ServerInfo serverInfo) { + super(serverInfo); + this.dbSystem = dbSystem; + } + + @Override + public String onPlaceholderRequest(Player p, String params) throws Exception { + if ("operators_total".equalsIgnoreCase(params)) { + return dbSystem.getDatabase().query(PlayerCountQueries.operators(serverUUID())).toString(); + } + + return null; + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/PlayerPlaceHolder.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/PlayerPlaceHolder.java new file mode 100644 index 000000000..6fa52aefd --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/PlayerPlaceHolder.java @@ -0,0 +1,134 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.delivery.domain.container.PlayerContainer; +import com.djrapitops.plan.delivery.domain.keys.PlayerKeys; +import com.djrapitops.plan.delivery.domain.mutators.PingMutator; +import com.djrapitops.plan.delivery.domain.mutators.PvpInfoMutator; +import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries; +import com.djrapitops.plan.utilities.Predicates; +import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.bukkit.entity.Player; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Placeholders about a player. + * + * @author aidn5, Rsl1122 + */ +public class PlayerPlaceHolder extends AbstractPlanPlaceHolder { + + private final DBSystem dbSystem; + private Formatter year; + + public PlayerPlaceHolder( + DBSystem dbSystem, + ServerInfo serverInfo, + Formatters formatters + ) { + super(serverInfo); + this.dbSystem = dbSystem; + year = formatters.yearLong(); + } + + @Override + public String onPlaceholderRequest(Player p, String params) throws Exception { + Serializable got = get(params, p.getUniqueId()); + return got != null ? got.toString() : null; + } + + // Checkstyle.OFF: CyclomaticComplexity + + public Serializable get(String params, UUID playerUUID) { + PlayerContainer player = getPlayer(playerUUID); + + switch (params.toLowerCase()) { + case "player_banned": + return player.getValue(PlayerKeys.BANNED) + .orElse(Boolean.FALSE) ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse(); + case "player_operator": + return player.getValue(PlayerKeys.OPERATOR) + .orElse(Boolean.FALSE) ? PlaceholderAPIPlugin.booleanTrue() : PlaceholderAPIPlugin.booleanFalse(); + + case "player_sessions_count": + return SessionsMutator.forContainer(player).count(); + + case "player_kick_count": + return player.getValue(PlayerKeys.KICK_COUNT).orElse(0); + case "player_death_count": + return player.getValue(PlayerKeys.DEATH_COUNT).orElse(0); + case "player_mob_kill_count": + return player.getValue(PlayerKeys.MOB_KILL_COUNT).orElse(0); + case "player_player_kill_count": + return player.getValue(PlayerKeys.PLAYER_KILL_COUNT).orElse(0); + case "player_kill_death_ratio": + return PvpInfoMutator.forContainer(player).killDeathRatio(); + + case "player_ping_average_day": + return PingMutator.forContainer(player).filterBy(Predicates.within(dayAgo(), now())).average(); + case "player_ping_average_week": + return PingMutator.forContainer(player).filterBy(Predicates.within(weekAgo(), now())).average(); + + case "player_ping_average_month": + return PingMutator.forContainer(player).filterBy(Predicates.within(monthAgo(), now())).average(); + + case "player_lastseen": + return year.apply(player.getValue(PlayerKeys.LAST_SEEN).orElse((long) 0)); + case "player_registered": + return year.apply(player.getValue(PlayerKeys.REGISTERED).orElse((long) 0)); + + case "player_time_active": + return year.apply(SessionsMutator.forContainer(player) + .toActivePlaytime()); + case "player_time_afk": + return year.apply(SessionsMutator.forContainer(player) + .toAfkTime()); + + case "player_time_total": + return year.apply(SessionsMutator.forContainer(player) + .toPlaytime()); + case "player_time_day": + return year.apply(SessionsMutator.forContainer(player) + .filterSessionsBetween(dayAgo(), now()) + .toPlaytime()); + case "player_time_week": + return year.apply(SessionsMutator.forContainer(player) + .filterSessionsBetween(weekAgo(), now()) + .toPlaytime()); + case "player_time_month": + return year.apply(SessionsMutator.forContainer(player) + .filterSessionsBetween(monthAgo(), now()) + .toPlaytime()); + + } + return null; + } + + // Checkstyle.ON: CyclomaticComplexity + + private PlayerContainer getPlayer(UUID playerUUID) { + return dbSystem.getDatabase().query(ContainerFetchQueries.fetchPlayerContainer(playerUUID)); + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/ServerPlaceHolders.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/ServerPlaceHolders.java new file mode 100644 index 000000000..e93f7e620 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/ServerPlaceHolders.java @@ -0,0 +1,131 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.objects.TPSQueries; +import org.bukkit.entity.Player; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Placeholders about a servers. + * + * @author aidn5, Rsl1122 + */ +public class ServerPlaceHolders extends AbstractPlanPlaceHolder { + + private final DBSystem dbSystem; + private Formatter decimals; + private Formatter percentage; + + public ServerPlaceHolders( + DBSystem dbSystem, + ServerInfo serverInfo, + Formatters formatters + ) { + super(serverInfo); + this.dbSystem = dbSystem; + decimals = formatters.decimals(); + percentage = formatters.percentage(); + } + + @Override + public String onPlaceholderRequest(Player p, String params) throws Exception { + Database database = dbSystem.getDatabase(); + UUID serverUUID = serverUUID(); + Serializable got = get(params, database, serverUUID); + return got != null ? got.toString() : null; + } + + // Checkstyle.OFF: CyclomaticComplexity + + public Serializable get(String params, Database database, UUID serverUUID) { + switch (params.toLowerCase()) { + case "server_tps_day": + return decimals.apply(database.query(TPSQueries.averageTPS(dayAgo(), now(), serverUUID))); + case "server_tps_week": + return decimals.apply(database.query(TPSQueries.averageTPS(weekAgo(), now(), serverUUID))); + case "server_tps_month": + return decimals.apply(database.query(TPSQueries.averageTPS(monthAgo(), now(), serverUUID))); + + case "server_cpu_day": + return percentage.apply(database.query(TPSQueries.averageCPU(dayAgo(), now(), serverUUID))); + case "server_cpu_week": + return percentage.apply(database.query(TPSQueries.averageCPU(weekAgo(), now(), serverUUID))); + case "server_cpu_month": + return percentage.apply(database.query(TPSQueries.averageCPU(monthAgo(), now(), serverUUID))); + + case "server_ram_day": + return database.query(TPSQueries.averageRAM(dayAgo(), now(), serverUUID)) + " MB"; + case "server_ram_week": + return database.query(TPSQueries.averageRAM(weekAgo(), now(), serverUUID)) + " MB"; + case "server_ram_month": + return database.query(TPSQueries.averageRAM(monthAgo(), now(), serverUUID)) + " MB"; + + case "server_chunks_day": + return database.query(TPSQueries.averageChunks(dayAgo(), now(), serverUUID)); + case "server_chunks_week": + return database.query(TPSQueries.averageChunks(weekAgo(), now(), serverUUID)); + case "server_chunks_month": + return database.query(TPSQueries.averageChunks(monthAgo(), now(), serverUUID)); + + case "server_entities_day": + return database.query(TPSQueries.averageEntities(dayAgo(), now(), serverUUID)); + case "server_entities_week": + return database.query(TPSQueries.averageEntities(weekAgo(), now(), serverUUID)); + case "server_entities_month": + return database.query(TPSQueries.averageEntities(monthAgo(), now(), serverUUID)); + + case "server_max_free_disk_day": + return database.query(TPSQueries.maxFreeDisk(dayAgo(), now(), serverUUID)); + case "server_max_free_disk_week": + return database.query(TPSQueries.maxFreeDisk(weekAgo(), now(), serverUUID)); + case "server_max_free_disk_month": + return database.query(TPSQueries.maxFreeDisk(monthAgo(), now(), serverUUID)); + + case "server_min_free_disk_day": + return database.query(TPSQueries.minFreeDisk(dayAgo(), now(), serverUUID)); + case "server_min_free_disk_week": + return database.query(TPSQueries.minFreeDisk(weekAgo(), now(), serverUUID)); + case "server_min_free_disk_month": + return database.query(TPSQueries.minFreeDisk(monthAgo(), now(), serverUUID)); + + case "server_average_free_disk_day": + return database.query(TPSQueries.averageFreeDisk(dayAgo(), now(), serverUUID)); + case "server_average_free_disk_week": + return database.query(TPSQueries.averageFreeDisk(weekAgo(), now(), serverUUID)); + case "server_average_free_disk_month": + return database.query(TPSQueries.averageFreeDisk(monthAgo(), now(), serverUUID)); + + case "server_name": + return serverInfo.getServer().getName(); + case "server_uuid": + return serverInfo.getServerUUID(); + + default: + return null; + } + } + + // Checkstyle.ON: CyclomaticComplexity +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/SessionPlaceHolder.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/SessionPlaceHolder.java new file mode 100644 index 000000000..a6a196452 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/SessionPlaceHolder.java @@ -0,0 +1,195 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.delivery.domain.DateHolder; +import com.djrapitops.plan.delivery.domain.DateObj; +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.settings.config.PlanConfig; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.Database; +import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries; +import com.djrapitops.plan.storage.database.queries.objects.KillQueries; +import com.djrapitops.plan.storage.database.queries.objects.PingQueries; +import com.djrapitops.plan.storage.database.queries.objects.SessionQueries; +import com.djrapitops.plan.storage.database.queries.objects.TPSQueries; +import org.bukkit.entity.Player; + +import java.io.Serializable; +import java.util.UUID; + +/** + * Placeholders about a sessions. + * + * @author aidn5, Rsl1122 + */ +public class SessionPlaceHolder extends AbstractPlanPlaceHolder { + + private final DBSystem dbSystem; + private Formatter timeAmount; + private int tzOffsetMs; + private Formatter year; + + public SessionPlaceHolder( + PlanConfig config, + DBSystem dbSystem, + ServerInfo serverInfo, + Formatters formatters + ) { + super(serverInfo); + this.dbSystem = dbSystem; + + tzOffsetMs = config.getTimeZone().getOffset(System.currentTimeMillis()); + timeAmount = formatters.timeAmount(); + year = formatters.year(); + } + + @Override + public String onPlaceholderRequest(Player p, String params) throws Exception { + Serializable got = get(params); + return got != null ? got.toString() : null; + } + + // Checkstyle.OFF: CyclomaticComplexity + + public Serializable get(String params) { + Database database = dbSystem.getDatabase(); + UUID serverUUID = serverUUID(); + + switch (params.toLowerCase()) { + + case "sessions_play_time_total": + return timeAmount.apply(database.query(SessionQueries.playtime(0L, now(), serverUUID))); + case "sessions_play_time_day": + return timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now(), serverUUID))); + case "sessions_play_time_week": + return timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now(), serverUUID))); + case "sessions_play_time_month": + return timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now(), serverUUID))); + + case "sessions_active_time_total": + return timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now(), serverUUID))); + case "sessions_active_time_day": + return timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now(), serverUUID))); + case "sessions_active_time_week": + return timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now(), serverUUID))); + case "sessions_active_time_month": + return timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now(), serverUUID))); + + case "sessions_afk_time_total": + return timeAmount.apply(database.query(SessionQueries.afkTime(0L, now(), serverUUID))); + case "sessions_afk_time_day": + return timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now(), serverUUID))); + case "sessions_afk_time_week": + return timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now(), serverUUID))); + case "sessions_afk_time_month": + return timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now(), serverUUID))); + + case "sessions_unique_players_total": + case "sessions_new_players_total": + return database.query(PlayerCountQueries.newPlayerCount(0L, now(), serverUUID)); + case "sessions_unique_players_day": + return database.query(PlayerCountQueries.uniquePlayerCount(dayAgo(), now(), serverUUID)); + case "sessions_unique_players_week": + return database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now(), serverUUID)); + case "sessions_unique_players_month": + return database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now(), serverUUID)); + + case "sessions_players_death_total": + return database.query(KillQueries.deathCount(0L, now(), serverUUID)); + case "sessions_players_death_day": + return database.query(KillQueries.deathCount(dayAgo(), now(), serverUUID)); + case "sessions_players_death_week": + return database.query(KillQueries.deathCount(weekAgo(), now(), serverUUID)); + case "sessions_players_death_month": + return database.query(KillQueries.deathCount(monthAgo(), now(), serverUUID)); + + case "sessions_players_kill_total": + return database.query(KillQueries.playerKillCount(0L, now(), serverUUID)); + case "sessions_players_kill_day": + return database.query(KillQueries.playerKillCount(dayAgo(), now(), serverUUID)); + case "sessions_players_kill_week": + return database.query(KillQueries.playerKillCount(weekAgo(), now(), serverUUID)); + case "sessions_players_kill_month": + return database.query(KillQueries.playerKillCount(monthAgo(), now(), serverUUID)); + + case "sessions_mob_kill_total": + return database.query(KillQueries.mobKillCount(0L, now(), serverUUID)); + case "sessions_mob_kill_day": + return database.query(KillQueries.mobKillCount(dayAgo(), now(), serverUUID)); + case "sessions_mob_kill_week": + return database.query(KillQueries.mobKillCount(weekAgo(), now(), serverUUID)); + case "sessions_mob_kill_month": + return database.query(KillQueries.mobKillCount(monthAgo(), now(), serverUUID)); + + case "sessions_average_session_length_total": + return getPlaytime(database, 0L, now(), serverUUID); + case "sessions_average_session_length_day": + return getPlaytime(database, dayAgo(), now(), serverUUID); + case "sessions_average_session_length_week": + return getPlaytime(database, weekAgo(), now(), serverUUID); + case "sessions_average_session_length_month": + return getPlaytime(database, monthAgo(), now(), serverUUID); + + case "sessions_average_unique_players_total": + return database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs, serverUUID)); + case "sessions_average_unique_players_day": + return database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs, serverUUID)); + case "sessions_average_unique_players_week": + return database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs, serverUUID)); + case "sessions_average_unique_players_month": + return database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs, serverUUID)); + case "sessions_new_players_day": + return database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now(), serverUUID)); + case "sessions_new_players_week": + return database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now(), serverUUID)); + case "sessions_new_players_month": + return database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now(), serverUUID)); + + case "ping_total": + return database.query(PingQueries.averagePing(0L, now(), serverUUID)); + case "ping_day": + return database.query(PingQueries.averagePing(dayAgo(), now(), serverUUID)); + case "ping_week": + return database.query(PingQueries.averagePing(weekAgo(), now(), serverUUID)); + case "ping_month": + return database.query(PingQueries.averagePing(monthAgo(), now(), serverUUID)); + + case "sessions_peak_count": + return database.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID)).map(DateObj::getValue).orElse(0); + case "sessions_peak_date": + return database.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID)).map(year).orElse("-"); + + case "sessions_recent_peak_count": + return database.query(TPSQueries.fetchPeakPlayerCount(serverUUID, dayAgo() * 2L)).map(DateObj::getValue).orElse(0); + case "sessions_recent_peak_date": + return database.query(TPSQueries.fetchPeakPlayerCount(serverUUID, dayAgo() * 2L)).map(year).orElse("-"); + + default: + return null; + } + } + // Checkstyle.ON: CyclomaticComplexity + + private String getPlaytime(Database database, long after, long before, UUID serverUUID) { + Long playtime = database.query(SessionQueries.playtime(after, before, serverUUID)); + Long sessionCount = database.query(SessionQueries.sessionCount(after, before, serverUUID)); + return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime); + } +} diff --git a/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/WorldTimePlaceHolder.java b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/WorldTimePlaceHolder.java new file mode 100644 index 000000000..30708de07 --- /dev/null +++ b/Plan/bukkit/src/main/java/com/djrapitops/plan/addons/placeholderapi/placeholders/WorldTimePlaceHolder.java @@ -0,0 +1,66 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.addons.placeholderapi.placeholders; + +import com.djrapitops.plan.delivery.formatting.Formatter; +import com.djrapitops.plan.delivery.formatting.Formatters; +import com.djrapitops.plan.gathering.domain.WorldTimes; +import com.djrapitops.plan.identification.ServerInfo; +import com.djrapitops.plan.storage.database.DBSystem; +import com.djrapitops.plan.storage.database.queries.objects.WorldTimesQueries; +import org.apache.commons.lang.StringUtils; +import org.bukkit.entity.Player; + +/** + * Placeholders about a world times. + * + * @author aidn5, Rsl1122 + */ +public class WorldTimePlaceHolder extends AbstractPlanPlaceHolder { + + private final DBSystem dbSystem; + private Formatter timeAmount; + + public WorldTimePlaceHolder( + DBSystem dbSystem, + ServerInfo serverInfo, + Formatters formatters + ) { + super(serverInfo); + this.dbSystem = dbSystem; + timeAmount = formatters.timeAmount(); + } + + @Override + public String onPlaceholderRequest(Player p, String params) throws Exception { + String string = params.toLowerCase(); + + if (StringUtils.startsWith(string, "worlds_playtime_total_")) { + // get world total play time + // e.g. "plan_worlds_playtime_total_%worldname%" + // where %worldname% is "world_nether" + + String worldName = StringUtils.removeStart(string, "worlds_playtime_total_"); + WorldTimes worldTimes = dbSystem.getDatabase().query(WorldTimesQueries.fetchServerTotalWorldTimes(serverUUID())); + + return timeAmount.apply(worldTimes.getWorldPlaytime(worldName)); + } + + return null; + } + +} diff --git a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PingMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PingMutator.java index 3e1ff7727..faaccb675 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PingMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PingMutator.java @@ -20,7 +20,7 @@ import com.djrapitops.plan.delivery.domain.container.DataContainer; import com.djrapitops.plan.delivery.domain.keys.CommonKeys; import com.djrapitops.plan.gathering.domain.Ping; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.SortedMap; import java.util.UUID; @@ -36,7 +36,7 @@ public class PingMutator { } public static PingMutator forContainer(DataContainer container) { - return new PingMutator(container.getValue(CommonKeys.PING).orElse(new ArrayList<>())); + return new PingMutator(container.getValue(CommonKeys.PING).orElse(Collections.emptyList())); } public PingMutator filterBy(Predicate predicate) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java index a22eeebda..7bfa09ca2 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/analysis/PlayerCountQueries.java @@ -432,4 +432,22 @@ public class PlayerCountQueries { } }; } + + public static Query operators(UUID serverUUID) { + String sql = SELECT + "COUNT(1) as player_count" + FROM + UserInfoTable.TABLE_NAME + + WHERE + UserInfoTable.SERVER_UUID + "=?" + + AND + UserInfoTable.OP + "=?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setBoolean(2, true); + } + + @Override + public Integer processResults(ResultSet set) throws SQLException { + return set.next() ? set.getInt("player_count") : 0; + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java index 0a820fe68..c0af918de 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/PingQueries.java @@ -293,4 +293,25 @@ public class PingQueries { } }; } + + public static Query averagePing(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + PingTable.AVG_PING + ") as average" + FROM + PingTable.TABLE_NAME + + WHERE + PingTable.SERVER_UUID + "=?" + + AND + PingTable.DATE + ">=?" + + AND + PingTable.DATE + "<=?"; + + return new QueryStatement(sql, 1000) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, after); + statement.setLong(3, before); + } + + @Override + public Double processResults(ResultSet set) throws SQLException { + return set.next() ? set.getDouble("average") : -1.0; + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java index 7cd034b1b..2afa7dc24 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/SessionQueries.java @@ -829,4 +829,26 @@ public class SessionQueries { } }; } + + public static Query activePlaytime(long after, long before, UUID serverUUID) { + String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + + ") as playtime" + + FROM + SessionsTable.TABLE_NAME + + WHERE + SessionsTable.SERVER_UUID + "=?" + + AND + SessionsTable.SESSION_END + ">=?" + + AND + SessionsTable.SESSION_START + "<=?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, after); + statement.setLong(3, before); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("playtime") : 0L; + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java index 4361223d8..40fd98886 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/storage/database/queries/objects/TPSQueries.java @@ -277,4 +277,164 @@ public class TPSQueries { } }; } + + public static Query averageTPS(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + TPS + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Double processResults(ResultSet set) throws SQLException { + return set.next() ? set.getDouble("average") : -1.0; + } + }; + } + + public static Query averageCPU(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + CPU_USAGE + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Double processResults(ResultSet set) throws SQLException { + return set.next() ? set.getDouble("average") : -1.0; + } + }; + } + + public static Query averageRAM(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + RAM_USAGE + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("average") : -1L; + } + }; + } + + public static Query averageChunks(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + CHUNKS + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("average") : -1L; + } + }; + } + + public static Query averageEntities(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + ENTITIES + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("average") : -1L; + } + }; + } + + public static Query maxFreeDisk(long after, long before, UUID serverUUID) { + String sql = SELECT + "MAX(" + FREE_DISK + ") as free" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("free") : -1L; + } + }; + } + + public static Query minFreeDisk(long after, long before, UUID serverUUID) { + String sql = SELECT + "MIN(" + FREE_DISK + ") as free" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("free") : -1L; + } + }; + } + + public static Query averageFreeDisk(long after, long before, UUID serverUUID) { + String sql = SELECT + "AVG(" + FREE_DISK + ") as average" + FROM + TABLE_NAME + + WHERE + SERVER_ID + '=' + ServerTable.STATEMENT_SELECT_SERVER_ID + + AND + DATE + "?"; + return new QueryStatement(sql) { + @Override + public void prepare(PreparedStatement statement) throws SQLException { + statement.setString(1, serverUUID.toString()); + statement.setLong(2, before); + statement.setLong(3, after); + } + + @Override + public Long processResults(ResultSet set) throws SQLException { + return set.next() ? set.getLong("average") : -1L; + } + }; + } } \ No newline at end of file diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/Predicates.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/Predicates.java index 030f7543b..2117b067f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/Predicates.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/Predicates.java @@ -31,7 +31,7 @@ public class Predicates { /* static method class */ } - public static Predicate within(long after, long before) { + public static Predicate within(long after, long before) { return holder -> { long date = holder.getDate(); return after < date && date <= before; diff --git a/Plan/common/src/main/resources/assets/plan/web/error.html b/Plan/common/src/main/resources/assets/plan/web/error.html index f7ed77f1d..e45166375 100644 --- a/Plan/common/src/main/resources/assets/plan/web/error.html +++ b/Plan/common/src/main/resources/assets/plan/web/error.html @@ -188,6 +188,7 @@

Player Analytics is developed by Rsl1122.

In addition following awesome people have contributed:

    +
  • aidn5
  • Argetan
  • BrainStone
  • CyanTech
  • diff --git a/Plan/common/src/main/resources/assets/plan/web/network.html b/Plan/common/src/main/resources/assets/plan/web/network.html index 30307fc8b..58e98f3b3 100644 --- a/Plan/common/src/main/resources/assets/plan/web/network.html +++ b/Plan/common/src/main/resources/assets/plan/web/network.html @@ -730,6 +730,7 @@

    Player Analytics is developed by Rsl1122.

    In addition following awesome people have contributed:

      +
    • aidn5
    • Argetan
    • Aurelien
    • BrainStone
    • diff --git a/Plan/common/src/main/resources/assets/plan/web/player.html b/Plan/common/src/main/resources/assets/plan/web/player.html index 3f979d3b7..63e4a5fc6 100644 --- a/Plan/common/src/main/resources/assets/plan/web/player.html +++ b/Plan/common/src/main/resources/assets/plan/web/player.html @@ -655,6 +655,7 @@

      Player Analytics is developed by Rsl1122.

      In addition following awesome people have contributed:

        +
      • aidn5
      • Argetan
      • Aurelien
      • BrainStone
      • diff --git a/Plan/common/src/main/resources/assets/plan/web/players.html b/Plan/common/src/main/resources/assets/plan/web/players.html index 06db87740..2cc813bcf 100644 --- a/Plan/common/src/main/resources/assets/plan/web/players.html +++ b/Plan/common/src/main/resources/assets/plan/web/players.html @@ -197,6 +197,7 @@

        Player Analytics is developed by Rsl1122.

        In addition following awesome people have contributed:

          +
        • aidn5
        • Argetan
        • Aurelien
        • BrainStone
        • diff --git a/Plan/common/src/main/resources/assets/plan/web/server.html b/Plan/common/src/main/resources/assets/plan/web/server.html index 7083ac2df..f1f757eb1 100644 --- a/Plan/common/src/main/resources/assets/plan/web/server.html +++ b/Plan/common/src/main/resources/assets/plan/web/server.html @@ -1201,6 +1201,7 @@

          Player Analytics is developed by Rsl1122.

          In addition following awesome people have contributed:

            +
          • aidn5
          • Argetan
          • Aurelien
          • BrainStone
          • diff --git a/Plan/common/src/main/resources/plugin.yml b/Plan/common/src/main/resources/plugin.yml index ddebc4394..57e091636 100644 --- a/Plan/common/src/main/resources/plugin.yml +++ b/Plan/common/src/main/resources/plugin.yml @@ -27,6 +27,7 @@ softdepend: - Towny - Vault - ViaVersion + - PlaceholderAPI commands: plan: diff --git a/Plan/config/checkstyle/checkstyle.xml b/Plan/config/checkstyle/checkstyle.xml index c832d6569..b43afdfe9 100644 --- a/Plan/config/checkstyle/checkstyle.xml +++ b/Plan/config/checkstyle/checkstyle.xml @@ -16,6 +16,12 @@ + + + + + +