Plan/Plan/common/src/main/java/com/djrapitops/plan/delivery/rendering/json/PlayerJSONCreator.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())));
}
}
}