diff --git a/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/analysis/NetworkActivityIndexQueries.java b/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/analysis/NetworkActivityIndexQueries.java
new file mode 100644
index 000000000..dd7d58417
--- /dev/null
+++ b/Plan/common/src/main/java/com/djrapitops/plan/db/access/queries/analysis/NetworkActivityIndexQueries.java
@@ -0,0 +1,415 @@
+/*
+ * 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 .
+ */
+package com.djrapitops.plan.db.access.queries.analysis;
+
+import com.djrapitops.plan.data.store.mutators.ActivityIndex;
+import com.djrapitops.plan.db.access.Query;
+import com.djrapitops.plan.db.access.QueryStatement;
+import com.djrapitops.plan.db.sql.tables.SessionsTable;
+import com.djrapitops.plan.db.sql.tables.UsersTable;
+
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static com.djrapitops.plan.db.sql.parsing.Sql.*;
+
+/**
+ * Queries for Activity Index that attempts to gain insight into player activity levels.
+ *
+ * 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}
+ *
+ * @author Rsl1122
+ */
+public class NetworkActivityIndexQueries {
+
+ private NetworkActivityIndexQueries() {
+ // Static method class
+ }
+
+ public static Query fetchRegularPlayerCount(long date, long playtimeThreshold) {
+ return fetchActivityGroupCount(date, playtimeThreshold, ActivityIndex.REGULAR, 5.1);
+ }
+
+ private static String selectActivityIndexSQL() {
+ String selectActivePlaytimeSQL = SELECT +
+ SessionsTable.USER_UUID +
+ ",SUM(" +
+ SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME +
+ ") as active_playtime" +
+ FROM + SessionsTable.TABLE_NAME +
+ WHERE + SessionsTable.SESSION_START + ">=?" +
+ AND + SessionsTable.SESSION_END + "<=?" +
+ GROUP_BY + SessionsTable.USER_UUID;
+
+ String selectThreeWeeks = selectActivePlaytimeSQL + UNION + selectActivePlaytimeSQL + UNION + selectActivePlaytimeSQL;
+
+ return SELECT +
+ "5.0 - 5.0 * AVG(1 / (?/2 * (q1.active_playtime/?) +1)) as activity_index," +
+ "q1." + SessionsTable.USER_UUID +
+ FROM + '(' + selectThreeWeeks + ") q1" +
+ GROUP_BY + "q1." + SessionsTable.USER_UUID;
+ }
+
+ private static void setSelectActivityIndexSQLParameters(PreparedStatement statement, int index, long playtimeThreshold, long date) throws SQLException {
+ statement.setDouble(index, Math.PI);
+ statement.setLong(index + 1, playtimeThreshold);
+
+ statement.setLong(index + 2, date - TimeUnit.DAYS.toMillis(7L));
+ statement.setLong(index + 3, date);
+ statement.setLong(index + 4, date - TimeUnit.DAYS.toMillis(14L));
+ statement.setLong(index + 5, date - TimeUnit.DAYS.toMillis(7L));
+ statement.setLong(index + 6, date - TimeUnit.DAYS.toMillis(21L));
+ statement.setLong(index + 7, date - TimeUnit.DAYS.toMillis(14L));
+ }
+
+ public static Query fetchActivityGroupCount(long date, long playtimeThreshold, double above, double below) {
+ String selectActivityIndex = selectActivityIndexSQL();
+
+ String selectIndexes = SELECT + "COALESCE(activity_index, 0) as activity_index" +
+ FROM + UsersTable.TABLE_NAME + " u" +
+ LEFT_JOIN + '(' + selectActivityIndex + ") q2 on q2." + SessionsTable.USER_UUID + "=u." + UsersTable.USER_UUID +
+ WHERE + "u." + UsersTable.REGISTERED + "<=?";
+
+ String selectActivePlayerCount = SELECT + "COUNT(1) as count" +
+ FROM + '(' + selectIndexes + ") i" +
+ WHERE + "i.activity_index>=?" +
+ AND + "i.activity_index";
+
+ return new QueryStatement(selectActivePlayerCount) {
+ @Override
+ public void prepare(PreparedStatement statement) throws SQLException {
+ setSelectActivityIndexSQLParameters(statement, 1, playtimeThreshold, date);
+ statement.setLong(9, date);
+ statement.setDouble(10, above);
+ statement.setDouble(11, below);
+ }
+
+ @Override
+ public Integer processResults(ResultSet set) throws SQLException {
+ return set.next() ? set.getInt("count") : 0;
+ }
+ };
+ }
+
+ public static Query