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

212 lines
7.6 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.domain.mutators;
import com.djrapitops.plan.delivery.domain.container.DataContainer;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Represents Activity index of a player at a certain date.
* <p>
* 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.
* <p>
* {@code T} - Time played after someone is considered active on a particular week
* {@code t1, t2, t3} - Time played that week
* <p>
* Activity index takes into account last 3 weeks.
* <p>
* 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 <a href="https://www.wolframalpha.com/input/?i=1+%2F+(x%2B1)+from+-1+to+2">here</a>
* <p>
* To fine tune the curve pi/2 is used since it felt like a good curve.
* <p>
* Activity index A is calculated by using the formula:
* {@code A = 5 - 5 * [A(t1) + A(t2) + A(t3)] / 3}
* <p>
* <a href="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">
* Plot for A and limits
* </a>
* <p>
* 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 {
public static final double VERY_ACTIVE = 3.75;
public static final double ACTIVE = 3.0;
public static final double REGULAR = 2.0;
public static final double IRREGULAR = 1.0;
private final double value;
private final long date;
private long playtimeMsThreshold;
public ActivityIndex(DataContainer container, long date, long playtimeMsThreshold) {
this.playtimeMsThreshold = playtimeMsThreshold;
this.date = date;
value = calculate(container);
}
public ActivityIndex(List<FinishedSession> sessions, long date, long playtimeMsThreshold) {
this.playtimeMsThreshold = playtimeMsThreshold;
this.date = date;
value = calculate(new SessionsMutator(sessions));
}
public ActivityIndex(double value, long date) {
this.value = value;
this.date = date;
}
public static String[] getDefaultGroups() {
return getGroups(null);
}
public static String[] getDefaultGroupLangKeys() {
return new String[]{
HtmlLang.INDEX_VERY_ACTIVE.getKey(),
HtmlLang.INDEX_ACTIVE.getKey(),
HtmlLang.INDEX_REGULAR.getKey(),
HtmlLang.INDEX_IRREGULAR.getKey(),
HtmlLang.INDEX_INACTIVE.getKey()
};
}
public static String[] getGroups(Locale locale) {
if (locale == null) {
return new String[]{
HtmlLang.INDEX_VERY_ACTIVE.getDefault(),
HtmlLang.INDEX_ACTIVE.getDefault(),
HtmlLang.INDEX_REGULAR.getDefault(),
HtmlLang.INDEX_IRREGULAR.getDefault(),
HtmlLang.INDEX_INACTIVE.getDefault()
};
}
return new String[]{
locale.getString(HtmlLang.INDEX_VERY_ACTIVE),
locale.getString(HtmlLang.INDEX_ACTIVE),
locale.getString(HtmlLang.INDEX_REGULAR),
locale.getString(HtmlLang.INDEX_IRREGULAR),
locale.getString(HtmlLang.INDEX_INACTIVE)
};
}
private double calculate(DataContainer container) {
return calculate(SessionsMutator.forContainer(container));
}
private double calculate(SessionsMutator sessionsMutator) {
if (sessionsMutator.all().isEmpty()) {
return 0.0;
}
long week = TimeUnit.DAYS.toMillis(7L);
long weekAgo = date - week;
long twoWeeksAgo = date - 2L * week;
long threeWeeksAgo = date - 3L * week;
SessionsMutator weekOne = sessionsMutator.filterSessionsBetween(weekAgo, date);
SessionsMutator weekTwo = sessionsMutator.filterSessionsBetween(twoWeeksAgo, weekAgo);
SessionsMutator weekThree = sessionsMutator.filterSessionsBetween(threeWeeksAgo, twoWeeksAgo);
double playtime1 = weekOne.toActivePlaytime();
double playtime2 = weekTwo.toActivePlaytime();
double playtime3 = weekThree.toActivePlaytime();
double indexW1 = 1.0 / (Math.PI / 2.0 * (playtime1 / playtimeMsThreshold) + 1.0);
double indexW2 = 1.0 / (Math.PI / 2.0 * (playtime2 / playtimeMsThreshold) + 1.0);
double indexW3 = 1.0 / (Math.PI / 2.0 * (playtime3 / playtimeMsThreshold) + 1.0);
double average = (indexW1 + indexW2 + indexW3) / 3.0;
return 5.0 - (5.0 * average);
}
public double getValue() {
return value;
}
public long getDate() {
return date;
}
public String getFormattedValue(Formatter<Double> formatter) {
return formatter.apply(value);
}
public double distance(ActivityIndex other) {
// Logarithm makes the distance function more skewed towards active players
// https://www.wolframalpha.com/input/?i=plot+y+%3D+log(5+-+5+*+(1+%2F+(pi%2F2+*+x%2B1)))+and+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
return Math.abs(Math.log(other.value) - Math.log(value));
}
public static HtmlLang getGroupLang(double value) {
if (value >= VERY_ACTIVE) {
return HtmlLang.INDEX_VERY_ACTIVE;
} else if (value >= ACTIVE) {
return HtmlLang.INDEX_ACTIVE;
} else if (value >= REGULAR) {
return HtmlLang.INDEX_REGULAR;
} else if (value >= IRREGULAR) {
return HtmlLang.INDEX_IRREGULAR;
} else {
return HtmlLang.INDEX_INACTIVE;
}
}
public static String getGroup(double value) {
return getGroupLang(value).getDefault();
}
public String getGroup() {
return getGroup(value);
}
public String getGroupLang() {
return getGroupLang(value).getKey();
}
public String getGroup(Locale locale) {
if (value >= VERY_ACTIVE) {
return locale.getString(HtmlLang.INDEX_VERY_ACTIVE);
} else if (value >= ACTIVE) {
return locale.getString(HtmlLang.INDEX_ACTIVE);
} else if (value >= REGULAR) {
return locale.getString(HtmlLang.INDEX_REGULAR);
} else if (value >= IRREGULAR) {
return locale.getString(HtmlLang.INDEX_IRREGULAR);
} else {
return locale.getString(HtmlLang.INDEX_INACTIVE);
}
}
}