diff --git a/.env.example b/.env.example index 7c5b8beb274..38932f88b36 100644 --- a/.env.example +++ b/.env.example @@ -183,7 +183,6 @@ CLIENT_CHECK_VERSION=false # BAN_PERSIST_DAYS=28 # ES_HOST=localhost:9200 -# ES_SCORES_HOST=localhost:9200 # ES_SOLO_SCORES_HOST=localhost:9200 # ES_INDEX_PREFIX= # ES_CLIENT_TIMEOUT=5 diff --git a/app/Console/Commands/UserBestScoresCheckCommand.php b/app/Console/Commands/UserBestScoresCheckCommand.php deleted file mode 100644 index 0b62c7cdc3e..00000000000 --- a/app/Console/Commands/UserBestScoresCheckCommand.php +++ /dev/null @@ -1,30 +0,0 @@ -. Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -namespace App\Console\Commands; - -use App\Libraries\UserBestScoresCheck; -use App\Models\User; -use Illuminate\Console\Command; - -class UserBestScoresCheckCommand extends Command -{ - protected $signature = 'user:check-best-scores {userId} {mode}'; - - protected $description = 'Removes the best scores for a given user from elasticsearch if it no longer exists in the database.'; - - public function handle() - { - $mode = $this->argument('mode'); - $user = User::findOrFail($this->argument('userId')); - - $checker = new UserBestScoresCheck($user); - $response = $checker->run($mode); - - $this->line("Found {$checker->esIdsFound} in index, {$checker->dbIdsFound} valid."); - $this->line('Delete query response from elasticsearch:'); - $this->line(json_encode($response, JSON_PRETTY_PRINT)); - } -} diff --git a/app/Http/Controllers/LegacyInterOpController.php b/app/Http/Controllers/LegacyInterOpController.php index dc97375c975..72693f84b94 100644 --- a/app/Http/Controllers/LegacyInterOpController.php +++ b/app/Http/Controllers/LegacyInterOpController.php @@ -10,7 +10,6 @@ use App\Jobs\Notifications\ForumTopicReply; use App\Jobs\RegenerateBeatmapsetCover; use App\Libraries\Chat; -use App\Libraries\UserBestScoresCheck; use App\Models\Beatmap; use App\Models\Beatmapset; use App\Models\Chat\Channel; @@ -297,17 +296,6 @@ public function userBatchSendMessage() return response()->json($results); } - public function userBestScoresCheck($id) - { - $user = User::findOrFail($id); - - foreach (Beatmap::MODES as $mode => $_v) { - (new UserBestScoresCheck($user))->run($mode); - } - - return ['success' => true]; - } - public function userIndex($id) { $user = User::findOrFail($id); diff --git a/app/Jobs/RemoveBeatmapsetBestScores.php b/app/Jobs/RemoveBeatmapsetBestScores.php index 568bea841c6..c72c47e9d75 100644 --- a/app/Jobs/RemoveBeatmapsetBestScores.php +++ b/app/Jobs/RemoveBeatmapsetBestScores.php @@ -6,7 +6,6 @@ namespace App\Jobs; use App\Libraries\Elasticsearch\BoolQuery; -use App\Libraries\Elasticsearch\Es; use App\Models\Beatmap; use App\Models\Beatmapset; use App\Models\Score\Best\Model; @@ -57,13 +56,6 @@ public function handle() $query->filter(['terms' => ['beatmap_id' => $beatmapIds]]); $query->filter(['range' => ['score_id' => ['lte' => $this->maxScoreIds[$mode]]]]); - // TODO: do something with response? - Es::getClient('scores')->deleteByQuery([ - 'index' => $GLOBALS['cfg']['osu']['elasticsearch']['prefix']."high_scores_{$mode}", - 'body' => ['query' => $query->toArray()], - 'client' => ['ignore' => 404], - ]); - $class = Model::getClass($mode); // Just delete until no more matching rows. $query = $class diff --git a/app/Libraries/UserBestScoresCheck.php b/app/Libraries/UserBestScoresCheck.php deleted file mode 100644 index 6f7c4b41db1..00000000000 --- a/app/Libraries/UserBestScoresCheck.php +++ /dev/null @@ -1,116 +0,0 @@ -. Licensed under the GNU Affero General Public License v3.0. -// See the LICENCE file in the repository root for full licence text. - -namespace App\Libraries; - -use App\Libraries\Elasticsearch\BoolQuery; -use App\Libraries\Elasticsearch\Es; -use App\Libraries\Elasticsearch\Search; -use App\Libraries\Elasticsearch\Sort; -use App\Libraries\Search\BasicSearch; -use App\Models\Score\Best; -use App\Models\User; - -/** - * Used to check the best scores for a user if it exists in elasticsearch but not longer - * exists in the database. A mismatch tends to break the current pager, so this can be run to - * fix it by deleting the invalid score from elasticsearch. - * - * e.g. - * (new UserBestScoresCheck(User::find($user_id)))->run($mode). - */ -class UserBestScoresCheck -{ - /** @var int */ - public $dbIdsFound; - /** @var int */ - public $esIdsFound; - - /** @var User */ - private $user; - - /** - * @param User $user The User to check the scores for. - */ - public function __construct(User $user) - { - $this->user = $user; - } - - /** - * Checks for extraneous scores in Elasticsearch. - * - * @param string $mode The game mode. - * @return array List of score_ids that exist in Elasticsearch but not the database. - */ - public function check(string $mode) - { - $this->dbIdsFound = 0; - $this->esIdsFound = 0; - - $clazz = Best\Model::getClass($mode); - - $search = $this->newSearch($mode); - $cursor = [0]; - - $missingIds = []; - - while ($cursor !== null) { - $esIds = $search->searchAfter(array_values($cursor))->response()->ids(); - $this->esIdsFound += count($esIds); - - $dbIds = $clazz::default()->whereIn('score_id', $esIds)->pluck('score_id')->all(); - $this->dbIdsFound += count($dbIds); - - $missingIds = array_merge( - $missingIds, - array_values(array_diff($esIds, $dbIds)) - ); - - $cursor = $search->getSortCursor(); - } - - return $missingIds; - } - - /** - * Removes extraneous scores from Elasticsearch. - * - * @param string $mode The game mode. - * @param array $ids List of score_ids to remove. - * @return array The raw response from Elasticsearch. - */ - public function removeFromEs(string $mode, array $ids) - { - return Es::getClient('scores')->deleteByQuery([ - 'index' => $GLOBALS['cfg']['osu']['elasticsearch']['prefix']."high_scores_{$mode}", - 'body' => ['query' => ['terms' => ['score_id' => $ids]]], - ]); - } - - /** - * Checks for extraneous scores in Elasticsearch and removes them. - * - * @param string $mode The game mode. - * @return array The raw response from Elasticsearch. - */ - public function run(string $mode) - { - return $this->removeFromEs($mode, $this->check($mode)); - } - - private function newSearch(string $mode): Search - { - $index = $GLOBALS['cfg']['osu']['elasticsearch']['prefix']."high_scores_{$mode}"; - - $search = new BasicSearch($index, "user_best_scores_check_{$mode}"); - $search->connectionName = 'scores'; - - return $search - ->sort(new Sort('score_id', 'asc')) - ->size(Es::CHUNK_SIZE) - ->query((new BoolQuery())->filter(['term' => ['user_id' => $this->user->getKey()]])); - } -} diff --git a/config/elasticsearch.php b/config/elasticsearch.php index 0ce6f8eba79..203283c7207 100644 --- a/config/elasticsearch.php +++ b/config/elasticsearch.php @@ -20,9 +20,6 @@ 'default' => array_merge($defaults, [ 'hosts' => $parseHosts('ES_HOST'), ]), - 'scores' => array_merge($defaults, [ - 'hosts' => $parseHosts('ES_SCORES_HOST'), - ]), 'solo_scores' => array_merge($defaults, [ 'hosts' => $parseHosts('ES_SOLO_SCORES_HOST'), ]), diff --git a/docker-compose.yml b/docker-compose.yml index 6d9ce7103d9..68636414721 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,6 @@ x-env: &x-env DB_CONNECTION_STRING: Server=db;Database=osu;Uid=osuweb; DB_HOST: db ES_HOST: http://elasticsearch:9200 - ES_SCORES_HOST: http://elasticsearch:9200 ES_SOLO_SCORES_HOST: http://elasticsearch:9200 GITHUB_TOKEN: "${GITHUB_TOKEN}" NOTIFICATION_REDIS_HOST: redis diff --git a/resources/css/bem/comment-editor.less b/resources/css/bem/comment-editor.less index 1007123e57b..3003461c998 100644 --- a/resources/css/bem/comment-editor.less +++ b/resources/css/bem/comment-editor.less @@ -8,11 +8,11 @@ @_spacing: 10px; @_border-radius: @border-radius-large; + --bg: hsl(var(--hsl-b3)); border-radius: @_border-radius; display: flex; flex-direction: column; - background-color: @osu-colour-b3; - border: 2px solid @osu-colour-b3; + background: var(--bg); position: relative; &--fancy { @@ -62,14 +62,15 @@ padding: @_spacing; font-size: @font-size--phone-input; border-radius: @_border-radius @_border-radius 0 0; - resize: none; + border: 2px solid var(--bg); + border-bottom: none; background-color: @osu-colour-b5; + resize: none; @media @desktop { font-size: inherit; } - color: white; &::placeholder { color: @osu-colour-f1; } diff --git a/resources/css/colors.less b/resources/css/colors.less index 957b4a686cc..fd9cc072ad8 100644 --- a/resources/css/colors.less +++ b/resources/css/colors.less @@ -203,9 +203,10 @@ body { @group-colour-default: #0087ca; -@colour-rank-d: #ff5858; -@colour-rank-c: #ea7948; -@colour-rank-b: #d99d03; -@colour-rank-a: #72c904; -@colour-rank-s: #0096a2; -@colour-rank-ss: #be0089; +// cross-reference: https://github.com/ppy/osu/blob/b658d9a681a04101900d5ce6c5b84d56320e08e7/osu.Game/Graphics/OsuColour.cs#L42-L73 +@colour-rank-d: #ff5a5a; +@colour-rank-c: #ff8e5d; +@colour-rank-b: #e3b130; +@colour-rank-a: #88da20; +@colour-rank-s: #02b5c3; +@colour-rank-ss: #de31ae; diff --git a/resources/js/beatmapsets-show/header.tsx b/resources/js/beatmapsets-show/header.tsx index d22d61d9e61..2bf1ff64c37 100644 --- a/resources/js/beatmapsets-show/header.tsx +++ b/resources/js/beatmapsets-show/header.tsx @@ -25,6 +25,19 @@ import Stats from './stats'; const favouritesToShow = 50; +function statusIcon(type: 'storyboard' | 'video') { + const iconClass = type === 'video' ? 'fas fa-film' : 'fas fa-image'; + + return ( +
+ +
+ ); +} + interface DownloadButtonOptions { bottomTextKey?: string; href: string; @@ -352,14 +365,8 @@ export default class Header extends React.Component { private renderStatusBar() { return (
- {this.controller.beatmapset.storyboard && -
- -
- } + {this.controller.beatmapset.video && statusIcon('video')} + {this.controller.beatmapset.storyboard && statusIcon('storyboard')} {trans(`beatmapsets.show.status.${this.controller.currentBeatmap.status}`)} diff --git a/resources/js/scores-show/dial.tsx b/resources/js/scores-show/dial.tsx index d99dd709578..fa37e6dcbb1 100644 --- a/resources/js/scores-show/dial.tsx +++ b/resources/js/scores-show/dial.tsx @@ -3,13 +3,12 @@ import * as d3 from 'd3'; import Rank from 'interfaces/rank'; -import Ruleset from 'interfaces/ruleset'; import * as React from 'react'; interface Props { accuracy: number; - mode: Ruleset; rank: Rank; + rankCutoffs: number[]; } const displayRank: Record = { @@ -24,46 +23,10 @@ const displayRank: Record = { XH: 'SS', }; -const refDataMap: Record = { - // : minimum acc => (higher rank acc - current acc) - // for SS, use minimum accuracy of 0.99 (any less and it's too small) - // actual array is reversed as it's rendered from D to SS clockwise - - // SS: 0.99 => 0.01 - // S: 0.9801 => 0.0099 - // A: 0.9401 => 0.04 - // B: 0.9001 => 0.04 - // C: 0.8501 => 0.05 - // D: 0 => 0.8501 - fruits: [0.8501, 0.05, 0.04, 0.04, 0.0099, 0.01], - // SS: 0.99 => 0.01 - // S: 0.95 => 0.04 - // A: 0.9 => 0.05 - // B: 0.8 => 0.1 - // C: 0.7 => 0.1 - // D: 0 => 0.7 - mania: [0.7, 0.1, 0.1, 0.05, 0.04, 0.01], - // SS: 0.99 => 0.01 - // S: (0.9 * 300 + 0.1 * 100) / 300 = 0.933 => 0.057 - // A: (0.8 * 300 + 0.2 * 100) / 300 = 0.867 => 0.066 - // B: (0.7 * 300 + 0.3 * 100) / 300 = 0.8 => 0.067 - // C: 0.6 => 0.2 - // D: 0 => 0.6 - osu: [0.6, 0.2, 0.067, 0.066, 0.057, 0.01], - // SS: 0.99 => 0.01 - // S: (0.9 * 300 + 0.1 * 50) / 300 = 0.917 => 0.073 - // A: (0.8 * 300 + 0.2 * 50) / 300 = 0.833 => 0.084 - // B: (0.7 * 300 + 0.3 * 50) / 300 = 0.75 => 0.083 - // C: 0.6 => 0.15 - // D: 0 => 0.6 - taiko: [0.6, 0.15, 0.083, 0.084, 0.073, 0.01], -}; - export default function Dial(props: Props) { const arc = d3.arc(); const pie = d3.pie().sortValues(null); const valueData = [props.accuracy, 1 - props.accuracy]; - const refData = refDataMap[props.mode]; return (
@@ -76,7 +39,7 @@ export default function Dial(props: Props) { - {pie(refData).map((d) => ( + {pie(props.rankCutoffs).map((d) => (
- +
diff --git a/resources/js/utils/score-helper.ts b/resources/js/utils/score-helper.ts index 7cb82f4ba32..b4ea926961f 100644 --- a/resources/js/utils/score-helper.ts +++ b/resources/js/utils/score-helper.ts @@ -105,6 +105,66 @@ export function rank(score: SoloScoreJson) { : score.rank; } +export function rankCutoffs(score: SoloScoreJson): number[] { + // for SS, use minimum accuracy of 0.99 (any less and it's too small) + // actual array is reversed as it's rendered from D to SS clockwise + + let absoluteCutoffs: number[] = []; + const ruleset = rulesetName(score.ruleset_id); + + if (shouldReturnLegacyValue(score)) { + switch (ruleset) { + case 'fruits': + absoluteCutoffs = [0, 0.8501, 0.9001, 0.9401, 0.9801, 0.99, 1]; + break; + + case 'mania': + absoluteCutoffs = [0, 0.7, 0.8, 0.9, 0.95, 0.99, 1]; + break; + + case 'osu': + // S: (0.9 * 300 + 0.1 * 100) / 300 = 0.933 + // A: (0.8 * 300 + 0.2 * 100) / 300 = 0.867 + // B: (0.7 * 300 + 0.3 * 100) / 300 = 0.8 + absoluteCutoffs = [0, 0.6, 0.8, 0.867, 0.933, 0.99, 1]; + break; + + case 'taiko': + // S: (0.9 * 300 + 0.1 * 50) / 300 = 0.917 + // A: (0.8 * 300 + 0.2 * 50) / 300 = 0.833 + // B: (0.7 * 300 + 0.3 * 50) / 300 = 0.75 + absoluteCutoffs = [0, 0.6, 0.75, 0.833, 0.917, 0.99, 1]; + break; + } + } else { + switch (ruleset) { + case 'fruits': + // cross-reference: https://github.com/ppy/osu/blob/b658d9a681a04101900d5ce6c5b84d56320e08e7/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs#L108-L135 + absoluteCutoffs = [0, 0.85, 0.9, 0.94, 0.98, 0.99, 1]; + break; + + case 'mania': + case 'osu': + case 'taiko': + // cross-reference: https://github.com/ppy/osu/blob/b658d9a681a04101900d5ce6c5b84d56320e08e7/osu.Game/Rulesets/Scoring/ScoreProcessor.cs#L541-L572 + absoluteCutoffs = [0, 0.7, 0.8, 0.9, 0.95, 0.99, 1]; + break; + } + } + + return differenceBetweenConsecutiveElements(absoluteCutoffs); +} + +function differenceBetweenConsecutiveElements(arr: number[]): number[] { + const result = []; + + for (let i = 1; i < arr.length; ++i) { + result.push(arr[i] - arr[i - 1]); + } + + return result; +} + export function scoreDownloadUrl(score: SoloScoreJson) { if (score.type === 'solo_score') { return route('scores.download', { score: score.id }); diff --git a/resources/views/forum/topics/_moderate_move.blade.php b/resources/views/forum/topics/_moderate_move.blade.php index 624a24d06b5..bcc1d2577da 100644 --- a/resources/views/forum/topics/_moderate_move.blade.php +++ b/resources/views/forum/topics/_moderate_move.blade.php @@ -35,11 +35,17 @@ class="btn-circle btn-circle--topic-nav btn-circle--yellow"