1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 09:27:34 +08:00

Merge branch 'master' into consume-bindable-current-factory

This commit is contained in:
Dean Herbert 2021-07-07 15:16:02 +09:00 committed by GitHub
commit bb58a9412b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
123 changed files with 1080 additions and 950 deletions

View File

@ -27,7 +27,7 @@
]
},
"ppy.localisationanalyser.tools": {
"version": "2021.608.0",
"version": "2021.705.0",
"commands": [
"localisation"
]

View File

@ -157,7 +157,7 @@ csharp_style_unused_value_assignment_preference = discard_variable:warning
#Style - variable declaration
csharp_style_inlined_variable_declaration = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_deconstructed_variable_declaration = false:silent
#Style - other C# 7.x features
dotnet_style_prefer_inferred_tuple_names = true:warning
@ -168,8 +168,8 @@ dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
#Style - C# 8 features
csharp_prefer_static_local_function = true:warning
csharp_prefer_simple_using_statement = true:silent
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_prefer_index_operator = false:silent
csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
#Supressing roslyn built-in analyzers

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.618.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.702.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.707.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. -->

View File

@ -5,8 +5,8 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
<AssemblyName>osu!</AssemblyName>
<Title>osu!lazer</Title>
<Product>osu!lazer</Product>
<Title>osu!</Title>
<Product>osu!</Product>
<ApplicationIcon>lazer.ico</ApplicationIcon>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Version>0.0.0</Version>

View File

@ -3,7 +3,7 @@
<metadata>
<id>osulazer</id>
<version>0.0.0</version>
<title>osu!lazer</title>
<title>osu!</title>
<authors>ppy Pty Ltd</authors>
<owners>Dean Herbert</owners>
<projectUrl>https://osu.ppy.sh/</projectUrl>
@ -20,4 +20,3 @@
<file src="**.config" target="lib\net45\"/>
</files>
</package>

View File

@ -0,0 +1,114 @@
// 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;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using Direction = osu.Game.Rulesets.Catch.UI.Direction;
namespace osu.Game.Rulesets.Catch.Tests
{
public class TestSceneCatchSkinConfiguration : OsuTestScene
{
[Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
private Catcher catcher;
private readonly Container container;
public TestSceneCatchSkinConfiguration()
{
Add(droppedObjectContainer = new DroppedObjectContainer());
Add(container = new Container { RelativeSizeAxes = Axes.Both });
}
[TestCase(false)]
[TestCase(true)]
public void TestCatcherPlateFlipping(bool flip)
{
AddStep("setup catcher", () =>
{
var skin = new TestSkin { FlipCatcherPlate = flip };
container.Child = new SkinProvidingContainer(skin)
{
Child = catcher = new Catcher(new Container())
{
Anchor = Anchor.Centre
}
};
});
Fruit fruit = new Fruit();
AddStep("catch fruit", () => catchFruit(fruit, 20));
float position = 0;
AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit));
AddStep("face left", () => catcher.VisualDirection = Direction.Left);
if (flip)
AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
else
AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
AddStep("face right", () => catcher.VisualDirection = Direction.Right);
AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
}
private float getCaughtObjectPosition(Fruit fruit)
{
var caughtObject = catcher.ChildrenOfType<CaughtObject>().Single(c => c.HitObject == fruit);
return caughtObject.Parent.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
}
private void catchFruit(Fruit fruit, float x)
{
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var drawableFruit = new DrawableFruit(fruit) { X = x };
var judgement = fruit.CreateJudgement();
catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement)
{
Type = judgement.MaxResult
});
}
private class TestSkin : DefaultSkin
{
public bool FlipCatcherPlate { get; set; }
public TestSkin()
: base(null)
{
}
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
if (lookup is CatchSkinConfiguration config)
{
if (config == CatchSkinConfiguration.FlipCatcherPlate)
return SkinUtils.As<TValue>(new Bindable<bool>(FlipCatcherPlate));
}
return base.GetConfig<TLookup, TValue>(lookup);
}
}
}
}

View File

@ -194,9 +194,9 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("catch more fruits", () => attemptCatch(() => new Fruit(), 9));
checkPlate(10);
AddAssert("caught objects are stacked", () =>
catcher.CaughtObjects.All(obj => obj.Y <= Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) &&
catcher.CaughtObjects.Any(obj => obj.Y == Catcher.CAUGHT_FRUIT_VERTICAL_OFFSET) &&
catcher.CaughtObjects.Any(obj => obj.Y < -25));
catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
catcher.CaughtObjects.Any(obj => obj.Y < 0));
}
[Test]

View File

@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("finish hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState(1);
catcherArea.MovableCatcher.SetHyperDashState();
catcherArea.MovableCatcher.FinishTransforms();
});

View File

@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.Edit
if (hitObject is BananaShower) return;
// TODO: confine in bounds
hitObject.OriginalXBindable.Value += deltaX;
hitObject.OriginalX += deltaX;
// Move the nested hit objects to give an instant result before nested objects are recreated.
foreach (var nested in hitObject.NestedHitObjects.OfType<CatchHitObject>())
nested.OriginalXBindable.Value += deltaX;
nested.OriginalX += deltaX;
});
return true;

View File

