1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 06:57:39 +08:00

Merge branch 'master' into catch-editor-flip

This commit is contained in:
Dean Herbert 2021-07-21 19:54:39 +09:00 committed by GitHub
commit 73866c2837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 590 additions and 359 deletions

View File

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Dual client test" type="CompoundRunConfigurationType">
<toRun name="osu!" type="DotNetProject" />
<toRun name="osu! (Second Client)" type="DotNetProject" />
<method v="2" />
</configuration>
</component>

View File

@ -0,0 +1,20 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (Second Client)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--debug-client-id=1" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net5.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net5.0" />
<method v="2">
<option name="Build" />
</method>
</configuration>
</component>

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.714.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.721.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

@ -3,7 +3,6 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework;
@ -17,13 +16,43 @@ namespace osu.Desktop
{
public static class Program
{
private const string base_game_name = @"osu";
[STAThread]
public static int Main(string[] args)
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
string gameName = base_game_name;
bool tournamentClient = false;
foreach (var arg in args)
{
var split = arg.Split('=');
var key = split[0];
var val = split[1];
switch (key)
{
case "--tournament":
tournamentClient = true;
break;
case "--debug-client-id":
if (!DebugUtils.IsDebugBuild)
throw new InvalidOperationException("Cannot use this argument in a non-debug build.");
if (!int.TryParse(val, out int clientID))
throw new ArgumentException("Provided client ID must be an integer.");
gameName = $"{base_game_name}-{clientID}";
break;
}
}
using (DesktopGameHost host = Host.GetSuitableHost(gameName, true))
{
host.ExceptionThrown += handleException;
@ -48,16 +77,10 @@ namespace osu.Desktop
return 0;
}
switch (args.FirstOrDefault() ?? string.Empty)
{
default:
host.Run(new OsuGameDesktop(args));
break;
case "--tournament":
host.Run(new TournamentGame());
break;
}
if (tournamentClient)
host.Run(new TournamentGame());
else
host.Run(new OsuGameDesktop(args));
return 0;
}

View File

@ -3,7 +3,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -24,16 +23,12 @@ 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 });
}
@ -46,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests
var skin = new TestSkin { FlipCatcherPlate = flip };
container.Child = new SkinProvidingContainer(skin)
{
Child = catcher = new Catcher(new Container())
Child = catcher = new Catcher(new Container(), new DroppedObjectContainer())
{
Anchor = Anchor.Centre
}

View File

@ -31,10 +31,12 @@ namespace osu.Game.Rulesets.Catch.Tests
[Resolved]
private OsuConfigManager config { get; set; }
private TestCatcher catcher;
private Container trailContainer;
private DroppedObjectContainer droppedObjectContainer;
private TestCatcher catcher;
[SetUp]
public void SetUp() => Schedule(() =>
{
@ -43,24 +45,18 @@ namespace osu.Game.Rulesets.Catch.Tests
CircleSize = 0,
};
var trailContainer = new Container
trailContainer = new Container();
droppedObjectContainer = new DroppedObjectContainer();
Child = new Container
{
Anchor = Anchor.Centre,
};
droppedObjectContainer = new DroppedObjectContainer();
Child = new DependencyProvidingContainer
{
CachedDependencies = new (Type, object)[]
{
(typeof(DroppedObjectContainer), droppedObjectContainer),
},
Children = new Drawable[]
{
droppedObjectContainer,
catcher = new TestCatcher(trailContainer, difficulty),
trailContainer
},
Anchor = Anchor.Centre
catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty),
trailContainer,
}
};
});
@ -298,8 +294,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public IEnumerable<CaughtObject> CaughtObjects => this.ChildrenOfType<CaughtObject>();
public TestCatcher(Container trailsTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, difficulty)
public TestCatcher(Container trailsTarget, DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
: base(trailsTarget, droppedObjectTarget, difficulty)
{
}
}

View File

