mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-04-12 18:20:26 +08:00
[3.4.0] Merge pull request #109 from Rsl1122/Version-3.4.0
Pull Request for 3.4.0
This commit is contained in:
commit
c40d11b4da
@ -26,7 +26,7 @@
|
||||
<dependency>
|
||||
<groupId>com.hm</groupId>
|
||||
<artifactId>advanced.achievements</artifactId>
|
||||
<version>5.1</version>
|
||||
<version>5.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -183,7 +183,6 @@
|
||||
<param>test.java.main.java.com.djrapitops.plan.*</param>
|
||||
</targetTests>
|
||||
<timeoutConstant>1000</timeoutConstant>
|
||||
<verbose>true</verbose>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
@ -385,7 +385,7 @@ public class Plan extends JavaPlugin {
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to set the current instance of Plan.
|
||||
*
|
||||
|
@ -16,6 +16,8 @@ public enum Settings {
|
||||
ANALYSIS_REFRESH_ON_ENABLE("Settings.Cache.AnalysisCache.RefreshAnalysisCacheOnEnable"),
|
||||
ANALYSIS_LOG_TO_CONSOLE("Settings.Analysis.LogProgressOnConsole"),
|
||||
ANALYSIS_LOG_FINISHED("Settings.Analysis.NotifyWhenFinished"),
|
||||
ANALYSIS_REMOVE_OUTLIERS("Settings.Analysis.RemoveOutliersFromVisualization"),
|
||||
ANALYSIS_EXPORT("Settings.Analysis.Export.Enabled"),
|
||||
SHOW_ALTERNATIVE_IP("Settings.WebServer.ShowAlternativeServerIP"),
|
||||
USE_ALTERNATIVE_UI("Settings.UseTextUI"),
|
||||
GATHERLOCATIONS("Settings.Data.GatherLocations"),
|
||||
@ -48,6 +50,7 @@ public enum Settings {
|
||||
LOCALE("Settings.Locale"),
|
||||
WEBSERVER_IP("Settings.WebServer.InternalIP"),
|
||||
SECURITY_CODE("Settings.WebServer.Security.AddressSecurityCode"),
|
||||
ANALYSIS_EXPORT_PATH("Settings.Analysis.Export.DestinationFolder"),
|
||||
//
|
||||
SERVER_NAME("Customization.ServerName"),
|
||||
//
|
||||
@ -76,9 +79,6 @@ public enum Settings {
|
||||
HCOLOR_GMP_1("Customization.Colors.HTML.GamemodePie.Creative"),
|
||||
HCOLOR_GMP_2("Customization.Colors.HTML.GamemodePie.Adventure"),
|
||||
HCOLOR_GMP_3("Customization.Colors.HTML.GamemodePie.Spectator"),
|
||||
HCOLOR_GENP_M("Customization.Colors.HTML.GenderPie.Male"),
|
||||
HCOLOR_GENP_F("Customization.Colors.HTML.GenderPie.Female"),
|
||||
HCOLOR_GENP_U("Customization.Colors.HTML.GenderPie.Unknown"),
|
||||
// StringList
|
||||
HIDE_FACTIONS("Customization.Plugins.Factions.HideFactions"),
|
||||
HIDE_TOWNS("Customization.Plugins.Towny.HideTowns");
|
||||
@ -124,7 +124,7 @@ public enum Settings {
|
||||
public int getNumber() {
|
||||
return Plan.getInstance().getConfig().getInt(configPath);
|
||||
}
|
||||
|
||||
|
||||
public List<String> getStringList() {
|
||||
return Plan.getInstance().getConfig().getStringList(configPath);
|
||||
}
|
||||
|
@ -1,11 +1,8 @@
|
||||
package main.java.com.djrapitops.plan.command.commands.manage;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Permissions;
|
||||
@ -15,10 +12,7 @@ import main.java.com.djrapitops.plan.command.CommandType;
|
||||
import main.java.com.djrapitops.plan.command.SubCommand;
|
||||
import main.java.com.djrapitops.plan.data.handling.importing.ImportUtils;
|
||||
import main.java.com.djrapitops.plan.data.handling.importing.Importer;
|
||||
import main.java.com.djrapitops.plan.utilities.ManageUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayers;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
@ -55,14 +49,13 @@ public class ManageImportCommand extends SubCommand {
|
||||
}
|
||||
|
||||
String importFromPlugin = args[0].toLowerCase();
|
||||
List<String> supportedImports = Arrays.asList(new String[]{"ontime"});
|
||||
if (!supportedImports.contains(importFromPlugin)) {
|
||||
Map<String, Importer> importPlugins = ImportUtils.getImporters();
|
||||
if (!importPlugins.keySet().contains(importFromPlugin)) {
|
||||
sender.sendMessage(Phrase.MANAGE_ERROR_INCORRECT_PLUGIN + importFromPlugin);
|
||||
return true;
|
||||
}
|
||||
Map<String, Importer> importPlugins = ImportUtils.getImporters();
|
||||
|
||||
if (!importPlugins.keySet().contains(importFromPlugin) || !ImportUtils.isPluginEnabled(importFromPlugin)) {
|
||||
if (!ImportUtils.isPluginEnabled(importFromPlugin)) {
|
||||
sender.sendMessage(Phrase.MANAGE_ERROR_PLUGIN_NOT_ENABLED + importFromPlugin);
|
||||
return true;
|
||||
}
|
||||
@ -73,7 +66,6 @@ public class ManageImportCommand extends SubCommand {
|
||||
}
|
||||
|
||||
final Importer importer = importPlugins.get(importFromPlugin);
|
||||
// Header
|
||||
BukkitTask asyncImportTask = new BukkitRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@ -83,7 +75,6 @@ public class ManageImportCommand extends SubCommand {
|
||||
sender.sendMessage(Phrase.MANAGE_SUCCESS + "");
|
||||
} else {
|
||||
sender.sendMessage(Phrase.MANAGE_PROCESS_FAIL + "");
|
||||
|
||||
}
|
||||
this.cancel();
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ import main.java.com.djrapitops.plan.ui.RecentPlayersButtonsCreator;
|
||||
import main.java.com.djrapitops.plan.ui.graphs.PlayerActivityGraphCreator;
|
||||
import main.java.com.djrapitops.plan.ui.tables.SortableCommandUseTableCreator;
|
||||
import main.java.com.djrapitops.plan.ui.tables.SortablePlayersTableCreator;
|
||||
import main.java.com.djrapitops.plan.utilities.Analysis;
|
||||
import main.java.com.djrapitops.plan.utilities.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.Analysis;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.PlaceholderUtils;
|
||||
|
||||
/**
|
||||
@ -70,7 +70,14 @@ public class AnalysisData {
|
||||
private String geomapZ;
|
||||
private String geomapCodes;
|
||||
|
||||
private int[] genderData;
|
||||
private int avgUniqJoins;
|
||||
private int avgUniqJoinsDay;
|
||||
private int avgUniqJoinsWeek;
|
||||
private int avgUniqJoinsMonth;
|
||||
|
||||
private int uniqueJoinsDay;
|
||||
private int uniqueJoinsWeek;
|
||||
private int uniqueJoinsMonth;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@ -78,6 +85,13 @@ public class AnalysisData {
|
||||
* All data has to be set with setters to avoid NPEs.
|
||||
*/
|
||||
public AnalysisData() {
|
||||
avgUniqJoins = 0;
|
||||
avgUniqJoinsDay = 0;
|
||||
avgUniqJoinsWeek = 0;
|
||||
avgUniqJoinsMonth = 0;
|
||||
uniqueJoinsDay = 0;
|
||||
uniqueJoinsWeek = 0;
|
||||
uniqueJoinsMonth = 0;
|
||||
sortablePlayersTable = Html.ERROR_NOT_SET + "";
|
||||
commandUseTableHtml = Html.ERROR_NOT_SET + "";
|
||||
recentPlayers = Html.ERROR_NOT_SET + "";
|
||||
@ -88,7 +102,6 @@ public class AnalysisData {
|
||||
sessionDistributionData = new String[]{"[]", "[]"};
|
||||
playtimeDistributionData = new String[]{"[]", "[]"};
|
||||
playersDataArray = new String[]{"[0]", "[\"No data\"]", "[0]", "[\"No data\"]", "[0]", "[\"No data\"]"};
|
||||
genderData = new int[]{0, 0, 0};
|
||||
additionalDataReplaceMap = new HashMap<>();
|
||||
}
|
||||
|
||||
@ -194,9 +207,6 @@ public class AnalysisData {
|
||||
if (!Arrays.deepEquals(this.playersDataArray, other.playersDataArray)) {
|
||||
return false;
|
||||
}
|
||||
if (!Arrays.equals(this.genderData, other.genderData)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -863,49 +873,114 @@ public class AnalysisData {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the integer array containing 3 numbers.
|
||||
* Get the data for the Session Punchcard.
|
||||
*
|
||||
* 0 Male, 1 Female, 2 Unknown.
|
||||
*
|
||||
* @return for example [0, 4, 5] when 0 male, 4 female and 5 unknown.
|
||||
* @return Array of x y r coordinates: [{x: 4, y: 5, r: 4}]
|
||||
*/
|
||||
public int[] getGenderData() {
|
||||
return genderData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Set the integer array containing 3 numbers.
|
||||
*
|
||||
* 0 Male, 1 Female, 2 Unknown.
|
||||
*
|
||||
* @param genderData for example [0, 4, 5]
|
||||
*/
|
||||
public void setGenderData(int[] genderData) {
|
||||
this.genderData = genderData;
|
||||
}
|
||||
|
||||
public String getPunchCardData() {
|
||||
return punchCardData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data for the Session Punchcard.
|
||||
*
|
||||
* @param punchCardData Array of x y r coordinates: [{x: 4, y: 5, r: 4}]
|
||||
*/
|
||||
public void setPunchCardData(String punchCardData) {
|
||||
this.punchCardData = punchCardData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data and labels for the session distribution barchart.
|
||||
*
|
||||
* @return index 0: [0, 5, 4], 1: ["0-5", "5-10", "10-15"]
|
||||
*/
|
||||
public String[] getSessionDistributionData() {
|
||||
return sessionDistributionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data and labels for the session distribution barchart.
|
||||
*
|
||||
* @param sessionDistributionData index 0: [0, 5, 4], 1: ["0-5", "5-10",
|
||||
* "10-15"]
|
||||
*/
|
||||
public void setSessionDistributionData(String[] sessionDistributionData) {
|
||||
this.sessionDistributionData = sessionDistributionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data and labels for the playtime distribution barchart.
|
||||
*
|
||||
* @return index 0: [0, 5, 4], 1: ["0-5", "5-10", "10-15"]
|
||||
*/
|
||||
public String[] getPlaytimeDistributionData() {
|
||||
return playtimeDistributionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data and labels for the playtime distribution barchart.
|
||||
*
|
||||
* @param playtimeDistributionData index 0: [0, 5, 4], 1: ["0-5", "5-10",
|
||||
* "10-15"]
|
||||
*/
|
||||
public void setPlaytimeDistributionData(String[] playtimeDistributionData) {
|
||||
this.playtimeDistributionData = playtimeDistributionData;
|
||||
}
|
||||
|
||||
public int getAvgUniqJoins() {
|
||||
return avgUniqJoins;
|
||||
}
|
||||
|
||||
public int getAvgUniqJoinsDay() {
|
||||
return avgUniqJoinsDay;
|
||||
}
|
||||
|
||||
public int getAvgUniqJoinsWeek() {
|
||||
return avgUniqJoinsWeek;
|
||||
}
|
||||
|
||||
public int getAvgUniqJoinsMonth() {
|
||||
return avgUniqJoinsMonth;
|
||||
}
|
||||
|
||||
public void setAvgUniqJoins(int avgUniqJoins) {
|
||||
this.avgUniqJoins = avgUniqJoins;
|
||||
}
|
||||
|
||||
public void setAvgUniqJoinsDay(int avgUniqJoinsDay) {
|
||||
this.avgUniqJoinsDay = avgUniqJoinsDay;
|
||||
}
|
||||
|
||||
public void setAvgUniqJoinsWeek(int avgUniqJoinsWeek) {
|
||||
this.avgUniqJoinsWeek = avgUniqJoinsWeek;
|
||||
}
|
||||
|
||||
public void setAvgUniqJoinsMonth(int avgUniqJoinsMonth) {
|
||||
this.avgUniqJoinsMonth = avgUniqJoinsMonth;
|
||||
}
|
||||
|
||||
public int getUniqueJoinsDay() {
|
||||
return uniqueJoinsDay;
|
||||
}
|
||||
|
||||
public void setUniqueJoinsDay(int uniqueJoinsDay) {
|
||||
this.uniqueJoinsDay = uniqueJoinsDay;
|
||||
}
|
||||
|
||||
public int getUniqueJoinsWeek() {
|
||||
return uniqueJoinsWeek;
|
||||
}
|
||||
|
||||
public void setUniqueJoinsWeek(int uniqueJoinsWeek) {
|
||||
this.uniqueJoinsWeek = uniqueJoinsWeek;
|
||||
}
|
||||
|
||||
public int getUniqueJoinsMonth() {
|
||||
return uniqueJoinsMonth;
|
||||
}
|
||||
|
||||
public void setUniqueJoinsMonth(int uniqueJoinsMonth) {
|
||||
this.uniqueJoinsMonth = uniqueJoinsMonth;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import main.java.com.djrapitops.plan.utilities.Analysis;
|
||||
import java.util.UUID;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.Analysis;
|
||||
|
||||
/**
|
||||
* This class is used for storing combined data of several UserData objects
|
||||
@ -34,11 +35,11 @@ public class RawAnalysisData {
|
||||
private Map<String, Long> latestLogins;
|
||||
private Map<String, Long> playtimes;
|
||||
private List<SessionData> sessiondata;
|
||||
private Map<UUID, List<SessionData>> sortedSessionData;
|
||||
private Map<String, Integer> commandUse;
|
||||
private Map<String, Integer> geolocations;
|
||||
private Map<String, String> geocodes;
|
||||
private List<Long> registered;
|
||||
private int[] genders;
|
||||
|
||||
/**
|
||||
* Constructor for a new empty dataset.
|
||||
@ -56,16 +57,17 @@ public class RawAnalysisData {
|
||||
inactive = 0;
|
||||
totalKills = 0;
|
||||
totalMobKills = 0;
|
||||
totalDeaths = 0;
|
||||
ops = 0;
|
||||
ages = new ArrayList<>();
|
||||
latestLogins = new HashMap<>();
|
||||
playtimes = new HashMap<>();
|
||||
sessiondata = new ArrayList<>();
|
||||
sortedSessionData = new HashMap<>();
|
||||
commandUse = new HashMap<>();
|
||||
geolocations = new HashMap<>();
|
||||
geocodes = new HashMap<>();
|
||||
registered = new ArrayList<>();
|
||||
genders = new int[]{0, 0, 0};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -368,6 +370,15 @@ public class RawAnalysisData {
|
||||
return sessiondata;
|
||||
}
|
||||
|
||||
public Map<UUID, List<SessionData>> getSortedSessionData() {
|
||||
return sortedSessionData;
|
||||
}
|
||||
|
||||
public void addSessions(UUID uuid, List<SessionData> sessions) {
|
||||
sessiondata.addAll(sessions);
|
||||
sortedSessionData.put(uuid, sessions);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param commandUse
|
||||
@ -391,29 +402,4 @@ public class RawAnalysisData {
|
||||
public List<Long> getRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public int[] getGenders() {
|
||||
return genders;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param gender
|
||||
*/
|
||||
public void setGenders(int[] gender) {
|
||||
this.genders = gender;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param i
|
||||
* @param amount
|
||||
*/
|
||||
public void addToGender(int i, int amount) {
|
||||
this.genders[i] = this.genders[i] + amount;
|
||||
}
|
||||
}
|
||||
|
@ -944,11 +944,20 @@ public class UserData {
|
||||
this.clearAfterSave = clearAfterSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the banned value.
|
||||
*
|
||||
* @param isBanned true/false
|
||||
*/
|
||||
public void setBanned(boolean isBanned) {
|
||||
this.isBanned = isBanned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the online value.
|
||||
* @param isOnline true/false
|
||||
*/
|
||||
public void setOnline(boolean isOnline) {
|
||||
this.isOnline = isOnline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ import static org.bukkit.plugin.java.JavaPlugin.getPlugin;
|
||||
*/
|
||||
public abstract class Hook {
|
||||
|
||||
/**
|
||||
* Is the plugin being hooked properly enabled?
|
||||
*/
|
||||
protected boolean enabled;
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,11 @@ package main.java.com.djrapitops.plan.data.additional.advancedachievements;
|
||||
|
||||
import com.hm.achievement.api.AdvancedAchievementsAPI;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import main.java.com.djrapitops.plan.data.additional.AnalysisType;
|
||||
import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
|
||||
/**
|
||||
* PluginData class for AdvancedAchievements-plugin.
|
||||
@ -20,6 +22,8 @@ import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
public class AdvancedAchievementsAchievements extends PluginData {
|
||||
|
||||
private AdvancedAchievementsAPI aaAPI;
|
||||
private long lastRefresh;
|
||||
private Map<UUID, Integer> totalAchievements;
|
||||
|
||||
/**
|
||||
* Class Constructor, sets the parameters of the PluginData object.
|
||||
@ -32,16 +36,30 @@ public class AdvancedAchievementsAchievements extends PluginData {
|
||||
super.setAnalysisOnly(false);
|
||||
super.setIcon("check-circle-o");
|
||||
super.setPrefix("Achivements: ");
|
||||
totalAchievements = aaAPI.getPlayersTotalAchievements();
|
||||
lastRefresh = MiscUtils.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtmlReplaceValue(String modifierPrefix, UUID uuid) {
|
||||
return parseContainer(modifierPrefix, aaAPI.getPlayerTotalAchievements(uuid) + "");
|
||||
if (MiscUtils.getTime()- lastRefresh > 60000) {
|
||||
totalAchievements = aaAPI.getPlayersTotalAchievements();
|
||||
}
|
||||
if (totalAchievements.containsKey(uuid)) {
|
||||
return parseContainer(modifierPrefix,totalAchievements.get(uuid) + "");
|
||||
}
|
||||
return parseContainer(modifierPrefix, 0+"");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable getValue(UUID uuid) {
|
||||
return aaAPI.getPlayerTotalAchievements(uuid);
|
||||
if (MiscUtils.getTime()- lastRefresh > 60000) {
|
||||
totalAchievements = aaAPI.getPlayersTotalAchievements();
|
||||
}
|
||||
if (totalAchievements.containsKey(uuid)) {
|
||||
return totalAchievements.get(uuid);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,8 +4,12 @@ import com.hm.achievement.api.AdvancedAchievementsAPI;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Plan;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.data.additional.AnalysisType;
|
||||
import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
import main.java.com.djrapitops.plan.ui.Html;
|
||||
@ -47,13 +51,24 @@ public class AdvancedAchievementsTable extends PluginData {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHtmlReplaceValue(String modifierPrefix, UUID uuid) {
|
||||
public String getHtmlReplaceValue(String modifierPrefix, UUID uuidUnused) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
List<OfflinePlayer> offlinePlayers = Arrays.stream(getOfflinePlayers()).filter(p -> p.hasPlayedBefore()).collect(Collectors.toList());
|
||||
Map<UUID, OfflinePlayer> offlinePlayers = Arrays.stream(getOfflinePlayers()).filter(p -> p.hasPlayedBefore()).collect(Collectors.toMap(p -> p.getUniqueId(), Function.identity()));
|
||||
if (offlinePlayers.isEmpty()) {
|
||||
html.append(Html.TABLELINE_2.parse("No Players.", ""));
|
||||
} else if (aaAPI.getAdvancedAchievementsVersionCode() >= 520) {
|
||||
Map<UUID, Integer> achievementsMap = aaAPI.getPlayersTotalAchievements();
|
||||
for (UUID uuid : achievementsMap.keySet()) {
|
||||
OfflinePlayer p = offlinePlayers.get(uuid);
|
||||
if (p == null) {
|
||||
continue;
|
||||
}
|
||||
String inspectUrl = HtmlUtils.getInspectUrl(p.getName());
|
||||
int achievements = achievementsMap.get(uuid);
|
||||
html.append(Html.TABLELINE_2.parse(Html.LINK.parse(inspectUrl, p.getName()), achievements+""));
|
||||
}
|
||||
} else {
|
||||
for (OfflinePlayer p : offlinePlayers) {
|
||||
for (OfflinePlayer p : offlinePlayers.values()) {
|
||||
String inspectUrl = HtmlUtils.getInspectUrl(p.getName());
|
||||
String achievements = aaAPI.getPlayerTotalAchievements(p.getUniqueId()) + "";
|
||||
html.append(Html.TABLELINE_2.parse(Html.LINK.parse(inspectUrl, p.getName()), achievements));
|
||||
|
@ -41,7 +41,7 @@ public class EssentialsJailed extends PluginData {
|
||||
if (user != null) {
|
||||
return parseContainer(modifier, user.isJailed() ? "Yes" : "No");
|
||||
}
|
||||
return "";
|
||||
return parseContainer(modifier, "No");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -41,7 +41,7 @@ public class EssentialsMuted extends PluginData {
|
||||
if (user != null) {
|
||||
return parseContainer("", user.isMuted() ? "Yes" : "No");
|
||||
}
|
||||
return "";
|
||||
return parseContainer(modifier, "No");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,6 @@ package main.java.com.djrapitops.plan.data.additional.jobs;
|
||||
|
||||
import com.gamingmesh.jobs.Jobs;
|
||||
import com.gamingmesh.jobs.PlayerManager;
|
||||
import com.gamingmesh.jobs.container.Job;
|
||||
import com.gamingmesh.jobs.container.JobProgression;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
@ -15,7 +14,7 @@ import main.java.com.djrapitops.plan.data.additional.AnalysisType;
|
||||
import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
import main.java.com.djrapitops.plan.ui.Html;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayers;
|
||||
|
||||
/**
|
||||
@ -29,6 +28,9 @@ import static org.bukkit.Bukkit.getOfflinePlayers;
|
||||
*/
|
||||
public class JobsAnalysisJobTable extends PluginData {
|
||||
|
||||
/**
|
||||
* Class Constructor, sets the parameters of the PluginData object.
|
||||
*/
|
||||
public JobsAnalysisJobTable() {
|
||||
super("Jobs", "analysistable", AnalysisType.HTML);
|
||||
final String job = Html.FONT_AWESOME_ICON.parse("suitcase") + " Job";
|
||||
|
@ -22,6 +22,9 @@ import main.java.com.djrapitops.plan.ui.Html;
|
||||
*/
|
||||
public class JobsInspectJobTable extends PluginData {
|
||||
|
||||
/**
|
||||
* Class Constructor, sets the parameters of the PluginData object.
|
||||
*/
|
||||
public JobsInspectJobTable() {
|
||||
super("Jobs", "inspecttable");
|
||||
super.setAnalysisOnly(false);
|
||||
@ -33,18 +36,21 @@ public class JobsInspectJobTable extends PluginData {
|
||||
|
||||
@Override
|
||||
public String getHtmlReplaceValue(String modifierPrefix, UUID uuid) {
|
||||
PlayerManager pm = Jobs.getPlayerManager();
|
||||
PlayerInfo info = pm.getPlayerInfo(uuid);
|
||||
JobsPlayer player = pm.getJobsPlayerOffline(info);
|
||||
List<JobProgression> progression = player.getJobProgression();
|
||||
if (progression.isEmpty()) {
|
||||
return parseContainer("", Html.TABLELINE_2.parse("No Jobs.", ""));
|
||||
try {
|
||||
PlayerManager pm = Jobs.getPlayerManager();
|
||||
PlayerInfo info = pm.getPlayerInfo(uuid);
|
||||
JobsPlayer player = pm.getJobsPlayerOffline(info);
|
||||
List<JobProgression> progression = player.getJobProgression();
|
||||
if (!progression.isEmpty()) {
|
||||
StringBuilder html = new StringBuilder();
|
||||
for (JobProgression job : progression) {
|
||||
html.append(Html.TABLELINE_2.parse(job.getJob().getName(), "" + job.getLevel()));
|
||||
}
|
||||
return parseContainer("", html.toString());
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
}
|
||||
StringBuilder html = new StringBuilder();
|
||||
for (JobProgression job : progression) {
|
||||
html.append(Html.TABLELINE_2.parse(job.getJob().getName(), "" + job.getLevel()));
|
||||
}
|
||||
return parseContainer("", html.toString());
|
||||
return parseContainer("", Html.TABLELINE_2.parse("No Jobs.", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,7 +6,6 @@ import com.gmail.nossr50.util.player.UserManager;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
@ -14,10 +13,9 @@ import main.java.com.djrapitops.plan.data.additional.AnalysisType;
|
||||
import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
import main.java.com.djrapitops.plan.ui.Html;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import static org.bukkit.Bukkit.getOnlinePlayers;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* PluginData class for McMMO-plugin.
|
||||
@ -30,6 +28,9 @@ import org.bukkit.entity.Player;
|
||||
*/
|
||||
public class McmmoAnalysisSkillTable extends PluginData {
|
||||
|
||||
/**
|
||||
* Class Constructor, sets the parameters of the PluginData object.
|
||||
*/
|
||||
public McmmoAnalysisSkillTable() {
|
||||
super("McMMO", "analysistable", AnalysisType.HTML);
|
||||
final String skill = Html.FONT_AWESOME_ICON.parse("star") + " Skill";
|
||||
|
@ -32,6 +32,9 @@ import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
*/
|
||||
public class McmmoInspectSkillTable extends PluginData {
|
||||
|
||||
/**
|
||||
* Class Constructor, sets the parameters of the PluginData object.
|
||||
*/
|
||||
public McmmoInspectSkillTable() {
|
||||
super("McMMO", "inspectskilltable");
|
||||
super.setAnalysisOnly(false);
|
||||
|
@ -37,7 +37,7 @@ public class TownyTown extends PluginData {
|
||||
public String getHtmlReplaceValue(String modifierPrefix, UUID uuid) {
|
||||
OfflinePlayer offlinePlayer = getOfflinePlayer(uuid);
|
||||
if (!offlinePlayer.hasPlayedBefore()) {
|
||||
return "";
|
||||
return parseContainer(modifierPrefix, Phrase.NOT_IN_TOWN + "");
|
||||
}
|
||||
String name = offlinePlayer.getName();
|
||||
try {
|
||||
@ -50,7 +50,7 @@ public class TownyTown extends PluginData {
|
||||
}
|
||||
return parseContainer("", town);
|
||||
} catch (NotRegisteredException ex) {
|
||||
return "";
|
||||
return parseContainer(modifierPrefix, Phrase.NOT_IN_TOWN + "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ package main.java.com.djrapitops.plan.data.cache;
|
||||
|
||||
import main.java.com.djrapitops.plan.Plan;
|
||||
import main.java.com.djrapitops.plan.data.AnalysisData;
|
||||
import main.java.com.djrapitops.plan.utilities.Analysis;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.Analysis;
|
||||
|
||||
/**
|
||||
* This class is used to store the most recent AnalysisData object and to run
|
||||
|
@ -5,12 +5,14 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.Plan;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.database.Database;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
|
||||
/**
|
||||
* This class stores UserData objects used for displaying the Html pages.
|
||||
@ -22,7 +24,8 @@ public class InspectCacheHandler {
|
||||
|
||||
private DataCacheHandler handler;
|
||||
private Plan plugin;
|
||||
private HashMap<UUID, UserData> cache;
|
||||
private Map<UUID, UserData> cache;
|
||||
private Map<UUID, Long> cacheTimes;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@ -33,6 +36,7 @@ public class InspectCacheHandler {
|
||||
this.handler = plugin.getHandler();
|
||||
this.plugin = plugin;
|
||||
this.cache = new HashMap<>();
|
||||
cacheTimes = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,11 +52,19 @@ public class InspectCacheHandler {
|
||||
@Override
|
||||
public void process(UserData data) {
|
||||
cache.put(uuid, new UserData(data));
|
||||
cacheTimes.put(uuid, MiscUtils.getTime());
|
||||
}
|
||||
};
|
||||
handler.getUserDataForProcessing(cacher, uuid, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to cache all UserData to the InspectCache from the cache and
|
||||
* provided database.
|
||||
*
|
||||
* @param db Database to cache from if data is not in the cache.
|
||||
* @throws SQLException If Database is not properly enabled
|
||||
*/
|
||||
public void cacheAllUserData(Database db) throws SQLException {
|
||||
Set<UUID> cachedUserData = handler.getDataCache().keySet();
|
||||
for (UUID uuid : cachedUserData) {
|
||||
@ -66,8 +78,11 @@ public class InspectCacheHandler {
|
||||
}
|
||||
savedUUIDs.removeAll(cachedUserData);
|
||||
List<UserData> userDataForUUIDS = db.getUserDataForUUIDS(savedUUIDs);
|
||||
long time = MiscUtils.getTime();
|
||||
for (UserData uData : userDataForUUIDS) {
|
||||
cache.put(uData.getUuid(), uData);
|
||||
UUID uuid = uData.getUuid();
|
||||
cache.put(uuid, uData);
|
||||
cacheTimes.put(uuid, time);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,6 +96,19 @@ public class InspectCacheHandler {
|
||||
return cache.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Epoch millisecond the data was cached to the inspect cache.
|
||||
*
|
||||
* @param uuid UUID of the player.
|
||||
* @return -1 when not cached or Epoch millisecond.
|
||||
*/
|
||||
public long getCacheTime(UUID uuid) {
|
||||
if (cacheTimes.containsKey(uuid)) {
|
||||
return cacheTimes.get(uuid);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the data of a player is in the inspect cache.
|
||||
*
|
||||
@ -90,7 +118,12 @@ public class InspectCacheHandler {
|
||||
public boolean isCached(UUID uuid) {
|
||||
return cache.containsKey(uuid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to get all cached userdata objects.
|
||||
*
|
||||
* @return List of cached userdata objects.
|
||||
*/
|
||||
public List<UserData> getCachedUserData() {
|
||||
return new ArrayList<>(cache.values());
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class KillHandling {
|
||||
int victimID;
|
||||
try {
|
||||
UUID victimUUID = deadPlayer.getUniqueId();
|
||||
victimID = plugin.getDB().getUserId(victimUUID + "");
|
||||
victimID = plugin.getDB().getUsersTable().getUserId(victimUUID + "");
|
||||
if (victimID == -1) {
|
||||
return;
|
||||
}
|
||||
|
@ -1,8 +1,3 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package main.java.com.djrapitops.plan.data.handling.importing;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -10,14 +5,28 @@ import java.util.Map;
|
||||
import static org.bukkit.Bukkit.getPluginManager;
|
||||
|
||||
/**
|
||||
* This class is responsible for static utility methods used for importing.
|
||||
*
|
||||
* @author Risto
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public class ImportUtils {
|
||||
|
||||
/**
|
||||
* Checks if a plugin is enabled.
|
||||
*
|
||||
* @param pluginName Name of the plugin
|
||||
* @return true/false
|
||||
*/
|
||||
public static boolean isPluginEnabled(String pluginName) {
|
||||
return getPluginManager().isPluginEnabled(pluginName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to get all importers for different plugins.
|
||||
*
|
||||
* @return Map of importers with pluginname in lowercase as key.
|
||||
*/
|
||||
public static Map<String, Importer> getImporters() {
|
||||
Map<String, Importer> importers = new HashMap<>();
|
||||
importers.put("ontime", new OnTimeImporter());
|
||||
|
@ -6,8 +6,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.Plan;
|
||||
import main.java.com.djrapitops.plan.data.cache.DataCacheHandler;
|
||||
@ -17,15 +15,28 @@ import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
|
||||
/**
|
||||
* Abstract class used for importing data from other plugins.
|
||||
*
|
||||
* @author Rsl1122
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public abstract class Importer {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public Importer() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used for the import.
|
||||
*
|
||||
* Creates UserData for players that have not been saved to the database.
|
||||
*
|
||||
* @param uuids UUIDs to be imported
|
||||
* @return success
|
||||
*/
|
||||
public boolean importData(Collection<UUID> uuids) {
|
||||
Plan plan = Plan.getInstance();
|
||||
DataCacheHandler handler = plan.getHandler();
|
||||
@ -49,5 +60,12 @@ public abstract class Importer {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used for getting the HandlingInfo object for the import data.
|
||||
*
|
||||
* @param uuid UUID of the player
|
||||
* @return HandlingInfo object that modifies the UserData so that the data
|
||||
* is imported.
|
||||
*/
|
||||
public abstract HandlingInfo importData(UUID uuid);
|
||||
}
|
||||
|
@ -1,31 +1,38 @@
|
||||
package main.java.com.djrapitops.plan.data.handling.importing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.data.handling.info.HandlingInfo;
|
||||
import main.java.com.djrapitops.plan.data.handling.info.InfoType;
|
||||
import me.edge209.OnTime.OnTimeAPI;
|
||||
import me.edge209.OnTime.OnTimeAPI.data;
|
||||
import org.bukkit.Bukkit;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
|
||||
/**
|
||||
* Class responsible for importing data from OnTime plugin.
|
||||
*
|
||||
* Imports playtime
|
||||
*
|
||||
* @author Rsl1122
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public class OnTimeImporter extends Importer {
|
||||
|
||||
/**
|
||||
*
|
||||
* Constructor.
|
||||
*/
|
||||
public OnTimeImporter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Imports playtime from Ontime.
|
||||
*
|
||||
* Resets Gamemode times to survival because it is playtime dependent.
|
||||
*
|
||||
* @param uuid UUID of the player
|
||||
* @return HandlingInfo object
|
||||
*/
|
||||
@Override
|
||||
public HandlingInfo importData(UUID uuid) {
|
||||
OfflinePlayer p = getOfflinePlayer(uuid);
|
||||
|
@ -14,25 +14,65 @@ import main.java.com.djrapitops.plan.database.tables.NicknamesTable;
|
||||
import main.java.com.djrapitops.plan.database.tables.SessionsTable;
|
||||
import main.java.com.djrapitops.plan.database.tables.UsersTable;
|
||||
import main.java.com.djrapitops.plan.database.tables.VersionTable;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
/**
|
||||
* Abstract class representing a Database.
|
||||
*
|
||||
* All methods should be only called from an asyncronous thread, unless stated
|
||||
* otherwise.
|
||||
*
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public abstract class Database {
|
||||
|
||||
/**
|
||||
* Instance of Plan used with this database.
|
||||
*/
|
||||
protected final Plan plugin;
|
||||
|
||||
/**
|
||||
* Table representing plan_users in the database.
|
||||
*/
|
||||
protected UsersTable usersTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_gamemodetimes in the database.
|
||||
*/
|
||||
protected GMTimesTable gmTimesTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_kills in the database.
|
||||
*/
|
||||
protected KillsTable killsTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_locations in the database.
|
||||
*/
|
||||
protected LocationsTable locationsTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_nicknames in the database.
|
||||
*/
|
||||
protected NicknamesTable nicknamesTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_sessions in the database.
|
||||
*/
|
||||
protected SessionsTable sessionsTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_ips in the database.
|
||||
*/
|
||||
protected IPsTable ipsTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_commandusages in the database.
|
||||
*/
|
||||
protected CommandUseTable commandUseTable;
|
||||
|
||||
/**
|
||||
* Table representing plan_version in the database.
|
||||
*/
|
||||
protected VersionTable versionTable;
|
||||
|
||||
/**
|
||||
@ -81,22 +121,16 @@ public abstract class Database {
|
||||
*/
|
||||
public abstract void giveUserDataToProcessors(UUID uuid, Collection<DBCallableProcessor> processors) throws SQLException;
|
||||
|
||||
public abstract List<UserData> getUserDataForUUIDS(Collection<UUID> uuids) throws SQLException;
|
||||
|
||||
/**
|
||||
* Used to save UserData object of a user.
|
||||
* Used to get all UserData for multiple UUIDs.
|
||||
*
|
||||
* @param uuid UUID of the player
|
||||
* @param data UserData of the Player.
|
||||
* @throws SQLException If a database error occurs.
|
||||
* @deprecated Separate UUID no longer required.
|
||||
* Should only be called from async thread.
|
||||
*
|
||||
* @param uuids UUIDs to fetch data for.
|
||||
* @return Data for matching UUIDs.
|
||||
* @throws SQLException If database error occurs.
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveUserData(UUID uuid, UserData data) throws SQLException {
|
||||
if (uuid.equals(data.getUuid())) {
|
||||
saveUserData(data);
|
||||
}
|
||||
}
|
||||
public abstract List<UserData> getUserDataForUUIDS(Collection<UUID> uuids) throws SQLException;
|
||||
|
||||
/**
|
||||
* Used to save UserData object of a user.
|
||||
@ -130,6 +164,8 @@ public abstract class Database {
|
||||
/**
|
||||
* Used to get the name of the database type.
|
||||
*
|
||||
* Thread safe.
|
||||
*
|
||||
* @return SQLite/MySQL
|
||||
*/
|
||||
public abstract String getName();
|
||||
@ -137,6 +173,8 @@ public abstract class Database {
|
||||
/**
|
||||
* Used to get the config name of the database type.
|
||||
*
|
||||
* Thread safe.
|
||||
*
|
||||
* @return sqlite/mysql
|
||||
*/
|
||||
public String getConfigName() {
|
||||
@ -190,119 +228,101 @@ public abstract class Database {
|
||||
/**
|
||||
* Used to save CommandUse map.
|
||||
*
|
||||
* @param data
|
||||
* @param data String command (key), Integer times used
|
||||
* @throws SQLException If a database error occurs.
|
||||
* @throws NullPointerException If the database has not initialized tables.
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveCommandUse(Map<String, Integer> data) throws SQLException, NullPointerException {
|
||||
commandUseTable.saveCommandUse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fetch the saved UUIDs in the users table.
|
||||
*
|
||||
* @return @throws SQLException If a database error occurs.
|
||||
* @return Set of saved UUIDs
|
||||
* @throws SQLException If a database error occurs.
|
||||
*/
|
||||
public Set<UUID> getSavedUUIDs() throws SQLException {
|
||||
return usersTable.getSavedUUIDs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the Command usage mep.
|
||||
*
|
||||
* @return @throws SQLException If a database error occurs.
|
||||
* @return String command (key), Integer times used
|
||||
* @throws SQLException If a database error occurs.
|
||||
*/
|
||||
@Deprecated
|
||||
public Map<String, Integer> getCommandUse() throws SQLException {
|
||||
return commandUseTable.getCommandUse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the users table.
|
||||
*
|
||||
* @param uuid
|
||||
* @return
|
||||
* @throws SQLException If a database error occurs.
|
||||
*/
|
||||
@Deprecated
|
||||
public int getUserId(String uuid) throws SQLException {
|
||||
return usersTable.getUserId(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param worlds
|
||||
* @return
|
||||
* @throws SQLException If a database error occurs.
|
||||
*/
|
||||
@Deprecated
|
||||
public List<Location> getLocations(String userId, HashMap<String, World> worlds) throws SQLException {
|
||||
return getLocations(Integer.parseInt(userId), worlds);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public List<Location> getLocations(int userId, HashMap<String, World> worlds) throws SQLException {
|
||||
return locationsTable.getLocations(userId, worlds);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_users
|
||||
*/
|
||||
public UsersTable getUsersTable() {
|
||||
return usersTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the users table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_sessions
|
||||
*/
|
||||
public SessionsTable getSessionsTable() {
|
||||
return sessionsTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the gm times table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_gamemodetimes
|
||||
*/
|
||||
public GMTimesTable getGmTimesTable() {
|
||||
return gmTimesTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the kills table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_kills
|
||||
*/
|
||||
public KillsTable getKillsTable() {
|
||||
return killsTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the locations table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_locations
|
||||
*/
|
||||
public LocationsTable getLocationsTable() {
|
||||
return locationsTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the ips table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_ips
|
||||
*/
|
||||
public IPsTable getIpsTable() {
|
||||
return ipsTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the nicknames table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_nicknames
|
||||
*/
|
||||
public NicknamesTable getNicknamesTable() {
|
||||
return nicknamesTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get the command usage table.
|
||||
*
|
||||
* @return
|
||||
* @return Table representing plan_commandusages
|
||||
*/
|
||||
public CommandUseTable getCommandUseTable() {
|
||||
return commandUseTable;
|
||||
|
@ -31,8 +31,6 @@ import org.bukkit.scheduler.BukkitRunnable;
|
||||
*/
|
||||
public abstract class SQLDB extends Database {
|
||||
|
||||
final Plan plugin;
|
||||
|
||||
private final boolean supportsModification;
|
||||
|
||||
private Connection connection;
|
||||
@ -44,7 +42,6 @@ public abstract class SQLDB extends Database {
|
||||
*/
|
||||
public SQLDB(Plan plugin, boolean supportsModification) {
|
||||
super(plugin);
|
||||
this.plugin = plugin;
|
||||
this.supportsModification = supportsModification;
|
||||
boolean usingMySQL = getName().equals("MySQL");
|
||||
|
||||
@ -146,6 +143,9 @@ public abstract class SQLDB extends Database {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public void convertBukkitDataToDB() {
|
||||
new BukkitRunnable() {
|
||||
@Override
|
||||
@ -155,6 +155,7 @@ public abstract class SQLDB extends Database {
|
||||
Set<UUID> uuids = usersTable.getSavedUUIDs();
|
||||
uuids.removeAll(usersTable.getContainsBukkitData(uuids));
|
||||
if (uuids.isEmpty()) {
|
||||
Log.debug("No conversion necessary.");
|
||||
return;
|
||||
}
|
||||
Log.info("Beginning Bukkit Data -> DB Conversion for " + uuids.size() + " players");
|
||||
@ -310,12 +311,18 @@ public abstract class SQLDB extends Database {
|
||||
List<SessionData> sessions = sessionsTable.getSessionData(userId);
|
||||
data.addSessions(sessions);
|
||||
data.setPlayerKills(killsTable.getPlayerKills(userId));
|
||||
for (DBCallableProcessor processor : processors) {
|
||||
processors.stream().forEach((processor) -> {
|
||||
processor.process(data);
|
||||
}
|
||||
});
|
||||
Benchmark.stop("DB Give userdata to processors");
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuidsCol
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Override
|
||||
public List<UserData> getUserDataForUUIDS(Collection<UUID> uuidsCol) throws SQLException {
|
||||
if (uuidsCol == null || uuidsCol.isEmpty()) {
|
||||
@ -324,12 +331,9 @@ public abstract class SQLDB extends Database {
|
||||
|
||||
Benchmark.start("DB get UserData for " + uuidsCol.size());
|
||||
Map<UUID, Integer> userIds = usersTable.getAllUserIds();
|
||||
Set<UUID> remove = new HashSet<>();
|
||||
for (UUID uuid : uuidsCol) {
|
||||
if (!userIds.containsKey(uuid)) {
|
||||
remove.add(uuid);
|
||||
}
|
||||
}
|
||||
Set<UUID> remove = uuidsCol.stream()
|
||||
.filter((uuid) -> (!userIds.containsKey(uuid)))
|
||||
.collect(Collectors.toSet());
|
||||
List<UUID> uuids = new ArrayList<>(uuidsCol);
|
||||
Log.debug("Data not found for: " + remove.size());
|
||||
uuids.removeAll(remove);
|
||||
@ -417,23 +421,20 @@ public abstract class SQLDB extends Database {
|
||||
gmTimesTable.saveGMTimes(id, gmTimes.get(id));
|
||||
}
|
||||
Benchmark.stop("Save GMTimes");
|
||||
for (Integer id : locations.keySet()) {
|
||||
UUID uuid = uuids.get(id);
|
||||
if (uuid != null) {
|
||||
UserData uData = userDatas.get(uuid);
|
||||
if (uData != null) {
|
||||
userDatas.values().stream()
|
||||
.filter(u -> u != null)
|
||||
.filter(uData -> uData.isAccessed())
|
||||
.forEach(uData -> {
|
||||
uData.stopAccessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Save leftovers
|
||||
for (UserData userData : saveLast) {
|
||||
saveLast.stream().forEach((userData) -> {
|
||||
try {
|
||||
saveUserData(userData);
|
||||
} catch (SQLException | NullPointerException e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!exceptions.isEmpty()) {
|
||||
Log.error("SEVERE: MULTIPLE ERRORS OCCURRED: " + exceptions.size());
|
||||
Log.toLog(this.getClass().getName(), exceptions);
|
||||
@ -469,74 +470,6 @@ public abstract class SQLDB extends Database {
|
||||
data.stopAccessing();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param locations
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveAdditionalLocationsList(int userId, List<Location> locations) throws SQLException {
|
||||
locationsTable.saveAdditionalLocationsList(userId, locations);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param names
|
||||
* @param lastNick
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveNickList(int userId, Set<String> names, String lastNick) throws SQLException {
|
||||
nicknamesTable.saveNickList(userId, names, lastNick);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param sessions
|
||||
* @throws SQLException
|
||||
* @deprecated Use sessionsTable instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveSessionList(int userId, List<SessionData> sessions) throws SQLException {
|
||||
sessionsTable.saveSessionData(userId, sessions);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param kills
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Deprecated
|
||||
public void savePlayerKills(int userId, List<KillData> kills) throws SQLException {
|
||||
killsTable.savePlayerKills(userId, kills);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param ips
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveIPList(int userId, Set<InetAddress> ips) throws SQLException {
|
||||
ipsTable.saveIPList(userId, ips);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
* @param gamemodeTimes
|
||||
* @throws SQLException
|
||||
*/
|
||||
@Deprecated
|
||||
public void saveGMTimes(int userId, Map<GameMode, Long> gamemodeTimes) throws SQLException {
|
||||
gmTimesTable.saveGMTimes(userId, gamemodeTimes);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -104,7 +104,7 @@ public class CommandUseTable extends Table {
|
||||
commitRequired = true;
|
||||
}
|
||||
if (commitRequired) {
|
||||
Log.debug("CommandUse: Executing batch, size: "+data.size());
|
||||
Log.debug("CommandUse: Executing batch, size: " + data.size());
|
||||
statement.executeBatch();
|
||||
}
|
||||
} finally {
|
||||
|
@ -7,7 +7,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.database.databases.SQLDB;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import org.bukkit.GameMode;
|
||||
|
||||
/**
|
||||
@ -120,7 +119,7 @@ public class GMTimesTable extends Table {
|
||||
if (gamemodeTimes == null || gamemodeTimes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PreparedStatement statement = null;
|
||||
GameMode[] gms = new GameMode[]{GameMode.SURVIVAL, GameMode.CREATIVE, GameMode.ADVENTURE, GameMode.SPECTATOR};
|
||||
int update = 0;
|
||||
@ -147,12 +146,12 @@ public class GMTimesTable extends Table {
|
||||
}
|
||||
update = statement.executeUpdate();
|
||||
} finally {
|
||||
close(statement);
|
||||
close(statement);
|
||||
}
|
||||
if (update == 0) {
|
||||
addNewGMTimesRow(userId, gamemodeTimes);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void addNewGMTimesRow(int userId, Map<GameMode, Long> gamemodeTimes) throws SQLException {
|
||||
|
@ -21,7 +21,7 @@ import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
* @author Rsl1122
|
||||
*/
|
||||
public class IPsTable extends Table {
|
||||
|
||||
|
||||
private final String columnUserID;
|
||||
private final String columnIP;
|
||||
|
||||
@ -145,7 +145,13 @@ public class IPsTable extends Table {
|
||||
Benchmark.stop("Save Ips");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<Integer, Set<InetAddress>> getIPList(Collection<Integer> ids) throws SQLException {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
@ -177,7 +183,12 @@ public class IPsTable extends Table {
|
||||
Benchmark.stop("Get Ips Multiple " + ids.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ips
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void saveIPList(Map<Integer, Set<InetAddress>> ips) throws SQLException {
|
||||
if (ips == null || ips.isEmpty()) {
|
||||
return;
|
||||
|
@ -156,6 +156,13 @@ public class KillsTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ids
|
||||
* @param uuids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<Integer, List<KillData>> getPlayerKills(Collection<Integer> ids, Map<Integer, UUID> uuids) throws SQLException {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
@ -187,6 +194,12 @@ public class KillsTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param kills
|
||||
* @param uuids
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void savePlayerKills(Map<Integer, List<KillData>> kills, Map<Integer, UUID> uuids) throws SQLException {
|
||||
if (kills == null || kills.isEmpty()) {
|
||||
return;
|
||||
|
@ -91,7 +91,7 @@ public class LocationsTable extends Table {
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<Location> getLocations(int userId, HashMap<String, World> worlds) throws SQLException {
|
||||
public List<Location> getLocations(int userId, Map<String, World> worlds) throws SQLException {
|
||||
Benchmark.start("Get Locations");
|
||||
PreparedStatement statement = null;
|
||||
ResultSet set = null;
|
||||
@ -111,6 +111,29 @@ public class LocationsTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, List<Location>> getAllLocations(Map<String, World> worlds) throws SQLException {
|
||||
Benchmark.start("Get Locations Multiple");
|
||||
PreparedStatement statement = null;
|
||||
ResultSet set = null;
|
||||
Map<Integer, List<Location>> locations = new HashMap<>();
|
||||
try {
|
||||
statement = prepareStatement("SELECT * FROM " + tableName);
|
||||
set = statement.executeQuery();
|
||||
while (set.next()) {
|
||||
int id = set.getInt(columnID);
|
||||
if (!locations.containsKey(id)) {
|
||||
locations.put(id, new ArrayList<>());
|
||||
}
|
||||
locations.get(id).add(new Location(worlds.get(set.getString(columnWorld)), set.getInt(columnCoordinatesX), 0, set.getInt(columnCoordinatesZ)));
|
||||
}
|
||||
return locations;
|
||||
} finally {
|
||||
close(set);
|
||||
close(statement);
|
||||
Benchmark.stop("Get Locations Multiple");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param userId
|
||||
@ -121,7 +144,7 @@ public class LocationsTable extends Table {
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Benchmark.start("Save Locations "+locations.size());
|
||||
Benchmark.start("Save Locations " + locations.size());
|
||||
List<Location> newLocations = new ArrayList<>();
|
||||
newLocations.addAll(locations);
|
||||
PreparedStatement statement = null;
|
||||
@ -153,15 +176,20 @@ public class LocationsTable extends Table {
|
||||
}
|
||||
} finally {
|
||||
close(statement);
|
||||
Benchmark.stop("Save Locations "+locations.size());
|
||||
Benchmark.stop("Save Locations " + locations.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param locations
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void saveAdditionalLocationsLists(Map<Integer, List<Location>> locations) throws SQLException {
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Benchmark.start("Save Locations Multiple "+locations.size());
|
||||
Benchmark.start("Save Locations Multiple " + locations.size());
|
||||
PreparedStatement statement = null;
|
||||
try {
|
||||
statement = prepareStatement("INSERT INTO " + tableName + " ("
|
||||
@ -194,7 +222,7 @@ public class LocationsTable extends Table {
|
||||
}
|
||||
} finally {
|
||||
close(statement);
|
||||
Benchmark.stop("Save Locations Multiple "+locations.size());
|
||||
Benchmark.stop("Save Locations Multiple " + locations.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,6 +173,12 @@ public class NicknamesTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<Integer, List<String>> getNicknames(Collection<Integer> ids) throws SQLException {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
@ -218,6 +224,12 @@ public class NicknamesTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param nicknames
|
||||
* @param lastNicks
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void saveNickLists(Map<Integer, Set<String>> nicknames, Map<Integer, String> lastNicks) throws SQLException {
|
||||
if (nicknames == null || nicknames.isEmpty()) {
|
||||
return;
|
||||
|
@ -151,11 +151,17 @@ public class SessionsTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<Integer, List<SessionData>> getSessionData(Collection<Integer> ids) throws SQLException {
|
||||
if (ids == null || ids.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
Benchmark.start("Get Sessions multiple "+ids.size());
|
||||
Benchmark.start("Get Sessions multiple " + ids.size());
|
||||
PreparedStatement statement = null;
|
||||
ResultSet set = null;
|
||||
try {
|
||||
@ -179,15 +185,20 @@ public class SessionsTable extends Table {
|
||||
} finally {
|
||||
close(set);
|
||||
close(statement);
|
||||
Benchmark.stop("Get Sessions multiple "+ids.size());
|
||||
Benchmark.stop("Get Sessions multiple " + ids.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sessions
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void saveSessionData(Map<Integer, List<SessionData>> sessions) throws SQLException {
|
||||
if (sessions == null || sessions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Benchmark.start("Save Sessions multiple "+sessions.size());
|
||||
Benchmark.start("Save Sessions multiple " + sessions.size());
|
||||
Map<Integer, List<SessionData>> saved = getSessionData(sessions.keySet());
|
||||
PreparedStatement statement = null;
|
||||
try {
|
||||
@ -225,7 +236,7 @@ public class SessionsTable extends Table {
|
||||
}
|
||||
} finally {
|
||||
close(statement);
|
||||
Benchmark.start("Save Sessions multiple "+sessions.size());
|
||||
Benchmark.stop("Save Sessions multiple " + sessions.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@ -21,10 +20,7 @@ import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.database.databases.SQLDB;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.UUIDFetcher;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayers;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import static org.bukkit.Bukkit.getOfflinePlayer;
|
||||
|
||||
/**
|
||||
@ -284,6 +280,12 @@ public class UsersTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuid
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public UserData getUserData(UUID uuid) throws SQLException {
|
||||
Benchmark.start(uuid + " Get UserData");
|
||||
boolean containsBukkitData = getContainsBukkitData(uuid);
|
||||
@ -317,6 +319,12 @@ public class UsersTable extends Table {
|
||||
return containsBukkitData;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<UserData> getUserData(Collection<UUID> uuids) throws SQLException {
|
||||
Benchmark.start("Get UserData Multiple " + uuids.size());
|
||||
List<UUID> containsBukkitData = getContainsBukkitData(uuids);
|
||||
@ -337,6 +345,12 @@ public class UsersTable extends Table {
|
||||
return datas;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public List<UUID> getContainsBukkitData(Collection<UUID> uuids) throws SQLException {
|
||||
PreparedStatement statement = null;
|
||||
ResultSet set = null;
|
||||
@ -473,6 +487,11 @@ public class UsersTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @throws SQLException
|
||||
*/
|
||||
public void addUserInformationToUserData(List<UserData> data) throws SQLException {
|
||||
Benchmark.start("addUserInformationToUserData Multiple " + data.size());
|
||||
PreparedStatement statement = null;
|
||||
@ -704,6 +723,12 @@ public class UsersTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param uuids
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<UUID, Integer> getUserIds(Collection<UUID> uuids) throws SQLException {
|
||||
Benchmark.start("Get User IDS " + uuids.size());
|
||||
PreparedStatement statement = null;
|
||||
@ -728,6 +753,11 @@ public class UsersTable extends Table {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
* @throws SQLException
|
||||
*/
|
||||
public Map<UUID, Integer> getAllUserIds() throws SQLException {
|
||||
Benchmark.start("Get User IDS ALL");
|
||||
PreparedStatement statement = null;
|
||||
|
@ -41,8 +41,7 @@ public class VersionTable extends Table {
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
* @throws SQLException
|
||||
* @return @throws SQLException
|
||||
*/
|
||||
@Override
|
||||
public int getVersion() throws SQLException {
|
||||
|
@ -7,9 +7,9 @@ import main.java.com.djrapitops.plan.data.AnalysisData;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.data.cache.AnalysisCacheHandler;
|
||||
import main.java.com.djrapitops.plan.data.cache.InspectCacheHandler;
|
||||
import main.java.com.djrapitops.plan.utilities.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package main.java.com.djrapitops.plan.ui.graphs;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
@ -10,9 +11,11 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Settings;
|
||||
import main.java.com.djrapitops.plan.data.SessionData;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -35,16 +38,14 @@ public class PlayerActivityGraphCreator {
|
||||
List<Long> sessionStarts = s.get(0);
|
||||
List<Long> sessionEnds = s.get(1);
|
||||
|
||||
Benchmark.start("Player Activity Graph Before Addition");
|
||||
int amount = (int) sessionStarts.stream().filter(start -> start < nowMinusScale).count();
|
||||
for (int i = amount; i > 0; i--) {
|
||||
sessionStarts.add(nowMinusScale);
|
||||
}
|
||||
Benchmark.stop("Player Activity Graph Before Addition");
|
||||
Benchmark.start("Player Activity Graph Amount Calculation");
|
||||
|
||||
|
||||
Map<Long, Integer> change = transformIntoChangeMap(sessionStarts, sessionEnds);
|
||||
|
||||
|
||||
long lastPValue = 0;
|
||||
long lastSavedPValue = -1;
|
||||
long lastSaveIndex = 0;
|
||||
@ -67,13 +68,37 @@ public class PlayerActivityGraphCreator {
|
||||
playersOnline.add(lastPValue);
|
||||
}
|
||||
}
|
||||
if (Settings.ANALYSIS_REMOVE_OUTLIERS.isTrue()) {
|
||||
long average = MathUtils.averageLong(playersOnline.stream());
|
||||
double standardDiviation = getStandardDiviation(playersOnline, average);
|
||||
if (standardDiviation > 3) {
|
||||
for (int i = 0; i < playersOnline.size(); i++) {
|
||||
long value = playersOnline.get(i);
|
||||
if (value - average > 3 * standardDiviation) {
|
||||
playersOnline.set(i, (long) maxPlayers + 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Benchmark.stop("Player Activity Graph Amount Calculation");
|
||||
playersOnline.add(0L);
|
||||
playersOnline.add(0L);
|
||||
playersOnline.add(0L);
|
||||
playersOnline.add(0L);
|
||||
playersOnline.add((long) maxPlayers);
|
||||
Benchmark.stop("Generate Player Activity Graph " + sessionData.size() + " " + scale + " |");
|
||||
return new String[]{playersOnline.toString(), labels.toString()};
|
||||
}
|
||||
|
||||
private static double getStandardDiviation(List<Long> players, long avg) {
|
||||
List<Double> valueMinusAvg = players.stream()
|
||||
.map(p -> Math.pow(Math.abs(p - avg), 2))
|
||||
.collect(Collectors.toList());
|
||||
int size = valueMinusAvg.size();
|
||||
double sum = MathUtils.sumDouble(valueMinusAvg.stream().map(p -> (Serializable) p));
|
||||
return Math.sqrt(sum / size);
|
||||
}
|
||||
|
||||
private static Map<Long, Integer> transformIntoChangeMap(List<Long> sessionStarts, List<Long> sessionEnds) {
|
||||
Benchmark.start("Player Activity Graph Calc. Change");
|
||||
Map<Long, Integer> starts = sessionStarts.stream().distinct().collect(Collectors.toMap(Function.identity(), start -> Collections.frequency(sessionStarts, start)));
|
||||
@ -115,7 +140,6 @@ public class PlayerActivityGraphCreator {
|
||||
* @return
|
||||
*/
|
||||
public static List<List<Long>> filterAndTransformSessions(List<SessionData> sessionData, long nowMinusScale) {
|
||||
Benchmark.start("Player Activity Graph Transform " + sessionData.size() + " " + nowMinusScale);
|
||||
List<Long[]> values = sessionData.parallelStream()
|
||||
.filter(session -> (session != null))
|
||||
.filter(session -> session.isValid())
|
||||
@ -131,7 +155,6 @@ public class PlayerActivityGraphCreator {
|
||||
List<List<Long>> r = new ArrayList<>();
|
||||
r.add(sessionStarts);
|
||||
r.add(sessionEnds);
|
||||
Benchmark.stop("Player Activity Graph Transform " + sessionData.size() + " " + nowMinusScale);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -6,12 +6,15 @@
|
||||
package main.java.com.djrapitops.plan.ui.graphs;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.Settings;
|
||||
import main.java.com.djrapitops.plan.data.SessionData;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -19,10 +22,15 @@ import main.java.com.djrapitops.plan.data.SessionData;
|
||||
*/
|
||||
public class PunchCardGraphCreator {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static String generateDataArray(Collection<SessionData> data) {
|
||||
// Initialize dataset
|
||||
List<Long> sessionStarts = getSessionStarts(data);
|
||||
List<int[]> daysAndHours = getDaysAndHours(sessionStarts);
|
||||
List<int[]> daysAndHours = AnalysisUtils.getDaysAndHours(sessionStarts);
|
||||
int[][] dataArray = createDataArray(daysAndHours);
|
||||
int big = findBiggestValue(dataArray);
|
||||
int[][] scaled = scale(dataArray, big);
|
||||
@ -60,36 +68,65 @@ public class PunchCardGraphCreator {
|
||||
int h = dAndH[1];
|
||||
dataArray[d][h] = dataArray[d][h] + 1;
|
||||
}
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Log.debug(" " + Arrays.toString(dataArray[i]));
|
||||
}
|
||||
if (Settings.ANALYSIS_REMOVE_OUTLIERS.isTrue()) {
|
||||
int avg = findAverage(dataArray);
|
||||
double standardDiviation = getStandardDiviation(dataArray, avg);
|
||||
Log.debug("Diviation: " + standardDiviation);
|
||||
if (standardDiviation > 3) {
|
||||
for (int i = 0; i < 7; i++) {
|
||||
for (int j = 0; j < 24; j++) {
|
||||
int value = dataArray[i][j];
|
||||
if (value - avg > 3 * standardDiviation) {
|
||||
dataArray[i][j] = (int) (avg);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Log.debug(" " + Arrays.toString(dataArray[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
return dataArray;
|
||||
}
|
||||
|
||||
private static List<int[]> getDaysAndHours(List<Long> sessionStarts) {
|
||||
List<int[]> daysAndHours = sessionStarts.stream().map(start -> {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.setTimeInMillis(start);
|
||||
int hourOfDay = day.get(Calendar.HOUR_OF_DAY);
|
||||
private static double getStandardDiviation(int[][] array, int avg) {
|
||||
int[][] valueMinusAvg = new int[7][24];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
for (int j = 0; j < 24; j++) {
|
||||
valueMinusAvg[i][j] = (int) Math.pow(Math.abs(array[i][j] - avg), 2);
|
||||
}
|
||||
}
|
||||
int size = array.length * array[0].length;
|
||||
int sum = sum(valueMinusAvg);
|
||||
return Math.sqrt(sum / size);
|
||||
}
|
||||
|
||||
int dayOfWeek = day.get(Calendar.DAY_OF_WEEK) - 2;
|
||||
if (hourOfDay == 24) {
|
||||
hourOfDay = 0;
|
||||
dayOfWeek += 1;
|
||||
private static int findAverage(int[][] array) {
|
||||
int total = sum(array);
|
||||
int size = array.length * array[0].length;
|
||||
return (int) MathUtils.average(total, size);
|
||||
}
|
||||
|
||||
private static int sum(int[][] array) {
|
||||
int total = 0;
|
||||
for (int[] is : array) {
|
||||
for (int i : is) {
|
||||
total += i;
|
||||
}
|
||||
if (dayOfWeek > 6) {
|
||||
dayOfWeek = 0;
|
||||
}
|
||||
if (dayOfWeek < 0) {
|
||||
dayOfWeek = 6;
|
||||
}
|
||||
return new int[]{dayOfWeek, hourOfDay};
|
||||
}).collect(Collectors.toList());
|
||||
return daysAndHours;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
private static List<Long> getSessionStarts(Collection<SessionData> data) {
|
||||
long now = MiscUtils.getTime();
|
||||
List<Long> sessionStarts = data.stream()
|
||||
.filter(s -> s != null)
|
||||
.filter(s -> s.isValid())
|
||||
.map(s -> s.getSessionStart())
|
||||
.filter(start -> now - start < (long) 2592000 * (long) 1000)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
return sessionStarts;
|
||||
@ -130,7 +167,10 @@ public class PunchCardGraphCreator {
|
||||
scaled[i][j] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Log.debug("Biggest value: " + big);
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Log.debug(" " + Arrays.toString(scaled[i]));
|
||||
}
|
||||
return scaled;
|
||||
}
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.data.SessionData;
|
||||
import main.java.com.djrapitops.plan.utilities.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -22,11 +22,21 @@ import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
*/
|
||||
public class SessionLengthDistributionGraphCreator {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param data
|
||||
* @return
|
||||
*/
|
||||
public static String[] generateDataArraySessions(Collection<SessionData> data) {
|
||||
List<Long> lengths = AnalysisUtils.transformSessionDataToLengths(data);
|
||||
return generateDataArray(lengths);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param lengths
|
||||
* @return
|
||||
*/
|
||||
public static String[] generateDataArray(Collection<Long> lengths) {
|
||||
Map<Long, Integer> values = getValues(lengths);
|
||||
Map<Long, Integer> scaled = scale(values);
|
||||
|
@ -3,7 +3,7 @@ package main.java.com.djrapitops.plan.ui.tables;
|
||||
import java.util.Collection;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.ui.Html;
|
||||
import main.java.com.djrapitops.plan.utilities.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.HtmlUtils;
|
||||
|
@ -69,11 +69,11 @@ public class Response {
|
||||
String playerName = requestArgs[3].trim();
|
||||
UUID uuid = UUIDFetcher.getUUIDOf(playerName);
|
||||
if (uuid == null) {
|
||||
String errorMessage = "HTTP/1.1 404 UUID not Found\r\n"
|
||||
String errorMessage = "HTTP/1.1 500 UUID not Found\r\n"
|
||||
+ "Content-Type: text/html;\r\n"
|
||||
+ "Content-Length: 30\r\n"
|
||||
+ "\r\n"
|
||||
+ "<h1>404 - Player doesn't exist</h1>";
|
||||
+ "<h1>500 - Player has no UUID. </h1>";
|
||||
output.write(errorMessage.getBytes());
|
||||
return;
|
||||
}
|
||||
@ -87,11 +87,11 @@ public class Response {
|
||||
output.write((htmlDef + dataHtml).getBytes());
|
||||
} catch (NullPointerException e) {
|
||||
Log.toLog(this.getClass().getName(), e);
|
||||
String errorMessage = "HTTP/1.1 404 Error\r\n"
|
||||
String errorMessage = "HTTP/1.1 500 Error\r\n"
|
||||
+ "Content-Type: text/html;\r\n"
|
||||
+ "Content-Length: 30\r\n"
|
||||
+ "\r\n"
|
||||
+ "<h1>404 - Error has occurred..</h1>";
|
||||
+ "<h1>500 - Error has occurred..</h1>";
|
||||
output.write(errorMessage.getBytes());
|
||||
}
|
||||
return;
|
||||
|
@ -12,11 +12,20 @@ public class Benchmark {
|
||||
|
||||
private static Map<String, Long> starts = new HashMap<>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source
|
||||
*/
|
||||
public static void start(String source) {
|
||||
starts.put(source, System.nanoTime());
|
||||
Log.debug(source);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
public static long stop(String source) {
|
||||
Long s = starts.get(source);
|
||||
if (s != null) {
|
||||
|
@ -11,14 +11,30 @@ import org.bukkit.Location;
|
||||
*/
|
||||
public class FormatUtils {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ms
|
||||
* @return
|
||||
*/
|
||||
public static String formatTimeAmount(long ms) {
|
||||
return formatMilliseconds(ms);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param before
|
||||
* @param after
|
||||
* @return
|
||||
*/
|
||||
public static String formatTimeAmountDifference(long before, long after) {
|
||||
return formatMilliseconds(Math.abs(after - before));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param epochMs
|
||||
* @return
|
||||
*/
|
||||
public static String formatTimeStamp(long epochMs) {
|
||||
Date sfd = new Date(epochMs);
|
||||
return ("" + sfd).substring(4, 19);
|
||||
|
@ -106,7 +106,6 @@ public class HtmlUtils {
|
||||
StringBuilder html = new StringBuilder();
|
||||
String temp = "";
|
||||
int evenSize = pluginNames.size() - (pluginNames.size() % 2);
|
||||
Log.debug("Html parsing for:" + pluginNames + ", " + (evenSize));
|
||||
for (int i = 0; i < evenSize; i++) {
|
||||
String name = pluginNames.get(i);
|
||||
if (i % 2 == 0) {
|
||||
@ -134,7 +133,6 @@ public class HtmlUtils {
|
||||
}
|
||||
|
||||
private static String getContent(String name, List<String> placeholders) {
|
||||
Log.debug("Getting content for: "+name);
|
||||
StringBuilder html = new StringBuilder();
|
||||
html.append(Html.HEADER.parse(name));
|
||||
html.append(Html.PLUGIN_CONTAINER_START.parse());
|
||||
@ -145,6 +143,11 @@ public class HtmlUtils {
|
||||
return html.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string
|
||||
* @return
|
||||
*/
|
||||
public static String swapColorsToSpan(String string) {
|
||||
Html[] replacer = new Html[]{Html.COLOR_0, Html.COLOR_1, Html.COLOR_2, Html.COLOR_3,
|
||||
Html.COLOR_4, Html.COLOR_5, Html.COLOR_6, Html.COLOR_7, Html.COLOR_8, Html.COLOR_9,
|
||||
|
@ -4,13 +4,10 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.Permissions;
|
||||
import main.java.com.djrapitops.plan.Phrase;
|
||||
@ -21,13 +18,20 @@ import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
* Utility method class containing various static methods.
|
||||
*
|
||||
* @author Rsl1122
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class MiscUtils {
|
||||
|
||||
/**
|
||||
* Used to get the current time as milliseconds.
|
||||
*
|
||||
* @return Epoch ms.
|
||||
*/
|
||||
public static long getTime() {
|
||||
return new Date().getTime();
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,7 +44,11 @@ public class MiscUtils {
|
||||
Plan plugin = Plan.getInstance();
|
||||
String cVersion = plugin.getDescription().getVersion();
|
||||
String gitVersion = getGitVersion();
|
||||
return checkVersion(cVersion, gitVersion);
|
||||
if (checkVersion(cVersion, gitVersion)) {
|
||||
return Phrase.VERSION_NEW_AVAILABLE.parse(gitVersion);
|
||||
} else {
|
||||
return Phrase.VERSION_LATEST + "";
|
||||
}
|
||||
} catch (IOException | NumberFormatException e) {
|
||||
Log.error(Phrase.VERSION_CHECK_ERROR + "");
|
||||
}
|
||||
@ -68,14 +76,10 @@ public class MiscUtils {
|
||||
* @return
|
||||
* @throws NumberFormatException
|
||||
*/
|
||||
public static String checkVersion(String currentVersion, String gitVersion) throws NumberFormatException {
|
||||
public static boolean checkVersion(String currentVersion, String gitVersion) throws NumberFormatException {
|
||||
int newestVersionNumber = FormatUtils.parseVersionNumber(gitVersion);
|
||||
int currentVersionNumber = FormatUtils.parseVersionNumber(currentVersion);
|
||||
if (newestVersionNumber > currentVersionNumber) {
|
||||
return Phrase.VERSION_NEW_AVAILABLE.parse(gitVersion);
|
||||
} else {
|
||||
return Phrase.VERSION_LATEST + "";
|
||||
}
|
||||
return newestVersionNumber > currentVersionNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,6 +97,7 @@ public class MiscUtils {
|
||||
*
|
||||
* @param args Arguments of a command, must not be empty if console sender.
|
||||
* @param sender Command sender
|
||||
* @param perm
|
||||
* @return The name of the player (first argument or sender)
|
||||
*/
|
||||
public static String getPlayerName(String[] args, CommandSender sender, Permissions perm) {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package main.java.com.djrapitops.plan.utilities;
|
||||
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@ -33,6 +35,7 @@ public class PlaceholderUtils {
|
||||
public static Map<String, String> getAnalysisReplaceRules(AnalysisData data) {
|
||||
Benchmark.start("Replace Placeholders Anaysis");
|
||||
HashMap<String, String> replaceMap = new HashMap<>();
|
||||
replaceMap.put("%currenttime%", MiscUtils.getTime()+"");
|
||||
replaceMap.put("%gm0%", (int) (data.getGm0Perc() * 100) + "%");
|
||||
replaceMap.put("%gm1%", (int) (data.getGm1Perc() * 100) + "%");
|
||||
replaceMap.put("%gm2%", (int) (data.getGm2Perc() * 100) + "%");
|
||||
@ -62,6 +65,13 @@ public class PlaceholderUtils {
|
||||
replaceMap.put("%version%", plugin.getDescription().getVersion());
|
||||
replaceMap.put("%planlite%", "");
|
||||
replaceMap.put("%sortabletable%", data.getSortablePlayersTable());
|
||||
replaceMap.put("%uniquejoinsday%", data.getUniqueJoinsDay()+"");
|
||||
replaceMap.put("%uniquejoinsweek%", data.getUniqueJoinsWeek()+"");
|
||||
replaceMap.put("%uniquejoinsmonth%", data.getUniqueJoinsMonth()+"");
|
||||
replaceMap.put("%avguniquejoins%", data.getAvgUniqJoins()+"");
|
||||
replaceMap.put("%avguniquejoinsday%", data.getAvgUniqJoinsDay()+"");
|
||||
replaceMap.put("%avguniquejoinsweek%", data.getAvgUniqJoinsWeek()+"");
|
||||
replaceMap.put("%avguniquejoinsmonth%", data.getAvgUniqJoinsMonth()+"");
|
||||
replaceMap.put("%dataday%", data.getPlayersDataArray()[0]);
|
||||
replaceMap.put("%labelsday%", data.getPlayersDataArray()[1]);
|
||||
replaceMap.put("%dataweek%", data.getPlayersDataArray()[2]);
|
||||
@ -99,14 +109,6 @@ public class PlaceholderUtils {
|
||||
replaceMap.put("%gmlabels%", "[\"Survival\", \"Creative\", \"Adventure\", \"Spectator\"]");
|
||||
replaceMap.put("%gmcolors%", "\"#" + Settings.HCOLOR_GMP_0 + "\",\"#" + Settings.HCOLOR_GMP_1
|
||||
+ "\",\"#" + Settings.HCOLOR_GMP_2 + "\",\"#" + Settings.HCOLOR_GMP_3 + "\"");
|
||||
replaceMap.put("%genderdata%", Arrays.toString(data.getGenderData()));
|
||||
replaceMap.put("%gendermale%", data.getGenderData()[0] + "");
|
||||
replaceMap.put("%genderfemale%", data.getGenderData()[1] + "");
|
||||
replaceMap.put("%genderlabels%", "[\"Male\", \"Female\", \"Unknown\"]");
|
||||
replaceMap.put("%gendercolors%", "\"#" + Settings.HCOLOR_GENP_M + "\",\"#" + Settings.HCOLOR_GENP_F
|
||||
+ "\",\"#" + Settings.HCOLOR_GENP_U + "\"");
|
||||
replaceMap.put("%genderfcolor%", "#" + Settings.HCOLOR_GENP_F);
|
||||
replaceMap.put("%gendermcolor%", "#" + Settings.HCOLOR_GENP_M);
|
||||
replaceMap.put("%sessionaverage%", FormatUtils.formatTimeAmount(data.getSessionAverage()));
|
||||
replaceMap.put("%geomapcountries%", data.getGeomapCountries());
|
||||
replaceMap.put("%geomapz%", data.getGeomapZ());
|
||||
@ -128,6 +130,7 @@ public class PlaceholderUtils {
|
||||
replaceMap.put("#" + defaultCols[i], "#" + colors[i]);
|
||||
}
|
||||
}
|
||||
replaceMap.put("%refreshlong%", data.getRefreshDate()+"");
|
||||
replaceMap.put("%servername%", Settings.SERVER_NAME.toString());
|
||||
Benchmark.stop("Replace Placeholders Anaysis");
|
||||
return replaceMap;
|
||||
@ -142,6 +145,7 @@ public class PlaceholderUtils {
|
||||
*/
|
||||
public static Map<String, String> getInspectReplaceRules(UserData data) throws FileNotFoundException {
|
||||
Benchmark.start("Replace Placeholders Inspect");
|
||||
|
||||
HashMap<String, String> replaceMap = new HashMap<>();
|
||||
boolean showIPandUUID = Settings.SECURITY_IP_UUID.isTrue();
|
||||
UUID uuid = data.getUuid();
|
||||
@ -222,6 +226,8 @@ public class PlaceholderUtils {
|
||||
replaceMap.put("#" + defaultCols[i], "#" + colors[i]);
|
||||
}
|
||||
}
|
||||
replaceMap.put("%refreshlong%", plugin.getInspectCache().getCacheTime(uuid)+"");
|
||||
replaceMap.put("%currenttime%", MiscUtils.getTime()+"");
|
||||
replaceMap.put("%servername%", Settings.SERVER_NAME.toString());
|
||||
String pluginsTabHtml = plugin.getHookHandler().getPluginsTabLayoutForInspect();
|
||||
Map<String, String> additionalReplaceRules = plugin.getHookHandler().getAdditionalInspectReplaceRules(uuid);
|
||||
|
@ -1,4 +1,4 @@
|
||||
package main.java.com.djrapitops.plan.utilities;
|
||||
package main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
@ -30,6 +30,9 @@ import main.java.com.djrapitops.plan.ui.graphs.PunchCardGraphCreator;
|
||||
import main.java.com.djrapitops.plan.ui.graphs.SessionLengthDistributionGraphCreator;
|
||||
import main.java.com.djrapitops.plan.ui.tables.SortableCommandUseTableCreator;
|
||||
import main.java.com.djrapitops.plan.ui.tables.SortablePlayersTableCreator;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.HtmlUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.scheduler.BukkitRunnable;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
@ -113,14 +116,55 @@ public class Analysis {
|
||||
List<UUID> uuids = rawData.stream().map(d -> d.getUuid()).collect(Collectors.toList());
|
||||
Benchmark.stop("Analysis UUID transform");
|
||||
Benchmark.start("Analysis Create Empty dataset");
|
||||
Map<String, Integer> commandUse = plugin.getHandler().getCommandUse();
|
||||
long now = MiscUtils.getTime();
|
||||
final RawAnalysisData sorted = new RawAnalysisData();
|
||||
sorted.setCommandUse(plugin.getHandler().getCommandUse());
|
||||
AnalysisData analysisData = new AnalysisData();
|
||||
Benchmark.stop("Analysis Create Empty dataset");
|
||||
log(Phrase.ANALYSIS_BEGIN_ANALYSIS + "");
|
||||
String playersTable = SortablePlayersTableCreator.createSortablePlayersTable(rawData);
|
||||
analysisData.setSortablePlayersTable(playersTable);
|
||||
|
||||
RawAnalysisData sorted = fillDataset(commandUse, rawData, now);
|
||||
|
||||
// Analyze & Save RawAnalysisData to AnalysisData
|
||||
createCloroplethMap(analysisData, sorted.getGeolocations(), sorted.getGeocodes());
|
||||
createPlayerActivityGraphs(analysisData, sorted.getSessiondata(), sorted.getRegistered(), sorted.getSortedSessionData());
|
||||
analysisData.setRecentPlayers(RecentPlayersButtonsCreator.createRecentLoginsButtons(sorted.getLatestLogins(), 20));
|
||||
long totalPlaytime = sorted.getTotalPlaytime();
|
||||
analysisData.setTotalPlayTime(totalPlaytime);
|
||||
analysisData.setAveragePlayTime(totalPlaytime / rawData.size());
|
||||
analysisData.setSessionAverage(MathUtils.averageLong(AnalysisUtils.transformSessionDataToLengths(sorted.getSessiondata())));
|
||||
analysisData.setTotalLoginTimes(sorted.getTotalLoginTimes());
|
||||
createActivityVisalization(uuids.size(), sorted.getTotalBanned(), sorted.getActive(), sorted.getInactive(), sorted.getJoinleaver(), analysisData);
|
||||
analysisData.setOps(sorted.getOps());
|
||||
analyzeAverageAge(sorted.getAges(), analysisData);
|
||||
createGamemodeUsageVisualization(sorted.getGmZero(), sorted.getGmOne(), sorted.getGmTwo(), sorted.getGmThree(), analysisData);
|
||||
createCommandUseTable(sorted, analysisData);
|
||||
analysisData.setTotaldeaths(sorted.getTotalDeaths());
|
||||
analysisData.setTotalkills(sorted.getTotalKills());
|
||||
analysisData.setTotalmobkills(sorted.getTotalMobKills());
|
||||
analysisData.setRefreshDate(now);
|
||||
analysisData.setPunchCardData(PunchCardGraphCreator.generateDataArray(sorted.getSessiondata()));
|
||||
analysisData.setSessionDistributionData(SessionLengthDistributionGraphCreator.generateDataArraySessions(sorted.getSessiondata()));
|
||||
analysisData.setPlaytimeDistributionData(SessionLengthDistributionGraphCreator.generateDataArray(sorted.getPlaytimes().values()));
|
||||
|
||||
analysisData.setAdditionalDataReplaceMap(analyzeAdditionalPluginData(uuids));
|
||||
|
||||
analysisCache.cache(analysisData);
|
||||
Benchmark.stop("Analysis");
|
||||
if (Settings.ANALYSIS_LOG_FINISHED.isTrue()) {
|
||||
Log.info(Phrase.ANALYSIS_COMPLETE + "");
|
||||
}
|
||||
// LocationAnalysis.performAnalysis(analysisData, plugin.getDB());
|
||||
if (Settings.ANALYSIS_EXPORT.isTrue()) {
|
||||
ExportUtility.export(plugin, analysisData, rawData);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private RawAnalysisData fillDataset(Map<String, Integer> commandUse, List<UserData> rawData, long now) {
|
||||
final RawAnalysisData sorted = new RawAnalysisData();
|
||||
sorted.setCommandUse(commandUse);
|
||||
sorted.fillGeolocations();
|
||||
Benchmark.start("Analysis Fill Dataset");
|
||||
rawData.stream().forEach((uData) -> {
|
||||
@ -183,56 +227,14 @@ public class Analysis {
|
||||
sorted.addTotalDeaths(uData.getDeaths());
|
||||
List<SessionData> sessions = uData.getSessions();
|
||||
if (!sessions.isEmpty()) {
|
||||
sorted.getSessiondata().addAll(sessions);
|
||||
sorted.addSessions(uData.getUuid(), sessions);
|
||||
}
|
||||
sorted.getRegistered().add(uData.getRegistered());
|
||||
sorted.addGeoloc(demData.getGeoLocation());
|
||||
uData.stopAccessing();
|
||||
Gender gender = demData.getGender();
|
||||
if (null != gender) {
|
||||
switch (gender) {
|
||||
case MALE:
|
||||
sorted.addToGender(0, 1);
|
||||
break;
|
||||
case FEMALE:
|
||||
sorted.addToGender(1, 1);
|
||||
break;
|
||||
default:
|
||||
sorted.addToGender(2, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
Benchmark.stop("Analysis Fill Dataset");
|
||||
createCloroplethMap(analysisData, sorted.getGeolocations(), sorted.getGeocodes());
|
||||
// Analyze & Save RawAnalysisData to AnalysisData
|
||||
createPlayerActivityGraphs(analysisData, sorted.getSessiondata(), sorted.getRegistered());
|
||||
analysisData.setRecentPlayers(RecentPlayersButtonsCreator.createRecentLoginsButtons(sorted.getLatestLogins(), 20));
|
||||
long totalPlaytime = sorted.getTotalPlaytime();
|
||||
analysisData.setTotalPlayTime(totalPlaytime);
|
||||
analysisData.setAveragePlayTime(totalPlaytime / rawData.size());
|
||||
analysisData.setSessionAverage(MathUtils.averageLong(AnalysisUtils.transformSessionDataToLengths(sorted.getSessiondata())));
|
||||
analysisData.setTotalLoginTimes(sorted.getTotalLoginTimes());
|
||||
createActivityVisalization(uuids.size(), sorted.getTotalBanned(), sorted.getActive(), sorted.getInactive(), sorted.getJoinleaver(), analysisData);
|
||||
analysisData.setOps(sorted.getOps());
|
||||
analyzeAverageAge(sorted.getAges(), analysisData);
|
||||
createGamemodeUsageVisualization(sorted.getGmZero(), sorted.getGmOne(), sorted.getGmTwo(), sorted.getGmThree(), analysisData);
|
||||
createCommandUseTable(sorted, analysisData);
|
||||
analysisData.setTotaldeaths(sorted.getTotalDeaths());
|
||||
analysisData.setTotalkills(sorted.getTotalKills());
|
||||
analysisData.setTotalmobkills(sorted.getTotalMobKills());
|
||||
analysisData.setRefreshDate(now);
|
||||
analysisData.setGenderData(sorted.getGenders());
|
||||
analysisData.setPunchCardData(PunchCardGraphCreator.generateDataArray(sorted.getSessiondata()));
|
||||
analysisData.setSessionDistributionData(SessionLengthDistributionGraphCreator.generateDataArraySessions(sorted.getSessiondata()));
|
||||
analysisData.setPlaytimeDistributionData(SessionLengthDistributionGraphCreator.generateDataArray(sorted.getPlaytimes().values()));
|
||||
analysisData.setAdditionalDataReplaceMap(analyzeAdditionalPluginData(uuids));
|
||||
analysisCache.cache(analysisData);
|
||||
Benchmark.stop("Analysis");
|
||||
if (Settings.ANALYSIS_LOG_FINISHED.isTrue()) {
|
||||
Log.info(Phrase.ANALYSIS_COMPLETE + "");
|
||||
}
|
||||
return true;
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private void createCommandUseTable(final RawAnalysisData raw, AnalysisData data) {
|
||||
@ -258,16 +260,9 @@ public class Analysis {
|
||||
Benchmark.stop("Analysis Activity Visualization");
|
||||
}
|
||||
|
||||
// TODO Refactor
|
||||
private void analyzeAverageAge(List<Integer> ages, AnalysisData data) {
|
||||
int totalAge = 0;
|
||||
for (int age : ages) {
|
||||
totalAge += age;
|
||||
}
|
||||
double averageAge;
|
||||
if (!ages.isEmpty()) {
|
||||
averageAge = totalAge * 1.0 / ages.size();
|
||||
} else {
|
||||
double averageAge = MathUtils.averageInt(ages.stream());
|
||||
if (averageAge == 0) {
|
||||
averageAge = -1;
|
||||
}
|
||||
data.setAverageAge(averageAge);
|
||||
@ -291,7 +286,7 @@ public class Analysis {
|
||||
Benchmark.stop("Analysis GMVisualization");
|
||||
}
|
||||
|
||||
private void createPlayerActivityGraphs(AnalysisData data, List<SessionData> sData, List<Long> registered) {
|
||||
private void createPlayerActivityGraphs(AnalysisData data, List<SessionData> sData, List<Long> registered, Map<UUID, List<SessionData>> sortedSData) {
|
||||
long now = new Date().toInstant().getEpochSecond() * (long) 1000;
|
||||
|
||||
long scaleDay = 86400 * 1000;
|
||||
@ -303,12 +298,25 @@ public class Analysis {
|
||||
data.setNewPlayersWeek(AnalysisUtils.getNewPlayers(registered, scaleWeek, now));
|
||||
data.setNewPlayersMonth(AnalysisUtils.getNewPlayers(registered, scaleMonth, now));
|
||||
|
||||
Benchmark.start("Analysis Unique/day");
|
||||
data.setAvgUniqJoins(AnalysisUtils.getUniqueJoinsPerDay(sortedSData, -1));
|
||||
data.setAvgUniqJoinsDay(AnalysisUtils.getUniqueJoinsPerDay(sortedSData, scaleDay));
|
||||
data.setAvgUniqJoinsWeek(AnalysisUtils.getUniqueJoinsPerDay(sortedSData, scaleWeek));
|
||||
data.setAvgUniqJoinsMonth(AnalysisUtils.getUniqueJoinsPerDay(sortedSData, scaleMonth));
|
||||
Benchmark.stop("Analysis Unique/day");
|
||||
|
||||
Benchmark.start("Analysis Unique");
|
||||
data.setUniqueJoinsDay(AnalysisUtils.getUniqueJoins(sortedSData, scaleDay));
|
||||
data.setUniqueJoinsWeek(AnalysisUtils.getUniqueJoins(sortedSData, scaleWeek));
|
||||
data.setUniqueJoinsMonth(AnalysisUtils.getUniqueJoins(sortedSData, scaleMonth));
|
||||
Benchmark.stop("Analysis Unique");
|
||||
|
||||
List<SessionData> sessions = sData.stream()
|
||||
.filter(session -> (session != null))
|
||||
.filter(session -> session.isValid())
|
||||
.filter((session) -> (session.getSessionStart() >= now-scaleMonth || session.getSessionEnd() >= now-scaleMonth))
|
||||
.filter((session) -> (session.getSessionStart() >= now - scaleMonth || session.getSessionEnd() >= now - scaleMonth))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
||||
String[] dayArray = PlayerActivityGraphCreator.generateDataArray(sessions, scaleDay, maxPlayers);
|
||||
String[] weekArray = PlayerActivityGraphCreator.generateDataArray(sessions, scaleWeek, maxPlayers);
|
||||
String[] monthArray = PlayerActivityGraphCreator.generateDataArray(sessions, scaleMonth, maxPlayers);
|
||||
@ -347,6 +355,7 @@ public class Analysis {
|
||||
}
|
||||
|
||||
private Map<String, String> analyzeAdditionalPluginData(List<UUID> uuids) {
|
||||
Benchmark.start("Analysis 3rd party");
|
||||
final Map<String, String> replaceMap = new HashMap<>();
|
||||
final HookHandler hookHandler = plugin.getHookHandler();
|
||||
final List<PluginData> sources = hookHandler.getAdditionalDataSources();
|
||||
@ -393,6 +402,7 @@ public class Analysis {
|
||||
Benchmark.stop("Source " + source.getPlaceholder("").replace("%", ""));
|
||||
}
|
||||
});
|
||||
Benchmark.stop("Analysis 3rd party");
|
||||
return replaceMap;
|
||||
}
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
package main.java.com.djrapitops.plan.utilities;
|
||||
package main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@ -11,6 +16,8 @@ import main.java.com.djrapitops.plan.Settings;
|
||||
import main.java.com.djrapitops.plan.data.SessionData;
|
||||
import main.java.com.djrapitops.plan.data.additional.AnalysisType;
|
||||
import main.java.com.djrapitops.plan.data.additional.PluginData;
|
||||
import main.java.com.djrapitops.plan.utilities.FormatUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -30,6 +37,14 @@ public class AnalysisUtils {
|
||||
return isActive(MiscUtils.getTime(), lastPlayed, playTime, loginTimes);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param now
|
||||
* @param lastPlayed
|
||||
* @param playTime
|
||||
* @param loginTimes
|
||||
* @return
|
||||
*/
|
||||
public static boolean isActive(long now, long lastPlayed, long playTime, int loginTimes) {
|
||||
int timeToActive = Settings.ANALYSIS_MINUTES_FOR_ACTIVE.getNumber();
|
||||
if (timeToActive < 0) {
|
||||
@ -54,7 +69,6 @@ public class AnalysisUtils {
|
||||
* @return
|
||||
*/
|
||||
public static int getNewPlayers(List<Long> registered, long scale, long now) {
|
||||
Benchmark.start("Get new players for "+registered.size()+" "+scale+" | ");
|
||||
int newPlayers = 0;
|
||||
if (!registered.isEmpty()) {
|
||||
newPlayers = registered.stream()
|
||||
@ -63,7 +77,6 @@ public class AnalysisUtils {
|
||||
.map((_item) -> 1).reduce(newPlayers, Integer::sum);
|
||||
}
|
||||
// Filters out register dates before scale
|
||||
Benchmark.stop("Get new players for "+registered.size()+" "+scale+" | ");
|
||||
return newPlayers;
|
||||
}
|
||||
|
||||
@ -162,6 +175,13 @@ public class AnalysisUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param analysisType
|
||||
* @param source
|
||||
* @param uuids
|
||||
* @return
|
||||
*/
|
||||
public static String getBooleanPercentage(AnalysisType analysisType, PluginData source, List<UUID> uuids) {
|
||||
if (analysisType == AnalysisType.BOOLEAN_PERCENTAGE) {
|
||||
try {
|
||||
@ -177,6 +197,13 @@ public class AnalysisUtils {
|
||||
return source.parseContainer("Err ", "Wrong Analysistype specified: " + analysisType.name());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param analysisType
|
||||
* @param source
|
||||
* @param uuids
|
||||
* @return
|
||||
*/
|
||||
public static String getBooleanTotal(AnalysisType analysisType, PluginData source, List<UUID> uuids) {
|
||||
if (analysisType == AnalysisType.BOOLEAN_TOTAL) {
|
||||
try {
|
||||
@ -198,4 +225,74 @@ public class AnalysisUtils {
|
||||
Log.toLog("com.djrapitops.plan.utilities.AnalysisUtils", e);
|
||||
return source.parseContainer("", "Exception during calculation.");
|
||||
}
|
||||
|
||||
public static Integer getUniqueJoins(Map<UUID, List<SessionData>> sessions, long scale) {
|
||||
long now = MiscUtils.getTime();
|
||||
long nowMinusScale = now - scale;
|
||||
Set<UUID> uniqueJoins = new HashSet<>();
|
||||
sessions.keySet().stream().forEach((uuid) -> {
|
||||
List<SessionData> s = sessions.get(uuid);
|
||||
for (SessionData session : s) {
|
||||
if (session.getSessionStart() < nowMinusScale) {
|
||||
continue;
|
||||
}
|
||||
uniqueJoins.add(uuid);
|
||||
}
|
||||
});
|
||||
return uniqueJoins.size();
|
||||
}
|
||||
|
||||
public static Integer getUniqueJoinsPerDay(Map<UUID, List<SessionData>> sessions, long scale) {
|
||||
Map<Integer, Set<UUID>> uniqueJoins = new HashMap<>();
|
||||
long now = MiscUtils.getTime();
|
||||
long nowMinusScale = now - scale;
|
||||
sessions.keySet().stream().forEach((uuid) -> {
|
||||
List<SessionData> s = sessions.get(uuid);
|
||||
for (SessionData session : s) {
|
||||
if (scale != -1) {
|
||||
if (session.getSessionStart() < nowMinusScale) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
int day = getDayOfYear(session);
|
||||
if (!uniqueJoins.containsKey(day)) {
|
||||
uniqueJoins.put(day, new HashSet<>());
|
||||
}
|
||||
uniqueJoins.get(day).add(uuid);
|
||||
}
|
||||
});
|
||||
int total = MathUtils.sumInt(uniqueJoins.values().stream().map(s -> s.size()));
|
||||
int size = uniqueJoins.keySet().size();
|
||||
if (size == 0) {
|
||||
return 0;
|
||||
}
|
||||
return total / size;
|
||||
}
|
||||
|
||||
public static List<int[]> getDaysAndHours(List<Long> sessionStarts) {
|
||||
List<int[]> daysAndHours = sessionStarts.stream().map((Long start) -> {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.setTimeInMillis(start);
|
||||
int hourOfDay = day.get(Calendar.HOUR_OF_DAY);
|
||||
int dayOfWeek = day.get(Calendar.DAY_OF_WEEK) - 2;
|
||||
if (hourOfDay == 24) {
|
||||
hourOfDay = 0;
|
||||
dayOfWeek += 1;
|
||||
}
|
||||
if (dayOfWeek > 6) {
|
||||
dayOfWeek = 0;
|
||||
}
|
||||
if (dayOfWeek < 0) {
|
||||
dayOfWeek = 6;
|
||||
}
|
||||
return new int[]{dayOfWeek, hourOfDay};
|
||||
}).collect(Collectors.toList());
|
||||
return daysAndHours;
|
||||
}
|
||||
|
||||
private static int getDayOfYear(SessionData session) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.setTimeInMillis(session.getSessionStart());
|
||||
return day.get(Calendar.DAY_OF_YEAR);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.Plan;
|
||||
import main.java.com.djrapitops.plan.Settings;
|
||||
import main.java.com.djrapitops.plan.data.AnalysisData;
|
||||
import main.java.com.djrapitops.plan.data.UserData;
|
||||
import main.java.com.djrapitops.plan.ui.DataRequestHandler;
|
||||
import main.java.com.djrapitops.plan.ui.webserver.WebSocketServer;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.HtmlUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.PlaceholderUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rsl1122
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public class ExportUtility {
|
||||
|
||||
private static File getFolder() throws IOException {
|
||||
String path = Settings.ANALYSIS_EXPORT_PATH.toString();
|
||||
if (path.contains(":")) {
|
||||
File folder = new File(path);
|
||||
if (folder.exists()) {
|
||||
if (folder.isDirectory()) {
|
||||
return folder;
|
||||
}
|
||||
}
|
||||
folder.mkdirs();
|
||||
return folder;
|
||||
}
|
||||
File dataFolder = Plan.getInstance().getDataFolder();
|
||||
File folder = new File(dataFolder, path);
|
||||
folder.mkdirs();
|
||||
return folder;
|
||||
}
|
||||
|
||||
public static void export(Plan plugin, AnalysisData analysisData, List<UserData> rawData) {
|
||||
Benchmark.start("Exporting Html pages");
|
||||
try {
|
||||
File folder = getFolder();
|
||||
writeAnalysisHtml(analysisData, folder);
|
||||
File playersFolder = new File(folder, "player");
|
||||
playersFolder.mkdirs();
|
||||
for (UserData userData : rawData) {
|
||||
writeInspectHtml(userData, playersFolder);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
Log.toLog("ExportUtils.export", ex);
|
||||
} finally {
|
||||
Benchmark.stop("Exporting Html pages");
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeInspectHtml(UserData userData, File playersFolder) throws FileNotFoundException, IOException {
|
||||
String inspectHtml = HtmlUtils.replacePlaceholders(HtmlUtils.getHtmlStringFromResource("player.html"),
|
||||
PlaceholderUtils.getInspectReplaceRules(userData));
|
||||
File playerFolder = new File(playersFolder, userData.getName());
|
||||
playerFolder.mkdir();
|
||||
File inspectHtmlFile = new File(playerFolder, "index.html");
|
||||
if (inspectHtmlFile.exists()) {
|
||||
inspectHtmlFile.delete();
|
||||
}
|
||||
Files.write(inspectHtmlFile.toPath(), Arrays.asList(inspectHtml));
|
||||
}
|
||||
|
||||
private static void writeAnalysisHtml(AnalysisData analysisData, File folder) throws FileNotFoundException, IOException {
|
||||
String analysisHtml = HtmlUtils.replacePlaceholders(HtmlUtils.getHtmlStringFromResource("analysis.html"),
|
||||
PlaceholderUtils.getAnalysisReplaceRules(analysisData))
|
||||
.replace(HtmlUtils.getInspectUrl(""), "./player/");
|
||||
File analysisHtmlFile = new File(folder, "analysis.html");
|
||||
if (analysisHtmlFile.exists()) {
|
||||
analysisHtmlFile.delete();
|
||||
}
|
||||
Files.write(analysisHtmlFile.toPath(), Arrays.asList(analysisHtml));
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main.java.com.djrapitops.plan.utilities;
|
||||
package main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
@ -11,6 +11,11 @@ import java.util.stream.Stream;
|
||||
*/
|
||||
public class MathUtils {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static double averageInt(Stream<Integer> values) {
|
||||
OptionalDouble average = values.mapToInt(i -> i).average();
|
||||
if (average.isPresent()) {
|
||||
@ -20,10 +25,20 @@ public class MathUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static long averageLong(Collection<Long> values) {
|
||||
return averageLong(values.stream());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static long averageLong(Stream<Long> values) {
|
||||
OptionalDouble average = values.mapToLong(i -> i).average();
|
||||
if (average.isPresent()) {
|
||||
@ -33,6 +48,11 @@ public class MathUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static double averageDouble(Stream<Double> values) {
|
||||
OptionalDouble average = values.mapToDouble(i -> i).average();
|
||||
if (average.isPresent()) {
|
||||
@ -42,32 +62,63 @@ public class MathUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param total
|
||||
* @param size
|
||||
* @return
|
||||
*/
|
||||
public static double average(int total, int size) {
|
||||
return 1.0 * total / size;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static long countTrueBoolean(Stream<Boolean> values) {
|
||||
return values.filter(i -> i).count();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static int sumInt(Stream<Serializable> values) {
|
||||
return values
|
||||
.mapToInt(value -> (Integer) value)
|
||||
.sum();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static long sumLong(Stream<Serializable> values) {
|
||||
return values
|
||||
.mapToLong(value -> (Long) value)
|
||||
.sum();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static double sumDouble(Stream<Serializable> values) {
|
||||
return values
|
||||
.mapToDouble(value -> (Double) value)
|
||||
.sum();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static int getBiggest(Collection<Integer> values) {
|
||||
int biggest = 1;
|
||||
for (Integer value : values) {
|
||||
@ -77,6 +128,12 @@ public class MathUtils {
|
||||
}
|
||||
return biggest;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param values
|
||||
* @return
|
||||
*/
|
||||
public static long getBiggestLong(Collection<Long> values) {
|
||||
long biggest = 1;
|
||||
for (Long value : values) {
|
@ -0,0 +1,96 @@
|
||||
package main.java.com.djrapitops.plan.utilities.analysis.locations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import main.java.com.djrapitops.plan.Log;
|
||||
import main.java.com.djrapitops.plan.data.AnalysisData;
|
||||
import main.java.com.djrapitops.plan.database.Database;
|
||||
import main.java.com.djrapitops.plan.utilities.Benchmark;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import static org.bukkit.Bukkit.getWorlds;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Rsl1122
|
||||
* @since 3.4.0
|
||||
*/
|
||||
public class LocationAnalysis {
|
||||
|
||||
public static void performAnalysis(AnalysisData data, Database db) {
|
||||
Benchmark.start("Location Analysis");
|
||||
try {
|
||||
Map<Integer, List<Location>> playerLocations = db.getLocationsTable().getAllLocations(getWorlds().stream().collect(Collectors.toMap(w -> w.getName(), Function.identity())));
|
||||
List<Location> locations = new ArrayList<>();
|
||||
for (Integer id : playerLocations.keySet()) {
|
||||
locations.addAll(playerLocations.get(id));
|
||||
}
|
||||
Map<String, Map<Point, Integer>> worldPoints = getWorldPoints(locations);
|
||||
for (String world : worldPoints.keySet()) {
|
||||
Map<Point, Integer> worldLocs = worldPoints.get(world);
|
||||
Set<Point> frequentPoints = getFrequentPoints(worldLocs);
|
||||
Log.debug(frequentPoints.toString());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.toLog("LocationAnalysis.performAnalysis", ex);
|
||||
}
|
||||
Benchmark.stop("Location Analysis");
|
||||
}
|
||||
|
||||
public static Map<Point, Object> cluster(Collection<Point> freqPoints, Collection<Point> allPoints) {
|
||||
Benchmark.start("LocAnalysis cluster");
|
||||
allPoints.removeAll(freqPoints);
|
||||
for (Point point : freqPoints) {
|
||||
Set<Point> cluster = allPoints.stream().filter(p -> distance(point, p) < 5).collect(Collectors.toSet());
|
||||
}
|
||||
Benchmark.stop("LocAnalysis cluster");
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
public static Set<Point> getFrequentPoints(Map<Point, Integer> points) {
|
||||
Benchmark.start("LocAnalysis getFrequentPoints");
|
||||
if (points.isEmpty()) {
|
||||
return new HashSet<>();
|
||||
}
|
||||
double averageFreq = MathUtils.averageInt(points.values().stream());
|
||||
Set<Point> freqPoints = points.entrySet().stream().filter(e -> e.getValue() > averageFreq).map(e -> e.getKey()).collect(Collectors.toSet());
|
||||
Benchmark.stop("LocAnalysis getFrequentPoints");
|
||||
return freqPoints;
|
||||
}
|
||||
|
||||
public static Map<String, Map<Point, Integer>> getWorldPoints(Collection<Location> locations) {
|
||||
Benchmark.start("LocAnalysis getWorldPoints");
|
||||
Map<String, Map<Point, Integer>> pointMap = new HashMap<>();
|
||||
for (Location location : locations) {
|
||||
World world = location.getWorld();
|
||||
if (world == null) {
|
||||
continue;
|
||||
}
|
||||
String worldName = world.getName();
|
||||
if (!pointMap.containsKey(worldName)) {
|
||||
pointMap.put(worldName, new HashMap<>());
|
||||
}
|
||||
Map<Point, Integer> numOfLocs = pointMap.get(worldName);
|
||||
Point point = new Point(location.getBlockX(), location.getBlockZ());
|
||||
if (!numOfLocs.containsKey(point)) {
|
||||
numOfLocs.put(point, 0);
|
||||
}
|
||||
numOfLocs.replace(point, numOfLocs.get(point) + 1);
|
||||
}
|
||||
Benchmark.stop("LocAnalysis getWorldPoints");
|
||||
return pointMap;
|
||||
}
|
||||
|
||||
public static double distance(Point one, Point two) {
|
||||
return Math.hypot(one.getX() - two.getX(), one.getY() - one.getY());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package main.java.com.djrapitops.plan.utilities.analysis.locations;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Risto
|
||||
*/
|
||||
public class Point {
|
||||
|
||||
final private int x;
|
||||
final private int y;
|
||||
|
||||
public Point(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
hash = 97 * hash + this.x;
|
||||
hash = 97 * hash + this.y;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Point other = (Point) obj;
|
||||
if (this.x != other.x) {
|
||||
return false;
|
||||
}
|
||||
if (this.y != other.y) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "P[x:" + x + "|y:" + y + ']';
|
||||
}
|
||||
|
||||
}
|
@ -279,7 +279,7 @@ header p {
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body onload="countUpTimer()">
|
||||
<header>
|
||||
<div class="header-content">
|
||||
<img src="http://puu.sh/tJZUb/c2e0ab220f.png" alt="Player Analytics | Analysis">
|
||||
@ -290,7 +290,7 @@ header p {
|
||||
<div id="content" class="content">
|
||||
|
||||
<div id="sidenav" class="sidenav">
|
||||
<p>Last Refresh: <br>%refresh% ago</p>
|
||||
<p>Last Refresh: <br><span id="divTime">%refresh%</span> ago</p>
|
||||
<a href="javascript:void(0)" class="nav-button">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i> Information
|
||||
</a>
|
||||
@ -336,7 +336,8 @@ header p {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="playerChartDay" width="1000" height="350" style="width: 95%;"></canvas><br/>
|
||||
<canvas id="playerChartDay" width="1000" height="350" style="width: 95%;"></canvas>
|
||||
<p><i class="fa fa-user-circle" aria-hidden="true"></i> Unique Players: %uniquejoinsday% | <i class="fa fa-user-circle-o" aria-hidden="true"></i> Unique/Day: %avguniquejoinsday%</p>
|
||||
</div>
|
||||
<div class=" box column" style="order: 5;">
|
||||
<p class="header-label" style="color: #267F00; "><i class="fa fa-calendar-check-o" aria-hidden="true"></i><span class="header-text"> Recent Logins</span></p>
|
||||
@ -367,7 +368,8 @@ header p {
|
||||
</div>
|
||||
<p><i class="fa fa-clock-o" aria-hidden="true"></i> Total Playtime: %totalplaytime% | <i class="fa fa-clock-o" aria-hidden="true"></i> Player Average: %avgplaytime%<br/>
|
||||
<i class="fa fa-clock-o" aria-hidden="true"></i> Average Session Length: %sessionaverage%<br/>
|
||||
<i class="fa fa-calendar-plus-o" aria-hidden="true"></i> Total Login times: %totallogins%<br/>
|
||||
<i class="fa fa-calendar-plus-o" aria-hidden="true"></i> Total Login times: %totallogins%<br/>
|
||||
<i class="fa fa-user-circle-o" aria-hidden="true"></i> Average Unique Players/Day: %avguniquejoins%<br>
|
||||
<b><i class="fa fa-crosshairs" aria-hidden="true"></i></b> Player kills: %playerkills% | <i class="fa fa-crosshairs" aria-hidden="true"></i> Mob kills: %mobkills% | <i class="fa fa-meh-o" aria-hidden="true"></i> Deaths: %deaths%</p>
|
||||
</div>
|
||||
<div class=" box column">
|
||||
@ -452,6 +454,7 @@ header p {
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="playerChartDay2" width="1000" height="350" style="width: 95%;"></canvas>
|
||||
<p><i class="fa fa-user-circle" aria-hidden="true"></i> Unique Players: %uniquejoinsday% | <i class="fa fa-user-circle-o" aria-hidden="true"></i> Unique/Day: %avguniquejoinsday%</p>
|
||||
</div>
|
||||
<div class=" box column">
|
||||
<div class="headerbox">
|
||||
@ -473,6 +476,7 @@ header p {
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="playerChartWeek" width="1000" height="350" style="width: 95%;"></canvas>
|
||||
<p><i class="fa fa-user-circle" aria-hidden="true"></i> Unique Players: %uniquejoinsweek% | <i class="fa fa-user-circle-o" aria-hidden="true"></i> Unique/Day: %avguniquejoinsweek%</p>
|
||||
</div>
|
||||
<div class=" box column">
|
||||
<div class="headerbox">
|
||||
@ -494,6 +498,7 @@ header p {
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="playerChartMonth" width="1000" height="350" style="width: 95%;"></canvas>
|
||||
<p><i class="fa fa-user-circle" aria-hidden="true"></i> Unique Playes: %uniquejoinsmonth% | <i class="fa fa-user-circle-o" aria-hidden="true"></i> Unique/Day: %avguniquejoinsmonth%</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -585,7 +590,7 @@ header p {
|
||||
<div class=" box column">
|
||||
<div class="headerbox">
|
||||
<div class="header-icon" style="width: 50%">
|
||||
<div class="header-label"><i class="fa fa-braille" aria-hidden="true"></i><span class="header-text"> PunchCard</span></div>
|
||||
<div class="header-label"><i class="fa fa-braille" aria-hidden="true"></i><span class="header-text"> PunchCard - Last 30d</span></div>
|
||||
</div>
|
||||
<div class="infobox" >
|
||||
<div class="info-icon">
|
||||
@ -730,7 +735,7 @@ header p {
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<div class="row">
|
||||
<div class="row" style="width: 100%;">
|
||||
<div class=" box column">
|
||||
<div class="headerbox">
|
||||
<div class="header-icon">
|
||||
@ -753,42 +758,6 @@ header p {
|
||||
<div style="width:100%;"><div id="cloropleth"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class=" box column">
|
||||
<div class="headerbox">
|
||||
<div class="header-icon">
|
||||
<div class="header-label"><i class="fa fa-male" aria-hidden="true"></i><i class="fa fa-female" aria-hidden="true"></i><span class="header-text"> Gender Distribution</span></div>
|
||||
</div>
|
||||
<div class="infobox" style="width: 22%; background-color: %genderfcolor%">
|
||||
<div class="info-icon">
|
||||
<i class="fa fa-female" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="info-text">
|
||||
<div class="info-number">
|
||||
%genderfemale%
|
||||
</div>
|
||||
<div class="info-label">
|
||||
Female
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="infobox" style="width: 22%; background-color: %gendermcolor%">
|
||||
<div class="info-icon">
|
||||
<i class="fa fa-male" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="info-text">
|
||||
<div class="info-number">
|
||||
%gendermale%
|
||||
</div>
|
||||
<div class="info-label">
|
||||
Male
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="genderPie" width="1000" height="600" style="width: 95%;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab" style="display: block;">
|
||||
%plugins%
|
||||
@ -808,7 +777,7 @@ function closeNav() {
|
||||
}
|
||||
var navButtons = document.getElementsByClassName("nav-button");
|
||||
var tabs = document.getElementsByClassName("tab");
|
||||
var slideIndex = window.localStorage.getItem("AnalysisSlideIndex");
|
||||
var slideIndex = window.sessionStorage.getItem("AnalysisSlideIndex");
|
||||
if (slideIndex == null) {
|
||||
slideIndex = 0;
|
||||
}
|
||||
@ -823,6 +792,7 @@ for (i=0; i < navButtons.length; i++) {
|
||||
}
|
||||
x.style.opacity = "1";
|
||||
openFunc(slideIndex)();
|
||||
countUpTimer();
|
||||
|
||||
function openFunc(i) {
|
||||
return function() {
|
||||
@ -843,12 +813,48 @@ function openFunc(i) {
|
||||
slideIndex = i;
|
||||
if (slideIndex>max) {slideIndex=0};
|
||||
if (slideIndex<0) {slideIndex=max};
|
||||
window.localStorage.setItem("AnalysisSlideIndex", slideIndex);
|
||||
window.sessionStorage.setItem("AnalysisSlideIndex", slideIndex);
|
||||
var value = slideIndex*perc;
|
||||
x.style.transition = "0.5s";
|
||||
x.style.transform = "translate3d("+value+"%,0px,0)";
|
||||
};
|
||||
}
|
||||
var serverTime = new Date(%currenttime%);
|
||||
var now = new Date();
|
||||
var timediff = serverTime.getTime()-now.getTime();
|
||||
function countUpTimer() {
|
||||
var now = new Date();
|
||||
var begin = new Date(%refreshlong%-timediff);
|
||||
var out = "";
|
||||
|
||||
var seconds = now.getTime() - begin.getTime();
|
||||
seconds = Math.floor(seconds / 1000);
|
||||
|
||||
var dd = Math.floor(seconds / 86400);
|
||||
seconds -= (dd * 86400);
|
||||
|
||||
var dh = Math.floor(seconds / 3600);
|
||||
seconds -= (dh * 3600);
|
||||
|
||||
var dm = Math.floor(seconds / 60);
|
||||
seconds -= (dm * 60);
|
||||
|
||||
seconds = Math.floor(seconds);
|
||||
|
||||
if (dd != 0) {
|
||||
out += dd.toString() + "d ";
|
||||
}
|
||||
if (dh != 0) {
|
||||
out += dh.toString() + "h ";
|
||||
}
|
||||
if (dm != 0) {
|
||||
out += dm.toString() + "m ";
|
||||
}
|
||||
out += seconds.toString() + "s ";
|
||||
|
||||
document.getElementById('divTime').innerHTML = out;
|
||||
setTimeout('countUpTimer()', 1000);
|
||||
}
|
||||
</script>
|
||||
<script src="http://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.js"></script>
|
||||
@ -894,8 +900,7 @@ function openFunc(i) {
|
||||
var ctxweek = document.getElementById("playerChartWeek");
|
||||
var ctxmonth = document.getElementById("playerChartMonth");
|
||||
var ctxactivitypie = document.getElementById("activityPie");
|
||||
var ctxgmpie = document.getElementById("gmPie");
|
||||
var ctxgenderpie = document.getElementById("genderPie");
|
||||
var ctxgmpie = document.getElementById("gmPie");
|
||||
var dataday = {
|
||||
labels: %labelsday%,
|
||||
datasets: [
|
||||
@ -1017,29 +1022,7 @@ function openFunc(i) {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
var dataGenderPie = {
|
||||
labels: %genderlabels%,
|
||||
datasets: [
|
||||
{
|
||||
data: %genderdata%,
|
||||
backgroundColor: [%gendercolors%],
|
||||
hoverBackgroundColor: [%gendercolors%]
|
||||
}
|
||||
]
|
||||
}
|
||||
var GenderPie = new Chart(ctxgenderpie, {
|
||||
type: 'doughnut',
|
||||
data: dataGenderPie,
|
||||
options: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
labels: {
|
||||
padding: 7
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
var playersChartDay = new Chart(ctxday, {
|
||||
type: 'line',
|
||||
data: dataday,
|
||||
|
@ -8,6 +8,10 @@ Settings:
|
||||
LogProgressOnConsole: false
|
||||
NotifyWhenFinished: true
|
||||
MinutesPlayedUntilConsidiredActive: 10
|
||||
RemoveOutliersFromVisualization: true
|
||||
Export:
|
||||
Enabled: false
|
||||
DestinationFolder: 'Analysis Results'
|
||||
Cache:
|
||||
Processing:
|
||||
GetLimit: 2000
|
||||
@ -66,10 +70,6 @@ Customization:
|
||||
Banned: '951800'
|
||||
Inactive: 'A9A9A9'
|
||||
JoinedOnce: '808080'
|
||||
GenderPie:
|
||||
Female: ED97E3
|
||||
Male: 7CB9D6
|
||||
Unknown: A9A9A9
|
||||
DemographicsTriggers:
|
||||
Trigger: "i'm, am, im, bin"
|
||||
Female: 'female, girl, gurl, woman, gal, mrs, she, miss, feminin, weiblich, mädchen, frau'
|
||||
|
@ -325,8 +325,8 @@ table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sor
|
||||
</div>
|
||||
</header>
|
||||
<div id="content" class="content">
|
||||
<div id="sidenav" class="sidenav">
|
||||
<p>Has Connected from ips:<br>%ips%</p>
|
||||
<div id="sidenav" class="sidenav">
|
||||
<p>Last Refresh: <br><span id="divTime">%refresh%</span> ago</p>
|
||||
<a href="javascript:void(0)" class="nav-button">
|
||||
<i class="fa fa-info-circle" aria-hidden="true"></i> Information
|
||||
</a>
|
||||
@ -370,7 +370,8 @@ table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sor
|
||||
<br/>
|
||||
<i class="fa fa-globe" aria-hidden="true"></i> Geolocation: %geoloc%<br/>
|
||||
<i class="fa fa-play" aria-hidden="true"></i> Age: %age% | <i class="fa fa-male" aria-hidden="true"></i><i class="fa fa-female" aria-hidden="true"></i> Gender: %gender%<br/>
|
||||
<i class="fa fa-tag" aria-hidden="true"></i> UUID: %uuid%</p>
|
||||
<i class="fa fa-tag" aria-hidden="true"></i> UUID: %uuid%<br/>
|
||||
<i class="fa fa-globe" aria-hidden="true"></i> Has Connected from ips: %ips%</p>
|
||||
</div>
|
||||
<div class="about box column">
|
||||
<div class="headerbox">
|
||||
@ -559,7 +560,7 @@ function closeNav() {
|
||||
}
|
||||
var navButtons = document.getElementsByClassName("nav-button");
|
||||
var tabs = document.getElementsByClassName("tab");
|
||||
var slideIndex = window.localStorage.getItem("InspectSlideIndex");
|
||||
var slideIndex = window.sessionStorage.getItem("InspectSlideIndex");
|
||||
if (slideIndex == null) {
|
||||
slideIndex = 0;
|
||||
}
|
||||
@ -574,6 +575,7 @@ for (i=0; i < navButtons.length; i++) {
|
||||
}
|
||||
x.style.opacity = "1";
|
||||
openFunc(slideIndex)();
|
||||
countUpTimer();
|
||||
|
||||
function openFunc(i) {
|
||||
return function() {
|
||||
@ -594,12 +596,49 @@ function openFunc(i) {
|
||||
slideIndex = i;
|
||||
if (slideIndex>max) {slideIndex=0};
|
||||
if (slideIndex<0) {slideIndex=max};
|
||||
window.localStorage.setItem("InspectSlideIndex", slideIndex);
|
||||
window.sessionStorage.setItem("InspectSlideIndex", slideIndex);
|
||||
var value = slideIndex*perc;
|
||||
x.style.transition = "0.5s";
|
||||
x.style.transform = "translate3d("+value+"%,0px,0)";
|
||||
};
|
||||
}
|
||||
|
||||
var serverTime = new Date(%currenttime%);
|
||||
var now = new Date();
|
||||
var timediff = serverTime.getTime()-now.getTime();
|
||||
function countUpTimer() {
|
||||
var now = new Date();
|
||||
var begin = new Date(%refreshlong%-timediff);
|
||||
var out = "";
|
||||
|
||||
var seconds = now.getTime() - begin.getTime();
|
||||
seconds = Math.floor(seconds / 1000);
|
||||
|
||||
var dd = Math.floor(seconds / 86400);
|
||||
seconds -= (dd * 86400);
|
||||
|
||||
var dh = Math.floor(seconds / 3600);
|
||||
seconds -= (dh * 3600);
|
||||
|
||||
var dm = Math.floor(seconds / 60);
|
||||
seconds -= (dm * 60);
|
||||
|
||||
seconds = Math.floor(seconds);
|
||||
|
||||
if (dd != 0) {
|
||||
out += dd.toString() + "d ";
|
||||
}
|
||||
if (dh != 0) {
|
||||
out += dh.toString() + "h ";
|
||||
}
|
||||
if (dm != 0) {
|
||||
out += dm.toString() + "m ";
|
||||
}
|
||||
out += seconds.toString() + "s ";
|
||||
|
||||
document.getElementById('divTime').innerHTML = out;
|
||||
setTimeout('countUpTimer()', 1000);
|
||||
}
|
||||
</script>
|
||||
<script src="http://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.js"></script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: Plan
|
||||
author: Rsl1122
|
||||
main: main.java.com.djrapitops.plan.Plan
|
||||
version: 3.3.0
|
||||
version: 3.4.0
|
||||
|
||||
softdepend:
|
||||
- OnTime
|
||||
|
@ -18,14 +18,23 @@ import test.java.utils.MockUtils;
|
||||
*/
|
||||
public class PermissionsTest {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public PermissionsTest() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testUserHasThisPermission() {
|
||||
assertTrue(Permissions.INSPECT_OTHER.userHasThisPermission(MockUtils.mockPlayer()));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testGetPermission() {
|
||||
assertEquals("plan.inspect.other", Permissions.INSPECT_OTHER.getPermission());
|
||||
|
@ -64,6 +64,9 @@ public class SettingsTest {
|
||||
assertEquals(8804, Settings.WEBSERVER_PORT.getNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testGetStringList() {
|
||||
List<String> exp = new ArrayList<>();
|
||||
|
@ -15,14 +15,23 @@ import static org.junit.Assert.*;
|
||||
*/
|
||||
public class AnalysisTypeTest {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public AnalysisTypeTest() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testGetModifier() {
|
||||
assertEquals("Average ", AnalysisType.INT_AVG.getModifier());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testGetPlaceholderModifier() {
|
||||
assertEquals("totalInt_", AnalysisType.INT_TOTAL.getPlaceholderModifier());
|
||||
|
@ -60,7 +60,7 @@ public class DataCacheSaveQueueTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveUserData(UUID uuid, UserData data) throws SQLException {
|
||||
public void saveUserData(UserData data) throws SQLException {
|
||||
if (calledSaveUserData) {
|
||||
calledSaveUserData2 = true;
|
||||
}
|
||||
|
@ -65,8 +65,8 @@ public class KillHandlingTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUserId(String uuid) {
|
||||
return 1;
|
||||
public void convertBukkitDataToDB() {
|
||||
|
||||
}
|
||||
};
|
||||
when(plan.getDB()).thenReturn(db);
|
||||
@ -89,7 +89,8 @@ public class KillHandlingTest {
|
||||
public void testProcessKillInfoPlayer() throws SQLException {
|
||||
UserData data = new UserData(MockUtils.mockPlayer(), new DemographicsData());
|
||||
Player dead = MockUtils.mockPlayer2();
|
||||
// db.saveUserData(dead.getUniqueId(), new UserData(dead, new DemographicsData()));
|
||||
db.init();
|
||||
db.saveUserData(new UserData(dead, new DemographicsData()));
|
||||
KillHandling.processKillInfo(data, 10L, dead, "TestWeapon");
|
||||
KillData expected = new KillData(dead.getUniqueId(), 1, "TestWeapon", 10L);
|
||||
assertTrue("Didn't add the kill", data.getPlayerKills().size() == 1);
|
||||
|
@ -59,8 +59,8 @@ public class KillInfoTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUserId(String uuid) {
|
||||
return 1;
|
||||
public void convertBukkitDataToDB() {
|
||||
|
||||
}
|
||||
};
|
||||
when(plan.getDB()).thenReturn(db);
|
||||
@ -83,7 +83,8 @@ public class KillInfoTest {
|
||||
public void testProcess() throws SQLException {
|
||||
UserData data = new UserData(MockUtils.mockPlayer(), new DemographicsData());
|
||||
Player dead = MockUtils.mockPlayer2();
|
||||
// db.saveUserData(dead.getUniqueId(), new UserData(dead, new DemographicsData()));
|
||||
db.init();
|
||||
db.saveUserData(new UserData(dead, new DemographicsData()));
|
||||
KillInfo i = new KillInfo(data.getUuid(), 10L, dead, "TestWeapon");
|
||||
assertTrue(i.process(data));
|
||||
KillData expected = new KillData(dead.getUniqueId(), 1, "TestWeapon", 10L);
|
||||
|
@ -275,7 +275,7 @@ public class DatabaseTest {
|
||||
db.saveUserData(data2);
|
||||
data.addNickname("s); DROP TABLE plan_users;--");
|
||||
db.saveUserData(data);
|
||||
assertTrue("Removed Users table.", db.getUserId(data2.getUuid().toString()) != -1);
|
||||
assertTrue("Removed Users table.", db.getUsersTable().getUserId(data2.getUuid().toString()) != -1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,6 +23,9 @@ import test.java.utils.TestInit;
|
||||
@PrepareForTest(JavaPlugin.class)
|
||||
public class PlayerActivityGraphCreatorTest {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
TestInit t = new TestInit();
|
||||
|
@ -26,6 +26,9 @@ import test.java.utils.*;
|
||||
@PrepareForTest(JavaPlugin.class)
|
||||
public class FormatUtilsTest {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
TestInit t = new TestInit();
|
||||
|
@ -6,7 +6,6 @@
|
||||
package test.java.main.java.com.djrapitops.plan.utilities;
|
||||
|
||||
import java.util.Set;
|
||||
import main.java.com.djrapitops.plan.Phrase;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
@ -59,9 +58,7 @@ public class MiscUtilsTest {
|
||||
@Test
|
||||
public void testCheckVersion() {
|
||||
String versionG = "2.10.9";
|
||||
String result = MiscUtils.checkVersion("2.0.0", versionG);
|
||||
String exp = Phrase.VERSION_NEW_AVAILABLE.parse(versionG);
|
||||
assertEquals(exp, result);
|
||||
assertTrue(MiscUtils.checkVersion("2.0.0", versionG));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,9 +66,12 @@ public class MiscUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCheckVersion2() {
|
||||
String result = MiscUtils.checkVersion("3.0.0", "2.10.9");
|
||||
String exp = Phrase.VERSION_LATEST + "";
|
||||
assertEquals(exp, result);
|
||||
assertTrue(!MiscUtils.checkVersion("3.0.0", "2.10.9"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckVersion5() {
|
||||
assertTrue(MiscUtils.checkVersion("2.10.9", "3.0.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,9 +79,7 @@ public class MiscUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCheckVersion3() {
|
||||
String result = MiscUtils.checkVersion("2.11.0", "2.10.9");
|
||||
String exp = Phrase.VERSION_LATEST + "";
|
||||
assertEquals(exp, result);
|
||||
assertTrue(!MiscUtils.checkVersion("2.11.0", "2.10.9"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,9 +87,7 @@ public class MiscUtilsTest {
|
||||
*/
|
||||
@Test
|
||||
public void testCheckVersion4() {
|
||||
String result = MiscUtils.checkVersion("2.11.0", "2.11.0");
|
||||
String exp = Phrase.VERSION_LATEST + "";
|
||||
assertEquals(exp, result);
|
||||
assertTrue(!MiscUtils.checkVersion("2.11.0", "2.11.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -181,7 +177,7 @@ public class MiscUtilsTest {
|
||||
boolean equalToExp1 = r.getName().equals(exp1.getName());
|
||||
boolean equalToExp2 = r.getName().equals(exp2.getName());
|
||||
if (!(equalToExp1 || equalToExp2)) {
|
||||
fail("Unknown result!: "+r.getName());
|
||||
fail("Unknown result!: " + r.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,7 +193,7 @@ public class MiscUtilsTest {
|
||||
assertEquals(1, result.size());
|
||||
for (OfflinePlayer r : result) {
|
||||
if (!r.getName().equals(exp2.getName())) {
|
||||
fail("Unknown result!: "+r.getName());
|
||||
fail("Unknown result!: " + r.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,13 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package test.java.main.java.com.djrapitops.plan.utilities;
|
||||
package test.java.main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import main.java.com.djrapitops.plan.data.SessionData;
|
||||
import main.java.com.djrapitops.plan.utilities.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.AnalysisUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.MiscUtils;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import static org.junit.Assert.*;
|
@ -3,13 +3,13 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package test.java.main.java.com.djrapitops.plan.utilities;
|
||||
package test.java.main.java.com.djrapitops.plan.utilities.analysis;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import main.java.com.djrapitops.plan.utilities.MathUtils;
|
||||
import main.java.com.djrapitops.plan.utilities.analysis.MathUtils;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -19,9 +19,15 @@ import org.junit.Test;
|
||||
*/
|
||||
public class MathUtilsTest {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public MathUtilsTest() {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testAverageInt() {
|
||||
List<Integer> l = new ArrayList<>();
|
||||
@ -34,6 +40,9 @@ public class MathUtilsTest {
|
||||
assertTrue(exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testAverageIntEmpty() {
|
||||
List<Integer> l = new ArrayList<>();
|
||||
@ -42,6 +51,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testAverageLong_Collection() {
|
||||
List<Long> l = new ArrayList<>();
|
||||
@ -54,6 +66,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testAverageDouble() {
|
||||
List<Double> l = new ArrayList<>();
|
||||
@ -67,6 +82,9 @@ public class MathUtilsTest {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testAverage() {
|
||||
double exp = 10;
|
||||
@ -74,6 +92,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testCountTrueBoolean() {
|
||||
List<Boolean> l = new ArrayList<>();
|
||||
@ -88,6 +109,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testSumInt() {
|
||||
List<Serializable> l = new ArrayList<>();
|
||||
@ -100,6 +124,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testSumLong() {
|
||||
List<Serializable> l = new ArrayList<>();
|
||||
@ -112,6 +139,9 @@ public class MathUtilsTest {
|
||||
assertTrue(result + "/" + exp, exp == result);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@Test
|
||||
public void testSumDouble() {
|
||||
List<Serializable> l = new ArrayList<>();
|
@ -24,6 +24,9 @@ Config.Point | Version introduced | Type | Default | Description
|
||||
LogProgressOnConsole | 2.4.0 | boolean | false | More detailed analysis progress to console.
|
||||
NotifyWhenFinished | 3.0.0 | boolean | true | Enables ["Analysis Complete"-message](https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/java/com/djrapitops/plan/Phrase.java#L73) will be shown on the console after analysis is complete.
|
||||
MinutesPlayedUntilConsidiredActive | 2.0.0 | Integer | 10 | This setting affects how the Analysis treats player's activity. Whether or not a player is active is determined with 3 values: Last Login, Playtime and Login Times. If the player has logged in in the last 2 weeks, has playtime higher than in the config, and has logged in 3 times, the player is considered active. Otherwise the player is counted as inactive.
|
||||
RemoveOutliersFromVisualization | 3.4.0 | boolean | true | This setting attempts to remove big spikes from data visualization.
|
||||
Export.Enabled | 3.4.0 | boolean | false | Enables export of html pages after analysis
|
||||
Export.DestinationFolder | 3.4.0 | String | 'Analysis Results' | Path to the export folder. Will be created if doesn't exist. If contains ':' will be regarded as full filepath.
|
||||
|
||||
## Cache settings
|
||||
|
||||
|
@ -15,6 +15,8 @@ The plugin uses placeholders to place the values into the html. Here I will go t
|
||||
|
||||
Placeholder | Description | Example
|
||||
---------- | ------------------------------------- | -----
|
||||
%currenttime% | Server epoch ms, used for clock. | 1496486156
|
||||
%refreshlong% | Epoch ms of last refresh (server time) | 1496486156
|
||||
%uuid% | Players UUID or 'Hidden' if config setting for UUID visibility is disabled. | 88493cd1-567a-49aa-acaa-84197b5de595
|
||||
%lastseen% | A formatted version of the last Epoch second the user was seen. | Feb 02 18:03:12
|
||||
%logintimes% | How many times the user has logged in | 34
|
||||
@ -64,6 +66,8 @@ Placeholder | Description | Example
|
||||
|
||||
Placeholder | Description | Example
|
||||
---------- | ------------------------------------- | -----
|
||||
%currenttime% | Server epoch ms, used for clock. | 1496486156
|
||||
%refreshlong% | Epoch ms of last refresh (server time) | 1496486156
|
||||
%gm0% | Total percentage all players have spent in SURVIVAL | 66%
|
||||
%gm1% | Total percentage all players have spent in CREATIVE | 19%
|
||||
%gm2% | Total percentage all players have spent in ADVENTURE | 10%
|
||||
@ -92,6 +96,13 @@ Placeholder | Description | Example
|
||||
%sessionaverage% | Formatted time amount of the average session length | 4m 30s
|
||||
%version% | Version of Plan | 3.2.0
|
||||
%planlite% | Replaced with an empty string. Old feature. |
|
||||
%uniquejoinsday% | Replaced with number of unique players | 5
|
||||
%uniquejoinsweek% | Replaced with number of unique players | 47
|
||||
%uniquejoinsmonth% | Replaced with number of unique players | 234
|
||||
%avguniquejoins% | Replaced with number of average joins / day | 56
|
||||
%avguniquejoinsday% | Replaced with number of average joins / day in last 24h | 56
|
||||
%avguniquejoinsweek% | Replaced with number of average joins / day in last 7d | 60
|
||||
%avguniquejoinsmonth% | Replaced with number of average joins / day in last 30d | 59
|
||||
%sortabletable% | Multi column table containing all players | [Created with this code](https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/java/com/djrapitops/plan/ui/tables/SortablePlayersTableCreator.java)
|
||||
%dataday% | Data for 24h online activity | [0, 0, 0, 0, 1]
|
||||
%dataweek% | Data for 7d online activity | [0, 0, 0, 0, 1]
|
||||
@ -106,12 +117,6 @@ Placeholder | Description | Example
|
||||
%geomapz% | Number array for Chloropleth map | [...]
|
||||
%gmdata% | Data for the sectors on the gm usage pie | [43242, 432423421, 35345, 5432534]
|
||||
%gmlabels% | Labels for the sectors of gm pie. | ["Survival", "Creative", "Adventure", "Spectator"]
|
||||
%genderdata% | Data for the sectors on the Gender pie | [3, 4, 5]
|
||||
%gendermale% | Amount of male players | 3
|
||||
%genderfemale% | Amount of female players | 4
|
||||
%gendercolors% | Colors for the gender pie in the config | [#ffffff, #000000, #ffffff]
|
||||
%genderfcolor% | Color of the FEMALE sector in gender pie | #ffffff
|
||||
%gendermcolor% | Color of the MALE sector in gender pie | #000000
|
||||
%activecol% | Color of the ACTIVE sector in config | ffffff
|
||||
%inactivecol% | Color of the INACTIVE sector in config | 000000
|
||||
%joinleavecol% | Color of the UNKNOWN sector in config | ffffff
|
||||
|
Loading…
x
Reference in New Issue
Block a user