osu/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs

241 lines
9.7 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.OnlinePlay
{
/// <summary>
/// Represents a handler which pretends to be a server, handling room retrieval and manipulation requests
/// and returning a roughly expected state, without the need for a server to be running.
/// </summary>
public class TestRoomRequestsHandler
{
public IReadOnlyList<Room> ServerSideRooms => serverSideRooms;
private readonly List<Room> serverSideRooms = new List<Room>();
private int currentRoomId = 1;
private int currentPlaylistItemId = 1;
private int currentScoreId = 1;
/// <summary>
/// Handles an API request, while also updating the local state to match
/// how the server would eventually respond and update an <see cref="RoomManager"/>.
/// </summary>
/// <param name="request">The API request to handle.</param>
/// <param name="localUser">The local user to store in responses where required.</param>
/// <param name="beatmapManager">The beatmap manager to attempt to retrieve beatmaps from, prior to returning dummy beatmaps.</param>
/// <returns>Whether the request was successfully handled.</returns>
public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager beatmapManager)
{
switch (request)
{
case CreateRoomRequest createRoomRequest:
var apiRoom = cloneRoom(createRoomRequest.Room);
// Passwords are explicitly not copied between rooms.
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
AddServerSideRoom(apiRoom, localUser);
var responseRoom = new APICreatedRoom();
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
createRoomRequest.TriggerSuccess(responseRoom);
return true;
case JoinRoomRequest joinRoomRequest:
{
var room = ServerSideRooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
if (joinRoomRequest.Password != room.Password.Value)
{
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
return true;
}
joinRoomRequest.TriggerSuccess();
return true;
}
case GetRoomLeaderboardRequest roomLeaderboardRequest:
roomLeaderboardRequest.TriggerSuccess(new APILeaderboard
{
Leaderboard = new List<APIUserScoreAggregate>
{
new APIUserScoreAggregate
{
TotalScore = 1000000,
TotalAttempts = 5,
CompletedBeatmaps = 2,
User = new APIUser { Username = "best user" }
},
new APIUserScoreAggregate
{
TotalScore = 50,
TotalAttempts = 1,
CompletedBeatmaps = 1,
User = new APIUser { Username = "worst user" }
}
}
});
return true;
case PartRoomRequest partRoomRequest:
partRoomRequest.TriggerSuccess();
return true;
case GetRoomsRequest getRoomsRequest:
var roomsWithoutParticipants = new List<Room>();
foreach (var r in ServerSideRooms)
roomsWithoutParticipants.Add(createResponseRoom(r, false));
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
return true;
case GetRoomRequest getRoomRequest:
getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
return true;
case CreateRoomScoreRequest createRoomScoreRequest:
createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 });
return true;
case SubmitRoomScoreRequest submitRoomScoreRequest:
submitRoomScoreRequest.TriggerSuccess(new MultiplayerScore
{
ID = currentScoreId++,
Accuracy = 1,
EndedAt = DateTimeOffset.Now,
Passed = true,
Rank = ScoreRank.S,
MaxCombo = 1000,
TotalScore = 1000000,
User = localUser,
Statistics = new Dictionary<HitResult, int>()
});
return true;
case GetBeatmapRequest getBeatmapRequest:
{
getBeatmapRequest.TriggerSuccess(createResponseBeatmaps(getBeatmapRequest.BeatmapInfo.OnlineID).Single());
return true;
}
case GetBeatmapsRequest getBeatmapsRequest:
{
getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = createResponseBeatmaps(getBeatmapsRequest.BeatmapIds.ToArray()) });
return true;
}
case GetBeatmapSetRequest getBeatmapSetRequest:
{
var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId
? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID)
: beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID);
if (baseBeatmap == null)
{
baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo;
baseBeatmap.OnlineID = getBeatmapSetRequest.ID;
baseBeatmap.BeatmapSet!.OnlineID = getBeatmapSetRequest.ID;
}
getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap));
return true;
}
}
List<APIBeatmap> createResponseBeatmaps(params int[] beatmapIds)
{
var result = new List<APIBeatmap>();
foreach (int id in beatmapIds)
{
var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id);
if (baseBeatmap == null)
{
baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo;
baseBeatmap.OnlineID = id;
baseBeatmap.BeatmapSet!.OnlineID = id;
}
result.Add(OsuTestScene.CreateAPIBeatmap(baseBeatmap));
}
return result;
}
return false;
}
/// <summary>
/// Adds a room to a local "server-side" list that's returned when a <see cref="GetRoomsRequest"/> is fired.
/// </summary>
/// <param name="room">The room.</param>
/// <param name="host">The room host.</param>
public void AddServerSideRoom(Room room, APIUser host)
{
room.RoomID.Value ??= currentRoomId++;
room.Host.Value = host;
for (int i = 0; i < room.Playlist.Count; i++)
{
room.Playlist[i].ID = currentPlaylistItemId++;
room.Playlist[i].OwnerID = room.Host.Value.OnlineID;
}
serverSideRooms.Add(room);
}
private Room createResponseRoom(Room room, bool withParticipants)
{
var responseRoom = cloneRoom(room);
// Password is hidden from the response, and is only propagated via HasPassword.
bool hadPassword = responseRoom.HasPassword.Value;
responseRoom.Password.Value = null;
responseRoom.HasPassword.Value = hadPassword;
if (!withParticipants)
responseRoom.RecentParticipants.Clear();
return responseRoom;
}
private Room cloneRoom(Room source)
{
var result = JsonConvert.DeserializeObject<Room>(JsonConvert.SerializeObject(source));
Debug.Assert(result != null);
// Playlist item IDs aren't serialised.
if (source.CurrentPlaylistItem.Value != null)
result.CurrentPlaylistItem.Value.ID = source.CurrentPlaylistItem.Value.ID;
for (int i = 0; i < source.Playlist.Count; i++)
result.Playlist[i].ID = source.Playlist[i].ID;
return result;
}
}
}