@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
area.OnNewResult(drawable, new CatchJudgementResult(fruit, new CatchJudgement())
{
Type = area.MovableCatcher.CanCatch(fruit) ? HitResult.Great : HitResult.Miss
Type = area.Catcher.CanCatch(fruit) ? HitResult.Great : HitResult.Miss
});
drawable.Expire();
@ -119,16 +119,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestCatcherArea : CatcherArea
{
[Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
: base(beatmapDifficulty)
{
AddInternal(droppedObjectContainer = new DroppedObjectContainer());
var droppedObjectContainer = new DroppedObjectContainer();
Add(droppedObjectContainer);
Catcher = new Catcher(this, droppedObjectContainer, beatmapDifficulty)
{
X = CatchPlayfield.CENTER_X
};
}
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
public void ToggleHyperDash(bool status) => Catcher.SetHyperDashState(status ? 2 : 1);
}
}
}

View File

@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private bool playfieldIsEmpty => !((CatchPlayfield)drawableRuleset.Playfield).AllHitObjects.Any(h => h.IsAlive);
private CatcherAnimationState catcherState => ((CatchPlayfield)drawableRuleset.Playfield).CatcherArea.MovableCatcher.CurrentState;
private CatcherAnimationState catcherState => ((CatchPlayfield)drawableRuleset.Playfield).Catcher.CurrentState;
private void spawnFruits(bool hit = false)
{
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Catch.Tests
float xCoords = CatchPlayfield.CENTER_X;
if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
catchPlayfield.Catcher.X = xCoords - x_offset;
if (hit)
xCoords -= x_offset;

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Tests
// this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
{
var catcher = Player.ChildrenOfType<CatcherArea>().FirstOrDefault()?.MovableCatcher;
var catcher = Player.ChildrenOfType<Catcher>().FirstOrDefault();
if (catcher == null)
return;

View File

@ -113,36 +113,45 @@ namespace osu.Game.Rulesets.Catch.Tests
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
{
CatcherArea catcherArea = null;
Container trailsContainer = null;
Catcher catcher = null;
CatcherTrailDisplay trails = null;
AddStep("create hyper-dashing catcher", () =>
{
Child = setupSkinHierarchy(catcherArea = new TestCatcherArea
trailsContainer = new Container();
Child = setupSkinHierarchy(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
Children = new Drawable[]
{
catcher = new Catcher(trailsContainer, new DroppedObjectContainer())
{
Scale = new Vector2(4)
},
trailsContainer
}
}, skin);
});
AddStep("get trails container", () =>
{
trails = catcherArea.OfType<CatcherTrailDisplay>().Single();
catcherArea.MovableCatcher.SetHyperDashState(2);
trails = trailsContainer.OfType<CatcherTrailDisplay>().Single();
catcher.SetHyperDashState(2);
});
AddUntilStep("catcher colour is correct", () => catcherArea.MovableCatcher.Colour == expectedCatcherColour);
AddUntilStep("catcher colour is correct", () => catcher.Colour == expectedCatcherColour);
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
AddStep("finish hyper-dashing", () =>
{
catcherArea.MovableCatcher.SetHyperDashState();
catcherArea.MovableCatcher.FinishTransforms();
catcher.SetHyperDashState();
catcher.FinishTransforms();
});
AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
AddAssert("catcher colour returned to white", () => catcher.Colour == Color4.White);
}
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
@ -205,18 +214,5 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
}
private class TestCatcherArea : CatcherArea
{
[Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
public TestCatcherArea()
{
Scale = new Vector2(4f);
AddInternal(droppedObjectContainer = new DroppedObjectContainer());
}
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Edit
base.LoadComplete();
// TODO: honor "hit animation" setting?
CatcherArea.MovableCatcher.CatchFruitOnPlate = false;
Catcher.CatchFruitOnPlate = false;
// TODO: disable hit lighting as well
}

View File

@ -41,9 +41,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
base.Update();
var catcherArea = playfield.CatcherArea;
FlashlightPosition = catcherArea.ToSpaceOfOtherDrawable(catcherArea.MovableCatcher.DrawPosition, this);
FlashlightPosition = playfield.CatcherArea.ToSpaceOfOtherDrawable(playfield.Catcher.DrawPosition, this);
}
private float getSizeFor(int combo)

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Mods
var drawableCatchRuleset = (DrawableCatchRuleset)drawableRuleset;
var catchPlayfield = (CatchPlayfield)drawableCatchRuleset.Playfield;
catchPlayfield.CatcherArea.MovableCatcher.CatchFruitOnPlate = false;
catchPlayfield.Catcher.CatchFruitOnPlate = false;
}
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)

