mirror of
https://github.com/ppy/osu.git
synced 2026-06-09 01:03:55 +08:00
c73af24bbc
- Added a small breakdown animation to the results screen. - Added individual multiplier text to user corner pieces. - Removed global multiplier text from the stage overlay, since we're going with individual multipliers. https://github.com/user-attachments/assets/47cec478-6ad5-49fa-9f69-b6df079ce41c (This is dev design and I'm focusing on functionality rather than presentation for now.) The implementation might be over-engineered a bit, but I'm not sure on the final structure of things and I want to give a bit of elasticity to the system, so I've frankensteined a new "damage sources" list inside `RankedPlayDamageInfo` that the results screen uses to display the breakdown. If the server doesn't provide a breakdown (e.g. by client and server being slightly out-of-date), the results screen will behave as it does on current `master`. In other words this is forwards/backwards compatible. --------- Co-authored-by: Dean Herbert <pe@ppy.sh>
353 lines
13 KiB
C#
353 lines
13 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.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using NUnit.Framework;
|
|
using osu.Framework.Extensions;
|
|
using osu.Framework.Screens;
|
|
using osu.Framework.Utils;
|
|
using osu.Game.Online.API;
|
|
using osu.Game.Online.API.Requests.Responses;
|
|
using osu.Game.Online.Multiplayer;
|
|
using osu.Game.Online.Multiplayer.MatchTypes.RankedPlay;
|
|
using osu.Game.Online.Rooms;
|
|
using osu.Game.Rulesets.Scoring;
|
|
using osu.Game.Scoring;
|
|
using osu.Game.Screens.OnlinePlay.Matchmaking.RankedPlay;
|
|
using osu.Game.Tests.Visual.Multiplayer;
|
|
|
|
namespace osu.Game.Tests.Visual.RankedPlay
|
|
{
|
|
public partial class TestSceneResultsScreen : MultiplayerTestScene
|
|
{
|
|
private RankedPlayScreen screen = null!;
|
|
|
|
public override void SetUpSteps()
|
|
{
|
|
base.SetUpSteps();
|
|
|
|
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.RankedPlay)));
|
|
WaitForJoined();
|
|
|
|
AddStep("add other user", () => MultiplayerClient.AddUser(new MultiplayerRoomUser(2)));
|
|
|
|
AddStep("load screen", () => LoadScreen(screen = new RankedPlayScreen(MultiplayerClient.ClientRoom!)));
|
|
AddUntilStep("screen loaded", () => screen.IsLoaded);
|
|
|
|
setupRequestHandler();
|
|
}
|
|
|
|
[Test]
|
|
public void TestBasic()
|
|
{
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 123_456,
|
|
Damage = 123_456,
|
|
OldLife = 500_000,
|
|
NewLife = 500_000 - 123_456
|
|
};
|
|
|
|
userInfo.Life = 500_000 - 123_456;
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
}
|
|
|
|
[Test]
|
|
public void TestMultiplier()
|
|
{
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
state.DamageMultiplier = 1.5;
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageMultiplier = 0.5;
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 123_456,
|
|
Damage = 123_456 * 2,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000 - 123_456 * 2,
|
|
Multiplier = 2,
|
|
DirectDamage = 123_456,
|
|
};
|
|
|
|
userInfo.Life = 1_000_000 - 123_456 * 2;
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageMultiplier = 0.5;
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
Multiplier = 2,
|
|
DirectDamage = 0,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
}
|
|
|
|
[Test]
|
|
public void TestMissingScores()
|
|
{
|
|
AddStep("setup request handler", () =>
|
|
{
|
|
Func<APIRequest, bool>? defaultRequestHandler = ((DummyAPIAccess)API).HandleRequest;
|
|
|
|
((DummyAPIAccess)API).HandleRequest = request =>
|
|
{
|
|
switch (request)
|
|
{
|
|
case IndexPlaylistScoresRequest index:
|
|
index.TriggerSuccess(new IndexedMultiplayerScores());
|
|
return true;
|
|
|
|
default:
|
|
return defaultRequestHandler?.Invoke(request) ?? false;
|
|
}
|
|
};
|
|
});
|
|
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
state.DamageMultiplier = 2;
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 123_456,
|
|
Damage = 123_456 * 2,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000 - 123_456 * 2,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
}
|
|
|
|
[Test]
|
|
[Explicit("Test exercises correct stopping of audio playback. Has no assertions, only useful when checked manually by a human.")]
|
|
public void TestAllSamplesStopOnExit()
|
|
{
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 123_456,
|
|
Damage = 123_456,
|
|
OldLife = 500_000,
|
|
NewLife = 500_000 - 123_456,
|
|
};
|
|
|
|
userInfo.Life = 500_000 - 123_456;
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
AddWaitStep("wait for samples to start playing", 5);
|
|
AddRepeatStep("exit", () => screen.Exit(), 2);
|
|
}
|
|
|
|
[Test]
|
|
public void TestDamageBreakdown()
|
|
{
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 600_000,
|
|
Damage = 1_100_000,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1,
|
|
DirectDamage = 500_000,
|
|
Multiplier = 2,
|
|
BonusDamage = 100_000
|
|
};
|
|
|
|
userInfo.Life = 653_088;
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
}
|
|
|
|
[Test]
|
|
public void TestDamageBreakdownWithNegativeValues()
|
|
{
|
|
AddStep("set results state", () => MultiplayerClient.RankedPlayChangeStage(RankedPlayStage.Results, state =>
|
|
{
|
|
int losingPlayer = state.Users.Keys.First();
|
|
|
|
foreach (var (id, userInfo) in state.Users)
|
|
{
|
|
if (id == losingPlayer)
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 500_000,
|
|
Damage = 200_000,
|
|
OldLife = 1_000_000,
|
|
NewLife = 800_000,
|
|
DirectDamage = 600_000,
|
|
Multiplier = 0.5,
|
|
BonusDamage = -100_000
|
|
};
|
|
|
|
userInfo.Life = 653_088;
|
|
}
|
|
else
|
|
{
|
|
userInfo.DamageInfo = new RankedPlayDamageInfo
|
|
{
|
|
RawDamage = 0,
|
|
Damage = 0,
|
|
OldLife = 1_000_000,
|
|
NewLife = 1_000_000,
|
|
};
|
|
}
|
|
}
|
|
}).WaitSafely());
|
|
}
|
|
|
|
private void setupRequestHandler()
|
|
{
|
|
AddStep("setup request handler", () =>
|
|
{
|
|
Func<APIRequest, bool>? defaultRequestHandler = ((DummyAPIAccess)API).HandleRequest;
|
|
|
|
((DummyAPIAccess)API).HandleRequest = request =>
|
|
{
|
|
switch (request)
|
|
{
|
|
case IndexPlaylistScoresRequest index:
|
|
var result = new IndexedMultiplayerScores();
|
|
|
|
foreach (int userId in new[] { 2, API.LocalUser.Value.OnlineID })
|
|
{
|
|
result.Scores.Add(new MultiplayerScore
|
|
{
|
|
ID = userId,
|
|
Accuracy = RNG.NextSingle(),
|
|
EndedAt = DateTimeOffset.Now,
|
|
Passed = true,
|
|
Rank = (ScoreRank)RNG.Next((int)ScoreRank.D, (int)ScoreRank.XH),
|
|
MaxCombo = RNG.Next(1000),
|
|
TotalScore = userId == 2 ? 750_000 : 750_000 - 123_456,
|
|
Statistics = new Dictionary<HitResult, int>
|
|
{
|
|
[HitResult.Miss] = 1,
|
|
[HitResult.Meh] = 50,
|
|
[HitResult.Ok] = 100,
|
|
[HitResult.Good] = 200,
|
|
[HitResult.Great] = 300,
|
|
[HitResult.Perfect] = 320,
|
|
[HitResult.SmallTickHit] = 50,
|
|
[HitResult.SmallTickMiss] = 25,
|
|
[HitResult.LargeTickHit] = 100,
|
|
[HitResult.LargeTickMiss] = 50,
|
|
[HitResult.SmallBonus] = 10,
|
|
[HitResult.LargeBonus] = 50
|
|
},
|
|
MaximumStatistics = new Dictionary<HitResult, int>
|
|
{
|
|
[HitResult.Perfect] = 971,
|
|
[HitResult.SmallTickHit] = 75,
|
|
[HitResult.LargeTickHit] = 150,
|
|
[HitResult.SmallBonus] = 10,
|
|
[HitResult.LargeBonus] = 50,
|
|
},
|
|
User = new APIUser
|
|
{
|
|
Id = userId,
|
|
Username = $"user {userId}",
|
|
}
|
|
});
|
|
}
|
|
|
|
index.TriggerSuccess(result);
|
|
return true;
|
|
|
|
default:
|
|
return defaultRequestHandler?.Invoke(request) ?? false;
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
}
|