@ -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 Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -20,6 +21,11 @@ namespace osu.Game.Rulesets.Catch.Objects
/// <summary>
/// The horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
/// </summary>
/// <remarks>
/// Only setter is exposed.
/// Use <see cref="OriginalX"/> or <see cref="EffectiveX"/> to get the horizontal position.
/// </remarks>
[JsonIgnore]
public float X
{
set => OriginalXBindable.Value = value;
@ -34,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
public float XOffset
{
get => XOffsetBindable.Value;
set => XOffsetBindable.Value = value;
}
@ -44,7 +51,11 @@ namespace osu.Game.Rulesets.Catch.Objects
/// This value is the original <see cref="X"/> value specified in the beatmap, not affected by the beatmap processing.
/// Use <see cref="EffectiveX"/> for a gameplay.
/// </remarks>
public float OriginalX => OriginalXBindable.Value;
public float OriginalX
{
get => OriginalXBindable.Value;
set => OriginalXBindable.Value = value;
}
/// <summary>
/// The effective horizontal position of the hit object between 0 and <see cref="CatchPlayfield.WIDTH"/>.
@ -53,9 +64,9 @@ namespace osu.Game.Rulesets.Catch.Objects
/// This value is the original <see cref="X"/> value plus the offset applied by the beatmap processing.
/// Use <see cref="OriginalX"/> if a value not affected by the offset is desired.
/// </remarks>
public float EffectiveX => OriginalXBindable.Value + XOffsetBindable.Value;
public float EffectiveX => OriginalX + XOffset;
public double TimePreempt = 1000;
public double TimePreempt { get; set; } = 1000;
public readonly Bindable<int> IndexInBeatmapBindable = new Bindable<int>();

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@ -25,7 +26,10 @@ namespace osu.Game.Rulesets.Catch.Objects
public int RepeatCount { get; set; }
[JsonIgnore]
public double Velocity { get; private set; }
[JsonIgnore]
public double TickDistance { get; private set; }
/// <summary>
@ -113,6 +117,7 @@ namespace osu.Game.Rulesets.Catch.Objects
public float EndX => OriginalX + this.CurvePositionAt(1).X;
[JsonIgnore]
public double Duration
{
get => this.SpanCount() * Path.Distance / Velocity;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects.Types;
using osuTK.Graphics;
@ -33,6 +34,7 @@ namespace osu.Game.Rulesets.Catch.Objects
/// <summary>
/// The target fruit if we are to initiate a hyperdash.
/// </summary>
[JsonIgnore]
public CatchHitObject HyperDashTarget
{
get => hyperDashTarget;

View File

@ -0,0 +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.
namespace osu.Game.Rulesets.Catch.Skinning
{
public enum CatchSkinConfiguration
{
/// <summary>
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
/// </summary>
FlipCatcherPlate
}
}

View File

@ -103,6 +103,19 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
return (IBindable<TValue>)result;
case CatchSkinConfiguration config:
switch (config)
{
case CatchSkinConfiguration.FlipCatcherPlate:
// Don't flip catcher plate contents if the catcher is provided by this legacy skin.
if (GetDrawableComponent(new CatchSkinComponent(CatchSkinComponents.Catcher)) != null)
return (IBindable<TValue>)new Bindable<bool>();
break;
}
break;
}
return base.GetConfig<TLookup, TValue>(lookup);

View File

@ -56,11 +56,6 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier;
/// <summary>
/// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught".
/// </summary>
public const float CAUGHT_FRUIT_VERTICAL_OFFSET = -5;
/// <summary>
/// The amount by which caught fruit should be scaled down to fit on the plate.
/// </summary>
@ -84,8 +79,8 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherAnimationState CurrentState
{
get => body.AnimationState.Value;
private set => body.AnimationState.Value = value;
get => Body.AnimationState.Value;
private set => Body.AnimationState.Value = value;
}
/// <summary>
@ -108,18 +103,22 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
public Direction VisualDirection
{
get => Scale.X > 0 ? Direction.Right : Direction.Left;
set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y);
}
/// <summary>
/// The currently facing direction.
/// </summary>
public Direction VisualDirection { get; set; } = Direction.Right;
/// <summary>
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
/// </summary>
private bool flipCatcherPlate;
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
private readonly float catchWidth;
private readonly SkinnableCatcher body;
internal readonly SkinnableCatcher Body;
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
@ -157,8 +156,10 @@ namespace osu.Game.Rulesets.Catch.UI
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
// offset fruit vertically to better place "above" the plate.
Y = -5
},
body = new SkinnableCatcher(),
Body = new SkinnableCatcher(),
hitExplosionContainer = new HitExplosionContainer
{
Anchor = Anchor.TopCentre,
@ -347,6 +348,8 @@ namespace osu.Game.Rulesets.Catch.UI
trails.HyperDashTrailsColour = hyperDashColour;
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
flipCatcherPlate = skin.GetConfig<CatchSkinConfiguration, bool>(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true;
runHyperDashStateTransition(HyperDashing);
}
@ -354,6 +357,10 @@ namespace osu.Game.Rulesets.Catch.UI
{
base.Update();
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
Body.Scale = scaleFromDirection;
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
// Correct overshooting.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
@ -388,9 +395,6 @@ namespace osu.Game.Rulesets.Catch.UI
float adjustedRadius = displayRadius * lenience_adjust;
float checkDistance = MathF.Pow(adjustedRadius, 2);
// offset fruit vertically to better place "above" the plate.
position.Y += CAUGHT_FRUIT_VERTICAL_OFFSET;
while (caughtObjectContainer.Any(f => Vector2Extensions.DistanceSquared(f.Position, position) < checkDistance))
{
position.X += RNG.NextSingle(-adjustedRadius, adjustedRadius);
@ -465,7 +469,7 @@ namespace osu.Game.Rulesets.Catch.UI
break;
case DroppedObjectAnimation.Explode:
var originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * Scale.X;
float originalX = droppedObjectTarget.ToSpaceOfOtherDrawable(d.DrawPosition, caughtObjectContainer).X * caughtObjectContainer.Scale.X;
d.MoveToY(d.Y - 50, 250, Easing.OutSine).Then().MoveToY(d.Y + 50, 500, Easing.InSine);
d.MoveToX(d.X + originalX * 6, 1000);
d.FadeOut(750);

View File

@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Catch.UI
CatcherTrail sprite = trailPool.Get();
sprite.AnimationState = catcher.CurrentState;
sprite.Scale = catcher.Scale;
sprite.Scale = catcher.Scale * catcher.Body.Scale;
sprite.Position = catcher.Position;
target.Add(sprite);

View File

@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
case OsuSkinConfiguration osuLookup:
if (osuLookup == OsuSkinConfiguration.CursorCentre)
return SkinUtils.As<TValue>(new BindableBool(false));
return SkinUtils.As<TValue>(new BindableBool());
break;
}

View File

@ -158,17 +158,17 @@ namespace osu.Game.Rulesets.Osu.Mods
var firstObj = beatmap.HitObjects[0];
var startDelay = firstObj.StartTime - firstObj.TimePreempt;
using (BeginAbsoluteSequence(startDelay + break_close_late, true))
using (BeginAbsoluteSequence(startDelay + break_close_late))
leaveBreak();
foreach (var breakInfo in beatmap.Breaks)
{
if (breakInfo.HasEffect)
{
using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true))
using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early))
{
enterBreak();
using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true))
using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late))
leaveBreak();
}
}

View File

@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (drawable)
{
case DrawableHitCircle circle:
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
{
circle.ApproachCircle.Hide();

View File

@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
{
var h = hitObject.HitObject;
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt))
(hitCircle ?? hitObject).Hide();
}

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods
double appearTime = hitObject.StartTime - hitObject.TimePreempt - 1;
double moveDuration = hitObject.TimePreempt + 1;
using (drawable.BeginAbsoluteSequence(appearTime, true))
using (drawable.BeginAbsoluteSequence(appearTime))
{
drawable
.MoveToOffset(appearOffset)

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Mods
for (int i = 0; i < amountWiggles; i++)
{
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration, true))
using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration))
wiggle();
}
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Mods
for (int i = 0; i < amountWiggles; i++)
{
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration, true))
using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration))
wiggle();
}
}

View File

@ -233,35 +233,43 @@ namespace osu.Game.Rulesets.Osu.Replays
// Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - getReactionTime(h.StartTime - h.TimePreempt));
bool hasWaited = false;
if (waitTime > lastFrame.Time)
{
lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions };
hasWaited = true;
AddFrameToReplay(lastFrame);
}
Vector2 lastPosition = lastFrame.Position;
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
OsuReplayFrame lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
if (timeDifference > 0 && // Sanity checks
((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
if (timeDifference > 0)
{
// Perform eased movement
// If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously.
if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited)
{
// [lastLastFrame] ... [lastFrame] ... [current frame]
// We want to find the cursor position at lastFrame, so interpolate between lastLastFrame and the new target position.
lastFrame.Position = Interpolation.ValueAt(lastFrame.Time, lastFrame.Position, targetPos, lastLastFrame.Time, h.StartTime, easing);
}
Vector2 lastPosition = lastFrame.Position;
// Perform the rest of the eased movement until the target position is reached.
for (double time = lastFrame.Time + GetFrameDelay(lastFrame.Time); time < h.StartTime; time += GetFrameDelay(time))
{
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
}
}
buttonIndex = 0;
}
else
{
// Start alternating once the time separation is too small (faster than ~225BPM).
if (timeDifference > 0 && timeDifference < 266)
buttonIndex++;
}
else
buttonIndex = 0;
}
/// <summary>
@ -284,7 +292,7 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
double hEndTime = h.GetEndTime() + KEY_UP_DELAY;
int endDelay = h is Spinner ? 1 : 0;
var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
var endFrame = new OsuKeyUpReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
// Decrement because we want the previous frame, not the next one
int index = FindInsertionIndex(startFrame) - 1;
@ -381,5 +389,13 @@ namespace osu.Game.Rulesets.Osu.Replays
}
#endregion
private class OsuKeyUpReplayFrame : OsuReplayFrame
{
public OsuKeyUpReplayFrame(double time, Vector2 position)
: base(time, position)
{
}
}
}
}

View File

@ -130,18 +130,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
Spinner spinner = drawableSpinner.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
this.ScaleTo(initial_scale);
this.RotateTo(0);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
// constant ambient rotation to give the spinner "spinning" character.
this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration);
}
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true))
using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset))
{
switch (state)
{
@ -157,17 +157,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
}
}
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
centre.ScaleTo(0);
mainContainer.ScaleTo(0);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint);
mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint);
using (BeginDelayedSequence(spinner.TimePreempt / 2, true))
using (BeginDelayedSequence(spinner.TimePreempt / 2))
{
centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint);
mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint);
@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
}
// transforms we have from completing the spinner will be rolled back, so reapply immediately.
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
updateComplete(state == ArmedState.Hit, 0);
}

View File

@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
public override void ApplyTransformsAt(double time, bool propagateChildren = false)
{
// For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either.
// ReSharper disable once RedundantArgumentDefaultValue - removing the "redundant" default value triggers BaseMethodCallWithDefaultParameter
base.ApplyTransformsAt(time, false);
}

View File

@ -100,17 +100,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
case DrawableSpinner d:
Spinner spinner = d.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
this.FadeOut();
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
this.FadeInFromZero(spinner.TimeFadeIn / 2);
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
fixedMiddle.FadeColour(Color4.White);
using (BeginDelayedSequence(spinner.TimePreempt, true))
using (BeginDelayedSequence(spinner.TimePreempt))
fixedMiddle.FadeColour(Color4.Red, spinner.Duration);
}

View File

