the browser-facing portion of osu!
at master 3.7 kB view raw
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 6declare(strict_types=1); 7 8namespace App\Models\Multiplayer; 9 10use App\Exceptions\InvariantException; 11use App\Models\Model; 12use App\Models\ScoreToken; 13use App\Models\Solo\Score; 14use App\Models\User; 15use Illuminate\Database\Eloquent\Relations\BelongsTo; 16use Illuminate\Database\Eloquent\Relations\HasOne; 17 18/** 19 * @property PlaylistItem $playlistItem 20 * @property int $playlist_item_id 21 * @property Score $score 22 * @property int $score_id 23 * @property User $user 24 * @property int $user_id 25 */ 26class ScoreLink extends Model 27{ 28 public static function complete(ScoreToken $token, array $params): static 29 { 30 return \DB::transaction(function () use ($params, $token) { 31 // multiplayer scores are always preserved. 32 $score = Score::createFromJsonOrExplode([...$params, 'preserve' => true]); 33 34 $playlistItem = $token->playlistItem()->firstOrFail(); 35 $requiredMods = array_column($playlistItem->required_mods, 'acronym'); 36 $mods = array_column($score->data->mods, 'acronym'); 37 $mods = app('mods')->excludeModsAlwaysValidForSubmission($playlistItem->ruleset_id, $mods); 38 39 if (!empty($requiredMods)) { 40 if (!empty(array_diff($requiredMods, $mods))) { 41 throw new InvariantException('This play does not include the mods required.'); 42 } 43 } 44 45 $allowedMods = array_column($playlistItem->allowed_mods, 'acronym'); 46 if (!empty(array_diff($mods, $requiredMods, $allowedMods))) { 47 throw new InvariantException('This play includes mods that are not allowed.'); 48 } 49 50 $token->score()->associate($score)->saveOrExplode(); 51 52 $ret = (new static()) 53 ->playlistItem()->associate($playlistItem) 54 ->score()->associate($score) 55 ->user()->associate($token->user); 56 $ret->saveOrExplode(); 57 58 return $ret; 59 }); 60 } 61 62 public $incrementing = false; 63 public $timestamps = false; 64 65 protected $primaryKey = 'score_id'; 66 protected $table = 'multiplayer_playlist_item_scores'; 67 68 public function playlistItem(): BelongsTo 69 { 70 return $this->belongsTo(PlaylistItem::class, 'playlist_item_id'); 71 } 72 73 public function playlistItemUserHighScore(): HasOne 74 { 75 return $this->hasOne(PlaylistItemUserHighScore::class); 76 } 77 78 public function score(): BelongsTo 79 { 80 return $this->belongsTo(Score::class, 'score_id'); 81 } 82 83 public function user(): BelongsTo 84 { 85 return $this->belongsTo(User::class, 'user_id'); 86 } 87 88 public function getAttribute($key) 89 { 90 return match ($key) { 91 'playlist_item_id', 92 'score_id', 93 'user_id' => $this->getRawAttribute($key), 94 95 'playlistItem', 96 'playlistItemUserHighScore', 97 'score', 98 'user' => $this->getRelationValue($key), 99 }; 100 } 101 102 public function position(): ?int 103 { 104 $score = $this->score; 105 106 if ($score === null) { 107 return null; 108 } 109 110 $query = PlaylistItemUserHighScore 111 ::where('playlist_item_id', $this->playlist_item_id) 112 ->whereHas('user', fn ($userQuery) => $userQuery->default()) 113 ->cursorSort('score_asc', [ 114 'total_score' => $score->total_score, 115 'score_id' => $this->getKey(), 116 ]); 117 118 return 1 + $query->count(); 119 } 120}