mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 02:32:55 +08:00
Merge branch 'master' into remove-hud-component-lookup
This commit is contained in:
commit
02d18c7a49
@ -31,6 +31,12 @@
|
|||||||
"commands": [
|
"commands": [
|
||||||
"CodeFileSanity"
|
"CodeFileSanity"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"ppy.localisationanalyser.tools": {
|
||||||
|
"version": "2021.524.0",
|
||||||
|
"commands": [
|
||||||
|
"localisation"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature Request
|
|
||||||
about: Propose a feature you would like to see in the game!
|
|
||||||
---
|
|
||||||
**Describe the new feature:**
|
|
||||||
|
|
||||||
**Proposal designs of the feature:**
|
|
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,5 +1,12 @@
|
|||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
|
- name: Suggestions or feature request
|
||||||
|
url: https://github.com/ppy/osu/discussions/categories/ideas
|
||||||
|
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||||
|
- name: Help
|
||||||
|
url: https://github.com/ppy/osu/discussions/categories/q-a
|
||||||
|
about: osu! not working as you'd expect? Not sure it's a bug? Check the Q&A section!
|
||||||
- name: osu!stable issues
|
- name: osu!stable issues
|
||||||
url: https://github.com/ppy/osu-stable-issues
|
url: https://github.com/ppy/osu-stable-issues
|
||||||
about: For issues regarding osu!stable (not osu!lazer), open them here.
|
about: For osu!stable bugs (not osu!lazer), check out the dedicated repository. Note that we only accept serious bug reports.
|
||||||
|
|
||||||
|
@ -52,6 +52,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.422.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.513.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.528.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -161,13 +161,13 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
switch (result)
|
switch (result)
|
||||||
{
|
{
|
||||||
case HitResult.LargeTickHit:
|
case HitResult.LargeTickHit:
|
||||||
return "large droplet";
|
return "Large droplet";
|
||||||
|
|
||||||
case HitResult.SmallTickHit:
|
case HitResult.SmallTickHit:
|
||||||
return "small droplet";
|
return "Small droplet";
|
||||||
|
|
||||||
case HitResult.LargeBonus:
|
case HitResult.LargeBonus:
|
||||||
return "banana";
|
return "Banana";
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDisplayNameForHitResult(result);
|
return base.GetDisplayNameForHitResult(result);
|
||||||
|
@ -22,28 +22,27 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
if (component is CatchSkinComponent catchSkinComponent)
|
||||||
return null;
|
{
|
||||||
|
|
||||||
switch (catchSkinComponent.Component)
|
switch (catchSkinComponent.Component)
|
||||||
{
|
{
|
||||||
case CatchSkinComponents.Fruit:
|
case CatchSkinComponents.Fruit:
|
||||||
if (GetTexture("fruit-pear") != null)
|
if (GetTexture("fruit-pear") != null)
|
||||||
return new LegacyFruitPiece();
|
return new LegacyFruitPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.Banana:
|
case CatchSkinComponents.Banana:
|
||||||
if (GetTexture("fruit-bananas") != null)
|
if (GetTexture("fruit-bananas") != null)
|
||||||
return new LegacyBananaPiece();
|
return new LegacyBananaPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.Droplet:
|
case CatchSkinComponents.Droplet:
|
||||||
if (GetTexture("fruit-drop") != null)
|
if (GetTexture("fruit-drop") != null)
|
||||||
return new LegacyDropletPiece();
|
return new LegacyDropletPiece();
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
|
||||||
case CatchSkinComponents.CatcherIdle:
|
case CatchSkinComponents.CatcherIdle:
|
||||||
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
return this.GetAnimation("fruit-catcher-idle", true, true, true) ??
|
||||||
@ -61,10 +60,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
if (providesComboCounter)
|
if (providesComboCounter)
|
||||||
return new LegacyCatchComboCounter(Source);
|
return new LegacyCatchComboCounter(Source);
|
||||||
|
|
||||||
break;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
@ -35,7 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceBeforeCurrentTimeDownwards()
|
public void TestPlaceBeforeCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10)));
|
AddStep("move mouse before current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -45,7 +48,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceAfterCurrentTimeDownwards()
|
public void TestPlaceAfterCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single()));
|
AddStep("move mouse after current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
// 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.Framework.Graphics;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableManiaHitObject : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
private Column column;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
TimeRange = 2000,
|
||||||
|
Clock = new FramedClock(clock),
|
||||||
|
Child = column = new Column(0)
|
||||||
|
{
|
||||||
|
Action = { Value = ManiaAction.Key1 },
|
||||||
|
Height = 0.85f,
|
||||||
|
AccentColour = Color4.Gray
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteHeadVisibility()
|
||||||
|
{
|
||||||
|
DrawableHoldNote note = null;
|
||||||
|
AddStep("Add hold note", () =>
|
||||||
|
{
|
||||||
|
var h = new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Duration = 1000
|
||||||
|
};
|
||||||
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
column.Add(note = new DrawableHoldNote(h));
|
||||||
|
});
|
||||||
|
AddStep("Hold key", () =>
|
||||||
|
{
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
note.OnPressed(ManiaAction.Key1);
|
||||||
|
});
|
||||||
|
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||||
|
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -414,14 +412,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
|
||||||
AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true);
|
||||||
AddAssert("head is visible",
|
|
||||||
() => currentPlayer.ChildrenOfType<DrawableHoldNote>()
|
|
||||||
.Single(note => note.HitObject == beatmap.HitObjects[0])
|
|
||||||
.Head
|
|
||||||
.Alpha == 1);
|
|
||||||
|
|
||||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||||
availableLines.Push(line);
|
availableLines.Push(line);
|
||||||
|
|
||||||
grid.Clear(false);
|
grid.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionTimeRange == null)
|
if (selectionTimeRange == null)
|
||||||
|
@ -58,8 +58,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is ManiaHitObject maniaObj)
|
maniaPlayfield.Remove(h);
|
||||||
maniaObj.Column += columnDelta;
|
((ManiaHitObject)h).Column += columnDelta;
|
||||||
|
maniaPlayfield.Add(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The intended <see cref="Column"/> index for this component.
|
|
||||||
/// May be null if the component does not exist in a <see cref="Column"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int? TargetColumn;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The intended <see cref="StageDefinition"/> for this component.
|
/// The intended <see cref="StageDefinition"/> for this component.
|
||||||
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
||||||
@ -25,12 +19,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The component.</param>
|
/// <param name="component">The component.</param>
|
||||||
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
|
||||||
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
||||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
|
public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null)
|
||||||
: base(component)
|
: base(component)
|
||||||
{
|
{
|
||||||
TargetColumn = targetColumn;
|
|
||||||
StageDefinition = stageDefinition;
|
StageDefinition = stageDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -29,21 +31,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||||
|
|
||||||
private readonly Container<DrawableHoldNoteHead> headContainer;
|
private Container<DrawableHoldNoteHead> headContainer;
|
||||||
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
private Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
private Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container sizingContainer;
|
private Container sizingContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container maskingContainer;
|
private Container maskingContainer;
|
||||||
|
|
||||||
private readonly SkinnableDrawable bodyPiece;
|
private SkinnableDrawable bodyPiece;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||||
@ -60,11 +62,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double? releaseTime;
|
private double? releaseTime;
|
||||||
|
|
||||||
|
public DrawableHoldNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
Container maskedContents;
|
Container maskedContents;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
@ -86,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
|
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
})
|
})
|
||||||
@ -105,6 +115,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
sizingContainer.Size = Vector2.One;
|
||||||
|
HoldStartTime = null;
|
||||||
|
HoldBrokenTime = null;
|
||||||
|
releaseTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
@ -128,37 +148,23 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
{
|
{
|
||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
headContainer.Clear();
|
headContainer.Clear(false);
|
||||||
tailContainer.Clear();
|
tailContainer.Clear(false);
|
||||||
tickContainer.Clear();
|
tickContainer.Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case TailNote _:
|
case TailNote tail:
|
||||||
return new DrawableHoldNoteTail(this)
|
return new DrawableHoldNoteTail(tail);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case Note _:
|
case HeadNote head:
|
||||||
return new DrawableHoldNoteHead(this)
|
return new DrawableHoldNoteHead(head);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case HoldNoteTick tick:
|
case HoldNoteTick tick:
|
||||||
return new DrawableHoldNoteTick(tick)
|
return new DrawableHoldNoteTick(tick);
|
||||||
{
|
|
||||||
HoldStartTime = () => HoldStartTime,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -12,11 +13,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
||||||
|
|
||||||
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
|
public DrawableHoldNoteHead()
|
||||||
: base(holdNote.HitObject.Head)
|
: this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteHead(HeadNote headNote)
|
||||||
|
: base(headNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -20,12 +21,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
private readonly DrawableHoldNote holdNote;
|
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
|
public DrawableHoldNoteTail()
|
||||||
: base(holdNote.HitObject.Tail)
|
: this(null)
|
||||||
{
|
{
|
||||||
this.holdNote = holdNote;
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteTail(TailNote tailNote)
|
||||||
|
: base(tailNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
@ -54,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null))
|
if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null))
|
||||||
result = HitResult.Meh;
|
result = HitResult.Meh;
|
||||||
|
|
||||||
r.Type = result;
|
r.Type = result;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osuTK;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,22 +20,28 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// References the time at which the user started holding the hold note.
|
/// References the time at which the user started holding the hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<double?> HoldStartTime;
|
private Func<double?> holdStartTime;
|
||||||
|
|
||||||
|
private Container glowContainer;
|
||||||
|
|
||||||
|
public DrawableHoldNoteTick()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Container glowContainer;
|
|
||||||
|
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Size = new Vector2(1);
|
}
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
glowContainer = new CircularContainer
|
AddInternal(glowContainer = new CircularContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
@ -49,8 +56,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
AlwaysPresent = true
|
AlwaysPresent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
@ -64,12 +75,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
Debug.Assert(ParentHitObject != null);
|
||||||
|
|
||||||
|
var holdNote = (DrawableHoldNote)ParentHitObject;
|
||||||
|
holdStartTime = () => holdNote.HoldStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
holdStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (Time.Current < HitObject.StartTime)
|
if (Time.Current < HitObject.StartTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var startTime = HoldStartTime?.Invoke();
|
var startTime = holdStartTime?.Invoke();
|
||||||
|
|
||||||
if (startTime == null || startTime > HitObject.StartTime)
|
if (startTime == null || startTime > HitObject.StartTime)
|
||||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
|
@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -59,9 +60,31 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
Action.BindTo(action);
|
Action.BindTo(action);
|
||||||
|
|
||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(scrollingInfo.Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
Direction.BindValueChanged(OnDirectionChanged, true);
|
Direction.BindValueChanged(OnDirectionChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.BindTo(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
private double computedLifetimeStart;
|
private double computedLifetimeStart;
|
||||||
|
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
@ -147,12 +170,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
where TObject : ManiaHitObject
|
where TObject : ManiaHitObject
|
||||||
{
|
{
|
||||||
public new readonly TObject HitObject;
|
public new TObject HitObject => (TObject)base.HitObject;
|
||||||
|
|
||||||
protected DrawableManiaHitObject(TObject hitObject)
|
protected DrawableManiaHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,31 +33,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
||||||
|
|
||||||
private readonly Drawable headPiece;
|
private Drawable headPiece;
|
||||||
|
|
||||||
|
public DrawableNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableNote(Note hitObject)
|
public DrawableNote(Note hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece())
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(ManiaRulesetConfigManager rulesetConfig)
|
private void load(ManiaRulesetConfigManager rulesetConfig)
|
||||||
{
|
{
|
||||||
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||||
|
|
||||||
|
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour());
|
base.LoadComplete();
|
||||||
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true);
|
|
||||||
|
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour());
|
||||||
|
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||||
@ -102,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
private void updateSnapColour()
|
private void updateSnapColour()
|
||||||
{
|
{
|
||||||
if (beatmap == null) return;
|
if (beatmap == null || HitObject == null) return;
|
||||||
|
|
||||||
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
||||||
|
|
||||||
|
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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.Mania.Objects
|
||||||
|
{
|
||||||
|
public class HeadNote : Note
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The head note of the hold.
|
/// The head note of the hold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Note Head { get; private set; }
|
public HeadNote Head { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tail note of the hold.
|
/// The tail note of the hold.
|
||||||
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
createTicks(cancellationToken);
|
createTicks(cancellationToken);
|
||||||
|
|
||||||
AddNested(Head = new Note
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
|
@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Drawable getResult(HitResult result)
|
private Drawable getResult(HitResult result)
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Width = COLUMN_WIDTH;
|
Width = COLUMN_WIDTH;
|
||||||
|
|
||||||
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
|
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -83,6 +84,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
|
|
||||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
|
|
||||||
|
RegisterPool<Note, DrawableNote>(10, 50);
|
||||||
|
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||||
|
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||||
|
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||||
|
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
NewResult += OnNewResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColumnType ColumnType { get; set; }
|
public ColumnType ColumnType { get; set; }
|
||||||
@ -98,28 +112,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||||
/// Adds a DrawableHitObject to this Playfield.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObject">The DrawableHitObject to add.</param>
|
|
||||||
public override void Add(DrawableHitObject hitObject)
|
|
||||||
{
|
{
|
||||||
hitObject.AccentColour.Value = AccentColour;
|
base.OnNewDrawableHitObject(drawableHitObject);
|
||||||
hitObject.OnNewResult += OnNewResult;
|
|
||||||
|
|
||||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject;
|
||||||
|
|
||||||
|
maniaObject.AccentColour.Value = AccentColour;
|
||||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
base.Add(hitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
|
||||||
{
|
|
||||||
if (!base.Remove(h))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 2,
|
Depth = 2,
|
||||||
},
|
},
|
||||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = 1
|
Depth = 1
|
||||||
|
@ -18,7 +18,6 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -134,20 +133,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
||||||
|
|
||||||
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h)
|
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h) => null;
|
||||||
{
|
|
||||||
switch (h)
|
|
||||||
{
|
|
||||||
case HoldNote holdNote:
|
|
||||||
return new DrawableHoldNote(holdNote);
|
|
||||||
|
|
||||||
case Note note:
|
|
||||||
return new DrawableNote(note);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -56,6 +57,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject);
|
||||||
|
|
||||||
|
public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject);
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion())
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -132,33 +133,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
base.LoadComplete();
|
||||||
|
NewResult += OnNewResult;
|
||||||
int columnIndex = -1;
|
|
||||||
|
|
||||||
maniaObject.ColumnBindable.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
if (columnIndex != -1)
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Add(h);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
h.OnNewResult += OnNewResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
public override void Add(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Add(hitObject);
|
||||||
{
|
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
|
||||||
int columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
public override bool Remove(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Remove(hitObject);
|
||||||
return true;
|
|
||||||
}
|
public override void Add(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Add(h);
|
||||||
|
|
||||||
|
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
|
||||||
|
|
||||||
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
@ -32,6 +34,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay)));
|
AddStep($"{term} Small", () => SetContents(() => testSingle(7, autoplay)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinningSamplePitchShift()
|
||||||
|
{
|
||||||
|
AddStep("Add spinner", () => SetContents(() => testSingle(5, true, 4000)));
|
||||||
|
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
||||||
|
AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8);
|
||||||
|
|
||||||
|
PausableSkinnableSound getSpinningSample() => drawableSpinner.ChildrenOfType<PausableSkinnableSound>().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin"))));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestLongSpinner(bool autoplay)
|
public void TestLongSpinner(bool autoplay)
|
||||||
@ -93,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
if (auto)
|
if (auto)
|
||||||
RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 3));
|
RotationTracker.AddRotation((float)(Clock.ElapsedFrameTime * 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
|
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
|
||||||
|
|
||||||
var item = new PathTypeMenuItem(type, () =>
|
var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ =>
|
||||||
{
|
{
|
||||||
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
|
||||||
updatePathType(p, type);
|
updatePathType(p, type);
|
||||||
@ -258,15 +258,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PathTypeMenuItem : TernaryStateMenuItem
|
|
||||||
{
|
|
||||||
public PathTypeMenuItem(PathType? type, Action action)
|
|
||||||
: base(type == null ? "Inherit" : type.ToString().Humanize(), changeState, MenuItemType.Standard, _ => action?.Invoke())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TernaryState changeState(TernaryState state) => TernaryState.True;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Compose;
|
using osu.Game.Screens.Edit.Compose;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -25,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||||
{
|
{
|
||||||
|
protected new DrawableSlider DrawableObject => (DrawableSlider)base.DrawableObject;
|
||||||
|
|
||||||
protected SliderBodyPiece BodyPiece { get; private set; }
|
protected SliderBodyPiece BodyPiece { get; private set; }
|
||||||
protected SliderCircleOverlay HeadOverlay { get; private set; }
|
protected SliderCircleOverlay HeadOverlay { get; private set; }
|
||||||
protected SliderCircleOverlay TailOverlay { get; private set; }
|
protected SliderCircleOverlay TailOverlay { get; private set; }
|
||||||
@ -236,7 +239,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
|
||||||
};
|
};
|
||||||
|
|
||||||
public override Vector2 ScreenSpaceSelectionPoint => BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
|
// Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions.
|
||||||
|
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset)
|
||||||
|
?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -18,6 +17,17 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
public class OsuSelectionHandler : EditorSelectionHandler
|
public class OsuSelectionHandler : EditorSelectionHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
||||||
|
/// </summary>
|
||||||
|
private Vector2? referenceOrigin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// During a transform, the initial path types of a single selected slider are stored so they
|
||||||
|
/// can be maintained throughout the operation.
|
||||||
|
/// </summary>
|
||||||
|
private List<PathType?> referencePathTypes;
|
||||||
|
|
||||||
protected override void OnSelectionChanged()
|
protected override void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
base.OnSelectionChanged();
|
base.OnSelectionChanged();
|
||||||
@ -50,17 +60,6 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// During a transform, the initial origin is stored so it can be used throughout the operation.
|
|
||||||
/// </summary>
|
|
||||||
private Vector2? referenceOrigin;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// During a transform, the initial path types of a single selected slider are stored so they
|
|
||||||
/// can be maintained throughout the operation.
|
|
||||||
/// </summary>
|
|
||||||
private List<PathType?> referencePathTypes;
|
|
||||||
|
|
||||||
public override bool HandleReverse()
|
public override bool HandleReverse()
|
||||||
{
|
{
|
||||||
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
var hitObjects = EditorBeatmap.SelectedHitObjects;
|
||||||
@ -114,24 +113,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
|
|
||||||
var selectedObjectsQuad = getSurroundingQuad(hitObjects);
|
var selectedObjectsQuad = getSurroundingQuad(hitObjects);
|
||||||
var centre = selectedObjectsQuad.Centre;
|
|
||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
{
|
||||||
var pos = h.Position;
|
h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position);
|
||||||
|
|
||||||
switch (direction)
|
|
||||||
{
|
|
||||||
case Direction.Horizontal:
|
|
||||||
pos.X = centre.X - (pos.X - centre.X);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Direction.Vertical:
|
|
||||||
pos.Y = centre.Y - (pos.Y - centre.Y);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Position = pos;
|
|
||||||
|
|
||||||
if (h is Slider slider)
|
if (h is Slider slider)
|
||||||
{
|
{
|
||||||
@ -187,12 +172,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
{
|
||||||
h.Position = rotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta);
|
h.Position = RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta);
|
||||||
|
|
||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
foreach (var point in path.Path.ControlPoints)
|
foreach (var point in path.Path.ControlPoints)
|
||||||
point.Position.Value = rotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta);
|
point.Position.Value = RotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
||||||
|
|
||||||
Quad sliderQuad = getSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
||||||
|
|
||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
||||||
@ -239,26 +224,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
||||||
{
|
{
|
||||||
scale = getClampedScale(hitObjects, reference, scale);
|
scale = getClampedScale(hitObjects, reference, scale);
|
||||||
|
|
||||||
// move the selection before scaling if dragging from top or left anchors.
|
|
||||||
float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0;
|
|
||||||
float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0;
|
|
||||||
|
|
||||||
Quad selectionQuad = getSurroundingQuad(hitObjects);
|
Quad selectionQuad = getSurroundingQuad(hitObjects);
|
||||||
|
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
{
|
h.Position = GetScaledPosition(reference, scale, selectionQuad, h.Position);
|
||||||
var newPosition = h.Position;
|
|
||||||
|
|
||||||
// guard against no-ops and NaN.
|
|
||||||
if (scale.X != 0 && selectionQuad.Width > 0)
|
|
||||||
newPosition.X = selectionQuad.TopLeft.X + xOffset + (h.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X);
|
|
||||||
|
|
||||||
if (scale.Y != 0 && selectionQuad.Height > 0)
|
|
||||||
newPosition.Y = selectionQuad.TopLeft.Y + yOffset + (h.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y);
|
|
||||||
|
|
||||||
h.Position = newPosition;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (bool X, bool Y) isQuadInBounds(Quad quad)
|
private (bool X, bool Y) isQuadInBounds(Quad quad)
|
||||||
@ -333,7 +302,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
/// <param name="hitObjects">The hit objects to calculate a quad for.</param>
|
||||||
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
private Quad getSurroundingQuad(OsuHitObject[] hitObjects) =>
|
||||||
getSurroundingQuad(hitObjects.SelectMany(h =>
|
GetSurroundingQuad(hitObjects.SelectMany(h =>
|
||||||
{
|
{
|
||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
@ -348,58 +317,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return new[] { h.Position };
|
return new[] { h.Position };
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a gamefield-space quad surrounding the provided points.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="points">The points to calculate a quad for.</param>
|
|
||||||
private Quad getSurroundingQuad(IEnumerable<Vector2> points)
|
|
||||||
{
|
|
||||||
if (!EditorBeatmap.SelectedHitObjects.Any())
|
|
||||||
return new Quad();
|
|
||||||
|
|
||||||
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
|
||||||
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
|
|
||||||
|
|
||||||
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
|
|
||||||
foreach (var p in points)
|
|
||||||
{
|
|
||||||
minPosition = Vector2.ComponentMin(minPosition, p);
|
|
||||||
maxPosition = Vector2.ComponentMax(maxPosition, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector2 size = maxPosition - minPosition;
|
|
||||||
|
|
||||||
return new Quad(minPosition.X, minPosition.Y, size.X, size.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// All osu! hitobjects which can be moved/rotated/scaled.
|
/// All osu! hitobjects which can be moved/rotated/scaled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
private OsuHitObject[] selectedMovableObjects => SelectedItems.OfType<OsuHitObject>()
|
||||||
.Where(h => !(h is Spinner))
|
.Where(h => !(h is Spinner))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Rotate a point around an arbitrary origin.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="point">The point.</param>
|
|
||||||
/// <param name="origin">The centre origin to rotate around.</param>
|
|
||||||
/// <param name="angle">The angle to rotate (in degrees).</param>
|
|
||||||
private static Vector2 rotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle)
|
|
||||||
{
|
|
||||||
angle = -angle;
|
|
||||||
|
|
||||||
point.X -= origin.X;
|
|
||||||
point.Y -= origin.Y;
|
|
||||||
|
|
||||||
Vector2 ret;
|
|
||||||
ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle));
|
|
||||||
ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle));
|
|
||||||
|
|
||||||
ret.X += origin.X;
|
|
||||||
ret.Y += origin.Y;
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
|
public override bool DisplayResult => !HitObject.OnlyJudgeNestedObjects;
|
||||||
|
|
||||||
private PlaySliderBody sliderBody => Body.Drawable as PlaySliderBody;
|
[CanBeNull]
|
||||||
|
public PlaySliderBody SliderBody => Body.Drawable as PlaySliderBody;
|
||||||
|
|
||||||
public IBindable<int> PathVersion => pathVersion;
|
public IBindable<int> PathVersion => pathVersion;
|
||||||
private readonly Bindable<int> pathVersion = new Bindable<int>();
|
private readonly Bindable<int> pathVersion = new Bindable<int>();
|
||||||
@ -215,16 +216,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1);
|
double completionProgress = Math.Clamp((Time.Current - HitObject.StartTime) / HitObject.Duration, 0, 1);
|
||||||
|
|
||||||
Ball.UpdateProgress(completionProgress);
|
Ball.UpdateProgress(completionProgress);
|
||||||
sliderBody?.UpdateProgress(completionProgress);
|
SliderBody?.UpdateProgress(completionProgress);
|
||||||
|
|
||||||
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
||||||
{
|
{
|
||||||
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(sliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(sliderBody?.SnakedEnd ?? 0));
|
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(HitObject.Path.PositionAt(SliderBody?.SnakedStart ?? 0), HitObject.Path.PositionAt(SliderBody?.SnakedEnd ?? 0));
|
||||||
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
|
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
|
||||||
}
|
}
|
||||||
|
|
||||||
Size = sliderBody?.Size ?? Vector2.Zero;
|
Size = SliderBody?.Size ?? Vector2.Zero;
|
||||||
OriginPosition = sliderBody?.PathOffset ?? Vector2.Zero;
|
OriginPosition = SliderBody?.PathOffset ?? Vector2.Zero;
|
||||||
|
|
||||||
if (DrawSize != Vector2.Zero)
|
if (DrawSize != Vector2.Zero)
|
||||||
{
|
{
|
||||||
@ -238,7 +239,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
public override void OnKilled()
|
public override void OnKilled()
|
||||||
{
|
{
|
||||||
base.OnKilled();
|
base.OnKilled();
|
||||||
sliderBody?.RecyclePath();
|
SliderBody?.RecyclePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
|
||||||
@ -324,7 +325,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out);
|
||||||
if (sliderBody?.SnakingOut.Value == true)
|
if (SliderBody?.SnakingOut.Value == true)
|
||||||
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -332,7 +333,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => sliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
private class DefaultSliderBody : PlaySliderBody
|
private class DefaultSliderBody : PlaySliderBody
|
||||||
{
|
{
|
||||||
|
@ -7,13 +7,14 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking, IHasMainCirclePiece
|
public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, IHasMainCirclePiece
|
||||||
{
|
{
|
||||||
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
public new SliderTailCircle HitObject => (SliderTailCircle)base.HitObject;
|
||||||
|
|
||||||
@ -111,7 +112,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateSnakingPosition(Vector2 start, Vector2 end) =>
|
protected override void OnApply()
|
||||||
Position = HitObject.RepeatIndex % 2 == 0 ? end : start;
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (Slider != null)
|
||||||
|
Position = Slider.CurvePositionAt(HitObject.RepeatIndex % 2 == 0 ? 1 : 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private Bindable<bool> isSpinning;
|
private Bindable<bool> isSpinning;
|
||||||
private bool spinnerFrequencyModulate;
|
private bool spinnerFrequencyModulate;
|
||||||
|
|
||||||
|
private const float spinning_sample_initial_frequency = 1.0f;
|
||||||
|
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
|
/// The amount of bonus score gained from spinning after the required number of spins, for display purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -106,9 +109,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
isSpinning.BindValueChanged(updateSpinningSample);
|
isSpinning.BindValueChanged(updateSpinningSample);
|
||||||
}
|
}
|
||||||
|
|
||||||
private const float spinning_sample_initial_frequency = 1.0f;
|
|
||||||
private const float spinning_sample_modulated_base_frequency = 0.5f;
|
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
|
@ -34,9 +34,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (!(component is OsuSkinComponent osuComponent))
|
if (component is OsuSkinComponent osuComponent)
|
||||||
return null;
|
{
|
||||||
|
|
||||||
switch (osuComponent.Component)
|
switch (osuComponent.Component)
|
||||||
{
|
{
|
||||||
case OsuSkinComponents.FollowPoint:
|
case OsuSkinComponents.FollowPoint:
|
||||||
@ -116,8 +115,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
=> AddStep("clear SHOC", () => hitObjectContainer.Clear(false));
|
=> AddStep("clear SHOC", () => hitObjectContainer.Clear());
|
||||||
|
|
||||||
protected void AddHitObject(DrawableHitObject hitObject)
|
protected void AddHitObject(DrawableHitObject hitObject)
|
||||||
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
||||||
|
@ -79,8 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
// Old osu! used hit sounding to determine various hit type information
|
// Old osu! used hit sounding to determine various hit type information
|
||||||
IList<HitSampleInfo> samples = obj.Samples;
|
IList<HitSampleInfo> samples = obj.Samples;
|
||||||
|
|
||||||
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
|
||||||
|
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case IHasDistance distanceData:
|
case IHasDistance distanceData:
|
||||||
@ -94,15 +92,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
|
||||||
{
|
{
|
||||||
IList<HitSampleInfo> currentSamples = allSamples[i];
|
IList<HitSampleInfo> currentSamples = allSamples[i];
|
||||||
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
|
|
||||||
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
|
|
||||||
|
|
||||||
yield return new Hit
|
yield return new Hit
|
||||||
{
|
{
|
||||||
StartTime = j,
|
StartTime = j,
|
||||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
|
||||||
Samples = currentSamples,
|
Samples = currentSamples,
|
||||||
IsStrong = strong
|
|
||||||
};
|
};
|
||||||
|
|
||||||
i = (i + 1) % allSamples.Count;
|
i = (i + 1) % allSamples.Count;
|
||||||
@ -117,7 +111,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
IsStrong = strong,
|
|
||||||
Duration = taikoDuration,
|
Duration = taikoDuration,
|
||||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||||
};
|
};
|
||||||
@ -143,16 +136,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
bool isRimDefinition(HitSampleInfo s) => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE;
|
|
||||||
|
|
||||||
bool isRim = samples.Any(isRimDefinition);
|
|
||||||
|
|
||||||
yield return new Hit
|
yield return new Hit
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
Type = isRim ? HitType.Rim : HitType.Centre,
|
|
||||||
Samples = samples,
|
Samples = samples,
|
||||||
IsStrong = strong
|
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -14,10 +14,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
private readonly HitPiece piece;
|
private readonly HitPiece piece;
|
||||||
|
|
||||||
private static Hit hit;
|
public new Hit HitObject => (Hit)base.HitObject;
|
||||||
|
|
||||||
public HitPlacementBlueprint()
|
public HitPlacementBlueprint()
|
||||||
: base(hit = new Hit())
|
: base(new Hit())
|
||||||
{
|
{
|
||||||
InternalChild = piece = new HitPiece
|
InternalChild = piece = new HitPiece
|
||||||
{
|
{
|
||||||
@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
|||||||
switch (e.Button)
|
switch (e.Button)
|
||||||
{
|
{
|
||||||
case MouseButton.Left:
|
case MouseButton.Left:
|
||||||
hit.Type = HitType.Centre;
|
HitObject.Type = HitType.Centre;
|
||||||
EndPlacement(true);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case MouseButton.Right:
|
case MouseButton.Right:
|
||||||
hit.Type = HitType.Rim;
|
HitObject.Type = HitType.Rim;
|
||||||
EndPlacement(true);
|
EndPlacement(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -69,17 +69,21 @@ namespace osu.Game.Rulesets.Taiko.Edit
|
|||||||
{
|
{
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is Hit taikoHit) taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
if (h is Hit taikoHit)
|
||||||
|
{
|
||||||
|
taikoHit.Type = state ? HitType.Rim : HitType.Centre;
|
||||||
|
EditorBeatmap.Update(h);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<HitObject>> selection)
|
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint<HitObject>> selection)
|
||||||
{
|
{
|
||||||
if (selection.All(s => s.Item is Hit))
|
if (selection.All(s => s.Item is Hit))
|
||||||
yield return new TernaryStateMenuItem("Rim") { State = { BindTarget = selectionRimState } };
|
yield return new TernaryStateToggleMenuItem("Rim") { State = { BindTarget = selectionRimState } };
|
||||||
|
|
||||||
if (selection.All(s => s.Item is TaikoHitObject))
|
if (selection.All(s => s.Item is TaikoHitObject))
|
||||||
yield return new TernaryStateMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
|
yield return new TernaryStateToggleMenuItem("Strong") { State = { BindTarget = selectionStrongState } };
|
||||||
|
|
||||||
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
foreach (var item in base.GetContextMenuItemsForSelection(selection))
|
||||||
yield return item;
|
yield return item;
|
||||||
|
@ -52,23 +52,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
type.BindTo(HitObject.TypeBindable);
|
type.BindTo(HitObject.TypeBindable);
|
||||||
type.BindValueChanged(_ =>
|
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||||
{
|
type.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||||
updateActionsFromType();
|
|
||||||
|
|
||||||
// will overwrite samples, should only be called on subsequent changes
|
|
||||||
// after the initial application.
|
|
||||||
updateSamplesFromTypeChange();
|
|
||||||
|
|
||||||
RecreatePieces();
|
|
||||||
});
|
|
||||||
|
|
||||||
// action update also has to happen immediately on application.
|
|
||||||
updateActionsFromType();
|
|
||||||
|
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void RecreatePieces()
|
||||||
|
{
|
||||||
|
updateActionsFromType();
|
||||||
|
base.RecreatePieces();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnFree()
|
protected override void OnFree()
|
||||||
{
|
{
|
||||||
base.OnFree();
|
base.OnFree();
|
||||||
@ -83,33 +78,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
validActionPressed = pressHandledThisFrame = false;
|
validActionPressed = pressHandledThisFrame = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HitSampleInfo[] getRimSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
|
||||||
|
|
||||||
protected override void LoadSamples()
|
|
||||||
{
|
|
||||||
base.LoadSamples();
|
|
||||||
|
|
||||||
type.Value = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSamplesFromTypeChange()
|
|
||||||
{
|
|
||||||
var rimSamples = getRimSamples();
|
|
||||||
|
|
||||||
bool isRimType = HitObject.Type == HitType.Rim;
|
|
||||||
|
|
||||||
if (isRimType != rimSamples.Any())
|
|
||||||
{
|
|
||||||
if (isRimType)
|
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var sample in rimSamples)
|
|
||||||
HitObject.Samples.Remove(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateActionsFromType()
|
private void updateActionsFromType()
|
||||||
{
|
{
|
||||||
HitActions =
|
HitActions =
|
||||||
|
@ -137,7 +137,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
MainPiece?.Expire();
|
if (MainPiece != null)
|
||||||
|
Content.Remove(MainPiece);
|
||||||
|
|
||||||
Content.Add(MainPiece = CreateMainPiece());
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -29,14 +27,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override void OnApply()
|
protected override void OnApply()
|
||||||
{
|
{
|
||||||
isStrong.BindTo(HitObject.IsStrongBindable);
|
isStrong.BindTo(HitObject.IsStrongBindable);
|
||||||
isStrong.BindValueChanged(_ =>
|
// this doesn't need to be run inline as RecreatePieces is called by the base call below.
|
||||||
{
|
isStrong.BindValueChanged(_ => Scheduler.AddOnce(RecreatePieces));
|
||||||
// will overwrite samples, should only be called on subsequent changes
|
|
||||||
// after the initial application.
|
|
||||||
updateSamplesFromStrong();
|
|
||||||
|
|
||||||
RecreatePieces();
|
|
||||||
});
|
|
||||||
|
|
||||||
base.OnApply();
|
base.OnApply();
|
||||||
}
|
}
|
||||||
@ -50,30 +42,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
isStrong.UnbindEvents();
|
isStrong.UnbindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private HitSampleInfo[] getStrongSamples() => HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
|
||||||
|
|
||||||
protected override void LoadSamples()
|
|
||||||
{
|
|
||||||
base.LoadSamples();
|
|
||||||
isStrong.Value = getStrongSamples().Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSamplesFromStrong()
|
|
||||||
{
|
|
||||||
var strongSamples = getStrongSamples();
|
|
||||||
|
|
||||||
if (isStrong.Value != strongSamples.Any())
|
|
||||||
{
|
|
||||||
if (isStrong.Value)
|
|
||||||
HitObject.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var sample in strongSamples)
|
|
||||||
HitObject.Samples.Remove(sample);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void RecreatePieces()
|
protected override void RecreatePieces()
|
||||||
{
|
{
|
||||||
base.RecreatePieces();
|
base.RecreatePieces();
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
{
|
{
|
||||||
@ -18,6 +20,40 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
set => TypeBindable.Value = value;
|
set => TypeBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Hit()
|
||||||
|
{
|
||||||
|
TypeBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||||
|
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTypeFromSamples()
|
||||||
|
{
|
||||||
|
Type = getRimSamples().Any() ? HitType.Rim : HitType.Centre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns an array of any samples which would cause this object to be a "rim" type hit.
|
||||||
|
/// </summary>
|
||||||
|
private HitSampleInfo[] getRimSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE).ToArray();
|
||||||
|
|
||||||
|
private void updateSamplesFromType()
|
||||||
|
{
|
||||||
|
var rimSamples = getRimSamples();
|
||||||
|
|
||||||
|
bool isRimType = Type == HitType.Rim;
|
||||||
|
|
||||||
|
if (isRimType != rimSamples.Any())
|
||||||
|
{
|
||||||
|
if (isRimType)
|
||||||
|
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_CLAP));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var sample in rimSamples)
|
||||||
|
Samples.Remove(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||||
|
|
||||||
public class StrongNestedHit : StrongNestedHitObject
|
public class StrongNestedHit : StrongNestedHitObject
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects
|
namespace osu.Game.Rulesets.Taiko.Objects
|
||||||
@ -34,6 +36,35 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
set => IsStrongBindable.Value = value;
|
set => IsStrongBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TaikoStrongableHitObject()
|
||||||
|
{
|
||||||
|
IsStrongBindable.BindValueChanged(_ => updateSamplesFromType());
|
||||||
|
SamplesBindable.BindCollectionChanged((_, __) => updateTypeFromSamples());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTypeFromSamples()
|
||||||
|
{
|
||||||
|
IsStrong = getStrongSamples().Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSamplesFromType()
|
||||||
|
{
|
||||||
|
var strongSamples = getStrongSamples();
|
||||||
|
|
||||||
|
if (IsStrongBindable.Value != strongSamples.Any())
|
||||||
|
{
|
||||||
|
if (IsStrongBindable.Value)
|
||||||
|
Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_FINISH));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var sample in strongSamples)
|
||||||
|
Samples.Remove(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HitSampleInfo[] getStrongSamples() => Samples.Where(s => s.Name == HitSampleInfo.HIT_FINISH).ToArray();
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects(cancellationToken);
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
@ -38,9 +38,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
return Drawable.Empty().With(d => d.Expire());
|
return Drawable.Empty().With(d => d.Expire());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(component is TaikoSkinComponent taikoComponent))
|
if (component is TaikoSkinComponent taikoComponent)
|
||||||
return null;
|
{
|
||||||
|
|
||||||
switch (taikoComponent.Component)
|
switch (taikoComponent.Component)
|
||||||
{
|
{
|
||||||
case TaikoSkinComponents.DrumRollBody:
|
case TaikoSkinComponents.DrumRollBody:
|
||||||
@ -131,6 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
case TaikoSkinComponents.Mascot:
|
case TaikoSkinComponents.Mascot:
|
||||||
return new DrawableTaikoMascot();
|
return new DrawableTaikoMascot();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Source.GetDrawableComponent(component);
|
return Source.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(new MemoryStream());
|
await importCollectionsFromStream(osu, new MemoryStream());
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
Assert.That(osu.CollectionManager.Collections.Count, Is.Zero);
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host);
|
var osu = LoadOsuIntoHost(host);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host, true);
|
var osu = LoadOsuIntoHost(host, true);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2));
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
|
|
||||||
ms.Seek(0, SeekOrigin.Begin);
|
ms.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(ms);
|
await importCollectionsFromStream(osu, ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.That(host.UpdateThread.Running, Is.True);
|
Assert.That(host.UpdateThread.Running, Is.True);
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
{
|
{
|
||||||
var osu = LoadOsuIntoHost(host, true);
|
var osu = LoadOsuIntoHost(host, true);
|
||||||
|
|
||||||
await osu.CollectionManager.Import(TestResources.OpenResource("Collections/collections.db"));
|
await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db"));
|
||||||
|
|
||||||
// Move first beatmap from second collection into the first.
|
// Move first beatmap from second collection into the first.
|
||||||
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
osu.CollectionManager.Collections[0].Beatmaps.Add(osu.CollectionManager.Collections[1].Beatmaps[0]);
|
||||||
@ -169,5 +169,12 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task importCollectionsFromStream(TestOsuGameBase osu, Stream stream)
|
||||||
|
{
|
||||||
|
// intentionally spin this up on a separate task to avoid disposal deadlocks.
|
||||||
|
// see https://github.com/EventStore/EventStore/issues/1179
|
||||||
|
await Task.Run(() => osu.CollectionManager.Import(stream).Wait());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
175
osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs
Normal file
175
osu.Game.Tests/Editing/TestSceneHitObjectContainerEventBuffer.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Editing
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly TestHitObject testObj = new TestHitObject();
|
||||||
|
|
||||||
|
private TestPlayfield playfield1;
|
||||||
|
private TestPlayfield playfield2;
|
||||||
|
private TestDrawable intermediateDrawable;
|
||||||
|
private HitObjectUsageEventBuffer eventBuffer;
|
||||||
|
|
||||||
|
private HitObject beganUsage;
|
||||||
|
private HitObject finishedUsage;
|
||||||
|
private HitObject transferredUsage;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
|
||||||
|
if (eventBuffer != null)
|
||||||
|
{
|
||||||
|
eventBuffer.HitObjectUsageBegan -= onHitObjectUsageBegan;
|
||||||
|
eventBuffer.HitObjectUsageFinished -= onHitObjectUsageFinished;
|
||||||
|
eventBuffer.HitObjectUsageTransferred -= onHitObjectUsageTransferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
var topPlayfield = new TestPlayfield();
|
||||||
|
topPlayfield.AddNested(playfield1 = new TestPlayfield());
|
||||||
|
topPlayfield.AddNested(playfield2 = new TestPlayfield());
|
||||||
|
|
||||||
|
eventBuffer = new HitObjectUsageEventBuffer(topPlayfield);
|
||||||
|
eventBuffer.HitObjectUsageBegan += onHitObjectUsageBegan;
|
||||||
|
eventBuffer.HitObjectUsageFinished += onHitObjectUsageFinished;
|
||||||
|
eventBuffer.HitObjectUsageTransferred += onHitObjectUsageTransferred;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
topPlayfield,
|
||||||
|
intermediateDrawable = new TestDrawable(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
private void onHitObjectUsageBegan(HitObject obj) => beganUsage = obj;
|
||||||
|
|
||||||
|
private void onHitObjectUsageFinished(HitObject obj) => finishedUsage = obj;
|
||||||
|
|
||||||
|
private void onHitObjectUsageTransferred(HitObject obj, DrawableHitObject drawableObj) => transferredUsage = obj;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageBeganAfterAdd()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addCheckStep(began: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageFinishedAfterRemove()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("remove hitobject", () => playfield1.Remove(testObj));
|
||||||
|
addCheckStep(finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUsageTransferredWhenMovedBetweenPlayfields()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("transfer hitobject to other playfield", () =>
|
||||||
|
{
|
||||||
|
playfield1.Remove(testObj);
|
||||||
|
playfield2.Add(testObj);
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(transferred: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveImmediatelyAfterUsageBegan()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject and schedule removal", () =>
|
||||||
|
{
|
||||||
|
playfield1.Add(testObj);
|
||||||
|
intermediateDrawable.Schedule(() => playfield1.Remove(testObj));
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(began: true, finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveImmediatelyAfterTransferred()
|
||||||
|
{
|
||||||
|
AddStep("add hitobject", () => playfield1.Add(testObj));
|
||||||
|
addResetStep();
|
||||||
|
AddStep("transfer hitobject to other playfield and schedule removal", () =>
|
||||||
|
{
|
||||||
|
playfield1.Remove(testObj);
|
||||||
|
playfield2.Add(testObj);
|
||||||
|
intermediateDrawable.Schedule(() => playfield2.Remove(testObj));
|
||||||
|
});
|
||||||
|
|
||||||
|
addCheckStep(transferred: true, finished: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
eventBuffer.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addResetStep() => AddStep("reset", reset);
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
beganUsage = null;
|
||||||
|
finishedUsage = null;
|
||||||
|
transferredUsage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addCheckStep(bool began = false, bool finished = false, bool transferred = false)
|
||||||
|
=> AddAssert($"began = {began}, finished = {finished}, transferred = {transferred}",
|
||||||
|
() => (beganUsage == testObj) == began && (finishedUsage == testObj) == finished && (transferredUsage == testObj) == transferred);
|
||||||
|
|
||||||
|
private class TestPlayfield : Playfield
|
||||||
|
{
|
||||||
|
public TestPlayfield()
|
||||||
|
{
|
||||||
|
RegisterPool<TestHitObject, TestDrawableHitObject>(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void AddNested(Playfield playfield)
|
||||||
|
{
|
||||||
|
AddInternal(playfield);
|
||||||
|
base.AddNested(playfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject)
|
||||||
|
{
|
||||||
|
var entry = base.CreateLifetimeEntry(hitObject);
|
||||||
|
entry.KeepAlive = true;
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHitObject : HitObject
|
||||||
|
{
|
||||||
|
public override string ToString() => "TestHitObject";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawable : Drawable
|
||||||
|
{
|
||||||
|
public new void Schedule(Action action) => base.Schedule(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -45,15 +47,16 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep("Create DHO", () =>
|
AddStep("Create DHO", () =>
|
||||||
{
|
{
|
||||||
dho = new TestDrawableHitObject(null);
|
dho = new TestDrawableHitObject(null);
|
||||||
dho.Apply(entry = new TestLifetimeEntry(new HitObject())
|
dho.Apply(entry = new TestLifetimeEntry(new HitObject()));
|
||||||
{
|
|
||||||
LifetimeStart = 0,
|
|
||||||
LifetimeEnd = 1000,
|
|
||||||
});
|
|
||||||
Child = dho;
|
Child = dho;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("KeepAlive = true", () => entry.KeepAlive = true);
|
AddStep("KeepAlive = true", () =>
|
||||||
|
{
|
||||||
|
entry.LifetimeStart = 0;
|
||||||
|
entry.LifetimeEnd = 1000;
|
||||||
|
entry.KeepAlive = true;
|
||||||
|
});
|
||||||
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
||||||
|
|
||||||
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
||||||
@ -69,15 +72,46 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
|
AddAssert("Lifetime is changed", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLifetimeUpdatedOnDefaultApplied()
|
||||||
|
{
|
||||||
|
TestLifetimeEntry entry = null;
|
||||||
|
AddStep("Create entry", () => entry = new TestLifetimeEntry(new HitObject()) { LifetimeStart = 1 });
|
||||||
|
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||||
|
AddAssert("Lifetime is updated", () => entry.LifetimeStart == -TestLifetimeEntry.INITIAL_LIFETIME_OFFSET);
|
||||||
|
|
||||||
|
TestDrawableHitObject dho = null;
|
||||||
|
AddStep("Create DHO", () =>
|
||||||
|
{
|
||||||
|
dho = new TestDrawableHitObject(null);
|
||||||
|
dho.Apply(entry);
|
||||||
|
Child = dho;
|
||||||
|
dho.SetLifetimeStartOnApply = true;
|
||||||
|
});
|
||||||
|
AddStep("ApplyDefaults", () => entry.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()));
|
||||||
|
AddAssert("Lifetime is correct", () => dho.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY && entry.LifetimeStart == TestDrawableHitObject.LIFETIME_ON_APPLY);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
{
|
{
|
||||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||||
|
public const double LIFETIME_ON_APPLY = 222;
|
||||||
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
protected override double InitialLifetimeOffset => INITIAL_LIFETIME_OFFSET;
|
||||||
|
|
||||||
|
public bool SetLifetimeStartOnApply;
|
||||||
|
|
||||||
public TestDrawableHitObject(HitObject hitObject)
|
public TestDrawableHitObject(HitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (SetLifetimeStartOnApply)
|
||||||
|
LifetimeStart = LIFETIME_ON_APPLY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||||
|
85
osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs
Normal file
85
osu.Game.Tests/Gameplay/TestSceneProxyContainer.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Gameplay
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneProxyContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private HitObjectContainer hitObjectContainer;
|
||||||
|
private ProxyContainer proxyContainer;
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hitObjectContainer = new HitObjectContainer(),
|
||||||
|
proxyContainer = new ProxyContainer()
|
||||||
|
},
|
||||||
|
Clock = new FramedClock(clock)
|
||||||
|
};
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestProxyLifetimeManagement()
|
||||||
|
{
|
||||||
|
AddStep("Add proxy drawables", () =>
|
||||||
|
{
|
||||||
|
addProxy(new TestDrawableHitObject(1000));
|
||||||
|
addProxy(new TestDrawableHitObject(3000));
|
||||||
|
addProxy(new TestDrawableHitObject(5000));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("time = 1000", () => clock.CurrentTime = 1000);
|
||||||
|
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
|
||||||
|
AddStep("time = 5000", () => clock.CurrentTime = 5000);
|
||||||
|
AddAssert("One proxy is alive", () => proxyContainer.AliveChildren.Count == 1);
|
||||||
|
AddStep("time = 6000", () => clock.CurrentTime = 6000);
|
||||||
|
AddAssert("No proxy is alive", () => proxyContainer.AliveChildren.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addProxy(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
hitObjectContainer.Add(drawableHitObject);
|
||||||
|
proxyContainer.AddProxy(drawableHitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProxyContainer : LifetimeManagementContainer
|
||||||
|
{
|
||||||
|
public IReadOnlyList<Drawable> AliveChildren => AliveInternalChildren;
|
||||||
|
|
||||||
|
public void AddProxy(Drawable d) => AddInternal(d.CreateProxy());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
|
{
|
||||||
|
protected override double InitialLifetimeOffset => 100;
|
||||||
|
|
||||||
|
public TestDrawableHitObject(double startTime)
|
||||||
|
: base(new HitObject { StartTime = startTime })
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
LifetimeEnd = LifetimeStart + 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
130
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Lists;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
|
||||||
|
{
|
||||||
|
private ISkin currentBeatmapSkin;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
[Cached(typeof(HealthProcessor))]
|
||||||
|
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
|
{
|
||||||
|
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||||
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("setup skins", () =>
|
||||||
|
{
|
||||||
|
skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
|
||||||
|
currentBeatmapSkin = getBeatmapSkin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource)
|
||||||
|
{
|
||||||
|
var actualComponentsContainer = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target)
|
||||||
|
.ChildrenOfType<SkinnableTargetComponentsContainer>().SingleOrDefault();
|
||||||
|
|
||||||
|
if (actualComponentsContainer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
|
||||||
|
|
||||||
|
var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target));
|
||||||
|
if (expectedComponentsContainer == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var expectedComponentsAdjustmentContainer = new Container
|
||||||
|
{
|
||||||
|
Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
|
||||||
|
Size = actualComponentsContainer.DrawSize,
|
||||||
|
Child = expectedComponentsContainer,
|
||||||
|
};
|
||||||
|
|
||||||
|
Add(expectedComponentsAdjustmentContainer);
|
||||||
|
expectedComponentsAdjustmentContainer.UpdateSubTree();
|
||||||
|
var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
|
||||||
|
Remove(expectedComponentsAdjustmentContainer);
|
||||||
|
|
||||||
|
return almostEqual(actualInfo, expectedInfo);
|
||||||
|
|
||||||
|
static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
|
||||||
|
other != null
|
||||||
|
&& info.Type == other.Type
|
||||||
|
&& info.Anchor == other.Anchor
|
||||||
|
&& info.Origin == other.Origin
|
||||||
|
&& Precision.AlmostEquals(info.Position, other.Position)
|
||||||
|
&& Precision.AlmostEquals(info.Scale, other.Scale)
|
||||||
|
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
|
||||||
|
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
|
||||||
|
|
||||||
|
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly ISkin beatmapSkin;
|
||||||
|
|
||||||
|
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
|
||||||
|
: base(beatmap, storyboard, referenceClock, audio)
|
||||||
|
{
|
||||||
|
this.beatmapSkin = beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuRuleset : OsuRuleset
|
||||||
|
{
|
||||||
|
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source);
|
||||||
|
|
||||||
|
private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
|
||||||
|
{
|
||||||
|
public TestOsuLegacySkinTransformer(ISkinSource source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// 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 NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,29 +14,35 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Catch.Scoring;
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public class TestSceneHitErrorMeter : OsuTestScene
|
public class TestSceneHitErrorMeter : OsuTestScene
|
||||||
{
|
{
|
||||||
private HitWindows hitWindows;
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
[Cached(typeof(DrawableRuleset))]
|
||||||
|
private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset();
|
||||||
|
|
||||||
public TestSceneHitErrorMeter()
|
public TestSceneHitErrorMeter()
|
||||||
{
|
{
|
||||||
recreateDisplay(new OsuHitWindows(), 5);
|
recreateDisplay(new OsuHitWindows(), 5);
|
||||||
|
|
||||||
AddRepeatStep("New random judgement", () => newJudgement(), 40);
|
AddRepeatStep("New random judgement", () => newJudgement(), 40);
|
||||||
|
|
||||||
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
|
AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
|
||||||
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
|
AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
|
||||||
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
|
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
|
||||||
|
|
||||||
AddStep("Judgement barrage", () =>
|
AddStep("Judgement barrage", () =>
|
||||||
@ -83,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
||||||
{
|
{
|
||||||
this.hitWindows = hitWindows;
|
|
||||||
|
|
||||||
hitWindows?.SetDifficulty(overallDifficulty);
|
hitWindows?.SetDifficulty(overallDifficulty);
|
||||||
|
|
||||||
|
drawableRuleset.HitWindows = hitWindows;
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
Add(new FillFlowContainer
|
Add(new FillFlowContainer
|
||||||
@ -103,40 +112,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, true)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, false)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, true)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Rotation = 270,
|
Rotation = 270,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Margin = new MarginPadding { Right = 50 }
|
Margin = new MarginPadding { Right = 50 }
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Margin = new MarginPadding { Left = 50 }
|
Margin = new MarginPadding { Left = 50 }
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -147,11 +156,47 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void newJudgement(double offset = 0)
|
private void newJudgement(double offset = 0)
|
||||||
{
|
{
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
|
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement())
|
||||||
{
|
{
|
||||||
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
||||||
Type = HitResult.Perfect,
|
Type = HitResult.Perfect,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
|
||||||
|
private class TestDrawableRuleset : DrawableRuleset
|
||||||
|
{
|
||||||
|
public HitWindows HitWindows;
|
||||||
|
|
||||||
|
public override IEnumerable<HitObject> Objects => new[] { new HitCircle { HitWindows = HitWindows } };
|
||||||
|
|
||||||
|
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; }
|
||||||
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,13 +88,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
beforeLoadAction?.Invoke();
|
beforeLoadAction?.Invoke();
|
||||||
|
|
||||||
|
prepareBeatmap();
|
||||||
|
|
||||||
|
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepareBeatmap()
|
||||||
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
||||||
|
|
||||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||||
|
|
||||||
LoadScreen(loader = new TestPlayerLoader(() => player = new TestPlayer(interactive, interactive)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -178,10 +183,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("load slow dummy beatmap", () =>
|
AddStep("load slow dummy beatmap", () =>
|
||||||
{
|
{
|
||||||
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
|
prepareBeatmap();
|
||||||
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
|
slowPlayer = new SlowLoadPlayer(false, false);
|
||||||
|
LoadScreen(loader = new TestPlayerLoader(() => slowPlayer));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("schedule slow load", () => Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000));
|
||||||
|
|
||||||
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" };
|
private readonly User streamingUser = new User { Id = MultiplayerTestScene.PLAYER_1_ID, Username = "Test user" };
|
||||||
|
|
||||||
[Cached(typeof(SpectatorStreamingClient))]
|
[Cached(typeof(SpectatorClient))]
|
||||||
private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient();
|
private TestSpectatorClient testSpectatorClient = new TestSpectatorClient();
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
@ -61,8 +61,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
AddStep("add streaming client", () =>
|
AddStep("add streaming client", () =>
|
||||||
{
|
{
|
||||||
Remove(testSpectatorStreamingClient);
|
Remove(testSpectatorClient);
|
||||||
Add(testSpectatorStreamingClient);
|
Add(testSpectatorClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
private void waitForPlayer() => AddUntilStep("wait for player", () => Stack.CurrentScreen is Player);
|
||||||
|
|
||||||
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorStreamingClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void start(int? beatmapId = null) => AddStep("start play", () => testSpectatorClient.StartPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
||||||
|
|
||||||
private void finish(int? beatmapId = null) => AddStep("end play", () => testSpectatorStreamingClient.EndPlay(streamingUser.Id, beatmapId ?? importedBeatmapId));
|
private void finish() => AddStep("end play", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
||||||
|
|
||||||
private void checkPaused(bool state) =>
|
private void checkPaused(bool state) =>
|
||||||
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
AddUntilStep($"game is {(state ? "paused" : "playing")}", () => player.ChildrenOfType<DrawableRuleset>().First().IsPaused.Value == state);
|
||||||
@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
AddStep("send frames", () =>
|
AddStep("send frames", () =>
|
||||||
{
|
{
|
||||||
testSpectatorStreamingClient.SendFrames(streamingUser.Id, nextFrame, count);
|
testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count);
|
||||||
nextFrame += count;
|
nextFrame += count;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private SpectatorStreamingClient streamingClient { get; set; }
|
private SpectatorClient spectatorClient { get; set; }
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
|
private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
replay = new Replay();
|
replay = new Replay();
|
||||||
|
|
||||||
users.BindTo(streamingClient.PlayingUsers);
|
users.BindTo(spectatorClient.PlayingUsers);
|
||||||
users.BindCollectionChanged((obj, args) =>
|
users.BindCollectionChanged((obj, args) =>
|
||||||
{
|
{
|
||||||
switch (args.Action)
|
switch (args.Action)
|
||||||
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (int user in args.NewItems)
|
foreach (int user in args.NewItems)
|
||||||
{
|
{
|
||||||
if (user == api.LocalUser.Value.Id)
|
if (user == api.LocalUser.Value.Id)
|
||||||
streamingClient.WatchUser(user);
|
spectatorClient.WatchUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -91,14 +91,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
foreach (int user in args.OldItems)
|
foreach (int user in args.OldItems)
|
||||||
{
|
{
|
||||||
if (user == api.LocalUser.Value.Id)
|
if (user == api.LocalUser.Value.Id)
|
||||||
streamingClient.StopWatchingUser(user);
|
spectatorClient.StopWatchingUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
streamingClient.OnNewFrames += onNewFrames;
|
spectatorClient.OnNewFrames += onNewFrames;
|
||||||
|
|
||||||
Add(new GridContainer
|
Add(new GridContainer
|
||||||
{
|
{
|
||||||
@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS;
|
private double latency = SpectatorClient.TIME_BETWEEN_SENDS;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("stop recorder", () =>
|
AddStep("stop recorder", () =>
|
||||||
{
|
{
|
||||||
recorder.Expire();
|
recorder.Expire();
|
||||||
streamingClient.OnNewFrames -= onNewFrames;
|
spectatorClient.OnNewFrames -= onNewFrames;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
CreateTest(null);
|
CreateTest(null);
|
||||||
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
|
||||||
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
|
||||||
AddAssert("score shown", () => Player.IsScoreShown);
|
AddUntilStep("wait for score shown", () => Player.IsScoreShown);
|
||||||
|
AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -23,8 +23,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
|
public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
|
||||||
{
|
{
|
||||||
[Cached(typeof(SpectatorStreamingClient))]
|
[Cached(typeof(SpectatorClient))]
|
||||||
private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient();
|
private TestSpectatorClient spectatorClient = new TestSpectatorClient();
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
base.Content.AddRange(new Drawable[]
|
base.Content.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
streamingClient,
|
spectatorClient,
|
||||||
lookupCache,
|
lookupCache,
|
||||||
content = new Container { RelativeSizeAxes = Axes.Both }
|
content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
});
|
});
|
||||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
foreach (var (userId, clock) in clocks)
|
foreach (var (userId, clock) in clocks)
|
||||||
{
|
{
|
||||||
streamingClient.EndPlay(userId, 0);
|
spectatorClient.EndPlay(userId);
|
||||||
clock.CurrentTime = 0;
|
clock.CurrentTime = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
{
|
{
|
||||||
foreach (var (userId, _) in clocks)
|
foreach (var (userId, _) in clocks)
|
||||||
streamingClient.StartPlay(userId, 0);
|
spectatorClient.StartPlay(userId, 0);
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
@ -96,10 +96,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
// For player 2, send frames in sets of 10.
|
// For player 2, send frames in sets of 10.
|
||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
streamingClient.SendFrames(PLAYER_1_ID, i, 1);
|
spectatorClient.SendFrames(PLAYER_1_ID, i, 1);
|
||||||
|
|
||||||
if (i % 10 == 0)
|
if (i % 10 == 0)
|
||||||
streamingClient.SendFrames(PLAYER_2_ID, i, 10);
|
spectatorClient.SendFrames(PLAYER_2_ID, i, 10);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,8 +22,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
|
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
|
||||||
{
|
{
|
||||||
[Cached(typeof(SpectatorStreamingClient))]
|
[Cached(typeof(SpectatorClient))]
|
||||||
private TestSpectatorStreamingClient streamingClient = new TestSpectatorStreamingClient();
|
private TestSpectatorClient spectatorClient = new TestSpectatorClient();
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestUserLookupCache();
|
||||||
@ -59,14 +59,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddStep("add streaming client", () =>
|
AddStep("add streaming client", () =>
|
||||||
{
|
{
|
||||||
Remove(streamingClient);
|
Remove(spectatorClient);
|
||||||
Add(streamingClient);
|
Add(spectatorClient);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("finish previous gameplay", () =>
|
AddStep("finish previous gameplay", () =>
|
||||||
{
|
{
|
||||||
foreach (var id in playingUserIds)
|
foreach (var id in playingUserIds)
|
||||||
streamingClient.EndPlay(id, importedBeatmapId);
|
spectatorClient.EndPlay(id);
|
||||||
playingUserIds.Clear();
|
playingUserIds.Clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -87,11 +87,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
loadSpectateScreen(false);
|
loadSpectateScreen(false);
|
||||||
|
|
||||||
AddWaitStep("wait a bit", 10);
|
AddWaitStep("wait a bit", 10);
|
||||||
AddStep("load player first_player_id", () => streamingClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
|
AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
|
||||||
AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 1);
|
AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 1);
|
||||||
|
|
||||||
AddWaitStep("wait a bit", 10);
|
AddWaitStep("wait a bit", 10);
|
||||||
AddStep("load player second_player_id", () => streamingClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
|
AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
|
||||||
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 2);
|
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,18 +251,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
foreach (int id in userIds)
|
foreach (int id in userIds)
|
||||||
{
|
{
|
||||||
Client.CurrentMatchPlayingUserIds.Add(id);
|
Client.CurrentMatchPlayingUserIds.Add(id);
|
||||||
streamingClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
|
||||||
playingUserIds.Add(id);
|
playingUserIds.Add(id);
|
||||||
nextFrame[id] = 0;
|
nextFrame[id] = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finish(int userId, int? beatmapId = null)
|
private void finish(int userId)
|
||||||
{
|
{
|
||||||
AddStep("end play", () =>
|
AddStep("end play", () =>
|
||||||
{
|
{
|
||||||
streamingClient.EndPlay(userId, beatmapId ?? importedBeatmapId);
|
spectatorClient.EndPlay(userId);
|
||||||
playingUserIds.Remove(userId);
|
playingUserIds.Remove(userId);
|
||||||
nextFrame.Remove(userId);
|
nextFrame.Remove(userId);
|
||||||
});
|
});
|
||||||
@ -276,7 +276,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
foreach (int id in userIds)
|
foreach (int id in userIds)
|
||||||
{
|
{
|
||||||
streamingClient.SendFrames(id, nextFrame[id], count);
|
spectatorClient.SendFrames(id, nextFrame[id], count);
|
||||||
nextFrame[id] += count;
|
nextFrame[id] += count;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
|
||||||
{
|
{
|
||||||
[Cached(typeof(StatefulMultiplayerClient))]
|
[Cached(typeof(MultiplayerClient))]
|
||||||
public readonly TestMultiplayerClient Client;
|
public readonly TestMultiplayerClient Client;
|
||||||
|
|
||||||
public TestMultiplayer()
|
public TestMultiplayer()
|
||||||
|
@ -28,8 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private const int users = 16;
|
private const int users = 16;
|
||||||
|
|
||||||
[Cached(typeof(SpectatorStreamingClient))]
|
[Cached(typeof(SpectatorClient))]
|
||||||
private TestMultiplayerStreaming streamingClient = new TestMultiplayerStreaming();
|
private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient();
|
||||||
|
|
||||||
[Cached(typeof(UserLookupCache))]
|
[Cached(typeof(UserLookupCache))]
|
||||||
private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
|
private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
|
||||||
@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
base.Content.Children = new Drawable[]
|
base.Content.Children = new Drawable[]
|
||||||
{
|
{
|
||||||
streamingClient,
|
spectatorClient,
|
||||||
lookupCache,
|
lookupCache,
|
||||||
Content
|
Content
|
||||||
};
|
};
|
||||||
@ -71,10 +71,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
||||||
|
|
||||||
for (int i = 0; i < users; i++)
|
for (int i = 0; i < users; i++)
|
||||||
streamingClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
|
||||||
|
|
||||||
|
spectatorClient.Schedule(() =>
|
||||||
|
{
|
||||||
Client.CurrentMatchPlayingUserIds.Clear();
|
Client.CurrentMatchPlayingUserIds.Clear();
|
||||||
Client.CurrentMatchPlayingUserIds.AddRange(streamingClient.PlayingUsers);
|
Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
|
||||||
|
});
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -83,7 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
scoreProcessor.ApplyBeatmap(playable);
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, streamingClient.PlayingUsers.ToArray())
|
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -91,12 +94,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||||
|
AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScoreUpdates()
|
public void TestScoreUpdates()
|
||||||
{
|
{
|
||||||
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 100);
|
AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100);
|
||||||
AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
|
AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,12 +113,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestChangeScoringMode()
|
public void TestChangeScoringMode()
|
||||||
{
|
{
|
||||||
AddRepeatStep("update state", () => streamingClient.RandomlyUpdateState(), 5);
|
AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5);
|
||||||
AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic));
|
AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic));
|
||||||
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
|
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestMultiplayerStreaming : TestSpectatorStreamingClient
|
public class TestMultiplayerSpectatorClient : TestSpectatorClient
|
||||||
{
|
{
|
||||||
private readonly Dictionary<int, FrameHeader> lastHeaders = new Dictionary<int, FrameHeader>();
|
private readonly Dictionary<int, FrameHeader> lastHeaders = new Dictionary<int, FrameHeader>();
|
||||||
|
|
||||||
|
@ -11,8 +11,8 @@ using osu.Game.Overlays;
|
|||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Select;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
{
|
{
|
||||||
@ -37,17 +37,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtSongSelect()
|
public void TestPerformAtSongSelect()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenuFromSongSelect()
|
public void TestPerformAtMenuFromSongSelect()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||||
@ -57,18 +57,18 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtSongSelectFromPlayerLoader()
|
public void TestPerformAtSongSelectFromPlayerLoader()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(PlaySongSelect) }));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||||
AddAssert("did perform", () => actionPerformed);
|
AddAssert("did perform", () => actionPerformed);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPerformAtMenuFromPlayerLoader()
|
public void TestPerformAtMenuFromPlayerLoader()
|
||||||
{
|
{
|
||||||
PushAndConfirm(() => new PlaySongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
|
||||||
|
|
||||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||||
|
@ -34,9 +34,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitSongSelectWithEscape()
|
public void TestExitSongSelectWithEscape()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
pushEscape();
|
pushEscape();
|
||||||
@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestOpenModSelectOverlayUsingAction()
|
public void TestOpenModSelectOverlayUsingAction()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
Player player = null;
|
Player player = null;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
|
|
||||||
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
WorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||||
|
|
||||||
PushAndConfirm(() => new TestSongSelect());
|
PushAndConfirm(() => new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait());
|
||||||
|
|
||||||
@ -139,9 +139,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestMenuMakesMusic()
|
public void TestMenuMakesMusic()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
||||||
|
|
||||||
@ -153,9 +153,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestExitSongSelectWithClick()
|
public void TestExitSongSelectWithClick()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||||
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
|
||||||
@ -213,9 +213,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestModSelectInput()
|
public void TestModSelectInput()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||||
|
|
||||||
@ -234,9 +234,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestBeatmapOptionsInput()
|
public void TestBeatmapOptionsInput()
|
||||||
{
|
{
|
||||||
TestSongSelect songSelect = null;
|
TestPlaySongSelect songSelect = null;
|
||||||
|
|
||||||
PushAndConfirm(() => songSelect = new TestSongSelect());
|
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||||
|
|
||||||
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
||||||
|
|
||||||
@ -312,11 +312,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
ConfirmAtMainMenu();
|
ConfirmAtMainMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestSongSelect : PlaySongSelect
|
public class TestPlaySongSelect : PlaySongSelect
|
||||||
{
|
{
|
||||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||||
|
|
||||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||||
|
|
||||||
|
protected override bool DisplayStableImportPrompt => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.AccountCreation;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
@ -36,8 +40,6 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
API.Logout();
|
|
||||||
|
|
||||||
localUser = API.LocalUser.GetBoundCopy();
|
localUser = API.LocalUser.GetBoundCopy();
|
||||||
localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true);
|
localUser.BindValueChanged(user => { userPanelArea.Child = new UserGridPanel(user.NewValue) { Width = 200 }; }, true);
|
||||||
}
|
}
|
||||||
@ -46,11 +48,14 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
public void TestOverlayVisibility()
|
public void TestOverlayVisibility()
|
||||||
{
|
{
|
||||||
AddStep("start hidden", () => accountCreation.Hide());
|
AddStep("start hidden", () => accountCreation.Hide());
|
||||||
AddStep("log out", API.Logout);
|
AddStep("log out", () => API.Logout());
|
||||||
|
|
||||||
AddStep("show manually", () => accountCreation.Show());
|
AddStep("show manually", () => accountCreation.Show());
|
||||||
AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
|
AddUntilStep("overlay is visible", () => accountCreation.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("click button", () => accountCreation.ChildrenOfType<SettingsButton>().Single().Click());
|
||||||
|
AddUntilStep("warning screen is present", () => accountCreation.ChildrenOfType<ScreenWarning>().SingleOrDefault()?.IsPresent == true);
|
||||||
|
|
||||||
AddStep("log back in", () => API.Login("dummy", "password"));
|
AddStep("log back in", () => API.Login("dummy", "password"));
|
||||||
AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden);
|
AddUntilStep("overlay is hidden", () => accountCreation.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
public class TestSceneCurrentlyPlayingDisplay : OsuTestScene
|
public class TestSceneCurrentlyPlayingDisplay : OsuTestScene
|
||||||
{
|
{
|
||||||
[Cached(typeof(SpectatorStreamingClient))]
|
private readonly User streamingUser = new User { Id = 2, Username = "Test user" };
|
||||||
private TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSpectatorStreamingClient();
|
|
||||||
|
[Cached(typeof(SpectatorClient))]
|
||||||
|
private TestSpectatorClient testSpectatorClient = new TestSpectatorClient();
|
||||||
|
|
||||||
private CurrentlyPlayingDisplay currentlyPlaying;
|
private CurrentlyPlayingDisplay currentlyPlaying;
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
AddStep("add streaming client", () =>
|
AddStep("add streaming client", () =>
|
||||||
{
|
{
|
||||||
nestedContainer?.Remove(testSpectatorStreamingClient);
|
nestedContainer?.Remove(testSpectatorClient);
|
||||||
Remove(lookupCache);
|
Remove(lookupCache);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -45,7 +47,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
testSpectatorStreamingClient,
|
testSpectatorClient,
|
||||||
currentlyPlaying = new CurrentlyPlayingDisplay
|
currentlyPlaying = new CurrentlyPlayingDisplay
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -55,15 +57,15 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Reset players", () => testSpectatorStreamingClient.PlayingUsers.Clear());
|
AddStep("Reset players", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasicDisplay()
|
public void TestBasicDisplay()
|
||||||
{
|
{
|
||||||
AddStep("Add playing user", () => testSpectatorStreamingClient.PlayingUsers.Add(2));
|
AddStep("Add playing user", () => testSpectatorClient.StartPlay(streamingUser.Id, 0));
|
||||||
AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType<UserGridPanel>()?.FirstOrDefault()?.User.Id == 2);
|
AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType<UserGridPanel>()?.FirstOrDefault()?.User.Id == 2);
|
||||||
AddStep("Remove playing user", () => testSpectatorStreamingClient.PlayingUsers.Remove(2));
|
AddStep("Remove playing user", () => testSpectatorClient.EndPlay(streamingUser.Id));
|
||||||
AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType<UserGridPanel>().Any());
|
AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType<UserGridPanel>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.News.Sidebar;
|
||||||
|
using static osu.Game.Overlays.News.Sidebar.YearsPanel;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneNewsSidebar : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
private TestNewsSidebar sidebar;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMetadataWithNoPosts()
|
||||||
|
{
|
||||||
|
AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts);
|
||||||
|
AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestYearsPanelVisibility()
|
||||||
|
{
|
||||||
|
AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0);
|
||||||
|
AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year);
|
||||||
|
|
||||||
|
private YearsPanel yearsPanel => sidebar.ChildrenOfType<YearsPanel>().FirstOrDefault();
|
||||||
|
|
||||||
|
private APINewsSidebar getMetadata(int year) => new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = year,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = new List<APINewsPost>
|
||||||
|
{
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Short title",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = 2021,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = Array.Empty<APINewsPost>()
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestNewsSidebar : NewsSidebar
|
||||||
|
{
|
||||||
|
public Action<int> YearChanged;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Metadata.BindValueChanged(metadata =>
|
||||||
|
{
|
||||||
|
foreach (var b in this.ChildrenOfType<YearButton>())
|
||||||
|
b.Action = () => YearChanged?.Invoke(b.Year);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
43
osu.Game.Tests/Visual/Online/TestSceneWikiMainPage.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Wiki;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneWikiMainPage : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||||
|
|
||||||
|
public TestSceneWikiMainPage()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = overlayColour.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new BasicScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Child = new WikiMainPage
|
||||||
|
{
|
||||||
|
Markdown = main_page_markdown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// From https://osu.ppy.sh/api/v2/wiki/en/Main_Page
|
||||||
|
private const string main_page_markdown =
|
||||||
|
"---\nlayout: main_page\n---\n\n<!-- Do not add any empty lines inside this div. -->\n\n<div class=\"wiki-main-page__blurb\">\nWelcome to the osu! wiki, a project containing a wide range of osu! related information.\n</div>\n\n<div class=\"wiki-main-page__panels\">\n<div class=\"wiki-main-page-panel wiki-main-page-panel--full\">\n\n# Getting started\n\n[Welcome](/wiki/Welcome) • [Installation](/wiki/Installation) • [Registration](/wiki/Registration) • [Help Centre](/wiki/Help_Centre) • [FAQ](/wiki/FAQ)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Game client\n\n[Interface](/wiki/Interface) • [Options](/wiki/Options) • [Visual settings](/wiki/Visual_Settings) • [Shortcut key reference](/wiki/Shortcut_key_reference) • [Configuration file](/wiki/osu!_Program_Files/User_Configuration_File) • [Program files](/wiki/osu!_Program_Files)\n\n[File formats](/wiki/osu!_File_Formats): [.osz](/wiki/osu!_File_Formats/Osz_(file_format)) • [.osk](/wiki/osu!_File_Formats/Osk_(file_format)) • [.osr](/wiki/osu!_File_Formats/Osr_(file_format)) • [.osu](/wiki/osu!_File_Formats/Osu_(file_format)) • [.osb](/wiki/osu!_File_Formats/Osb_(file_format)) • [.db](/wiki/osu!_File_Formats/Db_(file_format))\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Gameplay\n\n[Game modes](/wiki/Game_mode): [osu!](/wiki/Game_mode/osu!) • [osu!taiko](/wiki/Game_mode/osu!taiko) • [osu!catch](/wiki/Game_mode/osu!catch) • [osu!mania](/wiki/Game_mode/osu!mania)\n\n[Beatmap](/wiki/Beatmap) • [Hit object](/wiki/Hit_object) • [Mods](/wiki/Game_modifier) • [Score](/wiki/Score) • [Replay](/wiki/Replay) • [Multi](/wiki/Multi)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# [Beatmap editor](/wiki/Beatmap_Editor)\n\nSections: [Compose](/wiki/Beatmap_Editor/Compose) • [Design](/wiki/Beatmap_Editor/Design) • [Timing](/wiki/Beatmap_Editor/Timing) • [Song setup](/wiki/Beatmap_Editor/Song_Setup)\n\nComponents: [AiMod](/wiki/Beatmap_Editor/AiMod) • [Beat snap divisor](/wiki/Beatmap_Editor/Beat_Snap_Divisor) • [Distance snap](/wiki/Beatmap_Editor/Distance_Snap) • [Menu](/wiki/Beatmap_Editor/Menu) • [SB load](/wiki/Beatmap_Editor/SB_Load) • [Timelines](/wiki/Beatmap_Editor/Timelines)\n\n[Beatmapping](/wiki/Beatmapping) • [Difficulty](/wiki/Beatmap/Difficulty) • [Mapping techniques](/wiki/Mapping_Techniques) • [Storyboarding](/wiki/Storyboarding)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Beatmap submission and ranking\n\n[Submission](/wiki/Submission) • [Modding](/wiki/Modding) • [Ranking procedure](/wiki/Beatmap_ranking_procedure) • [Mappers' Guild](/wiki/Mappers_Guild) • [Project Loved](/wiki/Project_Loved)\n\n[Ranking criteria](/wiki/Ranking_Criteria): [osu!](/wiki/Ranking_Criteria/osu!) • [osu!taiko](/wiki/Ranking_Criteria/osu!taiko) • [osu!catch](/wiki/Ranking_Criteria/osu!catch) • [osu!mania](/wiki/Ranking_Criteria/osu!mania)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# Community\n\n[Tournaments](/wiki/Tournaments) • [Skinning](/wiki/Skinning) • [Projects](/wiki/Projects) • [Guides](/wiki/Guides) • [osu!dev Discord server](/wiki/osu!dev_Discord_server) • [How you can help](/wiki/How_You_Can_Help!) • [Glossary](/wiki/Glossary)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# People\n\n[The Team](/wiki/People/The_Team): [Developers](/wiki/People/The_Team/Developers) • [Global Moderation Team](/wiki/People/The_Team/Global_Moderation_Team) • [Support Team](/wiki/People/The_Team/Support_Team) • [Nomination Assessment Team](/wiki/People/The_Team/Nomination_Assessment_Team) • [Beatmap Nominators](/wiki/People/The_Team/Beatmap_Nominators) • [osu! Alumni](/wiki/People/The_Team/osu!_Alumni) • [Project Loved Team](/wiki/People/The_Team/Project_Loved_Team)\n\nOrganisations: [osu! UCI](/wiki/Organisations/osu!_UCI)\n\n[Community Contributors](/wiki/People/Community_Contributors) • [Users with unique titles](/wiki/People/Users_with_unique_titles)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# For developers\n\n[API](/wiki/osu!api) • [Bot account](/wiki/Bot_account) • [Brand identity guidelines](/wiki/Brand_identity_guidelines)\n\n</div>\n<div class=\"wiki-main-page-panel\">\n\n# About the wiki\n\n[Sitemap](/wiki/Sitemap) • [Contribution guide](/wiki/osu!_wiki_Contribution_Guide) • [Article styling criteria](/wiki/Article_Styling_Criteria) • [News styling criteria](/wiki/News_Styling_Criteria)\n\n</div>\n</div>\n";
|
||||||
|
}
|
||||||
|
}
|
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
175
osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
// 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 Markdig.Syntax.Inlines;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics.Containers.Markdown;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Wiki.Markdown;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneWikiMarkdownContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private TestMarkdownContainer markdownContainer;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Orange);
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = overlayColour.Background5,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
new BasicScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding(20),
|
||||||
|
Child = markdownContainer = new TestMarkdownContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLink()
|
||||||
|
{
|
||||||
|
AddStep("set current path", () => markdownContainer.CurrentPath = "Article_styling_criteria/");
|
||||||
|
|
||||||
|
AddStep("set '/wiki/Main_Page''", () => markdownContainer.Text = "[wiki main page](/wiki/Main_Page)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Main_Page");
|
||||||
|
|
||||||
|
AddStep("set '../FAQ''", () => markdownContainer.Text = "[FAQ](../FAQ)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/FAQ");
|
||||||
|
|
||||||
|
AddStep("set './Writing''", () => markdownContainer.Text = "[wiki writing guidline](./Writing)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Writing");
|
||||||
|
|
||||||
|
AddStep("set 'Formatting''", () => markdownContainer.Text = "[wiki formatting guidline](Formatting)");
|
||||||
|
AddAssert("check url", () => markdownContainer.Link.Url == $"{API.WebsiteRootUrl}/wiki/Article_styling_criteria/Formatting");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOutdatedNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add outdated yaml header", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
outdated: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNeedsCleanupNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add needs cleanup yaml header", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
needs_cleanup: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOnlyShowOutdatedNoticeBox()
|
||||||
|
{
|
||||||
|
AddStep("Add outdated and needs cleanup yaml", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = @"---
|
||||||
|
outdated: true
|
||||||
|
needs_cleanup: true
|
||||||
|
---";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAbsoluteImage()
|
||||||
|
{
|
||||||
|
AddStep("Add absolute image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRelativeImage()
|
||||||
|
{
|
||||||
|
AddStep("Add relative image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.CurrentPath = "Interface/";
|
||||||
|
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBlockImage()
|
||||||
|
{
|
||||||
|
AddStep("Add paragraph with block image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.CurrentPath = "Interface/";
|
||||||
|
markdownContainer.Text = @"Line before image
|
||||||
|
|
||||||
|
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
|
||||||
|
|
||||||
|
Line after image";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInlineImage()
|
||||||
|
{
|
||||||
|
AddStep("Add inline image", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.DocumentUrl = "https://dev.ppy.sh";
|
||||||
|
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestMarkdownContainer : WikiMarkdownContainer
|
||||||
|
{
|
||||||
|
public LinkInline Link;
|
||||||
|
|
||||||
|
public new string DocumentUrl
|
||||||
|
{
|
||||||
|
set => base.DocumentUrl = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
|
||||||
|
{
|
||||||
|
UrlAdded = link => Link = link,
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer
|
||||||
|
{
|
||||||
|
public Action<LinkInline> UrlAdded;
|
||||||
|
|
||||||
|
protected override void AddLinkText(string text, LinkInline linkInline)
|
||||||
|
{
|
||||||
|
base.AddLinkText(text, linkInline);
|
||||||
|
|
||||||
|
UrlAdded?.Invoke(linkInline);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void AddImage(LinkInline linkInline) => AddDrawable(new WikiMarkdownImage(linkInline));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,82 +22,17 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
{
|
{
|
||||||
public class TestSceneAccuracyCircle : OsuTestScene
|
public class TestSceneAccuracyCircle : OsuTestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[TestCase(0.2, ScoreRank.D)]
|
||||||
public void TestLowDRank()
|
[TestCase(0.5, ScoreRank.D)]
|
||||||
|
[TestCase(0.75, ScoreRank.C)]
|
||||||
|
[TestCase(0.85, ScoreRank.B)]
|
||||||
|
[TestCase(0.925, ScoreRank.A)]
|
||||||
|
[TestCase(0.975, ScoreRank.S)]
|
||||||
|
[TestCase(0.9999, ScoreRank.S)]
|
||||||
|
[TestCase(1, ScoreRank.X)]
|
||||||
|
public void TestRank(double accuracy, ScoreRank rank)
|
||||||
{
|
{
|
||||||
var score = createScore();
|
var score = createScore(accuracy, rank);
|
||||||
score.Accuracy = 0.2;
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestDRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.5;
|
|
||||||
score.Rank = ScoreRank.D;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestCRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.75;
|
|
||||||
score.Rank = ScoreRank.C;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestBRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.85;
|
|
||||||
score.Rank = ScoreRank.B;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestARank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.925;
|
|
||||||
score.Rank = ScoreRank.A;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.975;
|
|
||||||
score.Rank = ScoreRank.S;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestAlmostSSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 0.9999;
|
|
||||||
score.Rank = ScoreRank.S;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestSSRank()
|
|
||||||
{
|
|
||||||
var score = createScore();
|
|
||||||
score.Accuracy = 1;
|
|
||||||
score.Rank = ScoreRank.X;
|
|
||||||
|
|
||||||
addCircleStep(score);
|
addCircleStep(score);
|
||||||
}
|
}
|
||||||
@ -120,7 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new AccuracyCircle(score, true)
|
new AccuracyCircle(score)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -129,7 +64,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
private ScoreInfo createScore() => new ScoreInfo
|
private ScoreInfo createScore(double accuracy, ScoreRank rank) => new ScoreInfo
|
||||||
{
|
{
|
||||||
User = new User
|
User = new User
|
||||||
{
|
{
|
||||||
@ -139,9 +74,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
|
||||||
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
Accuracy = 0.95,
|
Accuracy = accuracy,
|
||||||
MaxCombo = 999,
|
MaxCombo = 999,
|
||||||
Rank = ScoreRank.S,
|
Rank = rank,
|
||||||
Date = DateTimeOffset.Now,
|
Date = DateTimeOffset.Now,
|
||||||
Statistics =
|
Statistics =
|
||||||
{
|
{
|
||||||
|
@ -29,13 +29,8 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneResultsScreen : OsuManualInputManagerTestScene
|
public class TestSceneResultsScreen : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private BeatmapManager beatmaps;
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(BeatmapManager beatmaps)
|
|
||||||
{
|
|
||||||
this.beatmaps = beatmaps;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -46,10 +41,6 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
|
||||||
|
|
||||||
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestResultsWithoutPlayer()
|
public void TestResultsWithoutPlayer()
|
||||||
{
|
{
|
||||||
@ -69,12 +60,25 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
AddAssert("retry overlay not present", () => screen.RetryOverlay == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase(0.2, ScoreRank.D)]
|
||||||
public void TestResultsWithPlayer()
|
[TestCase(0.5, ScoreRank.D)]
|
||||||
|
[TestCase(0.75, ScoreRank.C)]
|
||||||
|
[TestCase(0.85, ScoreRank.B)]
|
||||||
|
[TestCase(0.925, ScoreRank.A)]
|
||||||
|
[TestCase(0.975, ScoreRank.S)]
|
||||||
|
[TestCase(0.9999, ScoreRank.S)]
|
||||||
|
[TestCase(1, ScoreRank.X)]
|
||||||
|
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
|
||||||
{
|
{
|
||||||
TestResultsScreen screen = null;
|
TestResultsScreen screen = null;
|
||||||
|
|
||||||
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
|
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
Accuracy = accuracy,
|
||||||
|
Rank = rank
|
||||||
|
};
|
||||||
|
|
||||||
|
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score)));
|
||||||
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
AddUntilStep("wait for loaded", () => screen.IsLoaded);
|
||||||
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
|
||||||
}
|
}
|
||||||
@ -232,6 +236,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
AddAssert("download button is enabled", () => screen.ChildrenOfType<DownloadButton>().Last().Enabled.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
|
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
|
||||||
|
|
||||||
private class TestResultsContainer : Container
|
private class TestResultsContainer : Container
|
||||||
{
|
{
|
||||||
[Cached(typeof(Player))]
|
[Cached(typeof(Player))]
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.KeyBinding;
|
using osu.Game.Overlays.KeyBinding;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -28,6 +29,39 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
panel.Show();
|
panel.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Scroll to top", () => panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollToTop());
|
||||||
|
AddWaitStep("wait for scroll", 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBindingMouseWheelToNonGameplay()
|
||||||
|
{
|
||||||
|
scrollToAndStartBinding("Increase volume");
|
||||||
|
AddStep("press k", () => InputManager.Key(Key.K));
|
||||||
|
checkBinding("Increase volume", "K");
|
||||||
|
|
||||||
|
AddStep("click again", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1));
|
||||||
|
|
||||||
|
checkBinding("Increase volume", "Wheel Up");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBindingMouseWheelToGameplay()
|
||||||
|
{
|
||||||
|
scrollToAndStartBinding("Left button");
|
||||||
|
AddStep("press k", () => InputManager.Key(Key.Z));
|
||||||
|
checkBinding("Left button", "Z");
|
||||||
|
|
||||||
|
AddStep("click again", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("scroll mouse wheel", () => InputManager.ScrollVerticalBy(1));
|
||||||
|
|
||||||
|
checkBinding("Left button", "Z");
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClickTwiceOnClearButton()
|
public void TestClickTwiceOnClearButton()
|
||||||
{
|
{
|
||||||
@ -36,6 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
AddStep("click first row", () =>
|
AddStep("click first row", () =>
|
||||||
{
|
{
|
||||||
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
InputManager.MoveMouseTo(firstRow);
|
InputManager.MoveMouseTo(firstRow);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
@ -104,6 +139,64 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleBindingResetButton()
|
||||||
|
{
|
||||||
|
KeyBindingRow settingsKeyBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click first row", () =>
|
||||||
|
{
|
||||||
|
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.PressKey(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.P);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||||
|
|
||||||
|
AddStep("click reset button for bindings", () =>
|
||||||
|
{
|
||||||
|
var resetButton = settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First();
|
||||||
|
|
||||||
|
resetButton.Click();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
|
|
||||||
|
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetAllBindingsButton()
|
||||||
|
{
|
||||||
|
KeyBindingRow settingsKeyBindingRow = null;
|
||||||
|
|
||||||
|
AddStep("click first row", () =>
|
||||||
|
{
|
||||||
|
settingsKeyBindingRow = panel.ChildrenOfType<KeyBindingRow>().First();
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(settingsKeyBindingRow);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.PressKey(Key.P);
|
||||||
|
InputManager.ReleaseKey(Key.P);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha > 0);
|
||||||
|
|
||||||
|
AddStep("click reset button for bindings", () =>
|
||||||
|
{
|
||||||
|
var resetButton = panel.ChildrenOfType<ResetButton>().First();
|
||||||
|
|
||||||
|
resetButton.Click();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
|
||||||
|
|
||||||
|
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestClickRowSelectsFirstBinding()
|
public void TestClickRowSelectsFirstBinding()
|
||||||
{
|
{
|
||||||
@ -135,5 +228,37 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
|
|
||||||
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().First().IsBinding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkBinding(string name, string keyName)
|
||||||
|
{
|
||||||
|
AddAssert($"Check {name} is bound to {keyName}", () =>
|
||||||
|
{
|
||||||
|
var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text == name));
|
||||||
|
var firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First();
|
||||||
|
|
||||||
|
return firstButton.Text.Text == keyName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scrollToAndStartBinding(string name)
|
||||||
|
{
|
||||||
|
KeyBindingRow.KeyButton firstButton = null;
|
||||||
|
|
||||||
|
AddStep($"Scroll to {name}", () =>
|
||||||
|
{
|
||||||
|
var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text == name));
|
||||||
|
firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First();
|
||||||
|
|
||||||
|
panel.ChildrenOfType<SettingsPanel.SettingsSectionsContainer>().First().ScrollTo(firstButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait for scroll", 5);
|
||||||
|
|
||||||
|
AddStep("click to bind", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(firstButton);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Settings
|
namespace osu.Game.Tests.Visual.Settings
|
||||||
{
|
{
|
||||||
@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
|
|
||||||
private class TestSettingsTextBox : SettingsTextBox
|
private class TestSettingsTextBox : SettingsTextBox
|
||||||
{
|
{
|
||||||
public new Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton>().Single();
|
public Drawable RestoreDefaultValueButton => this.ChildrenOfType<RestoreDefaultValueButton<string>>().Single();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -786,9 +786,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkVisibleItemCount(bool diff, int count) =>
|
private void checkVisibleItemCount(bool diff, int count)
|
||||||
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
{
|
||||||
|
// until step required as we are querying against alive items, which are loaded asynchronously inside DrawableCarouselBeatmapSet.
|
||||||
|
AddUntilStep($"{count} {(diff ? "diffs" : "sets")} visible", () =>
|
||||||
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
|
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
private void checkNoSelection() => AddAssert("Selection is null", () => currentSelection == null);
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -390,7 +390,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
|
||||||
|
|
||||||
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
|
||||||
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo == target);
|
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
|
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
|
||||||
|
|
||||||
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo == groupIcon.Items.First().Beatmap);
|
AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneBreadcrumbControlHeader : OsuTestScene
|
||||||
|
{
|
||||||
|
private static readonly string[] items = { "first", "second", "third", "fourth", "fifth" };
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
|
||||||
|
|
||||||
|
private TestHeader header;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = header = new TestHeader
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddAndRemoveItem()
|
||||||
|
{
|
||||||
|
foreach (var item in items.Skip(1))
|
||||||
|
AddStep($"Add {item} item", () => header.AddItem(item));
|
||||||
|
|
||||||
|
foreach (var item in items.Reverse().SkipLast(3))
|
||||||
|
AddStep($"Remove {item} item", () => header.RemoveItem(item));
|
||||||
|
|
||||||
|
AddStep("Clear items", () => header.ClearItems());
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
AddStep($"Add {item} item", () => header.AddItem(item));
|
||||||
|
|
||||||
|
foreach (var item in items)
|
||||||
|
AddStep($"Remove {item} item", () => header.RemoveItem(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestHeader : BreadcrumbControlOverlayHeader
|
||||||
|
{
|
||||||
|
public TestHeader()
|
||||||
|
{
|
||||||
|
TabControl.AddItem(items[0]);
|
||||||
|
Current.Value = items[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddItem(string value)
|
||||||
|
{
|
||||||
|
TabControl.AddItem(value);
|
||||||
|
Current.Value = TabControl.Items.LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveItem(string value)
|
||||||
|
{
|
||||||
|
TabControl.RemoveItem(value);
|
||||||
|
Current.Value = TabControl.Items.LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearItems()
|
||||||
|
{
|
||||||
|
TabControl.Clear();
|
||||||
|
Current.Value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override OverlayTitle CreateTitle() => new TestTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTitle : OverlayTitle
|
||||||
|
{
|
||||||
|
public TestTitle()
|
||||||
|
{
|
||||||
|
Title = "Test Title";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,9 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Dialog;
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
@ -11,13 +13,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneDialogOverlay : OsuTestScene
|
public class TestSceneDialogOverlay : OsuTestScene
|
||||||
{
|
{
|
||||||
public TestSceneDialogOverlay()
|
private DialogOverlay overlay;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
DialogOverlay overlay;
|
AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay());
|
||||||
|
}
|
||||||
|
|
||||||
Add(overlay = new DialogOverlay());
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
TestPopupDialog dialog = null;
|
||||||
|
|
||||||
AddStep("dialog #1", () => overlay.Push(new TestPopupDialog
|
AddStep("dialog #1", () => overlay.Push(dialog = new TestPopupDialog
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Regular.TrashAlt,
|
Icon = FontAwesome.Regular.TrashAlt,
|
||||||
HeaderText = @"Confirm deletion of",
|
HeaderText = @"Confirm deletion of",
|
||||||
@ -37,7 +46,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AddStep("dialog #2", () => overlay.Push(new TestPopupDialog
|
AddAssert("first dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||||
|
|
||||||
|
AddStep("dialog #2", () => overlay.Push(dialog = new TestPopupDialog
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.Cog,
|
Icon = FontAwesome.Solid.Cog,
|
||||||
HeaderText = @"What do you want to do with",
|
HeaderText = @"What do you want to do with",
|
||||||
@ -70,6 +81,42 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
AddAssert("second dialog displayed", () => overlay.CurrentDialog == dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDismissBeforePush()
|
||||||
|
{
|
||||||
|
AddStep("dismissed dialog push", () =>
|
||||||
|
{
|
||||||
|
overlay.Push(new TestPopupDialog
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Hidden }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDismissBeforePushViaButtonPress()
|
||||||
|
{
|
||||||
|
AddStep("dismissed dialog push", () =>
|
||||||
|
{
|
||||||
|
TestPopupDialog dialog;
|
||||||
|
overlay.Push(dialog = new TestPopupDialog
|
||||||
|
{
|
||||||
|
Buttons = new PopupDialogButton[]
|
||||||
|
{
|
||||||
|
new PopupDialogOkButton { Text = @"OK" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.PerformOkAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no dialog pushed", () => overlay.CurrentDialog == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestPopupDialog : PopupDialog
|
private class TestPopupDialog : PopupDialog
|
||||||
|
@ -86,6 +86,15 @@ _**italic with underscore, bold with asterisk**_";
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLinkWithTitle()
|
||||||
|
{
|
||||||
|
AddStep("Add Link with title", () =>
|
||||||
|
{
|
||||||
|
markdownContainer.Text = "[wikipedia](https://www.wikipedia.org \"The Free Encyclopedia\")";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestInlineCode()
|
public void TestInlineCode()
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene
|
public class TestSceneStatefulMenuItem : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTernaryMenuItem()
|
public void TestTernaryRadioMenuItem()
|
||||||
{
|
{
|
||||||
OsuMenu menu = null;
|
OsuMenu menu = null;
|
||||||
|
|
||||||
@ -30,9 +30,57 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Items = new[]
|
Items = new[]
|
||||||
{
|
{
|
||||||
new TernaryStateMenuItem("First"),
|
new TernaryStateRadioMenuItem("First"),
|
||||||
new TernaryStateMenuItem("Second") { State = { BindTarget = state } },
|
new TernaryStateRadioMenuItem("Second") { State = { BindTarget = state } },
|
||||||
new TernaryStateMenuItem("Third") { State = { Value = TernaryState.True } },
|
new TernaryStateRadioMenuItem("Third") { State = { Value = TernaryState.True } },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
checkState(TernaryState.Indeterminate);
|
||||||
|
|
||||||
|
click();
|
||||||
|
checkState(TernaryState.True);
|
||||||
|
|
||||||
|
click();
|
||||||
|
checkState(TernaryState.True);
|
||||||
|
|
||||||
|
click();
|
||||||
|
checkState(TernaryState.True);
|
||||||
|
|
||||||
|
AddStep("change state via bindable", () => state.Value = TernaryState.True);
|
||||||
|
|
||||||
|
void click() =>
|
||||||
|
AddStep("click", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(menu.ScreenSpaceDrawQuad.Centre);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
void checkState(TernaryState expected)
|
||||||
|
=> AddAssert($"state is {expected}", () => state.Value == expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTernaryToggleMenuItem()
|
||||||
|
{
|
||||||
|
OsuMenu menu = null;
|
||||||
|
|
||||||
|
Bindable<TernaryState> state = new Bindable<TernaryState>(TernaryState.Indeterminate);
|
||||||
|
|
||||||
|
AddStep("create menu", () =>
|
||||||
|
{
|
||||||
|
state.Value = TernaryState.Indeterminate;
|
||||||
|
|
||||||
|
Child = menu = new OsuMenu(Direction.Vertical, true)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Items = new[]
|
||||||
|
{
|
||||||
|
new TernaryStateToggleMenuItem("First"),
|
||||||
|
new TernaryStateToggleMenuItem("Second") { State = { BindTarget = state } },
|
||||||
|
new TernaryStateToggleMenuItem("Third") { State = { Value = TernaryState.True } },
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
// 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 System.Threading;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Drawables;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneUpdateableBeatmapSetCover : OsuTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestLocal([Values] BeatmapSetCoverType coverType)
|
||||||
|
{
|
||||||
|
AddStep("setup cover", () => Child = new UpdateableBeatmapSetCover(coverType)
|
||||||
|
{
|
||||||
|
BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for load", () => this.ChildrenOfType<BeatmapSetCover>().SingleOrDefault()?.IsLoaded ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUnloadAndReload()
|
||||||
|
{
|
||||||
|
OsuScrollContainer scroll = null;
|
||||||
|
List<UpdateableBeatmapSetCover> covers = new List<UpdateableBeatmapSetCover>();
|
||||||
|
|
||||||
|
AddStep("setup covers", () =>
|
||||||
|
{
|
||||||
|
BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
|
||||||
|
|
||||||
|
FillFlowContainer fillFlow;
|
||||||
|
|
||||||
|
Child = scroll = new OsuScrollContainer
|
||||||
|
{
|
||||||
|
Size = new Vector2(500f),
|
||||||
|
Child = fillFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(10),
|
||||||
|
Padding = new MarginPadding { Bottom = 550 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var coverTypes = Enum.GetValues(typeof(BeatmapSetCoverType))
|
||||||
|
.Cast<BeatmapSetCoverType>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < 25; i++)
|
||||||
|
{
|
||||||
|
var coverType = coverTypes[i % coverTypes.Count];
|
||||||
|
|
||||||
|
var cover = new UpdateableBeatmapSetCover(coverType)
|
||||||
|
{
|
||||||
|
BeatmapSet = setInfo,
|
||||||
|
Height = 100,
|
||||||
|
Masking = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (coverType == BeatmapSetCoverType.Cover)
|
||||||
|
cover.Width = 500;
|
||||||
|
else if (coverType == BeatmapSetCoverType.Card)
|
||||||
|
cover.Width = 400;
|
||||||
|
else if (coverType == BeatmapSetCoverType.List)
|
||||||
|
cover.Size = new Vector2(100, 50);
|
||||||
|
|
||||||
|
fillFlow.Add(cover);
|
||||||
|
covers.Add(cover);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var loadedCovers = covers.Where(c => c.ChildrenOfType<BeatmapSetCover>().SingleOrDefault()?.IsLoaded ?? false);
|
||||||
|
|
||||||
|
AddUntilStep("some loaded", () => loadedCovers.Any());
|
||||||
|
AddStep("scroll to end", () => scroll.ScrollToEnd());
|
||||||
|
AddUntilStep("all unloaded", () => !loadedCovers.Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSetNullBeatmapWhileLoading()
|
||||||
|
{
|
||||||
|
TestUpdateableBeatmapSetCover updateableCover = null;
|
||||||
|
|
||||||
|
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover
|
||||||
|
{
|
||||||
|
BeatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change model", () => updateableCover.BeatmapSet = null);
|
||||||
|
AddWaitStep("wait some", 5);
|
||||||
|
AddAssert("no cover added", () => !updateableCover.ChildrenOfType<DelayedLoadUnloadWrapper>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCoverChangeOnNewBeatmap()
|
||||||
|
{
|
||||||
|
TestUpdateableBeatmapSetCover updateableCover = null;
|
||||||
|
BeatmapSetCover initialCover = null;
|
||||||
|
|
||||||
|
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableBeatmapSetCover(0)
|
||||||
|
{
|
||||||
|
BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"),
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Alpha = 0.4f
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("cover loaded", () => updateableCover.ChildrenOfType<BeatmapSetCover>().Any());
|
||||||
|
AddStep("store initial cover", () => initialCover = updateableCover.ChildrenOfType<BeatmapSetCover>().Single());
|
||||||
|
AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("switch beatmap",
|
||||||
|
() => updateableCover.BeatmapSet = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg"));
|
||||||
|
AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType<BeatmapSetCover>().Except(new[] { initialCover }).Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineInfo = new BeatmapSetOnlineInfo
|
||||||
|
{
|
||||||
|
Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestUpdateableBeatmapSetCover : UpdateableBeatmapSetCover
|
||||||
|
{
|
||||||
|
private readonly int loadDelay;
|
||||||
|
|
||||||
|
public TestUpdateableBeatmapSetCover(int loadDelay = 10000)
|
||||||
|
{
|
||||||
|
this.loadDelay = loadDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateDrawable(BeatmapSetInfo model)
|
||||||
|
{
|
||||||
|
if (model == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new TestBeatmapSetCover(model, loadDelay)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBeatmapSetCover : BeatmapSetCover
|
||||||
|
{
|
||||||
|
private readonly int loadDelay;
|
||||||
|
|
||||||
|
public TestBeatmapSetCover(BeatmapSetInfo set, int loadDelay)
|
||||||
|
: base(set)
|
||||||
|
{
|
||||||
|
this.loadDelay = loadDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Thread.Sleep(loadDelay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
@ -52,6 +53,8 @@ namespace osu.Game.Tests
|
|||||||
|
|
||||||
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
|
|
||||||
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
@ -244,6 +245,8 @@ namespace osu.Game.Beatmaps
|
|||||||
{
|
{
|
||||||
var setInfo = info.BeatmapSet;
|
var setInfo = info.BeatmapSet;
|
||||||
|
|
||||||
|
Debug.Assert(setInfo.Files != null);
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using (var stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
@ -290,7 +293,9 @@ namespace osu.Game.Beatmaps
|
|||||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
||||||
return DefaultBeatmap;
|
return DefaultBeatmap;
|
||||||
|
|
||||||
if (beatmapInfo.BeatmapSet.Files == null)
|
// force a re-query if files are not in a state which looks like the model has
|
||||||
|
// full database information present.
|
||||||
|
if (beatmapInfo.BeatmapSet.Files == null || beatmapInfo.BeatmapSet.Files.Count == 0)
|
||||||
{
|
{
|
||||||
var info = beatmapInfo;
|
var info = beatmapInfo;
|
||||||
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
||||||
@ -526,6 +531,7 @@ namespace osu.Game.Beatmaps
|
|||||||
protected override IBeatmap GetBeatmap() => beatmap;
|
protected override IBeatmap GetBeatmap() => beatmap;
|
||||||
protected override Texture GetBackground() => null;
|
protected override Texture GetBackground() => null;
|
||||||
protected override Track GetBeatmapTrack() => null;
|
protected override Track GetBeatmapTrack() => null;
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -8,78 +9,52 @@ using osu.Game.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables
|
namespace osu.Game.Beatmaps.Drawables
|
||||||
{
|
{
|
||||||
public class UpdateableBeatmapSetCover : Container
|
public class UpdateableBeatmapSetCover : ModelBackedDrawable<BeatmapSetInfo>
|
||||||
{
|
{
|
||||||
private Drawable displayedCover;
|
private readonly BeatmapSetCoverType coverType;
|
||||||
|
|
||||||
private BeatmapSetInfo beatmapSet;
|
|
||||||
|
|
||||||
public BeatmapSetInfo BeatmapSet
|
public BeatmapSetInfo BeatmapSet
|
||||||
{
|
{
|
||||||
get => beatmapSet;
|
get => Model;
|
||||||
set
|
set => Model = value;
|
||||||
{
|
|
||||||
if (value == beatmapSet) return;
|
|
||||||
|
|
||||||
beatmapSet = value;
|
|
||||||
|
|
||||||
if (IsLoaded)
|
|
||||||
updateCover();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover;
|
public new bool Masking
|
||||||
|
|
||||||
public BeatmapSetCoverType CoverType
|
|
||||||
{
|
{
|
||||||
get => coverType;
|
get => base.Masking;
|
||||||
set
|
set => base.Masking = value;
|
||||||
{
|
|
||||||
if (value == coverType) return;
|
|
||||||
|
|
||||||
coverType = value;
|
|
||||||
|
|
||||||
if (IsLoaded)
|
|
||||||
updateCover();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UpdateableBeatmapSetCover()
|
public UpdateableBeatmapSetCover(BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover)
|
||||||
{
|
{
|
||||||
Child = new Box
|
this.coverType = coverType;
|
||||||
|
|
||||||
|
InternalChild = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(0.2f),
|
Colour = OsuColour.Gray(0.2f),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override double LoadDelay => 500;
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
updateCover();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCover()
|
protected override double TransformDuration => 400;
|
||||||
{
|
|
||||||
displayedCover?.FadeOut(400);
|
|
||||||
displayedCover?.Expire();
|
|
||||||
displayedCover = null;
|
|
||||||
|
|
||||||
if (beatmapSet != null)
|
protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func<Drawable> createContentFunc, double timeBeforeLoad)
|
||||||
|
=> new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad);
|
||||||
|
|
||||||
|
protected override Drawable CreateDrawable(BeatmapSetInfo model)
|
||||||
{
|
{
|
||||||
Add(displayedCover = new DelayedLoadUnloadWrapper(() =>
|
if (model == null)
|
||||||
{
|
return null;
|
||||||
var cover = new BeatmapSetCover(beatmapSet, coverType)
|
|
||||||
|
return new BeatmapSetCover(model, coverType)
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
FillMode = FillMode.Fill,
|
FillMode = FillMode.Fill,
|
||||||
};
|
};
|
||||||
cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out);
|
|
||||||
return cover;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
{
|
{
|
||||||
@ -49,6 +50,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
protected override Track GetBeatmapTrack() => GetVirtualTrack();
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => null;
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => null;
|
public override Stream GetStream(string storagePath) => null;
|
||||||
|
|
||||||
private class DummyRulesetInfo : RulesetInfo
|
private class DummyRulesetInfo : RulesetInfo
|
||||||
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
|||||||
public bool SkinLoaded => skin.IsResultAvailable;
|
public bool SkinLoaded => skin.IsResultAvailable;
|
||||||
public ISkin Skin => skin.Value;
|
public ISkin Skin => skin.Value;
|
||||||
|
|
||||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
protected abstract ISkin GetSkin();
|
||||||
private readonly RecyclableLazy<ISkin> skin;
|
private readonly RecyclableLazy<ISkin> skin;
|
||||||
|
|
||||||
public abstract Stream GetStream(string storagePath);
|
public abstract Stream GetStream(string storagePath);
|
||||||
|
@ -8,13 +8,13 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Legacy;
|
using osu.Game.IO.Legacy;
|
||||||
using osu.Game.Overlays.Notifications;
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
@ -38,8 +38,6 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
public readonly BindableList<BeatmapCollection> Collections = new BindableList<BeatmapCollection>();
|
||||||
|
|
||||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
@ -60,8 +58,13 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
if (storage.Exists(database_name))
|
if (storage.Exists(database_name))
|
||||||
{
|
{
|
||||||
|
List<BeatmapCollection> beatmapCollections;
|
||||||
|
|
||||||
using (var stream = storage.GetStream(database_name))
|
using (var stream = storage.GetStream(database_name))
|
||||||
importCollections(readCollections(stream));
|
beatmapCollections = readCollections(stream);
|
||||||
|
|
||||||
|
// intentionally fire-and-forget async.
|
||||||
|
importCollections(beatmapCollections);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,25 +99,12 @@ namespace osu.Game.Collections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Action<Notification> PostNotification { protected get; set; }
|
public Action<Notification> PostNotification { protected get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
|
||||||
/// </summary>
|
|
||||||
public Func<Storage> GetStableStorage { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ImportFromStableAsync()
|
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||||
{
|
{
|
||||||
var stable = GetStableStorage?.Invoke();
|
if (!stableStorage.Exists(database_name))
|
||||||
|
|
||||||
if (stable == null)
|
|
||||||
{
|
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stable.Exists(database_name))
|
|
||||||
{
|
{
|
||||||
// This handles situations like when the user does not have a collections.db file
|
// This handles situations like when the user does not have a collections.db file
|
||||||
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
||||||
@ -123,7 +113,7 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
return Task.Run(async () =>
|
return Task.Run(async () =>
|
||||||
{
|
{
|
||||||
using (var stream = stable.GetStream(database_name))
|
using (var stream = stableStorage.GetStream(database_name))
|
||||||
await Import(stream).ConfigureAwait(false);
|
await Import(stream).ConfigureAwait(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,6 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.KeyOverlay, false);
|
SetDefault(OsuSetting.KeyOverlay, false);
|
||||||
SetDefault(OsuSetting.PositionalHitSounds, true);
|
SetDefault(OsuSetting.PositionalHitSounds, true);
|
||||||
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
||||||
SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
|
|
||||||
|
|
||||||
SetDefault(OsuSetting.FloatingComments, false);
|
SetDefault(OsuSetting.FloatingComments, false);
|
||||||
|
|
||||||
@ -213,7 +212,6 @@ namespace osu.Game.Configuration
|
|||||||
KeyOverlay,
|
KeyOverlay,
|
||||||
PositionalHitSounds,
|
PositionalHitSounds,
|
||||||
AlwaysPlayFirstComboBreak,
|
AlwaysPlayFirstComboBreak,
|
||||||
ScoreMeter,
|
|
||||||
FloatingComments,
|
FloatingComments,
|
||||||
HUDVisibilityMode,
|
HUDVisibilityMode,
|
||||||
ShowProgressGraph,
|
ShowProgressGraph,
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// 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.ComponentModel;
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
|
||||||
{
|
|
||||||
public enum ScoreMeterType
|
|
||||||
{
|
|
||||||
[Description("None")]
|
|
||||||
None,
|
|
||||||
|
|
||||||
[Description("Hit Error (left)")]
|
|
||||||
HitErrorLeft,
|
|
||||||
|
|
||||||
[Description("Hit Error (right)")]
|
|
||||||
HitErrorRight,
|
|
||||||
|
|
||||||
[Description("Hit Error (left+right)")]
|
|
||||||
HitErrorBoth,
|
|
||||||
|
|
||||||
[Description("Hit Error (bottom)")]
|
|
||||||
HitErrorBottom,
|
|
||||||
|
|
||||||
[Description("Colour (left)")]
|
|
||||||
ColourLeft,
|
|
||||||
|
|
||||||
[Description("Colour (right)")]
|
|
||||||
ColourRight,
|
|
||||||
|
|
||||||
[Description("Colour (left+right)")]
|
|
||||||
ColourBoth,
|
|
||||||
|
|
||||||
[Description("Colour (bottom)")]
|
|
||||||
ColourBottom,
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,7 +10,6 @@ using System.Threading.Tasks;
|
|||||||
using Humanizer;
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using osu.Framework;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -81,8 +80,6 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
|
public virtual IEnumerable<string> HandledExtensions => new[] { ".zip" };
|
||||||
|
|
||||||
public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
|
||||||
|
|
||||||
protected readonly FileStore Files;
|
protected readonly FileStore Files;
|
||||||
|
|
||||||
protected readonly IDatabaseContextFactory ContextFactory;
|
protected readonly IDatabaseContextFactory ContextFactory;
|
||||||
@ -669,16 +666,6 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
#region osu-stable import
|
#region osu-stable import
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set a storage with access to an osu-stable install for import purposes.
|
|
||||||
/// </summary>
|
|
||||||
public Func<StableStorage> GetStableStorage { private get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Denotes whether an osu-stable installation is present to perform automated imports from.
|
|
||||||
/// </summary>
|
|
||||||
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The relative path from osu-stable's data directory to import items from.
|
/// The relative path from osu-stable's data directory to import items from.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -700,22 +687,16 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Task ImportFromStableAsync()
|
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||||
{
|
{
|
||||||
var stableStorage = GetStableStorage?.Invoke();
|
|
||||||
|
|
||||||
if (stableStorage == null)
|
|
||||||
{
|
|
||||||
Logger.Log("No osu!stable installation available!", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
var storage = PrepareStableStorage(stableStorage);
|
var storage = PrepareStableStorage(stableStorage);
|
||||||
|
|
||||||
|
// Handle situations like when the user does not have a Skins folder.
|
||||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||||
{
|
{
|
||||||
// This handles situations like when the user does not have a Skins folder
|
string fullPath = storage.GetFullPath(ImportFromStablePath);
|
||||||
Logger.Log($"No {ImportFromStablePath} folder available in osu!stable installation", LoggingTarget.Information, LogLevel.Error);
|
|
||||||
|
Logger.Log($"Folder \"{fullPath}\" not available in the target osu!stable installation to import {HumanisedModelName}s.", LoggingTarget.Information, LogLevel.Error);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
96
osu.Game/Database/StableImportManager.cs
Normal file
96
osu.Game/Database/StableImportManager.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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.Threading.Tasks;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Collections;
|
||||||
|
using osu.Game.IO;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings.Sections.Maintenance;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
|
namespace osu.Game.Database
|
||||||
|
{
|
||||||
|
public class StableImportManager : Component
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ScoreManager scores { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private CollectionManager collections { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private DialogOverlay dialogOverlay { get; set; }
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private DesktopGameHost desktopGameHost { get; set; }
|
||||||
|
|
||||||
|
private StableStorage cachedStorage;
|
||||||
|
|
||||||
|
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||||
|
|
||||||
|
public async Task ImportFromStableAsync(StableContent content)
|
||||||
|
{
|
||||||
|
var stableStorage = await getStableStorage().ConfigureAwait(false);
|
||||||
|
var importTasks = new List<Task>();
|
||||||
|
|
||||||
|
Task beatmapImportTask = Task.CompletedTask;
|
||||||
|
if (content.HasFlagFast(StableContent.Beatmaps))
|
||||||
|
importTasks.Add(beatmapImportTask = beatmaps.ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Skins))
|
||||||
|
importTasks.Add(skins.ImportFromStableAsync(stableStorage));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Collections))
|
||||||
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
|
if (content.HasFlagFast(StableContent.Scores))
|
||||||
|
importTasks.Add(beatmapImportTask.ContinueWith(_ => scores.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion));
|
||||||
|
|
||||||
|
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<StableStorage> getStableStorage()
|
||||||
|
{
|
||||||
|
if (cachedStorage != null)
|
||||||
|
return cachedStorage;
|
||||||
|
|
||||||
|
var stableStorage = game.GetStorageForStableInstall();
|
||||||
|
if (stableStorage != null)
|
||||||
|
return cachedStorage = stableStorage;
|
||||||
|
|
||||||
|
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)));
|
||||||
|
var stablePath = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return cachedStorage = new StableStorage(stablePath, desktopGameHost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum StableContent
|
||||||
|
{
|
||||||
|
Beatmaps = 1 << 0,
|
||||||
|
Scores = 1 << 1,
|
||||||
|
Skins = 1 << 2,
|
||||||
|
Collections = 1 << 3,
|
||||||
|
All = Beatmaps | Scores | Skins | Collections
|
||||||
|
}
|
||||||
|
}
|
@ -6,11 +6,13 @@ using Markdig.Extensions.AutoIdentifiers;
|
|||||||
using Markdig.Extensions.Tables;
|
using Markdig.Extensions.Tables;
|
||||||
using Markdig.Extensions.Yaml;
|
using Markdig.Extensions.Yaml;
|
||||||
using Markdig.Syntax;
|
using Markdig.Syntax;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Containers.Markdown;
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
@ -21,6 +23,16 @@ namespace osu.Game.Graphics.Containers.Markdown
|
|||||||
LineSpacing = 21;
|
LineSpacing = 21;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var api = parent.Get<IAPIProvider>();
|
||||||
|
|
||||||
|
// needs to be set before the base BDL call executes to avoid invalidating any already populated markdown content.
|
||||||
|
DocumentUrl = api.WebsiteRootUrl;
|
||||||
|
|
||||||
|
return base.CreateChildDependencies(parent);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
protected override void AddMarkdownComponent(IMarkdownObject markdownObject, FillFlowContainer container, int level)
|
||||||
{
|
{
|
||||||
switch (markdownObject)
|
switch (markdownObject)
|
||||||
|
@ -1,48 +1,63 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using Markdig.Syntax.Inlines;
|
using Markdig.Syntax.Inlines;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers.Markdown;
|
using osu.Framework.Graphics.Containers.Markdown;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Containers.Markdown
|
namespace osu.Game.Graphics.Containers.Markdown
|
||||||
{
|
{
|
||||||
public class OsuMarkdownLinkText : MarkdownLinkText
|
public class OsuMarkdownLinkText : MarkdownLinkText
|
||||||
{
|
{
|
||||||
[Resolved]
|
[Resolved(canBeNull: true)]
|
||||||
private OverlayColourProvider colourProvider { get; set; }
|
private OsuGame game { get; set; }
|
||||||
|
|
||||||
private SpriteText spriteText;
|
private readonly string text;
|
||||||
|
private readonly string title;
|
||||||
|
|
||||||
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
public OsuMarkdownLinkText(string text, LinkInline linkInline)
|
||||||
: base(text, linkInline)
|
: base(text, linkInline)
|
||||||
{
|
{
|
||||||
|
this.text = text;
|
||||||
|
title = linkInline.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
spriteText.Colour = colourProvider.Light2;
|
var textDrawable = CreateSpriteText().With(t => t.Text = text);
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
textDrawable,
|
||||||
|
new OsuMarkdownLinkCompiler(new[] { textDrawable })
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Action = OnLinkPressed,
|
||||||
|
TooltipText = title ?? Url,
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override SpriteText CreateSpriteText()
|
protected override void OnLinkPressed() => game?.HandleLink(Url);
|
||||||
|
|
||||||
|
private class OsuMarkdownLinkCompiler : DrawableLinkCompiler
|
||||||
|
{
|
||||||
|
public OsuMarkdownLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
|
: base(parts)
|
||||||
{
|
{
|
||||||
return spriteText = base.CreateSpriteText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
spriteText.Colour = colourProvider.Light1;
|
IdleColour = colourProvider.Light2;
|
||||||
return base.OnHover(e);
|
HoverColour = colourProvider.Light1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
|
||||||
{
|
|
||||||
spriteText.Colour = colourProvider.Light2;
|
|
||||||
base.OnHoverLost(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Extensions.Color4Extensions;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
@ -180,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string text;
|
private LocalisableString text;
|
||||||
|
|
||||||
public string Text
|
public LocalisableString Text
|
||||||
{
|
{
|
||||||
get => text;
|
get => text;
|
||||||
set
|
set
|
||||||
|
@ -9,28 +9,17 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="OsuMenuItem"/> with three possible states.
|
/// An <see cref="OsuMenuItem"/> with three possible states.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
|
public abstract class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="text">The text to display.</param>
|
/// <param name="text">The text to display.</param>
|
||||||
|
/// <param name="nextStateFunction">A function to inform what the next state should be when this item is clicked.</param>
|
||||||
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
||||||
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
||||||
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
|
protected TernaryStateMenuItem(string text, Func<TernaryState, TernaryState> nextStateFunction, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
|
||||||
: this(text, getNextState, type, action)
|
: base(text, nextStateFunction, type, action)
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">The text to display.</param>
|
|
||||||
/// <param name="changeStateFunc">A function that mutates a state to another state after this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
|
||||||
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
|
||||||
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
|
||||||
protected TernaryStateMenuItem(string text, Func<TernaryState, TernaryState> changeStateFunc, MenuItemType type, Action<TernaryState> action)
|
|
||||||
: base(text, changeStateFunc, type, action)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,23 +36,5 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TernaryState getNextState(TernaryState state)
|
|
||||||
{
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case TernaryState.False:
|
|
||||||
return TernaryState.True;
|
|
||||||
|
|
||||||
case TernaryState.Indeterminate:
|
|
||||||
return TernaryState.True;
|
|
||||||
|
|
||||||
case TernaryState.True:
|
|
||||||
return TernaryState.False;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs
Normal file
26
osu.Game/Graphics/UserInterface/TernaryStateRadioMenuItem.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ternary state menu item which will always set the item to <c>true</c> on click, even if already <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
public class TernaryStateRadioMenuItem : TernaryStateMenuItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="TernaryStateMenuItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text to display.</param>
|
||||||
|
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
||||||
|
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
||||||
|
public TernaryStateRadioMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
|
||||||
|
: base(text, getNextState, type, action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TernaryState getNextState(TernaryState state) => TernaryState.True;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A ternary state menu item which toggles the state of this item <c>false</c> if clicked when <c>true</c>.
|
||||||
|
/// </summary>
|
||||||
|
public class TernaryStateToggleMenuItem : TernaryStateMenuItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="TernaryStateToggleMenuItem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The text to display.</param>
|
||||||
|
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
|
||||||
|
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
|
||||||
|
public TernaryStateToggleMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
|
||||||
|
: base(text, getNextState, type, action)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TernaryState getNextState(TernaryState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case TernaryState.False:
|
||||||
|
return TernaryState.True;
|
||||||
|
|
||||||
|
case TernaryState.Indeterminate:
|
||||||
|
return TernaryState.True;
|
||||||
|
|
||||||
|
case TernaryState.True:
|
||||||
|
return TernaryState.False;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -73,10 +73,11 @@ namespace osu.Game.Input.Bindings
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
KeyBindings = store.Query(ruleset?.ID, variant)
|
KeyBindings = store.Query(ruleset?.ID, variant)
|
||||||
|
.OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction))
|
||||||
// this ordering is important to ensure that we read entries from the database in the order
|
// this ordering is important to ensure that we read entries from the database in the order
|
||||||
// enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise
|
// enforced by DefaultKeyBindings. allow for song select to handle actions that may otherwise
|
||||||
// have been eaten by the music controller due to query order.
|
// have been eaten by the music controller due to query order.
|
||||||
.OrderBy(b => defaults.FindIndex(d => (int)d.Action == b.IntAction)).ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -16,6 +17,17 @@ namespace osu.Game.Input
|
|||||||
{
|
{
|
||||||
public event Action KeyBindingChanged;
|
public event Action KeyBindingChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Keys which should not be allowed for gameplay input purposes.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly IEnumerable<InputKey> banned_keys = new[]
|
||||||
|
{
|
||||||
|
InputKey.MouseWheelDown,
|
||||||
|
InputKey.MouseWheelLeft,
|
||||||
|
InputKey.MouseWheelUp,
|
||||||
|
InputKey.MouseWheelRight
|
||||||
|
};
|
||||||
|
|
||||||
public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null)
|
public KeyBindingStore(DatabaseContextFactory contextFactory, RulesetStore rulesets, Storage storage = null)
|
||||||
: base(contextFactory, storage)
|
: base(contextFactory, storage)
|
||||||
{
|
{
|
||||||
@ -93,6 +105,9 @@ namespace osu.Game.Input
|
|||||||
using (ContextFactory.GetForWrite())
|
using (ContextFactory.GetForWrite())
|
||||||
{
|
{
|
||||||
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
|
var dbKeyBinding = (DatabasedKeyBinding)keyBinding;
|
||||||
|
|
||||||
|
Debug.Assert(dbKeyBinding.RulesetID == null || CheckValidForGameplay(keyBinding.KeyCombination));
|
||||||
|
|
||||||
Refresh(ref dbKeyBinding);
|
Refresh(ref dbKeyBinding);
|
||||||
|
|
||||||
if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination))
|
if (dbKeyBinding.KeyCombination.Equals(keyBinding.KeyCombination))
|
||||||
@ -103,5 +118,16 @@ namespace osu.Game.Input
|
|||||||
|
|
||||||
KeyBindingChanged?.Invoke();
|
KeyBindingChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool CheckValidForGameplay(KeyCombination combination)
|
||||||
|
{
|
||||||
|
foreach (var key in banned_keys)
|
||||||
|
{
|
||||||
|
if (combination.Keys.Contains(key))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
38
osu.Game/Input/OsuUserInputManager.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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.Input;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Input
|
||||||
|
{
|
||||||
|
public class OsuUserInputManager : UserInputManager
|
||||||
|
{
|
||||||
|
internal OsuUserInputManager()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
|
||||||
|
{
|
||||||
|
switch (button)
|
||||||
|
{
|
||||||
|
case MouseButton.Right:
|
||||||
|
return new RightMouseManager(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateButtonEventManagerFor(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RightMouseManager : MouseButtonEventManager
|
||||||
|
{
|
||||||
|
public RightMouseManager(MouseButton button)
|
||||||
|
: base(button)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers.
|
||||||
|
public override bool EnableClick => false;
|
||||||
|
public override bool ChangeFocusOnClick => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
osu.Game/Localisation/ButtonSystem.ja.resx
Normal file
38
osu.Game/Localisation/ButtonSystem.ja.resx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<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>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user