Skip to content

Commit

Permalink
Update score table structure
Browse files Browse the repository at this point in the history
- a bunch of attributes are moved out of the json
- pp and legacy id moved to main scores table
  • Loading branch information
nanaya committed Jan 16, 2024
1 parent 48d63ae commit efcdec8
Show file tree
Hide file tree
Showing 24 changed files with 358 additions and 324 deletions.
2 changes: 1 addition & 1 deletion app/Http/Controllers/BeatmapsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ public function soloScores($id)
'type' => $type,
'user' => $currentUser,
]);
$scores = $esFetch->all()->loadMissing(['beatmap', 'performance', 'user.country', 'user.userProfileCustomization']);
$scores = $esFetch->all()->loadMissing(['beatmap', 'user.country', 'user.userProfileCustomization']);
$userScore = $esFetch->userBest();
$scoreTransformer = new ScoreTransformer(ScoreTransformer::TYPE_SOLO);

Expand Down
2 changes: 0 additions & 2 deletions app/Jobs/RemoveBeatmapsetSoloScores.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
use App\Models\Beatmap;
use App\Models\Beatmapset;
use App\Models\Solo\Score;
use App\Models\Solo\ScorePerformance;
use DB;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
Expand Down Expand Up @@ -68,7 +67,6 @@ private function deleteScores(Collection $scores): void
$scoresQuery->update(['preserve' => false]);
$this->scoreSearch->queueForIndex($this->schemas, $ids);
DB::transaction(function () use ($ids, $scoresQuery): void {
ScorePerformance::whereKey($ids)->delete();
$scoresQuery->delete();
});
}
Expand Down
4 changes: 2 additions & 2 deletions app/Libraries/Search/ScoreSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ public function getQuery(): BoolQuery
$beforeTotalScore = $this->params->beforeTotalScore;
if ($beforeTotalScore === null && $this->params->beforeScore !== null) {
$beforeTotalScore = $this->params->beforeScore->isLegacy()
? $this->params->beforeScore->data->legacyTotalScore
: $this->params->beforeScore->data->totalScore;
? $this->params->beforeScore->legacy_total_score
: $this->params->beforeScore->total_score;
}
if ($beforeTotalScore !== null) {
$scoreQuery = (new BoolQuery())->shouldMatch(1);
Expand Down
6 changes: 3 additions & 3 deletions app/Models/Multiplayer/PlaylistItemUserHighScore.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public static function scoresAround(ScoreLink $scoreLink): array
{
$placeholder = new static([
'score_id' => $scoreLink->getKey(),
'total_score' => $scoreLink->score->data->totalScore,
'total_score' => $scoreLink->score->total_score,
]);

static $typeOptions = [
Expand Down Expand Up @@ -117,10 +117,10 @@ public function updateWithScoreLink(ScoreLink $scoreLink): void
$score = $scoreLink->score;

$this->fill([
'accuracy' => $score->data->accuracy,
'accuracy' => $score->accuracy,
'pp' => $score->pp,
'score_id' => $scoreLink->getKey(),
'total_score' => $score->data->totalScore,
'total_score' => $score->total_score,
])->save();
}
}
2 changes: 1 addition & 1 deletion app/Models/Multiplayer/ScoreLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function position(): ?int
$query = PlaylistItemUserHighScore
::where('playlist_item_id', $this->playlist_item_id)
->cursorSort('score_asc', [
'total_score' => $score->data->totalScore,
'total_score' => $score->total_score,
'score_id' => $this->getKey(),
]);

Expand Down
8 changes: 4 additions & 4 deletions app/Models/Multiplayer/UserScoreAggregate.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function addScoreLink(ScoreLink $scoreLink, ?PlaylistItemUserHighScore $h
$scoreLink->playlist_item_id,
);

if ($score->data->passed && $score->data->totalScore > $highestScore->total_score) {
if ($score->passed && $score->total_score > $highestScore->total_score) {
$this->updateUserTotal($scoreLink, $highestScore);
$highestScore->updateWithScoreLink($scoreLink);
}
Expand Down Expand Up @@ -134,7 +134,7 @@ public function recalculate()
$scoreLinks = ScoreLink
::whereHas('playlistItem', fn ($q) => $q->where('room_id', $this->room_id))
->where('user_id', $this->user_id)
->with('score.performance')
->with('score')
->get();
foreach ($scoreLinks as $scoreLink) {
$this->addScoreLink(
Expand Down Expand Up @@ -221,8 +221,8 @@ private function updateUserTotal(ScoreLink $currentScoreLink, PlaylistItemUserHi

$current = $currentScoreLink->score;

$this->total_score += $current->data->totalScore;
$this->accuracy += $current->data->accuracy;
$this->total_score += $current->total_score;
$this->accuracy += $current->accuracy;
$this->pp += $current->pp;
$this->completed++;
$this->last_score_id = $currentScoreLink->getKey();
Expand Down
29 changes: 8 additions & 21 deletions app/Models/Score/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

namespace App\Models\Score;

use App\Enums\Ruleset;
use App\Exceptions\ClassNotFoundException;
use App\Libraries\Mods;
use App\Models\Beatmap;
use App\Models\Model as BaseModel;
use App\Models\Solo\ScoreData;
use App\Models\Traits\Scoreable;
use App\Models\User;

Expand Down Expand Up @@ -161,47 +161,34 @@ public function getMode(): string
return snake_case(get_class_basename(static::class));
}

protected function getData()
public function statistics(): array
{
$mods = array_map(fn ($m) => ['acronym' => $m, 'settings' => []], $this->enabled_mods);
$statistics = [
'miss' => $this->countmiss,
'great' => $this->count300,
];
$ruleset = $this->getMode();
$ruleset = Ruleset::tryFromName($this->getMode());
switch ($ruleset) {
case 'osu':
case Ruleset::osu:
$statistics['ok'] = $this->count100;
$statistics['meh'] = $this->count50;
break;
case 'taiko':
case Ruleset::taiko:
$statistics['ok'] = $this->count100;
break;
case 'fruits':
case Ruleset::catch:
$statistics['large_tick_hit'] = $this->count100;
$statistics['small_tick_hit'] = $this->count50;
$statistics['small_tick_miss'] = $this->countkatu;
break;
case 'mania':
case Ruleset::mania:
$statistics['perfect'] = $this->countgeki;
$statistics['good'] = $this->countkatu;
$statistics['ok'] = $this->count100;
$statistics['meh'] = $this->count50;
break;
}

return new ScoreData([
'accuracy' => $this->accuracy(),
'beatmap_id' => $this->beatmap_id,
'ended_at' => $this->date_json,
'max_combo' => $this->maxcombo,
'mods' => $mods,
'passed' => $this->pass,
'rank' => $this->rank,
'ruleset_id' => Beatmap::modeInt($ruleset),
'statistics' => $statistics,
'total_score' => $this->score,
'user_id' => $this->user_id,
]);
return $statistics;
}
}
135 changes: 88 additions & 47 deletions app/Models/Solo/Score.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

namespace App\Models\Solo;

use App\Enums\ScoreRank;
use App\Exceptions\InvariantException;
use App\Libraries\Score\UserRank;
use App\Libraries\Search\ScoreSearchParams;
use App\Models\Beatmap;
Expand All @@ -16,7 +18,6 @@
use App\Models\ScoreToken;
use App\Models\Traits;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use LaravelRedis;
use Storage;
Expand Down Expand Up @@ -45,53 +46,69 @@ class Score extends Model implements Traits\ReportableInterface

protected $casts = [
'data' => ScoreData::class,
'ended_at' => 'datetime',
'has_replay' => 'boolean',
'passed' => 'boolean',
'preserve' => 'boolean',
'ranked' => 'boolean',
'started_at' => 'datetime',
];

public static function createFromJsonOrExplode(array $params)
public static function createFromJsonOrExplode(array $params): static
{
$score = new static([
'beatmap_id' => $params['beatmap_id'],
'ruleset_id' => $params['ruleset_id'],
'user_id' => $params['user_id'],
'data' => $params,
]);
$params['data'] = [
'maximum_statistics' => $params['maximum_statistics'] ?? [],
'mods' => $params['mods'] ?? [],
'statistics' => $params['statistics'] ?? [],
];
unset(
$params['maximum_statistics'],
$params['mods'],
$params['statistics'],
);

$score = new static($params);

$score->data->assertCompleted();
$score->assertCompleted();

// this should potentially just be validation rather than applying this logic here, but
// older lazer builds potentially submit incorrect details here (and we still want to
// accept their scores.
if (!$score->data->passed) {
$score->data->rank = 'F';
if (!$score->passed) {
$score->rank = 'F';
}

$score->saveOrExplode();

return $score;
}

public static function extractParams(array $params, ScoreToken|MultiplayerScoreLink $scoreToken): array
public static function extractParams(array $rawParams, ScoreToken|MultiplayerScoreLink $scoreToken): array
{
return [
...get_params($params, null, [
'accuracy:float',
'max_combo:int',
'maximum_statistics:array',
'passed:bool',
'rank:string',
'statistics:array',
'total_score:int',
]),
'beatmap_id' => $scoreToken->beatmap_id,
'build_id' => $scoreToken->build_id,
'ended_at' => json_time(Carbon::now()),
'mods' => app('mods')->parseInputArray($scoreToken->ruleset_id, get_arr($params['mods'] ?? null) ?? []),
'ruleset_id' => $scoreToken->ruleset_id,
'started_at' => $scoreToken->created_at_json,
'user_id' => $scoreToken->user_id,
];
$params = get_params($rawParams, null, [
'accuracy:float',
'max_combo:int',
'maximum_statistics:array',
'mods:array',
'passed:bool',
'rank:string',
'statistics:array',
'total_score:int',
]);

$params['maximum_statistics'] ??= [];
$params['statistics'] ??= [];

$params['mods'] = app('mods')->parseInputArray($scoreToken->ruleset_id, $params['mods'] ?? []);

$params['beatmap_id'] = $scoreToken->beatmap_id;
$params['build_id'] = $scoreToken->build_id;
$params['ended_at'] = new \DateTime();
$params['ruleset_id'] = $scoreToken->ruleset_id;
$params['started_at'] = $scoreToken->created_at;
$params['user_id'] = $scoreToken->user_id;

return $params;
}

/**
Expand Down Expand Up @@ -119,11 +136,6 @@ public function beatmap()
return $this->belongsTo(Beatmap::class, 'beatmap_id');
}

public function performance()
{
return $this->hasOne(ScorePerformance::class, 'score_id');
}

public function user()
{
return $this->belongsTo(User::class, 'user_id');
Expand All @@ -147,22 +159,34 @@ public function scopeIndexable(Builder $query): Builder
public function getAttribute($key)
{
return match ($key) {
'accuracy',
'beatmap_id',
'build_id',
'id',
'legacy_score_id',
'legacy_total_score',
'max_combo',
'pp',
'ruleset_id',
'total_score',
'unix_updated_at',
'user_id' => $this->getRawAttribute($key),

'rank' => $this->getRawAttribute($key) ?? 'F',

'data' => $this->getClassCastableAttributeValue($key, $this->getRawAttribute($key)),

'has_replay',
'preserve',
'ranked' => (bool) $this->getRawAttribute($key),
'passed',
'preserve' => (bool) $this->getRawAttribute($key),

'ranked' => (bool) ($this->getRawAttribute($key) ?? true),

'created_at' => $this->getTimeFast($key),
'created_at_json' => $this->getJsonTimeFast($key),
'ended_at',
'started_at' => $this->getTimeFast($key),

'pp' => $this->performance?->pp,
'ended_at_json',
'started_at_json' => $this->getJsonTimeFast($key),

'beatmap',
'performance',
Expand All @@ -171,6 +195,23 @@ public function getAttribute($key)
};
}

public function assertCompleted(): void
{
if (ScoreRank::tryFrom($this->rank ?? '') === null) {
throw new InvariantException("'{$this->rank}' is not a valid rank.");
}

foreach (['total_score', 'accuracy', 'max_combo', 'passed'] as $field) {
if (!present($this->$field)) {
throw new InvariantException("field missing: '{$field}'");
}
}

if ($this->data->statistics->isEmpty()) {
throw new InvariantException("field cannot be empty: 'statistics'");
}
}

public function createLegacyEntryOrExplode()
{
$score = $this->makeLegacyEntry();
Expand All @@ -193,12 +234,12 @@ public function getReplayFile(): ?string

public function isLegacy(): bool
{
return $this->data->buildId === null;
return $this->legacy_score_id !== null;
}

public function legacyScore(): ?LegacyScore\Best\Model
{
$id = $this->data->legacyScoreId;
$id = $this->legacy_score_id;

return $id === null
? null
Expand All @@ -216,11 +257,11 @@ public function makeLegacyEntry(): LegacyScore\Model
'beatmapset_id' => $this->beatmap?->beatmapset_id ?? 0,
'countmiss' => $statistics->miss,
'enabled_mods' => app('mods')->idsToBitset(array_column($data->mods, 'acronym')),
'maxcombo' => $data->maxCombo,
'pass' => $data->passed,
'perfect' => $data->passed && $statistics->miss + $statistics->large_tick_miss === 0,
'rank' => $data->rank,
'score' => $data->totalScore,
'maxcombo' => $this->max_combo,
'pass' => $this->passed,
'perfect' => $this->passed && $statistics->miss + $statistics->large_tick_miss === 0,
'rank' => $this->rank,
'score' => $this->total_score,
'scorechecksum' => "\0",
'user_id' => $this->user_id,
]);
Expand Down
Loading

0 comments on commit efcdec8

Please sign in to comment.