Player retention

This commit is contained in:
Rsl1122 2018-06-20 12:13:17 +03:00
parent 7e420aaf91
commit 566f838a3a
23 changed files with 297 additions and 162 deletions

View File

@ -2,10 +2,10 @@ package com.djrapitops.plan.command.commands;
import com.djrapitops.plan.PlanPlugin; import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.api.exceptions.database.DBOpException; import com.djrapitops.plan.api.exceptions.database.DBOpException;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.GeoInfo; import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.data.store.containers.PlayerContainer; import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys; import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.GeoInfoMutator; import com.djrapitops.plan.data.store.mutators.GeoInfoMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter; import com.djrapitops.plan.data.store.mutators.formatting.Formatter;

View File

@ -4,11 +4,11 @@
*/ */
package com.djrapitops.plan.data; package com.djrapitops.plan.data;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.Action; import com.djrapitops.plan.data.container.Action;
import com.djrapitops.plan.data.container.GeoInfo; import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.data.container.PlayerKill; import com.djrapitops.plan.data.container.PlayerKill;
import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.time.WorldTimes; import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.utilities.comparators.ActionComparator; import com.djrapitops.plan.utilities.comparators.ActionComparator;

View File

@ -4,10 +4,10 @@ import com.djrapitops.plan.PlanPlugin;
import com.djrapitops.plan.data.PlayerProfile; import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plan.data.ServerProfile; import com.djrapitops.plan.data.ServerProfile;
import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.container.StickyData;
import com.djrapitops.plan.data.container.TPS; import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.element.AnalysisContainer; import com.djrapitops.plan.data.element.AnalysisContainer;
import com.djrapitops.plan.data.plugin.PluginData; import com.djrapitops.plan.data.plugin.PluginData;
import com.djrapitops.plan.data.store.mutators.RetentionData;
import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.data.time.WorldTimes; import com.djrapitops.plan.data.time.WorldTimes;
@ -55,7 +55,7 @@ public class AnalysisData extends RawData {
private long refreshDate; private long refreshDate;
private Map<String, Long> analyzedValues; private Map<String, Long> analyzedValues;
private Set<StickyData> stickyMonthData; private Set<RetentionData> stickyMonthData;
private List<PlayerProfile> players; private List<PlayerProfile> players;
public AnalysisData() { public AnalysisData() {
@ -250,7 +250,7 @@ public class AnalysisData extends RawData {
got("stuckPerM", stuckPerM); got("stuckPerM", stuckPerM);
got("stuckPerW", stuckPerW); got("stuckPerW", stuckPerW);
stickyMonthData = newMonth.stream().map(StickyData::new).distinct().collect(Collectors.toSet()); stickyMonthData = newMonth.stream().map(RetentionData::new).distinct().collect(Collectors.toSet());
addValue("playersStuckMonth", stuckPerM); addValue("playersStuckMonth", stuckPerM);
addValue("playersStuckWeek", stuckPerW); addValue("playersStuckWeek", stuckPerW);
@ -288,16 +288,16 @@ public class AnalysisData extends RawData {
return; return;
} }
List<StickyData> stuck = stuckAfterMonth.stream().map(StickyData::new).collect(Collectors.toList()); List<RetentionData> stuck = stuckAfterMonth.stream().map(RetentionData::new).collect(Collectors.toList());
List<StickyData> nonStuck = notStuckAfterMonth.stream().map(StickyData::new).collect(Collectors.toList()); List<RetentionData> nonStuck = notStuckAfterMonth.stream().map(RetentionData::new).collect(Collectors.toList());
StickyData avgStuck = AnalysisUtils.average(stuck); RetentionData avgStuck = AnalysisUtils.average(stuck);
StickyData avgNonStuck = AnalysisUtils.average(nonStuck); RetentionData avgNonStuck = AnalysisUtils.average(nonStuck);
int stuckPerD = 0; int stuckPerD = 0;
for (PlayerProfile player : newDay) { for (PlayerProfile player : newDay) {
StickyData stickyData = new StickyData(player); RetentionData retentionData = new RetentionData(player);
if (stickyData.distance(avgStuck) < stickyData.distance(avgNonStuck)) { if (retentionData.distance(avgStuck) < retentionData.distance(avgNonStuck)) {
stuckPerD++; stuckPerD++;
} }
} }
@ -403,7 +403,7 @@ public class AnalysisData extends RawData {
return analyzedValues.getOrDefault(key, 0L); return analyzedValues.getOrDefault(key, 0L);
} }
public Set<StickyData> getStickyMonthData() { public Set<RetentionData> getStickyMonthData() {
return stickyMonthData; return stickyMonthData;
} }

View File

@ -6,8 +6,8 @@ package com.djrapitops.plan.data.calculation;
import com.djrapitops.plan.data.PlayerProfile; import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plan.data.ServerProfile; import com.djrapitops.plan.data.ServerProfile;
import com.djrapitops.plan.data.container.StickyData;
import com.djrapitops.plan.data.container.TPS; import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.store.mutators.RetentionData;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter; import com.djrapitops.plan.data.store.mutators.formatting.Formatter;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.Settings;
@ -128,7 +128,7 @@ public class HealthNotes {
} }
private void newPlayerNote() { private void newPlayerNote() {
double avgOnlineOnRegister = MathUtils.averageDouble(analysisData.getStickyMonthData().stream().map(StickyData::getOnlineOnJoin)); double avgOnlineOnRegister = MathUtils.averageDouble(analysisData.getStickyMonthData().stream().map(RetentionData::getOnlineOnJoin));
if (avgOnlineOnRegister >= 1) { if (avgOnlineOnRegister >= 1) {
notes.add("<p>" + Html.GREEN_THUMB.parse() + " New Players have players to play with when they join (" notes.add("<p>" + Html.GREEN_THUMB.parse() + " New Players have players to play with when they join ("
+ FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>"); + FormatUtils.cutDecimals(avgOnlineOnRegister) + " on average)</p>");

View File

@ -1,108 +0,0 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.data.container;
import com.djrapitops.plan.data.Actions;
import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plugin.api.TimeAmount;
import com.google.common.base.Objects;
import java.util.List;
public class StickyData {
private final double activityIndex;
private Double messagesSent;
private Double onlineOnJoin;
public StickyData(PlayerProfile player) {
activityIndex = player.getActivityIndex(player.getRegistered() + TimeAmount.DAY.ms()).getValue();
loadActionVariables(player.getActions());
}
public StickyData(double activityIndex, Double messagesSent, Double onlineOnJoin) {
this.activityIndex = activityIndex;
this.messagesSent = messagesSent;
this.onlineOnJoin = onlineOnJoin;
}
private void loadActionVariables(List<Action> actions) {
for (Action action : actions) {
try {
if (messagesSent == null && action.getDoneAction() == Actions.FIRST_LOGOUT) {
messagesSent = (double) loadSentMessages(action);
}
if (onlineOnJoin == null && action.getDoneAction() == Actions.FIRST_SESSION) {
onlineOnJoin = (double) loadOnlineOnJoin(action);
}
} catch (IllegalArgumentException ignore) {
/* continue */
}
}
setDefaultValuesIfNull();
}
private void setDefaultValuesIfNull() {
if (messagesSent == null) {
messagesSent = 0.0;
}
if (onlineOnJoin == null) {
onlineOnJoin = 0.0;
}
}
private int loadOnlineOnJoin(Action action) {
String additionalInfo = action.getAdditionalInfo();
String[] split = additionalInfo.split(" ");
if (split.length == 3) {
return Integer.parseInt(split[1]);
}
throw new IllegalArgumentException("Improper Action");
}
private int loadSentMessages(Action action) {
String additionalInfo = action.getAdditionalInfo();
String[] split = additionalInfo.split(": ");
if (split.length == 2) {
return Integer.parseInt(split[1]);
}
throw new IllegalArgumentException("Improper Action");
}
public double distance(StickyData data) {
double num = 0;
num += Math.abs(data.activityIndex - activityIndex) * 2.0;
num += Math.abs(data.onlineOnJoin - onlineOnJoin) / 10.0;
num += Math.abs(data.messagesSent - messagesSent) / 10.0;
return num;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StickyData that = (StickyData) o;
return Double.compare(that.activityIndex, activityIndex) == 0 &&
Objects.equal(messagesSent, that.messagesSent) &&
Objects.equal(onlineOnJoin, that.onlineOnJoin);
}
@Override
public int hashCode() {
return Objects.hashCode(activityIndex, messagesSent, onlineOnJoin);
}
public double getOnlineOnJoin() {
return onlineOnJoin;
}
public double getActivityIndex() {
return activityIndex;
}
public Double getMessagesSent() {
return messagesSent;
}
}

View File

@ -5,10 +5,7 @@ import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.keys.AnalysisKeys; import com.djrapitops.plan.data.store.keys.AnalysisKeys;
import com.djrapitops.plan.data.store.keys.PlayerKeys; import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.keys.ServerKeys; import com.djrapitops.plan.data.store.keys.ServerKeys;
import com.djrapitops.plan.data.store.mutators.CommandUseMutator; import com.djrapitops.plan.data.store.mutators.*;
import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.TPSMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.data.time.WorldTimes; import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.system.database.databases.Database; import com.djrapitops.plan.system.database.databases.Database;
@ -32,10 +29,7 @@ import com.djrapitops.plan.utilities.html.tables.PlayersTable;
import com.djrapitops.plan.utilities.html.tables.ServerSessionTable; import com.djrapitops.plan.utilities.html.tables.ServerSessionTable;
import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.TimeAmount;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -172,6 +166,28 @@ public class AnalysisContainer extends DataContainer {
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_DAY, () -> getUnsafe(uniqueDay).newPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_DAY, () -> getUnsafe(uniqueDay).newPerDay());
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_WEEK, () -> getUnsafe(uniqueWeek).newPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_WEEK, () -> getUnsafe(uniqueWeek).newPerDay());
putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_MONTH, () -> getUnsafe(uniqueMonth).newPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_NEW_MONTH, () -> getUnsafe(uniqueMonth).newPerDay());
Key<Integer> retentionDay = new Key<>(Integer.class, "RETENTION_DAY");
// compareAndFindThoseLikelyToBeRetained can throw exception.
putSupplier(retentionDay, () -> getUnsafe(AnalysisKeys.PLAYERS_MUTATOR).compareAndFindThoseLikelyToBeRetained(
getUnsafe(newDay).all(), getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO),
getUnsafe(AnalysisKeys.PLAYERS_ONLINE_RESOLVER)
).count()
);
putSupplier(AnalysisKeys.PLAYERS_RETAINED_DAY, () -> {
try {
return getUnsafe(retentionDay);
} catch (IllegalStateException e) {
return 0;
}
});
putSupplier(AnalysisKeys.PLAYERS_RETAINED_DAY_PERC, () -> {
try {
return Formatters.percentage().apply(1.0 * getUnsafe(retentionDay) / getUnsafe(AnalysisKeys.PLAYERS_NEW_DAY));
} catch (IllegalStateException e) {
return "Not enough data";
}
});
} }
private void addSessionSuppliers() { private void addSessionSuppliers() {
@ -228,6 +244,24 @@ public class AnalysisContainer extends DataContainer {
putSupplier(AnalysisKeys.AVG_PLAYERS_DAY, () -> getUnsafe(sessionsDay).toUniqueJoinsPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_DAY, () -> getUnsafe(sessionsDay).toUniqueJoinsPerDay());
putSupplier(AnalysisKeys.AVG_PLAYERS_WEEK, () -> getUnsafe(sessionsWeek).toUniqueJoinsPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_WEEK, () -> getUnsafe(sessionsWeek).toUniqueJoinsPerDay());
putSupplier(AnalysisKeys.AVG_PLAYERS_MONTH, () -> getUnsafe(sessionsMonth).toUniqueJoinsPerDay()); putSupplier(AnalysisKeys.AVG_PLAYERS_MONTH, () -> getUnsafe(sessionsMonth).toUniqueJoinsPerDay());
putSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK, () ->
PlayersMutator.copyOf(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR)).filterRetained(
getUnsafe(AnalysisKeys.ANALYSIS_TIME_WEEK_AGO),
getUnsafe(AnalysisKeys.ANALYSIS_TIME)
).count()
);
putSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH, () ->
PlayersMutator.copyOf(getUnsafe(AnalysisKeys.PLAYERS_MUTATOR)).filterRetained(
getUnsafe(AnalysisKeys.ANALYSIS_TIME_MONTH_AGO),
getUnsafe(AnalysisKeys.ANALYSIS_TIME)
).count()
);
putSupplier(AnalysisKeys.PLAYERS_RETAINED_WEEK_PERC, () -> Formatters.percentage().apply(
1.0 * getUnsafe(AnalysisKeys.PLAYERS_RETAINED_WEEK) / getUnsafe(AnalysisKeys.PLAYERS_NEW_WEEK))
);
putSupplier(AnalysisKeys.PLAYERS_RETAINED_MONTH_PERC, () -> Formatters.percentage().apply(
1.0 * getUnsafe(AnalysisKeys.PLAYERS_RETAINED_MONTH) / getUnsafe(AnalysisKeys.PLAYERS_NEW_MONTH))
);
} }
private void addGraphSuppliers() { private void addGraphSuppliers() {
@ -256,6 +290,13 @@ public class AnalysisContainer extends DataContainer {
putSupplier(AnalysisKeys.ACTIVITY_PIE_SERIES, () -> putSupplier(AnalysisKeys.ACTIVITY_PIE_SERIES, () ->
new ActivityPie(getUnsafe(AnalysisKeys.ACTIVITY_DATA).get(getUnsafe(AnalysisKeys.ANALYSIS_TIME))).toHighChartsSeries() new ActivityPie(getUnsafe(AnalysisKeys.ACTIVITY_DATA).get(getUnsafe(AnalysisKeys.ANALYSIS_TIME))).toHighChartsSeries()
); );
putSupplier(AnalysisKeys.PLAYERS_REGULAR, () -> {
Map<String, Set<UUID>> activityNow = getUnsafe(AnalysisKeys.ACTIVITY_DATA).get(getUnsafe(AnalysisKeys.ANALYSIS_TIME));
Set<UUID> veryActiveNow = activityNow.getOrDefault("Very Active", new HashSet<>());
Set<UUID> activeNow = activityNow.getOrDefault("Active", new HashSet<>());
Set<UUID> regularNow = activityNow.getOrDefault("Regular", new HashSet<>());
return veryActiveNow.size() + activeNow.size() + regularNow.size();
});
} }
private void addTPSAverageSuppliers() { private void addTPSAverageSuppliers() {
@ -273,6 +314,8 @@ public class AnalysisContainer extends DataContainer {
.filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME)) .filterDataBetween(getUnsafe(AnalysisKeys.ANALYSIS_TIME_DAY_AGO), getUnsafe(AnalysisKeys.ANALYSIS_TIME))
); );
putSupplier(AnalysisKeys.PLAYERS_ONLINE_RESOLVER, () -> new PlayersOnlineResolver(getUnsafe(AnalysisKeys.TPS_MUTATOR)));
putSupplier(AnalysisKeys.TPS_SPIKE_MONTH, () -> getUnsafe(tpsMonth).lowTpsSpikeCount()); putSupplier(AnalysisKeys.TPS_SPIKE_MONTH, () -> getUnsafe(tpsMonth).lowTpsSpikeCount());
putSupplier(AnalysisKeys.AVG_TPS_MONTH, () -> getUnsafe(tpsMonth).averageTPS()); putSupplier(AnalysisKeys.AVG_TPS_MONTH, () -> getUnsafe(tpsMonth).averageTPS());
putSupplier(AnalysisKeys.AVG_CPU_MONTH, () -> getUnsafe(tpsMonth).averageCPU()); putSupplier(AnalysisKeys.AVG_CPU_MONTH, () -> getUnsafe(tpsMonth).averageCPU());

