Merge pull request #682 from Rsl1122/4.4.3

Pull request for 4.4.3
This commit is contained in:
Rsl1122 2018-08-11 19:19:24 +03:00 committed by GitHub
commit 385772a6a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 1011 additions and 1051 deletions

View File

@ -87,6 +87,10 @@
<exclude>org.slf4j.Logger</exclude>
</excludes>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>com.djrapitops.plan.utilities.metrics</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
@ -145,6 +149,14 @@
<id>sponge-repo</id>
<url>https://repo.spongepowered.org/maven</url>
</repository>
<repository>
<id>md_5-snapshots</id>
<url>http://repo.md-5.net/content/repositories/snapshots/</url>
</repository>
<repository>
<id>bstats-repo</id>
<url>http://repo.bstats.org/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
<dependency>
@ -347,6 +359,12 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee</artifactId>
<version>0.3.8-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
@ -370,7 +388,7 @@
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.21.0.1</version>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -24,6 +24,16 @@
<id>sponge-repo</id>
<url>https://repo.spongepowered.org/maven</url>
</repository>
<!-- RedisBungee Repository -->
<repository>
<id>md_5-snapshots</id>
<url>http://repo.md-5.net/content/repositories/snapshots/</url>
</repository>
<!-- Metrics -->
<repository>
<id>bstats-repo</id>
<url>http://repo.bstats.org/content/repositories/releases/</url>
</repository>
</repositories>
<dependencies>
@ -77,6 +87,14 @@
<type>jar</type>
<scope>provided</scope>
</dependency>
<!-- RedisBungee -->
<dependency>
<groupId>com.imaginarycode.minecraft</groupId>
<artifactId>RedisBungee</artifactId>
<version>0.3.8-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<!-- String Replacer -->
<dependency>
@ -104,6 +122,23 @@
<version>2.9.0</version>
</dependency>
<!-- Metrics -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bungeecord</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-sponge</artifactId>
<version>1.2</version>
</dependency>
<!-- Mockito (Test Dependency) -->
<dependency>
<groupId>org.mockito</groupId>
@ -116,7 +151,7 @@
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.21.0.1</version>
<version>3.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
@ -217,6 +252,10 @@
<exclude>org.slf4j.Logger</exclude>
</excludes>
</relocation>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>com.djrapitops.plan.utilities.metrics</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>

View File

