From b2a02a3576ffa37a873b85ef49c437c4163ae05d Mon Sep 17 00:00:00 2001 From: Rsl1122 Date: Sun, 7 Jul 2019 15:27:05 +0300 Subject: [PATCH] Rewrote ActivityIndex calculation (not tested) --- .../plan/api/data/PlayerContainer.java | 2 +- .../command/commands/QInspectCommand.java | 6 +- .../store/containers/PlayerContainer.java | 4 +- .../data/store/mutators/ActivityIndex.java | 122 +++++++++--------- .../data/store/mutators/PlayersMutator.java | 10 +- .../data/store/mutators/RetentionData.java | 6 +- .../system/json/PlayersTableJSONParser.java | 2 +- .../utilities/html/pages/InspectPage.java | 4 +- .../utilities/html/tables/PlayersTable.java | 2 +- 9 files changed, 73 insertions(+), 85 deletions(-) diff --git a/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java index 76d0c728e..93462ba5c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/api/data/PlayerContainer.java @@ -39,7 +39,7 @@ public class PlayerContainer { } public double getActivityIndex(long date, long playtimeMsThreshold, int loginThreshold) { - return container.getActivityIndex(date, playtimeMsThreshold, loginThreshold).getValue(); + return container.getActivityIndex(date, playtimeMsThreshold).getValue(); } public boolean playedBetween(long after, long before) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/command/commands/QInspectCommand.java b/Plan/common/src/main/java/com/djrapitops/plan/command/commands/QInspectCommand.java index 1e8786ae2..40c02b50c 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/command/commands/QInspectCommand.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/command/commands/QInspectCommand.java @@ -143,11 +143,7 @@ public class QInspectCommand extends CommandNode { String playerName = player.getValue(PlayerKeys.NAME).orElse(locale.getString(GenericLang.UNKNOWN)); - ActivityIndex activityIndex = player.getActivityIndex( - now, - config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD), - config.get(TimeSettings.ACTIVE_LOGIN_THRESHOLD) - ); + ActivityIndex activityIndex = player.getActivityIndex(now, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD)); Long registered = player.getValue(PlayerKeys.REGISTERED).orElse(0L); Long lastSeen = player.getValue(PlayerKeys.LAST_SEEN).orElse(0L); List geoInfo = player.getValue(PlayerKeys.GEO_INFO).orElse(new ArrayList<>()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/PlayerContainer.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/PlayerContainer.java index d8423890c..2def7d4f7 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/PlayerContainer.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/containers/PlayerContainer.java @@ -38,8 +38,8 @@ public class PlayerContainer extends DynamicDataContainer { activityIndexCache = new HashMap<>(); } - public ActivityIndex getActivityIndex(long date, long playtimeMsThreshold, int loginThreshold) { - return activityIndexCache.computeIfAbsent(date, time -> new ActivityIndex(this, time, playtimeMsThreshold, loginThreshold)); + public ActivityIndex getActivityIndex(long date, long playtimeMsThreshold) { + return activityIndexCache.computeIfAbsent(date, time -> new ActivityIndex(this, time, playtimeMsThreshold)); } public boolean playedBetween(long after, long before) { diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/ActivityIndex.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/ActivityIndex.java index dd3ca8485..6f2466aaa 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/ActivityIndex.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/ActivityIndex.java @@ -25,21 +25,56 @@ import com.djrapitops.plugin.api.TimeAmount; import java.util.List; import java.util.Optional; +/** + * Represents Activity index of a player at a certain date. + *

+ * Old formula for activity index was not linear and difficult to turn into a query due to conditional multipliers. + * Thus a new formula was written. + *

+ * {@code T} - Time played after someone is considered active on a particular week + * {@code t1, t2, t3} - Time played that week + *

+ * Activity index takes into account last 3 weeks. + *

+ * Activity for a single week is calculated using {@code A(t) = (1 / (pi/2 * (t/T) + 1))}. + * A(t) is based on function f(x) = 1 / (x + 1), which has property f(0) = 1, decreasing from there, but not in a straight line. + * You can see the function plotted here https://www.wolframalpha.com/input/?i=1+%2F+(x%2B1)+from+-1+to+2 + *

+ * To fine tune the curve pi/2 is used since it felt like a good curve. + *

+ * Activity index A is calculated by using the formula: + * {@code A = 5 - 5 * [A(t1) + A(t2) + A(t3)] / 3} + *

+ * Plot for A and limits + * https://www.wolframalpha.com/input/?i=plot+y+%3D+5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1))+and+y+%3D1+and+y+%3D+2+and+y+%3D+3+and+y+%3D+3.75+from+-0.5+to+3 + *

+ * New Limits for A would thus be + * {@code < 1: Inactive} + * {@code > 1: Irregular} + * {@code > 2: Regular} + * {@code > 3: Active} + * {@code > 3.75: Very Active} + */ public class ActivityIndex { private final double value; + private final long date; - private final long playtimeMsThreshold; - private final int loginThreshold; + private long playtimeMsThreshold; public ActivityIndex( DataContainer container, long date, - long playtimeMsThreshold, int loginThreshold + long playtimeMsThreshold ) { this.playtimeMsThreshold = playtimeMsThreshold; - this.loginThreshold = loginThreshold; value = calculate(container, date); + this.date = date; + } + + public ActivityIndex(double value, long date) { + this.value = value; + this.date = date; } public static String[] getGroups() { @@ -52,9 +87,6 @@ public class ActivityIndex { long twoWeeksAgo = date - 2L * week; long threeWeeksAgo = date - 3L * week; - long activePlayThreshold = playtimeMsThreshold; - int activeLoginThreshold = loginThreshold; - Optional> sessionsValue = container.getValue(PlayerKeys.SESSIONS); if (!sessionsValue.isPresent()) { return 0.0; @@ -68,75 +100,39 @@ public class ActivityIndex { SessionsMutator weekTwo = sessionsMutator.filterSessionsBetween(twoWeeksAgo, weekAgo); SessionsMutator weekThree = sessionsMutator.filterSessionsBetween(threeWeeksAgo, twoWeeksAgo); - // Playtime per week multipliers, max out to avoid too high values. - double max = 4.0; + long playtime1 = weekOne.toActivePlaytime(); + long playtime2 = weekTwo.toActivePlaytime(); + long playtime3 = weekThree.toActivePlaytime(); - long playtimeWeek = weekOne.toActivePlaytime(); - double weekPlay = playtimeWeek * 1.0 / activePlayThreshold; - if (weekPlay > max) { - weekPlay = max; - } - long playtimeWeek2 = weekTwo.toActivePlaytime(); - double week2Play = playtimeWeek2 * 1.0 / activePlayThreshold; - if (week2Play > max) { - week2Play = max; - } - long playtimeWeek3 = weekThree.toActivePlaytime(); - double week3Play = playtimeWeek3 * 1.0 / activePlayThreshold; - if (week3Play > max) { - week3Play = max; - } + double A1 = 1.0 / (Math.PI / 2.0 * (playtime1 * 1.0 / playtimeMsThreshold) + 1.0); + double A2 = 1.0 / (Math.PI / 2.0 * (playtime2 * 1.0 / playtimeMsThreshold) + 1.0); + double A3 = 1.0 / (Math.PI / 2.0 * (playtime3 * 1.0 / playtimeMsThreshold) + 1.0); - double playtimeMultiplier = 1.0; - if (playtimeWeek + playtimeWeek2 + playtimeWeek3 > activePlayThreshold * 3.0) { - playtimeMultiplier = 1.25; - } + double average = (A1 + A2 + A3) / 3.0; - // Reduce the harshness for new players and players who have had a vacation - if (weekPlay > 1 && week3Play > 1 && week2Play == 0.0) { - week2Play = 0.5; - } - if (weekPlay > 1 && week2Play == 0.0) { - week2Play = 0.6; - } - if (weekPlay > 1 && week3Play == 0.0) { - week3Play = 0.75; - } - - double playAvg = (weekPlay + week2Play + week3Play) / 3.0; - - double weekLogin = weekOne.count() >= activeLoginThreshold ? 1.0 : 0.5; - double week2Login = weekTwo.count() >= activeLoginThreshold ? 1.0 : 0.5; - double week3Login = weekThree.count() >= activeLoginThreshold ? 1.0 : 0.5; - - double loginMultiplier = 1.0; - double loginTotal = weekLogin + week2Login + week3Login; - double loginAvg = loginTotal / 3.0; - - if (loginTotal <= 2.0) { - // Reduce index for players that have not logged in the threshold amount for 2 weeks - loginMultiplier = 0.75; - } - - return playAvg * loginAvg * loginMultiplier * playtimeMultiplier; + return 5.0 - (5.0 * average); } public double getValue() { return value; } + public long getDate() { + return date; + } + public String getFormattedValue(Formatter formatter) { return formatter.apply(value); } public String getGroup() { - if (value >= 3.5) { + if (value >= 3.75) { return "Very Active"; - } else if (value >= 1.75) { + } else if (value >= 3) { return "Active"; - } else if (value >= 1.0) { + } else if (value >= 2) { return "Regular"; - } else if (value >= 0.5) { + } else if (value >= 1) { return "Irregular"; } else { return "Inactive"; @@ -144,13 +140,13 @@ public class ActivityIndex { } public String getColor() { - if (value >= 3.5) { + if (value >= 3.75) { return "green"; - } else if (value >= 1.75) { + } else if (value >= 3) { return "green"; - } else if (value >= 1.0) { + } else if (value >= 2) { return "lime"; - } else if (value >= 0.5) { + } else if (value >= 1) { return "amber"; } else { return "blue-gray"; diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/PlayersMutator.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/PlayersMutator.java index 1b1d60b36..48752f77f 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/PlayersMutator.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/PlayersMutator.java @@ -87,7 +87,7 @@ public class PlayersMutator { } public PlayersMutator filterActive(long date, long msThreshold, int loginThreshold, double limit) { - return filterBy(player -> player.getActivityIndex(date, msThreshold, loginThreshold).getValue() >= limit); + return filterBy(player -> player.getActivityIndex(date, msThreshold).getValue() >= limit); } public PlayersMutator filterPlayedOnServer(UUID serverUUID) { @@ -148,7 +148,7 @@ public class PlayersMutator { if (player.getValue(PlayerKeys.REGISTERED).orElse(0L) > time) { continue; } - ActivityIndex activityIndex = player.getActivityIndex(time, msThreshold, loginThreshold); + ActivityIndex activityIndex = player.getActivityIndex(time, msThreshold); String activityGroup = activityIndex.getGroup(); Set uuids = map.getOrDefault(activityGroup, new HashSet<>()); @@ -227,10 +227,10 @@ public class PlayersMutator { } List retained = retainedAfterMonth.stream() - .map(player -> new RetentionData(player, onlineResolver, activityMsThreshold, activityLoginThreshold)) + .map(player -> new RetentionData(player, onlineResolver, activityMsThreshold)) .collect(Collectors.toList()); List notRetained = notRetainedAfterMonth.stream() - .map(player -> new RetentionData(player, onlineResolver, activityMsThreshold, activityLoginThreshold)) + .map(player -> new RetentionData(player, onlineResolver, activityMsThreshold)) .collect(Collectors.toList()); RetentionData avgRetained = RetentionData.average(retained); @@ -238,7 +238,7 @@ public class PlayersMutator { List toBeRetained = new ArrayList<>(); for (PlayerContainer player : compareTo) { - RetentionData retentionData = new RetentionData(player, onlineResolver, activityMsThreshold, activityLoginThreshold); + RetentionData retentionData = new RetentionData(player, onlineResolver, activityMsThreshold); if (retentionData.distance(avgRetained) < retentionData.distance(avgNotRetained)) { toBeRetained.add(player); } diff --git a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/RetentionData.java b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/RetentionData.java index f3b46c812..f3fa8f6c3 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/RetentionData.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/data/store/mutators/RetentionData.java @@ -60,16 +60,14 @@ public class RetentionData { public RetentionData( PlayerContainer player, PlayersOnlineResolver onlineOnJoin, - long activityMsThreshold, - int activityLoginThreshold + long activityMsThreshold ) { Optional registeredValue = player.getValue(PlayerKeys.REGISTERED); activityIndex = registeredValue .map(registered -> new ActivityIndex( player, registered + TimeUnit.DAYS.toMillis(1L), - activityMsThreshold, - activityLoginThreshold + activityMsThreshold ).getValue()) .orElse(0.0); this.onlineOnJoin = registeredValue diff --git a/Plan/common/src/main/java/com/djrapitops/plan/system/json/PlayersTableJSONParser.java b/Plan/common/src/main/java/com/djrapitops/plan/system/json/PlayersTableJSONParser.java index ce32bfa53..d5c9527ba 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/system/json/PlayersTableJSONParser.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/system/json/PlayersTableJSONParser.java @@ -149,7 +149,7 @@ public class PlayersTableJSONParser { long registered = player.getValue(PlayerKeys.REGISTERED).orElse(0L); long lastSeen = sessionsMutator.toLastSeen(); - ActivityIndex activityIndex = player.getActivityIndex(now, activeMsThreshold, activeLoginThreshold); + ActivityIndex activityIndex = player.getActivityIndex(now, activeMsThreshold); boolean isBanned = player.getValue(PlayerKeys.BANNED).orElse(false); String activityString = activityIndex.getFormattedValue(decimalFormatter) + (isBanned ? " (Banned)" : " (" + activityIndex.getGroup() + ")"); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/pages/InspectPage.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/pages/InspectPage.java index 5e07099c6..8abca4be6 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/pages/InspectPage.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/pages/InspectPage.java @@ -239,9 +239,7 @@ public class InspectPage implements Page { pvpAndPve(replacer, sessionsMutator, weekSessionsMutator, monthSessionsMutator); - ActivityIndex activityIndex = player.getActivityIndex( - now, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD), config.get(TimeSettings.ACTIVE_LOGIN_THRESHOLD) - ); + ActivityIndex activityIndex = player.getActivityIndex(now, config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD)); replacer.put("activityIndexNumber", activityIndex.getFormattedValue(decimalFormatter)); replacer.put("activityIndexColor", activityIndex.getColor()); diff --git a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/tables/PlayersTable.java b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/tables/PlayersTable.java index 4e2708ec6..0262b49af 100644 --- a/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/tables/PlayersTable.java +++ b/Plan/common/src/main/java/com/djrapitops/plan/utilities/html/tables/PlayersTable.java @@ -100,7 +100,7 @@ class PlayersTable extends TableContainer { long registered = player.getValue(PlayerKeys.REGISTERED).orElse(0L); long lastSeen = sessionsMutator.toLastSeen(); - ActivityIndex activityIndex = player.getActivityIndex(now, activeMsThreshold, activeLoginThreshold); + ActivityIndex activityIndex = player.getActivityIndex(now, activeMsThreshold); boolean isBanned = player.getValue(PlayerKeys.BANNED).orElse(false); String activityString = activityIndex.getFormattedValue(decimalFormatter) + (isBanned ? " (Banned)" : " (" + activityIndex.getGroup() + ")");