View File

@ -1,5 +1,7 @@
package com.djrapitops.plan.data.store.containers; package com.djrapitops.plan.data.store.containers;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
/** /**
* DataContainer about a Player. * DataContainer about a Player.
* <p> * <p>
@ -9,4 +11,9 @@ package com.djrapitops.plan.data.store.containers;
* @see com.djrapitops.plan.data.store.keys.PlayerKeys For Key objects. * @see com.djrapitops.plan.data.store.keys.PlayerKeys For Key objects.
*/ */
public class PlayerContainer extends DataContainer { public class PlayerContainer extends DataContainer {
public boolean playedBetween(long after, long before) {
return SessionsMutator.forContainer(this).playedBetween(after, before);
}
} }

View File

@ -4,6 +4,7 @@ import com.djrapitops.plan.data.store.Key;
import com.djrapitops.plan.data.store.PlaceholderKey; import com.djrapitops.plan.data.store.PlaceholderKey;
import com.djrapitops.plan.data.store.Type; import com.djrapitops.plan.data.store.Type;
import com.djrapitops.plan.data.store.mutators.PlayersMutator; import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.data.store.mutators.PlayersOnlineResolver;
import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.TPSMutator; import com.djrapitops.plan.data.store.mutators.TPSMutator;
@ -86,12 +87,12 @@ public class AnalysisKeys {
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_DAY = new PlaceholderKey<>(Integer.class, "playersNewAverageDay"); public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_DAY = new PlaceholderKey<>(Integer.class, "playersNewAverageDay");
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_WEEK = new PlaceholderKey<>(Integer.class, "playersNewAverageWeek"); public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_WEEK = new PlaceholderKey<>(Integer.class, "playersNewAverageWeek");
public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_MONTH = new PlaceholderKey<>(Integer.class, "playersNewAverageMonth"); public static final PlaceholderKey<Integer> AVG_PLAYERS_NEW_MONTH = new PlaceholderKey<>(Integer.class, "playersNewAverageMonth");
public static final PlaceholderKey<Integer> PLAYERS_STUCK_DAY = new PlaceholderKey<>(Integer.class, "playersStuckDay"); public static final PlaceholderKey<Integer> PLAYERS_RETAINED_DAY = new PlaceholderKey<>(Integer.class, "playersStuckDay");
public static final PlaceholderKey<String> PLAYERS_STUCK_DAY_PERC = new PlaceholderKey<>(String.class, "playersStuckPercDay"); public static final PlaceholderKey<String> PLAYERS_RETAINED_DAY_PERC = new PlaceholderKey<>(String.class, "playersStuckPercDay");
public static final PlaceholderKey<Integer> PLAYERS_STUCK_WEEK = new PlaceholderKey<>(Integer.class, "playersStuckWeek"); public static final PlaceholderKey<Integer> PLAYERS_RETAINED_WEEK = new PlaceholderKey<>(Integer.class, "playersStuckWeek");
public static final PlaceholderKey<String> PLAYERS_STUCK_WEEK_PERC = new PlaceholderKey<>(String.class, "playersStuckPercWeek"); public static final PlaceholderKey<String> PLAYERS_RETAINED_WEEK_PERC = new PlaceholderKey<>(String.class, "playersStuckPercWeek");
public static final PlaceholderKey<Integer> PLAYERS_STUCK_MONTH = new PlaceholderKey<>(Integer.class, "playersStuckMonth"); public static final PlaceholderKey<Integer> PLAYERS_RETAINED_MONTH = new PlaceholderKey<>(Integer.class, "playersStuckMonth");
public static final PlaceholderKey<String> PLAYERS_STUCK_MONTH_PERC = new PlaceholderKey<>(String.class, "playersStuckPercMonth"); public static final PlaceholderKey<String> PLAYERS_RETAINED_MONTH_PERC = new PlaceholderKey<>(String.class, "playersStuckPercMonth");
// //
public static final PlaceholderKey<Integer> TPS_SPIKE_MONTH = new PlaceholderKey<>(Integer.class, "tpsSpikeMonth"); public static final PlaceholderKey<Integer> TPS_SPIKE_MONTH = new PlaceholderKey<>(Integer.class, "tpsSpikeMonth");
public static final PlaceholderKey<Integer> TPS_SPIKE_WEEK = new PlaceholderKey<>(Integer.class, "tpsSpikeWeek"); public static final PlaceholderKey<Integer> TPS_SPIKE_WEEK = new PlaceholderKey<>(Integer.class, "tpsSpikeWeek");
@ -130,6 +131,7 @@ public class AnalysisKeys {
public static final Key<SessionsMutator> SESSIONS_MUTATOR = new Key<>(SessionsMutator.class, "SESSIONS_MUTATOR"); public static final Key<SessionsMutator> SESSIONS_MUTATOR = new Key<>(SessionsMutator.class, "SESSIONS_MUTATOR");
public static final Key<TPSMutator> TPS_MUTATOR = new Key<>(TPSMutator.class, "TPS_MUTATOR"); public static final Key<TPSMutator> TPS_MUTATOR = new Key<>(TPSMutator.class, "TPS_MUTATOR");
public static final Key<PlayersMutator> PLAYERS_MUTATOR = new Key<>(PlayersMutator.class, "PLAYERS_MUTATOR"); public static final Key<PlayersMutator> PLAYERS_MUTATOR = new Key<>(PlayersMutator.class, "PLAYERS_MUTATOR");
public static final Key<PlayersOnlineResolver> PLAYERS_ONLINE_RESOLVER = new Key<>(PlayersOnlineResolver.class, "PLAYERS_ONLINE_RESOLVER");
public static final Key<Long> PLAYTIME_TOTAL = new Key<>(Long.class, "PLAYTIME_TOTAL"); public static final Key<Long> PLAYTIME_TOTAL = new Key<>(Long.class, "PLAYTIME_TOTAL");
public static final Key<Long> ANALYSIS_TIME = new Key<>(Long.class, "ANALYSIS_TIME"); public static final Key<Long> ANALYSIS_TIME = new Key<>(Long.class, "ANALYSIS_TIME");
public static final Key<Long> ANALYSIS_TIME_DAY_AGO = new Key<>(Long.class, "ANALYSIS_TIME_DAY_AGO"); public static final Key<Long> ANALYSIS_TIME_DAY_AGO = new Key<>(Long.class, "ANALYSIS_TIME_DAY_AGO");

View File

@ -1,7 +1,6 @@
package com.djrapitops.plan.data.calculation; package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.data.store.containers.DataContainer; import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.FormatUtils; import com.djrapitops.plan.utilities.FormatUtils;
import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.TimeAmount;

View File

@ -1,6 +1,5 @@
package com.djrapitops.plan.data.store.mutators; package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.GeoInfo; import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.data.store.containers.DataContainer; import com.djrapitops.plan.data.store.containers.DataContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer; import com.djrapitops.plan.data.store.containers.PlayerContainer;
@ -8,6 +7,7 @@ import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.keys.ServerKeys; import com.djrapitops.plan.data.store.keys.ServerKeys;
import com.djrapitops.plan.data.store.keys.SessionKeys; import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.utilities.analysis.AnalysisUtils;
import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.TimeAmount;
import java.util.*; import java.util.*;
@ -54,6 +54,19 @@ public class PlayersMutator {
return this; return this;
} }
public PlayersMutator filterRetained(long after, long before) {
players = players.stream()
.filter(player -> {
long backLimit = Math.max(after, player.getValue(PlayerKeys.REGISTERED).orElse(0L));
long half = backLimit + ((before - backLimit) / 2L);
SessionsMutator firstHalf = SessionsMutator.forContainer(player);
SessionsMutator secondHalf = SessionsMutator.copyOf(firstHalf);
return !firstHalf.playedBetween(backLimit, half) && !secondHalf.playedBetween(half, before);
})
.collect(Collectors.toList());
return this;
}
public List<PlayerContainer> all() { public List<PlayerContainer> all() {
return players; return players;
} }
@ -117,4 +130,59 @@ public class PlayersMutator {
} }
return total / numberOfDays; return total / numberOfDays;
} }
/**
* 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) {
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.ms();
long half = registered + (TimeAmount.MONTH.ms() / 2L);
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))
.collect(Collectors.toList());
List<RetentionData> notRetained = notRetainedAfterMonth.stream()
.map(player -> new RetentionData(player, onlineResolver))
.collect(Collectors.toList());
RetentionData avgRetained = AnalysisUtils.average(retained);
RetentionData avgNotRetained = AnalysisUtils.average(notRetained);
List<PlayerContainer> toBeRetained = new ArrayList<>();
for (PlayerContainer player : compareTo) {
RetentionData retentionData = new RetentionData(player, onlineResolver);
if (retentionData.distance(avgRetained) < retentionData.distance(avgNotRetained)) {
toBeRetained.add(player);
}
}
return new PlayersMutator(toBeRetained);
}
} }

View File

@ -0,0 +1,35 @@
package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.utilities.html.graphs.line.Point;
import java.util.*;
/**
* Resolves dates into players online numbers with a help of a NavigableMap.
* <p>
* Time Complexity of O(n / 2) with the use of TreeMap.
*
* @author Rsl1122
*/
public class PlayersOnlineResolver {
private final NavigableMap<Long, Integer> onlineNumberMap;
public PlayersOnlineResolver(TPSMutator mutator) {
List<Point> points = mutator.playersOnlinePoints();
onlineNumberMap = new TreeMap<>();
for (Point point : points) {
double date = point.getX();
double value = point.getY();
onlineNumberMap.put((long) date, (int) value);
}
}
public Optional<Integer> getOnlineOn(long date) {
Map.Entry<Long, Integer> entry = onlineNumberMap.floorEntry(date);
if (entry == null) {
return Optional.empty();
}
return Optional.of(entry.getValue());
}
}