@ -89,10 +89,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Spinner spinner = d.HitObject;
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
this.FadeOut();
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true))
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
this.FadeInFromZero(spinner.TimeFadeIn / 2);
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
/// <remarks>
/// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
/// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
/// This offset is negated to bring all constants into window-space.
/// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
/// </remarks>
protected const float SPINNER_TOP_OFFSET = 45f - 16f;
@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
double startTime = Math.Min(Time.Current, DrawableSpinner.HitStateUpdateTime - 400);
using (BeginAbsoluteSequence(startTime, true))
using (BeginAbsoluteSequence(startTime))
{
clear.FadeInFromZero(400, Easing.Out);
@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
const double fade_out_duration = 50;
using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration, true))
using (BeginAbsoluteSequence(DrawableSpinner.HitStateUpdateTime - fade_out_duration))
clear.FadeOut(fade_out_duration);
}
else
@ -182,14 +182,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
double spinFadeOutLength = Math.Min(400, d.HitObject.Duration);
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength, true))
using (BeginAbsoluteSequence(drawableHitObject.HitStateUpdateTime - spinFadeOutLength))
spin.FadeOutFromOne(spinFadeOutLength);
break;
case DrawableSpinnerTick d:
if (state == ArmedState.Hit)
{
using (BeginAbsoluteSequence(d.HitStateUpdateTime, true))
using (BeginAbsoluteSequence(d.HitStateUpdateTime))
spin.FadeOut(300);
}

View File

@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
base.UpdateStartTimeStateTransforms();
using (BeginDelayedSequence(-ring_appear_offset, true))
using (BeginDelayedSequence(-ring_appear_offset))
targetRing.ScaleTo(target_ring_scale, 400, Easing.OutQuint);
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestSingleSpan()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray();
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestRepeat()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray();
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestNonEvenTicks()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray();
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestLegacyLastTickOffset()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray();
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
Assert.That(events[2].Time, Is.EqualTo(900));
@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps
const double velocity = 5;
const double min_distance = velocity * 10;
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray();
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
Assert.Multiple(() =>
{

View File

@ -38,19 +38,28 @@ namespace osu.Game.Tests.Database
[Test]
public void TestDefaultsPopulationAndQuery()
{
Assert.That(query().Count, Is.EqualTo(0));
Assert.That(queryCount(), Is.EqualTo(0));
KeyBindingContainer testContainer = new TestKeyBindingContainer();
keyBindingStore.Register(testContainer);
Assert.That(query().Count, Is.EqualTo(3));
Assert.That(queryCount(), Is.EqualTo(3));
Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Back).Count, Is.EqualTo(1));
Assert.That(query().Where(k => k.ActionInt == (int)GlobalAction.Select).Count, Is.EqualTo(2));
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
}
private IQueryable<RealmKeyBinding> query() => realmContextFactory.Context.All<RealmKeyBinding>();
private int queryCount(GlobalAction? match = null)
{
using (var usage = realmContextFactory.GetForRead())
{
var results = usage.Realm.All<RealmKeyBinding>();
if (match.HasValue)
results = results.Where(k => k.ActionInt == (int)match.Value);
return results.Count();
}
}
[Test]
public void TestUpdateViaQueriedReference()
@ -59,25 +68,28 @@ namespace osu.Game.Tests.Database
keyBindingStore.Register(testContainer);
var backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back);
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
var tsr = ThreadSafeReference.Create(backBinding);
using (var usage = realmContextFactory.GetForWrite())
using (var primaryUsage = realmContextFactory.GetForRead())
{
var binding = usage.Realm.ResolveReference(tsr);
binding.KeyCombination = new KeyCombination(InputKey.BackSpace);
var backBinding = primaryUsage.Realm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
usage.Commit();
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
var tsr = ThreadSafeReference.Create(backBinding);
using (var usage = realmContextFactory.GetForWrite())
{
var binding = usage.Realm.ResolveReference(tsr);
binding.KeyCombination = new KeyCombination(InputKey.BackSpace);
usage.Commit();
}
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
// check still correct after re-query.
backBinding = primaryUsage.Realm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
}
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
// check still correct after re-query.
backBinding = query().Single(k => k.ActionInt == (int)GlobalAction.Back);
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
}
[TearDown]

View File

@ -0,0 +1,41 @@
// 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 NUnit.Framework;
using osu.Game.Beatmaps;
namespace osu.Game.Tests.Localisation
{
[TestFixture]
public class BeatmapMetadataRomanisationTest
{
[Test]
public void TestRomanisation()
{
var metadata = new BeatmapMetadata
{
Artist = "Romanised Artist",
ArtistUnicode = "Unicode Artist",
Title = "Romanised title",
TitleUnicode = "Unicode Title"
};
var romanisableString = metadata.ToRomanisableString();
Assert.AreEqual(metadata.ToString(), romanisableString.Romanised);
Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original);
}
[Test]
public void TestRomanisationNoUnicode()
{
var metadata = new BeatmapMetadata
{
Artist = "Romanised Artist",
Title = "Romanised title"
};
var romanisableString = metadata.ToRomanisableString();
Assert.AreEqual(romanisableString.Romanised, romanisableString.Original);
}
}
}

View File

@ -0,0 +1,123 @@
// 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.CodeAnalysis;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Tests.NonVisual
{
public class FirstAvailableHitWindowsTest
{
private TestDrawableRuleset testDrawableRuleset;
[SetUp]
public void Setup()
{
testDrawableRuleset = new TestDrawableRuleset();
}
[Test]
public void TestResultIfOnlyParentHitWindowIsEmpty()
{
var testObject = new TestHitObject(HitWindows.Empty);
HitObject nested = new TestHitObject(new HitWindows());
testObject.AddNested(nested);
testDrawableRuleset.HitObjects = new List<HitObject> { testObject };
Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, nested.HitWindows);
}
[Test]
public void TestResultIfParentHitWindowsIsNotEmpty()
{
var testObject = new TestHitObject(new HitWindows());
HitObject nested = new TestHitObject(new HitWindows());
testObject.AddNested(nested);
testDrawableRuleset.HitObjects = new List<HitObject> { testObject };
Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, testObject.HitWindows);
}
[Test]
public void TestResultIfParentAndChildHitWindowsAreEmpty()
{
var firstObject = new TestHitObject(HitWindows.Empty);
HitObject nested = new TestHitObject(HitWindows.Empty);
firstObject.AddNested(nested);
var secondObject = new TestHitObject(new HitWindows());
testDrawableRuleset.HitObjects = new List<HitObject> { firstObject, secondObject };
Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows);
}
[Test]
public void TestResultIfAllHitWindowsAreEmpty()
{
var firstObject = new TestHitObject(HitWindows.Empty);
HitObject nested = new TestHitObject(HitWindows.Empty);
firstObject.AddNested(nested);
testDrawableRuleset.HitObjects = new List<HitObject> { firstObject };
Assert.IsNull(testDrawableRuleset.FirstAvailableHitWindows);
}
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
private class TestDrawableRuleset : DrawableRuleset
{
public List<HitObject> HitObjects;
public override IEnumerable<HitObject> Objects => HitObjects;
public override event Action<JudgementResult> NewResult;
public override event Action<JudgementResult> RevertResult;
public override Playfield Playfield { get; }
public override Container Overlays { get; }
public override Container FrameStableComponents { get; }
public override IFrameStableClock FrameStableClock { get; }
internal override bool FrameStablePlayback { get; set; }
public override IReadOnlyList<Mod> Mods { get; }
public override double GameplayStartTime { get; }
public override GameplayCursorContainer Cursor { get; }
public TestDrawableRuleset()
: base(new OsuRuleset())
{
// won't compile without this.
NewResult?.Invoke(null);
RevertResult?.Invoke(null);
}
public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();
public override void SetRecordTarget(Score score) => throw new NotImplementedException();
public override void RequestResume(Action continueResume) => throw new NotImplementedException();
public override void CancelResume() => throw new NotImplementedException();
}
public class TestHitObject : HitObject
{
public TestHitObject(HitWindows hitWindows)
{
HitWindows = hitWindows;
HitWindows.SetDifficulty(0.5f);
}
public new void AddNested(HitObject nested) => base.AddNested(nested);
}
}
}

View File

