the browser-facing portion of osu!
at master 6.6 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 6namespace App\Http\Controllers\Multiplayer; 7 8use App\Http\Controllers\Controller; 9use App\Http\Controllers\Ranking\DailyChallengeController; 10use App\Models\Model; 11use App\Models\Multiplayer\Room; 12use App\Transformers\Multiplayer\RoomTransformer; 13 14class RoomsController extends Controller 15{ 16 public function __construct() 17 { 18 $this->middleware('auth', ['except' => 'show']); 19 $this->middleware('require-scopes:public', ['only' => ['index', 'leaderboard', 'show']]); 20 } 21 22 public function destroy($id) 23 { 24 Room::findOrFail($id)->endGame(\Auth::user()); 25 26 return response(null, 204); 27 } 28 29 /** 30 * Get Multiplayer Rooms 31 * 32 * Returns a list of multiplayer rooms. 33 * 34 * @group Multiplayer 35 * 36 * @queryParam limit Maximum number of results. No-example 37 * @queryParam mode Filter mode; `active` (default), `all`, `ended`, `participated`, `owned`. No-example 38 * @queryParam season_id Season ID to return Rooms from. No-example 39 * @queryParam sort Sort order; `ended`, `created`. No-example 40 * @queryParam type_group `playlists` (default) or `realtime`. No-example 41 */ 42 public function index() 43 { 44 $apiVersion = api_version(); 45 $compactReturn = $apiVersion >= 20220217; 46 $objectReturn = $apiVersion >= 99999999; 47 $params = request()->all(); 48 $params['user'] = auth()->user(); 49 50 $includes = ['host.country', 'playlist.beatmap']; 51 52 if (!$compactReturn) { 53 $includes = [...$includes, 'playlist.beatmap.beatmapset', 'playlist.beatmap.baseMaxCombo']; 54 } 55 56 $search = Room::search($params); 57 $query = $search['query']; 58 59 // temporary workaround for lazer client failing to deserialise `daily_challenge` room category 60 // can be removed 20241129 61 if ($apiVersion < 20240529) { 62 $query->whereNot('category', 'daily_challenge'); 63 } 64 65 $rooms = $query 66 ->with($includes) 67 ->withRecentParticipantIds() 68 ->get(); 69 Room::preloadRecentParticipants($rooms); 70 71 if ($compactReturn) { 72 $rooms->each->findAndSetCurrentPlaylistItem(); 73 $rooms->loadMissing('currentPlaylistItem.beatmap.beatmapset'); 74 75 $roomsJson = json_collection($rooms, new RoomTransformer(), [ 76 'current_playlist_item.beatmap.beatmapset', 77 'difficulty_range', 78 'host.country', 79 'playlist_item_stats', 80 'recent_participants', 81 ]); 82 83 if ($objectReturn) { 84 return array_merge([ 85 'rooms' => $roomsJson, 86 ], cursor_for_response($search['cursorHelper']->next($rooms))); 87 } else { 88 return $roomsJson; 89 } 90 } else { 91 return json_collection($rooms, new RoomTransformer(), [ 92 'host.country', 93 'playlist.beatmap.beatmapset', 94 'playlist.beatmap.checksum', 95 'playlist.beatmap.max_combo', 96 'recent_participants', 97 ]); 98 } 99 } 100 101 public function join($roomId, $userId) 102 { 103 $currentUser = \Auth::user(); 104 // this allows admins/whatever to add users to games in the future 105 if (get_int($userId) !== $currentUser->getKey()) { 106 abort(403); 107 } 108 109 $room = Room::findOrFail($roomId); 110 $room->assertCorrectPassword(get_string(request('password'))); 111 112 $room->join($currentUser); 113 114 return RoomTransformer::createShowResponse($room); 115 } 116 117 public function leaderboard($roomId) 118 { 119 $limit = \Number::clamp(get_int(request('limit')) ?? Model::PER_PAGE, 1, 50); 120 $room = Room::findOrFail($roomId); 121 122 // leaderboard currently requires auth so auth()->check() is not required. 123 $userScore = $room->topScores()->where('user_id', auth()->id())->first(); 124 125 return [ 126 'leaderboard' => json_collection( 127 $room->topScores()->paginate($limit), 128 'Multiplayer\UserScoreAggregate', 129 ['user.country'] 130 ), 131 'user_score' => $userScore !== null ? json_item( 132 $userScore, 133 'Multiplayer\UserScoreAggregate', 134 ['position', 'user.country'] 135 ) : null, 136 ]; 137 } 138 139 public function part($roomId, $userId) 140 { 141 $currentUser = \Auth::user(); 142 // this allows admins/host/whoever to remove users from games in the future 143 if (get_int($userId) !== $currentUser->getKey()) { 144 abort(403); 145 } 146 147 $room = Room::findOrFail($roomId); 148 $room->part($currentUser); 149 150 return response([], 204); 151 } 152 153 public function show($id) 154 { 155 if ($id === 'latest') { 156 $room = Room::featured()->last(); 157 158 if ($room === null) { 159 abort(404); 160 } 161 } else { 162 $room = Room::findOrFail($id); 163 } 164 165 if (is_api_request()) { 166 return RoomTransformer::createShowResponse($room); 167 } 168 169 if ($room->category === 'daily_challenge') { 170 return ujs_redirect(route('daily-challenge.show', DailyChallengeController::roomId($room))); 171 } 172 173 $playlistItemsQuery = $room->playlist(); 174 if ($room->isRealtime()) { 175 $playlistItemsQuery->whereHas('scoreLinks'); 176 } 177 $beatmaps = $playlistItemsQuery->with('beatmap.beatmapset.beatmaps')->get()->pluck('beatmap'); 178 $beatmapsets = $beatmaps->pluck('beatmapset'); 179 $highScores = $room->topScores()->paginate(); 180 $spotlightRooms = Room::featured()->orderBy('id', 'DESC')->get(); 181 182 $userScore = ($currentUser = \Auth::user()) === null 183 ? null 184 : $room->topScores()->whereBelongsTo($currentUser)->first(); 185 186 return ext_view('multiplayer.rooms.show', [ 187 'beatmaps' => $beatmaps, 188 'beatmapsets' => $beatmapsets, 189 'room' => $room, 190 'rooms' => $spotlightRooms, 191 'scores' => $highScores, 192 'userScore' => $userScore, 193 ]); 194 } 195 196 public function store() 197 { 198 $room = (new Room())->startGame(\Auth::user(), \Request::all()); 199 200 return RoomTransformer::createShowResponse($room); 201 } 202}