Plan/Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/mutators/PlayersMutator.java

273 lines
11 KiB
Java
Raw Normal View History

/*
* 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.domain.mutators;
import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.container.DataContainer;
import com.djrapitops.plan.delivery.domain.container.PlayerContainer;
import com.djrapitops.plan.delivery.domain.keys.PlayerKeys;
import com.djrapitops.plan.delivery.domain.keys.ServerKeys;
import com.djrapitops.plan.delivery.domain.keys.SessionKeys;
import com.djrapitops.plan.gathering.domain.GeoInfo;
import com.djrapitops.plan.gathering.domain.Ping;
import com.djrapitops.plan.gathering.domain.Session;
2018-06-19 20:08:02 +02:00
import com.djrapitops.plugin.api.TimeAmount;
import java.util.*;
2018-07-09 09:25:49 +02:00
import java.util.function.Predicate;
2018-06-19 20:50:47 +02:00
import java.util.stream.Collectors;
2018-06-19 20:08:02 +02:00
/**
Interface redesign package restructuring (#1146) * command.commands -> command.subcommands * command -> commands * commands -> system.commands * system.locale -> system.settings.locale * system.settings.changes -> system.settings.config.changes * system.settings.paths -> system.settings.config.paths * system.database -> system.storage.database * db -> system.storage.database * system.storage.database.access.queries -> system.storage.database.queries * system.storage.database.access.transactions -> system.storage.database.transactions * system.storage.database.access -> system.storage.database.operation * Moved Query classes to system.storage.database.queries * Moved Executable classes to system.storage.database.transactions * system.storage.database.patches -> system.storage.database.transactions.patches * system.file -> system.storage.file * system.settings.upkeep * system.storage.upkeep * system.server.info -> system.identification * system.importing -> system.gathering.importing * system.listeners -> system.gathering.listeners * system.gathering.timed * Removed duplicate class * data.container -> system.gathering.domain * data.plugin.PluginsConfigSection -> system.settings.config.ExtensionSettings * data.time -> system.gathering.domain * system.afk -> system.gathering.afk * system.cache -> system.gathering.cache * system.status -> system.gathering.listeners * system.export -> system.delivery.export * system.webserver -> system.delivery.webserver * system.json -> system.delivery.rendering.json * utilities.html -> system.delivery.rendering.html * system.delivery.rendering.html.graphs -> system.delivery.rendering.json.graphs * system.delivery.rendering.html.pages -> system.delivery.rendering.pages * system.delivery.upkeep * utilities.file -> system.settings.upkeep * data.store -> system.delivery.domain * system.update -> system.version * api.exceptions -> exceptions * ShutdownHook -> system.gathering * system.HtmlUtilities - > system.delivery.DeliveryUtilities * PeriodicAnalysisTask -> PeriodicServerExportTask * Deprecated APIv4 classes * Removed ServerTaskSystem (Reduces headache) * Moved & Fixed some tests
2019-08-30 11:36:38 +02:00
* Mutator for a bunch of {@link PlayerContainer}s.
2018-06-19 20:08:02 +02:00
*
* @author Rsl1122
*/
public class PlayersMutator {
private final List<PlayerContainer> players;
2018-06-19 20:08:02 +02:00
public PlayersMutator(List<PlayerContainer> players) {
this.players = players;
}
public static PlayersMutator copyOf(PlayersMutator mutator) {
return new PlayersMutator(new ArrayList<>(mutator.players));
}
public static PlayersMutator forContainer(DataContainer container) {
return new PlayersMutator(container.getValue(ServerKeys.PLAYERS).orElse(new ArrayList<>()));
}
2018-07-09 09:25:49 +02:00
public <T extends Predicate<PlayerContainer>> PlayersMutator filterBy(T by) {
return new PlayersMutator(players.stream().filter(by).collect(Collectors.toList()));
}
2018-06-19 20:50:47 +02:00
public PlayersMutator filterPlayedBetween(long after, long before) {
2018-07-09 09:25:49 +02:00
return filterBy(
player -> player.getValue(PlayerKeys.SESSIONS)
2018-06-19 20:50:47 +02:00
.map(sessions -> sessions.stream().anyMatch(session -> {
long start = session.getValue(SessionKeys.START).orElse(-1L);
long end = session.getValue(SessionKeys.END).orElse(-1L);
return (after <= start && start <= before) || (after <= end && end <= before);
})).orElse(false)
2018-07-09 09:25:49 +02:00
);
2018-06-19 20:50:47 +02:00
}
public PlayersMutator filterRegisteredBetween(long after, long before) {
2018-07-09 09:25:49 +02:00
return filterBy(
player -> player.getValue(PlayerKeys.REGISTERED)
.map(date -> after <= date && date <= before).orElse(false)
);
2018-06-19 20:50:47 +02:00
}
2018-06-20 11:13:17 +02:00
public PlayersMutator filterRetained(long after, long before) {
2018-07-09 09:25:49 +02:00
return filterBy(
player -> {
2018-06-20 11:13:17 +02:00
long backLimit = Math.max(after, player.getValue(PlayerKeys.REGISTERED).orElse(0L));
long half = backLimit + ((before - backLimit) / 2L);
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
return sessionsMutator.playedBetween(backLimit, half) &&
sessionsMutator.playedBetween(half, before);
2018-07-09 09:25:49 +02:00
}
);
2018-06-20 11:13:17 +02:00
}
2019-07-13 09:50:21 +02:00
public PlayersMutator filterActive(long date, long msThreshold, double limit) {
return filterBy(player -> player.getActivityIndex(date, msThreshold).getValue() >= limit);
2018-06-20 11:39:35 +02:00
}
public PlayersMutator filterPlayedOnServer(UUID serverUUID) {
return filterBy(player -> !SessionsMutator.forContainer(player)
.filterPlayedOnServer(serverUUID)
.all().isEmpty()
);
}
2018-06-19 20:08:02 +02:00
public List<PlayerContainer> all() {
return players;
}
2018-06-19 20:50:47 +02:00
public List<Long> registerDates() {
List<Long> registerDates = new ArrayList<>();
for (PlayerContainer player : players) {
registerDates.add(player.getValue(PlayerKeys.REGISTERED).orElse(-1L));
}
return registerDates;
}
2018-06-19 20:08:02 +02:00
public List<String> getGeolocations() {
List<String> geolocations = new ArrayList<>();
for (PlayerContainer player : players) {
Optional<GeoInfo> mostRecent = GeoInfoMutator.forContainer(player).mostRecent();
2018-07-15 19:34:05 +02:00
geolocations.add(mostRecent.map(GeoInfo::getGeolocation).orElse("Unknown"));
2018-06-19 20:08:02 +02:00
}
return geolocations;
}
public Map<String, List<Ping>> getPingPerCountry(UUID serverUUID) {
Map<String, List<Ping>> pingPerCountry = new HashMap<>();
for (PlayerContainer player : players) {
Optional<GeoInfo> mostRecent = GeoInfoMutator.forContainer(player).mostRecent();
if (!mostRecent.isPresent()) {
continue;
}
List<Ping> pings = player.getValue(PlayerKeys.PING).orElse(new ArrayList<>());
String country = mostRecent.get().getGeolocation();
List<Ping> countryPings = pingPerCountry.getOrDefault(country, new ArrayList<>());
pings.stream()
.filter(ping -> ping.getServerUUID().equals(serverUUID))
.forEach(countryPings::add);
pingPerCountry.put(country, countryPings);
}
return pingPerCountry;
}
public TreeMap<Long, Map<String, Set<UUID>>> toActivityDataMap(long date, long msThreshold) {
2018-06-19 20:08:02 +02:00
TreeMap<Long, Map<String, Set<UUID>>> activityData = new TreeMap<>();
for (long time = date; time >= date - TimeAmount.MONTH.toMillis(2L); time -= TimeAmount.WEEK.toMillis(1L)) {
Map<String, Set<UUID>> map = activityData.getOrDefault(time, new HashMap<>());
if (!players.isEmpty()) {
for (PlayerContainer player : players) {
if (player.getValue(PlayerKeys.REGISTERED).orElse(0L) > time) {
continue;
}
ActivityIndex activityIndex = player.getActivityIndex(time, msThreshold);
2018-06-19 20:08:02 +02:00
String activityGroup = activityIndex.getGroup();
Set<UUID> uuids = map.getOrDefault(activityGroup, new HashSet<>());
uuids.add(player.getUnsafe(PlayerKeys.UUID));
map.put(activityGroup, uuids);
}
}
activityData.put(time, map);
2018-06-19 20:08:02 +02:00
}
return activityData;
}
2018-06-19 20:50:47 +02:00
public int count() {
return players.size();
}
public int averageNewPerDay(TimeZone timeZone) {
return MutatorFunctions.average(newPerDay(timeZone));
}
2018-06-19 20:50:47 +02:00
public TreeMap<Long, Integer> newPerDay(TimeZone timeZone) {
List<DateObj> registerDates = registerDates().stream()
.map(value -> new DateObj<>(value, value))
.collect(Collectors.toList());
// Adds timezone offset
SortedMap<Long, List<DateObj>> byDay = new DateHoldersMutator<>(registerDates).groupByStartOfDay(timeZone);
TreeMap<Long, Integer> byDayCounts = new TreeMap<>();
for (Map.Entry<Long, List<DateObj>> entry : byDay.entrySet()) {
byDayCounts.put(
entry.getKey(),
entry.getValue().size()
);
2018-06-19 20:50:47 +02:00
}
return byDayCounts;
2018-06-19 20:50:47 +02:00
}
2018-06-20 11:13:17 +02:00
/**
* Compares players in the mutator to other players in terms of player retention.
*
* @param compareTo Players to compare to.
* @param dateLimit Epoch ms back limit, if the player registered after this their value is not used.
* @return Mutator containing the players that are considered to be retained.
* @throws IllegalStateException If all players are rejected due to dateLimit.
*/
public PlayersMutator compareAndFindThoseLikelyToBeRetained(
Iterable<PlayerContainer> compareTo,
long dateLimit,
PlayersOnlineResolver onlineResolver,
2019-07-13 09:50:21 +02:00
long activityMsThreshold
) {
2018-06-20 11:13:17 +02:00
Collection<PlayerContainer> retainedAfterMonth = new ArrayList<>();
Collection<PlayerContainer> notRetainedAfterMonth = new ArrayList<>();
for (PlayerContainer player : players) {
long registered = player.getValue(PlayerKeys.REGISTERED).orElse(System.currentTimeMillis());
// Discard uncertain data
if (registered > dateLimit) {
continue;
}
long monthAfterRegister = registered + TimeAmount.MONTH.toMillis(1L);
long half = registered + (TimeAmount.MONTH.toMillis(1L) / 2L);
2018-06-20 11:13:17 +02:00
if (player.playedBetween(registered, half) && player.playedBetween(half, monthAfterRegister)) {
retainedAfterMonth.add(player);
} else {
notRetainedAfterMonth.add(player);
}
}
if (retainedAfterMonth.isEmpty() || notRetainedAfterMonth.isEmpty()) {
throw new IllegalStateException("No players to compare to after rejecting with dateLimit");
}
List<RetentionData> retained = retainedAfterMonth.stream()
.map(player -> new RetentionData(player, onlineResolver, activityMsThreshold))
2018-06-20 11:13:17 +02:00
.collect(Collectors.toList());
List<RetentionData> notRetained = notRetainedAfterMonth.stream()
.map(player -> new RetentionData(player, onlineResolver, activityMsThreshold))
2018-06-20 11:13:17 +02:00
.collect(Collectors.toList());
2018-08-12 08:42:09 +02:00
RetentionData avgRetained = RetentionData.average(retained);
RetentionData avgNotRetained = RetentionData.average(notRetained);
2018-06-20 11:13:17 +02:00
List<PlayerContainer> toBeRetained = new ArrayList<>();
for (PlayerContainer player : compareTo) {
RetentionData retentionData = new RetentionData(player, onlineResolver, activityMsThreshold);
2018-06-20 11:13:17 +02:00
if (retentionData.distance(avgRetained) < retentionData.distance(avgNotRetained)) {
toBeRetained.add(player);
}
}
return new PlayersMutator(toBeRetained);
}
public List<Session> getSessions() {
return players.stream()
.map(player -> player.getValue(PlayerKeys.SESSIONS).orElse(new ArrayList<>()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
public List<UUID> uuids() {
return players.stream()
.map(player -> player.getValue(PlayerKeys.UUID).orElse(null))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public List<PlayerContainer> operators() {
return players.stream()
.filter(player -> player.getValue(PlayerKeys.OPERATOR).orElse(false)).collect(Collectors.toList());
}
2018-07-16 14:23:49 +02:00
public List<Ping> pings() {
return players.stream()
.map(player -> player.getValue(PlayerKeys.PING).orElse(new ArrayList<>()))
.flatMap(Collection::stream)
.collect(Collectors.toList());
}
2018-06-19 20:08:02 +02:00
}