@ -114,11 +114,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public bool ResultsCreated { get; private set; }
public FakeRankingPushPlayer()
: base(true, true)
{
}
protected override ResultsScreen CreateResults(ScoreInfo score)
{
var results = base.CreateResults(score);

View File

@ -9,6 +9,8 @@ using osu.Game.Online.Rooms;
using osu.Game.Online.Solo;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Gameplay
@ -35,6 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addFakeHit();
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
@ -52,6 +57,9 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addFakeHit();
AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
@ -67,10 +75,29 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addFakeHit();
AddStep("exit", () => Player.Exit());
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
[Test]
public void TestNoSubmissionOnEmptyFail()
{
prepareTokenResponse(true);
CreateTest(() => allowFail = true);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
[Test]
public void TestSubmissionOnFail()
{
@ -79,12 +106,28 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(() => allowFail = true);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
addFakeHit();
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
}
[Test]
public void TestNoSubmissionOnEmptyExit()
{
prepareTokenResponse(true);
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
AddStep("exit", () => Player.Exit());
AddAssert("ensure no submission", () => Player.SubmittedScore == null);
}
[Test]
public void TestSubmissionOnExit()
{
@ -93,10 +136,27 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(() => allowFail = false);
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
addFakeHit();
AddStep("exit", () => Player.Exit());
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
}
private void addFakeHit()
{
AddUntilStep("wait for first result", () => Player.Results.Count > 0);
AddStep("force successfuly hit", () =>
{
Player.ScoreProcessor.RevertResult(Player.Results.First());
Player.ScoreProcessor.ApplyResult(new OsuJudgementResult(Beatmap.Value.Beatmap.HitObjects.First(), new OsuJudgement())
{
Type = HitResult.Great,
});
});
}
private void prepareTokenResponse(bool validToken)
{
AddStep("Prepare test API", () =>

View File

@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Children = new[]
{
new ExposedSkinnableDrawable("default", _ => new DefaultBox()),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.ScaleToFit),
new ExposedSkinnableDrawable("available", _ => new DefaultBox()),
new ExposedSkinnableDrawable("available", _ => new DefaultBox(), ConfineMode.NoScaling)
}
},

View File

@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Gameplay
foreach (var legacyFrame in frames.Frames)
{
var frame = new TestReplayFrame();
frame.FromLegacy(legacyFrame, null, null);
frame.FromLegacy(legacyFrame, null);
replay.Frames.Add(frame);
}
}

View File

@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send initial frames for both players. A few more for player 1.
sendFrames(PLAYER_1_ID, 20);
sendFrames(PLAYER_2_ID, 10);
sendFrames(PLAYER_2_ID);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
@ -194,7 +194,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertMuted(PLAYER_1_ID, true);
assertMuted(PLAYER_2_ID, true);
sendFrames(PLAYER_1_ID, 10);
sendFrames(PLAYER_1_ID);
sendFrames(PLAYER_2_ID, 20);
checkPaused(PLAYER_1_ID, false);
assertOneNotMuted();

View File

@ -11,6 +11,7 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
@ -39,10 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayer multiplayerScreen;
private TestMultiplayerClient client;
public TestSceneMultiplayer()
{
loadMultiplayer();
}
[Cached(typeof(UserLookupCache))]
private UserLookupCache lookupCache = new TestUserLookupCache();
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@ -51,18 +50,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
}
[SetUp]
public void Setup() => Schedule(() =>
public override void SetUpSteps()
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
});
base.SetUpSteps();
AddStep("import beatmap", () =>
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
});
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
AddStep("load dependencies", () =>
{
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
// The screen gets suspended so it stops receiving updates.
Child = client;
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
});
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
}
[Test]
public void TestEmpty()
{
// used to test the flow of multiplayer from visual tests.
}
[Test]
public void TestUserSetToIdleWhenBeatmapDeleted()
{
loadMultiplayer();
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@ -85,8 +109,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{
loadMultiplayer();
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@ -123,8 +145,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLocalPlayStartsWhileSpectatingWhenBeatmapBecomesAvailable()
{
loadMultiplayer();
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@ -167,8 +187,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestLeaveNavigation()
{
loadMultiplayer();
createRoom(() => new Room
{
Name = { Value = "Test Room" },
@ -227,26 +245,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => client.Room != null);
}
private void loadMultiplayer()
{
AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
AddStep("load dependencies", () =>
{
client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
// The screen gets suspended so it stops receiving updates.
Child = client;
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
});
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
}
/// <summary>
/// Used for the sole purpose of adding <see cref="TestMultiplayerClient"/> as a resolvable dependency.
/// </summary>

View File

@ -113,10 +113,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
addClickButtonStep();
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
addClickButtonStep();
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
}
[TestCase(true)]
@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
addClickButtonStep();
AddAssert("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
verifyGameplayStartFlow();
}
@ -206,8 +206,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void verifyGameplayStartFlow()
{
AddUntilStep("user is ready", () => Client.Room?.Users[0].State == MultiplayerUserState.Ready);
addClickButtonStep();
AddAssert("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
AddUntilStep("user waiting for load", () => Client.Room?.Users[0].State == MultiplayerUserState.WaitingForLoad);
AddAssert("ready button disabled", () => !button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddStep("transitioned to gameplay", () => readyClickOperation.Dispose());
@ -218,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.ChangeUserState(Client.Room?.Users[0].UserID ?? 0, MultiplayerUserState.FinishedPlay);
});
AddAssert("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
AddUntilStep("ready button enabled", () => button.ChildrenOfType<OsuButton>().Single().Enabled.Value);
}
}
}

View File

@ -122,10 +122,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestToggleWhenIdle(MultiplayerUserState initialState)
{
addClickSpectateButtonStep();
AddAssert("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
AddUntilStep("user is spectating", () => Client.Room?.Users[0].State == MultiplayerUserState.Spectating);
addClickSpectateButtonStep();
AddAssert("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
AddUntilStep("user is idle", () => Client.Room?.Users[0].State == MultiplayerUserState.Idle);
}
[TestCase(MultiplayerRoomState.Closed)]
@ -174,9 +174,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
});
private void assertSpectateButtonEnablement(bool shouldBeEnabled)
=> AddAssert($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
=> AddUntilStep($"spectate button {(shouldBeEnabled ? "is" : "is not")} enabled", () => spectateButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
private void assertReadyButtonEnablement(bool shouldBeEnabled)
=> AddAssert($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
=> AddUntilStep($"ready button {(shouldBeEnabled ? "is" : "is not")} enabled", () => readyButton.ChildrenOfType<OsuButton>().Single().Enabled.Value == shouldBeEnabled);
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Source = "osu!",
Tags = "this beatmap has all the metrics",
},
BaseDifficulty = new BeatmapDifficulty
@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Version = "Only Ratings",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Source = "osu!",
Tags = "this beatmap has ratings metrics but not retries or fails",
},
BaseDifficulty = new BeatmapDifficulty
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Source = "osu!",
Tags = "this beatmap has retries and fails but no ratings",
},
BaseDifficulty = new BeatmapDifficulty
@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Version = "No Metrics",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Source = "osu!",
Tags = "this beatmap has no metrics",
},
BaseDifficulty = new BeatmapDifficulty

View File

@ -103,14 +103,11 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = osu.GetModsFor(ModType.DifficultyReduction);
var harderMods = osu.GetModsFor(ModType.DifficultyIncrease);
var conversionMods = osu.GetModsFor(ModType.Conversion);
var noFailMod = osu.GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var targetMod = conversionMods.FirstOrDefault(m => m is OsuModTarget);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@ -118,8 +115,6 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiMod(doubleTimeMod);
testIncompatibleMods(easy, hardRock);
testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
testUnimplementedMod(targetMod);
}
[Test]
@ -249,6 +244,19 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("DT + HD still selected", () => modSelect.ChildrenOfType<ModButton>().Count(b => b.Selected) == 2);
}
[Test]
public void TestUnimplementedModIsUnselectable()
{
var testRuleset = new TestUnimplementedModOsuRuleset();
changeTestRuleset(testRuleset.RulesetInfo);
var conversionMods = testRuleset.GetModsFor(ModType.Conversion);
var unimplementedMod = conversionMods.FirstOrDefault(m => m is TestUnimplementedMod);
testUnimplementedMod(unimplementedMod);
}
private void testSingleMod(Mod mod)
{
selectNext(mod);
@ -343,6 +351,12 @@ namespace osu.Game.Tests.Visual.UserInterface
waitForLoad();
}
private void changeTestRuleset(RulesetInfo rulesetInfo)
{
AddStep($"change ruleset to {rulesetInfo.Name}", () => { Ruleset.Value = rulesetInfo; });
waitForLoad();
}
private void waitForLoad() =>
AddUntilStep("wait for icons to load", () => modSelect.AllLoaded);
@ -401,5 +415,24 @@ namespace osu.Game.Tests.Visual.UserInterface
{
protected override bool Stacked => false;
}
private class TestUnimplementedMod : Mod
{
public override string Name => "Unimplemented mod";
public override string Acronym => "UM";
public override string Description => "A mod that is not implemented.";
public override double ScoreMultiplier => 1;
public override ModType Type => ModType.Conversion;
}
private class TestUnimplementedModOsuRuleset : OsuRuleset
{
public override IEnumerable<Mod> GetModsFor(ModType type)
{
if (type == ModType.Conversion) return base.GetModsFor(type).Concat(new[] { new TestUnimplementedMod() });
return base.GetModsFor(type);
}
}
}
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestInitialVisibility()
{
AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero, 0));
AddStep("Create header with 0 value", () => createHeader("Header with visible when zero counter", CounterVisibilityState.VisibleWhenZero));
AddAssert("Value is 0", () => header.Current.Value == 0);
AddAssert("Counter is visible", () => header.ChildrenOfType<CounterPill>().First().Alpha == 1);