View File

@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Replays
bool impossibleJump = speedRequired > movement_speed * 2;
// todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f;
const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f;
if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX)
{

View File

@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@ -26,38 +27,53 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
public const float CENTER_X = WIDTH / 2;
[Cached]
private readonly DroppedObjectContainer droppedObjectContainer;
internal readonly CatcherArea CatcherArea;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
// only check the X position; handle all vertical space.
base.ReceivePositionalInputAt(new Vector2(screenSpacePos.X, ScreenSpaceDrawQuad.Centre.Y));
internal Catcher Catcher { get; private set; }
internal CatcherArea CatcherArea { get; private set; }
private readonly BeatmapDifficulty difficulty;
public CatchPlayfield(BeatmapDifficulty difficulty)
{
CatcherArea = new CatcherArea(difficulty)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
};
InternalChildren = new[]
{
droppedObjectContainer = new DroppedObjectContainer(),
CatcherArea.MovableCatcher.CreateProxiedContent(),
HitObjectContainer.CreateProxy(),
// This ordering (`CatcherArea` before `HitObjectContainer`) is important to
// make sure the up-to-date catcher position is used for the catcher catching logic of hit objects.
CatcherArea,
HitObjectContainer,
};
this.difficulty = difficulty;
}
[BackgroundDependencyLoader]
private void load()
{
var trailContainer = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft
};
var droppedObjectContainer = new DroppedObjectContainer();
Catcher = new Catcher(trailContainer, droppedObjectContainer, difficulty)
{
X = CENTER_X
};
AddRangeInternal(new[]
{
droppedObjectContainer,
Catcher.CreateProxiedContent(),
HitObjectContainer.CreateProxy(),
// This ordering (`CatcherArea` before `HitObjectContainer`) is important to
// make sure the up-to-date catcher position is used for the catcher catching logic of hit objects.
CatcherArea = new CatcherArea
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Catcher = Catcher,
},
trailContainer,
HitObjectContainer,
});
RegisterPool<Droplet, DrawableDroplet>(50);
RegisterPool<TinyDroplet, DrawableTinyDroplet>(50);
RegisterPool<Fruit, DrawableFruit>(100);
@ -80,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.UI
((DrawableCatchHitObject)d).CheckPosition = checkIfWeCanCatch;
}
private bool checkIfWeCanCatch(CatchHitObject obj) => CatcherArea.MovableCatcher.CanCatch(obj);
private bool checkIfWeCanCatch(CatchHitObject obj) => Catcher.CanCatch(obj);
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);

View File

@ -21,6 +21,6 @@ namespace osu.Game.Rulesets.Catch.UI
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<CatchAction> actions, ReplayFrame previousFrame)
=> new CatchReplayFrame(Time.Current, playfield.CatcherArea.MovableCatcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame);
=> new CatchReplayFrame(Time.Current, playfield.Catcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame);
}
}

View File

