212 lines
7.6 KiB
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);
|
|
}
|
|
}
|
|
}
|