View File

@ -20,7 +20,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
[Test]
public void TestDefaultDirectory()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory)))
{
try
{
@ -139,8 +139,13 @@ namespace osu.Game.Tournament.Tests.NonVisual
}
finally
{
host.Storage.Delete("tournament.ini");
host.Storage.DeleteDirectory("tournaments");
try
{
host.Storage.Delete("tournament.ini");
host.Storage.DeleteDirectory("tournaments");
}
catch { }
host.Exit();
}
}

View File

@ -172,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
{
chat?.Contract();
using (BeginDelayedSequence(300, true))
using (BeginDelayedSequence(300))
{
scoreDisplay.FadeIn(100);
SongBar.Expanded = true;

2
osu.Game/.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.cs]
dotnet_diagnostic.OLOC001.prefix_namespace = osu.Game.Resources.Localisation

View File

@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import)
{
if (!base.CanReuseExisting(existing, import))
if (!base.CanSkipImport(existing, import))
return false;
return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null);

View File

@ -94,7 +94,10 @@ namespace osu.Game.Beatmaps
public RomanisableString ToRomanisableString()
{
string author = Author == null ? string.Empty : $"({Author})";
return new RomanisableString($"{ArtistUnicode} - {TitleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim());
var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode;
var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode;
return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim());
}
[JsonIgnore]

View File

@ -353,8 +353,6 @@ namespace osu.Game.Database
{
cancellationToken.ThrowIfCancellationRequested();
delayEvents();
bool checkedExisting = false;
TModel existing = null;
@ -394,6 +392,8 @@ namespace osu.Game.Database
}
}
delayEvents();
try
{
LogForModel(item, @"Beginning import...");

View File

@ -9,6 +9,7 @@ namespace osu.Game.Database
{
/// <summary>
/// The main realm context, bound to the update thread.
/// If querying from a non-update thread is needed, use <see cref="GetForRead"/> or <see cref="GetForWrite"/> to receive a context instead.
/// </summary>
Realm Context { get; }

View File

@ -77,6 +77,9 @@ namespace osu.Game.Database
{
cmd.CommandText = "PRAGMA journal_mode=WAL;";
cmd.ExecuteNonQuery();
cmd.CommandText = "PRAGMA foreign_keys=OFF;";
cmd.ExecuteNonQuery();
}
}
catch

View File

@ -2,8 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Platform;
@ -38,21 +40,29 @@ namespace osu.Game.Database
private static readonly GlobalStatistic<int> pending_writes = GlobalStatistics.Get<int>("Realm", "Pending writes");
private static readonly GlobalStatistic<int> active_usages = GlobalStatistics.Get<int>("Realm", "Active usages");
private readonly object updateContextLock = new object();
private Realm context;
public Realm Context
{
get
{
if (context == null)
if (!ThreadSafety.IsUpdateThread)
throw new InvalidOperationException($"Use {nameof(GetForRead)} or {nameof(GetForWrite)} when performing realm operations from a non-update thread");
lock (updateContextLock)
{
context = createContext();
Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}");
if (context == null)
{
context = createContext();
Logger.Log($"Opened realm \"{context.Config.DatabasePath}\" at version {context.Config.SchemaVersion}");
}
// creating a context will ensure our schema is up-to-date and migrated.
return context;
}
// creating a context will ensure our schema is up-to-date and migrated.
return context;
}
}
@ -107,8 +117,11 @@ namespace osu.Game.Database
{
base.Update();
if (context?.Refresh() == true)
refreshes.Value++;
lock (updateContextLock)
{
if (context?.Refresh() == true)
refreshes.Value++;
}
}
private Realm createContext()
@ -154,9 +167,15 @@ namespace osu.Game.Database
private void flushContexts()
{
Logger.Log(@"Flushing realm contexts...", LoggingTarget.Database);
Debug.Assert(blockingLock.CurrentCount == 0);
var previousContext = context;
context = null;
Realm previousContext;
lock (updateContextLock)
{
previousContext = context;
context = null;
}
// wait for all threaded usages to finish
while (active_usages.Value > 0)

View File

@ -27,6 +27,30 @@ namespace osu.Game.Database
[ItemCanBeNull]
public Task<User> GetUserAsync(int userId, CancellationToken token = default) => GetAsync(userId, token);
/// <summary>
/// Perform an API lookup on the specified users, populating a <see cref="User"/> model.
/// </summary>
/// <param name="userIds">The users to lookup.</param>
/// <param name="token">An optional cancellation token.</param>
/// <returns>The populated users. May include null results for failed retrievals.</returns>
public Task<User[]> GetUsersAsync(int[] userIds, CancellationToken token = default)
{
var userLookupTasks = new List<Task<User>>();
foreach (var u in userIds)
{
userLookupTasks.Add(GetUserAsync(u, token).ContinueWith(task =>
{
if (!task.IsCompletedSuccessfully)
return null;
return task.Result;
}, token));
}
return Task.WhenAll(userLookupTasks);
}
protected override async Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
=> await queryUser(lookup).ConfigureAwait(false);

View File

@ -97,7 +97,7 @@ namespace osu.Game.Graphics.Containers
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
return;
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
using (BeginDelayedSequence(-TimeSinceLastBeat))
OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty);
lastBeat = beatIndex;

View File

@ -15,6 +15,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
namespace osu.Game.Graphics.UserInterface
{
@ -99,7 +100,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colours)
{
sample = audio.Samples.Get(@"UI/sliderbar-notch");
sample = audio.Samples.Get(@"UI/notch-tick");
AccentColour = colours.Pink;
}
@ -149,7 +150,7 @@ namespace osu.Game.Graphics.UserInterface
private void playSample(T value)
{
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50)
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30)
return;
if (value.Equals(lastSampleValue))
@ -158,13 +159,15 @@ namespace osu.Game.Graphics.UserInterface
lastSampleValue = value;
lastSampleTime = Clock.CurrentTime;
var channel = sample.Play();
var channel = sample.GetChannel();
channel.Frequency.Value = 1 + NormalizedValue * 0.2f;
if (NormalizedValue == 0)
channel.Frequency.Value -= 0.4f;
else if (NormalizedValue == 1)
channel.Frequency.Value += 0.4f;
channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + NormalizedValue * 0.2f;
// intentionally pitched down, even when hitting max.
if (NormalizedValue == 0 || NormalizedValue == 1)
channel.Frequency.Value -= 0.5f;
channel.Play();
}
private void updateTooltipText(T value)

View File

@ -113,7 +113,7 @@ namespace osu.Game.Graphics.UserInterface
double delay = (current <= newValue ? Math.Max(i - current, 0) : Math.Max(current - 1 - i, 0)) * AnimationDelay;
using (star.BeginDelayedSequence(delay, true))
using (star.BeginDelayedSequence(delay))
star.DisplayAt(getStarScale(i, newValue));
}
}

View File

@ -1,38 +0,0 @@
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="solo" xml:space="preserve">
<value>ソロ</value>
</data>
<data name="playlists" xml:space="preserve">
<value>プレイリスト</value>
</data>
<data name="play" xml:space="preserve">
<value>遊ぶ</value>
</data>
<data name="multi" xml:space="preserve">
<value>マルチ</value>
</data>
<data name="edit" xml:space="preserve">
<value>エディット</value>
</data>
<data name="browse" xml:space="preserve">
<value>ブラウズ</value>
</data>
<data name="exit" xml:space="preserve">
<value>閉じる</value>
</data>
<data name="settings" xml:space="preserve">
<value>設定</value>
</data>
</root>