@ -25,6 +25,16 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class Catcher : SkinReloadableDrawable
{
/// <summary>
/// The size of the catcher at 1x scale.
/// </summary>
public const float BASE_SIZE = 106.75f;
/// <summary>
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
/// </summary>
public const float ALLOWED_CATCH_RANGE = 0.8f;
/// <summary>
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
/// and end glow/after-image during a hyper-dash.
@ -74,8 +84,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// <summary>
/// Contains objects dropped from the plate.
/// </summary>
[Resolved]
private DroppedObjectContainer droppedObjectTarget { get; set; }
private readonly DroppedObjectContainer droppedObjectTarget;
public CatcherAnimationState CurrentState
{
@ -83,11 +92,6 @@ namespace osu.Game.Rulesets.Catch.UI
private set => Body.AnimationState.Value = value;
}
/// <summary>
/// The width of the catcher which can receive fruit. Equivalent to "catchMargin" in osu-stable.
/// </summary>
public const float ALLOWED_CATCH_RANGE = 0.8f;
private bool dashing;
public bool Dashing
@ -134,13 +138,14 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool<CaughtBanana> caughtBananaPool;
private readonly DrawablePool<CaughtDroplet> caughtDropletPool;
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
public Catcher([NotNull] Container trailsTarget, [NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
{
this.trailsTarget = trailsTarget;
this.droppedObjectTarget = droppedObjectTarget;
Origin = Anchor.TopCentre;
Size = new Vector2(CatcherArea.CATCHER_SIZE);
Size = new Vector2(BASE_SIZE);
if (difficulty != null)
Scale = calculateScale(difficulty);
@ -197,7 +202,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Calculates the width of the area used for attempting catches in gameplay.
/// </summary>
/// <param name="scale">The scale of the catcher.</param>
public static float CalculateCatchWidth(Vector2 scale) => CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
public static float CalculateCatchWidth(Vector2 scale) => BASE_SIZE * Math.Abs(scale.X) * ALLOWED_CATCH_RANGE;
/// <summary>
/// Calculates the width of the area used for attempting catches in gameplay.

View File

@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Replays;
@ -16,13 +15,29 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
/// <summary>
/// The horizontal band at the bottom of the playfield the catcher is moving on.
/// It holds a <see cref="Catcher"/> as a child and translates input to the catcher movement.
/// It also holds a combo display that is above the catcher, and judgment results are translated to the catcher and the combo display.
/// </summary>
public class CatcherArea : Container, IKeyBindingHandler<CatchAction>
{
public const float CATCHER_SIZE = 106.75f;
public Catcher Catcher
{
get => catcher;
set
{
if (catcher != null)
Remove(catcher);
Add(catcher = value);
}
}
public readonly Catcher MovableCatcher;
private readonly CatchComboDisplay comboDisplay;
private Catcher catcher;
/// <summary>
/// <c>-1</c> when only left button is pressed.
/// <c>1</c> when only right button is pressed.
@ -30,27 +45,26 @@ namespace osu.Game.Rulesets.Catch.UI
/// </summary>
private int currentDirection;
public CatcherArea(BeatmapDifficulty difficulty = null)
/// <remarks>
/// <see cref="Catcher"/> must be set before loading.
/// </remarks>
public CatcherArea()
{
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
Children = new Drawable[]
Size = new Vector2(CatchPlayfield.WIDTH, Catcher.BASE_SIZE);
Child = comboDisplay = new CatchComboDisplay
{
comboDisplay = new CatchComboDisplay
{
RelativeSizeAxes = Axes.None,
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopLeft,
Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X
},
MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X },
RelativeSizeAxes = Axes.None,
AutoSizeAxes = Axes.Both,
Anchor = Anchor.TopLeft,
Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 350f },
X = CatchPlayfield.CENTER_X
};
}
public void OnNewResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
MovableCatcher.OnNewResult(hitObject, result);
Catcher.OnNewResult(hitObject, result);
if (!result.Type.IsScorable())
return;
@ -58,9 +72,9 @@ namespace osu.Game.Rulesets.Catch.UI
if (hitObject.HitObject.LastInCombo)
{
if (result.Judgement is CatchJudgement catchJudgement && catchJudgement.ShouldExplodeFor(result))
MovableCatcher.Explode();
Catcher.Explode();
else
MovableCatcher.Drop();
Catcher.Drop();
}
comboDisplay.OnNewResult(hitObject, result);
@ -69,7 +83,7 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
{
comboDisplay.OnRevertResult(hitObject, result);
MovableCatcher.OnRevertResult(hitObject, result);
Catcher.OnRevertResult(hitObject, result);
}
protected override void Update()
@ -80,27 +94,27 @@ namespace osu.Game.Rulesets.Catch.UI
SetCatcherPosition(
replayState?.CatcherX ??
(float)(MovableCatcher.X + MovableCatcher.Speed * currentDirection * Clock.ElapsedFrameTime));
(float)(Catcher.X + Catcher.Speed * currentDirection * Clock.ElapsedFrameTime));
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
comboDisplay.X = MovableCatcher.X;
comboDisplay.X = Catcher.X;
}
public void SetCatcherPosition(float X)
{
float lastPosition = MovableCatcher.X;
float lastPosition = Catcher.X;
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
MovableCatcher.X = newPosition;
Catcher.X = newPosition;
if (lastPosition < newPosition)
MovableCatcher.VisualDirection = Direction.Right;
Catcher.VisualDirection = Direction.Right;
else if (lastPosition > newPosition)
MovableCatcher.VisualDirection = Direction.Left;
Catcher.VisualDirection = Direction.Left;
}
public bool OnPressed(CatchAction action)
@ -116,7 +130,7 @@ namespace osu.Game.Rulesets.Catch.UI
return true;
case CatchAction.Dash:
MovableCatcher.Dashing = true;
Catcher.Dashing = true;
return true;
}
@ -136,7 +150,7 @@ namespace osu.Game.Rulesets.Catch.UI
break;
case CatchAction.Dash:
MovableCatcher.Dashing = false;
Catcher.Dashing = false;
break;
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
public CatcherTrail()
{
Size = new Vector2(CatcherArea.CATCHER_SIZE);
Size = new Vector2(Catcher.BASE_SIZE);
Origin = Anchor.TopCentre;
Blending = BlendingParameters.Additive;
InternalChild = body = new SkinnableCatcher

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
Anchor = Anchor.TopCentre;
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(0.5f, 0.06f) * CatcherArea.CATCHER_SIZE;
OriginPosition = new Vector2(0.5f, 0.06f) * Catcher.BASE_SIZE;
}
}
}

