Version 2.1.0 update - More Customization

This update brings more customization options, couple bugfixes and extra
slice to the player composition pie that tells how many players have
only joined once, but never returned (previously counted as inactive).

In more detail:
Added "Unknown" slice to the Player Composition Pie (Players who have
joined only once)
ServerData and UserData now saved with seperate timers to avoid loss of
player activity data with less dense cache saves.
Added possibility to turn off the WebServer.
- API will return HTML without webserver if the server is off.
- If AlternativeIP is in use, all commands will use that as link,
otherwise:
- Analysis & Search commands are disabled if the webserver is off.
- If PlanLite is installed and is used as alternative UI (config),
Inspect command is passed to PlanLite.
Added possibility to use PlanLite as an alternative UI.
- Use /plan lite inspect <player> to view the data in the chatbox.
- Graphs, piecharts & list available only on the web UI.
- Analysis not yet available in PlanLite UI.
Added possibility to change all colors of the Web UI and command
messages.
- Changes in the color settings require plugin restart, fix for this in
the future.
- Web UI Colors use the HTML Color Codes without the # (hashtag)
Added possibility to translate & add/remove Demographics triggers
Bugfixes:
- Fixed ConcurrentModificationException when data is being cleared after
a save.
- Attempted fix of PlanLite balance analysis by rewriting a formatting
utility method.
This commit is contained in:
Rsl1122 2017-01-19 12:01:18 +02:00
parent cda0148380
commit 0dfb2570e8
25 changed files with 408 additions and 166 deletions

View File