View File

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="solo" xml:space="preserve">
<value>solo</value>
</data>
<data name="multi" xml:space="preserve">
<value>multi</value>
</data>
<data name="playlists" xml:space="preserve">
<value>playlists</value>
</data>
<data name="play" xml:space="preserve">
<value>play</value>
</data>
<data name="edit" xml:space="preserve">
<value>edit</value>
</data>
<data name="browse" xml:space="preserve">
<value>browse</value>
</data>
<data name="settings" xml:space="preserve">
<value>settings</value>
</data>
<data name="back" xml:space="preserve">
<value>back</value>
</data>
<data name="exit" xml:space="preserve">
<value>exit</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class ButtonSystemStrings
{
private const string prefix = @"osu.Game.Localisation.ButtonSystem";
private const string prefix = @"osu.Game.Resources.Localisation.ButtonSystem";
/// <summary>
/// "solo"
@ -56,4 +56,4 @@ namespace osu.Game.Localisation
private static string getKey(string key) => $@"{prefix}:{key}";
}
}
}

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="header_title" xml:space="preserve">
<value>chat</value>
</data>
<data name="header_description" xml:space="preserve">
<value>join the real-time discussion</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class ChatStrings
{
private const string prefix = @"osu.Game.Localisation.Chat";
private const string prefix = @"osu.Game.Resources.Localisation.Chat";
/// <summary>
/// "chat"

View File

@ -1,64 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="cancel" xml:space="preserve">
<value>Cancel</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class CommonStrings
{
private const string prefix = @"osu.Game.Localisation.Common";
private const string prefix = @"osu.Game.Resources.Localisation.Common";
/// <summary>
/// "Cancel"

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="header_title" xml:space="preserve">
<value>notifications</value>
</data>
<data name="header_description" xml:space="preserve">
<value>waiting for 'ya</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class NotificationsStrings
{
private const string prefix = @"osu.Game.Localisation.Notifications";
private const string prefix = @"osu.Game.Resources.Localisation.Notifications";
/// <summary>
/// "notifications"

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="header_title" xml:space="preserve">
<value>now playing</value>
</data>
<data name="header_description" xml:space="preserve">
<value>manage the currently playing track</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class NowPlayingStrings
{
private const string prefix = @"osu.Game.Localisation.NowPlaying";
private const string prefix = @"osu.Game.Resources.Localisation.NowPlaying";
/// <summary>
/// "now playing"

View File

@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="header_title" xml:space="preserve">
<value>settings</value>
</data>
<data name="header_description" xml:space="preserve">
<value>change the way osu! behaves</value>
</data>
</root>

View File

@ -7,7 +7,7 @@ namespace osu.Game.Localisation
{
public static class SettingsStrings
{
private const string prefix = @"osu.Game.Localisation.Settings";
private const string prefix = @"osu.Game.Resources.Localisation.Settings";
/// <summary>
/// "settings"

View File

@ -13,7 +13,7 @@ namespace osu.Game.Online.API.Requests
private readonly RankingsSortCriteria sort;
public GetSpotlightRankingsRequest(RulesetInfo ruleset, int spotlight, RankingsSortCriteria sort)
: base(ruleset, 1)
: base(ruleset)
{
this.spotlight = spotlight;
this.sort = sort;

View File

@ -320,6 +320,7 @@ namespace osu.Game.Online.Chat
JoinMultiplayerMatch,
Spectate,
OpenUserProfile,
SearchBeatmapSet,
OpenWiki,
Custom,
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Online.Leaderboards
foreach (var s in scrollFlow.Children)
{
using (s.BeginDelayedSequence(i++ * 50, true))
using (s.BeginDelayedSequence(i++ * 50))
s.Show();
}

View File

@ -248,7 +248,7 @@ namespace osu.Game.Online.Leaderboards
this.FadeIn(200);
content.MoveToY(0, 800, Easing.OutQuint);
using (BeginDelayedSequence(100, true))
using (BeginDelayedSequence(100))
{
avatar.FadeIn(300, Easing.OutQuint);
nameLabel.FadeIn(350, Easing.OutQuint);
@ -256,12 +256,12 @@ namespace osu.Game.Online.Leaderboards
avatar.MoveToX(0, 300, Easing.OutQuint);
nameLabel.MoveToX(0, 350, Easing.OutQuint);
using (BeginDelayedSequence(250, true))
using (BeginDelayedSequence(250))
{
scoreLabel.FadeIn(200);
scoreRank.FadeIn(200);
using (BeginDelayedSequence(50, true))
using (BeginDelayedSequence(50))
{
var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray();
for (int i = 0; i < drawables.Length; i++)

View File

@ -305,6 +305,10 @@ namespace osu.Game
ShowChannel(link.Argument);
break;
case LinkAction.SearchBeatmapSet:
SearchBeatmapSet(link.Argument);
break;
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
@ -375,6 +379,12 @@ namespace osu.Game
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId));
/// <summary>
/// Shows the beatmap listing overlay, with the given <paramref name="query"/> in the search box.
/// </summary>
/// <param name="query">The query to search for.</param>
public void SearchBeatmapSet(string query) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithSearch(query));
/// <summary>
/// Show a wiki's page as an overlay
/// </summary>

View File

@ -80,7 +80,7 @@ namespace osu.Game
return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release");
var version = AssemblyVersion;
return $@"{version.Major}.{version.Minor}.{version.Build}";
return $@"{version.Major}.{version.Minor}.{version.Build}-lazer";
}
}
@ -162,7 +162,7 @@ namespace osu.Game
public OsuGameBase()
{
UseDevelopmentServer = DebugUtils.IsDebugBuild;
Name = @"osu!lazer";
Name = @"osu!";
}
[BackgroundDependencyLoader]

View File

@ -122,6 +122,9 @@ namespace osu.Game.Overlays.BeatmapListing
sortControlBackground.Colour = colourProvider.Background5;
}
public void Search(string query)
=> searchControl.Query.Value = query;
protected override void LoadComplete()
{
base.LoadComplete();

View File

@ -89,6 +89,12 @@ namespace osu.Game.Overlays
};
}
public void ShowWithSearch(string query)
{
filterControl.Search(query);
Show();
}
protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader();
protected override Color4 BackgroundColour => ColourProvider.Background6;

View File

@ -8,15 +8,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Overlays.BeatmapSet
{
public class Info : Container
{
private const float transition_duration = 250;
private const float metadata_width = 175;
private const float spacing = 20;
private const float base_height = 220;
@ -60,7 +57,7 @@ namespace osu.Game.Overlays.BeatmapSet
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new MetadataSection("Description"),
Child = new MetadataSection(MetadataType.Description),
},
},
new Container
@ -78,10 +75,10 @@ namespace osu.Game.Overlays.BeatmapSet
Direction = FillDirection.Full,
Children = new[]
{
source = new MetadataSection("Source"),
genre = new MetadataSection("Genre") { Width = 0.5f },
language = new MetadataSection("Language") { Width = 0.5f },
tags = new MetadataSection("Tags"),
source = new MetadataSection(MetadataType.Source),
genre = new MetadataSection(MetadataType.Genre) { Width = 0.5f },
language = new MetadataSection(MetadataType.Language) { Width = 0.5f },
tags = new MetadataSection(MetadataType.Tags),
},
},
},
@ -135,48 +132,5 @@ namespace osu.Game.Overlays.BeatmapSet
successRateBackground.Colour = colourProvider.Background4;
background.Colour = colourProvider.Background5;
}
private class MetadataSection : FillFlowContainer
{
private readonly TextFlowContainer textFlow;
public string Text
{
set
{
if (string.IsNullOrEmpty(value))
{
Hide();
return;
}
this.FadeIn(transition_duration);
textFlow.Clear();
textFlow.AddText(value, s => s.Font = s.Font.With(size: 12));
}
}
public MetadataSection(string title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Spacing = new Vector2(5f);
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Text = title,
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
Margin = new MarginPadding { Top = 15 },
},
textFlow = new OsuTextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
};
}
}
}
}

View File