View File

@ -129,9 +129,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public bool OnPressed(PlatformAction action)
{
switch (action.ActionMethod)
switch (action)
{
case PlatformActionMethod.Delete:
case PlatformAction.Delete:
return DeleteSelected();
}

View File

@ -330,15 +330,15 @@ namespace osu.Game.Tests.Visual.Online
InputManager.ReleaseKey(Key.AltLeft);
}
private void pressCloseDocumentKeys() => pressKeysFor(PlatformActionType.DocumentClose);
private void pressCloseDocumentKeys() => pressKeysFor(PlatformAction.DocumentClose);
private void pressNewTabKeys() => pressKeysFor(PlatformActionType.TabNew);
private void pressNewTabKeys() => pressKeysFor(PlatformAction.TabNew);
private void pressRestoreTabKeys() => pressKeysFor(PlatformActionType.TabRestore);
private void pressRestoreTabKeys() => pressKeysFor(PlatformAction.TabRestore);
private void pressKeysFor(PlatformActionType type)
private void pressKeysFor(PlatformAction type)
{
var binding = host.PlatformKeyBindings.First(b => ((PlatformAction)b.Action).ActionType == type);
var binding = host.PlatformKeyBindings.First(b => (PlatformAction)b.Action == type);
foreach (var k in binding.KeyCombination.Keys)
InputManager.PressKey((Key)k);

View File

@ -29,6 +29,12 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo)));
}
[Test]
public void TestExcessMods()
{
AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo, true)));
}
private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score)
{
Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score);

View File

@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
using osu.Game.Screens.Ranking;
@ -36,6 +37,17 @@ namespace osu.Game.Tests.Visual.Ranking
{
Beatmap = createTestBeatmap(author)
}));
}
[Test]
public void TestExcessMods()
{
var author = new User { Username = "mapper_name" };
AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true)
{
Beatmap = createTestBeatmap(author)
}));
AddAssert("mapper name present", () => this.ChildrenOfType<OsuSpriteText>().Any(spriteText => spriteText.Current.Value == "mapper_name"));
}
@ -50,9 +62,33 @@ namespace osu.Game.Tests.Visual.Ranking
AddAssert("mapped by text not present", () =>
this.ChildrenOfType<OsuSpriteText>().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by")));
AddAssert("play time displayed", () => this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
}
private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score);
[Test]
public void TestWithDefaultDate()
{
AddStep("show autoplay score", () =>
{
var ruleset = new OsuRuleset();
var mods = new Mod[] { ruleset.GetAutoplayMod() };
var beatmap = createTestBeatmap(null);
showPanel(new TestScoreInfo(ruleset.RulesetInfo)
{
Mods = mods,
Beatmap = beatmap,
Date = default,
});
});
AddAssert("play time not displayed", () => !this.ChildrenOfType<ExpandedPanelMiddleContent.PlayedOnText>().Any());
}
private void showPanel(ScoreInfo score) =>
Child = new ExpandedPanelMiddleContentContainer(score);
private BeatmapInfo createTestBeatmap(User author)
{

View File

@ -11,10 +11,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModDisplay : OsuTestScene
{
[TestCase(ExpansionMode.ExpandOnHover)]
[TestCase(ExpansionMode.AlwaysExpanded)]
[TestCase(ExpansionMode.AlwaysContracted)]
public void TestMode(ExpansionMode mode)
[Test]
public void TestMode([Values] ExpansionMode mode)
{
AddStep("create mod display", () =>
{

View File

@ -0,0 +1,49 @@
// 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.Graphics;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play.HUD;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneModFlowDisplay : OsuTestScene
{
private ModFlowDisplay modFlow;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = modFlow = new ModFlowDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.None,
Width = 200,
Current =
{
Value = new OsuRuleset().GetAllMods().ToArray(),
}
};
});
[Test]
public void TestWrapping()
{
AddSliderStep("icon size", 0.1f, 2, 1, val =>
{
if (modFlow != null)
modFlow.IconScale = val;
});
AddSliderStep("flow width", 100, 500, 200, val =>
{
if (modFlow != null)
modFlow.Width = val;
});
}
}
}

View File

@ -416,7 +416,6 @@ namespace osu.Game.Tests.Visual.UserInterface
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Position = new Vector2(-5, 25),
Current = { BindTarget = modSelect.SelectedMods }
}

