api: re-impl histories (muchhhhhhhhhh better now)
Some checks failed
Deploy API / docker (17, 3.8.5) (push) Failing after 37s

This commit is contained in:
Lee 2024-08-05 03:57:59 +01:00
parent 29f5d5983a
commit 7b0c9f54ff
18 changed files with 441 additions and 144 deletions

@ -3,17 +3,36 @@ package cc.fascinated.common;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Date; import java.util.Date;
import java.util.Locale;
/** /**
* @author Fascinated (fascinated7) * @author Fascinated (fascinated7)
*/ */
@UtilityClass @UtilityClass
public class DateUtils { public class DateUtils {
private static final ZoneId ZONE_ID = ZoneId.of("Europe/London");
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_INSTANT
.withLocale(Locale.UK)
.withZone(ZONE_ID);
private static final DateTimeFormatter SIMPLE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withLocale(Locale.UK)
.withZone(ZONE_ID);
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_INSTANT; /**
* Gets the date from a string.
*
* @param date The date string.
* @return The date.
*/
public static Date getDateFromIsoString(String date) {
return Date.from(Instant.from(ISO_FORMATTER.parse(date)));
}
/** /**
* Gets the date from a string. * Gets the date from a string.
@ -22,13 +41,25 @@ public class DateUtils {
* @return The date. * @return The date.
*/ */
public static Date getDateFromString(String date) { public static Date getDateFromString(String date) {
return Date.from(Instant.from(FORMATTER.parse(date))); LocalDate localDate = LocalDate.parse(date, SIMPLE_FORMATTER);
ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZONE_ID);
return Date.from(zonedDateTime.toInstant());
}
/**
* Formats a date to a string.
*
* @param date The date to format.
* @return The formatted date.
*/
public String formatDate(Date date) {
return SIMPLE_FORMATTER.format(date.toInstant());
} }
/** /**
* Aligns the date to the current hour. * Aligns the date to the current hour.
* <p> * <p>
* eg: 00:05 -> 00:00 * eg: 00:05 -> 00:00
* </p> * </p>
* *
* @param date The date to align. * @param date The date to align.
@ -47,4 +78,13 @@ public class DateUtils {
public static Date getDaysAgo(int days) { public static Date getDaysAgo(int days) {
return Date.from(Instant.now().minus(days, ChronoUnit.DAYS)); return Date.from(Instant.now().minus(days, ChronoUnit.DAYS));
} }
/**
* Gets the date for midnight today.
*
* @return The date.
*/
public static Date getMidnightToday() {
return Date.from(Instant.now().truncatedTo(ChronoUnit.DAYS));
}
} }