@ -0,0 +1,115 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapSet
{
public class MetadataSection : Container
{
private readonly FillFlowContainer textContainer;
private readonly MetadataType type;
private TextFlowContainer textFlow;
private const float transition_duration = 250;
public MetadataSection(MetadataType type)
{
this.type = type;
Alpha = 0;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = textContainer = new FillFlowContainer
{
Alpha = 0,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Margin = new MarginPadding { Top = 15 },
Spacing = new Vector2(5),
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new OsuSpriteText
{
Text = this.type.ToString(),
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14),
},
},
},
};
}
public string Text
{
set
{
if (string.IsNullOrEmpty(value))
{
this.FadeOut(transition_duration);
return;
}
this.FadeIn(transition_duration);
setTextAsync(value);
}
}
private void setTextAsync(string text)
{
LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Colour = Color4.White.Opacity(0.75f),
}, loaded =>
{
textFlow?.Expire();
switch (type)
{
case MetadataType.Tags:
string[] tags = text.Split(" ");
for (int i = 0; i <= tags.Length - 1; i++)
{
loaded.AddLink(tags[i], LinkAction.SearchBeatmapSet, tags[i]);
if (i != tags.Length - 1)
loaded.AddText(" ");
}
break;
case MetadataType.Source:
loaded.AddLink(text, LinkAction.SearchBeatmapSet, text);
break;
default:
loaded.AddText(text);
break;
}
textContainer.Add(textFlow = loaded);
// fade in if we haven't yet.
textContainer.FadeIn(transition_duration);
});
}
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace osu.Game.Overlays.BeatmapSet
{
public enum MetadataType
{
Tags,
Source,
Description,
Genre,
Language
}
}

View File

@ -213,7 +213,7 @@ namespace osu.Game.Overlays
innerSpin.Spin(20000, RotationDirection.Clockwise);
outerSpin.Spin(40000, RotationDirection.Clockwise);
using (BeginDelayedSequence(200, true))
using (BeginDelayedSequence(200))
{
disc.FadeIn(initial_duration)
.ScaleTo(1f, initial_duration * 2, Easing.OutElastic);
@ -221,7 +221,7 @@ namespace osu.Game.Overlays
particleContainer.FadeIn(initial_duration);
outerSpin.FadeTo(0.1f, initial_duration * 2);
using (BeginDelayedSequence(initial_duration + 200, true))
using (BeginDelayedSequence(initial_duration + 200))
{
backgroundStrip.FadeIn(step_duration);
leftStrip.ResizeWidthTo(1f, step_duration, Easing.OutQuint);

View File

@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Mods
backgroundIcon.Mod = newSelection;
using (BeginDelayedSequence(mod_switch_duration, true))
using (BeginDelayedSequence(mod_switch_duration))
{
foregroundIcon
.RotateTo(-rotate_angle * direction)

View File

@ -128,7 +128,7 @@ namespace osu.Game.Overlays.Mods
RowDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 90),
new Dimension(GridSizeMode.Distributed),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]

View File

@ -13,7 +13,7 @@ namespace osu.Game.Overlays.News
public Action ShowFrontPage;
private readonly Bindable<string> article = new Bindable<string>(null);
private readonly Bindable<string> article = new Bindable<string>();
public NewsHeader()
{

View File

@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Distributed)
new Dimension()
},
Content = new[]
{

View File

@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Rankings.Tables
private static TableColumn[] mainHeaders => new[]
{
new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.Absolute, 40)), // place
new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed)), // flag and username (country name)
new TableColumn(string.Empty, Anchor.CentreLeft, new Dimension()), // flag and username (country name)
};
protected abstract TableColumn[] CreateAdditionalHeaders();

View File

@ -4,6 +4,8 @@
using System;
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -36,6 +38,9 @@ namespace osu.Game.Overlays.Volume
private OsuSpriteText text;
private BufferedContainer maxGlow;
private Sample sample;
private double sampleLastPlaybackTime;
public VolumeMeter(string name, float circleSize, Color4 meterColour)
{
this.circleSize = circleSize;
@ -46,8 +51,11 @@ namespace osu.Game.Overlays.Volume
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OsuColour colours, AudioManager audio)
{
sample = audio.Samples.Get(@"UI/notch-tick");
sampleLastPlaybackTime = Time.Current;
Color4 backgroundColour = colours.Gray1;
CircularProgress bgProgress;
@ -178,22 +186,12 @@ namespace osu.Game.Overlays.Volume
}
};
Bindable.ValueChanged += volume =>
{
this.TransformTo("DisplayVolume",
volume.NewValue,
400,
Easing.OutQuint);
};
Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
bgProgress.Current.Value = 0.75f;
}
protected override void LoadComplete()
{
base.LoadComplete();
Bindable.TriggerChange();
}
private int? displayVolumeInt;
private double displayVolume;
@ -204,6 +202,11 @@ namespace osu.Game.Overlays.Volume
{
displayVolume = value;
int intValue = (int)Math.Round(displayVolume * 100);
bool intVolumeChanged = intValue != displayVolumeInt;
displayVolumeInt = intValue;
if (displayVolume >= 0.995f)
{
text.Text = "MAX";
@ -212,14 +215,36 @@ namespace osu.Game.Overlays.Volume
else
{
maxGlow.EffectColour = Color4.Transparent;
text.Text = Math.Round(displayVolume * 100).ToString(CultureInfo.CurrentCulture);
text.Text = intValue.ToString(CultureInfo.CurrentCulture);
}
volumeCircle.Current.Value = displayVolume * 0.75f;
volumeCircleGlow.Current.Value = displayVolume * 0.75f;
if (intVolumeChanged && IsLoaded)
Scheduler.AddOnce(playTickSound);
}
}
private void playTickSound()
{
const int tick_debounce_time = 30;
if (Time.Current - sampleLastPlaybackTime <= tick_debounce_time)
return;
var channel = sample.GetChannel();
channel.Frequency.Value = 0.99f + RNG.NextDouble(0.02f) + displayVolume * 0.1f;
// intentionally pitched down, even when hitting max.
if (displayVolumeInt == 0 || displayVolumeInt == 100)
channel.Frequency.Value -= 0.5f;
channel.Play();
sampleLastPlaybackTime = Time.Current;
}
public double Volume
{
get => Bindable.Value;

View File

@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Judgements
LifetimeStart = Result.TimeAbsolute;
using (BeginAbsoluteSequence(Result.TimeAbsolute, true))
using (BeginAbsoluteSequence(Result.TimeAbsolute))
{
// not sure if this should remain going forward.
JudgementBody.ResetAnimation();

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods
};
[SettingSource("Direction", "The direction of rotation")]
public Bindable<RotationDirection> Direction { get; } = new Bindable<RotationDirection>(RotationDirection.Clockwise);
public Bindable<RotationDirection> Direction { get; } = new Bindable<RotationDirection>();
public override string Name => "Barrel Roll";
public override string Acronym => "BR";

View File

@ -404,13 +404,13 @@ namespace osu.Game.Rulesets.Objects.Drawables
clearExistingStateTransforms();
using (BeginAbsoluteSequence(transformTime, true))
using (BeginAbsoluteSequence(transformTime))
UpdateInitialTransforms();
using (BeginAbsoluteSequence(StateUpdateTime, true))
using (BeginAbsoluteSequence(StateUpdateTime))
UpdateStartTimeStateTransforms();
using (BeginAbsoluteSequence(HitStateUpdateTime, true))
using (BeginAbsoluteSequence(HitStateUpdateTime))
UpdateHitStateTransforms(newState);
state.Value = newState;

View File

@ -489,15 +489,15 @@ namespace osu.Game.Rulesets.UI
{
get
{
foreach (var h in Objects)
foreach (var hitObject in Objects)
{
if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return h.HitWindows;
if (hitObject.HitWindows.WindowFor(HitResult.Miss) > 0)
return hitObject.HitWindows;
foreach (var n in h.NestedHitObjects)
foreach (var nested in hitObject.NestedHitObjects)
{
if (h.HitWindows.WindowFor(HitResult.Miss) > 0)
return n.HitWindows;
if (nested.HitWindows.WindowFor(HitResult.Miss) > 0)
return nested.HitWindows;
}
}

View File

@ -208,7 +208,7 @@ namespace osu.Game.Scoring
}
else
{
// This score is guaranteed to be an osu!lazer score.
// This is guaranteed to be a non-legacy score.
// The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values.
beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum();
}

View File

@ -297,7 +297,7 @@ namespace osu.Game.Screens.Menu
Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}");
using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0, true))
using (buttonArea.BeginDelayedSequence(lastState == ButtonSystemState.Initial ? 150 : 0))
{
buttonArea.ButtonSystemState = state;

View File

@ -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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -36,6 +37,8 @@ namespace osu.Game.Screens.Menu
private readonly Bindable<User> currentUser = new Bindable<User>();
private FillFlowContainer fill;
private readonly List<Drawable> expendableText = new List<Drawable>();
public Disclaimer(OsuScreen nextScreen = null)
{
this.nextScreen = nextScreen;
@ -54,7 +57,7 @@ namespace osu.Game.Screens.Menu
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.Solid.Flask,
Icon = OsuIcon.Logo,
Size = new Vector2(icon_size),
Y = icon_y,
},
@ -70,37 +73,55 @@ namespace osu.Game.Screens.Menu
{
textFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
Width = 680,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Spacing = new Vector2(0, 2),
LayoutDuration = 2000,
LayoutEasing = Easing.OutQuint
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Alpha = 0,
Spacing = new Vector2(0, 2),
},
}
}
},
supportFlow = new LinkFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
TextAnchor = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Padding = new MarginPadding(20),
Alpha = 0,
Spacing = new Vector2(0, 2),
},
};
textFlow.AddText("This project is an ongoing ", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Light));
textFlow.AddText("work in progress", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.SemiBold));
textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular));
expendableText.AddRange(textFlow.AddText("lazer", t =>
{
t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular);
t.Colour = colours.PinkLight;
}));
static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular);
static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold);
textFlow.NewParagraph();
static void format(SpriteText t) => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold);
textFlow.AddText("the next ", formatRegular);
textFlow.AddText("major update", t =>
{
t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold);
t.Colour = colours.Pink;
});
expendableText.AddRange(textFlow.AddText(" coming to osu!", formatRegular));
textFlow.AddText(".", formatRegular);
textFlow.AddParagraph(getRandomTip(), t => t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold));
textFlow.NewParagraph();
textFlow.NewParagraph();
textFlow.AddParagraph("today's tip:", formatSemiBold);
textFlow.AddParagraph(getRandomTip(), formatRegular);
textFlow.NewParagraph();
textFlow.NewParagraph();
@ -116,19 +137,19 @@ namespace osu.Game.Screens.Menu
if (e.NewValue.IsSupporter)
{
supportFlow.AddText("Eternal thanks to you for supporting osu!", format);
supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold);
}
else
{
supportFlow.AddText("Consider becoming an ", format);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format);
supportFlow.AddText(" to help support the game", format);
supportFlow.AddText("Consider becoming an ", formatSemiBold);
supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold);
supportFlow.AddText(" to help support osu!'s development", formatSemiBold);
}
heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t =>
{
t.Padding = new MarginPadding { Left = 5, Top = 3 };
t.Font = t.Font.With(size: 12);
t.Font = t.Font.With(size: 20);
t.Origin = Anchor.Centre;
t.Colour = colours.Pink;
}).First();
@ -160,7 +181,7 @@ namespace osu.Game.Screens.Menu
icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint);
using (BeginDelayedSequence(3000, true))
using (BeginDelayedSequence(3000))
{
icon.FadeColour(iconColour, 200, Easing.OutQuint);
icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc)
@ -169,7 +190,15 @@ namespace osu.Game.Screens.Menu
.MoveToY(icon_y, 160, Easing.InQuart)
.FadeColour(Color4.White, 160);
fill.Delay(520 + 160).MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
using (BeginDelayedSequence(520 + 160))
{
fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart);
Schedule(() => expendableText.ForEach(t =>
{
t.FadeOut(100);
t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart);
}));
}
}
supportFlow.FadeOut().Delay(2000).FadeIn(500);
@ -201,7 +230,7 @@ namespace osu.Game.Screens.Menu
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
"For now, what used to be \"osu!direct\" is available to all users on lazer. You can access it anywhere using Ctrl-D!",
"What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-D!",
"Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!",
"Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!",
"Try scrolling down in the mod select panel to find a bunch of new fun mods!",