@ -1,6 +1,7 @@
package com.djrapitops.plan;
import org.bukkit.ChatColor;
import static org.bukkit.plugin.java.JavaPlugin.getPlugin;
/**
*
@ -13,13 +14,26 @@ public enum Phrase {
USERNAME_NOT_VALID(ChatColor.RED + "This Player doesn't exist."),
USERNAME_NOT_SEEN(ChatColor.RED + "This Player has not played on this server."),
USERNAME_NOT_KNOWN(ChatColor.RED + "Player not found from the database."),
COLOR_MAIN(ChatColor.DARK_GREEN),
COLOR_SEC(ChatColor.GRAY),
COLOR_TER(ChatColor.WHITE),
COLOR_MAIN(ChatColor.getByChar(getPlugin(Plan.class).getConfig().getString("Customization.Colors.Commands.Main").charAt(1))),
COLOR_SEC(ChatColor.getByChar(getPlugin(Plan.class).getConfig().getString("Customization.Colors.Commands.Secondary").charAt(1))),
COLOR_TER(ChatColor.getByChar(getPlugin(Plan.class).getConfig().getString("Customization.Colors.Commands.Highlight").charAt(1))),
HCOLOR_ACT_ONL(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityGraph.OnlinePlayers")),
HCOLOR_ACT_NEW(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityGraph.NewPlayers")),
HCOLOR_GMP_0(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.GamemodePie.Survival")),
HCOLOR_GMP_1(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.GamemodePie.Creative")),
HCOLOR_GMP_2(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.GamemodePie.Adventure")),
HCOLOR_GMP_3(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.GamemodePie.Spectator")),
HCOLOR_ACTP_ACT(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityPie.Active")),
HCOLOR_ACTP_BAN(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityPie.Banned")),
HCOLOR_ACTP_INA(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityPie.Inactive")),
HCOLOR_ACTP_JON(getPlugin(Plan.class).getConfig().getString("Customization.Colors.HTML.ActivityPie.JoinedOnce")),
ARROWS_RIGHT("»"),
BALL(""),
ERROR_PLANLITE("PlanLite not found, if you're have plugins using PlanAPI v1.6.0 download PlanLite."),
ERROR_NO_USERNAME("INSPECT-GETNAME\nNo username given, returned empty username.\n"),
ERROR_NO_DATA_VIEW(ChatColor.YELLOW + "Webserver disabled but Alternative IP/PlanLite not used, no way to view data!"),
ERROR_WEBSERVER_OFF_ANALYSIS(ChatColor.YELLOW + "[Plan] This command can be only used if the webserver is running on this server."),
ERROR_WEBSERVER_OFF_INSPECT(ChatColor.YELLOW + "[Plan] This command can be only used if webserver/planlite is enabled on this server."),
COMMAND_SENDER_NOT_PLAYER(ChatColor.RED + "[Plan] This command can be only used as a player."),
COMMAND_REQUIRES_ARGUMENTS(ChatColor.RED + "[Plan] Command requires arguments."),
COMMAND_REQUIRES_ARGUMENTS_ONE(ChatColor.RED + "[Plan] Command requires one argument."),

View File

@ -20,6 +20,31 @@ import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.scheduler.BukkitRunnable;
/* TODO 2.1.0
Placeholder API
Html getter without webserver
Webserver off setting
Immutable InspectCache ?
Join-leavers to activity pie.
Recent players
Customizability
Colors, chat, analysis
Demographics triggers
Optimize db with batch processing (commanduse, ips, nicks)
Manage command
Database cleaning
Server & user data saved seperately with different times
Alternative ip & webserver off check warning, check for PlanLite too.
Fix PlanLite balance.
PlanLite Top 20 richest
PlanLite Top 20 most votes
Top 20 most active
Clear setting multiper (InspectCache)
Clear check for existing clear task. (InspectCache)
ErrorManager
(Alternative ui) ? Push data to PlanLite (setting)
DataBase init message
*/
public class Plan extends JavaPlugin {
private API api;
@ -61,7 +86,9 @@ public class Plan extends JavaPlugin {
saveConfig();
log("Database init..");
initDatabase();
log("Database initiated.");
hookPlanLite();
this.handler = new DataCacheHandler(this);
@ -76,27 +103,32 @@ public class Plan extends JavaPlugin {
this.api = new API(this);
handler.handleReload();
uiServer = new WebSocketServer(this);
uiServer.initServer();
log("Player Analytics Enabled.");
if (getConfig().getBoolean("Settings.Cache.AnalysisCache.RefreshAnalysisCacheOnEnable")) {
log("Analysis | Boot analysis in 30 seconds..");
(new BukkitRunnable() {
@Override
public void run() {
log("Analysis | Starting Boot Analysis..");
analysisCache.updateCache();
this.cancel();
}
}).runTaskLater(this, 30 * 20);
if (getConfig().getBoolean("Settings.WebServer.Enabled")) {
uiServer = new WebSocketServer(this);
uiServer.initServer();
if (getConfig().getBoolean("Settings.Cache.AnalysisCache.RefreshAnalysisCacheOnEnable")) {
log("Analysis | Boot analysis in 30 seconds..");
(new BukkitRunnable() {
@Override
public void run() {
log("Analysis | Starting Boot Analysis..");
analysisCache.updateCache();
this.cancel();
}
}).runTaskLater(this, 30 * 20);
}
} else if (!(getConfig().getBoolean("Settings.WebServer.ShowAlternativeServerIP")
|| (getConfig().getBoolean("Settings.PlanLite.UseAsAlternativeUI")
&& planLiteHook.isEnabled()))) {
Bukkit.getServer().getConsoleSender().sendMessage("[Plan] "
+ Phrase.ERROR_NO_DATA_VIEW);
}
log("Player Analytics Enabled.");
}
public void hookPlanLite() {
try {
planLiteHook = new PlanLiteHook(this);
planLiteHook = new PlanLiteHook(this);
} catch (NoClassDefFoundError | Exception e) {
}
@ -106,13 +138,15 @@ public class Plan extends JavaPlugin {
public void onDisable() {
uiServer.stop();
Bukkit.getScheduler().cancelTasks(this);
log("Saving cached data..");
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.execute(() -> {
handler.saveCacheOnDisable();
});
scheduler.shutdown();
if (handler != null) {
log("Saving cached data..");
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.execute(() -> {
handler.saveCacheOnDisable();
});
scheduler.shutdown();
}
log("Player Analytics Disabled.");
}

View File

@ -1,20 +1,28 @@
package com.djrapitops.plan;
import com.djrapitops.plan.data.UserData;
import com.djrapitops.plan.data.cache.InspectCacheHandler;
import com.djrapitops.plan.utilities.AnalysisUtils;
import com.djrapitops.planlite.PlanLite;
import com.djrapitops.planlite.UUIDFetcher;
import com.djrapitops.planlite.api.API;
import com.djrapitops.planlite.api.DataPoint;
import com.djrapitops.planlite.api.DataType;
import com.djrapitops.planlite.api.Hook;
import java.util.HashMap;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import static org.bukkit.plugin.java.JavaPlugin.getPlugin;
/**
*
* @author Rsl1122
*/
public class PlanLiteHook {
public class PlanLiteHook implements Hook{
private PlanLite planLite;
private Plan plugin;
@ -31,7 +39,8 @@ public class PlanLiteHook {
*/
public PlanLiteHook(Plan plugin) {
this.plugin = plugin;
if (plugin.getConfig().getBoolean("Settings.PlanLite.Enabled")) {
FileConfiguration config = plugin.getConfig();
if (config.getBoolean("Settings.PlanLite.Enabled")) {
if (Bukkit.getPluginManager().isPluginEnabled("PlanLite")) {
try {
this.planLite = getPlugin(PlanLite.class);
@ -40,6 +49,9 @@ public class PlanLiteHook {
}
enabled = true;
planLiteApi = planLite.getAPI();
if (config.getBoolean("Settings.PlanLite.UseAsAlternativeUI")) {
planLite.addExtraHook("Plan", this);
}
} catch (Exception e) {
}
} else {
@ -85,4 +97,31 @@ public class PlanLiteHook {
public boolean hasVault() {
return getEnabledHooksNames().contains("Vault");
}
@Override
public HashMap<String, DataPoint> getData(String playername) throws Exception {
HashMap<String, DataPoint> data = new HashMap<>();
try {
UUID uuid = UUIDFetcher.getUUIDOf(playername);
if (uuid != null) {
InspectCacheHandler inspectCache = plugin.getInspectCache();
inspectCache.cache(uuid);
UserData uData = inspectCache.getFromCache(uuid);
HashMap<String, String> userData = AnalysisUtils.getInspectReplaceRules(uData);
for (String key : userData.keySet()) {
if (key.equals("%planlite%") || key.equals("%gmpiechart%")) {
continue;
}
data.put("PLA-"+key.toUpperCase().substring(1, key.length()-1), new DataPoint(userData.get(key), DataType.OTHER));
}
}
} catch (Exception e) {
}
return data;
}
@Override
public HashMap<String, DataPoint> getAllData(String playername) throws Exception {
return getData(playername);
}
}

View File

@ -4,10 +4,12 @@ import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanLiteHook;
import com.djrapitops.plan.data.AnalysisData;
import com.djrapitops.plan.data.UserData;
import com.djrapitops.plan.ui.DataRequestHandler;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.planlite.UUIDFetcher;
import java.util.Date;
import java.util.UUID;
import main.java.com.djrapitops.plan.ui.webserver.WebSocketServer;
/**
*
@ -98,6 +100,8 @@ public class API {
* Returns the ip:port/player/playername html as a string so it can be
* integrated into other webserver plugins.
*
* Should use cacheUserDataToInspectCache(UUID uuid) before using this method.
*
* If UserData of the specified player is not in the Cache returns <h1>404
* Data was not found in cache</h1>
*
@ -105,7 +109,12 @@ public class API {
* @return html as a string or a single error line html.
*/
public String getPlayerHtmlAsString(UUID uuid) {
return plugin.getUiServer().getDataReqHandler().getDataHtml(uuid);
WebSocketServer server = plugin.getUiServer();
if (server != null) {
return server.getDataReqHandler().getDataHtml(uuid);
}
DataRequestHandler reqH = new DataRequestHandler(plugin);
return reqH.getDataHtml(uuid);
}
/**
@ -120,13 +129,20 @@ public class API {
* Returns the ip:port/server html as a string so it can be integrated into
* other webserver plugins.
*
* Should use updateAnalysisCache() before using this method.
*
* If AnalysisData is not in the AnalysisCache: returns <h1>404 Data was not
* found in cache</h1>
*
* @return html as a string or a single error line html.
*/
public String getAnalysisHtmlAsString() {
return plugin.getUiServer().getDataReqHandler().getAnalysisHtml();
WebSocketServer server = plugin.getUiServer();
if (server != null) {
return server.getDataReqHandler().getAnalysisHtml();
}
DataRequestHandler reqH = new DataRequestHandler(plugin);
return reqH.getAnalysisHtml();
}
/**
@ -141,6 +157,7 @@ public class API {
/**
* Returns AnalysisData from the AnalysisCache
*
* @return AnalysisData in the AnalysisCache or null if not found
*/
public AnalysisData getAnalysisDataFromCache() {

View File

@ -9,6 +9,7 @@ import java.util.Date;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
@ -48,46 +49,60 @@ public class AnalyzeCommand extends SubCommand {
*/
@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
FileConfiguration config = plugin.getConfig();
if (!config.getBoolean("Settings.WebServer.Enabled")) {
if (!config.getBoolean("Settings.WebServer.ShowAlternativeServerIP")) {
sender.sendMessage(Phrase.ERROR_WEBSERVER_OFF_ANALYSIS.toString());
return true;
} else {
sendAnalysisMessage(sender, config);
return true;
}
}
if (!analysisCache.isCached()) {
analysisCache.updateCache();
} else if (new Date().getTime() - analysisCache.getData().getRefreshDate() > 60000) {
analysisCache.updateCache();
}
ChatColor oColor = Phrase.COLOR_MAIN.color();
ChatColor tColor = Phrase.COLOR_SEC.color();
ChatColor hColor = Phrase.COLOR_TER.color();
FileConfiguration config = plugin.getConfig();
final boolean useAlternativeIP = config.getBoolean("Settings.WebServer.ShowAlternativeServerIP");
final int port = config.getInt("Settings.WebServer.Port");
final String alternativeIP = config.getString("Settings.WebServer.AlternativeIP").replaceAll("%port%", "" + port);
(new BukkitRunnable() {
@Override
public void run() {
if (analysisCache.isCached()) {
// Header
sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString() + oColor
+ " Player Analytics - Analysis results");
// Link
String url = "http://" + (useAlternativeIP ? alternativeIP : plugin.getServer().getIp() + ":" + port)
+ "/server";
String message = tColor + " " + Phrase.BALL.toString() + oColor + " Link: " + hColor;
boolean console = !(sender instanceof Player);
if (console) {
sender.sendMessage(message + url);
} else {
sender.sendMessage(message);
Player player = (Player) sender;
Bukkit.getServer().dispatchCommand(
Bukkit.getConsoleSender(),
"tellraw " + player.getName() + " [\"\",{\"text\":\"Click Me\",\"underlined\":true,"
+ "\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + url + "\"}}]");
}
// Footer
sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString());
sendAnalysisMessage(sender, config);
this.cancel();
}
}
}).runTaskTimer(plugin, 1 * 20, 5 * 20);
return true;
}
public void sendAnalysisMessage(CommandSender sender, FileConfiguration config) throws CommandException {
ChatColor oColor = Phrase.COLOR_MAIN.color();
ChatColor tColor = Phrase.COLOR_SEC.color();
ChatColor hColor = Phrase.COLOR_TER.color();
final boolean useAlternativeIP = config.getBoolean("Settings.WebServer.ShowAlternativeServerIP");
final int port = config.getInt("Settings.WebServer.Port");
final String alternativeIP = config.getString("Settings.WebServer.AlternativeIP").replaceAll("%port%", "" + port);
// Header
sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString() + oColor
+ " Player Analytics - Analysis results");
// Link
String url = "http://" + (useAlternativeIP ? alternativeIP : plugin.getServer().getIp() + ":" + port)
+ "/server";
String message = tColor + " " + Phrase.BALL.toString() + oColor + " Link: " + hColor;
boolean console = !(sender instanceof Player);
if (console) {
sender.sendMessage(message + url);
} else {
sender.sendMessage(message);
Player player = (Player) sender;
Bukkit.getServer().dispatchCommand(
Bukkit.getConsoleSender(),
"tellraw " + player.getName() + " [\"\",{\"text\":\"Click Me\",\"underlined\":true,"
+ "\"clickEvent\":{\"action\":\"open_url\",\"value\":\"" + url + "\"}}]");
}
// Footer
sender.sendMessage(hColor + Phrase.ARROWS_RIGHT.toString());
}
}

View File

@ -26,9 +26,9 @@ public class InfoCommand extends SubCommand {
ChatColor hColor = Phrase.COLOR_TER.color();
String[] messages = {
hColor + Phrase.ARROWS_RIGHT.toString() + oColor + "Player Analytics - Info",
tColor + " " + Phrase.BALL.toString() + oColor + "Version: " + tColor + plugin.getDescription().getVersion(),
tColor + " " + Phrase.BALL.toString() + tColor + MiscUtils.checkVersion(),
hColor + Phrase.ARROWS_RIGHT.toString() + oColor + " Player Analytics - Info",
tColor + " " + Phrase.BALL.toString() + oColor + " Version: " + tColor + plugin.getDescription().getVersion(),
tColor + " " + Phrase.BALL.toString() + tColor +" "+ MiscUtils.checkVersion(),
hColor + Phrase.ARROWS_RIGHT.toString()
};
sender.sendMessage(messages);

View File

@ -2,13 +2,13 @@ package com.djrapitops.plan.command.commands;
import com.djrapitops.plan.Phrase;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.PlanLiteHook;
import com.djrapitops.plan.utilities.UUIDFetcher;
import com.djrapitops.plan.command.CommandType;
import com.djrapitops.plan.command.SubCommand;
import java.util.Date;
import com.djrapitops.plan.data.cache.InspectCacheHandler;
import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plan.utilities.MiscUtils;
import java.util.UUID;
import org.bukkit.Bukkit;
@ -58,6 +58,19 @@ public class InspectCommand extends SubCommand {
*/
@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
FileConfiguration config = plugin.getConfig();
if (!config.getBoolean("Settings.WebServer.Enabled")) {
if (!config.getBoolean("Settings.WebServer.ShowAlternativeServerIP")) {
PlanLiteHook planLiteHook = plugin.getPlanLiteHook();
if (config.getBoolean("Settings.PlanLite.UseAsAlternativeUI") && planLiteHook.isEnabled()) {
sender.sendMessage(ChatColor.YELLOW + "[Plan] Passing to PlanLite..");
planLiteHook.passCommand(sender, cmd, commandLabel, args);
} else {
sender.sendMessage(Phrase.ERROR_WEBSERVER_OFF_INSPECT.toString());
}
return true;
}
}
String playerName = MiscUtils.getPlayerDisplayname(args, sender);
UUID uuid;
@ -80,12 +93,10 @@ public class InspectCommand extends SubCommand {
return true;
}
Date refreshDate = new Date();
inspectCache.cache(uuid);
ChatColor oColor = Phrase.COLOR_MAIN.color();
ChatColor tColor = Phrase.COLOR_SEC.color();
ChatColor hColor = Phrase.COLOR_TER.color();
FileConfiguration config = plugin.getConfig();
final boolean useAlternativeIP = config.getBoolean("Settings.WebServer.ShowAlternativeServerIP");
final int port = config.getInt("Settings.WebServer.Port");

View File

@ -20,11 +20,10 @@ public class LiteCommand extends SubCommand {
}
@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
if (hook.isEnabled()) {
return hook.passCommand(sender, cmd, commandLabel, args);
}
return false;
}
}

View File

@ -21,7 +21,7 @@ public class ReloadCommand extends SubCommand {
@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
plugin.reloadConfig();
plugin.getHandler().saveCachedData();
plugin.getHandler().saveCachedUserData();
plugin.hookPlanLite();
sender.sendMessage(ChatColor.GREEN + "[Plan] Reload complete.");

View File

@ -52,8 +52,13 @@ public class SearchCommand extends SubCommand {
*/
@Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
if (!plugin.getConfig().getBoolean("Settings.WebServer.Enabled")) {
sender.sendMessage(Phrase.ERROR_WEBSERVER_OFF_ANALYSIS.toString());
return true;
}
if (args.length != 1) {
sender.sendMessage(Phrase.COMMAND_REQUIRES_ARGUMENTS_ONE.toString());
return true;
}
ChatColor oColor = Phrase.COLOR_MAIN.color();

View File

@ -28,6 +28,7 @@ public class AnalysisData {
private int banned;
private int active;
private int inactive;
private int joinleaver;
private int total;
private int totalPlayers;
@ -42,6 +43,14 @@ public class AnalysisData {
// Getters and setters v---------------------------------v
public int getJoinleaver() {
return joinleaver;
}
public void setJoinleaver(int joinleaver) {
this.joinleaver = joinleaver;
}
public boolean isPlanLiteEnabled() {
return planLiteEnabled;
}

View File

@ -6,12 +6,13 @@ import com.djrapitops.plan.data.*;
import com.djrapitops.plan.data.handlers.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Map;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import static org.bukkit.Bukkit.getPlayer;
import static org.bukkit.Bukkit.getPlayer;
/**
*
@ -66,6 +67,10 @@ public class DataCacheHandler {
if (minutes <= 0) {
minutes = 5;
}
int sMinutes = plugin.getConfig().getInt("Settings.Cache.DataCache.SaveServerDataEveryXMinutes");
if (sMinutes <= 0) {
sMinutes = 5;
}
final int clearAfterXsaves;
int configValue = plugin.getConfig().getInt("Settings.Cache.DataCache.ClearCacheEveryXSaves");
if (configValue <= 1) {
@ -78,13 +83,21 @@ public class DataCacheHandler {
public void run() {
DataCacheHandler handler = plugin.getHandler();
handler.saveHandlerDataToCache();
handler.saveCachedData();
handler.saveCachedUserData();
if (timesSaved % clearAfterXsaves == 0) {
handler.clearCache();
}
handler.clearNulls();
timesSaved++;
}
}).runTaskTimerAsynchronously(plugin, 60 * 20 * minutes, 60 * 20 * minutes);
(new BukkitRunnable() {
@Override
public void run() {
serverData.updatePlayerCount();
saveServerData();
}
}).runTaskTimerAsynchronously(plugin, 60 * 20 * sMinutes, 60 * 20 * sMinutes);
}
/**
@ -134,12 +147,10 @@ public class DataCacheHandler {
/**
* Saves all data in the cache to Database with AsyncTasks
*/
public void saveCachedData() {
public void saveCachedUserData() {
dataCache.keySet().stream().forEach((uuid) -> {
saveCachedData(uuid);
});
serverData.updatePlayerCount();
saveServerData();
timesSaved++;
}
@ -219,10 +230,9 @@ public class DataCacheHandler {
* Clears all UserData from the HashMap
*/
public void clearCache() {
Set<UUID> uuidSet = dataCache.keySet();
Iterator<UUID> uuidIterator = uuidSet.iterator();
while (uuidIterator.hasNext()) {
clearFromCache(uuidIterator.next());
Iterator<Map.Entry<UUID, UserData>> clearIterator = dataCache.entrySet().iterator();
while (clearIterator.hasNext()) {
clearFromCache(clearIterator.next());
}
}
@ -251,6 +261,36 @@ public class DataCacheHandler {
}
}
/**
* Setting entry value to null, clearing it from memory.
*
* This method is used to avoid ConcurrentModificationException when
* clearing the cache.
*
* @param entry An entry from the cache HashMap that is being iterated over.
*/
public void clearFromCache(Map.Entry<UUID, UserData> entry) {
if (entry != null) {
if (entry.getValue() != null) {
if (entry.getValue().isAccessed()) {
(new BukkitRunnable() {
@Override
public void run() {
if (entry.getValue().isAccessed()) {
entry.setValue(null);
plugin.log("Cleared " + entry.getKey().toString() + " from Cache. (Delay task)");
this.cancel();
}
}
}).runTaskTimer(plugin, 30 * 20, 30 * 20);
} else {
entry.setValue(null);
plugin.log("Cleared " + entry.getKey().toString() + " from Cache.");
}
}
}
}
/**
* Creates a new UserData instance and saves it to the Database
*
@ -371,4 +411,13 @@ public class DataCacheHandler {
public int getMaxPlayers() {
return maxPlayers;
}
private void clearNulls() {
Iterator<UUID> clearIterator = dataCache.keySet().iterator();
while (clearIterator.hasNext()) {
if (dataCache.get(clearIterator.next()) == null) {
clearIterator.remove();
}
}
}
}

View File

@ -21,6 +21,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
public class DemographicsHandler {
private final DataCacheHandler handler;
private final Plan plugin;
/**
* Class Constructor
@ -30,6 +31,7 @@ public class DemographicsHandler {
*/
public DemographicsHandler(Plan plugin, DataCacheHandler h) {
this.handler = h;
this.plugin = plugin;
}
/**
@ -42,10 +44,10 @@ public class DemographicsHandler {
* @param data UserData corresponding to player of this event.
*/
public void handleChatEvent(AsyncPlayerChatEvent event, UserData data) {
List<String> triggers = Arrays.asList("i\'m", "am", "im");
List<String> female = Arrays.asList("female", "girl", "gurl", "woman", "gal", "mrs", "she", "miss");
List<String> male = Arrays.asList("male", "boy", "man", "boe", "sir", "mr", "guy", "he");
List<String> ignore = Arrays.asList("sure", "think", "with", "are");
List<String> triggers = Arrays.asList(plugin.getConfig().getString("Customization.DemographicsTriggers.Trigger").split(", "));
List<String> female = Arrays.asList(plugin.getConfig().getString("Customization.DemographicsTriggers.Female").split(", "));
List<String> male = Arrays.asList(plugin.getConfig().getString("Customization.DemographicsTriggers.Male").split(", "));
List<String> ignore = Arrays.asList(plugin.getConfig().getString("Customization.DemographicsTriggers.IgnoreWhen").split(", "));
String message = event.getMessage();
String[] messageA = message.toLowerCase().split("\\s+");

View File

@ -9,6 +9,7 @@ import static org.bukkit.Bukkit.getOfflinePlayer;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.event.player.PlayerQuitEvent;
import static org.bukkit.Bukkit.getOfflinePlayer;
/**
*

View File

@ -35,7 +35,7 @@ public class PlanLiteHandler {
}
Player p = event.getPlayer();
String playerName = p.getName();
handleEvents(playerName, data);
}
@ -44,42 +44,54 @@ public class PlanLiteHandler {
return;
}
Set<String> enabledHooks = hook.getEnabledHooksNames();
HashMap<String, DataPoint> liteData = hook.getAllData(playerName, true);
PlanLitePlayerData plData = new PlanLitePlayerData();
plData.setTowny(enabledHooks.contains("Towny"));
plData.setFactions(enabledHooks.contains("Factions"));
plData.setSuperbVote(enabledHooks.contains("SuperbVote"));
plData.setVault(enabledHooks.contains("Vault"));
if (plData.hasTowny()) {
DataPoint town = liteData.get("TOW-TOWN");
plData.setTown((town != null) ? town.data() : "Not in a town");
DataPoint friends = liteData.get("TOW-FRIENDS");
plData.setFriends((town != null) ? friends.data() : "");
DataPoint perms = liteData.get("TOW-PLOT PERMS");
plData.setPlotPerms((perms != null) ? perms.data() : "");
DataPoint options = liteData.get("TOW-PLOT OPTIONS");
plData.setPlotOptions((options != null) ? options.data() : "");
}
if (plData.hasFactions()) {
DataPoint faction = liteData.get("FAC-FACTION");
plData.setFaction((faction != null) ? faction.data() : "Not in a faction");
}
if (plData.hasSuperbVote()) {
try {
plData.setVotes(Integer.parseInt(liteData.get("SVO-VOTES").data()));
} catch (Exception e) {
plData.setVotes(0);
// Avoiding StackOverFlow
if (plugin.getConfig().getBoolean("Settings.PlanLite.UseAsAlternativeUI")
&& plugin.getPlanLiteHook().isEnabled()) {
data.setPlanLiteFound(false);
plData.setTowny(false);
plData.setFactions(false);
plData.setSuperbVote(false);
plData.setVault(false);
data.setPlanLiteData(plData);
} else {
HashMap<String, DataPoint> liteData = hook.getAllData(playerName, true);
plData.setTowny(enabledHooks.contains("Towny"));
plData.setFactions(enabledHooks.contains("Factions"));
plData.setSuperbVote(enabledHooks.contains("SuperbVote"));
plData.setVault(enabledHooks.contains("Vault"));
if (plData.hasTowny()) {
DataPoint town = liteData.get("TOW-TOWN");
plData.setTown((town != null) ? town.data() : "Not in a town");
DataPoint friends = liteData.get("TOW-FRIENDS");
plData.setFriends((town != null) ? friends.data() : "");
DataPoint perms = liteData.get("TOW-PLOT PERMS");
plData.setPlotPerms((perms != null) ? perms.data() : "");
DataPoint options = liteData.get("TOW-PLOT OPTIONS");
plData.setPlotOptions((options != null) ? options.data() : "");
}
}
if (plData.hasVault()) {
try {
plData.setMoney(Double.parseDouble(FormatUtils.removeLetters(liteData.get("ECO-BALANCE").data())));
} catch (Exception e) {
plData.setMoney(0);
if (plData.hasFactions()) {
DataPoint faction = liteData.get("FAC-FACTION");
plData.setFaction((faction != null) ? faction.data() : "Not in a faction");
}
if (plData.hasSuperbVote()) {
try {
plData.setVotes(Integer.parseInt(liteData.get("SVO-VOTES").data()));
} catch (Exception e) {
plData.setVotes(0);
}
}
if (plData.hasVault()) {
try {
plData.setMoney(Double.parseDouble(FormatUtils.removeLetters(liteData.get("ECO-BALANCE").data())));
} catch (Exception e) {
plData.setMoney(0);
}
}
data.setPlanLiteFound(true);
data.setPlanLiteData(plData);
}
data.setPlanLiteFound(true);
data.setPlanLiteData(plData);
}
}

View File

@ -1,5 +1,6 @@
package main.java.com.djrapitops.plan.ui.graphs;
import com.djrapitops.plan.Phrase;
import com.googlecode.charts4j.Color;
import com.googlecode.charts4j.GCharts;
import com.googlecode.charts4j.PieChart;
@ -17,27 +18,30 @@ public class ActivityPieChartCreator {
* @param totalBanned Number of Banned Players
* @param active Number of Active Players
* @param inactive Number of Inactive Players
* @param joinleaver Number of players who have joined only once
* @return Url to Image link.
*/
public static String createChart(int totalBanned, int active, int inactive) {
public static String createChart(int totalBanned, int active, int inactive, int joinleaver) {
int total = totalBanned + active + inactive;
int banPerc = (int) ((totalBanned * 1.0 / total) * 100);
int inacPerc = (int) ((inactive * 1.0 / total) * 100);
int actPerc = (int) ((active * 1.0 / total) * 100);
while (banPerc + inacPerc + actPerc < 100) {
int joinlPerc = (int) ((joinleaver * 1.0 / total) * 100);
while (banPerc + inacPerc + actPerc + joinlPerc < 100) {
actPerc++;
}
while (banPerc + inacPerc + actPerc > 100) {
while (banPerc + inacPerc + actPerc + joinlPerc > 100) {
actPerc--;
}
Slice s1 = Slice.newSlice((int) (banPerc), Color.newColor("951800"), "Banned", "Banned");
Slice s3 = Slice.newSlice((int) (inacPerc), Color.newColor("A9A9A9"), "Inactive", "Inactive");
Slice s4 = Slice.newSlice((int) (actPerc), Color.newColor("228B22"), "Active", "Active");
Slice bannedSlice = Slice.newSlice((int) (banPerc), Color.newColor(Phrase.HCOLOR_ACTP_BAN+""), "Banned", "Banned");
Slice joinLeaverSlice = Slice.newSlice((int) (joinlPerc), Color.newColor(Phrase.HCOLOR_ACTP_JON+""), "Unknown", "Unknown");
Slice inactiveSlice = Slice.newSlice((int) (inacPerc), Color.newColor(Phrase.HCOLOR_ACTP_INA+""), "Inactive", "Inactive");
Slice activeSlice = Slice.newSlice((int) (actPerc), Color.newColor(Phrase.HCOLOR_ACTP_ACT+""), "Active", "Active");
PieChart refChart = GCharts.newPieChart(s4, s3, s1);
PieChart refChart = GCharts.newPieChart(activeSlice, bannedSlice, inactiveSlice, joinLeaverSlice);
refChart.setSize(400, 150);
refChart.setThreeD(true);
String refURL = refChart.toURLString();

View File

@ -1,5 +1,6 @@
package com.djrapitops.plan.ui.graphs;
import com.djrapitops.plan.Phrase;
import com.googlecode.charts4j.Color;
import com.googlecode.charts4j.GCharts;
import com.googlecode.charts4j.PieChart;
@ -53,10 +54,10 @@ public class GMTimesPieChartCreator {
one--;
}
Slice s1 = Slice.newSlice(zero, Color.newColor("951800"), "Survival", "Survival");
Slice s2 = Slice.newSlice(one, Color.newColor("01A1DB"), "Creative", "Creative");
Slice s3 = Slice.newSlice(two, Color.newColor("FFFF33"), "Adventure", "Adventure");
Slice s4 = Slice.newSlice(three, Color.newColor("228B22"), "Spectator", "Spectator");
Slice s1 = Slice.newSlice(zero, Color.newColor(Phrase.HCOLOR_GMP_0 + ""), "Survival", "Survival");
Slice s2 = Slice.newSlice(one, Color.newColor(Phrase.HCOLOR_GMP_1+""), "Creative", "Creative");
Slice s3 = Slice.newSlice(two, Color.newColor(Phrase.HCOLOR_GMP_2+""), "Adventure", "Adventure");
Slice s4 = Slice.newSlice(three, Color.newColor(Phrase.HCOLOR_GMP_3+""), "Spectator", "Spectator");
PieChart refChart = GCharts.newPieChart(s1, s2, s3, s4);
refChart.setSize(400, 150);

View File

@ -1,5 +1,6 @@
package main.java.com.djrapitops.plan.ui.graphs;
import com.djrapitops.plan.Phrase;
import com.djrapitops.plan.Plan;
import com.djrapitops.plan.data.ServerData;
import com.djrapitops.plan.utilities.FormatUtils;
@ -84,8 +85,8 @@ public class PlayerActivityGraphCreator {
Data pYData = Data.newData(pYList);
Data nYData = Data.newData(nYList);
XYLine playerLine = Plots.newXYLine(xData, pYData, Color.BLUE, "Online Players");
XYLine newPlayerLine = Plots.newXYLine(xData, nYData, Color.GREEN, "New Players");
XYLine playerLine = Plots.newXYLine(xData, pYData, Color.newColor(Phrase.HCOLOR_ACT_ONL + ""), "Online Players");
XYLine newPlayerLine = Plots.newXYLine(xData, nYData, Color.newColor(Phrase.HCOLOR_ACT_NEW + ""), "New Players");
LineChart chart = GCharts.newLineChart(playerLine, newPlayerLine);
chart.addXAxisLabels(xAxisLabels);
chart.addTopAxisLabels(AxisLabelsFactory.newAxisLabels("Players", 1));

View File

@ -75,25 +75,23 @@ public class Analysis {
rawServerData = plugin.getDB().getServerDataHashMap();
plugin.log("Analysis | Data Fetched, beginning Analysis of data..");
AnalysisData data = new AnalysisData();
long scaleMonth = (long) 2592000 * (long) 1000;
String playerActivityHtmlMonth = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleMonth);
data.setPlayersChartImgHtmlMonth(playerActivityHtmlMonth);
long scaleWeek = 604800 * 1000;
String playerActivityHtmlWeek = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleWeek);
data.setPlayersChartImgHtmlWeek(playerActivityHtmlWeek);
long scaleDay = 86400 * 1000;
String playerActivityHtmlDay = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleDay);
data.setPlayersChartImgHtmlDay(playerActivityHtmlDay);
createPlayerActivityGraphs(data);
// Create empty Dataset
long gmZero = 0;
long gmOne = 0;
long gmTwo = 0;
long gmThree = 0;
long totalPlaytime = 0;
int totalBanned = 0;
long totalLoginTimes = 0;
long totalPlaytime = 0;
int totalBanned = 0;
int active = 0;
int joinleaver = 0;
int inactive = 0;
int ops = 0;
List<Integer> ages = new ArrayList<>();
@ -105,6 +103,7 @@ public class Analysis {
int totalVotes = 0;
int totalMoney = 0;
// Fill Dataset with userdata.
for (UserData uData : rawData) {
if (planLiteEnabled) {
PlanLitePlayerData litePlayerData = uData.getPlanLiteData();
@ -138,12 +137,13 @@ public class Analysis {
if (uData.isBanned()) {
totalBanned++;
} else if (uData.getLoginTimes() == 1) {
joinleaver++;
} else if (AnalysisUtils.isActive(uData.getLastPlayed(), uData.getPlayTime(), uData.getLoginTimes())) {
active++;
} else {
inactive++;
}
}
if (planLiteEnabled) {
@ -159,29 +159,30 @@ public class Analysis {
data.setTotalLoginTimes(totalLoginTimes);
String activityPieChartHtml = AnalysisUtils.createActivityPieChart(totalBanned, active, inactive);
String activityPieChartHtml = AnalysisUtils.createActivityPieChart(totalBanned, active, inactive, joinleaver);
data.setActivityChartImgHtml(activityPieChartHtml);
data.setActive(active);
data.setInactive(inactive);
data.setBanned(totalBanned);
data.setTotal(offlinePlayers.length);
data.setJoinleaver(joinleaver);
data.setTotal(offlinePlayers.length);
data.setOps(ops);
data.setTotalPlayTime(totalPlaytime);
long averagePlaytime = totalPlaytime / rawData.size();
data.setAveragePlayTime(averagePlaytime);
data.setAveragePlayTime(totalPlaytime / rawData.size());
int totalAge = 0;
for (int age : ages) {
totalAge += age;
}
double averageAge;
if (ages.size() != 0) {
if (!ages.isEmpty()) {
averageAge = totalAge * 1.0 / ages.size();
} else {
averageAge = -1;
}
data.setAverageAge(averageAge);
long gmTotal = gmZero + gmOne + gmTwo + gmThree;
HashMap<GameMode, Long> totalGmTimes = new HashMap<>();
totalGmTimes.put(GameMode.SURVIVAL, gmZero);
@ -213,6 +214,18 @@ public class Analysis {
plugin.log("Analysis | Analysis Complete.");
this.cancel();
}
private void createPlayerActivityGraphs(AnalysisData data) {
long scaleMonth = (long) 2592000 * (long) 1000;
String playerActivityHtmlMonth = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleMonth);
data.setPlayersChartImgHtmlMonth(playerActivityHtmlMonth);
long scaleWeek = 604800 * 1000;
String playerActivityHtmlWeek = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleWeek);
data.setPlayersChartImgHtmlWeek(playerActivityHtmlWeek);
long scaleDay = 86400 * 1000;
String playerActivityHtmlDay = AnalysisUtils.createPlayerActivityGraph(rawServerData, scaleDay);
data.setPlayersChartImgHtmlDay(playerActivityHtmlDay);
}
}).runTaskAsynchronously(plugin);
}
}

View File

@ -91,6 +91,7 @@ public class AnalysisUtils {
replaceMap.put("%active%", "" + data.getActive());
replaceMap.put("%banned%", "" + data.getBanned());
replaceMap.put("%inactive%", "" + data.getInactive());
replaceMap.put("%joinleaver%", ""+data.getJoinleaver());
replaceMap.put("%activitytotal%", "" + data.getTotal());
replaceMap.put("%playerchartmonth%", data.getPlayersChartImgHtmlMonth());
replaceMap.put("%playerchartweek%", data.getPlayersChartImgHtmlWeek());
@ -124,8 +125,8 @@ public class AnalysisUtils {
return false;
}
static String createActivityPieChart(int totalBanned, int active, int inactive) {
String url = ActivityPieChartCreator.createChart(totalBanned, active, inactive);
static String createActivityPieChart(int totalBanned, int active, int inactive, int joinleaver) {
String url = ActivityPieChartCreator.createChart(totalBanned, active, inactive, joinleaver);
return "<img src=\"" + url + "\">";
}

View File

@ -1,6 +1,7 @@
package com.djrapitops.plan.utilities;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.bukkit.Location;
@ -37,20 +38,8 @@ public class FormatUtils {
}
// Removes letters from a string leaving only numbers and dots.
public static String removeLetters(String dataPoint) {
String numbers = "0123456789.";
List<Character> numList = new ArrayList<>();
char[] numberArray = numbers.toCharArray();
for (char c : numberArray) {
numList.add(c);
}
String returnString = "";
for (int i = 0; i < dataPoint.length(); i++) {
if (numList.contains(dataPoint.charAt(i))) {
returnString += dataPoint.charAt(i);
}
}
return returnString;
public static String removeLetters(String dataPoint) {
return dataPoint.replaceAll("[^\\d.]", "");
}
// Formats long in milliseconds into d:h:m:s string

View File

@ -39,7 +39,7 @@
<p>The average of known player ages is %avgage%.</p>
<br/><h4>Playerbase composition</h4>
%activitypiechart%
<p>Active %active% | Inactive %inactive% | Banned %banned% | Total Players: %activitytotal%</p>
<p>Active %active% | Inactive %inactive% | Banned %banned% | Joined once %joinleaver% | Total: %activitytotal%</p>
<br/><h4>Gamemode Usage</h4>
%gmpiechart%
<p>Survival: %gm0% | Creative: %gm1% | Adventure: %gm2% | Spectator: %gm3%</p>

View File

@ -5,19 +5,45 @@ Settings:
AnalysisCache:
RefreshAnalysisCacheOnEnable: true
InspectCache:
ClearFromInspectCacheAfterXMinutes: 3
ClearFromInspectCacheAfterXMinutes: 5
DataCache:
SaveEveryXMinutes: 5
SaveServerDataEveryXMinutes: 5
ClearCacheEveryXSaves: 5
WebServer:
Enabled: true
Port: 8804
ShowAlternativeServerIP: false
AlternativeIP: your.ip.here:%port%
PlanLite:
Enabled: true
UseAsAlternativeUI: false
Customization:
Colors:
Commands:
Main: '&2'
Secondary: '&7'
Highlight: '&f'
HTML:
ActivityGraph:
OnlinePlayers: '1E90FF'
NewPlayers: '228B22'
GamemodePie:
Survival: '951800'
Creative: '01A1DB'
Adventure: 'FFFF33'
Spectator: '228B22'
ActivityPie:
Active: '228B22'
Banned: '951800'
Inactive: 'A9A9A9'
JoinedOnce: '808080'
DemographicsTriggers:
Trigger: "i'm, am, im"
Female: 'female, girl, gurl, woman, gal, mrs, she, miss'
Male: 'male, boy, man, boe, sir, mr, guy, he'
IgnoreWhen: 'sure, think, with, are, you'
database:
type: sqlite
type: sqlite

View File

@ -1,7 +1,7 @@
name: Plan
author: Rsl1122
main: com.djrapitops.plan.Plan
version: 2.0.0
version: 2.1.0
commands:
plan:

View File

@ -1 +1 @@
version: 2.0.0
version: 1.6.3