1<?php
2
3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
4// See the LICENCE file in the repository root for full licence text.
5
6namespace App\Transformers;
7
8use App\Models\Beatmap;
9use App\Models\UserStatistics;
10use League\Fractal\Resource\ResourceInterface;
11
12class UserStatisticsTransformer extends TransformerAbstract
13{
14 protected array $availableIncludes = [
15 'country_rank',
16 'rank',
17 'rank_change_since_30_days',
18 'user',
19 'variants',
20 ];
21
22 public function transform(UserStatistics\Model $stats = null)
23 {
24 if ($stats === null) {
25 $stats = new UserStatistics\Osu();
26 }
27
28 if (!$GLOBALS['cfg']['osu']['scores']['experimental_rank_as_default'] && $GLOBALS['cfg']['osu']['scores']['experimental_rank_as_extra']) {
29 $globalRankExp = $stats->globalRankExp();
30 $ppExp = $stats->rank_score_exp;
31 }
32
33 return [
34 'count_100' => $stats->count100,
35 'count_300' => $stats->count300,
36 'count_50' => $stats->count50,
37 'count_miss' => $stats->countMiss,
38 'level' => [
39 'current' => $stats->currentLevel(),
40 'progress' => $stats->currentLevelProgressPercent(),
41 ],
42 'global_rank' => $stats->globalRank(),
43 'global_rank_exp' => $globalRankExp ?? null,
44 'pp' => $stats->pp(),
45 'pp_exp' => $ppExp ?? 0,
46 'ranked_score' => $stats->ranked_score,
47 'hit_accuracy' => $stats->hit_accuracy,
48 'play_count' => $stats->playcount,
49 'play_time' => $stats->total_seconds_played,
50 'total_score' => $stats->total_score,
51 'total_hits' => $stats->totalHits(),
52 'maximum_combo' => $stats->max_combo,
53 'replays_watched_by_others' => $stats->replay_popularity,
54 'is_ranked' => $stats->isRanked(),
55 'grade_counts' => [
56 'ss' => $stats->x_rank_count,
57 'ssh' => $stats->xh_rank_count ?? 0, // osu_charts tables don't have the `h` columns
58 's' => $stats->s_rank_count,
59 'sh' => $stats->sh_rank_count ?? 0,
60 'a' => $stats->a_rank_count,
61 ],
62 ];
63 }
64
65 public function includeCountryRank(UserStatistics\Model $stats = null)
66 {
67 if ($stats !== null) {
68 return $this->primitive($stats->countryRank());
69 }
70 }
71
72 // TODO: remove this after country_rank is deployed
73 public function includeRank(UserStatistics\Model $stats = null)
74 {
75 if ($stats === null) {
76 $stats = new UserStatistics\Osu();
77 }
78
79 return $this->primitive(['country' => $stats->countryRank()]);
80 }
81
82 public function includeRankChangeSince30Days(UserStatistics\Model $stats): ResourceInterface
83 {
84 return $this->primitive($stats->rankHistory?->rankChangeSince30Days());
85 }
86
87 public function includeUser(UserStatistics\Model $stats = null)
88 {
89 if ($stats === null) {
90 $stats = new UserStatistics\Osu();
91 }
92
93 return $this->item($stats->user, new UserCompactTransformer());
94 }
95
96 public function includeVariants(UserStatistics\Model $stats = null)
97 {
98 if ($stats === null) {
99 return;
100 }
101
102 $mode = $stats->getMode();
103 $variants = Beatmap::VARIANTS[$mode] ?? null;
104
105 if ($variants === null) {
106 return;
107 }
108
109 $data = [];
110
111 foreach ($variants as $variant) {
112 // User should be preloaded in cases where this is used.
113 $entry = $stats->user->statistics($mode, false, $variant) ?? new (UserStatistics\Model::getClass($mode, $variant));
114
115 $data[] = [
116 'mode' => $mode,
117 'variant' => $variant,
118
119 'country_rank' => $entry->countryRank(),
120 'global_rank' => $entry->globalRank(),
121 'pp' => $entry->rank_score,
122 ];
123 }
124
125 return $this->primitive($data);
126 }
127}