View File

@ -189,7 +189,7 @@ namespace osu.Game.Screens.Menu
double remainingTime() => length - TransformDelay;
using (BeginDelayedSequence(250, true))
using (BeginDelayedSequence(250))
{
welcomeText.FadeIn(700);
welcomeText.TransformSpacingTo(new Vector2(20, 0), remainingTime(), Easing.Out);
@ -212,17 +212,17 @@ namespace osu.Game.Screens.Menu
lineBottomLeft.MoveTo(new Vector2(-line_end_offset, line_end_offset), line_duration, Easing.OutQuint);
lineBottomRight.MoveTo(new Vector2(line_end_offset, line_end_offset), line_duration, Easing.OutQuint);
using (BeginDelayedSequence(length * 0.56, true))
using (BeginDelayedSequence(length * 0.56))
{
bigRing.ResizeTo(logo_size, 500, Easing.InOutQuint);
bigRing.Foreground.Delay(250).ResizeTo(1, 850, Easing.OutQuint);
using (BeginDelayedSequence(250, true))
using (BeginDelayedSequence(250))
{
backgroundFill.ResizeHeightTo(1, remainingTime(), Easing.InOutQuart);
backgroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart);
using (BeginDelayedSequence(50, true))
using (BeginDelayedSequence(50))
{
foregroundFill.ResizeWidthTo(1, remainingTime(), Easing.InOutQuart);
foregroundFill.RotateTo(-90, remainingTime(), Easing.InOutQuart);
@ -239,19 +239,19 @@ namespace osu.Game.Screens.Menu
purpleCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart);
purpleCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart);
using (BeginDelayedSequence(appear_delay, true))
using (BeginDelayedSequence(appear_delay))
{
yellowCircle.MoveToY(-circle_size / 2, remainingTime(), Easing.InOutQuart);
yellowCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart);
yellowCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart);
using (BeginDelayedSequence(appear_delay, true))
using (BeginDelayedSequence(appear_delay))
{
blueCircle.MoveToX(-circle_size / 2, remainingTime(), Easing.InOutQuart);
blueCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart);
blueCircle.ResizeTo(circle_size, remainingTime(), Easing.InOutQuart);
using (BeginDelayedSequence(appear_delay, true))
using (BeginDelayedSequence(appear_delay))
{
pinkCircle.MoveToX(circle_size / 2, remainingTime(), Easing.InOutQuart);
pinkCircle.Delay(rotation_delay).RotateTo(-180, remainingTime() - rotation_delay, Easing.InOutQuart);

View File

@ -172,27 +172,27 @@ namespace osu.Game.Screens.Menu
lazerLogo.Hide();
background.ApplyToBackground(b => b.Hide());
using (BeginAbsoluteSequence(0, true))
using (BeginAbsoluteSequence(0))
{
using (BeginDelayedSequence(text_1, true))
using (BeginDelayedSequence(text_1))
welcomeText.FadeIn().OnComplete(t => t.Text = "wel");
using (BeginDelayedSequence(text_2, true))
using (BeginDelayedSequence(text_2))
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome");
using (BeginDelayedSequence(text_3, true))
using (BeginDelayedSequence(text_3))
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to");
using (BeginDelayedSequence(text_4, true))
using (BeginDelayedSequence(text_4))
{
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!");
welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000);
}
using (BeginDelayedSequence(text_glitch, true))
using (BeginDelayedSequence(text_glitch))
triangles.FadeIn();
using (BeginDelayedSequence(rulesets_1, true))
using (BeginDelayedSequence(rulesets_1))
{
rulesetsScale.ScaleTo(0.8f, 1000);
rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
@ -200,18 +200,18 @@ namespace osu.Game.Screens.Menu
triangles.FadeOut();
}
using (BeginDelayedSequence(rulesets_2, true))
using (BeginDelayedSequence(rulesets_2))
{
rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0));
}
using (BeginDelayedSequence(rulesets_3, true))
using (BeginDelayedSequence(rulesets_3))
{
rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0));
rulesetsScale.ScaleTo(1.3f, 1000);
}
using (BeginDelayedSequence(logo_1, true))
using (BeginDelayedSequence(logo_1))
{
rulesets.FadeOut();
@ -223,7 +223,7 @@ namespace osu.Game.Screens.Menu
logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad);
}
using (BeginDelayedSequence(logo_2, true))
using (BeginDelayedSequence(logo_2))
{
lazerLogo.FadeOut().OnComplete(_ =>
{

View File

@ -143,7 +143,7 @@ namespace osu.Game.Screens.Menu
{
base.LoadComplete();
using (BeginDelayedSequence(0, true))
using (BeginDelayedSequence(0))
{
scaleContainer.ScaleTo(0.9f).ScaleTo(1, delay_step_two).OnComplete(_ => Expire());
scaleContainer.FadeInFromZero(1800);

View File

@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{
new Dimension(GridSizeMode.Distributed),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]

View File

@ -125,9 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
const float padding = 44; // enough margin to avoid the hit error display.
leaderboard.Position = new Vector2(
padding,
padding + HUDOverlay.TopScoringElementsHeight);
leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>

Some files were not shown because too many files have changed in this diff Show More