@ -17,7 +17,8 @@ public class UserController {
/** /**
* The user service to use * The user service to use
*/ */
@NonNull private final UserService userService; @NonNull
private final UserService userService;
@Autowired @Autowired
public UserController(@NonNull UserService userService) { public UserController(@NonNull UserService userService) {
@ -36,4 +37,18 @@ public class UserController {
public ResponseEntity<UserDTO> getUser(@PathVariable String id) { public ResponseEntity<UserDTO> getUser(@PathVariable String id) {
return ResponseEntity.ok(userService.getUser(id).getAsDTO()); return ResponseEntity.ok(userService.getUser(id).getAsDTO());
} }
/**
* A GET mapping to retrieve a user's statistic
* history using the users steam id.
*
* @param id the id of the user
* @return the user's statistic history
* @throws BadRequestException if the user is not found
*/
@ResponseBody
@GetMapping(value = "/histories/{id}")
public ResponseEntity<?> getUserHistories(@PathVariable String id) {
return ResponseEntity.ok(userService.getUser(id).getHistory().getPreviousHistories(30));
}
} }

@ -0,0 +1,39 @@
package cc.fascinated.model.score;
import cc.fascinated.model.user.hmd.DeviceController;
import cc.fascinated.model.user.hmd.DeviceHeadset;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public class DeviceInformation {
/**
* The headset that was used to set the score.
*/
private final DeviceHeadset headset;
/**
* The left controller that was used to set the score.
*/
private final DeviceController leftController;
/**
* The right controller that was used to set the score.
*/
private final DeviceController rightController;
/**
* Checks if the device information contains unknown values.
*
* @return if the device information contains unknown values
*/
public boolean containsUnknownDevices() {
return headset == DeviceHeadset.UNKNOWN
|| leftController == DeviceController.UNKNOWN
|| rightController == DeviceController.UNKNOWN;
}
}

@ -36,7 +36,8 @@ public class Score {
/** /**
* The ID of the player that set the score. * The ID of the player that set the score.
*/ */
@Indexed @JsonIgnore @Indexed
@JsonIgnore
private final String playerId; private final String playerId;
/** /**
@ -59,7 +60,8 @@ public class Score {
/** /**
* The ID of the leaderboard the score was set on. * The ID of the leaderboard the score was set on.
*/ */
@Indexed @JsonIgnore @Indexed
@JsonIgnore
private final String leaderboardId; private final String leaderboardId;
/** /**
@ -100,6 +102,14 @@ public class Score {
*/ */
private final Integer badCuts; private final Integer badCuts;
/**
* The device information that was used to set the score.
* <p>
* Headset and controllers information.
* </p>
*/
private final DeviceInformation deviceInformation;
/** /**
* The score history for map the score was set on. * The score history for map the score was set on.
*/ */
@ -155,4 +165,13 @@ public class Score {
public List<Score> getPreviousScores() { public List<Score> getPreviousScores() {
return previousScores == null ? List.of() : previousScores; return previousScores == null ? List.of() : previousScores;
} }
/**
* Gets if the score is ranked.
*
* @return true if the score is ranked, false otherwise
*/
public boolean isRanked() {
return pp != null;
}
} }

@ -1,5 +1,6 @@
package cc.fascinated.model.score.impl.scoresaber; package cc.fascinated.model.score.impl.scoresaber;
import cc.fascinated.model.score.DeviceInformation;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import lombok.Getter; import lombok.Getter;
@ -28,10 +29,10 @@ public class ScoreSaberScore extends Score {
private final int maxCombo; private final int maxCombo;
public ScoreSaberScore(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank, public ScoreSaberScore(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
double accuracy, Double pp, int score, String[] modifiers, Integer misses, Integer badCuts, List<Score> previousScores, double accuracy, Double pp, int score, String[] modifiers, Integer misses, Integer badCuts, DeviceInformation deviceInformation,
Date timestamp, Double weight, double multiplier, int maxCombo) { List<Score> previousScores, Date timestamp, Double weight, double multiplier, int maxCombo) {
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses, super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses,
badCuts, previousScores, timestamp); badCuts, deviceInformation, previousScores, timestamp);
this.weight = weight; this.weight = weight;
this.multiplier = multiplier; this.multiplier = multiplier;
this.maxCombo = maxCombo; this.maxCombo = maxCombo;

@ -1,6 +1,7 @@
package cc.fascinated.model.score.impl.scoresaber; package cc.fascinated.model.score.impl.scoresaber;
import cc.fascinated.model.leaderboard.Leaderboard; import cc.fascinated.model.leaderboard.Leaderboard;
import cc.fascinated.model.score.DeviceInformation;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.model.user.UserDTO; import cc.fascinated.model.user.UserDTO;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
@ -25,10 +26,11 @@ public class ScoreSaberScoreResponse extends ScoreSaberScore {
private final Leaderboard leaderboard; private final Leaderboard leaderboard;
public ScoreSaberScoreResponse(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank, public ScoreSaberScoreResponse(long id, String playerId, Platform.Platforms platform, String platformScoreId, String leaderboardId, int rank,
double accuracy, double pp, int score, String[] modifiers, int misses, int badCuts, List<Score> previousScores, double accuracy, double pp, int score, String[] modifiers, int misses, int badCuts, DeviceInformation deviceInformation,
Date timestamp, double weight, double multiplier, int maxCombo, UserDTO user, Leaderboard leaderboard) { List<Score> previousScores, Date timestamp, double weight, double multiplier, int maxCombo, UserDTO user,
Leaderboard leaderboard) {
super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses, badCuts, super(id, playerId, platform, platformScoreId, leaderboardId, rank, accuracy, pp, score, modifiers, misses, badCuts,
previousScores, timestamp, weight, multiplier, maxCombo); deviceInformation, previousScores, timestamp, weight, multiplier, maxCombo);
this.user = user; this.user = user;
this.leaderboard = leaderboard; this.leaderboard = leaderboard;
} }

@ -56,7 +56,7 @@ public class ScoreSaberAccount {
token.getCountry(), token.getCountry(),
token.getRank(), token.getRank(),
token.getCountryRank(), token.getCountryRank(),
DateUtils.getDateFromString(token.getFirstSeen()), DateUtils.getDateFromIsoString(token.getFirstSeen()),
new Date() new Date()
); );
} }