View File

@ -0,0 +1,77 @@
/*
* Licence is provided in the jar as license.yml also here:
* https://github.com/Rsl1122/Plan-PlayerAnalytics/blob/master/Plan/src/main/resources/license.yml
*/
package com.djrapitops.plan.data.store.mutators;
import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plugin.api.TimeAmount;
import com.google.common.base.Objects;
import java.util.Optional;
/**
* Utility class for player retention calculations.
* <p>
* Previously known as StickyData.
*
* @author Rsl1122
*/
public class RetentionData {
private final double activityIndex;
private double onlineOnJoin;
@Deprecated
public RetentionData(PlayerProfile player) {
activityIndex = player.getActivityIndex(player.getRegistered() + TimeAmount.DAY.ms()).getValue();
}
public RetentionData(double activityIndex, double onlineOnJoin) {
this.activityIndex = activityIndex;
this.onlineOnJoin = onlineOnJoin;
}
public RetentionData(PlayerContainer player, PlayersOnlineResolver onlineOnJoin) {
Optional<Long> registeredValue = player.getValue(PlayerKeys.REGISTERED);
activityIndex = registeredValue
.map(registered -> new ActivityIndex(player, registered + TimeAmount.DAY.ms()).getValue())
.orElse(0.0);
this.onlineOnJoin = registeredValue
.map(registered -> onlineOnJoin.getOnlineOn(registered).orElse(-1))
.orElse(0);
}
public double distance(RetentionData data) {
double num = 0;
num += Math.abs(data.activityIndex - activityIndex) * 2.0;
num += data.onlineOnJoin != -1 && onlineOnJoin != -1
? Math.abs(data.onlineOnJoin - onlineOnJoin) / 10.0
: 0;
return num;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RetentionData that = (RetentionData) o;
return Double.compare(that.activityIndex, activityIndex) == 0 &&
Objects.equal(onlineOnJoin, that.onlineOnJoin);
}
@Override
public int hashCode() {
return Objects.hashCode(activityIndex, onlineOnJoin);
}
public double getOnlineOnJoin() {
return onlineOnJoin;
}
public double getActivityIndex() {
return activityIndex;
}
}