View File

@ -32,20 +32,15 @@ namespace osu.Game.Graphics.UserInterface
public override bool OnPressed(PlatformAction action)
{
switch (action.ActionType)
switch (action)
{
case PlatformActionType.LineEnd:
case PlatformActionType.LineStart:
return false;
case PlatformAction.MoveBackwardLine:
case PlatformAction.MoveForwardLine:
// Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
// as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text)
// Avoid handling it here to allow other components to potentially consume the shortcut.
case PlatformActionType.CharNext:
if (action.ActionMethod == PlatformActionMethod.Delete)
return false;
break;
case PlatformAction.DeleteForwardChar:
return false;
}
return base.OnPressed(action);

View File

@ -374,17 +374,17 @@ namespace osu.Game.Overlays
public bool OnPressed(PlatformAction action)
{
switch (action.ActionType)
switch (action)
{
case PlatformActionType.TabNew:
case PlatformAction.TabNew:
ChannelTabControl.SelectChannelSelectorTab();
return true;
case PlatformActionType.TabRestore:
case PlatformAction.TabRestore:
channelManager.JoinLastClosedChannel();
return true;
case PlatformActionType.DocumentClose:
case PlatformAction.DocumentClose:
channelManager.LeaveChannel(channelManager.CurrentChannel.Value);
return true;
}

View File

@ -230,9 +230,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
public bool OnPressed(PlatformAction action)
{
switch (action.ActionType)
switch (action)
{
case PlatformActionType.SelectAll:
case PlatformAction.SelectAll:
SelectAll();
return true;
}

View File

@ -139,9 +139,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
public bool OnPressed(PlatformAction action)
{
switch (action.ActionMethod)
switch (action)
{
case PlatformActionMethod.Delete:
case PlatformAction.Delete:
DeleteSelected();
return true;
}

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose
public bool OnPressed(PlatformAction action)
{
if (action.ActionType == PlatformActionType.Copy)
if (action == PlatformAction.Copy)
host.GetClipboard().SetText(formatSelectionAsString());
return false;

View File

@ -330,29 +330,29 @@ namespace osu.Game.Screens.Edit
public bool OnPressed(PlatformAction action)
{
switch (action.ActionType)
switch (action)
{
case PlatformActionType.Cut:
case PlatformAction.Cut:
Cut();
return true;
case PlatformActionType.Copy:
case PlatformAction.Copy:
Copy();
return true;
case PlatformActionType.Paste:
case PlatformAction.Paste:
Paste();
return true;
case PlatformActionType.Undo:
case PlatformAction.Undo:
Undo();
return true;
case PlatformActionType.Redo:
case PlatformAction.Redo:
Redo();
return true;
case PlatformActionType.Save:
case PlatformAction.Save:
Save();
return true;
}

View File

@ -172,7 +172,6 @@ namespace osu.Game.Screens.Play
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 20 },
Current = mods
},

View File

@ -15,24 +15,26 @@ using osuTK;
namespace osu.Game.Screens.Play.HUD
{
public class ModDisplay : Container, IHasCurrentValue<IReadOnlyList<Mod>>
/// <summary>
/// Displays a single-line horizontal auto-sized flow of mods. For cases where wrapping is required, use <see cref="ModFlowDisplay"/> instead.
/// </summary>
public class ModDisplay : CompositeDrawable, IHasCurrentValue<IReadOnlyList<Mod>>
{
private const int fade_duration = 1000;
public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover;
private readonly Bindable<IReadOnlyList<Mod>> current = new Bindable<IReadOnlyList<Mod>>();
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>();
public Bindable<IReadOnlyList<Mod>> Current
{
get => current;
get => current.Current;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
current.UnbindBindings();
current.BindTo(value);
current.Current = value;
}
}
@ -42,51 +44,34 @@ namespace osu.Game.Screens.Play.HUD
{
AutoSizeAxes = Axes.Both;
Child = new FillFlowContainer
InternalChild = iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
iconsContainer = new ReverseChildIDFillFlowContainer<ModIcon>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
},
},
Direction = FillDirection.Horizontal,
};
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Current.UnbindAll();
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(mods =>
{
iconsContainer.Clear();
if (mods.NewValue != null)
{
foreach (Mod mod in mods.NewValue)
iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
appearTransform();
}
}, true);
Current.BindValueChanged(updateDisplay, true);
iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint);
}
private void updateDisplay(ValueChangedEvent<IReadOnlyList<Mod>> mods)
{
iconsContainer.Clear();
if (mods.NewValue == null) return;
foreach (Mod mod in mods.NewValue)
iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) });
appearTransform();
}
private void appearTransform()
{
expand();

View File

@ -0,0 +1,83 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// A horizontally wrapping display of mods. For cases where wrapping is not required, use <see cref="ModDisplay"/> instead.
/// </summary>
public class ModFlowDisplay : ReverseChildIDFillFlowContainer<ModIcon>, IHasCurrentValue<IReadOnlyList<Mod>>
{
private const int fade_duration = 1000;
private readonly BindableWithCurrent<IReadOnlyList<Mod>> current = new BindableWithCurrent<IReadOnlyList<Mod>>();
public Bindable<IReadOnlyList<Mod>> Current
{
get => current.Current;
set
{
if (value == null)
throw new ArgumentNullException(nameof(value));
current.Current = value;
}
}
private float iconScale = 1;
public float IconScale
{
get => iconScale;
set
{
iconScale = value;
updateDisplay();
}
}
public ModFlowDisplay()
{
Direction = FillDirection.Full;
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateDisplay(), true);
this.FadeInFromZero(fade_duration, Easing.OutQuint);
}
private void updateDisplay()
{
Clear();
if (current.Value == null) return;
Spacing = new Vector2(0, -12 * iconScale);
foreach (Mod mod in current.Value)
{
Add(new ModIcon(mod)
{
Scale = new Vector2(0.6f * iconScale),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
});
}
}
}
}

