mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 13:32:54 +08:00
Merge branch 'master' into chat-mention-fix
This commit is contained in:
commit
51b6b9d857
@ -27,10 +27,10 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2021.725.0",
|
||||
"version": "2021.1210.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1203.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1206.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1210.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
|
@ -70,7 +70,9 @@ namespace osu.Desktop
|
||||
if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
|
||||
return stableInstallPath;
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
||||
@ -113,7 +115,7 @@ namespace osu.Desktop
|
||||
base.LoadComplete();
|
||||
|
||||
if (!noVersionOverlay)
|
||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
|
||||
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add);
|
||||
|
||||
LoadComponentAsync(new DiscordRichPresence(), Add);
|
||||
|
||||
|
@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -11,49 +11,65 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||
{
|
||||
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||
private const int min_delta_time = 25;
|
||||
private const float maximum_slider_radius = normalized_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalized_radius * 1.8f;
|
||||
private const float maximum_slider_radius = normalised_radius * 2.4f;
|
||||
private const float assumed_slider_radius = normalised_radius * 1.8f;
|
||||
|
||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double JumpDistance { get; private set; }
|
||||
public readonly double StrainTime;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Normalised distance from the "lazy" end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// <para>
|
||||
/// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public double MovementDistance { get; private set; }
|
||||
public double LazyJumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Normalised shortest distance to consider for a jump between the previous <see cref="OsuDifficultyHitObject"/> and this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is bounded from above by <see cref="LazyJumpDistance"/>, and is smaller than the former if a more natural path is able to be taken through the previous <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Suppose a linear slider - circle pattern.
|
||||
/// <br />
|
||||
/// Following the slider lazily (see: <see cref="LazyJumpDistance"/>) will result in underestimating the true end position of the slider as being closer towards the start position.
|
||||
/// As a result, <see cref="LazyJumpDistance"/> overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end,
|
||||
/// such that the jump is felt as only starting from the slider's true end position.
|
||||
/// <br />
|
||||
/// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider.
|
||||
/// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path.
|
||||
/// </example>
|
||||
public double MinimumJumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time taken to travel through <see cref="MinimumJumpDistance"/>, with a minimum value of 25ms.
|
||||
/// </summary>
|
||||
public double MinimumJumpTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalised distance between the start and end position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double TravelDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The time taken to travel through <see cref="TravelDistance"/>, with a minimum value of 25ms for a non-zero distance.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Angle the player has to take to hit this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// Calculated as the angle between the circles (current-2, current-1, current).
|
||||
/// </summary>
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the end time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double MovementTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/> to the end time of the same previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public double TravelTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||
/// </summary>
|
||||
public readonly double StrainTime;
|
||||
|
||||
private readonly OsuHitObject lastLastObject;
|
||||
private readonly OsuHitObject lastObject;
|
||||
|
||||
@ -71,12 +87,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
private void setDistances(double clockRate)
|
||||
{
|
||||
if (BaseObject is Slider currentSlider)
|
||||
{
|
||||
computeSliderCursorPosition(currentSlider);
|
||||
TravelDistance = currentSlider.LazyTravelDistance;
|
||||
TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
}
|
||||
|
||||
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
||||
if (BaseObject is Spinner || lastObject is Spinner)
|
||||
return;
|
||||
|
||||
// We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||
float scalingFactor = normalized_radius / (float)BaseObject.Radius;
|
||||
float scalingFactor = normalised_radius / (float)BaseObject.Radius;
|
||||
|
||||
if (BaseObject.Radius < 30)
|
||||
{
|
||||
@ -85,29 +108,40 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
|
||||
LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
MinimumJumpTime = StrainTime;
|
||||
MinimumJumpDistance = LazyJumpDistance;
|
||||
|
||||
if (lastObject is Slider lastSlider)
|
||||
{
|
||||
computeSliderCursorPosition(lastSlider);
|
||||
TravelDistance = lastSlider.LazyTravelDistance;
|
||||
TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
|
||||
double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||
MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time);
|
||||
|
||||
//
|
||||
// There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects.
|
||||
//
|
||||
// 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject.
|
||||
//
|
||||
// <======o==> ← slider
|
||||
// | ← most natural jump path
|
||||
// o ← a follow-up hitcircle
|
||||
//
|
||||
// In this case the most natural jump path is approximated by LazyJumpDistance.
|
||||
//
|
||||
// 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject.
|
||||
//
|
||||
// <======o==>---o
|
||||
// ↑
|
||||
// most natural jump path
|
||||
//
|
||||
// In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject.
|
||||
//
|
||||
// Thus, the player is assumed to jump the minimum of these two distances in all cases.
|
||||
//
|
||||
|
||||
// Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
|
||||
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
|
||||
|
||||
// For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
|
||||
// such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
|
||||
// In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
|
||||
// Additional distance is removed based on position of jump relative to slider follow circle radius.
|
||||
// JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible.
|
||||
MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
|
||||
}
|
||||
else
|
||||
{
|
||||
MovementTime = StrainTime;
|
||||
MovementDistance = JumpDistance;
|
||||
MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius));
|
||||
}
|
||||
|
||||
if (lastLastObject != null && !(lastLastObject is Spinner))
|
||||
@ -139,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived.
|
||||
var currCursorPosition = slider.StackedPosition;
|
||||
double scalingFactor = normalized_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used.
|
||||
|
||||
for (int i = 1; i < slider.NestedHitObjects.Count; i++)
|
||||
{
|
||||
@ -167,7 +201,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
else if (currMovementObj is SliderRepeat)
|
||||
{
|
||||
// For a slider repeat, assume a tighter movement threshold to better assess repeat sliders.
|
||||
requiredMovement = normalized_radius;
|
||||
requiredMovement = normalised_radius;
|
||||
}
|
||||
|
||||
if (currMovementLength > requiredMovement)
|
||||
|
@ -44,24 +44,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
|
||||
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
|
||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime;
|
||||
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
|
||||
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object
|
||||
|
||||
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
|
||||
}
|
||||
|
||||
// As above, do the same for the previous hitobject.
|
||||
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
|
||||
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime;
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider && withSliders)
|
||||
{
|
||||
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime;
|
||||
double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime;
|
||||
|
||||
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
|
||||
}
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||
}
|
||||
|
||||
// Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||
@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
if (Math.Max(prevVelocity, currVelocity) != 0)
|
||||
{
|
||||
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
|
||||
prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime;
|
||||
currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime;
|
||||
prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime;
|
||||
currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime;
|
||||
|
||||
// Scale with ratio of difference compared to 0.5 * max dist.
|
||||
double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2);
|
||||
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
// Reward for % distance slowed down compared to previous, paying attention to not award overlap
|
||||
double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity)
|
||||
// do not award overlap
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2);
|
||||
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2);
|
||||
|
||||
// Choose the largest bonus, multiplied by ratio.
|
||||
velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio;
|
||||
@ -128,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2);
|
||||
}
|
||||
|
||||
if (osuCurrObj.TravelTime != 0)
|
||||
if (osuLastObj.TravelTime != 0)
|
||||
{
|
||||
// Reward sliders based on velocity.
|
||||
sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
|
||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
}
|
||||
|
||||
// Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger.
|
||||
|
@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
smallDistNerf = Math.Min(1.0, jumpDistance / 75.0);
|
||||
|
||||
// We also want to nerf stacks so that only the first object of the stack is accounted for.
|
||||
double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0);
|
||||
double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0);
|
||||
|
||||
result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime;
|
||||
}
|
||||
|
@ -154,7 +154,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
if (strainTime < min_speed_bonus)
|
||||
speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance);
|
||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||
double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.MinimumJumpDistance);
|
||||
|
||||
return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime;
|
||||
}
|
||||
|
@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -32,5 +32,7 @@
|
||||
</array>
|
||||
<key>XSAppIconAssets</key>
|
||||
<string>Assets.xcassets/AppIcon.appiconset</string>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
public void TestCultureInvariance()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
var scoreInfo = new TestScoreInfo(ruleset);
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
var score = new Score
|
||||
{
|
||||
|
@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
||||
{
|
||||
OnlineScoreID = 2,
|
||||
OnlineID = 2,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
BeatmapInfoID = beatmapInfo.ID
|
||||
}, new ImportScoreTest.TestArchiveReader());
|
||||
|
@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database
|
||||
// TODO: reimplement when we have score support in realm.
|
||||
// return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
|
||||
// {
|
||||
// OnlineScoreID = 2,
|
||||
// OnlineID = 2,
|
||||
// Beatmap = beatmap,
|
||||
// BeatmapInfoID = beatmap.ID
|
||||
// }, new ImportScoreTest.TestArchiveReader());
|
||||
|
@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database
|
||||
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultsPopulationRemovesExcess()
|
||||
{
|
||||
Assert.That(queryCount(), Is.EqualTo(0));
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
// Add some excess bindings for an action which only supports 1.
|
||||
using (var realm = realmContextFactory.CreateContext())
|
||||
using (var transaction = realm.BeginWrite())
|
||||
{
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.A)
|
||||
});
|
||||
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.S)
|
||||
});
|
||||
|
||||
realm.Add(new RealmKeyBinding
|
||||
{
|
||||
Action = GlobalAction.Back,
|
||||
KeyCombination = new KeyCombination(InputKey.D)
|
||||
});
|
||||
|
||||
transaction.Commit();
|
||||
}
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
||||
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private int queryCount(GlobalAction? match = null)
|
||||
{
|
||||
using (var realm = realmContextFactory.CreateContext())
|
||||
|
@ -13,7 +13,6 @@ using osu.Game.Online.Solo;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
@ -94,7 +93,7 @@ namespace osu.Game.Tests.Online
|
||||
[Test]
|
||||
public void TestDeserialiseSubmittableScoreWithEmptyMods()
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo });
|
||||
var score = new SubmittableScore(new ScoreInfo());
|
||||
|
||||
var deserialised = JsonConvert.DeserializeObject<SubmittableScore>(JsonConvert.SerializeObject(score));
|
||||
|
||||
@ -106,7 +105,6 @@ namespace osu.Game.Tests.Online
|
||||
{
|
||||
var score = new SubmittableScore(new ScoreInfo
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }
|
||||
});
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
@ -12,8 +13,12 @@ using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Tests.Resources
|
||||
{
|
||||
@ -137,5 +142,63 @@ namespace osu.Game.Tests.Resources
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a test score model.
|
||||
/// </summary>
|
||||
/// <param name="ruleset">The ruleset for which the score was set against.</param>
|
||||
/// <returns></returns>
|
||||
public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) =>
|
||||
CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First());
|
||||
|
||||
/// <summary>
|
||||
/// Create a test score model.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap for which the score was set against.</param>
|
||||
/// <returns></returns>
|
||||
public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
BeatmapInfo = beatmap,
|
||||
Ruleset = beatmap.Ruleset,
|
||||
RulesetID = beatmap.Ruleset.ID ?? 0,
|
||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
Accuracy = 0.95,
|
||||
MaxCombo = 999,
|
||||
Position = 1,
|
||||
Rank = ScoreRank.S,
|
||||
Date = DateTimeOffset.Now,
|
||||
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.SmallBonus] = 50
|
||||
},
|
||||
};
|
||||
|
||||
private class TestModHardRock : ModHardRock
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
|
||||
private class TestModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Combo = 250,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineScoreID = 12345,
|
||||
OnlineID = 12345,
|
||||
};
|
||||
|
||||
var imported = await LoadScoreIntoOsu(osu, toImport);
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Assert.AreEqual(toImport.Combo, imported.Combo);
|
||||
Assert.AreEqual(toImport.User.Username, imported.User.Username);
|
||||
Assert.AreEqual(toImport.Date, imported.Date);
|
||||
Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID);
|
||||
Assert.AreEqual(toImport.OnlineID, imported.OnlineID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader());
|
||||
await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader());
|
||||
|
||||
var scoreManager = osu.Dependencies.Get<ScoreManager>();
|
||||
|
||||
// Note: A new score reference is used here since the import process mutates the original object to set an ID
|
||||
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 }));
|
||||
Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 }));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO
|
||||
Assert.That(score1, Is.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonMatchingByHash()
|
||||
{
|
||||
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
|
||||
ScoreInfo score2 = new ScoreInfo { Hash = "b" };
|
||||
|
||||
Assert.That(score1, Is.Not.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMatchingByHash()
|
||||
{
|
||||
ScoreInfo score1 = new ScoreInfo { Hash = "a" };
|
||||
ScoreInfo score2 = new ScoreInfo { Hash = "a" };
|
||||
|
||||
Assert.That(score1, Is.EqualTo(score2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonMatchingByNull()
|
||||
{
|
||||
|
@ -18,7 +18,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@ -28,7 +27,6 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
FadeAccessibleResults results = null;
|
||||
|
||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
|
||||
{
|
||||
User = new APIUser { Username = "osu!" },
|
||||
BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
|
||||
Ruleset = Ruleset.Value,
|
||||
})));
|
||||
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
|
||||
|
||||
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
|
||||
|
||||
|
@ -6,12 +6,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables.Cards;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -227,7 +229,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
Child = new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -248,6 +250,17 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
|
||||
public void TestNormal()
|
||||
{
|
||||
createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
|
||||
|
||||
AddToggleStep("toggle expanded state", expanded =>
|
||||
{
|
||||
var card = this.ChildrenOfType<BeatmapCard>().Last();
|
||||
if (!card.Expanded.Disabled)
|
||||
card.Expanded.Value = expanded;
|
||||
});
|
||||
AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType<BeatmapCard>().ForEach(card => card.Expanded.Disabled = disabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
|
||||
@ -89,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
confirmEditingBeatmap(() => targetDifficulty);
|
||||
|
||||
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
|
||||
AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType<DrawableRuleset>().SingleOrDefault()?.IsLoaded == true);
|
||||
AddStep("paste object", () => Editor.Paste());
|
||||
|
||||
if (sameRuleset)
|
||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
public void TestCreateNewBeatmap()
|
||||
{
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0);
|
||||
AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged);
|
||||
AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false);
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Edit;
|
||||
@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
protected override void LoadEditor()
|
||||
{
|
||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
|
||||
SelectedMods.Value = new[] { new ModCinema() };
|
||||
base.LoadEditor();
|
||||
}
|
||||
|
||||
@ -67,6 +69,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
var background = this.ChildrenOfType<BackgroundScreenBeatmap>().Single();
|
||||
return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0;
|
||||
});
|
||||
AddAssert("no mods selected", () => SelectedMods.Value.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("total number of results == 1", () =>
|
||||
{
|
||||
var score = new ScoreInfo();
|
||||
|
||||
((FailPlayer)Player).ScoreProcessor.PopulateScore(score);
|
||||
|
||||
return score.Statistics.Values.Sum() == 1;
|
||||
|
@ -251,7 +251,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestMutedNotificationMuteButton()
|
||||
{
|
||||
addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value);
|
||||
addVolumeSteps("mute button", () =>
|
||||
{
|
||||
// Importantly, in the case the volume is muted but the user has a volume level set, it should be retained.
|
||||
audioManager.VolumeTrack.Value = 0.5f;
|
||||
volumeOverlay.IsMuted.Value = true;
|
||||
}, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f);
|
||||
}
|
||||
|
||||
/// <remarks>
|
||||
|
@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable)
|
||||
{
|
||||
return new APIScoreInfo
|
||||
return new APIScore
|
||||
{
|
||||
OnlineID = 2553163309,
|
||||
RulesetID = 0,
|
||||
|
@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click edit button", () =>
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().AddOrEditPlaylistButton);
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
|
@ -11,12 +11,14 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
@ -172,6 +174,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultiplayerRooms()
|
||||
{
|
||||
AddStep("create rooms", () => Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
new DrawableMatchRoom(new Room
|
||||
{
|
||||
Name = { Value = "A host-only room" },
|
||||
QueueMode = { Value = QueueMode.HostOnly },
|
||||
Type = { Value = MatchType.HeadToHead }
|
||||
}),
|
||||
new DrawableMatchRoom(new Room
|
||||
{
|
||||
Name = { Value = "An all-players, team-versus room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayers },
|
||||
Type = { Value = MatchType.TeamVersus }
|
||||
}),
|
||||
new DrawableMatchRoom(new Room
|
||||
{
|
||||
Name = { Value = "A round-robin room" },
|
||||
QueueMode = { Value = QueueMode.AllPlayersRoundRobin },
|
||||
Type = { Value = MatchType.HeadToHead }
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DrawableRoom createLoungeRoom(Room room)
|
||||
{
|
||||
room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestNonEditableNonSelectable()
|
||||
{
|
||||
createPlaylist(false, false);
|
||||
createPlaylist();
|
||||
|
||||
moveToItem(0);
|
||||
assertHandleVisibility(0, false);
|
||||
@ -61,7 +62,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestEditable()
|
||||
{
|
||||
createPlaylist(true, false);
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowReordering = true;
|
||||
p.AllowDeletion = true;
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
assertHandleVisibility(0, true);
|
||||
@ -74,7 +79,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestMarkInvalid()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowReordering = true;
|
||||
p.AllowDeletion = true;
|
||||
p.AllowSelection = true;
|
||||
});
|
||||
|
||||
AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid());
|
||||
|
||||
@ -87,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestSelectable()
|
||||
{
|
||||
createPlaylist(false, true);
|
||||
createPlaylist(p => p.AllowSelection = true);
|
||||
|
||||
moveToItem(0);
|
||||
assertHandleVisibility(0, false);
|
||||
@ -101,7 +111,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestEditableSelectable()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowReordering = true;
|
||||
p.AllowDeletion = true;
|
||||
p.AllowSelection = true;
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
assertHandleVisibility(0, true);
|
||||
@ -115,7 +130,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestSelectionNotLostAfterRearrangement()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowReordering = true;
|
||||
p.AllowDeletion = true;
|
||||
p.AllowSelection = true;
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
@ -128,95 +148,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("item 1 is selected", () => playlist.SelectedItem.Value == playlist.Items[1]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemRemovedOnDeletion()
|
||||
{
|
||||
PlaylistItem selectedItem = null;
|
||||
|
||||
createPlaylist(true, true);
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value);
|
||||
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item removed", () => !playlist.Items.Contains(selectedItem));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterDeletion()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastItemSelectedAfterLastItemDeleted()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
|
||||
AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired.
|
||||
AddStep("scroll to bottom", () => playlist.ChildrenOfType<ScrollContainer<Drawable>>().First().ScrollToEnd(false));
|
||||
|
||||
moveToItem(19);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
moveToDeleteButton(19);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectionResetWhenAllItemsDeleted()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
|
||||
AddStep("remove all but one item", () =>
|
||||
{
|
||||
playlist.Items.RemoveRange(1, playlist.Items.Count - 1);
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
|
||||
}
|
||||
|
||||
// Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081)
|
||||
// [Test]
|
||||
public void TestNextItemSelectedAfterExternalDeletion()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("remove item 0", () => playlist.Items.RemoveAt(0));
|
||||
|
||||
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeBeatmapAndRemove()
|
||||
{
|
||||
createPlaylist(true, true);
|
||||
|
||||
AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30);
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDownloadButtonHiddenWhenBeatmapExists()
|
||||
{
|
||||
@ -224,7 +155,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait());
|
||||
|
||||
createPlaylist(beatmap);
|
||||
createPlaylistWithBeatmaps(beatmap);
|
||||
|
||||
assertDownloadButtonVisible(false);
|
||||
|
||||
@ -247,7 +178,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
var byChecksum = CreateAPIBeatmap();
|
||||
byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally.
|
||||
|
||||
createPlaylist(byOnlineId, byChecksum);
|
||||
createPlaylistWithBeatmaps(byOnlineId, byChecksum);
|
||||
|
||||
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadButton>().All(d => d.IsPresent));
|
||||
}
|
||||
@ -261,7 +192,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
beatmap.BeatmapSet.HasExplicitContent = true;
|
||||
|
||||
createPlaylist(beatmap);
|
||||
createPlaylistWithBeatmaps(beatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -269,7 +200,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist(false, false)
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -312,11 +243,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[TestCase(true)]
|
||||
public void TestWithOwner(bool withOwner)
|
||||
{
|
||||
createPlaylist(false, false, withOwner);
|
||||
createPlaylist(p => p.ShowItemOwners = withOwner);
|
||||
|
||||
AddAssert("owner visible", () => playlist.ChildrenOfType<UpdateableAvatar>().All(a => a.IsPresent == withOwner));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWithAllButtonsEnabled()
|
||||
{
|
||||
createPlaylist(p =>
|
||||
{
|
||||
p.AllowDeletion = true;
|
||||
p.AllowShowingResults = true;
|
||||
p.AllowEditing = true;
|
||||
});
|
||||
}
|
||||
|
||||
private void moveToItem(int index, Vector2? offset = null)
|
||||
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
|
||||
|
||||
@ -326,12 +268,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
InputManager.MoveMouseTo(item.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().Single(), offset);
|
||||
});
|
||||
|
||||
private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () =>
|
||||
{
|
||||
var item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
|
||||
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0), offset);
|
||||
});
|
||||
|
||||
private void assertHandleVisibility(int index, bool visible)
|
||||
=> AddAssert($"handle {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible);
|
||||
@ -340,17 +276,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
=> AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(2 + index * 2).Alpha > 0) == visible);
|
||||
|
||||
private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false)
|
||||
private void createPlaylist(Action<TestPlaylist> setupPlaylist = null)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner)
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
setupPlaylist?.Invoke(playlist);
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem
|
||||
@ -386,11 +324,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
private void createPlaylist(params IBeatmapInfo[] beatmaps)
|
||||
private void createPlaylistWithBeatmaps(params IBeatmapInfo[] beatmaps)
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist(false, false)
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -423,11 +361,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private class TestPlaylist : DrawableRoomPlaylist
|
||||
{
|
||||
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
||||
|
||||
public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false)
|
||||
: base(allowEdit, allowSelection, showItemOwner: showItemOwner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -74,11 +75,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddItemsAsHost()
|
||||
{
|
||||
addItem(() => OtherBeatmap);
|
||||
|
||||
AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2);
|
||||
}
|
||||
|
||||
private void selectNewItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click edit button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().AddOrEditPlaylistButton);
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
@ -90,5 +99,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
{
|
||||
AddStep("click add button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -416,8 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("Enter song select", () =>
|
||||
{
|
||||
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen;
|
||||
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap();
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
|
@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public new BeatmapCarousel Carousel => base.Carousel;
|
||||
|
||||
public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null)
|
||||
: base(room, beatmap, ruleset)
|
||||
: base(room, null, beatmap, ruleset)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,168 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerQueueList : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerQueueList playlist;
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
|
||||
|
||||
private BeatmapManager beatmaps;
|
||||
private RulesetStore rulesets;
|
||||
private BeatmapSetInfo importedSet;
|
||||
private BeatmapInfo importedBeatmap;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
{
|
||||
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
|
||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new MultiplayerQueueList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem },
|
||||
Items = { BindTarget = Client.APIRoom!.Playlist }
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
|
||||
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
|
||||
});
|
||||
|
||||
AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteButtonAlwaysVisibleForHost()
|
||||
{
|
||||
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
|
||||
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
|
||||
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
||||
assertDeleteButtonVisibility(1, true);
|
||||
addPlaylistItem(() => 1234);
|
||||
assertDeleteButtonVisibility(2, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost()
|
||||
{
|
||||
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
|
||||
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
|
||||
AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 }));
|
||||
AddStep("set other user as host", () => Client.TransferHost(1234));
|
||||
|
||||
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
||||
assertDeleteButtonVisibility(1, true);
|
||||
addPlaylistItem(() => 1234);
|
||||
assertDeleteButtonVisibility(2, false);
|
||||
|
||||
AddStep("set local user as host", () => Client.TransferHost(API.LocalUser.Value.OnlineID));
|
||||
assertDeleteButtonVisibility(1, true);
|
||||
assertDeleteButtonVisibility(2, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCurrentItemDoesNotHaveDeleteButton()
|
||||
{
|
||||
AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }));
|
||||
AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
|
||||
assertDeleteButtonVisibility(0, false);
|
||||
|
||||
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
|
||||
assertDeleteButtonVisibility(0, false);
|
||||
assertDeleteButtonVisibility(1, true);
|
||||
|
||||
// Run through gameplay.
|
||||
AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready));
|
||||
AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
|
||||
AddStep("start match", () => Client.StartMatch());
|
||||
AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad);
|
||||
AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded));
|
||||
AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing);
|
||||
AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay));
|
||||
AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results);
|
||||
|
||||
assertDeleteButtonVisibility(1, false);
|
||||
}
|
||||
|
||||
private void addPlaylistItem(Func<int> userId)
|
||||
{
|
||||
long itemId = -1;
|
||||
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = importedBeatmap },
|
||||
BeatmapID = importedBeatmap.OnlineID ?? -1,
|
||||
});
|
||||
|
||||
Client.AddUserPlaylistItem(userId(), item);
|
||||
|
||||
itemId = item.ID;
|
||||
});
|
||||
|
||||
AddUntilStep("item arrived in playlist", () => playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().Any(i => i.Model.ID == itemId));
|
||||
}
|
||||
|
||||
private void deleteItem(int index)
|
||||
{
|
||||
OsuRearrangeableListItem<PlaylistItem> item = null;
|
||||
|
||||
AddStep($"move mouse to delete button {index}", () =>
|
||||
{
|
||||
item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
|
||||
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0));
|
||||
});
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddUntilStep("item removed from playlist", () => !playlist.ChildrenOfType<RearrangeableListItem<PlaylistItem>>().Contains(item));
|
||||
}
|
||||
|
||||
private void assertDeleteButtonVisibility(int index, bool visible)
|
||||
=> AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible",
|
||||
() => (playlist.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(index).Alpha > 0) == visible);
|
||||
}
|
||||
}
|
@ -1,13 +1,11 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -22,20 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||
|
||||
var score = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineScoreID = 12345,
|
||||
Ruleset = rulesetInfo,
|
||||
};
|
||||
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
|
||||
|
||||
PlaylistItem playlistItem = new PlaylistItem
|
||||
{
|
||||
|
@ -1,15 +1,13 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@ -26,20 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
|
||||
|
||||
var score = new ScoreInfo
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
TotalScore = 987654,
|
||||
Accuracy = 0.8,
|
||||
MaxCombo = 500,
|
||||
Combo = 250,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
User = new APIUser { Username = "Test user" },
|
||||
Date = DateTimeOffset.Now,
|
||||
OnlineScoreID = 12345,
|
||||
Ruleset = rulesetInfo,
|
||||
};
|
||||
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
|
||||
|
||||
PlaylistItem playlistItem = new PlaylistItem
|
||||
{
|
||||
|
@ -0,0 +1,188 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestScenePlaylistsRoomSettingsPlaylist : OsuManualInputManagerTestScene
|
||||
{
|
||||
private TestPlaylist playlist;
|
||||
|
||||
[Cached(typeof(UserLookupCache))]
|
||||
private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache();
|
||||
|
||||
[Test]
|
||||
public void TestItemRemovedOnDeletion()
|
||||
{
|
||||
PlaylistItem selectedItem = null;
|
||||
|
||||
createPlaylist();
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value);
|
||||
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item removed", () => !playlist.Items.Contains(selectedItem));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterDeletion()
|
||||
{
|
||||
createPlaylist();
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLastItemSelectedAfterLastItemDeleted()
|
||||
{
|
||||
createPlaylist();
|
||||
|
||||
AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired.
|
||||
AddStep("scroll to bottom", () => playlist.ChildrenOfType<ScrollContainer<Drawable>>().First().ScrollToEnd(false));
|
||||
|
||||
moveToItem(19);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
moveToDeleteButton(19);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectionResetWhenAllItemsDeleted()
|
||||
{
|
||||
createPlaylist();
|
||||
|
||||
AddStep("remove all but one item", () =>
|
||||
{
|
||||
playlist.Items.RemoveRange(1, playlist.Items.Count - 1);
|
||||
});
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
|
||||
}
|
||||
|
||||
// Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081)
|
||||
// [Test]
|
||||
public void TestNextItemSelectedAfterExternalDeletion()
|
||||
{
|
||||
createPlaylist();
|
||||
|
||||
moveToItem(0);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("remove item 0", () => playlist.Items.RemoveAt(0));
|
||||
|
||||
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeBeatmapAndRemove()
|
||||
{
|
||||
createPlaylist();
|
||||
|
||||
AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30);
|
||||
moveToDeleteButton(0);
|
||||
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
|
||||
}
|
||||
|
||||
private void moveToItem(int index, Vector2? offset = null)
|
||||
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
|
||||
|
||||
private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () =>
|
||||
{
|
||||
var item = playlist.ChildrenOfType<OsuRearrangeableListItem<PlaylistItem>>().ElementAt(index);
|
||||
InputManager.MoveMouseTo(item.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistRemoveButton>().ElementAt(0), offset);
|
||||
});
|
||||
|
||||
private void createPlaylist()
|
||||
{
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new TestPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
playlist.Items.Add(new PlaylistItem
|
||||
{
|
||||
ID = i,
|
||||
OwnerID = 2,
|
||||
Beatmap =
|
||||
{
|
||||
Value = i % 2 == 1
|
||||
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
|
||||
: new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "Artist",
|
||||
Author = new APIUser { Username = "Creator name here" },
|
||||
Title = "Long title used to check background colour",
|
||||
},
|
||||
BeatmapSet = new BeatmapSetInfo()
|
||||
}
|
||||
},
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
RequiredMods =
|
||||
{
|
||||
new OsuModHardRock(),
|
||||
new OsuModDoubleTime(),
|
||||
new OsuModAutoplay()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
|
||||
}
|
||||
|
||||
private class TestPlaylist : PlaylistsRoomSettingsPlaylist
|
||||
{
|
||||
public new IReadOnlyDictionary<PlaylistItem, RearrangeableListItem<PlaylistItem>> ItemMap => base.ItemMap;
|
||||
|
||||
public TestPlaylist()
|
||||
{
|
||||
AllowSelection = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||
AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||
AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState);
|
||||
}
|
||||
|
||||
@ -162,13 +162,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
|
||||
AddUntilStep("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead);
|
||||
|
||||
AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam == null));
|
||||
|
||||
AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus));
|
||||
|
||||
AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||
AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus);
|
||||
|
||||
AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType<TeamDisplay>().All(d => d.DisplayedTeam != null));
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
imported = Game.ScoreManager.Import(new ScoreInfo
|
||||
{
|
||||
Hash = Guid.NewGuid().ToString(),
|
||||
OnlineScoreID = i,
|
||||
OnlineID = i,
|
||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
|
||||
}).Result.Value;
|
||||
|
@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var allScores = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APIScoreInfo>
|
||||
Scores = new List<APIScore>
|
||||
{
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567890,
|
||||
Accuracy = 1,
|
||||
},
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234789,
|
||||
Accuracy = 0.9997,
|
||||
},
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 12345678,
|
||||
Accuracy = 0.9854,
|
||||
},
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
TotalScore = 1234567,
|
||||
Accuracy = 0.8765,
|
||||
},
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var myBestScore = new APIScoreWithPosition
|
||||
{
|
||||
Score = new APIScoreInfo
|
||||
Score = new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var myBestScoreWithNullPosition = new APIScoreWithPosition
|
||||
{
|
||||
Score = new APIScoreInfo
|
||||
Score = new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
var oneScore = new APIScoresCollection
|
||||
{
|
||||
Scores = new List<APIScoreInfo>
|
||||
Scores = new List<APIScore>
|
||||
{
|
||||
new APIScoreInfo
|
||||
new APIScore
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public TestSceneUserProfileScores()
|
||||
{
|
||||
var firstScore = new APIScoreInfo
|
||||
var firstScore = new APIScore
|
||||
{
|
||||
PP = 1047.21,
|
||||
Rank = ScoreRank.SH,
|
||||
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Accuracy = 0.9813
|
||||
};
|
||||
|
||||
var secondScore = new APIScoreInfo
|
||||
var secondScore = new APIScore
|
||||
{
|
||||
PP = 134.32,
|
||||
Rank = ScoreRank.A,
|
||||
@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Accuracy = 0.998546
|
||||
};
|
||||
|
||||
var thirdScore = new APIScoreInfo
|
||||
var thirdScore = new APIScore
|
||||
{
|
||||
PP = 96.83,
|
||||
Rank = ScoreRank.S,
|
||||
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Accuracy = 0.9726
|
||||
};
|
||||
|
||||
var noPPScore = new APIScoreInfo
|
||||
var noPPScore = new APIScore
|
||||
{
|
||||
Rank = ScoreRank.B,
|
||||
Beatmap = new APIBeatmap
|
||||
|
@ -22,14 +22,17 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public class TestScenePlaylistsResultsScreen : ScreenTestScene
|
||||
{
|
||||
private const int scores_per_result = 10;
|
||||
private const int real_user_position = 200;
|
||||
|
||||
private TestResultsScreen resultsScreen;
|
||||
|
||||
private int currentScoreId;
|
||||
private bool requestComplete;
|
||||
private int totalCount;
|
||||
@ -37,7 +40,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
currentScoreId = 0;
|
||||
currentScoreId = 1;
|
||||
requestComplete = false;
|
||||
totalCount = 0;
|
||||
bindHandler();
|
||||
@ -50,13 +53,17 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
AddStep("bind user score info handler", () =>
|
||||
{
|
||||
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ };
|
||||
userScore = TestResources.CreateTestScoreInfo();
|
||||
userScore.OnlineID = currentScoreId++;
|
||||
|
||||
bindHandler(userScore: userScore);
|
||||
});
|
||||
|
||||
createResults(() => userScore);
|
||||
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
|
||||
AddAssert($"score panel position is {real_user_position}",
|
||||
() => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).ScorePosition.Value == real_user_position);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -74,14 +81,16 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
AddStep("bind user score info handler", () =>
|
||||
{
|
||||
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ };
|
||||
userScore = TestResources.CreateTestScoreInfo();
|
||||
userScore.OnlineID = currentScoreId++;
|
||||
|
||||
bindHandler(true, userScore);
|
||||
});
|
||||
|
||||
createResults(() => userScore);
|
||||
|
||||
AddAssert("more than 1 panel displayed", () => this.ChildrenOfType<ScorePanel>().Count() > 1);
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded);
|
||||
AddAssert("user score selected", () => this.ChildrenOfType<ScorePanel>().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -123,7 +132,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
AddStep("bind user score info handler", () =>
|
||||
{
|
||||
userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ };
|
||||
userScore = TestResources.CreateTestScoreInfo();
|
||||
userScore.OnlineID = currentScoreId++;
|
||||
|
||||
bindHandler(userScore: userScore);
|
||||
});
|
||||
|
||||
@ -230,12 +241,12 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
var multiplayerUserScore = new MultiplayerScore
|
||||
{
|
||||
ID = (int)(userScore.OnlineScoreID ?? currentScoreId++),
|
||||
ID = (int)(userScore.OnlineID > 0 ? userScore.OnlineID : currentScoreId++),
|
||||
Accuracy = userScore.Accuracy,
|
||||
EndedAt = userScore.Date,
|
||||
Passed = userScore.Passed,
|
||||
Rank = userScore.Rank,
|
||||
Position = 200,
|
||||
Position = real_user_position,
|
||||
MaxCombo = userScore.MaxCombo,
|
||||
TotalScore = userScore.TotalScore,
|
||||
User = userScore.User,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Ranking.Contracted;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -22,13 +24,18 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestShowPanel()
|
||||
{
|
||||
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
|
||||
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExcessMods()
|
||||
{
|
||||
AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo, true)));
|
||||
AddStep("show excess mods score", () =>
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray();
|
||||
showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), score);
|
||||
});
|
||||
}
|
||||
|
||||
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
|
||||
|
@ -20,6 +20,7 @@ using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -34,21 +35,21 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
var author = new APIUser { Username = "mapper_name" };
|
||||
|
||||
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo = createTestBeatmap(author)
|
||||
}));
|
||||
AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExcessMods()
|
||||
{
|
||||
var author = new APIUser { Username = "mapper_name" };
|
||||
|
||||
AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true)
|
||||
AddStep("show excess mods score", () =>
|
||||
{
|
||||
BeatmapInfo = createTestBeatmap(author)
|
||||
}));
|
||||
var author = new APIUser { Username = "mapper_name" };
|
||||
|
||||
var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author));
|
||||
score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray();
|
||||
|
||||
showPanel(score);
|
||||
});
|
||||
|
||||
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Current.Value == "mapper_name"));
|
||||
}
|
||||
@ -56,10 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestMapWithUnknownMapper()
|
||||
{
|
||||
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
BeatmapInfo = createTestBeatmap(new APIUser())
|
||||
}));
|
||||
AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser()))));
|
||||
|
||||
AddAssert("mapped by text not present", () =>
|
||||
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by")));
|
||||
@ -77,12 +75,12 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
var mods = new Mod[] { ruleset.GetAutoplayMod() };
|
||||
var beatmap = createTestBeatmap(new APIUser());
|
||||
|
||||
showPanel(new TestScoreInfo(ruleset.RulesetInfo)
|
||||
{
|
||||
Mods = mods,
|
||||
BeatmapInfo = beatmap,
|
||||
Date = default,
|
||||
});
|
||||
var score = TestResources.CreateTestScoreInfo(beatmap);
|
||||
|
||||
score.Mods = mods;
|
||||
score.Date = default;
|
||||
|
||||
showPanel(score);
|
||||
});
|
||||
|
||||
AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
|
||||
|
@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex("#444"),
|
||||
},
|
||||
new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User),
|
||||
new ExpandedPanelTopContent(TestResources.CreateTestScoreInfo().User),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -15,12 +15,12 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -72,11 +72,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
TestResultsScreen screen = null;
|
||||
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
Accuracy = accuracy,
|
||||
Rank = rank
|
||||
};
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
score.Accuracy = accuracy;
|
||||
score.Rank = rank;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score)));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
DelayedFetchResultsScreen screen = null;
|
||||
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000)));
|
||||
AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), 3000)));
|
||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||
AddStep("click expanded panel", () =>
|
||||
{
|
||||
@ -237,9 +236,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||
}
|
||||
|
||||
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo());
|
||||
|
||||
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo());
|
||||
|
||||
private class TestResultsContainer : Container
|
||||
{
|
||||
@ -282,7 +281,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.TotalScore += 10 - i;
|
||||
score.Hash = $"test{i}";
|
||||
scores.Add(score);
|
||||
@ -316,7 +315,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.TotalScore += 10 - i;
|
||||
scores.Add(score);
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
@ -17,7 +17,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestDRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.5;
|
||||
score.Rank = ScoreRank.D;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -25,7 +27,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestCRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.75;
|
||||
score.Rank = ScoreRank.C;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -33,7 +37,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestBRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.85;
|
||||
score.Rank = ScoreRank.B;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -41,7 +47,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestARank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.925;
|
||||
score.Rank = ScoreRank.A;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -49,7 +57,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestSRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.975;
|
||||
score.Rank = ScoreRank.S;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -57,7 +67,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAlmostSSRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.9999;
|
||||
score.Rank = ScoreRank.S;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -65,7 +77,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestSSRank()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 1;
|
||||
score.Rank = ScoreRank.X;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -73,7 +87,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAllHitResults()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Statistics[HitResult.Perfect] = 350;
|
||||
score.Statistics[HitResult.Ok] = 200;
|
||||
|
||||
addPanelStep(score);
|
||||
}
|
||||
@ -81,7 +97,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestContractedPanel()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.925;
|
||||
score.Rank = ScoreRank.A;
|
||||
|
||||
addPanelStep(score, PanelState.Contracted);
|
||||
}
|
||||
@ -89,7 +107,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestExpandAndContract()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A };
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.Accuracy = 0.925;
|
||||
score.Rank = ScoreRank.A;
|
||||
|
||||
addPanelStep(score, PanelState.Contracted);
|
||||
AddWaitStep("wait for transition", 10);
|
||||
|
@ -7,9 +7,9 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -29,14 +29,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
createListStep(() => new ScorePanelList
|
||||
{
|
||||
SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) }
|
||||
SelectedScore = { Value = TestResources.CreateTestScoreInfo() }
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddPanelAfterSelectingScore()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() => new ScorePanelList
|
||||
{
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAddPanelBeforeSelectingScore()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddStep("add many scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
list.AddScore(TestResources.CreateTestScoreInfo());
|
||||
});
|
||||
|
||||
assertFirstPanelCentred();
|
||||
@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAddManyScoresAfterExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var initialScore = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddStep("add many scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
|
||||
list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1));
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAddManyScoresBeforeExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var initialScore = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddStep("add scores", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
|
||||
list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1));
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAddManyPanelsOnBothSidesOfExpandedPanel()
|
||||
{
|
||||
var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var initialScore = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -143,10 +143,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
AddStep("add scores after", () =>
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 });
|
||||
list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1));
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 });
|
||||
list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1));
|
||||
});
|
||||
|
||||
assertScoreState(initialScore, true);
|
||||
@ -156,11 +156,11 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestSelectMultipleScores()
|
||||
{
|
||||
var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var firstScore = TestResources.CreateTestScoreInfo();
|
||||
var secondScore = TestResources.CreateTestScoreInfo();
|
||||
|
||||
firstScore.User.Username = "A";
|
||||
secondScore.User.Username = "B";
|
||||
firstScore.UserString = "A";
|
||||
secondScore.UserString = "B";
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestAddScoreImmediately()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
|
||||
createListStep(() =>
|
||||
{
|
||||
@ -206,9 +206,14 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestKeyboardNavigation()
|
||||
{
|
||||
var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 };
|
||||
var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 };
|
||||
var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
|
||||
var lowestScore = TestResources.CreateTestScoreInfo();
|
||||
lowestScore.MaxCombo = 100;
|
||||
|
||||
var middleScore = TestResources.CreateTestScoreInfo();
|
||||
middleScore.MaxCombo = 200;
|
||||
|
||||
var highestScore = TestResources.CreateTestScoreInfo();
|
||||
highestScore.MaxCombo = 300;
|
||||
|
||||
createListStep(() => new ScorePanelList());
|
||||
|
||||
@ -270,6 +275,13 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
assertExpandedPanelCentred();
|
||||
}
|
||||
|
||||
private ScoreInfo createScoreForTotalScore(long totalScore)
|
||||
{
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.TotalScore = totalScore;
|
||||
return score;
|
||||
}
|
||||
|
||||
private void createListStep(Func<ScorePanelList> creationFunc)
|
||||
{
|
||||
AddStep("create list", () => Child = list = creationFunc().With(d =>
|
||||
|
@ -6,11 +6,11 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Ranking.Statistics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
@ -20,10 +20,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestScoreWithTimeStatistics()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents()
|
||||
};
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents();
|
||||
|
||||
loadPanel(score);
|
||||
}
|
||||
@ -31,10 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestScoreWithPositionStatistics()
|
||||
{
|
||||
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
HitEvents = createPositionDistributedHitEvents()
|
||||
};
|
||||
var score = TestResources.CreateTestScoreInfo();
|
||||
score.HitEvents = createPositionDistributedHitEvents();
|
||||
|
||||
loadPanel(score);
|
||||
}
|
||||
@ -42,7 +38,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
[Test]
|
||||
public void TestScoreWithoutStatistics()
|
||||
{
|
||||
loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||
loadPanel(TestResources.CreateTestScoreInfo());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -835,12 +835,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
// this beatmap change should be overridden by the present.
|
||||
Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap());
|
||||
|
||||
songSelect.PresentScore(new ScoreInfo
|
||||
{
|
||||
User = new APIUser { Username = "woo" },
|
||||
BeatmapInfo = getPresentBeatmap(),
|
||||
Ruleset = getPresentBeatmap().Ruleset
|
||||
});
|
||||
songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap()));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen());
|
||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
var score = new ScoreInfo
|
||||
{
|
||||
OnlineScoreID = i,
|
||||
OnlineID = i,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
BeatmapInfoID = beatmapInfo.ID,
|
||||
Accuracy = RNG.NextDouble(),
|
||||
@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
|
||||
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("delete top score", () => scoreManager.Delete(importedScores[0]));
|
||||
AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID));
|
||||
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public float DrainRate { get; set; } = DEFAULT_DIFFICULTY;
|
||||
public float CircleSize { get; set; } = DEFAULT_DIFFICULTY;
|
||||
public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY;
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Beatmaps
|
||||
var localRulesetInfo = rulesetInfo as RulesetInfo;
|
||||
|
||||
// Difficulty can only be computed if the beatmap and ruleset are locally available.
|
||||
if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null)
|
||||
if (localBeatmapInfo?.IsManaged != true || localRulesetInfo == null)
|
||||
{
|
||||
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
||||
return Task.FromResult<StarDifficulty?>(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public int BeatmapVersion;
|
||||
|
||||
private int? onlineID;
|
||||
|
@ -20,6 +20,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[JsonProperty("title_unicode")]
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public int BeatmapSetInfoID { get; set; }
|
||||
|
||||
public int FileInfoID { get; set; }
|
||||
|
@ -18,6 +18,8 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
private int? onlineID;
|
||||
|
||||
[Column("OnlineBeatmapSetID")]
|
||||
|
@ -31,10 +31,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
public class BeatmapCard : OsuClickableContainer
|
||||
{
|
||||
public const float TRANSITION_DURATION = 400;
|
||||
public const float CORNER_RADIUS = 10;
|
||||
|
||||
public Bindable<bool> Expanded { get; } = new BindableBool();
|
||||
|
||||
private const float width = 408;
|
||||
private const float height = 100;
|
||||
private const float corner_radius = 10;
|
||||
private const float icon_area_width = 30;
|
||||
|
||||
private readonly APIBeatmapSet beatmapSet;
|
||||
@ -42,6 +44,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
private readonly BeatmapDownloadTracker downloadTracker;
|
||||
|
||||
private BeatmapCardContent content = null!;
|
||||
|
||||
private BeatmapCardThumbnail thumbnail = null!;
|
||||
|
||||
private Container rightAreaBackground = null!;
|
||||
@ -73,242 +77,247 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
CornerRadius = corner_radius;
|
||||
Masking = true;
|
||||
|
||||
FillFlowContainer leftIconArea;
|
||||
GridContainer titleContainer;
|
||||
GridContainer artistContainer;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = content = new BeatmapCardContent(height)
|
||||
{
|
||||
downloadTracker,
|
||||
rightAreaBackground = new Container
|
||||
MainContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = icon_area_width + 2 * corner_radius,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
// workaround for masking artifacts at the top & bottom of card,
|
||||
// which become especially visible on downloaded beatmaps (when the icon area has a lime background).
|
||||
Padding = new MarginPadding { Vertical = 1 },
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.White
|
||||
},
|
||||
},
|
||||
thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||
{
|
||||
Name = @"Left (icon) area",
|
||||
Size = new Vector2(height),
|
||||
Padding = new MarginPadding { Right = corner_radius },
|
||||
Child = leftIconArea = new FillFlowContainer
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1)
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Right (button) area",
|
||||
Width = 30,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
Padding = new MarginPadding { Vertical = 17.5f },
|
||||
Child = rightAreaButtons = new Container<BeatmapCardIconButton>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new BeatmapCardIconButton[]
|
||||
{
|
||||
new FavouriteButton(beatmapSet)
|
||||
{
|
||||
Current = favouriteState,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
},
|
||||
new DownloadButton(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State }
|
||||
},
|
||||
new GoToBeatmapButton(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mainContent = new Container
|
||||
{
|
||||
Name = @"Main content",
|
||||
X = height - corner_radius,
|
||||
Height = height,
|
||||
CornerRadius = corner_radius,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
mainContentBackground = new BeatmapCardContentBackground(beatmapSet)
|
||||
downloadTracker,
|
||||
rightAreaBackground = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = icon_area_width + 2 * CORNER_RADIUS,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
// workaround for masking artifacts at the top & bottom of card,
|
||||
// which become especially visible on downloaded beatmaps (when the icon area has a lime background).
|
||||
Padding = new MarginPadding { Vertical = 1 },
|
||||
Child = new Box
|
||||
{
|
||||
Horizontal = 10,
|
||||
Vertical = 4
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.White
|
||||
},
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
},
|
||||
thumbnail = new BeatmapCardThumbnail(beatmapSet)
|
||||
{
|
||||
Name = @"Left (icon) area",
|
||||
Size = new Vector2(height),
|
||||
Padding = new MarginPadding { Right = CORNER_RADIUS },
|
||||
Child = leftIconArea = new FillFlowContainer
|
||||
{
|
||||
titleContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
|
||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true
|
||||
},
|
||||
Empty()
|
||||
}
|
||||
}
|
||||
},
|
||||
artistContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = createArtistText(),
|
||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true
|
||||
},
|
||||
Empty()
|
||||
},
|
||||
}
|
||||
},
|
||||
new LinkFlowContainer(s =>
|
||||
{
|
||||
s.Shadow = false;
|
||||
s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
|
||||
}).With(d =>
|
||||
{
|
||||
d.AutoSizeAxes = Axes.Both;
|
||||
d.Margin = new MarginPadding { Top = 2 };
|
||||
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
|
||||
d.AddUserLink(beatmapSet.Author);
|
||||
}),
|
||||
Margin = new MarginPadding(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1)
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Bottom content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Padding = new MarginPadding
|
||||
Name = @"Right (button) area",
|
||||
Width = 30,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
Padding = new MarginPadding { Vertical = 17.5f },
|
||||
Child = rightAreaButtons = new Container<BeatmapCardIconButton>
|
||||
{
|
||||
Horizontal = 10,
|
||||
Vertical = 4
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new BeatmapCardIconButton[]
|
||||
{
|
||||
new FavouriteButton(beatmapSet)
|
||||
{
|
||||
Current = favouriteState,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre
|
||||
},
|
||||
new DownloadButton(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State }
|
||||
},
|
||||
new GoToBeatmapButton(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
State = { BindTarget = downloadTracker.State }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mainContent = new Container
|
||||
{
|
||||
Name = @"Main content",
|
||||
X = height - CORNER_RADIUS,
|
||||
Height = height,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
idleBottomContent = new FillFlowContainer
|
||||
mainContentBackground = new BeatmapCardContentBackground(beatmapSet)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 10,
|
||||
Vertical = 4
|
||||
},
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 3),
|
||||
AlwaysPresent = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
statisticsContainer = new FillFlowContainer<BeatmapCardStatistic>
|
||||
titleContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
ChildrenEnumerable = createStatistics()
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4, 0),
|
||||
Children = new Drawable[]
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Status = beatmapSet.Status,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new DifficultySpectrumDisplay(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
DotSize = new Vector2(6, 12)
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
|
||||
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true
|
||||
},
|
||||
Empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
artistContainer = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = createArtistText(),
|
||||
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Truncate = true
|
||||
},
|
||||
Empty()
|
||||
},
|
||||
}
|
||||
},
|
||||
new LinkFlowContainer(s =>
|
||||
{
|
||||
s.Shadow = false;
|
||||
s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
|
||||
}).With(d =>
|
||||
{
|
||||
d.AutoSizeAxes = Axes.Both;
|
||||
d.Margin = new MarginPadding { Top = 2 };
|
||||
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
|
||||
d.AddUserLink(beatmapSet.Author);
|
||||
}),
|
||||
}
|
||||
},
|
||||
downloadProgressBar = new BeatmapCardDownloadProgressBar
|
||||
new Container
|
||||
{
|
||||
Name = @"Bottom content",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 6,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
Progress = { BindTarget = downloadTracker.Progress }
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 10,
|
||||
Vertical = 4
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
idleBottomContent = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 3),
|
||||
AlwaysPresent = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
statisticsContainer = new FillFlowContainer<BeatmapCardStatistic>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true,
|
||||
ChildrenEnumerable = createStatistics()
|
||||
},
|
||||
new BeatmapCardExtraInfoRow(beatmapSet)
|
||||
{
|
||||
Hovered = _ =>
|
||||
{
|
||||
content.ScheduleShow();
|
||||
return false;
|
||||
},
|
||||
Unhovered = _ =>
|
||||
{
|
||||
// This hide should only trigger if the expanded content has not shown yet.
|
||||
// ie. if the user has not shown intent to want to see it (quickly moved over the info row area).
|
||||
if (!Expanded.Value)
|
||||
content.ScheduleHide();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
downloadProgressBar = new BeatmapCardDownloadProgressBar
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 6,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
State = { BindTarget = downloadTracker.State },
|
||||
Progress = { BindTarget = downloadTracker.Progress }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ExpandedContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 10, Vertical = 13 },
|
||||
Child = new BeatmapCardDifficultyList(beatmapSet)
|
||||
},
|
||||
Expanded = { BindTarget = Expanded }
|
||||
};
|
||||
|
||||
if (beatmapSet.HasVideo)
|
||||
@ -344,7 +353,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
downloadTracker.State.BindValueChanged(_ => updateState(), true);
|
||||
downloadTracker.State.BindValueChanged(_ => updateState());
|
||||
Expanded.BindValueChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
@ -356,6 +366,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
content.ScheduleHide();
|
||||
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
@ -386,19 +398,25 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
float targetWidth = width - height;
|
||||
if (IsHovered)
|
||||
targetWidth = targetWidth - icon_area_width + corner_radius;
|
||||
bool showDetails = IsHovered || Expanded.Value;
|
||||
|
||||
thumbnail.Dimmed.Value = IsHovered;
|
||||
float targetWidth = width - height;
|
||||
if (showDetails)
|
||||
targetWidth = targetWidth - icon_area_width + CORNER_RADIUS;
|
||||
|
||||
thumbnail.Dimmed.Value = showDetails;
|
||||
|
||||
// Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards.
|
||||
// This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left.
|
||||
content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint);
|
||||
|
||||
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
|
||||
mainContentBackground.Dimmed.Value = IsHovered;
|
||||
mainContentBackground.Dimmed.Value = showDetails;
|
||||
|
||||
statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint);
|
||||
rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
rightAreaButtons.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
foreach (var button in rightAreaButtons)
|
||||
{
|
||||
|
234
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs
Normal file
234
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs
Normal file
@ -0,0 +1,234 @@
|
||||
// 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 enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class BeatmapCardContent : CompositeDrawable
|
||||
{
|
||||
public Drawable MainContent
|
||||
{
|
||||
set => bodyContent.Child = value;
|
||||
}
|
||||
|
||||
public Drawable ExpandedContent
|
||||
{
|
||||
set => dropdownScroll.Child = value;
|
||||
}
|
||||
|
||||
public Bindable<bool> Expanded { get; } = new BindableBool();
|
||||
|
||||
private readonly Box background;
|
||||
private readonly Container content;
|
||||
private readonly Container bodyContent;
|
||||
private readonly Container dropdownContent;
|
||||
private readonly OsuScrollContainer dropdownScroll;
|
||||
private readonly Container borderContainer;
|
||||
|
||||
public BeatmapCardContent(float height)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = height;
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = content = new HoverHandlingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Unhovered = _ => checkForHide(),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
bodyContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = height,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
},
|
||||
dropdownContent = new HoverHandlingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Margin = new MarginPadding { Top = height },
|
||||
Alpha = 0,
|
||||
Hovered = _ =>
|
||||
{
|
||||
keep();
|
||||
return true;
|
||||
},
|
||||
Unhovered = _ => checkForHide(),
|
||||
Child = dropdownScroll = new ExpandedContentScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
ScrollbarVisible = false
|
||||
}
|
||||
},
|
||||
borderContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = BeatmapCard.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
BorderThickness = 3,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
background.Colour = colourProvider.Background2;
|
||||
borderContainer.BorderColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Expanded.BindValueChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private ScheduledDelegate? scheduledExpandedChange;
|
||||
|
||||
public void ScheduleShow()
|
||||
{
|
||||
scheduledExpandedChange?.Cancel();
|
||||
if (Expanded.Disabled || Expanded.Value)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (!Expanded.Disabled)
|
||||
Expanded.Value = true;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
public void ScheduleHide()
|
||||
{
|
||||
scheduledExpandedChange?.Cancel();
|
||||
if (Expanded.Disabled || !Expanded.Value)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
if (!Expanded.Disabled)
|
||||
Expanded.Value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void checkForHide()
|
||||
{
|
||||
if (Expanded.Disabled)
|
||||
return;
|
||||
|
||||
if (content.IsHovered || dropdownContent.IsHovered)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange?.Cancel();
|
||||
Expanded.Value = false;
|
||||
}
|
||||
|
||||
private void keep()
|
||||
{
|
||||
if (Expanded.Disabled)
|
||||
return;
|
||||
|
||||
scheduledExpandedChange?.Cancel();
|
||||
Expanded.Value = true;
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
||||
content.TweenEdgeEffectTo(new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0, 2),
|
||||
Radius = 10,
|
||||
Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f),
|
||||
Hollow = true,
|
||||
}, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private class ExpandedContentScrollContainer : OsuScrollContainer
|
||||
{
|
||||
public ExpandedContentScrollContainer()
|
||||
{
|
||||
ScrollbarVisible = false;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Height = Math.Min(Content.DrawHeight, 400);
|
||||
}
|
||||
|
||||
private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize);
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
if (!allowScroll)
|
||||
return false;
|
||||
|
||||
return base.OnDragStart(e);
|
||||
}
|
||||
|
||||
protected override void OnDrag(DragEvent e)
|
||||
{
|
||||
if (!allowScroll)
|
||||
return;
|
||||
|
||||
base.OnDrag(e);
|
||||
}
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e)
|
||||
{
|
||||
if (!allowScroll)
|
||||
return;
|
||||
|
||||
base.OnDragEnd(e);
|
||||
}
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e)
|
||||
{
|
||||
if (!allowScroll)
|
||||
return false;
|
||||
|
||||
return base.OnScroll(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
43
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs
Normal file
43
osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class BeatmapCardExtraInfoRow : HoverHandlingContainer
|
||||
{
|
||||
public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Status = beatmapSet.Status,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new DifficultySpectrumDisplay(beatmapSet)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
DotSize = new Vector2(6, 12)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
29
osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs
Normal file
29
osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// 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 enable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
public class HoverHandlingContainer : Container
|
||||
{
|
||||
public Func<HoverEvent, bool>? Hovered { get; set; }
|
||||
public Action<HoverLostEvent>? Unhovered { get; set; }
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
bool handledByBase = base.OnHover(e);
|
||||
return Hovered?.Invoke(e) ?? handledByBase;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
Unhovered?.Invoke(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ namespace osu.Game.Configuration
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public int? RulesetID { get; set; }
|
||||
|
||||
public int? Variant { get; set; }
|
||||
|
@ -476,7 +476,7 @@ namespace osu.Game.Database
|
||||
{
|
||||
Files.Dereference(file.FileInfo);
|
||||
|
||||
if (file.ID > 0)
|
||||
if (file.IsManaged)
|
||||
{
|
||||
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
|
||||
// Definitely can be removed once we rework the database backend.
|
||||
@ -505,7 +505,7 @@ namespace osu.Game.Database
|
||||
});
|
||||
}
|
||||
|
||||
if (model.ID > 0)
|
||||
if (model.IsManaged)
|
||||
Update(model);
|
||||
}
|
||||
|
||||
@ -737,7 +737,7 @@ namespace osu.Game.Database
|
||||
/// <param name="items">The usable items present in the store.</param>
|
||||
/// <returns>Whether the <typeparamref name="TModel"/> exists.</returns>
|
||||
protected virtual bool CheckLocalAvailability(TModel model, IQueryable<TModel> items)
|
||||
=> model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any());
|
||||
=> model.IsManaged && items.Any(i => i.ID == model.ID && i.Files.Any());
|
||||
|
||||
/// <summary>
|
||||
/// Whether import can be skipped after finding an existing import early in the process.
|
||||
|
@ -35,6 +35,16 @@ namespace osu.Game.Database
|
||||
|
||||
private void migrateSkins(DatabaseWriteUsage db)
|
||||
{
|
||||
// can be removed 20220530.
|
||||
var existingSkins = db.Context.SkinInfo
|
||||
.Include(s => s.Files)
|
||||
.ThenInclude(f => f.FileInfo)
|
||||
.ToList();
|
||||
|
||||
// previous entries in EF are removed post migration.
|
||||
if (!existingSkins.Any())
|
||||
return;
|
||||
|
||||
var userSkinChoice = config.GetBindable<string>(OsuSetting.Skin);
|
||||
int.TryParse(userSkinChoice.Value, out int userSkinInt);
|
||||
|
||||
@ -49,16 +59,6 @@ namespace osu.Game.Database
|
||||
break;
|
||||
}
|
||||
|
||||
// migrate ruleset settings. can be removed 20220530.
|
||||
var existingSkins = db.Context.SkinInfo
|
||||
.Include(s => s.Files)
|
||||
.ThenInclude(f => f.FileInfo)
|
||||
.ToList();
|
||||
|
||||
// previous entries in EF are removed post migration.
|
||||
if (!existingSkins.Any())
|
||||
return;
|
||||
|
||||
using (var realm = realmContextFactory.CreateContext())
|
||||
using (var transaction = realm.BeginWrite())
|
||||
{
|
||||
|
@ -11,5 +11,7 @@ namespace osu.Game.Database
|
||||
[JsonIgnore]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
int ID { get; set; }
|
||||
|
||||
bool IsManaged { get; }
|
||||
}
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ namespace osu.Game.Database
|
||||
|
||||
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
|
||||
|
||||
modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineScoreID).IsUnique();
|
||||
modelBuilder.Entity<ScoreInfo>().HasIndex(b => b.OnlineID).IsUnique();
|
||||
}
|
||||
|
||||
private class OsuDbLoggerFactory : ILoggerFactory
|
||||
|
@ -104,6 +104,14 @@ namespace osu.Game.Extensions
|
||||
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
|
||||
public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other);
|
||||
|
||||
/// <summary>
|
||||
/// Check whether the online ID of two <see cref="IScoreInfo"/>s match.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance to compare.</param>
|
||||
/// <param name="other">The other instance to compare against.</param>
|
||||
/// <returns>Whether online IDs match. If either instance is missing an online ID, this will return false.</returns>
|
||||
public static bool MatchesOnlineID(this IScoreInfo? instance, IScoreInfo? other) => matchesOnlineID(instance, other);
|
||||
|
||||
private static bool matchesOnlineID(this IHasOnlineID<long>? instance, IHasOnlineID<long>? other)
|
||||
{
|
||||
if (instance == null || other == null)
|
||||
|
@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples)
|
||||
capsTextAddedSample?.Play();
|
||||
else
|
||||
textAddedSamples[RNG.Next(0, 3)]?.Play();
|
||||
playTextAddedSample();
|
||||
}
|
||||
|
||||
protected override void OnUserTextRemoved(string removed)
|
||||
@ -117,6 +117,70 @@ namespace osu.Game.Graphics.UserInterface
|
||||
caretMovedSample?.Play();
|
||||
}
|
||||
|
||||
protected override void OnImeComposition(string newComposition, int removedTextLength, int addedTextLength, bool caretMoved)
|
||||
{
|
||||
base.OnImeComposition(newComposition, removedTextLength, addedTextLength, caretMoved);
|
||||
|
||||
if (string.IsNullOrEmpty(newComposition))
|
||||
{
|
||||
switch (removedTextLength)
|
||||
{
|
||||
case 0:
|
||||
// empty composition event, composition wasn't changed, don't play anything.
|
||||
return;
|
||||
|
||||
case 1:
|
||||
// composition probably ended by pressing backspace, or was cancelled.
|
||||
textRemovedSample?.Play();
|
||||
return;
|
||||
|
||||
default:
|
||||
// longer text removed, composition ended because it was cancelled.
|
||||
// could be a different sample if desired.
|
||||
textRemovedSample?.Play();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedTextLength > 0)
|
||||
{
|
||||
// some text was added, probably due to typing new text or by changing the candidate.
|
||||
playTextAddedSample();
|
||||
return;
|
||||
}
|
||||
|
||||
if (removedTextLength > 0)
|
||||
{
|
||||
// text was probably removed by backspacing.
|
||||
// it's also possible that a candidate that only removed text was changed to.
|
||||
textRemovedSample?.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
if (caretMoved)
|
||||
{
|
||||
// only the caret/selection was moved.
|
||||
caretMovedSample?.Play();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnImeResult(string result, bool successful)
|
||||
{
|
||||
base.OnImeResult(result, successful);
|
||||
|
||||
if (successful)
|
||||
{
|
||||
// composition was successfully completed, usually by pressing the enter key.
|
||||
textCommittedSample?.Play();
|
||||
}
|
||||
else
|
||||
{
|
||||
// composition was prematurely ended, eg. by clicking inside the textbox.
|
||||
// could be a different sample if desired.
|
||||
textCommittedSample?.Play();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
BorderThickness = 3;
|
||||
@ -142,6 +206,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
SelectionColour = SelectionColour,
|
||||
};
|
||||
|
||||
private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play();
|
||||
|
||||
private class OsuCaret : Caret
|
||||
{
|
||||
private const float caret_move_time = 60;
|
||||
|
@ -9,6 +9,8 @@ namespace osu.Game.IO
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public string Hash { get; set; }
|
||||
|
||||
public int ReferenceCount { get; set; }
|
||||
|
@ -81,20 +81,37 @@ namespace osu.Game.Input
|
||||
// compare counts in database vs defaults for each action type.
|
||||
foreach (var defaultsForAction in defaults.GroupBy(k => k.Action))
|
||||
{
|
||||
// avoid performing redundant queries when the database is empty and needs to be re-filled.
|
||||
int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key);
|
||||
IEnumerable<RealmKeyBinding> existing = existingBindings.Where(k =>
|
||||
k.RulesetName == rulesetName
|
||||
&& k.Variant == variant
|
||||
&& k.ActionInt == (int)defaultsForAction.Key);
|
||||
|
||||
if (defaultsForAction.Count() <= existingCount)
|
||||
continue;
|
||||
int defaultsCount = defaultsForAction.Count();
|
||||
int existingCount = existing.Count();
|
||||
|
||||
// insert any defaults which are missing.
|
||||
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
|
||||
if (defaultsCount > existingCount)
|
||||
{
|
||||
KeyCombinationString = k.KeyCombination.ToString(),
|
||||
ActionInt = (int)k.Action,
|
||||
RulesetName = rulesetName,
|
||||
Variant = variant
|
||||
}));
|
||||
// insert any defaults which are missing.
|
||||
realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding
|
||||
{
|
||||
KeyCombinationString = k.KeyCombination.ToString(),
|
||||
ActionInt = (int)k.Action,
|
||||
RulesetName = rulesetName,
|
||||
Variant = variant
|
||||
}));
|
||||
}
|
||||
else if (defaultsCount < existingCount)
|
||||
{
|
||||
// generally this shouldn't happen, but if the user has more key bindings for an action than we expect,
|
||||
// remove the last entries until the count matches for sanity.
|
||||
foreach (var k in existing.TakeLast(existingCount - defaultsCount).ToArray())
|
||||
{
|
||||
realm.Remove(k);
|
||||
|
||||
// Remove from the local flattened/cached list so future lookups don't query now deleted rows.
|
||||
existingBindings.Remove(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Online.API
|
||||
if (WebRequest != null)
|
||||
{
|
||||
Response = ((OsuJsonWebRequest<T>)WebRequest).ResponseObject;
|
||||
Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes");
|
||||
Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScoreInfo>>
|
||||
public class GetUserScoresRequest : PaginatedAPIRequest<List<APIScore>>
|
||||
{
|
||||
private readonly long userId;
|
||||
private readonly ScoreType type;
|
||||
|
@ -13,10 +13,11 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Scoring.Legacy;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.API.Requests.Responses
|
||||
{
|
||||
public class APIScoreInfo : IScoreInfo
|
||||
public class APIScore : IScoreInfo
|
||||
{
|
||||
[JsonProperty(@"score")]
|
||||
public long TotalScore { get; set; }
|
||||
@ -101,7 +102,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
BeatmapInfo = beatmap,
|
||||
User = User,
|
||||
Accuracy = Accuracy,
|
||||
OnlineScoreID = OnlineID,
|
||||
OnlineID = OnlineID,
|
||||
Date = Date,
|
||||
PP = PP,
|
||||
RulesetID = RulesetID,
|
||||
@ -150,6 +151,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID };
|
||||
IEnumerable<INamedFileUsage> IHasNamedFiles.Files => throw new NotImplementedException();
|
||||
|
||||
#region Implementation of IScoreInfo
|
||||
|
||||
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
|
||||
IUser IScoreInfo.User => User;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public int? Position;
|
||||
|
||||
[JsonProperty(@"score")]
|
||||
public APIScoreInfo Score;
|
||||
public APIScore Score;
|
||||
|
||||
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
public class APIScoresCollection
|
||||
{
|
||||
[JsonProperty(@"scores")]
|
||||
public List<APIScoreInfo> Scores;
|
||||
public List<APIScore> Scores;
|
||||
|
||||
[JsonProperty(@"userScore")]
|
||||
public APIScoreWithPosition UserScore;
|
||||
|
@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black,
|
||||
Colour = user.OnlineID == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black,
|
||||
Alpha = background_alpha,
|
||||
},
|
||||
},
|
||||
|
@ -82,5 +82,17 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
/// <param name="item">The item to add.</param>
|
||||
Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Edits an existing playlist item with new values.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to edit, containing new properties. Must have an ID.</param>
|
||||
Task EditPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item from the playlist.
|
||||
/// </summary>
|
||||
/// <param name="playlistItemId">The item to remove.</param>
|
||||
Task RemovePlaylistItem(long playlistItemId);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(joinedRoom != null);
|
||||
|
||||
// Populate playlist items.
|
||||
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false);
|
||||
var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false);
|
||||
|
||||
// Populate users.
|
||||
Debug.Assert(joinedRoom.Users != null);
|
||||
@ -335,6 +335,10 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item);
|
||||
|
||||
public abstract Task RemovePlaylistItem(long playlistItemId);
|
||||
|
||||
Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state)
|
||||
{
|
||||
if (Room == null)
|
||||
@ -470,7 +474,32 @@ namespace osu.Game.Online.Multiplayer
|
||||
|
||||
Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings)
|
||||
{
|
||||
Scheduler.Add(() => updateLocalRoomSettings(newSettings));
|
||||
Debug.Assert(APIRoom != null);
|
||||
Debug.Assert(Room != null);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
// ensure the new selected item is populated immediately.
|
||||
var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
{
|
||||
GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b =>
|
||||
{
|
||||
// Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards.
|
||||
bool success = b.Exception == null;
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
if (success)
|
||||
playlistItem.Beatmap.Value = b.Result;
|
||||
|
||||
updateLocalRoomSettings(newSettings);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@ -629,7 +658,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false);
|
||||
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
@ -673,7 +702,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
var playlistItem = await createPlaylistItem(item).ConfigureAwait(false);
|
||||
var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false);
|
||||
|
||||
Scheduler.Add(() =>
|
||||
{
|
||||
@ -728,10 +757,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
|
||||
}
|
||||
|
||||
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item)
|
||||
private async Task<PlaylistItem> createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately)
|
||||
{
|
||||
var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
var ruleset = Rulesets.GetRuleset(item.RulesetID);
|
||||
|
||||
Debug.Assert(ruleset != null);
|
||||
@ -741,8 +768,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
var playlistItem = new PlaylistItem
|
||||
{
|
||||
ID = item.ID,
|
||||
BeatmapID = item.BeatmapID,
|
||||
OwnerID = item.OwnerID,
|
||||
Beatmap = { Value = apiBeatmap },
|
||||
Ruleset = { Value = ruleset },
|
||||
Expired = item.Expired,
|
||||
PlaylistOrder = item.PlaylistOrder,
|
||||
@ -752,6 +779,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance)));
|
||||
|
||||
if (populateBeatmapImmediately)
|
||||
playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false);
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
|
@ -162,6 +162,22 @@ namespace osu.Game.Online.Multiplayer
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item);
|
||||
}
|
||||
|
||||
public override Task EditPlaylistItem(MultiplayerPlaylistItem item)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item);
|
||||
}
|
||||
|
||||
public override Task RemovePlaylistItem(long playlistItemId)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId);
|
||||
}
|
||||
|
||||
protected override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken);
|
||||
|
@ -62,6 +62,7 @@ namespace osu.Game.Online.Rooms
|
||||
public MultiplayerPlaylistItem(PlaylistItem item)
|
||||
{
|
||||
ID = item.ID;
|
||||
OwnerID = item.OwnerID;
|
||||
BeatmapID = item.BeatmapID;
|
||||
BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty;
|
||||
RulesetID = item.RulesetID;
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
OnlineScoreID = ID,
|
||||
OnlineID = ID,
|
||||
TotalScore = TotalScore,
|
||||
MaxCombo = MaxCombo,
|
||||
BeatmapInfo = beatmap,
|
||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Online.Rooms
|
||||
|
||||
req.ContentType = "application/json";
|
||||
req.Method = HttpMethod.Put;
|
||||
req.Timeout = 30000;
|
||||
|
||||
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
@ -35,7 +36,7 @@ namespace osu.Game.Online
|
||||
var scoreInfo = new ScoreInfo
|
||||
{
|
||||
ID = TrackedItem.ID,
|
||||
OnlineScoreID = TrackedItem.OnlineScoreID
|
||||
OnlineID = TrackedItem.OnlineID
|
||||
};
|
||||
|
||||
if (Manager.IsAvailableLocally(scoreInfo))
|
||||
@ -113,7 +114,7 @@ namespace osu.Game.Online
|
||||
UpdateState(DownloadState.NotDownloaded);
|
||||
});
|
||||
|
||||
private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID;
|
||||
private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.MatchesOnlineID(y);
|
||||
|
||||
#region Disposal
|
||||
|
||||
|
@ -31,6 +31,7 @@ namespace osu.Game.Online.Solo
|
||||
|
||||
req.ContentType = "application/json";
|
||||
req.Method = HttpMethod.Put;
|
||||
req.Timeout = 30000;
|
||||
|
||||
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
||||
{
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Online.Solo
|
||||
{
|
||||
/// <summary>
|
||||
/// A class specifically for sending scores to the API during score submission.
|
||||
/// This is used instead of <see cref="APIScoreInfo"/> due to marginally different serialisation naming requirements.
|
||||
/// This is used instead of <see cref="APIScore"/> due to marginally different serialisation naming requirements.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SubmittableScore
|
||||
|
@ -103,7 +103,7 @@ namespace osu.Game
|
||||
|
||||
private Container topMostOverlayContent;
|
||||
|
||||
private ScalingContainer screenContainer;
|
||||
protected ScalingContainer ScreenContainer { get; private set; }
|
||||
|
||||
protected Container ScreenOffsetContainer { get; private set; }
|
||||
|
||||
@ -179,7 +179,7 @@ namespace osu.Game
|
||||
}
|
||||
|
||||
private void updateBlockingOverlayFade() =>
|
||||
screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
|
||||
ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
|
||||
|
||||
public void AddBlockingOverlay(OverlayContainer overlay)
|
||||
{
|
||||
@ -487,8 +487,8 @@ namespace osu.Game
|
||||
// to ensure all the required data for presenting a replay are present.
|
||||
ScoreInfo databasedScoreInfo = null;
|
||||
|
||||
if (score.OnlineScoreID != null)
|
||||
databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID);
|
||||
if (score.OnlineID > 0)
|
||||
databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID);
|
||||
|
||||
databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash);
|
||||
|
||||
@ -698,7 +698,7 @@ namespace osu.Game
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
|
||||
ScreenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
@ -801,7 +801,7 @@ namespace osu.Game
|
||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add, true);
|
||||
loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true);
|
||||
|
||||
loadComponentSingleFile(new LoginOverlay
|
||||
{
|
||||
|
@ -151,7 +151,8 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
|
||||
// spawn new children with the contained so we only clear old content at the last moment.
|
||||
var content = new FillFlowContainer<BeatmapCard>
|
||||
// reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most).
|
||||
var content = new ReverseChildIDFillFlowContainer<BeatmapCard>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
|
||||
|
||||
topScoresContainer.Add(new DrawableTopScore(topScore));
|
||||
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID)
|
||||
if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID)
|
||||
topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position));
|
||||
}), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Profile
|
||||
|
||||
public abstract string Identifier { get; }
|
||||
|
||||
private readonly FillFlowContainer content;
|
||||
private readonly FillFlowContainer<Drawable> content;
|
||||
private readonly Box background;
|
||||
private readonly Box underscore;
|
||||
|
||||
@ -79,7 +80,9 @@ namespace osu.Game.Overlays.Profile
|
||||
}
|
||||
}
|
||||
},
|
||||
content = new FillFlowContainer
|
||||
// reverse ID flow is required for correct Z-ordering of the content (last item should be front-most).
|
||||
// particularly important in BeatmapsSection, as it uses beatmap cards, which have expandable overhanging content.
|
||||
content = new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK;
|
||||
|
||||
@ -26,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
protected int VisiblePages;
|
||||
protected int ItemsPerPage;
|
||||
|
||||
protected FillFlowContainer ItemsContainer { get; private set; }
|
||||
protected ReverseChildIDFillFlowContainer<Drawable> ItemsContainer { get; private set; }
|
||||
|
||||
private APIRequest<List<TModel>> retrievalRequest;
|
||||
private CancellationTokenSource loadCancellation;
|
||||
@ -48,11 +49,15 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
ItemsContainer = new FillFlowContainer
|
||||
// reverse ID flow is required for correct Z-ordering of the items (last item should be front-most).
|
||||
// particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content.
|
||||
ItemsContainer = new ReverseChildIDFillFlowContainer<Drawable>
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(0, 2),
|
||||
// ensure the container and its contents are in front of the "more" button.
|
||||
Depth = float.MinValue
|
||||
},
|
||||
moreButton = new ShowMoreButton
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
|
||||
private const float performance_background_shear = 0.45f;
|
||||
|
||||
protected readonly APIScoreInfo Score;
|
||||
protected readonly APIScore Score;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; }
|
||||
|
||||
public DrawableProfileScore(APIScoreInfo score)
|
||||
public DrawableProfileScore(APIScore score)
|
||||
{
|
||||
Score = score;
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
private readonly double weight;
|
||||
|
||||
public DrawableProfileWeightedScore(APIScoreInfo score, double weight)
|
||||
public DrawableProfileWeightedScore(APIScore score, double weight)
|
||||
: base(score)
|
||||
{
|
||||
this.weight = weight;
|
||||
|
@ -15,7 +15,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
|
||||
|
||||
namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
{
|
||||
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScoreInfo>
|
||||
public class PaginatedScoreContainer : PaginatedProfileSubsection<APIScore>
|
||||
{
|
||||
private readonly ScoreType type;
|
||||
|
||||
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnItemsReceived(List<APIScoreInfo> items)
|
||||
protected override void OnItemsReceived(List<APIScore> items)
|
||||
{
|
||||
if (VisiblePages == 0)
|
||||
drawableItemIndex = 0;
|
||||
@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
||||
base.OnItemsReceived(items);
|
||||
}
|
||||
|
||||
protected override APIRequest<List<APIScoreInfo>> CreateRequest() =>
|
||||
protected override APIRequest<List<APIScore>> CreateRequest() =>
|
||||
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
|
||||
|
||||
private int drawableItemIndex;
|
||||
|
||||
protected override Drawable CreateDrawableItem(APIScoreInfo model)
|
||||
protected override Drawable CreateDrawableItem(APIScore model)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
|
@ -135,7 +135,8 @@ namespace osu.Game.Overlays.Rankings
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ScoresTable(1, response.Users),
|
||||
new FillFlowContainer
|
||||
// reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most).
|
||||
new ReverseChildIDFillFlowContainer<BeatmapCard>
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -120,14 +120,14 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
|
||||
/// </summary>
|
||||
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
|
||||
public IEnumerable<DifficultyAttributes> CalculateAll()
|
||||
public IEnumerable<DifficultyAttributes> CalculateAll(CancellationToken cancellationToken = default)
|
||||
{
|
||||
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
|
||||
{
|
||||
if (combination is MultiMod multi)
|
||||
yield return Calculate(multi.Mods);
|
||||
yield return Calculate(multi.Mods, cancellationToken);
|
||||
else
|
||||
yield return Calculate(combination.Yield());
|
||||
yield return Calculate(combination.Yield(), cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +145,11 @@ namespace osu.Game.Rulesets.Difficulty
|
||||
{
|
||||
playableMods = mods.Select(m => m.DeepClone()).ToArray();
|
||||
|
||||
Beatmap = beatmap.GetPlayableBeatmap(ruleset, playableMods, cancellationToken);
|
||||
// Only pass through the cancellation token if it's non-default.
|
||||
// This allows for the default timeout to be applied for playable beatmap construction.
|
||||
Beatmap = cancellationToken == default
|
||||
? beatmap.GetPlayableBeatmap(ruleset, playableMods)
|
||||
: beatmap.GetPlayableBeatmap(ruleset, playableMods, cancellationToken);
|
||||
|
||||
var track = new TrackVirtual(10000);
|
||||
playableMods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
|
||||
|
@ -4,14 +4,14 @@
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
{
|
||||
public interface IScoreInfo : IHasOnlineID<long>, IHasNamedFiles
|
||||
{
|
||||
APIUser User { get; }
|
||||
IUser User { get; }
|
||||
|
||||
long TotalScore { get; }
|
||||
|
||||
|
@ -80,12 +80,9 @@ namespace osu.Game.Scoring.Legacy
|
||||
byte[] compressedReplay = sr.ReadByteArray();
|
||||
|
||||
if (version >= 20140721)
|
||||
scoreInfo.OnlineScoreID = sr.ReadInt64();
|
||||
scoreInfo.OnlineID = sr.ReadInt64();
|
||||
else if (version >= 20121008)
|
||||
scoreInfo.OnlineScoreID = sr.ReadInt32();
|
||||
|
||||
if (scoreInfo.OnlineScoreID <= 0)
|
||||
scoreInfo.OnlineScoreID = null;
|
||||
scoreInfo.OnlineID = sr.ReadInt32();
|
||||
|
||||
if (compressedReplay?.Length > 0)
|
||||
{
|
||||
|
@ -11,6 +11,8 @@ namespace osu.Game.Scoring
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public int FileInfoID { get; set; }
|
||||
|
||||
public FileInfo FileInfo { get; set; }
|
||||
|
@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Scoring
|
||||
@ -22,6 +23,8 @@ namespace osu.Game.Scoring
|
||||
{
|
||||
public int ID { get; set; }
|
||||
|
||||
public bool IsManaged => ID > 0;
|
||||
|
||||
public ScoreRank Rank { get; set; }
|
||||
|
||||
public long TotalScore { get; set; }
|
||||
@ -134,7 +137,14 @@ namespace osu.Game.Scoring
|
||||
[Column("Beatmap")]
|
||||
public BeatmapInfo BeatmapInfo { get; set; }
|
||||
|
||||
public long? OnlineScoreID { get; set; }
|
||||
private long? onlineID;
|
||||
|
||||
[Column("OnlineScoreID")]
|
||||
public long? OnlineID
|
||||
{
|
||||
get => onlineID;
|
||||
set => onlineID = value > 0 ? value : null;
|
||||
}
|
||||
|
||||
public DateTimeOffset Date { get; set; }
|
||||
|
||||
@ -229,24 +239,18 @@ namespace osu.Game.Scoring
|
||||
|
||||
public bool Equals(ScoreInfo other)
|
||||
{
|
||||
if (other == null)
|
||||
return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (other == null) return false;
|
||||
|
||||
if (ID != 0 && other.ID != 0)
|
||||
return ID == other.ID;
|
||||
|
||||
if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue)
|
||||
return OnlineScoreID == other.OnlineScoreID;
|
||||
|
||||
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
|
||||
return Hash == other.Hash;
|
||||
|
||||
return ReferenceEquals(this, other);
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Implementation of IHasOnlineID
|
||||
|
||||
public long OnlineID => OnlineScoreID ?? -1;
|
||||
long IHasOnlineID<long>.OnlineID => OnlineID ?? -1;
|
||||
|
||||
#endregion
|
||||
|
||||
@ -254,6 +258,7 @@ namespace osu.Game.Scoring
|
||||
|
||||
IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo;
|
||||
IRulesetInfo IScoreInfo.Ruleset => Ruleset;
|
||||
IUser IScoreInfo.User => User;
|
||||
bool IScoreInfo.HasReplay => Files.Any();
|
||||
|
||||
#endregion
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user