View File

@ -11,6 +11,7 @@ import com.djrapitops.plan.utilities.analysis.MathUtils;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -42,8 +43,7 @@ public class SessionsMutator {
public SessionsMutator filterSessionsBetween(long after, long before) { public SessionsMutator filterSessionsBetween(long after, long before) {
sessions = sessions.stream() sessions = sessions.stream()
.filter(session -> after <= session.getValue(SessionKeys.END).orElse(System.currentTimeMillis()) .filter(getBetweenPredicate(after, before))
&& session.getUnsafe(SessionKeys.START) <= before)
.collect(Collectors.toList()); .collect(Collectors.toList());
return this; return this;
} }
@ -164,4 +164,16 @@ public class SessionsMutator {
public int toPlayerKillCount() { public int toPlayerKillCount() {
return toPlayerKillList().size(); return toPlayerKillList().size();
} }
public boolean playedBetween(long after, long before) {
return sessions.stream().anyMatch(getBetweenPredicate(after, before));
}
private Predicate<Session> getBetweenPredicate(long after, long before) {
return session -> {
Long start = session.getUnsafe(SessionKeys.START);
Long end = session.getValue(SessionKeys.END).orElse(System.currentTimeMillis());
return (after <= start && start <= before) || (after <= end && end <= before);
};
}
} }