@ -27,7 +27,7 @@ import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.processing.importing.ImporterManager;
import com.djrapitops.plan.system.processing.importing.importers.OfflinePlayerImporter;
import com.djrapitops.plan.system.settings.theme.PlanColorScheme;
import com.djrapitops.plan.utilities.metrics.BStats;
import com.djrapitops.plan.utilities.metrics.BStatsBukkit;
import com.djrapitops.plugin.BukkitPlugin;
import com.djrapitops.plugin.StaticHolder;
import com.djrapitops.plugin.api.Benchmark;
@ -65,13 +65,12 @@ public class Plan extends BukkitPlugin implements PlanPlugin {
try {
Benchmark.start("Enable");
system = new BukkitSystem(this);
system.enable();
locale = system.getLocaleSystem().getLocale();
system.enable();
ImporterManager.registerImporter(new OfflinePlayerImporter());
BStats bStats = new BStats(this);
bStats.registerMetrics();
new BStatsBukkit(this).registerMetrics();
Log.debug("Verbose debug messages are enabled.");
Benchmark.stop("Enable", "Enable");

View File

@ -10,6 +10,7 @@ import com.djrapitops.plan.system.BungeeSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.settings.theme.PlanColorScheme;
import com.djrapitops.plan.utilities.metrics.BStatsBungee;
import com.djrapitops.plugin.BungeePlugin;
import com.djrapitops.plugin.StaticHolder;
import com.djrapitops.plugin.api.Benchmark;
@ -40,8 +41,10 @@ public class PlanBungee extends BungeePlugin implements PlanPlugin {
super.onEnable();
try {
system = new BungeeSystem(this);
system.enable();
locale = system.getLocaleSystem().getLocale();
system.enable();
new BStatsBungee(this).registerMetrics();
Log.info(locale.getString(PluginLang.ENABLED));
} catch (AbstractMethodError e) {

View File

@ -6,6 +6,7 @@ import com.djrapitops.plan.system.SpongeSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.PluginLang;
import com.djrapitops.plan.system.settings.theme.PlanColorScheme;
import com.djrapitops.plan.utilities.metrics.BStatsSponge;
import com.djrapitops.plugin.SpongePlugin;
import com.djrapitops.plugin.StaticHolder;
import com.djrapitops.plugin.api.Benchmark;
@ -23,7 +24,7 @@ import org.spongepowered.api.plugin.Plugin;
import java.io.File;
import java.io.InputStream;
@Plugin(id = "plan", name = "Plan", version = "4.4.2", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"})
@Plugin(id = "plan", name = "Plan", version = "4.4.3", description = "Player Analytics Plugin by Rsl1122", authors = {"Rsl1122"})
public class PlanSponge extends SpongePlugin implements PlanPlugin {
@Inject
@ -52,10 +53,12 @@ public class PlanSponge extends SpongePlugin implements PlanPlugin {
@Override
public void onEnable() {
super.onEnable();
system = new SpongeSystem(this);
try {
system.enable();
system = new SpongeSystem(this);
locale = system.getLocaleSystem().getLocale();
system.enable();
new BStatsSponge().registerMetrics();
Log.info(locale.getString(PluginLang.ENABLED));
} catch (AbstractMethodError e) {

View File

@ -3,6 +3,7 @@ package com.djrapitops.plan.command;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.command.commands.*;
import com.djrapitops.plan.command.commands.manage.ManageConDebugCommand;
import com.djrapitops.plan.command.commands.manage.ManageRawDataCommand;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.DeepHelpLang;
import com.djrapitops.plan.system.settings.Permissions;
@ -41,6 +42,7 @@ public class PlanBungeeCommand extends TreeCmdNode {
};
CommandNode[] manageGroup = {
new ManageConDebugCommand(plugin),
new ManageRawDataCommand(plugin),
new BungeeSetupToggleCommand(plugin),
new ReloadCommand(plugin),
new DisableCommand(plugin),

View File

@ -28,6 +28,7 @@ public class ManageCommand extends TreeCmdNode {
super.setColorScheme(plugin.getColorScheme());
setNodeGroups(
new CommandNode[]{
new ManageRawDataCommand(plugin),
new ManageMoveCommand(plugin),
new ManageBackupCommand(plugin),
new ManageRestoreCommand(plugin),

View File

@ -10,6 +10,8 @@ import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.command.CommandNode;
import com.djrapitops.plugin.command.CommandType;
import com.djrapitops.plugin.command.ISender;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.task.RunnableFactory;
/**
* This SubCommand is used to reload the plugin.
@ -34,12 +36,17 @@ public class ReloadCommand extends CommandNode {
@Override
public void onCommand(ISender sender, String commandLabel, String[] args) {
try {
plugin.reloadPlugin(true);
} catch (Exception e) {
Log.toLog(this.getClass(), e);
sender.sendMessage(locale.getString(CommandLang.RELOAD_FAILED));
}
sender.sendMessage(locale.getString(CommandLang.RELOAD_COMPLETE));
RunnableFactory.createNew("Reload task", new AbsRunnable() {
@Override
public void run() {
try {
plugin.reloadPlugin(true);
} catch (Exception e) {
Log.toLog(this.getClass(), e);
sender.sendMessage(locale.getString(CommandLang.RELOAD_FAILED));
}
sender.sendMessage(locale.getString(CommandLang.RELOAD_COMPLETE));
}
}).runTaskAsynchronously();
}
}

View File

@ -0,0 +1,60 @@
package com.djrapitops.plan.command.commands.manage;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import com.djrapitops.plan.system.locale.Locale;
import com.djrapitops.plan.system.locale.lang.CmdHelpLang;
import com.djrapitops.plan.system.locale.lang.CommandLang;
import com.djrapitops.plan.system.locale.lang.DeepHelpLang;
import com.djrapitops.plan.system.settings.Permissions;
import com.djrapitops.plan.utilities.MiscUtils;
import com.djrapitops.plugin.command.CommandNode;
import com.djrapitops.plugin.command.CommandType;
import com.djrapitops.plugin.command.CommandUtils;
import com.djrapitops.plugin.command.ISender;
import com.djrapitops.plugin.utilities.Verify;
import java.util.Arrays;
/**
* This manage subcommand is used to remove a single player's data from the
* database.
*
* @author Rsl1122
*/
public class ManageRawDataCommand extends CommandNode {
private final Locale locale;
public ManageRawDataCommand(PlanPlugin plugin) {
super("raw", Permissions.MANAGE.getPermission(), CommandType.PLAYER_OR_ARGS);
locale = plugin.getSystem().getLocaleSystem().getLocale();
setArguments("<player>");
setShortHelp(locale.getString(CmdHelpLang.MANAGE_RAW_DATA));
setInDepthHelp(locale.getArray(DeepHelpLang.MANAGE_RAW_DATA));
}
@Override
public void onCommand(ISender sender, String commandLabel, String[] args) {
Verify.isTrue(args.length >= 1,
() -> new IllegalArgumentException(locale.getString(CommandLang.FAIL_REQ_ONE_ARG, Arrays.toString(this.getArguments()))));
String playerName = MiscUtils.getPlayerName(args, sender, Permissions.MANAGE);
sender.sendMessage(locale.getString(CommandLang.HEADER_INSPECT, playerName));
// Link
String url = ConnectionSystem.getInstance().getMainAddress() + "/player/" + playerName + "/raw";
String linkPrefix = locale.getString(CommandLang.LINK_PREFIX);
boolean console = !CommandUtils.isPlayer(sender);
if (console) {
sender.sendMessage(linkPrefix + url);
} else {
sender.sendMessage(linkPrefix);
sender.sendLink(" ", locale.getString(CommandLang.LINK_CLICK_ME), url);
}
sender.sendMessage(">");
}
}

View File

@ -37,4 +37,12 @@ public class CachingSupplier<T> implements Supplier<T> {
}
return cachedValue;
}
public boolean isCached() {
return cachedValue != null;
}
public long getCacheTime() {
return cacheTime;
}
}

View File

@ -13,7 +13,7 @@ import com.djrapitops.plan.data.store.mutators.health.HealthInformation;
import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.ServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.system.settings.theme.Theme;
import com.djrapitops.plan.system.settings.theme.ThemeVal;

View File

@ -118,4 +118,8 @@ public class DataContainer {
public void clear() {
map.clear();
}
public Map<Key, Supplier> getMap() {
return map;
}
}

View File

@ -21,7 +21,7 @@ public class Nickname implements DateHolder {
}
public String getName() {
return name;
return name.length() <= 75 ? name : name.substring(0, 74);
}
@Override

View File

@ -4,7 +4,6 @@ import com.djrapitops.plan.data.store.objects.Nickname;
import com.djrapitops.plan.system.database.databases.sql.SQLDB;
import com.djrapitops.plan.system.database.databases.sql.processing.ExecStatement;
import com.djrapitops.plan.system.database.databases.sql.processing.QueryAllStatement;
import com.djrapitops.plan.system.database.databases.sql.tables.GeoInfoTable;
import com.djrapitops.plan.system.database.databases.sql.tables.NicknamesTable;
import com.djrapitops.plan.system.database.databases.sql.tables.UserIDTable;
@ -21,7 +20,7 @@ public class NicknameLastSeenPatch extends Patch {
@Override
public boolean hasBeenApplied() {
return hasColumn(GeoInfoTable.TABLE_NAME, GeoInfoTable.Col.LAST_USED.get());
return hasColumn(NicknamesTable.TABLE_NAME, NicknamesTable.Col.LAST_USED.get());
}
@Override

View File

@ -10,6 +10,7 @@ import com.djrapitops.plan.system.info.request.InfoRequestWithVariables;
import com.djrapitops.plan.system.info.server.Server;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.MiscUtils;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.utilities.Verify;
import org.apache.http.client.config.RequestConfig;
@ -29,6 +30,7 @@ import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
/**
@ -42,6 +44,17 @@ public class ConnectionOut {
private final UUID serverUUID;
private final InfoRequest infoRequest;
static {
try {
Properties properties = System.getProperties();
properties.setProperty("sun.net.client.defaultConnectTimeout", Long.toString(TimeAmount.MINUTE.ms()));
properties.setProperty("sun.net.client.defaultReadTimeout", Long.toString(TimeAmount.MINUTE.ms()));
properties.setProperty("sun.net.http.retryPost", Boolean.toString(false));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Constructor.
*
@ -118,10 +131,12 @@ public class ConnectionOut {
private void prepareRequest(HttpPost post, String parameters) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(10000)
.setConnectTimeout(9000)
.setRedirectsEnabled(true)
.setRelativeRedirectsAllowed(true)
.setContentCompressionEnabled(true)
.setMaxRedirects(1)
.build();
post.setConfig(requestConfig);

View File

@ -12,7 +12,10 @@ import com.djrapitops.plan.system.webserver.response.DefaultResponses;
import com.djrapitops.plan.system.webserver.response.Response;
import com.djrapitops.plan.system.webserver.response.api.BadRequestResponse;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.TimeAmount;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.task.RunnableFactory;
import com.djrapitops.plugin.utilities.Verify;
import java.util.Map;
@ -64,7 +67,12 @@ public class SaveDBSettingsRequest extends InfoRequestWithVariables implements S
Log.info("----------------------------------");
return DefaultResponses.SUCCESS.get();
} finally {
PlanPlugin.getInstance().reloadPlugin(true);
RunnableFactory.createNew("Bungee Setup Restart Task", new AbsRunnable() {
@Override
public void run() {
PlanPlugin.getInstance().reloadPlugin(true);
}
}).runTaskLater(TimeAmount.SECOND.ticks() * 2L);
}
}

View File

@ -56,6 +56,9 @@ public class SendDBSettingsRequest extends InfoRequestWithVariables implements S
if (Check.isBukkitAvailable()) {
return new BadRequestResponse("Not supposed to be called on a Bukkit server");
}
if (Check.isSpongeAvailable()) {
return new BadRequestResponse("Not supposed to be called on a Sponge server");
}
String address = variables.get("address");
Verify.nullCheck(address, () -> new BadRequestException("WebServer Address ('address') not specified in the request."));

View File

@ -9,6 +9,8 @@ import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.file.FileSystem;
import com.djrapitops.plan.system.info.server.properties.BukkitServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.system.webserver.WebServerSystem;
@ -29,7 +31,7 @@ public class BukkitServerInfo extends ServerInfo {
private Database database;
public BukkitServerInfo(Plan plugin) {
this(new ServerProperties(plugin.getServer()));
this(new BukkitServerProperties(plugin.getServer()));
}
public BukkitServerInfo(ServerProperties serverProperties) {

View File

@ -8,6 +8,8 @@ import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.properties.BungeeServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.webserver.WebServerSystem;
import com.djrapitops.plugin.api.utility.log.Log;
@ -22,7 +24,7 @@ import java.util.UUID;
public class BungeeServerInfo extends ServerInfo {
public BungeeServerInfo(PlanBungee plugin) {
super(new ServerProperties(plugin.getProxy()));
super(new BungeeServerProperties(plugin.getProxy()));
}
@Override

View File

@ -7,6 +7,7 @@ package com.djrapitops.plan.system.info.server;
import com.djrapitops.plan.api.exceptions.EnableException;
import com.djrapitops.plan.system.PlanSystem;
import com.djrapitops.plan.system.SubSystem;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plugin.utilities.Verify;
import java.util.UUID;

View File

@ -1,10 +1,11 @@
package com.djrapitops.plan.system.info.server;
import com.djrapitops.plan.system.info.server.properties.SpongeServerProperties;
import org.spongepowered.api.Sponge;
public class SpongeServerInfo extends BukkitServerInfo {
public SpongeServerInfo() {
super(new ServerProperties(Sponge.getGame()));
super(new SpongeServerProperties(Sponge.getGame()));
}
}

View File

@ -0,0 +1,25 @@
package com.djrapitops.plan.system.info.server.properties;
import org.bukkit.Server;
/**
* ServerProperties for Bukkit.
*
* @author Rsl1122
*/
public class BukkitServerProperties extends ServerProperties {
public BukkitServerProperties(Server server) {
super(
server.getServerId(),
server.getName(),
server.getPort(),
server.getVersion(),
server.getBukkitVersion(),
server::getIp,
server.getMaxPlayers(),
() -> server.getOnlinePlayers().size()
);
}
}

View File

@ -0,0 +1,27 @@
package com.djrapitops.plan.system.info.server.properties;
import com.djrapitops.plan.system.settings.Settings;
import net.md_5.bungee.api.ProxyServer;
/**
* ServerProperties for Bungee.
* <p>
* Supports RedisBungee for Players online getting.
*
* @author Rsl1122
*/
public class BungeeServerProperties extends ServerProperties {
public BungeeServerProperties(ProxyServer server) {
super(
server.getServers().toString(),
"BungeeCord",
-1,
server.getVersion(),
server.getVersion(),
Settings.BUNGEE_IP::toString,
server.getConfig().getPlayerLimit(),
RedisCheck.isClassAvailable() ? new RedisPlayersOnlineSupplier() : server::getOnlineCount
);
}
}

View File

@ -0,0 +1,19 @@
package com.djrapitops.plan.system.info.server.properties;
/**
* Utility class for checking if RedisBungee API is available.
*
* @author Rsl1122
*/
public class RedisCheck {
public static boolean isClassAvailable() {
try {
Class.forName("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}

View File

@ -0,0 +1,18 @@
package com.djrapitops.plan.system.info.server.properties;
import com.imaginarycode.minecraft.redisbungee.RedisBungee;
import java.util.function.Supplier;
/**
* Players online supplier when using RedisBungee.
*
* @author Rsl1122
*/
public class RedisPlayersOnlineSupplier implements Supplier<Integer> {
@Override
public Integer get() {
return RedisBungee.getApi().getPlayerCount();
}
}

View File

@ -1,8 +1,5 @@
package com.djrapitops.plan.system.info.server;
package com.djrapitops.plan.system.info.server.properties;
import com.djrapitops.plan.system.settings.Settings;
import net.md_5.bungee.api.ProxyServer;
import org.bukkit.Server;
import org.spongepowered.api.Game;
import java.net.InetSocketAddress;
@ -15,7 +12,7 @@ import java.util.function.Supplier;
* @author Rsl1122
* @since 3.4.1
*/
public class ServerProperties {
public abstract class ServerProperties {
private final String id;
private final String name;
@ -27,30 +24,18 @@ public class ServerProperties {
private final Supplier<Integer> onlinePlayers;
public ServerProperties(Server server) {
id = server.getServerId();
ip = server::getIp;
name = server.getName();
port = server.getPort();
version = server.getVersion();
implVersion = server.getBukkitVersion();
maxPlayers = server.getMaxPlayers();
onlinePlayers = () -> server.getOnlinePlayers().size();
}
public ServerProperties(ProxyServer server) {
id = server.getServers().toString();
ip = Settings.BUNGEE_IP::toString;
name = "BungeeCord";
port = -1;
version = server.getVersion();
implVersion = server.getVersion();
maxPlayers = server.getConfig().getPlayerLimit();
onlinePlayers = server::getOnlineCount;
protected ServerProperties(
String id, String name, int port,
String version, String implVersion,
Supplier<String> ip, int maxPlayers, Supplier<Integer> onlinePlayers) {
this.id = id;
this.name = name;
this.port = port;
this.version = version;
this.implVersion = implVersion;
this.ip = ip;
this.maxPlayers = maxPlayers;
this.onlinePlayers = onlinePlayers;
}
public ServerProperties(Game game) {

View File

@ -0,0 +1,28 @@
package com.djrapitops.plan.system.info.server.properties;
import org.spongepowered.api.Game;
import java.net.InetSocketAddress;
/**
* ServerProperties for Sponge.
*
* @author Rsl1122
*/
public class SpongeServerProperties extends ServerProperties {
public SpongeServerProperties(Game game) {
super(
game.getServer().getMotd().toPlain(),
"Sponge",
game.getServer().getBoundAddress().orElseGet(() -> new InetSocketAddress(25565)).getPort(),
game.getPlatform().getMinecraftVersion().getName(),
game.getPlatform().getMinecraftVersion().getName(),
() -> game.getServer().getBoundAddress()
.orElseGet(() -> new InetSocketAddress(25565))
.getAddress().getHostAddress(),
game.getServer().getMaxPlayers(),
() -> game.getServer().getOnlinePlayers().size()
);
}
}

View File

@ -8,6 +8,7 @@ import com.djrapitops.plan.system.processing.processors.info.PlayerPageUpdatePro
import com.djrapitops.plan.system.processing.processors.player.*;
import com.djrapitops.plugin.api.systems.NotificationCenter;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.task.RunnableFactory;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@ -96,13 +97,13 @@ public class PlayerOnlineListener implements Listener {
SessionCache.getInstance().cacheSession(uuid, new Session(uuid, time, world, gm));
Processing.submit(
RunnableFactory.createNew("Player Register: " + uuid,
new RegisterProcessor(uuid, player::getFirstPlayed, playerName,
new IPUpdateProcessor(uuid, address, time),
new NameProcessor(uuid, playerName, displayName),
new PlayerPageUpdateProcessor(uuid)
)
);
).runTaskAsynchronously();
Processing.submit(new NetworkPageUpdateProcessor());
}

View File

@ -8,6 +8,7 @@ import com.djrapitops.plan.system.processing.processors.info.PlayerPageUpdatePro
import com.djrapitops.plan.system.processing.processors.player.*;
import com.djrapitops.plugin.api.systems.NotificationCenter;
import com.djrapitops.plugin.api.utility.log.Log;
import com.djrapitops.plugin.task.RunnableFactory;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.entity.living.player.Player;
@ -99,13 +100,13 @@ public class SpongePlayerListener {
SessionCache.getInstance().cacheSession(uuid, new Session(uuid, time, world, gm));
Processing.submit(
RunnableFactory.createNew("Player Register: " + uuid,
new RegisterProcessor(uuid, () -> time, playerName,
new IPUpdateProcessor(uuid, address, time),
new NameProcessor(uuid, playerName, displayName),
new PlayerPageUpdateProcessor(uuid)
)
);
).runTaskAsynchronously();
Processing.submit(new NetworkPageUpdateProcessor());
}

View File

@ -39,7 +39,8 @@ public enum CmdHelpLang implements Lang {
WEB_LEVEL("Command Help - /plan web level", "Information about permission levels"),
WEB_LIST("Command Help - /plan web list", "List Web Users"),
WEB_CHECK("Command Help - /plan web check", "Inspect a Web User"),
WEB_DELETE("Command Help - /plan web delete", "Delete a Web User");
WEB_DELETE("Command Help - /plan web delete", "Delete a Web User"),
MANAGE_RAW_DATA("Command Help - /plan manage raw", "View raw JSON of player data");
private final String identifier;
private final String defaultValue;

View File

@ -31,7 +31,8 @@ public enum DeepHelpLang implements Lang {
MANAGE_RESTORE("In Depth Help - /plan manage restore ?", "> §2Restore Subcommand\\ Restore a previous backup SQLite database (.db file)\\ You can also restore database.db from another server to MySQL.\\ Target database is cleared before transfer."),
MANAGE_SETUP("In Depth Help - /plan manage setup ?", "> §2Setup Subcommand\\ Set-up a connection between Bungee and this server for network functionality.\\ BungeeAddress can be found in the enable log on console when Plan enables on Bungee."),
WEB_REGISTER("In Depth Help - /plan web register ?", "> §2Register Subcommand\\ Registers a new Web User.\\ Registering a user for another player requires plan.webmanage permission.\\ Passwords are hashed with PBKDF2 (64,000 iterations of SHA1) using a cryptographically-random salt.");
WEB_REGISTER("In Depth Help - /plan web register ?", "> §2Register Subcommand\\ Registers a new Web User.\\ Registering a user for another player requires plan.webmanage permission.\\ Passwords are hashed with PBKDF2 (64,000 iterations of SHA1) using a cryptographically-random salt."),
MANAGE_RAW_DATA("In Depth Help - /plan manage raw ?", "> §2Raw Data Subcommand\\ Displays link to raw JSON data page.\\ Not available if Plan webserver is not enabled.");
private final String identifier;
private final String defaultValue;

View File

@ -143,6 +143,16 @@ public class Processing implements SubSystem {
Log.toLog(this.getClass(), e);
}
}
if (!nonCriticalExecutor.isTerminated()) {
try {
nonCriticalExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
nonCriticalExecutor.shutdownNow();
}
}
if (!criticalExecutor.isTerminated()) {
criticalExecutor.shutdownNow();
}
Log.info(locale.get().getString(PluginLang.DISABLED_PROCESSING_COMPLETE));
}
}

View File

@ -5,8 +5,8 @@
package com.djrapitops.plan.system.processing.processors.player;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.processing.CriticalRunnable;
import com.djrapitops.plan.system.processing.Processing;
import com.djrapitops.plugin.task.AbsRunnable;
import com.djrapitops.plugin.utilities.Verify;
import java.util.UUID;
@ -17,7 +17,7 @@ import java.util.function.Supplier;
*
* @author Rsl1122
*/
public class RegisterProcessor implements CriticalRunnable {
public class RegisterProcessor extends AbsRunnable {
private final UUID uuid;
private final Supplier<Long> registered;
@ -25,6 +25,7 @@ public class RegisterProcessor implements CriticalRunnable {
private final Runnable[] afterProcess;
public RegisterProcessor(UUID uuid, Supplier<Long> registered, String name, Runnable... afterProcess) {
super(RegisterProcessor.class.getSimpleName());
this.uuid = uuid;
this.registered = registered;
this.name = name;
@ -46,6 +47,7 @@ public class RegisterProcessor implements CriticalRunnable {
for (Runnable runnable : afterProcess) {
Processing.submit(runnable);
}
cancel();
}
}
}

View File

@ -3,6 +3,7 @@ package com.djrapitops.plan.system.tasks.bungee;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.container.builders.TPSBuilder;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.tasks.TPSCountTimer;
public class BungeeTPSCountTimer extends TPSCountTimer<PlanBungee> {
@ -13,7 +14,7 @@ public class BungeeTPSCountTimer extends TPSCountTimer<PlanBungee> {
@Override
public void addNewTPSEntry(long nanoTime, long now) {
int onlineCount = plugin.getProxy().getOnlineCount();
int onlineCount = ServerInfo.getServerProperties().getOnlinePlayers();
TPS tps = TPSBuilder.get()
.date(now)
.skipTPS()

View File

@ -19,6 +19,7 @@ import com.djrapitops.plan.system.webserver.response.cache.PageId;
import com.djrapitops.plan.system.webserver.response.cache.ResponseCache;
import com.djrapitops.plan.system.webserver.response.errors.NotFoundResponse;
import com.djrapitops.plan.system.webserver.response.pages.InspectPageResponse;
import com.djrapitops.plan.system.webserver.response.pages.RawPlayerDataResponse;
import com.djrapitops.plan.utilities.uuid.UUIDUtility;
import java.util.List;
@ -41,17 +42,17 @@ public class PlayerPageHandler extends PageHandler {
UUID uuid = UUIDUtility.getUUIDOf(playerName);
Locale locale = request.getLocale();
boolean raw = target.size() >= 2 && target.get(1).equalsIgnoreCase("raw");
if (uuid == null) {
return notFound(locale.getString(ErrorPageLang.UUID_404));
}
try {
if (Database.getActive().check().isPlayerRegistered(uuid)) {
Response response = ResponseCache.loadResponse(PageId.PLAYER.of(uuid));
if (!(response instanceof InspectPageResponse)) {
InfoSystem.getInstance().generateAndCachePlayerPage(uuid);
response = ResponseCache.loadResponse(PageId.PLAYER.of(uuid));
if (raw) {
return ResponseCache.loadResponse(PageId.RAW_PLAYER.of(uuid), () -> new RawPlayerDataResponse(uuid));
}
return response != null ? response : notFound(locale.getString(ErrorPageLang.NO_SERVERS_404));
return playerResponseOrNotFound(uuid, locale);
} else {
return notFound(locale.getString(ErrorPageLang.NOT_PLAYED_404));
}
@ -61,6 +62,15 @@ public class PlayerPageHandler extends PageHandler {
return InspectPageResponse.getRefreshing();
}
private Response playerResponseOrNotFound(UUID uuid, Locale locale) throws WebException {
Response response = ResponseCache.loadResponse(PageId.PLAYER.of(uuid));
if (!(response instanceof InspectPageResponse)) {
InfoSystem.getInstance().generateAndCachePlayerPage(uuid);
response = ResponseCache.loadResponse(PageId.PLAYER.of(uuid));
}
return response != null ? response : notFound(locale.getString(ErrorPageLang.NO_SERVERS_404));
}
private Response notFound(String error) {
return ResponseCache.loadResponse(PageId.NOT_FOUND.of(error), () -> new NotFoundResponse(error));
}

View File

@ -15,6 +15,7 @@ import com.djrapitops.plan.system.webserver.response.Response;
import com.djrapitops.plan.system.webserver.response.cache.PageId;
import com.djrapitops.plan.system.webserver.response.cache.ResponseCache;
import com.djrapitops.plan.system.webserver.response.pages.AnalysisPageResponse;
import com.djrapitops.plan.system.webserver.response.pages.RawServerDataResponse;
import com.djrapitops.plugin.api.Check;
import java.util.List;
@ -31,6 +32,12 @@ public class ServerPageHandler extends PageHandler {
@Override
public Response getResponse(Request request, List<String> target) {
UUID serverUUID = getServerUUID(target);
boolean raw = target.size() >= 2 && target.get(1).equalsIgnoreCase("raw");
if (raw) {
return ResponseCache.loadResponse(PageId.RAW_SERVER.of(serverUUID), () -> new RawServerDataResponse(serverUUID));
}
Response response = ResponseCache.loadResponse(PageId.SERVER.of(serverUUID));
if (response != null) {

View File

@ -14,7 +14,9 @@ import java.util.UUID;
public enum PageId {
SERVER("serverPage:"),
RAW_SERVER("rawServer:"),
PLAYER("playerPage:"),
RAW_PLAYER("rawPlayer:"),
PLAYERS("playersPage"),
ERROR("error:"),

View File

@ -4,6 +4,7 @@ import com.djrapitops.plan.system.webserver.response.Response;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
/**
@ -93,4 +94,7 @@ public class ResponseCache {
cache.clear();
}
public static Set<String> getCacheKeys() {
return cache.keySet();
}
}

View File

@ -5,19 +5,28 @@
package com.djrapitops.plan.system.webserver.response.pages;
import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.CachingSupplier;
import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.data.store.objects.DateHolder;
import com.djrapitops.plan.system.cache.CacheSystem;
import com.djrapitops.plan.system.cache.SessionCache;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.connection.ConnectionLog;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import com.djrapitops.plan.system.info.server.Server;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.ServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.system.webserver.response.cache.ResponseCache;
import com.djrapitops.plan.system.webserver.response.errors.ErrorResponse;
import com.djrapitops.plan.utilities.file.FileUtil;
import com.djrapitops.plan.utilities.html.Html;
import com.djrapitops.plan.utilities.html.HtmlStructure;
import com.djrapitops.plan.utilities.html.icon.Icon;
import com.djrapitops.plan.utilities.html.structure.TabsElement;
import com.djrapitops.plugin.api.Benchmark;
import com.djrapitops.plugin.api.utility.log.ErrorLogger;
import com.djrapitops.plugin.api.utility.log.Log;
@ -28,6 +37,7 @@ import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.nio.charset.Charset;
import java.util.*;
import java.util.function.Supplier;
/**
* WebServer response for /debug-page used for easing issue reporting.
@ -39,27 +49,132 @@ public class DebugPageResponse extends ErrorResponse {
public DebugPageResponse() {
super.setHeader("HTTP/1.1 200 OK");
super.setTitle(Icon.called("bug") + " Debug Information");
super.setParagraph(buildParagraph());
super.setParagraph(buildContent());
replacePlaceholders();
}
private String buildParagraph() {
StringBuilder content = new StringBuilder();
private String buildContent() {
StringBuilder preContent = new StringBuilder();
String issueLink = Html.LINK_EXTERNAL.parse("https://github.com/Rsl1122/Plan-PlayerAnalytics/issues/new", "Create new issue on Github");
// Information
content.append("<p>")
.append(issueLink).append("<br><br>")
.append("This page contains debug information for an issue ticket.<br>You can copy it directly into the issue, the info is pre-formatted.")
String hastebinLink = Html.LINK_EXTERNAL.parse("https://hastebin.com/", "Create a new hastebin paste");
preContent.append("<p>")
.append(HtmlStructure.separateWithDots(issueLink, hastebinLink)).append("<br><br>")
.append("This page contains debug information for an issue ticket. You can copy it directly into the issue, the info is pre-formatted.")
.append("</p>");
appendServerInformation(content);
TabsElement.Tab info = new TabsElement.Tab(Icon.called("server") + " Server Information", createServerInfoContent());
TabsElement.Tab errors = new TabsElement.Tab(Icon.called("exclamation-circle") + " Errors", createErrorContent());
TabsElement.Tab debugLog = new TabsElement.Tab(Icon.called("bug") + " Debug Log", createDebugLogContent());
TabsElement.Tab config = new TabsElement.Tab(Icon.called("cogs") + " Plan Config", createConfigContent());
TabsElement.Tab caches = new TabsElement.Tab(Icon.called("archive") + " Plan Caches", createCacheContent());
appendConnectionLog(content);
appendLoggedErrors(content);
appendBenchmarks(content);
appendDebugLog(content);
TabsElement tabs = new TabsElement(info, errors, debugLog, config, caches);
return preContent + tabs.toHtmlFull();
}
private String createCacheContent() {
StringBuilder content = new StringBuilder();
appendResponseCache(content);
appendSessionCache(content);
appendDataContainerCache(content);
return content.toString();
}
private void appendResponseCache(StringBuilder content) {
try {
content.append("<pre>### Cached Responses:<br><br>");
List<String> cacheKeys = new ArrayList<>(ResponseCache.getCacheKeys());
if (cacheKeys.isEmpty()) {
content.append("Empty");
}
Collections.sort(cacheKeys);
for (String cacheKey : cacheKeys) {
content.append("- ").append(cacheKey).append("<br>");
}
content.append("</pre>");
} catch (Exception e) {
Log.toLog(this.getClass(), e);
}
}
private void appendSessionCache(StringBuilder content) {
try {
content.append("<pre>### Session Cache:<br><br>");
content.append("UUID | Session Started <br>")
.append("-- | -- <br>");
Formatter<Long> timeStamp = Formatters.yearLongValue();
Set<Map.Entry<UUID, Session>> sessions = SessionCache.getActiveSessions().entrySet();
if (sessions.isEmpty()) {
content.append("Empty");
}
for (Map.Entry<UUID, Session> entry : sessions) {
UUID uuid = entry.getKey();
String start = entry.getValue().getValue(SessionKeys.START).map(timeStamp).orElse("Unknown");
content.append(uuid.toString()).append(" | ").append(start).append("<br>");
}
content.append("</pre>");
} catch (Exception e) {
Log.toLog(this.getClass(), e);
}
}
private void appendDataContainerCache(StringBuilder content) {
try {
content.append("<pre>### DataContainer Cache:<br><br>");
content.append("Key | Is Cached | Cache Time <br>")
.append("-- | -- | -- <br>");
Formatter<Long> timeStamp = Formatters.yearLongValue();
Set<Map.Entry<Key, Supplier>> dataContainers = CacheSystem.getInstance().getDataContainerCache().getMap().entrySet();
if (dataContainers.isEmpty()) {
content.append("Empty");
}
for (Map.Entry<Key, Supplier> entry : dataContainers) {
String keyName = entry.getKey().getKeyName();
Supplier supplier = entry.getValue();
if (supplier instanceof CachingSupplier) {
CachingSupplier cachingSupplier = (CachingSupplier) supplier;
boolean isCached = cachingSupplier.isCached();
String cacheText = isCached ? "Yes" : "No";
String cacheTime = isCached ? timeStamp.apply(cachingSupplier.getCacheTime()) : "-";
content.append(keyName).append(" | ").append(cacheText).append(" | ").append(cacheTime).append("<br>");
} else {
content.append(keyName).append(" | ").append("Non-caching Supplier").append(" | ").append("-").append("<br>");
}
}
content.append("</pre>");
} catch (Exception e) {
Log.toLog(this.getClass(), e);
}
}
private String createConfigContent() {
StringBuilder content = new StringBuilder();
appendConfig(content);
return content.toString();
}
private String createDebugLogContent() {
StringBuilder content = new StringBuilder();
appendDebugLog(content);
return content.toString();
}
private String createErrorContent() {
StringBuilder content = new StringBuilder();
appendLoggedErrors(content);
return content.toString();
}
private String createServerInfoContent() {
StringBuilder content = new StringBuilder();
appendServerInformation(content);
appendConnectionLog(content);
appendBenchmarks(content);
return content.toString();
}
@ -68,12 +183,15 @@ public class DebugPageResponse extends ErrorResponse {
try {
Map<String, Map<String, ConnectionLog.Entry>> logEntries = ConnectionLog.getLogEntries();
content.append("<pre>**Connection Log:**<br>");
content.append("<pre>### Connection Log:<br><br>");
content.append("Server Address | Request Type | Response | Sent<br>")
.append("-- | -- | -- | --<br>");
Formatter<DateHolder> formatter = Formatters.second();
if (logEntries.isEmpty()) {
content.append("**No Connections Logged**<br>");
}
for (Map.Entry<String, Map<String, ConnectionLog.Entry>> entry : logEntries.entrySet()) {
String address = entry.getKey();
Map<String, ConnectionLog.Entry> requests = entry.getValue();
@ -90,7 +208,7 @@ public class DebugPageResponse extends ErrorResponse {
}
content.append("</pre>");
content.append("<pre>**Servers:**<br>");
content.append("<pre>### Servers:<br><br>");
List<Server> servers = ConnectionSystem.getInstance().getBukkitServers();
content.append("Server Name | Address | UUID <br>")
.append("-- | -- | --<br>");
@ -195,10 +313,9 @@ public class DebugPageResponse extends ErrorResponse {
}
}
for (String error : errorLines) {
content.append("&#96;&#96;&#96;<br>")
content.append("</pre><pre>&#96;&#96;&#96;<br>")
.append(error)
.append("&#96;&#96;&#96;<br>")
.append("- [ ] Fixed<br>");
.append("&#96;&#96;&#96;");
}
} else {
content.append("**No Errors logged.**<br>");

View File

@ -0,0 +1,71 @@
package com.djrapitops.plan.system.webserver.response.pages;
import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.system.webserver.response.Response;
import com.djrapitops.plan.system.webserver.response.ResponseType;
import com.google.gson.Gson;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Response for sending raw data as JSON when it is inside a DataContainer.
* <p>
* This transform class is required to remove Key-Supplier object pollution in the resulting JSON, as well as to remove
* the effects of the caching layer.
*
* @author Rsl1122
*/
public class RawDataResponse extends Response {
public RawDataResponse(DataContainer dataContainer) {
super(ResponseType.JSON);
Map<String, Object> values = mapToNormalMap(dataContainer);
super.setHeader("HTTP/1.1 200 OK");
Gson gson = new Gson();
super.setContent(gson.toJson(values));
}
private Map<String, Object> mapToNormalMap(DataContainer player) {
Map<String, Object> values = new HashMap<>();
player.getMap().forEach((key, value) ->
{
Object object = value.get();
if (object instanceof DataContainer) {
object = mapToNormalMap((DataContainer) object);
}
if (object instanceof Map) {
object = handleMap((Map) object);
}
if (object instanceof List) {
object = handleList((List) object);
}
values.put(key.getKeyName(), object);
}
);
return values;
}
private List handleList(List object) {
List<Object> list = object;
if (list.stream().findAny().orElse(null) instanceof DataContainer) {
return list.stream().map((obj) -> mapToNormalMap((DataContainer) obj)).collect(Collectors.toList());
}
return list;
}
private Map handleMap(Map object) {
Map<Object, Object> map = object;
if (map.values().stream().findAny().orElse(null) instanceof DataContainer) {
Map<Object, Object> newMap = new HashMap<>();
map.forEach((key, value) -> newMap.put(key, mapToNormalMap((DataContainer) value)));
return newMap;
}
return map;
}
}

View File

@ -0,0 +1,17 @@
package com.djrapitops.plan.system.webserver.response.pages;
import com.djrapitops.plan.system.cache.CacheSystem;
import java.util.UUID;
/**
* Raw Data JSON response for a Player.
*
* @author Rsl1122
*/
public class RawPlayerDataResponse extends RawDataResponse {
public RawPlayerDataResponse(UUID uuid) {
super(CacheSystem.getInstance().getDataContainerCache().getPlayerContainer(uuid));
}
}

View File

@ -0,0 +1,17 @@
package com.djrapitops.plan.system.webserver.response.pages;
import com.djrapitops.plan.system.cache.CacheSystem;
import java.util.UUID;
/**
* Raw Data JSON response for a Server.
*
* @author Rsl1122
*/
public class RawServerDataResponse extends RawDataResponse {
public RawServerDataResponse(UUID serverUUID) {
super(CacheSystem.getInstance().getDataContainerCache().getAnalysisContainer(serverUUID).getServerContainer());
}
}

View File

@ -8,7 +8,7 @@ import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.server.Server;
import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.info.server.ServerProperties;
import com.djrapitops.plan.system.info.server.properties.ServerProperties;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.html.graphs.line.OnlineActivityGraph;
import com.djrapitops.plan.utilities.html.icon.Color;

View File

@ -19,6 +19,11 @@ public class TabsElement {
this.tabs = tabs;
}
public String toHtmlFull() {
String[] navAndContent = toHtml();
return navAndContent[0] + navAndContent[1];
}
public String[] toHtml() {
StringBuilder nav = new StringBuilder();
StringBuilder content = new StringBuilder();

View File

@ -1,73 +0,0 @@
package com.djrapitops.plan.utilities.metrics;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.system.webserver.WebServer;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.utility.log.Log;
import java.util.HashMap;
import java.util.Map;
public class BStats {
private final Plan plugin;
private Metrics metrics;
public BStats(Plan plugin) {
this.plugin = plugin;
}
public void registerMetrics() {
Log.logDebug("Enable", "Enabling bStats Metrics.");
if (metrics == null) {
metrics = new Metrics(plugin);
}
registerConfigSettingGraphs();
}
private void registerConfigSettingGraphs() {
String serverType = plugin.getServer().getName();
if ("CraftBukkit".equals(serverType) && Check.isSpigotAvailable()) {
serverType = "Spigot";
}
String databaseType = Database.getActive().getName();
String analysisRefreshPeriod = Integer.toString(Settings.ANALYSIS_AUTO_REFRESH.getNumber());
String themeBase = Settings.THEME_BASE.toString();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
addStringSettingPie("analysis_periodic_refresh", analysisRefreshPeriod);
addStringSettingPie("theme_base", themeBase);
addFeatureBarChart("features");
}
private void addStringSettingPie(String id, String setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, () -> setting));
}
private void addFeatureBarChart(String id) {
metrics.addCustomChart(new Metrics.AdvancedBarChart(id, () -> {
Map<String, int[]> map = new HashMap<>();
map.put("HTTPS", isEnabled("HTTPS".equals(WebServer.getInstance().getProtocol().toUpperCase())));
map.put("HTML Export", isEnabled(Settings.ANALYSIS_EXPORT.isTrue()));
boolean isConnectedToBungee = ConnectionSystem.getInstance().isServerAvailable();
map.put("BungeeCord Connected", isEnabled(isConnectedToBungee));
if (isConnectedToBungee) {
map.put("Copy Bungee Config Values", isEnabled(Settings.BUNGEE_COPY_CONFIG.isTrue()));
map.put("Standalone Override", isEnabled(Settings.BUNGEE_OVERRIDE_STANDALONE_MODE.isTrue()));
}
map.put("Log Unknown Commands", isEnabled(Settings.LOG_UNKNOWN_COMMANDS.isTrue()));
map.put("Combine Command Aliases", isEnabled(Settings.COMBINE_COMMAND_ALIASES.isTrue()));
return map;
}));
}
private int[] isEnabled(boolean t) {
return t ? new int[]{1, 0} : new int[]{0, 1};
}
}

View File

@ -0,0 +1,40 @@
package com.djrapitops.plan.utilities.metrics;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plugin.api.Check;
import com.djrapitops.plugin.api.utility.log.Log;
import org.bstats.bukkit.Metrics;
public class BStatsBukkit {
private final Plan plugin;
private Metrics metrics;
public BStatsBukkit(Plan plugin) {
this.plugin = plugin;
}
public void registerMetrics() {
Log.logDebug("Enable", "Enabling bStats Metrics.");
if (metrics == null) {
metrics = new Metrics(plugin);
}
registerConfigSettingGraphs();
}
private void registerConfigSettingGraphs() {
String serverType = plugin.getServer().getName();
if ("CraftBukkit".equals(serverType) && Check.isSpigotAvailable()) {
serverType = "Spigot";
}
String databaseType = Database.getActive().getName();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
}
protected void addStringSettingPie(String id, String setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, () -> setting));
}
}

View File

@ -0,0 +1,40 @@
package com.djrapitops.plan.utilities.metrics;
import com.djrapitops.plan.PlanBungee;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import com.djrapitops.plugin.api.utility.log.Log;
import org.bstats.bungeecord.Metrics;
import java.io.Serializable;
public class BStatsBungee {
private final PlanBungee plugin;
private Metrics metrics;
public BStatsBungee(PlanBungee plugin) {
this.plugin = plugin;
}
public void registerMetrics() {
Log.logDebug("Enable", "Enabling bStats Metrics.");
if (metrics == null) {
metrics = new Metrics(plugin);
}
registerConfigSettingGraphs();
}
private void registerConfigSettingGraphs() {
String serverType = plugin.getProxy().getName();
String databaseType = Database.getActive().getName();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
addStringSettingPie("network_servers", ConnectionSystem.getInstance().getBukkitServers().size());
}
protected void addStringSettingPie(String id, Serializable setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, setting::toString));
}
}

View File

@ -0,0 +1,35 @@
package com.djrapitops.plan.utilities.metrics;
import com.djrapitops.plan.system.database.databases.Database;
import com.djrapitops.plan.system.info.connection.ConnectionSystem;
import com.djrapitops.plugin.api.utility.log.Log;
import org.bstats.sponge.Metrics;
import javax.inject.Inject;
import java.io.Serializable;
public class BStatsSponge {
@Inject
private Metrics metrics;
public void registerMetrics() {
Log.logDebug("Enable", "Enabling bStats Metrics.");
if (metrics != null) {
registerConfigSettingGraphs();
}
}
private void registerConfigSettingGraphs() {
String serverType = "Sponge";
String databaseType = Database.getActive().getName();
addStringSettingPie("server_type", serverType);
addStringSettingPie("database_type", databaseType);
addStringSettingPie("network_servers", ConnectionSystem.getInstance().getBukkitServers().size());
}
protected void addStringSettingPie(String id, Serializable setting) {
metrics.addCustomChart(new Metrics.SimplePie(id, setting::toString));
}
}

View File

@ -1,627 +0,0 @@
package com.djrapitops.plan.utilities.metrics;
import com.djrapitops.plugin.api.TimeAmount;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
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.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
* <p>
* Condition out https://bStats.org/ to learn more about bStats!
*/
@SuppressWarnings("unchecked")
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/bukkit";
// Should failed requests be logged?
private static boolean logFailedRequests;
// The uuid of the server
private static String serverUUID;
static {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D
final String defaultPackage = new String(
new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure nobody just copy & pastes the example and use the wrong package names
if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
// The plugin
private final JavaPlugin plugin;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
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);
// Condition 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) {
/* 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) {
/* Ignored */
}
}
// Register our service
Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal);
if (!found) {
// We are the first!
startSubmitting();
}
}
}
/**
* 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(StandardCharsets.UTF_8));
gzip.close();
return outputStream.toByteArray();
}
/**
* 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 Metrics metrics = this;
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, metrics::submitData);
}
}, TimeAmount.MINUTE.ms() * 5, TimeAmount.MINUTE.ms() * 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;
try {
// Around MC 1.8 the return type was changed to a collection from an array,
// This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
}
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 :)
for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
try {
pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider()));
} catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) {
/* Ignored */
}
}
} catch (NoSuchFieldException ignored) {
/* Ignored */
}
}
data.put("plugins", pluginData);
// Create a new thread for the connection to the bStats server
new Thread(() -> {
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();
}
public abstract static class CustomChart {
final String chartId;
CustomChart(String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
private 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 (Exception e) {
if (logFailedRequests) {
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, e);
}
return null;
}
return chart;
}
protected abstract JSONObject getChartData() throws Exception;
}
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
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;
}
}
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JSONObject value = new JSONObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
value.put(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
values.put(entryValues.getKey(), value);
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
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;
}
}
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
JSONArray categoryValues = new JSONArray();
categoryValues.add(entry.getValue());
values.put(entry.getKey(), categoryValues);
}
data.put("values", values);
return data;
}
}
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
continue; // Skip this invalid
}
allSkipped = false;
JSONArray categoryValues = new JSONArray();
for (int categoryValue : entry.getValue()) {
categoryValues.add(categoryValue);
}
values.put(entry.getKey(), categoryValues);
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
}

View File

@ -1,4 +1,4 @@
name: Plan
author: Rsl1122
main: com.djrapitops.plan.PlanBungee
version: 4.4.2
version: 4.4.3

View File

@ -1,314 +1,315 @@
Cmd - Click Me || 点击此处
Cmd - Link || §7 •§2 链接§f
Cmd Disable - Disabled || §aPlan systems are now disabled. You can still use /planbungee reload to restart the plugin.
Cmd Disable - Disabled || §a现已禁用计划系统。阁下仍可使用 /planbungee reload 重启插件。
Cmd FAIL - Invalid Username || §c[计划] 此玩家不存在。
Cmd FAIL - No Feature || §eDefine a feature to disable! (currently supports ${0})
Cmd FAIL - No Feature || §e请定义要禁用的功能!(当前支持 ${0}
Cmd FAIL - No Permission || §c[计划] 阁下没有所需权限。
Cmd FAIL - Require only one Argument || §c[计划] 命令需要一个参数。
Cmd FAIL - Requires Arguments || §c[计划] 命令缺少参数。${0}
Cmd FAIL - Unknown Username || §c[计划] 未在数据库中找到此玩家。
Cmd FAIL - WebUser does not exists || §cUser does not exists!
Cmd FAIL - WebUser exists || §cUser already exists!
Cmd FAIL - WebUser does not exists || §c用户不存在!
Cmd FAIL - WebUser exists || §c用户已存在!
Cmd Header - Analysis || §f»§2 玩家统计 - 统计结果
Cmd Header - Info || §f»§2 玩家统计 - 信息
Cmd Header - Inspect || §f»§2 玩家统计 - 检视结果:
Cmd Header - Network || > §2Network Page
Cmd Header - Players || > §2Players
Cmd Header - Network || > §2网络页面
Cmd Header - Players || > §2玩家
Cmd Header - Search || §f»§2 玩家统计 - 搜索结果:
Cmd Header - Servers || > §2Servers
Cmd Header - Web Users || > §2${0} Web Users
Cmd Info - Bungee Connection || §2Connected to Bungee: §f${0}
Cmd Info - Database || §2Active Database: §f${0}
Cmd Info - Reload Complete || §aReload Complete
Cmd Info - Reload Failed || §cSomething went wrong during reload of the plugin, a restart is recommended.
Cmd Info - Update || §2Update Available: §f${0}
Cmd Info - Version || §2Version: §f${0}
Cmd Notify - No WebUser || You might not have a web user, use /plan register <password>
Cmd Notify - WebUser register || Registered new user: '${0}' Perm level: ${1}
Cmd Qinspect - Activity Index || §2Activity Index: §f${0} | ${1}
Cmd Qinspect - Deaths || §2Deaths: §f${0}
Cmd Qinspect - Geolocation || §2Logged in from: §f${0}
Cmd Qinspect - Last Seen || §2Last Seen: §f${0}
Cmd Qinspect - Longest Session || §2Longest Session: §f${0}
Cmd Qinspect - Mob Kills || §2Mob Kills: §f${0}
Cmd Qinspect - Player Kills || §2Player Kills: §f${0}
Cmd Qinspect - Playtime || §2Playtime: §f${0}
Cmd Qinspect - Registered || §2Registered: §f${0}
Cmd Qinspect - Times Kicked || §2Times Kicked: §f${0}
Cmd Setup - Allowed || §aSet-up is now Allowed
Cmd Setup - Bad Request || §eConnection succeeded, but Receiving server was not a Bungee server. Use Bungee address instead.
Cmd Setup - Disallowed || §cSet-up is now Forbidden
Cmd Setup - Forbidden || §eConnection succeeded, but Bungee has set-up mode disabled - use '/planbungee setup' to enable it.
Cmd Setup - Gateway Error || §eConnection succeeded, but Bungee failed to connect to this server (Did current web server restart?). Use /plan m con & /planbungee con to debug.
Cmd Setup - Generic Fail || §eConnection failed: ${0}
Cmd Setup - Internal Error || §eConnection succeeded. ${0}, check possible ErrorLog on receiving server's debug page.
Cmd Setup - Success || §aConnection successful, Plan may restart in a few seconds..
Cmd Setup - Unauthorized || §eConnection succeeded, but Receiving server didn't authorize this server. Contact Discord for support
Cmd Setup - Url mistake || §cMake sure you're using the full address (Starts with http:// or https://) - Check Bungee enable log for the full address.
Cmd Setup - WebServer not Enabled || §cWebServer is not enabled on this server! Make sure it enables on boot!
Cmd SUCCESS - Feature disabled || §aDisabled '${0}' temporarily until next plugin reload.
Cmd SUCCESS - WebUser register || §aAdded a new user (${0}) successfully!
Cmd Update - Cancel Success || §aCancel operation performed.
Cmd Update - Cancelled || §cUpdate cancelled.
Cmd Update - Change log || Change Log v${0}:
Cmd Update - Fail Cacnel || §cUpdate failed on a server, cancelling update on all servers..
Cmd Update - Fail Force Notify || §e${0} failed to update, -force specified, continuing update.
Cmd Update - Fail Not Online || §cNot all servers were online or accessible, you can still update available servers using /plan update -u -force
Cmd Update - Notify Cancel || §aYou can cancel the update on servers that haven't rebooted yet with /plan update cancel.
Cmd Update - Online Check || Checking that all servers are online..
Cmd Update - Scheduled || §a${0} scheduled for update.
Cmd Update - Url mismatch || §cVersion download url did not start with ${0} and might not be trusted. You can download this version manually here (Direct download):
Cmd Web - Permission Levels || >\§70: Access all pages\§71: Access '/players' and all player pages\§72: Access player page with the same username as the webuser\§73+: No permissions
Cmd Header - Servers || > §2服务器
Cmd Header - Web Users || > §2${0} 个网页用户
Cmd Info - Bungee Connection || §2已连接至 Bungee§f${0}
Cmd Info - Database || §2活跃数据库:§f${0}
Cmd Info - Reload Complete || §a重新载入完毕
Cmd Info - Reload Failed || §c重载插件时发生错误,建议重新启动服务器。
Cmd Info - Update || §2更新可用:§f${0}
Cmd Info - Version || §2版本:§f${0}
Cmd Notify - No WebUser || 阁下可能还没有网页账户,请输入 /plan register <密码>
Cmd Notify - WebUser register || 已注册新用户:'${0}' 权限级别:${1}
Cmd Qinspect - Activity Index || §2活动索引:§f${0} | ${1}
Cmd Qinspect - Deaths || §2死亡数:§f${0}
Cmd Qinspect - Geolocation || §2从 §f${0} 处登录
Cmd Qinspect - Last Seen || §2最后在线:§f${0}
Cmd Qinspect - Longest Session || §2最长在线时间:§f${0}
Cmd Qinspect - Mob Kills || §2怪物击杀数:§f${0}
Cmd Qinspect - Player Kills || §2玩家击杀数:§f${0}
Cmd Qinspect - Playtime || §2游玩时间:§f${0}
Cmd Qinspect - Registered || §2注册时间:§f${0}
Cmd Qinspect - Times Kicked || §2踢出次数:§f${0}
Cmd Setup - Allowed || §a现已允许安装
Cmd Setup - Bad Request || §e连接成功,但接收服务器不是 Bungee 服务器。请使用 Bungee 地址。
Cmd Setup - Disallowed || §c现已禁止安装
Cmd Setup - Forbidden || §e连接成功,但 Bungee 已禁用安装模式 - 请使用 '/planbungee setup' 启用。
Cmd Setup - Gateway Error || §e连接成功,但 Bungee 无法与此服务器建立连接(是否重启了当前网页服务器?)请使用 /plan m con 与 /planbungee con 进行调试。
Cmd Setup - Generic Fail || §e连接失败:${0}
Cmd Setup - Internal Error || §e连接成功。${0},请检查接收服务器调试页面的错误日志。
Cmd Setup - Success || §a连接成功,计划可能将在几秒内重启···
Cmd Setup - Unauthorized || §e连接成功,但接收服务器并未授权此服务器。请在 Discord 上联系以寻求帮助
Cmd Setup - Url mistake || §c请确保阁下所输入的是完整地址(以 http:// 或 https:// 开头)- 请检查 Bungee 启用日志获取完整地址。
Cmd Setup - WebServer not Enabled || §c未在此服务器上启用网页服务器!请确保其在开机时启用!
Cmd SUCCESS - Feature disabled || §a已在下次插件重载前暂时禁用 '${0}'。
Cmd SUCCESS - WebUser register || §a已成功添加新用户(${0}
Cmd Update - Cancel Success || §a已完成取消操作。
Cmd Update - Cancelled || §c已取消更新。
Cmd Update - Change log || 更新日志 版本${0}
Cmd Update - Fail Cacnel || §c某个服务器更新失败,正在取消所有服务器的更新···
Cmd Update - Fail Force Notify || §e${0} 无法更新,但使用了 -force正在继续更新。
Cmd Update - Fail Not Online || §c并非所有服务器均在线或可访问,阁下仍可通过使用 /plan update -u -force 的方式强制更新。
Cmd Update - Notify Cancel || §a阁下可以使用 /plan update cancel 以取消尚未重启的服务器上的更新。
Cmd Update - Online Check || 正在检查是否所有服务器均在线···
Cmd Update - Scheduled || §a已为 ${0} 计划了更新。
Cmd Update - Url mismatch || §c版本下载网址不以 ${0} 开头故可能不受信任。阁下可手动下载此版本(直接下载):
Cmd Web - Permission Levels || >\§70:访问所有页面\§71访问 '/players' 及所有玩家页\§72访问用户名与网页用户名一致的玩家页\§73+:无权限
Command Help - /plan analyze || 查看服务器分析
Command Help - /plan dev || Development mode command
Command Help - /plan dev || 开发模式命令
Command Help - /plan help || 查看命令列表。
Command Help - /plan info || 查看计划版本
Command Help - /plan inspect || 检视玩家数据
Command Help - /plan manage || 数据库管理命令
Command Help - /plan manage backup || 备份数据库至 .db 文件
Command Help - /plan manage clear || 从数据库中清空所有数据
Command Help - /plan manage con || Debug Server-Bungee connections
Command Help - /plan manage disable || Disable a feature temporarily
Command Help - /plan manage con || 调试服务器至 Bungee 的连接
Command Help - /plan manage disable || 暂时禁用功能
Command Help - /plan manage hotswap || 热插拔数据库并重启插件
Command Help - /plan manage import || 从插件中导入数据
Command Help - /plan manage move || 在数据库间移动数据
Command Help - /plan manage remove || 从活跃数据库中移除玩家数据
Command Help - /plan manage restore || 恢复数据库
Command Help - /plan manage setup || Set-up Server-Bungee connection
Command Help - /plan network || View the Network Page
Command Help - /plan manage setup || 设置服务器至 Bungee 的连接
Command Help - /plan network || 查看网络页面
Command Help - /plan players || 列出所有已缓存玩家名单
Command Help - /plan qinspect || 在游戏内检视玩家数据
Command Help - /plan register || Register a Web User
Command Help - /plan register || 注册网页用户
Command Help - /plan reload || 重新启动插件(重载配置)
Command Help - /plan search || 搜索玩家
Command Help - /plan servers || List servers in Database
Command Help - /plan update || Get change log link or update plugin
Command Help - /plan servers || 列出数据库中的服务器
Command Help - /plan update || 获取更新日志链接或更新插件
Command Help - /plan web check || 检查网页用户的权限级别。
Command Help - /plan web delete || 删除网页用户
Command Help - /plan web level || 权限级别信息
Command Help - /plan web list || List Web Users
Command Help - /plan web list || 列出网页用户
Command Help - /plan webuser || 管理网页用户
Command Help - /planbungee con || Debug Bungee-Server connections
Command Help - /planbungee disable || Disable the plugin temporarily
Command Help - /planbungee setup || Toggle set-up mode
Database - Apply Patch || Applying Patch: ${0}..
Database - Patches Applied || All database patches applied successfully.
Database - Patches Applied Already || All database patches already applied.
Database MySQL - Launch Options Error || Launch Options were faulty, using default (${0})
Database Notify - Clean || Removed data of ${0} players.
Database Notify - SQLite No WAL || SQLite WAL mode not supported on this server version, using default. This may or may not affect performance.
Command Help - /planbungee con || 调试 Bungee 至服务器的连接
Command Help - /planbungee disable || 暂时禁用插件
Command Help - /planbungee setup || 开关设置功能
Database - Apply Patch || 正在应用更新:${0}···
Database - Patches Applied || 已成功应用所有数据库补丁。
Database - Patches Applied Already || 已应用所有数据库补丁。
Database MySQL - Launch Options Error || 启动参数出错,正使用默认参数(${0}
Database Notify - Clean || 移除了 ${0} 位用户的数据。
Database Notify - SQLite No WAL || 此服务器版本不支持 SQLite WAL 模式,正使用默认模式。这可能会影响性能。
Disable || 已禁用玩家分析。
Disable - Processing || Processing critical unprocessed tasks. (${0})
Disable - Processing Complete || Processing complete.
Disable - Processing || 正在处理未处理的关键任务。(${0}
Disable - Processing Complete || 处理完毕。
Disable - WebServer || 正在关闭网页服务器···
Enable || 已启用玩家分析。
Enable - Database || ${0}-已建立数据库连接。
Enable - Notify Address Confirmation || Make sure that this address points to THIS Server: ${0}
Enable - Notify Empty IP || IP in server.properties is empty & AlternativeIP is not in use. Incorrect links will be given!
Enable - Notify Geolocations disabled || Geolocation gathering is not active. (Data.Geolocations: false)
Enable - Notify Geolocations Internet Required || Plan Requires internet access on first run to download GeoLite2 Geolocation database.
Enable - Notify Webserver disabled || WebServer was not initialized. (WebServer.DisableWebServer: true)
Enable - Notify Address Confirmation || 确保此地址指向此服务器:${0}
Enable - Notify Empty IP || server.properties 中的 IP 为空且未使用替代 IP。这将给出错误地址
Enable - Notify Geolocations disabled || 地理位置收集不活跃。Data.Geolocations: false
Enable - Notify Geolocations Internet Required || 计划需要在首次运行时访问互联网以下载 GeoLite2 地理位置数据库。
Enable - Notify Webserver disabled || 未初始化网页服务器。WebServer.DisableWebServer: true
Enable - WebServer || 正在初始化网页服务器···
Enable FAIL - Database || ${0}-连接数据库失败:${1}
Enable FAIL - Database Patch || Database Patching failed, plugin has to be disabled. Please report this issue
Enable FAIL - GeoDB Write || Something went wrong saving the downloaded GeoLite2 Geolocation database
Enable FAIL - WebServer (Bungee) || WebServer did not initialize!
Enable FAIL - Database Patch || 数据库补丁失败,插件必须被禁用。请汇报此问题
Enable FAIL - GeoDB Write || 保存已下载的 GeoLite2 地理位置数据库时发生问题
Enable FAIL - WebServer (Bungee) || 网页服务器未初始化
Enable FAIL - Wrong Database Type || ${0} 此数据类型不存在。
HTML - ACTIVITY_INDEX || Activity Index
HTML - ALL || ALL
HTML - ALL_TIME_PEAK || All Time Peak
HTML - AVERAGE_PING || Average Ping
HTML - AVG || AVG
HTML - BANNED || Banned
HTML - BEST_PING || Best Ping
HTML - CALENDAR || CALENDAR
HTML - CALENDAR_TEXT || Calendar
HTML - CHUNKS || Chunks
HTML - COMMAND || Command
HTML - COMMNAND_USAGE || Command Usage
HTML - CONNECTION_INFORMATION || Connection Information
HTML - COUNTRY || Country
HTML - CURRENT_PLAYERBASE || Current Playerbase
HTML - DEATHS || Deaths
HTML - ENTITIES || Entities
HTML - ERROR || Authentication failed due to error
HTML - FAVORITE_SERVER || Favorite Server
HTML - GEOLOCATION || Geolocation
HTML - GEOLOCATION_TEXT || Geolocation
HTML - HEALTH_ESTIMATE || Health Estimate
HTML - INDEX_ACTIVE || Active
HTML - INDEX_INACTIVE || Inactive
HTML - INDEX_IRREGULAR || Irregular
HTML - INDEX_REGULAR || Regular
HTML - INDEX_VERY_ACTIVE || Very Active
HTML - IP_ADDRESS || IP-address
HTML - KILLED || Killed
HTML - KILLED_BY || Killed by
HTML - LAST_24_HOURS || LAST 24 HOURS
HTML - LAST_30_DAYS || LAST 30 DAYS
HTML - LAST_30_DAYS_TEXT || Last 30 Days
HTML - LAST_7_DAYS || LAST 7 DAYS
HTML - LAST_CONNECTED || Last Connected
HTML - LAST_PEAK || Last Peak
HTML - LAST_SEEN || LAST SEEN
HTML - LAST_SEEN_TEXT || Last Seen
HTML - LOADED_CHUNKS || Loaded Chunks
HTML - LOADED_ENTITIES || Loaded Entities
HTML - LOCAL_MACHINE || Local Machine
HTML - LONGEST || Longest
HTML - LOW_TPS_SPIKES || Low TPS Spikes
HTML - MOB_CAUSED_DEATHS || Mob caused Deaths
HTML - MOB_KDR || Mob KDR
HTML - MOB_KILLS || Mob Kills
HTML - MOST_RECENT_SESSIONS || Most Recent Sessions
HTML - NAME || Name
HTML - NAV_COMMAND_USAGE || Command Usage
HTML - NAV_GEOLOCATIONS || Geolocations
HTML - NAV_INFORMATION || Information
HTML - NAV_NETWORK_PLAYERS || Network Players
HTML - NAV_ONLINE_ACTIVITY || Online Activity
HTML - NAV_OVERVIEW || Overview
HTML - NAV_PERFORMANCE || Performance
HTML - NAV_PLAYERS || Players
HTML - NAV_PLUGINS || Plugins
HTML - NAV_SESSIONS || Sessions
HTML - NAV_SEVER_HEALTH || Server Health
HTML - NETWORK || Network
HTML - NETWORK_INFORMATION || NETWORK INFORMATION
HTML - NEW || NEW
HTML - NEW_CALENDAR || New:
HTML - NEW_PLAYERS_TEXT || New Players
HTML - NEW_RETENTION || New Player Retention
HTML - NEW_TEXT || New
HTML - NICKNAME || Nickname
HTML - NO_KILLS || No Kills
HTML - NO_PLAYER_CAUSED_DEATHS || No Player caused Deaths
HTML - OFFLINE || Offline
HTML - ONLINE || Online
HTML - ONLINE_ACTIVITY || ONLINE ACTIVITY
HTML - OPERATOR || Operator
HTML - OVERVIEW || OVERVIEW
HTML - PER_DAY || / Day
HTML - PLAYER_CAUSED_DEATHS || Player caused Deaths
HTML - PLAYER_KILLS || Player Kills
HTML - PLAYER_LIST || Player List
HTML - PLAYERBASE_DEVELOPMENT || Playerbase Development
HTML - PLAYERS || PLAYERS
HTML - PLAYERS_ONLINE || PLAYERS ONLINE
HTML - PLAYERS_ONLINE_TEXT || Players Online
HTML - PLAYERS_TEXT || Players
HTML - PLAYTIME || Playtime
HTML - PLEASE_WAIT || Please wait...
HTML - PREDICETED_RETENTION || Predicted Retention
HTML - PUNCH_CARD || Punchcard
HTML - PUNCHCARD || PUNCHCARD
HTML - RECENT_LOGINS || RECENT LOGINS
HTML - REGISTERED || REGISTERED
HTML - REGISTERED_TEXT || Registered
HTML - REGULAR || REGULAR
HTML - SEEN_NICKNAMES || Seen Nicknames
HTML - SERVER || Server
HTML - SERVER_ANALYSIS || Server Analysis
HTML - SERVER_HEALTH_ESTIMATE || Server Health Estimate
HTML - SERVER_INFORMATION || SERVER INFORMATION
HTML - SERVER_PREFERENCE || Server Preference
HTML - SERVERS || Servers
HTML - SESSION || Session
HTML - SESSION_ENDED || Session Ended
HTML - SESSION_LENGTH || Session Lenght
HTML - SESSION_MEDIAN || Session Median
HTML - SESSIONS || Sessions
HTML - TIME || Time
HTML - TIMES_KICKED || Times Kicked
HTML - TIMES_USED || Times Used
HTML - TOTAL_ACTIVE_TEXT || Total Active
HTML - TOTAL_AFK || Total AFK
HTML - TOTAL_PLAYERS || Total Players
HTML - TOTAL_PLAYTIME || Total Playtime
HTML - UNIQUE || UNIQUE
HTML - UNIQUE_CALENDAR || Unique:
HTML - UNIQUE_PLAYERS || UNIQUE PLAYERS
HTML - UNIQUE_PLAYERS_TEXT || Unique Players
HTML - UNIQUE_TEXT || Unique
HTML - USAGE || Usage
HTML - USED_COMMANDS || Used Commands
HTML - USER_AND_PASS_NOT_SPECIFIED || User and Password not specified
HTML - USER_DOES_NOT_EXIST || User does not exist
HTML - USER_INFORMATION || USER INFORMATION
HTML - USER_PASS_MISMATCH || User and Password did not match
HTML - WITH || <th>With
HTML - WORLD || World
HTML - WORLD_LOAD || WORLD LOAD
HTML - WORLD_PLAYTIME || World Playtime
HTML - WORST_PING || Worst Ping
HTML ERRORS - ACCESS_DENIED_403 || Access Denied
HTML ERRORS - ANALYSIS_REFRESH || Analysis is being refreshed..
HTML ERRORS - ANALYSIS_REFRESH_LONG || Analysis is being run, refresh the page after a few seconds..
HTML ERRORS - AUTH_FAIL_TIPS_401 || - Ensure you have registered a user with <b>/plan register</b><br>- Check that the username and password are correct<br>- Username and password are case-sensitive<br><br>If you have forgotten your password, ask a staff member to delete your old user and re-register.
HTML ERRORS - AUTHENTICATION_FAIlED_401 || Authentication Failed.
HTML ERRORS - FORBIDDEN_403 || Forbidden
HTML ERRORS - NO_SERVERS_404 || No Servers online to perform the request.
HTML ERRORS - NOT_FOUND_404 || Not Found
HTML ERRORS - NOT_PLAYED_404 || Player has not played on this server.
HTML ERRORS - PAGE_NOT_FOUND_404 || Page does not exist.
HTML ERRORS - UNAUTHORIZED_401 || Unauthorized
HTML ERRORS - UNKNOWN_PAGE_404 || Make sure you're accessing a link given by a command, Examples:</p><p>/player/PlayerName<br>/server/ServerName</p>
HTML ERRORS - UUID_404 || Player UUID was not found in the database.
HTML - ACTIVITY_INDEX || 活动索引
HTML - ALL || 全部
HTML - ALL_TIME_PEAK || 所有时间峰值
HTML - AVERAGE_PING || 平均延迟
HTML - AVG || 平均
HTML - BANNED || 已封禁
HTML - BEST_PING || 最低延迟
HTML - CALENDAR || 日历
HTML - CALENDAR_TEXT || 日历
HTML - CHUNKS || 区块
HTML - COMMAND || 命令
HTML - COMMNAND_USAGE || 命令用法
HTML - CONNECTION_INFORMATION || 连接信息
HTML - COUNTRY || 国家
HTML - CURRENT_PLAYERBASE || 当前玩家
HTML - DEATHS || 死亡数
HTML - ENTITIES || 实体数
HTML - ERROR || 认证时发生错误
HTML - FAVORITE_SERVER || 喜爱的服务器
HTML - GEOLOCATION || 地理位置
HTML - GEOLOCATION_TEXT || 地理位置
HTML - HEALTH_ESTIMATE || 预计健康度
HTML - INDEX_ACTIVE || 活跃
HTML - INDEX_INACTIVE || 不活跃
HTML - INDEX_IRREGULAR || 偶尔
HTML - INDEX_REGULAR || 经常
HTML - INDEX_VERY_ACTIVE || 非常活跃
HTML - IP_ADDRESS || IP 地址
HTML - KILLED || 被击杀
HTML - KILLED_BY || 击杀者
HTML - LAST_24_HOURS || 过去 24 小时
HTML - LAST_30_DAYS || 过去 30 天
HTML - LAST_30_DAYS_TEXT || 过去 30 天
HTML - LAST_7_DAYS || 过去 7 天
HTML - LAST_CONNECTED || 最后连接
HTML - LAST_PEAK || 上次峰值
HTML - LAST_SEEN || 最后在线
HTML - LAST_SEEN_TEXT || 最后在线
HTML - LOADED_CHUNKS || 已加载的区块
HTML - LOADED_ENTITIES || 已加载的实体
HTML - LOCAL_MACHINE || 本机
HTML - LONGEST || 最长
HTML - LOW_TPS_SPIKES || 最低 TPS 值
HTML - MOB_CAUSED_DEATHS || 被怪物击杀数
HTML - MOB_KDR || 怪物击杀比
HTML - MOB_KILLS || 怪物击杀数
HTML - MOST_RECENT_SESSIONS || 最近会话
HTML - NAME || 名称
HTML - NAV_COMMAND_USAGE || 指令用法
HTML - NAV_GEOLOCATIONS || 地理位置
HTML - NAV_INFORMATION || 信息
HTML - NAV_NETWORK_PLAYERS || 网络玩家
HTML - NAV_ONLINE_ACTIVITY || 在线活动
HTML - NAV_OVERVIEW || 预览
HTML - NAV_PERFORMANCE || 性能
HTML - NAV_PLAYERS || 玩家
HTML - NAV_PLUGINS || 插件
HTML - NAV_SESSIONS || 会话
HTML - NAV_SEVER_HEALTH || 服务器健康度
HTML - NETWORK || 网络
HTML - NETWORK_INFORMATION || 网络信息
HTML - NEW || 新建
HTML - NEW_CALENDAR || 新:
HTML - NEW_PLAYERS_TEXT || 新玩家
HTML - NEW_RETENTION || 新玩家保留
HTML - NEW_TEXT || 新建
HTML - NICKNAME || 昵称
HTML - NO_KILLS || 无击杀数
HTML - NO_PLAYER_CAUSED_DEATHS || 无被玩家击杀数
HTML - OFFLINE || 离线
HTML - ONLINE || 在线
HTML - ONLINE_ACTIVITY || 在线活动
HTML - OPERATOR || 操作员
HTML - OVERVIEW || 预览
HTML - PER_DAY || / 天
HTML - PLAYER_CAUSED_DEATHS || 被玩家击杀数
HTML - PLAYER_KILLS || 玩家击杀数
HTML - PLAYER_LIST || 玩家列表
HTML - PLAYERBASE_DEVELOPMENT || 玩家发展
HTML - PLAYERS || 玩家
HTML - PLAYERS_ONLINE || 在线玩家
HTML - PLAYERS_ONLINE_TEXT || 在线玩家
HTML - PLAYERS_TEXT || 玩家
HTML - PLAYTIME || 游玩时间
HTML - PLEASE_WAIT || 请稍候···
HTML - PREDICETED_RETENTION || 预计保留
HTML - PUNCH_CARD || 打卡签到
HTML - PUNCHCARD || 打卡签到
HTML - RECENT_LOGINS || 近期登陆
HTML - REGISTERED || 已注册
HTML - REGISTERED_TEXT || 已注册
HTML - REGULAR || 经常
HTML - SEEN_NICKNAMES || 可见昵称
HTML - SERVER || 服务器
HTML - SERVER_ANALYSIS || 服务器分析
HTML - SERVER_HEALTH_ESTIMATE || 服务器健康估计
HTML - SERVER_INFORMATION || 服务器信息
HTML - SERVER_PREFERENCE || 服务器偏好设置
HTML - SERVERS || 服务器
HTML - SESSION || 会话
HTML - SESSION_ENDED || 会话已结束
HTML - SESSION_LENGTH || 会话长度
HTML - SESSION_MEDIAN || 会话中位数
HTML - SESSIONS || 会话
# Not sure if this is the time or count of numbers.
HTML - TIME || 次数
HTML - TIMES_KICKED || 被踢次数
HTML - TIMES_USED || 使用次数
HTML - TOTAL_ACTIVE_TEXT || 总活跃玩家
HTML - TOTAL_AFK || 总挂机玩家
HTML - TOTAL_PLAYERS || 总玩家
HTML - TOTAL_PLAYTIME || 总在线时长
HTML - UNIQUE || 唯一
HTML - UNIQUE_CALENDAR || 唯一:
HTML - UNIQUE_PLAYERS || 唯一玩家
HTML - UNIQUE_PLAYERS_TEXT || 唯一玩家
HTML - UNIQUE_TEXT || 唯一
HTML - USAGE || 用法
HTML - USED_COMMANDS || 使用的命令
HTML - USER_AND_PASS_NOT_SPECIFIED || 未指定用户名与密码
HTML - USER_DOES_NOT_EXIST || 用户不存在
HTML - USER_INFORMATION || 用户信息
HTML - USER_PASS_MISMATCH || 用户名与密码不匹配
HTML - WITH || <th> 与
HTML - WORLD || 世界
HTML - WORLD_LOAD || 世界载入
HTML - WORLD_PLAYTIME || 世界游玩时间
HTML - WORST_PING || 最高延迟
HTML ERRORS - ACCESS_DENIED_403 || 拒绝访问
HTML ERRORS - ANALYSIS_REFRESH || 正在刷新分析···
HTML ERRORS - ANALYSIS_REFRESH_LONG || 正在执行分析,请在几秒后刷新页面···
HTML ERRORS - AUTH_FAIL_TIPS_401 || - 确保阁下已使用 <b>/plan register</b> 注册用户<br>- 检查用户名与密码是否正确<br>- 用户名与密码区分大小写<br><br>若阁下忘记了密码,请让工作人员删除阁下的旧密码并重新注册。
HTML ERRORS - AUTHENTICATION_FAIlED_401 || 认证失败。
HTML ERRORS - FORBIDDEN_403 || 禁止访问
HTML ERRORS - NO_SERVERS_404 || 无可执行此请求的在线服务器。
HTML ERRORS - NOT_FOUND_404 || 未找到
HTML ERRORS - NOT_PLAYED_404 || 玩家从未在此服务器上游玩过。
HTML ERRORS - PAGE_NOT_FOUND_404 || 页面不存在。
HTML ERRORS - UNAUTHORIZED_401 || 未认证
HTML ERRORS - UNKNOWN_PAGE_404 || 请确保阁下正通过命令所给出的链接访问,示例:</p><p>/player/玩家名<br>/server/服务器名</p>
HTML ERRORS - UUID_404 || 未在数据库中找到玩家的 UUID。
In Depth Help - /plan ? || §2/plan - 主命令\§f 用于访问所有子命令及帮助\§7 /plan - 列出子命令\§7 /plan <子命令> ? - 详细帮助
In Depth Help - /plan analyze ? || §2分析命令\§f 用于刷新分析缓存及访问结果页面\§7 /plan status 可用于在网页在线时检查分析状态fen.\§7 别名analyze, analyse, analysis, a
In Depth Help - /plan inspect ? || §2检视命令\§f 用于获取用户检视页链接。\§7 个人检视页可通过输入 /plan inspect 访问\§7 别名:/plan <名称>
In Depth Help - /plan manage ? || §2管理命令\§f 用于管理插件数据库。\§7 别名:/plan m\§7 /plan m - Auflistung der Unterbefehle\§7 /plan m <子命令> ? - 详细帮助
In Depth Help - /plan manage backup ? || > §2Backup Subcommand\ Creates a new SQLite database (.db file) with contents of currently active database in the Plan plugin folder.
In Depth Help - /plan manage backup ? || > §2备份命令\ 用于建立新 SQLite 数据库(.db 文件)并包含当前在计划插件文件夹中所有的活跃数据库内容。
In Depth Help - /plan manage clear ? || §2管理清除命令\§f 用于清除活跃数据库中的所有数据。\§7 插件在清理完毕后应被重载。\§7 别名:/plan pl
In Depth Help - /plan manage con ? || > §2Connection Debug Subcommand\ Used to debug connections in the network.\ Sends a request to each server in the database.
In Depth Help - /plan manage disable ? || > §2Disable Subcommand\ Can disable parts of the plugin until next reload.\ Accepted arguments:\ §2kickcount §fDisables kick counts in case /kickall is used on shutdown macro.
In Depth Help - /plan manage con ? || > §2调试连接命令\ 用于调试网络间的连接。\ 向数据库中的每个服务器发送请求。
In Depth Help - /plan manage disable ? || > §2禁用命令\ 用于在下次重新载入之前禁用插件的部分特性。\ 接受的参数:\ §2kickcount §f禁用在关闭服务器时 /kickall 的踢出计数。
In Depth Help - /plan manage import ? || §2管理导入命令\§f 用于从其它来源导入数据\§7 在导入过程中将禁用数据分析。
In Depth Help - /plan manage move ? || > §2Move Subcommand\ Move data from SQLite to MySQL or other way around.\ Target database is cleared before transfer.
In Depth Help - /plan manage move ? || > §2移动指令\ 将数据从 SQLite 移动至 MySQL反之亦然。\ 目标数据库将在转移前被清除。
In Depth Help - /plan manage remove ? || §2管理移除命令\§f 用于从活跃数据库中移除用户数据。
In Depth Help - /plan manage restore ? || > §2Restore Subcommand\ Restore a previous backup SQLite database (.db file)\ You can also restore database.db from another server to MySQL.\ Target database is cleared before transfer.
In Depth Help - /plan manage setup ? || > §2Setup Subcommand\ Set-up a connection between Bungee and this server for network functionality.\ BungeeAddress can be found in the enable log on console when Plan enables on Bungee.
In Depth Help - /plan network ? || > §2Network Command\ Displays link to the network page.\ If not on a network, this page displays the server page.
In Depth Help - /plan players ? || > §2Players Command\ Displays link to the players page.
In Depth Help - /plan manage restore ? || > §2恢复命令\ 从先前备份的 SQLite 数据库中恢复数据(.db 文件)\ 阁下也可以从其他服务器中恢复 database.db 到 MySQL。\ 目标数据库将在传输前被清除。
In Depth Help - /plan manage setup ? || > §2安装命令\ 设置 Bungee 与此服务器之间的网络连接。\ Bungee 地址可在计划于 Bungee 上启动时的控制台日志中找到。
In Depth Help - /plan network ? || > §2网络命令\ 显示到网络页的链接。\ 若不在群组网络上,此页面将显示为服务器页面。
In Depth Help - /plan players ? || > §2玩家命令\ 显示到玩家页的链接。
In Depth Help - /plan qinspect ? || §2快速检视命令\§f 用于获取游戏内关于检视的信息。\§7 相比检视网页有着更少的信息。\§7 别名:/plan qi
In Depth Help - /plan reload ? || > §2Reload Command\ Restarts the plugin using onDisable and onEnable.\ §bDoes not support swapping jar on the fly
In Depth Help - /plan reload ? || > §2重载命令\ 使用 onDisable 与 onEnable 重新载入插件。\ §b不支持运行时热切换插件
In Depth Help - /plan search ? || §2搜索命令\§f 用于获取匹配特定参数的玩家名列表。\§7 示例:/plan search 123 - 搜索名称中包含 123 的所有玩家。
In Depth Help - /plan servers ? || > §2Servers Command\ Displays list of Plan servers in the Database.\ Can be used to debug issues with database registration on a network.
In Depth Help - /plan update ? || > §2Update Command\ Used to update the plugin on the next shutdown\ /plan update - Changelog link\ /plan update -u - Schedule update to happen on all network servers that are online, next time they reboot.\ /plan update cancel - Cancel scheduled update on servers that haven't rebooted yet.
In Depth Help - /plan web ? || < §2Web User Manage Command.\ §2/plan web §fList subcommands\ §2/plan web <subcommand> ? §fIn Depth help
In Depth Help - /plan web register ? || > §2Register Subcommand\ Registers a new Web User.\ Registering a user for another player requires plan.webmanage permission.\ Passwords are hashed with PBKDF2 (64,000 iterations of SHA1) using a cryptographically-random salt.
In Depth Help - /planbungee disable ? || > §2Disable Command\ Runs onDisable on PlanBungee.\ Plugin can be enabled with /planbungee reload afterwards.\ §bDoes not support swapping jar on the fly
In Depth Help - /planbungee setup ? || > §2Set-up toggle Command\ Toggles set-up mode on Bungee.\ Safeguard against unauthorized MySQL snooping with another server.
In Depth Help - /plan servers ? || > §2服务器命令\ 显示数据库中的计划服务器列表。\ 可用于调试在网络上数据库注册的问题。
In Depth Help - /plan update ? || > §2更新命令\ 用于在下次关闭服务器时更新插件\ /plan update - 更新日志链接\ /plan update -u - 在所有在线的网络服务器上预定在下次重新启动时更新。\ /plan update cancel - 取消尚未重启的服务器上的更新。
In Depth Help - /plan web ? || < §2网页用户管理命令。\ §2/plan web §f列出子命令\ §2/plan web <子命令> ? §f详细帮助
In Depth Help - /plan web register ? || > §2注册命令\ 注册一位新的网页用户。\ 为其他用户注册需要 plan.webmanage 权限。\ 密码通过使用随机密码盐的 PBKDF2 哈希方法加密(重复 64000 次 SHA1
In Depth Help - /planbungee disable ? || > §2禁用命令\ 在 PlanBungee 上运行 onDisable。\ 插件之后需要运行 /planbungee reload 以重新启用。\ §b不支持运行时热切换插件
In Depth Help - /planbungee setup ? || > §2设置开关命令\ 开关 Bungee 上的安装功能。\ 用来防止与其他服务器之间未授权的 MySQL 监听。
Manage - Confirm Overwrite || 数据库 ${0} 中的数据将被覆盖!
Manage - Confirm Removal || 数据库 ${0} 中的数据将被移除!
Manage - Fail || > §c[计划] 处理数据时发生错误! ${0}
Manage - Fail File not found || > §c[计划] 备份文件不存在! ${0}
Manage - Fail Incorrect Database || > §c'${0}' is not a supported database.
Manage - Fail No Importer || §eImporter '${0}' doesn't exist
Manage - Fail Incorrect Database || > §c'${0}' 不是一个支持的数据库。
Manage - Fail No Importer || §e导入器 '${0}' 不存在
Manage - Fail Same Database || > §c[计划] 无法移动至相同的数据库!
Manage - Fail, Confirmation || > §c[计划] 请添加 -a 以确认执行!${0}
Manage - Fail, Connection Exception || §eFail reason:
Manage - Fail, No Servers || §cNo Servers found in the database.
Manage - Fail, Old version || §eFail reason: Older Plan version on receiving server
Manage - Fail, Unauthorized || §eFail reason: Unauthorized. Server might be using different database.
Manage - Fail, Unexpected Exception || §eOdd Exception: ${0}
Manage - List Importers || Importers:
Manage - Notify External Url || §eNon-local address, check that port is open
Manage - Remind HotSwap || §e[计划] 请记住要切换至新数据库并重新载入插件 (/plan m hotswap ${0})
Manage - Fail, Connection Exception || §e失败原因:
Manage - Fail, No Servers || §c未在数据库中找到服务器。
Manage - Fail, Old version || §e失败原因:接收服务器正运行计划的老版本。
Manage - Fail, Unauthorized || §e失败原因:未授权。服务器可能使用不同的数据库。
Manage - Fail, Unexpected Exception || §e异常例外:${0}
Manage - List Importers || 导入器:
Manage - Notify External Url || §e非本地地址,请检查端口是否开放
Manage - Remind HotSwap || §e[计划] 请记住要切换至新数据库并重新载入插件/plan m hotswap ${0}
Manage - Start || »§7 正在处理数据···
Manage - Success || §f» §2 成功!
Negative || No
Positive || Yes
Unknown || Unknown
Version - DEV || This is a DEV release.
Version - Latest || You're using the latest version.
Version - New || New Release (${0}) is available ${1}
Version - New (old) || New Version is available at ${0}
Version FAIL - Read info (old) || Failed to check newest version number
Version FAIL - Read versions.txt || Version information could not be loaded from Github/versions.txt
Web User Listing || §2${0} §7: §f${1}
WebServer - Notify HTTP || WebServer: No Certificate -> Using HTTP-server for Visualization.
WebServer - Notify HTTP User Auth || WebServer: User Authorization Disabled! (Not secure over HTTP)
WebServer - Notify no Cert file || WebServer: Certificate KeyStore File not Found: ${0}
WebServer FAIL - Port Bind || WebServer was not initialized successfully. Is the port (${0}) in use?
WebServer FAIL - SSL Context || WebServer: SSL Context Initialization Failed.
WebServer FAIL - Store Load || WebServer: SSL Certificate loading Failed.
Negative ||
Positive ||
Unknown || 未知
Version - DEV || 这是开发者版本。
Version - Latest || 阁下正使用最新版本。
Version - New || 新版本(${0})可用 ${1}
Version - New (old) || 新版本在 ${0} 可用
Version FAIL - Read info (old) || 无法检查最新版本号
Version FAIL - Read versions.txt || 无法从 Github/versions.txt 处载入版本信息
Web User Listing || §2${0} §7§f${1}
WebServer - Notify HTTP || 网页服务器:无证书 -> 正使用 HTTP 服务器提供可视化效果。
WebServer - Notify HTTP User Auth || 网页服务器已禁用用户认证HTTP 方式不安全)
WebServer - Notify no Cert file || 网页服务器:未找到证书密钥存储文件:${0}
WebServer FAIL - Port Bind || 未成功初始化网页服务器。端口(${0})是否被占用?
WebServer FAIL - SSL Context || 网页服务器SSL 环境初始化失败。
WebServer FAIL - Store Load || 网页服务器SSL 证书载入失败。

View File

@ -1,7 +1,7 @@
name: Plan
author: Rsl1122
main: com.djrapitops.plan.Plan
version: 4.4.2
version: 4.4.3
softdepend:
- EssentialsX
- Towny

View File

@ -285,7 +285,7 @@
</div>
<div class="col-xs-4 col-sm-4 col-md-4 col-lg-4">
<img class="center-block"
src="https://cravatar.eu/avatar/${playerName}/100">
src="https://cravatar.eu/avatar/${playerName}/100.png">
</div>
<div class="col-xs-4 col-sm-4 col-md-4 col-lg-4">
<p><i class="col-red fa fa-crosshairs"></i> Player Kills: ${playerKillCount}