View File

@ -282,7 +282,6 @@ namespace osu.Game.Screens.Play
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
};
protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay();

View File

@ -131,14 +131,14 @@ namespace osu.Game.Screens.Ranking.Contracted
createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"),
}
},
new ModDisplay
new ModFlowDisplay
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
ExpansionMode = ExpansionMode.AlwaysExpanded,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Current = { Value = score.Mods },
Scale = new Vector2(0.5f),
IconScale = 0.5f,
}
}
}

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;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
@ -79,162 +80,155 @@ namespace osu.Game.Screens.Ranking.Expanded
var starDifficulty = beatmapDifficultyCache.GetDifficultyAsync(beatmap, score.Ruleset, score.Mods).Result;
InternalChildren = new Drawable[]
AddInternal(new FillFlowContainer
{
new FillFlowContainer
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(20),
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(20),
Children = new Drawable[]
new FillFlowContainer
{
new FillFlowContainer
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
new OsuSpriteText
{
new OsuSpriteText
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 40 },
RelativeSizeAxes = Axes.X,
Height = 230,
Child = new AccuracyCircle(score, withFlair)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new RomanisableString(metadata.TitleUnicode, metadata.Title),
Font = OsuFont.Torus.With(size: 20, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,
},
new OsuSpriteText
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
}
},
scoreCounter = new TotalScoreCounter
{
Margin = new MarginPadding { Top = 0, Bottom = 5 },
Current = { Value = 0 },
Alpha = 0,
AlwaysPresent = true
},
starAndModDisplay = new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist),
Font = OsuFont.Torus.With(size: 14, weight: FontWeight.SemiBold),
MaxWidth = ScorePanel.EXPANDED_WIDTH - padding * 2,
Truncate = true,
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Top = 40 },
RelativeSizeAxes = Axes.X,
Height = 230,
Child = new AccuracyCircle(score, withFlair)
new StarRatingDisplay(starDifficulty)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
}
},
scoreCounter = new TotalScoreCounter
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
}
},
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
Margin = new MarginPadding { Top = 0, Bottom = 5 },
Current = { Value = 0 },
Alpha = 0,
AlwaysPresent = true
},
starAndModDisplay = new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
new OsuSpriteText
{
new StarRatingDisplay(starDifficulty)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
}
},
new FillFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = beatmap.Version,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
},
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
{
new OsuSpriteText
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}.With(t =>
{
if (!string.IsNullOrEmpty(creator))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = beatmap.Version,
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
},
new OsuTextFlowContainer(s => s.Font = OsuFont.Torus.With(size: 12))
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}.With(t =>
{
if (!string.IsNullOrEmpty(creator))
{
t.AddText("mapped by ");
t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
}
})
}
},
}
},
new FillFlowContainer
t.AddText("mapped by ");
t.AddText(creator, s => s.Font = s.Font.With(weight: FontWeight.SemiBold));
}
})
}
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[]
new GridContainer
{
new GridContainer
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { topStatistics.Cast<Drawable>().ToArray() },
RowDimensions = new[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { topStatistics.Cast<Drawable>().ToArray() },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
},
new GridContainer
new Dimension(GridSizeMode.AutoSize),
}
},
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() },
RowDimensions = new[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { bottomStatistics.Where(s => s.Result <= HitResult.Perfect).ToArray() },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
},
new GridContainer
new Dimension(GridSizeMode.AutoSize),
}
},
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() },
RowDimensions = new[]
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[] { bottomStatistics.Where(s => s.Result > HitResult.Perfect).ToArray() },
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
new Dimension(GridSizeMode.AutoSize),
}
}
}
}
},
new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"
}
};
});
if (score.Date != default)
AddInternal(new PlayedOnText(score.Date));
if (score.Mods.Any())
{
@ -276,5 +270,16 @@ namespace osu.Game.Screens.Ranking.Expanded
FinishTransforms(true);
});
}
public class PlayedOnText : OsuSpriteText
{
public PlayedOnText(DateTimeOffset time)
{
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold);
Text = $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}";
}
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -13,7 +14,7 @@ namespace osu.Game.Tests
{
public class TestScoreInfo : ScoreInfo
{
public TestScoreInfo(RulesetInfo ruleset)
public TestScoreInfo(RulesetInfo ruleset, bool excessMods = false)
{
User = new User
{
@ -25,7 +26,10 @@ namespace osu.Game.Tests
Beatmap = new TestBeatmap(ruleset).BeatmapInfo;
Ruleset = ruleset;
RulesetID = ruleset.ID ?? 0;
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
Mods = excessMods
? ruleset.CreateInstance().GetAllMods().ToArray()
: new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
TotalScore = 2845370;
Accuracy = 0.95;

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.3.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.714.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.721.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
<PackageReference Include="Sentry" Version="3.6.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />

View File

@ -70,7 +70,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.714.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.721.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.706.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -93,7 +93,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.714.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.721.0" />
<PackageReference Include="SharpCompress" Version="0.28.3" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -19,8 +19,8 @@
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignedValueIsNeverUsed/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignmentIsFullyDiscarded/@EntryIndexedValue">DO_NOT_SHOW</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AssignNullToNotNullAttribute/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002ELocal/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002EGlobal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=AutoPropertyCanBeMadeGetOnly_002ELocal/@EntryIndexedValue">HINT</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BadAttributeBracketsSpaces/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BadBracesSpaces/@EntryIndexedValue">WARNING</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=BadChildStatementIndent/@EntryIndexedValue">WARNING</s:String>