View File

@ -25,9 +25,7 @@ public class Formatters {
} }
public static Formatter<Long> yearLongValue() { public static Formatter<Long> yearLongValue() {
return date -> { return date -> date > 0 ? FormatUtils.formatTimeStampYear(date) : "-";
return date > 0 ? FormatUtils.formatTimeStampYear(date) : "-";
};
} }
public static Formatter<DateHolder> day() { public static Formatter<DateHolder> day() {
@ -66,4 +64,8 @@ public class Formatters {
return day.get(Calendar.DAY_OF_YEAR); return day.get(Calendar.DAY_OF_YEAR);
}; };
} }
public static Formatter<Double> percentage() {
return value -> value >= 0 ? FormatUtils.cutDecimals(value * 100.0) + "%" : "-";
}
} }

View File

@ -62,8 +62,8 @@ public class AnalysisPage extends Page {
// AVG_PLAYERS, AVG_PLAYERS_DAY, AVG_PLAYERS_WEEK, // AVG_PLAYERS, AVG_PLAYERS_DAY, AVG_PLAYERS_WEEK,
// AVG_PLAYERS_MONTH, AVG_PLAYERS_NEW, AVG_PLAYERS_NEW_DAY, // AVG_PLAYERS_MONTH, AVG_PLAYERS_NEW, AVG_PLAYERS_NEW_DAY,
// AVG_PLAYERS_NEW_WEEK, AVG_PLAYERS_NEW_MONTH, PLAYERS_STUCK_DAY, // AVG_PLAYERS_NEW_WEEK, AVG_PLAYERS_NEW_MONTH, PLAYERS_STUCK_DAY,
// PLAYERS_STUCK_DAY_PERC, PLAYERS_STUCK_WEEK, PLAYERS_STUCK_WEEK_PERC, // PLAYERS_STUCK_DAY_PERC, PLAYERS_RETAINED_WEEK, PLAYERS_RETAINED_WEEK_PERC,
// PLAYERS_STUCK_MONTH, PLAYERS_STUCK_MONTH_PERC, // PLAYERS_RETAINED_MONTH, PLAYERS_RETAINED_MONTH_PERC,
// //
// TPS_SPIKE_MONTH, TPS_SPIKE_WEEK, TPS_SPIKE_DAY, // TPS_SPIKE_MONTH, TPS_SPIKE_WEEK, TPS_SPIKE_DAY,
// AVG_TPS_MONTH, AVG_TPS_WEEK, AVG_TPS_DAY, // AVG_TPS_MONTH, AVG_TPS_WEEK, AVG_TPS_DAY,

View File

@ -6,12 +6,12 @@ package com.djrapitops.plan.system.webserver.pages.parsing;
import com.djrapitops.plan.api.exceptions.ParseException; import com.djrapitops.plan.api.exceptions.ParseException;
import com.djrapitops.plan.data.Actions; import com.djrapitops.plan.data.Actions;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.Action; import com.djrapitops.plan.data.container.Action;
import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.containers.PerServerContainer; import com.djrapitops.plan.data.store.containers.PerServerContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer; import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys; import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.PerServerDataMutator; import com.djrapitops.plan.data.store.mutators.PerServerDataMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatter; import com.djrapitops.plan.data.store.mutators.formatting.Formatter;

View File

@ -1,10 +1,10 @@
package com.djrapitops.plan.utilities.analysis; package com.djrapitops.plan.utilities.analysis;
import com.djrapitops.plan.data.PlayerProfile; import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.container.StickyData;
import com.djrapitops.plan.data.store.keys.SessionKeys; import com.djrapitops.plan.data.store.keys.SessionKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.RetentionData;
import com.djrapitops.plan.data.time.GMTimes; import com.djrapitops.plan.data.time.GMTimes;
import com.djrapitops.plan.data.time.WorldTimes; import com.djrapitops.plan.data.time.WorldTimes;
import com.djrapitops.plan.system.settings.WorldAliasSettings; import com.djrapitops.plan.system.settings.WorldAliasSettings;
@ -243,24 +243,21 @@ public class AnalysisUtils {
return gmTimesPerAlias; return gmTimesPerAlias;
} }
public static StickyData average(Collection<StickyData> stuck) { public static RetentionData average(Collection<RetentionData> stuck) {
int size = stuck.size(); int size = stuck.size();
double totalIndex = 0.0; double totalIndex = 0.0;
double totalMsgsSent = 0.0;
double totalPlayersOnline = 0.0; double totalPlayersOnline = 0.0;
for (StickyData stickyData : stuck) { for (RetentionData retentionData : stuck) {
totalIndex += stickyData.getActivityIndex(); totalIndex += retentionData.getActivityIndex();
totalMsgsSent += stickyData.getMessagesSent(); totalPlayersOnline += retentionData.getOnlineOnJoin();
totalPlayersOnline += stickyData.getOnlineOnJoin();
} }
double averageIndex = totalIndex / (double) size; double averageIndex = totalIndex / (double) size;
double averageMessagesSent = totalMsgsSent / (double) size;
double averagePlayersOnline = totalPlayersOnline / (double) size; double averagePlayersOnline = totalPlayersOnline / (double) size;
return new StickyData(averageIndex, averageMessagesSent, averagePlayersOnline); return new RetentionData(averageIndex, averagePlayersOnline);
} }
public static String getLongestWorldPlayed(Session session) { public static String getLongestWorldPlayed(Session session) {

View File

@ -4,7 +4,7 @@
*/ */
package com.djrapitops.plan.utilities.html.graphs; package com.djrapitops.plan.utilities.html.graphs;
import com.djrapitops.plan.data.calculation.ActivityIndex; import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.PlayersMutator; import com.djrapitops.plan.data.store.mutators.PlayersMutator;
import com.djrapitops.plan.system.settings.theme.Theme; import com.djrapitops.plan.system.settings.theme.Theme;
import com.djrapitops.plan.system.settings.theme.ThemeVal; import com.djrapitops.plan.system.settings.theme.ThemeVal;

View File

@ -4,7 +4,7 @@
*/ */
package com.djrapitops.plan.utilities.html.graphs.pie; package com.djrapitops.plan.utilities.html.graphs.pie;
import com.djrapitops.plan.data.calculation.ActivityIndex; import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.system.settings.theme.Theme; import com.djrapitops.plan.system.settings.theme.Theme;
import com.djrapitops.plan.system.settings.theme.ThemeVal; import com.djrapitops.plan.system.settings.theme.ThemeVal;

View File

@ -1,11 +1,11 @@
package com.djrapitops.plan.utilities.html.tables; package com.djrapitops.plan.utilities.html.tables;
import com.djrapitops.plan.api.PlanAPI; import com.djrapitops.plan.api.PlanAPI;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.GeoInfo; import com.djrapitops.plan.data.container.GeoInfo;
import com.djrapitops.plan.data.element.TableContainer; import com.djrapitops.plan.data.element.TableContainer;
import com.djrapitops.plan.data.store.containers.PlayerContainer; import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys; import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.GeoInfoMutator; import com.djrapitops.plan.data.store.mutators.GeoInfoMutator;
import com.djrapitops.plan.data.store.mutators.SessionsMutator; import com.djrapitops.plan.data.store.mutators.SessionsMutator;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;

View File

@ -2,7 +2,7 @@ package com.djrapitops.plan.utilities.html.tables;
import com.djrapitops.plan.api.PlanAPI; import com.djrapitops.plan.api.PlanAPI;
import com.djrapitops.plan.data.PlayerProfile; import com.djrapitops.plan.data.PlayerProfile;
import com.djrapitops.plan.data.calculation.ActivityIndex; import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.data.store.mutators.formatting.Formatters; import com.djrapitops.plan.data.store.mutators.formatting.Formatters;
import com.djrapitops.plan.system.info.server.ServerInfo; import com.djrapitops.plan.system.info.server.ServerInfo;
import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.Settings;

View File

@ -3,6 +3,7 @@ package com.djrapitops.plan.data.calculation;
import com.djrapitops.plan.data.container.Session; import com.djrapitops.plan.data.container.Session;
import com.djrapitops.plan.data.store.containers.PlayerContainer; import com.djrapitops.plan.data.store.containers.PlayerContainer;
import com.djrapitops.plan.data.store.keys.PlayerKeys; import com.djrapitops.plan.data.store.keys.PlayerKeys;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plugin.api.TimeAmount; import com.djrapitops.plugin.api.TimeAmount;
import org.junit.BeforeClass; import org.junit.BeforeClass;

View File

@ -4,8 +4,8 @@
*/ */
package com.djrapitops.plan.utilities.html.graphs; package com.djrapitops.plan.utilities.html.graphs;
import com.djrapitops.plan.data.calculation.ActivityIndex;
import com.djrapitops.plan.data.container.TPS; import com.djrapitops.plan.data.container.TPS;
import com.djrapitops.plan.data.store.mutators.ActivityIndex;
import com.djrapitops.plan.system.settings.Settings; import com.djrapitops.plan.system.settings.Settings;
import com.djrapitops.plan.utilities.html.graphs.line.*; import com.djrapitops.plan.utilities.html.graphs.line.*;
import com.djrapitops.plan.utilities.html.graphs.stack.AbstractStackGraph; import com.djrapitops.plan.utilities.html.graphs.stack.AbstractStackGraph;