388 lines
20 KiB
Java
388 lines
20 KiB
Java
/*
|
|
* This file is part of Player Analytics (Plan).
|
|
*
|
|
* Plan is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Lesser General Public License v3 as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Plan is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
package com.djrapitops.plan.delivery.rendering.json;
|
|
|
|
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
|
|
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
|
|
import com.djrapitops.plan.delivery.domain.datatransfer.extension.ExtensionsDto;
|
|
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
|
|
import com.djrapitops.plan.delivery.domain.mutators.*;
|
|
import com.djrapitops.plan.delivery.formatting.Formatter;
|
|
import com.djrapitops.plan.delivery.formatting.Formatters;
|
|
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
|
|
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
|
|
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
|
|
import com.djrapitops.plan.extension.implementation.results.ExtensionData;
|
|
import com.djrapitops.plan.extension.implementation.storage.queries.ExtensionPlayerDataQuery;
|
|
import com.djrapitops.plan.gathering.cache.SessionCache;
|
|
import com.djrapitops.plan.gathering.domain.GeoInfo;
|
|
import com.djrapitops.plan.gathering.domain.PlayerKill;
|
|
import com.djrapitops.plan.gathering.domain.WorldTimes;
|
|
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
|
|
import com.djrapitops.plan.identification.Server;
|
|
import com.djrapitops.plan.identification.ServerUUID;
|
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
|
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
|
|
import com.djrapitops.plan.settings.config.paths.TimeSettings;
|
|
import com.djrapitops.plan.settings.locale.lang.GenericLang;
|
|
import com.djrapitops.plan.settings.theme.Theme;
|
|
import com.djrapitops.plan.settings.theme.ThemeVal;
|
|
import com.djrapitops.plan.storage.database.DBSystem;
|
|
import com.djrapitops.plan.storage.database.Database;
|
|
import com.djrapitops.plan.storage.database.queries.containers.PlayerContainerQuery;
|
|
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
|
import com.djrapitops.plan.utilities.comparators.DateHolderRecentComparator;
|
|
import com.djrapitops.plan.utilities.java.Lists;
|
|
import com.djrapitops.plan.utilities.java.Maps;
|
|
|
|
import javax.inject.Inject;
|
|
import javax.inject.Singleton;
|
|
import java.util.*;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.function.Predicate;
|
|
|
|
@Singleton
|
|
public class PlayerJSONCreator {
|
|
|
|
private final PlanConfig config;
|
|
private final Theme theme;
|
|
private final DBSystem dbSystem;
|
|
private final Graphs graphs;
|
|
private final Formatters formatters;
|
|
|
|
private final Formatter<Long> timeAmount;
|
|
private final Formatter<Double> decimals;
|
|
private final Formatter<Long> year;
|
|
|
|
@Inject
|
|
public PlayerJSONCreator(
|
|
PlanConfig config,
|
|
Theme theme,
|
|
DBSystem dbSystem,
|
|
Formatters formatters,
|
|
Graphs graphs
|
|
) {
|
|
this.config = config;
|
|
this.theme = theme;
|
|
this.dbSystem = dbSystem;
|
|
|
|
this.formatters = formatters;
|
|
timeAmount = formatters.timeAmount();
|
|
decimals = formatters.decimals();
|
|
year = formatters.yearLong();
|
|
this.graphs = graphs;
|
|
}
|
|
|
|
public Map<String, Object> createJSONAsMap(UUID playerUUID, Predicate<WebPermission> hasPermission) {
|
|
Database db = dbSystem.getDatabase();
|
|
|
|
Map<ServerUUID, String> serverNames = db.query(ServerQueries.fetchServerNames());
|
|
PlayerContainer player = db.query(new PlayerContainerQuery(playerUUID));
|
|
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
|
|
|
|
PingMutator.forContainer(player).addPingToSessions(sessionsMutator.all());
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
|
long now = System.currentTimeMillis();
|
|
data.put("timestamp", now);
|
|
data.put("timestamp_f", year.apply(now));
|
|
|
|
if (hasPermission.test(WebPermission.PAGE_PLAYER_OVERVIEW)) {
|
|
data.put("info", createInfoJSONMap(player, serverNames));
|
|
data.put("online_activity", createOnlineActivityJSONMap(sessionsMutator));
|
|
data.put("nicknames", player.getValue(PlayerKeys.NICKNAMES)
|
|
.map(nicks -> Nickname.fromDataNicknames(nicks, serverNames, year))
|
|
.orElse(Collections.emptyList()));
|
|
data.put("connections", player.getValue(PlayerKeys.GEO_INFO)
|
|
.map(geoInfo -> ConnectionInfo.fromGeoInfo(geoInfo, year))
|
|
.orElse(Collections.emptyList()));
|
|
data.put("punchcard_series", graphs.special().punchCard(sessionsMutator).getDots());
|
|
} else {
|
|
data.put("info", createLimitedInfoMap(player));
|
|
}
|
|
if (hasPermission.test(WebPermission.PAGE_PLAYER_SESSIONS)) {
|
|
data.put("sessions", sessionsMutator.sort(new DateHolderRecentComparator()).toServerNameJSONMaps(graphs, config.getWorldAliasSettings(), formatters));
|
|
data.put("sessions_per_page", config.get(DisplaySettings.SESSIONS_PER_PAGE));
|
|
WorldPie worldPie = graphs.pie().worldPie(player.getValue(PlayerKeys.WORLD_TIMES).orElse(new WorldTimes()));
|
|
data.put("world_pie_series", worldPie.getSlices());
|
|
data.put("gm_series", worldPie.toHighChartsDrillDownMaps());
|
|
data.put("first_day", 1); // Monday
|
|
data.put("calendar_series", graphs.calendar().playerCalendar(player).getEntries());
|
|
}
|
|
if (hasPermission.test(WebPermission.PAGE_PLAYER_VERSUS)) {
|
|
List<PlayerKill> kills = player.getValue(PlayerKeys.PLAYER_KILLS).orElse(Collections.emptyList());
|
|
List<PlayerKill> deaths = player.getValue(PlayerKeys.PLAYER_DEATHS_KILLS).orElse(Collections.emptyList());
|
|
|
|
data.put("kill_data", createPvPPvEMap(player));
|
|
data.put("player_kills", new PlayerKillMutator(kills).filterNonSelfKills().toJSONAsMap(formatters));
|
|
data.put("player_deaths", new PlayerKillMutator(deaths).toJSONAsMap(formatters));
|
|
}
|
|
if (hasPermission.test(WebPermission.PAGE_PLAYER_SERVERS)) {
|
|
List<Map<String, Object>> serverAccordion = new ServerAccordion(player, serverNames, graphs, year, timeAmount, GenericLang.UNKNOWN.getKey()).asMaps();
|
|
Map<ServerUUID, WorldTimes> worldTimesPerServer = PerServerMutator.forContainer(player).worldTimesPerServer();
|
|
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
|
|
|
|
data.put("ping_graph", createPingGraphJson(player));
|
|
data.put("servers", serverAccordion);
|
|
data.put("server_pie_series", graphs.pie().serverPreferencePie(serverNames, worldTimesPerServer).getSlices());
|
|
data.put("server_pie_colors", pieColors);
|
|
}
|
|
if (hasPermission.test(WebPermission.PAGE_PLAYER_PLUGINS)) {
|
|
data.put("extensions", playerExtensionData(playerUUID));
|
|
} else {
|
|
data.put("extensions", List.of());
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
private Map<String, Object> createPingGraphJson(PlayerContainer player) {
|
|
PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING));
|
|
return Maps.builder(String.class, Object.class)
|
|
.put("min_ping_series", pingGraph.getMinGraph().getPointArrays())
|
|
.put("avg_ping_series", pingGraph.getAvgGraph().getPointArrays())
|
|
.put("max_ping_series", pingGraph.getMaxGraph().getPointArrays())
|
|
.put("colors", Maps.builder(String.class, String.class)
|
|
.put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING))
|
|
.put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING))
|
|
.put("max", theme.getValue(ThemeVal.GRAPH_MAX_PING))
|
|
.build())
|
|
.build();
|
|
}
|
|
|
|
private Map<String, Object> createOnlineActivityJSONMap(SessionsMutator sessionsMutator) {
|
|
long now = System.currentTimeMillis();
|
|
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
|
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);
|
|
SessionsMutator sessions30d = sessionsMutator.filterSessionsBetween(monthAgo, now);
|
|
SessionsMutator sessions7d = sessions30d.filterSessionsBetween(weekAgo, now);
|
|
|
|
Map<String, Object> onlineActivity = new HashMap<>();
|
|
|
|
onlineActivity.put("playtime_30d", timeAmount.apply(sessions30d.toPlaytime()));
|
|
onlineActivity.put("active_playtime_30d", timeAmount.apply(sessions30d.toActivePlaytime()));
|
|
onlineActivity.put("afk_time_30d", timeAmount.apply(sessions30d.toAfkTime()));
|
|
onlineActivity.put("average_session_length_30d", timeAmount.apply(sessions30d.toAverageSessionLength()));
|
|
onlineActivity.put("median_session_length_30d", timeAmount.apply(sessions30d.toMedianSessionLength()));
|
|
onlineActivity.put("session_count_30d", sessions30d.count());
|
|
onlineActivity.put("player_kill_count_30d", sessions30d.toPlayerKillCount());
|
|
onlineActivity.put("mob_kill_count_30d", sessions30d.toMobKillCount());
|
|
onlineActivity.put("death_count_30d", sessions30d.toDeathCount());
|
|
|
|
onlineActivity.put("playtime_7d", timeAmount.apply(sessions7d.toPlaytime()));
|
|
onlineActivity.put("active_playtime_7d", timeAmount.apply(sessions7d.toActivePlaytime()));
|
|
onlineActivity.put("afk_time_7d", timeAmount.apply(sessions7d.toAfkTime()));
|
|
onlineActivity.put("average_session_length_7d", timeAmount.apply(sessions7d.toAverageSessionLength()));
|
|
onlineActivity.put("median_session_length_7d", timeAmount.apply(sessions7d.toMedianSessionLength()));
|
|
onlineActivity.put("session_count_7d", sessions7d.count());
|
|
onlineActivity.put("player_kill_count_7d", sessions7d.toPlayerKillCount());
|
|
onlineActivity.put("mob_kill_count_7d", sessions7d.toMobKillCount());
|
|
onlineActivity.put("death_count_7d", sessions7d.toDeathCount());
|
|
|
|
return onlineActivity;
|
|
}
|
|
|
|
private Map<String, Object> createInfoJSONMap(PlayerContainer player, Map<ServerUUID, String> serverNames) {
|
|
SessionsMutator sessions = SessionsMutator.forContainer(player);
|
|
ActivityIndex activityIndex = player.getActivityIndex(System.currentTimeMillis(), config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD));
|
|
PerServerMutator perServer = PerServerMutator.forContainer(player);
|
|
PingMutator ping = PingMutator.forContainer(player);
|
|
|
|
Map<String, Object> info = new HashMap<>();
|
|
|
|
info.put("name", player.getValue(PlayerKeys.NAME).orElse(player.getUnsafe(PlayerKeys.UUID).toString()));
|
|
info.put("uuid", player.getUnsafe(PlayerKeys.UUID).toString());
|
|
info.put("online", SessionCache.getCachedSession(player.getUnsafe(PlayerKeys.UUID)).isPresent());
|
|
info.put("operator", player.getValue(PlayerKeys.OPERATOR).orElse(false));
|
|
info.put("banned", player.getValue(PlayerKeys.BANNED).orElse(false));
|
|
info.put("kick_count", player.getValue(PlayerKeys.KICK_COUNT).orElse(0));
|
|
info.put("player_kill_count", player.getValue(PlayerKeys.PLAYER_KILL_COUNT).orElse(0));
|
|
info.put("mob_kill_count", player.getValue(PlayerKeys.MOB_KILL_COUNT).orElse(0));
|
|
info.put("death_count", player.getValue(PlayerKeys.DEATH_COUNT).orElse(0));
|
|
info.put("playtime", timeAmount.apply(sessions.toPlaytime()));
|
|
info.put("active_playtime", timeAmount.apply(sessions.toActivePlaytime()));
|
|
info.put("afk_time", timeAmount.apply(sessions.toAfkTime()));
|
|
info.put("session_count", sessions.count());
|
|
info.put("longest_session_length", timeAmount.apply(sessions.toLongestSessionLength()));
|
|
info.put("session_median", timeAmount.apply(sessions.toMedianSessionLength()));
|
|
info.put("activity_index", decimals.apply(activityIndex.getValue()));
|
|
info.put("activity_index_group", activityIndex.getGroupLang());
|
|
info.put("favorite_server", perServer.favoriteServer().map(favoriteServer -> serverNames.getOrDefault(favoriteServer, favoriteServer.toString())).orElse(GenericLang.UNKNOWN.getKey()));
|
|
info.put("latest_join_address", sessions.latestSession()
|
|
.flatMap(session -> session.getExtraData(JoinAddress.class))
|
|
.map(JoinAddress::getAddress)
|
|
.orElse("-"));
|
|
double averagePing = ping.average();
|
|
int worstPing = ping.max();
|
|
int bestPing = ping.min();
|
|
|
|
String unavailable = GenericLang.UNAVAILABLE.getKey();
|
|
info.put("average_ping", averagePing != -1.0 ? decimals.apply(averagePing) + " ms" : unavailable);
|
|
info.put("worst_ping", worstPing != -1.0 ? worstPing + " ms" : unavailable);
|
|
info.put("best_ping", bestPing != -1.0 ? bestPing + " ms" : unavailable);
|
|
info.put("registered", player.getValue(PlayerKeys.REGISTERED).map(year).orElse("-"));
|
|
info.put("last_seen", player.getValue(PlayerKeys.LAST_SEEN).map(year).orElse("-"));
|
|
info.put("last_seen_raw_value", player.getValue(PlayerKeys.LAST_SEEN).orElse(0L));
|
|
|
|
return info;
|
|
}
|
|
|
|
private Map<String, Object> createLimitedInfoMap(PlayerContainer player) {
|
|
Map<String, Object> info = new HashMap<>();
|
|
|
|
info.put("name", player.getValue(PlayerKeys.NAME).orElse(player.getUnsafe(PlayerKeys.UUID).toString()));
|
|
info.put("uuid", player.getUnsafe(PlayerKeys.UUID).toString());
|
|
|
|
return info;
|
|
}
|
|
|
|
private Map<String, Object> createPvPPvEMap(PlayerContainer playerContainer) {
|
|
long now = System.currentTimeMillis();
|
|
long weekAgo = now - TimeUnit.DAYS.toMillis(7L);
|
|
long monthAgo = now - TimeUnit.DAYS.toMillis(30L);
|
|
|
|
PlayerVersusMutator playerVersus = PlayerVersusMutator.forContainer(playerContainer);
|
|
PlayerVersusMutator playerVersus30d = playerVersus.filterBetween(monthAgo, now);
|
|
PlayerVersusMutator playerVersus7d = playerVersus30d.filterBetween(weekAgo, now);
|
|
|
|
Map<String, Object> killData = new HashMap<>();
|
|
int pks = playerVersus.toPlayerKillCount();
|
|
int pks7d = playerVersus7d.toPlayerKillCount();
|
|
int pks30d = playerVersus30d.toPlayerKillCount();
|
|
killData.put("player_kills_total", pks);
|
|
killData.put("player_kills_30d", pks30d);
|
|
killData.put("player_kills_7d", pks7d);
|
|
|
|
int playerDeaths = playerVersus.toPlayerDeathCount();
|
|
int playerDeaths30d = playerVersus30d.toPlayerDeathCount();
|
|
int playerDeaths7d = playerVersus7d.toPlayerDeathCount();
|
|
killData.put("player_deaths_total", playerDeaths);
|
|
killData.put("player_deaths_30d", playerDeaths30d);
|
|
killData.put("player_deaths_7d", playerDeaths7d);
|
|
|
|
double kdr = playerDeaths != 0 ? (double) pks / playerDeaths : pks;
|
|
double kdr30d = playerDeaths30d != 0 ? (double) pks30d / playerDeaths30d : pks30d;
|
|
double krd7d = playerDeaths7d != 0 ? (double) pks7d / playerDeaths7d : pks7d;
|
|
killData.put("player_kdr_total", decimals.apply(kdr));
|
|
killData.put("player_kdr_30d", decimals.apply(kdr30d));
|
|
killData.put("player_kdr_7d", decimals.apply(krd7d));
|
|
|
|
int mobKills = playerVersus.toMobKillCount();
|
|
int mobKills30d = playerVersus30d.toMobKillCount();
|
|
int mobKills7d = playerVersus7d.toMobKillCount();
|
|
killData.put("mob_kills_total", mobKills);
|
|
killData.put("mob_kills_30d", mobKills30d);
|
|
killData.put("mob_kills_7d", mobKills7d);
|
|
|
|
int deaths = playerVersus.toDeathCount();
|
|
int deaths30d = playerVersus30d.toDeathCount();
|
|
int deaths7d = playerVersus7d.toDeathCount();
|
|
killData.put("deaths_total", deaths);
|
|
killData.put("deaths_30d", deaths30d);
|
|
killData.put("deaths_7d", deaths7d);
|
|
|
|
int mobDeaths = deaths - playerDeaths;
|
|
int mobDeaths30d = deaths30d - playerDeaths30d;
|
|
int mobDeaths7d = deaths7d - playerDeaths7d;
|
|
|
|
killData.put("mob_deaths_total", mobDeaths);
|
|
killData.put("mob_deaths_30d", mobDeaths30d);
|
|
killData.put("mob_deaths_7d", mobDeaths7d);
|
|
|
|
double mobKdr = mobDeaths != 0 ? (double) mobKills / mobDeaths : mobKills;
|
|
double mobKdr30d = mobDeaths30d != 0 ? (double) mobKills30d / mobDeaths30d : mobKills30d;
|
|
double mobKdr7d = mobDeaths7d != 0 ? (double) mobKills7d / mobDeaths7d : mobKills7d;
|
|
killData.put("mob_kdr_total", decimals.apply(mobKdr));
|
|
killData.put("mob_kdr_30d", decimals.apply(mobKdr30d));
|
|
killData.put("mob_kdr_7d", decimals.apply(mobKdr7d));
|
|
|
|
List<String> topWeapons = playerVersus.toTopWeapons(3);
|
|
killData.put("weapon_1st", getWeapon(topWeapons, 0).orElse("-"));
|
|
killData.put("weapon_2nd", getWeapon(topWeapons, 1).orElse("-"));
|
|
killData.put("weapon_3rd", getWeapon(topWeapons, 2).orElse("-"));
|
|
|
|
return killData;
|
|
}
|
|
|
|
private <T> Optional<T> getWeapon(List<T> list, int index) {
|
|
return list.size() <= index ? Optional.empty() : Optional.of(list.get(index));
|
|
}
|
|
|
|
|
|
public List<ExtensionsDto> playerExtensionData(UUID playerUUID) {
|
|
Database database = dbSystem.getDatabase();
|
|
Map<ServerUUID, List<ExtensionData>> extensionPlayerData = database.query(new ExtensionPlayerDataQuery(playerUUID));
|
|
Map<ServerUUID, Server> servers = database.query(ServerQueries.fetchPlanServerInformation());
|
|
|
|
List<ExtensionsDto> playerData = new ArrayList<>();
|
|
for (Map.Entry<ServerUUID, Server> entry : servers.entrySet()) {
|
|
ServerUUID serverUUID = entry.getKey();
|
|
playerData.add(new ExtensionsDto(
|
|
playerUUID.toString(), serverUUID.toString(),
|
|
entry.getValue().getIdentifiableName(),
|
|
extensionPlayerData.getOrDefault(serverUUID, Collections.emptyList())
|
|
));
|
|
}
|
|
return playerData;
|
|
}
|
|
|
|
public static class Nickname {
|
|
final String nickname;
|
|
final String server;
|
|
final String date;
|
|
|
|
public Nickname(String nickname, String server, String date) {
|
|
this.nickname = nickname;
|
|
this.server = server;
|
|
this.date = date;
|
|
}
|
|
|
|
public static List<Nickname> fromDataNicknames(
|
|
List<com.djrapitops.plan.delivery.domain.Nickname> nicknames,
|
|
Map<ServerUUID, String> serverNames,
|
|
Formatter<Long> dateFormatter
|
|
) {
|
|
nicknames.sort(new DateHolderRecentComparator());
|
|
List<Nickname> mapped = new ArrayList<>();
|
|
for (com.djrapitops.plan.delivery.domain.Nickname nickname : nicknames) {
|
|
mapped.add(new Nickname(
|
|
nickname.getName(),
|
|
serverNames.getOrDefault(nickname.getServerUUID(), nickname.getServerUUID().toString()),
|
|
dateFormatter.apply(nickname.getDate())
|
|
));
|
|
}
|
|
return mapped;
|
|
}
|
|
}
|
|
|
|
public static class ConnectionInfo {
|
|
final String geolocation;
|
|
final String date;
|
|
|
|
public ConnectionInfo(String geolocation, String date) {
|
|
this.geolocation = geolocation;
|
|
this.date = date;
|
|
}
|
|
|
|
public static List<ConnectionInfo> fromGeoInfo(List<GeoInfo> geoInfo, Formatter<Long> dateFormatter) {
|
|
return Lists.map(geoInfo, i -> new ConnectionInfo(i.getGeolocation(), dateFormatter.apply(i.getDate())));
|
|
}
|
|
}
|
|
|
|
}
|