@ -1,18 +1,17 @@
package cc.fascinated.model.user; package cc.fascinated.model.user;
import cc.fascinated.model.user.statistic.Statistic; import cc.fascinated.model.user.history.History;
import cc.fascinated.platform.Platform;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.log4j.Log4j2;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed; import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
/** /**
@ -21,11 +20,10 @@ import java.util.UUID;
@RequiredArgsConstructor @RequiredArgsConstructor
@Getter @Getter
@Setter @Setter
@Log4j2
@ToString @ToString
@Document("user") @Document("user")
public class User { public class User {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("EEE MMM d HH:mm:ss zzz yyyy");
/** /**
* The ID of the user. * The ID of the user.
*/ */
@ -67,55 +65,16 @@ public class User {
/** /**
* The user's statistic history. * The user's statistic history.
*/ */
public Map<Platform.Platforms, Map<String, Statistic>> histories; public History history;
/** /**
* The user's history points history. * Gets the user's statistic history
*/ */
@JsonIgnore public History getHistory() {
public Map<Platform.Platforms, Map<Date, Statistic>> getHistories() { if (this.history == null) {
if (this.histories == null) { this.history = new History();
this.histories = new HashMap<>();
} }
Map<Platform.Platforms, Map<Date, Statistic>> toReturn = new HashMap<>(); return this.history;
for (Platform.Platforms platform : histories.keySet()) {
toReturn.put(platform, getHistory(platform));
}
return toReturn;
}
/**
* Gets the history points for a platform.
*
* @param platform the platform to get the statistics for
* @return the statistics
*/
@SneakyThrows
public Map<Date, Statistic> getHistory(@NonNull Platform.Platforms platform) {
if (this.histories == null) {
this.histories = new HashMap<>();
}
Map<String, Statistic> statisticMap = this.histories.computeIfAbsent(platform, k -> new HashMap<>());
Map<Date, Statistic> statistics = new HashMap<>();
for (Map.Entry<String, Statistic> entry : statisticMap.entrySet()) {
statistics.put(DATE_FORMAT.parse(entry.getKey()), entry.getValue());
}
return statistics;
}
/**
* Adds a history point to the user's history.
*
* @param platform the platform to add the statistic for
* @param date the date of the statistic
* @param statistic the statistic to add
*/
public void addHistory(@NonNull Platform.Platforms platform, @NonNull Date date, @NonNull Statistic statistic) {
if (this.histories == null) {
this.histories = new HashMap<>();
}
Map<String, Statistic> statisticMap = this.histories.computeIfAbsent(platform, k -> new HashMap<>());
statisticMap.put(String.valueOf(date.toString()), statistic);
} }
/** /**

@ -0,0 +1,78 @@
package cc.fascinated.model.user.history;
import cc.fascinated.common.DateUtils;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.*;
/**
* @author Fascinated (fascinated7)
*/
public class History {
/**
* The user's history points in time.
*/
private Map<String, HistoryPoint> histories;
/**
* The user's history points history.
*/
@JsonIgnore
public Map<Date, HistoryPoint> getHistories() {
if (this.histories == null) {
this.histories = new HashMap<>();
}
Map<Date, HistoryPoint> toReturn = new HashMap<>();
this.histories.forEach((key, value) -> toReturn.put(DateUtils.getDateFromString(key), value));
return toReturn;
}
/**
* Gets the user's history for today.
*
* @return the user's history for today
*/
@JsonIgnore
public HistoryPoint getTodayHistory() {
if (this.histories == null) {
this.histories = new HashMap<>();
}
Date midnight = DateUtils.getMidnightToday();
return this.histories.computeIfAbsent(DateUtils.formatDate(midnight), key -> new HistoryPoint());
}
/**
* Gets the user's history for a specific date.
*
* @param date the date to get the history for
* @return the user's history for the date
*/
public HistoryPoint getHistoryForDate(Date date) {
if (this.histories == null) {
this.histories = new HashMap<>();
}
return this.histories.get(DateUtils.formatDate(date));
}
/**
* Gets the user's HistoryPoint history for
* an amount of days ago.
*
* @param days the amount of days ago
* @return the user's HistoryPoint history
*/
public TreeMap<String, HistoryPoint> getPreviousHistories(int days) {
Date date = DateUtils.getDaysAgo(days);
Map<String, HistoryPoint> toReturn = new HashMap<>();
for (Map.Entry<Date, HistoryPoint> history : getHistories().entrySet()) {
if (history.getKey().after(date)) {
toReturn.put(DateUtils.formatDate(history.getKey()), history.getValue());
}
}
// Sort the history by date (newest > oldest)
TreeMap<String, HistoryPoint> sorted = new TreeMap<>(Comparator.comparing(DateUtils::getDateFromString).reversed());
sorted.putAll(toReturn);
return sorted;
}
}

@ -0,0 +1,60 @@
package cc.fascinated.model.user.history;
import lombok.Getter;
import lombok.Setter;
/**
* @author Fascinated (fascinated7)
*/
@Getter
@Setter
public class HistoryPoint {
/**
* The rank of the player.
*/
private Integer rank;
/**
* The pp of the player.
*/
private Integer countryRank;
/**
* The pp of the player.
*/
private Double pp;
/**
* Play count of all the player's scores.
*/
private Integer totalPlayCount;
/**
* Play count of all the player's ranked scores.
*/
private Integer totalRankedPlayCount;
/**
* Play count for this day's unranked scores.
*/
private Integer unrankedPlayCount = 0;
/**
* Play count for this day's ranked scores.
*/
private Integer rankedPlayCount = 0;
/**
* Increment the total ranked play count for this day.
*/
public void incrementRankedPlayCount() {
rankedPlayCount++;
}
/**
* Increment the total unranked play count for this day.
*/
public void incrementUnrankedPlayCount() {
unrankedPlayCount++;
}
}

@ -0,0 +1,50 @@
package cc.fascinated.model.user.hmd;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public enum DeviceController {
UNKNOWN("Unknown"),
/**
* Oculus Controllers
*/
OCULUS_QUEST_TOUCH("Touch"),
OCULUS_QUEST_2_TOUCH("Quest 2 Touch"),
OCULUS_QUEST_3_TOUCH("Quest 3 Touch"),
/**
* HP Controllers
*/
HP_REVERB("HP Reverb"),
/**
* Valve Controllers
*/
VALVE_KNUCKLES("Knuckles");
/**
* The controller name
*/
private final String name;
/**
* Gets a controller by its name.
*
* @param name the name of the controller
* @return the controller
*/
public static DeviceController getByName(String name) {
for (DeviceController deviceController : values()) {
if (deviceController.getName().equalsIgnoreCase(name)) {
return deviceController;
}
}
return null;
}
}

@ -0,0 +1,57 @@
package cc.fascinated.model.user.hmd;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
public enum DeviceHeadset {
UNKNOWN("Unknown"),
/**
* Oculus HMDs
*/
OCULUS_CV1("Rift"),
OCULUS_QUEST("Quest"),
OCULUS_QUEST_2("Quest 2"),
OCULUS_QUEST_3("Quest 3"),
OCULUS_RIFT_S("Rift S"),
/**
* HTC HMDs
*/
HTC_VIVE("Vive"),
/**
* HP HMDs
*/
HP_REVERB("HP Reverb"),
/**
* Valve HMDs
*/
VALVE_INDEX("Valve Index");
/**
* The name of the headset.
*/
private final String name;
/**
* Gets a headset by its name.
*
* @param name the name of the headset
* @return the headset
*/
public static DeviceHeadset getByName(String name) {
for (DeviceHeadset deviceHeadset : values()) {
if (deviceHeadset.getName().equalsIgnoreCase(name)) {
return deviceHeadset;
}
}
return null;
}
}

@ -1,23 +0,0 @@
package cc.fascinated.model.user.statistic;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.mongodb.core.mapping.Document;
/**
* @author Fascinated (fascinated7)
*/
@AllArgsConstructor
@Getter
@Document("player_histories")
public class Statistic {
/**
* The rank of the player.
*/
private final int rank;
/**
* The pp of the player.
*/
private final int countryRank;
}

@ -1,38 +0,0 @@
package cc.fascinated.model.user.statistic.impl;
import cc.fascinated.model.user.statistic.Statistic;
import lombok.Getter;
/**
* @author Fascinated (fascinated7)
*/
@Getter
public class ScoreSaberStatistic extends Statistic {
/**
* The total score of the player.
*/
private final long totalScore;
/**
* The total ranked score of the player.
*/
private final long totalRankedScore;
/**
* The average ranked accuracy of the player.
*/
private final double averageRankedAccuracy;
/**
* The total play count of the player.
*/
private final int totalPlayCount;
public ScoreSaberStatistic(int rank, int countryRank, long totalScore, long totalRankedScore, double averageRankedAccuracy, int totalPlayCount) {
super(rank, countryRank);
this.totalScore = totalScore;
this.totalRankedScore = totalRankedScore;
this.averageRankedAccuracy = averageRankedAccuracy;
this.totalPlayCount = totalPlayCount;
}
}

@ -72,7 +72,7 @@ public abstract class Platform {
* Called to update the players * Called to update the players
* data in QuestDB. * data in QuestDB.
*/ */
public abstract void updatePlayers(); public abstract void trackPlayerMetrics();
/** /**
* Called to update the metrics * Called to update the metrics

@ -7,7 +7,7 @@ import cc.fascinated.model.token.ScoreSaberAccountToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken; import cc.fascinated.model.token.ScoreSaberLeaderboardPageToken;
import cc.fascinated.model.token.ScoreSaberLeaderboardToken; import cc.fascinated.model.token.ScoreSaberLeaderboardToken;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.model.user.statistic.impl.ScoreSaberStatistic; import cc.fascinated.model.user.history.HistoryPoint;
import cc.fascinated.platform.CurvePoint; import cc.fascinated.platform.CurvePoint;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import cc.fascinated.services.ScoreSaberService; import cc.fascinated.services.ScoreSaberService;
@ -137,21 +137,19 @@ public class ScoreSaberPlatform extends Platform {
} }
@Override @Override
public void updatePlayers() { public void trackPlayerMetrics() {
Date date = DateUtils.alignToCurrentHour(new Date()); Date date = DateUtils.getMidnightToday();
for (User user : this.userService.getUsers(false)) { for (User user : this.userService.getUsers(false)) {
if (!user.isLinkedAccount()) { // Check if the user has linked their account if (!user.isLinkedAccount()) { // Check if the user has linked their account
continue; continue;
} }
ScoreSaberAccountToken account = scoreSaberService.getAccount(user); // Get the account from the ScoreSaber API ScoreSaberAccountToken account = scoreSaberService.getAccount(user); // Get the account from the ScoreSaber API
user.addHistory(this.getPlatform(), date, new ScoreSaberStatistic( HistoryPoint history = user.getHistory().getHistoryForDate(date);
account.getRank(), history.setPp(account.getPp());
account.getCountryRank(), history.setRank(account.getRank());
account.getScoreStats().getTotalScore(), history.setCountryRank(account.getCountryRank());
account.getScoreStats().getTotalRankedScore(), history.setTotalPlayCount(account.getScoreStats().getTotalPlayCount());
account.getScoreStats().getAverageRankedAccuracy(), history.setTotalRankedPlayCount((int) account.getScoreStats().getTotalRankedScore());
account.getScoreStats().getTotalPlayCount()
)); // Add the statistic to the user's history
this.userService.saveUser(user); // Save the user this.userService.saveUser(user); // Save the user
} }
} }

@ -43,7 +43,7 @@ public class PlatformService {
/** /**
* Updates the platform metrics. * Updates the platform metrics.
* <p> * <p>
* This method is scheduled to run every minute. * This method is scheduled to run every 5 minutes.
* </p> * </p>
*/ */
@Scheduled(cron = "0 */5 * * * *") @Scheduled(cron = "0 */5 * * * *")
@ -65,7 +65,7 @@ public class PlatformService {
public void updatePlayerMetrics() { public void updatePlayerMetrics() {
log.info("Updating %s platform player metrics...".formatted(this.platforms.size())); log.info("Updating %s platform player metrics...".formatted(this.platforms.size()));
for (Platform platform : this.platforms) { for (Platform platform : this.platforms) {
platform.updatePlayers(); platform.trackPlayerMetrics();
} }
log.info("Finished updating platform player metrics."); log.info("Finished updating platform player metrics.");
} }

@ -5,6 +5,7 @@ import cc.fascinated.common.EnumUtils;
import cc.fascinated.common.MathUtils; import cc.fascinated.common.MathUtils;
import cc.fascinated.common.Tuple; import cc.fascinated.common.Tuple;
import cc.fascinated.model.leaderboard.Leaderboard; import cc.fascinated.model.leaderboard.Leaderboard;
import cc.fascinated.model.score.DeviceInformation;
import cc.fascinated.model.score.Score; import cc.fascinated.model.score.Score;
import cc.fascinated.model.score.TotalScoresResponse; import cc.fascinated.model.score.TotalScoresResponse;
import cc.fascinated.model.score.impl.scoresaber.ScoreSaberScore; import cc.fascinated.model.score.impl.scoresaber.ScoreSaberScore;
@ -14,6 +15,9 @@ import cc.fascinated.model.token.ScoreSaberPlayerScoreToken;
import cc.fascinated.model.token.ScoreSaberScoreToken; import cc.fascinated.model.token.ScoreSaberScoreToken;
import cc.fascinated.model.user.User; import cc.fascinated.model.user.User;
import cc.fascinated.model.user.UserDTO; import cc.fascinated.model.user.UserDTO;
import cc.fascinated.model.user.history.HistoryPoint;
import cc.fascinated.model.user.hmd.DeviceController;
import cc.fascinated.model.user.hmd.DeviceHeadset;
import cc.fascinated.platform.Platform; import cc.fascinated.platform.Platform;
import cc.fascinated.repository.ScoreRepository; import cc.fascinated.repository.ScoreRepository;
import lombok.NonNull; import lombok.NonNull;
@ -96,6 +100,7 @@ public class ScoreService {
score.getModifiers(), score.getModifiers(),
score.getMisses(), score.getMisses(),
score.getBadCuts(), score.getBadCuts(),
score.getDeviceInformation(),
score.getPreviousScores(), score.getPreviousScores(),
score.getTimestamp(), score.getTimestamp(),
scoreSaberScore.getWeight(), scoreSaberScore.getWeight(),
@ -130,6 +135,12 @@ public class ScoreService {
} }
} }
/**
* Gets the total scores for the platform.
*
* @param platform The platform to get the scores from.
* @return The total scores.
*/
public TotalScoresResponse getTotalScores(Platform.Platforms platform) { public TotalScoresResponse getTotalScores(Platform.Platforms platform) {
return new TotalScoresResponse( return new TotalScoresResponse(
scoreRepository.getTotalScores(platform), scoreRepository.getTotalScores(platform),
@ -179,15 +190,27 @@ public class ScoreService {
modifiers.length == 0 ? null : modifiers, // no modifiers, set to null to save data modifiers.length == 0 ? null : modifiers, // no modifiers, set to null to save data
score.getMissedNotes() == 0 ? null : score.getMissedNotes(), // no misses, set to null to save data score.getMissedNotes() == 0 ? null : score.getMissedNotes(), // no misses, set to null to save data
score.getBadCuts() == 0 ? null : score.getBadCuts(), // no bad cuts, set to null to save data score.getBadCuts() == 0 ? null : score.getBadCuts(), // no bad cuts, set to null to save data
new DeviceInformation(
score.getDeviceHmd() == null ? DeviceHeadset.UNKNOWN : DeviceHeadset.getByName(score.getDeviceHmd()),
score.getDeviceControllerLeft() == null ? DeviceController.UNKNOWN : DeviceController.getByName(score.getDeviceControllerLeft()),
score.getDeviceControllerRight() == null ? DeviceController.UNKNOWN : DeviceController.getByName(score.getDeviceControllerRight())
),
previousScores, previousScores,
DateUtils.getDateFromString(score.getTimeSet()), DateUtils.getDateFromIsoString(score.getTimeSet()),
score.getWeight() == 0 ? null : score.getWeight(), // no weight, set to null to save data score.getWeight() == 0 ? null : score.getWeight(), // no weight, set to null to save data
score.getMultiplier(), score.getMultiplier(),
score.getMaxCombo() score.getMaxCombo()
); );
scoreRepository.save(scoreSaberScore); this.saveScore(user, scoreSaberScore);
this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user, this.logScore(Platform.Platforms.SCORESABER, Leaderboard.getFromScoreSaberToken(leaderboard), scoreSaberScore, user,
previousScoreExists && previousScore.getScore() < scoreSaberScore.getScore()); previousScoreExists && previousScore.getScore() < scoreSaberScore.getScore());
if (scoreSaberScore.getDeviceInformation().containsUnknownDevices()) {
log.warn(" - Score contains unknown device: hmd: {}, controller left: {}, controller right: {}",
score.getDeviceHmd(),
score.getDeviceControllerLeft(),
score.getDeviceControllerRight()
);
}
} }
/** /**
@ -239,6 +262,22 @@ public class ScoreService {
return scoreRepository.getBestImprovedScores(platform, DateUtils.getDaysAgo(days)); return scoreRepository.getBestImprovedScores(platform, DateUtils.getDaysAgo(days));
} }
/**
* Saves a score.
*
* @param score The score to save.
*/
public void saveScore(User user, Score score) {
HistoryPoint todayHistory = user.getHistory().getTodayHistory();
if (score.isRanked()) {
todayHistory.incrementRankedPlayCount();
} else {
todayHistory.incrementUnrankedPlayCount();
}
userService.saveUser(user); // Save the user
scoreRepository.save(score); // Save the score
}
/** /**
* Logs a score. * Logs a score.
* *
@ -250,6 +289,7 @@ public class ScoreService {
@NonNull User user, boolean improvedScore) { @NonNull User user, boolean improvedScore) {
String platformName = EnumUtils.getEnumName(platform); String platformName = EnumUtils.getEnumName(platform);
boolean isRanked = score.getPp() != 0; boolean isRanked = score.getPp() != 0;
log.info("[{}] {}Tracked{} Score! id: {}, acc: {}%, {} score id: {},{} leaderboard: {}, difficulty: {}, player: {} ({})", log.info("[{}] {}Tracked{} Score! id: {}, acc: {}%, {} score id: {},{} leaderboard: {}, difficulty: {}, player: {} ({})",
platformName, platformName,
improvedScore ? "Improved " : "", improvedScore ? "Improved " : "",