diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 5ed563614a..8bba73ed64 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -5,20 +5,17 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!salad!" }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 983b302de8..4bd50f29f6 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private void load() { // todo: this should come from the skin. - AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); + AccentColour = colourForRepresentation(HitObject.VisualRepresentation); InternalChildren = new[] { @@ -275,7 +275,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); } - private Color4 colourForRrepesentation(FruitVisualRepresentation representation) + private Color4 colourForRepresentation(FruitVisualRepresentation representation) { switch (representation) { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 23b620248f..20bf2ee5c7 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -5,10 +5,10 @@ using System; using System.Linq; using osu.Framework.MathUtils; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Replays { @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Replays public CatchAutoGenerator(Beatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); } protected Replay Replay; diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index f05eb31454..c907fec653 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index d5c5eb844a..8c32b75959 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -2,9 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Catch.Replays diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index ef1bb7767f..3673657cd8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -5,12 +5,12 @@ using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs new file mode 100644 index 0000000000..fb8c740d16 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestCase.cs @@ -0,0 +1,52 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaPlacementBlueprintTestCase : PlacementBlueprintTestCase, IManiaHitObjectComposer + { + private readonly Column column; + + protected ManiaPlacementBlueprintTestCase() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo); + + return dependencies; + } + + protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; + + protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs new file mode 100644 index 0000000000..50de5ce7a3 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestCase.cs @@ -0,0 +1,38 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaSelectionBlueprintTestCase : SelectionBlueprintTestCase, IManiaHitObjectComposer + { + [Cached(Type = typeof(IAdjustableClock))] + private readonly IAdjustableClock clock = new StopwatchClock(); + + private readonly Column column; + + protected ManiaSelectionBlueprintTestCase() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs index 9bdb5ec1b2..eecc578861 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Mania.Tests Spacing = new Vector2(20, 0), Children = new[] { - createColumn(ScrollingDirection.Up, ManiaAction.Key1), - createColumn(ScrollingDirection.Down, ManiaAction.Key2) + createColumn(ScrollingDirection.Up, ManiaAction.Key1, 0), + createColumn(ScrollingDirection.Down, ManiaAction.Key2, 1) } }; } @@ -85,9 +85,9 @@ namespace osu.Game.Rulesets.Mania.Tests } } - private Drawable createColumn(ScrollingDirection direction, ManiaAction action) + private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index) { - var column = new Column + var column = new Column(index) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..ea7433268d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs index 993f7520e8..756031a463 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs @@ -15,7 +15,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseHoldNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestCaseHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase { private readonly DrawableHoldNote drawableObject; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs new file mode 100644 index 0000000000..9ae49d200e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestCaseNotePlacementBlueprint : ManiaPlacementBlueprintTestCase + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs index 67370be66c..4023f97eb3 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestCaseNoteSelectionBlueprint : ManiaSelectionBlueprintTestCase { private readonly DrawableNote drawableObject; diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1f26bda1b2..610c7a8fee 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Difficulty { @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs new file mode 100644 index 0000000000..41b2e950f9 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components +{ + public class EditBodyPiece : BodyPiece + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + + Background.Alpha = 0.5f; + Foreground.Alpha = 0; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..081bdffc27 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -0,0 +1,74 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class HoldNotePlacementBlueprint : ManiaPlacementBlueprint + { + private readonly EditBodyPiece bodyPiece; + private readonly EditNotePiece headPiece; + private readonly EditNotePiece tailPiece; + + public HoldNotePlacementBlueprint() + : base(new HoldNote()) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre }, + headPiece = new EditNotePiece { Origin = Anchor.Centre }, + tailPiece = new EditNotePiece { Origin = Anchor.Centre } + }; + } + + protected override void Update() + { + base.Update(); + + if (Column != null) + { + headPiece.Y = PositionAt(HitObject.StartTime); + tailPiece.Y = PositionAt(HitObject.EndTime); + } + + var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + var bottomPosition = new Vector2(headPiece.DrawPosition.X, Math.Max(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + + bodyPiece.Position = topPosition; + bodyPiece.Width = headPiece.Width; + bodyPiece.Height = (bottomPosition - topPosition).Y; + } + + private double originalStartTime; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (PlacementBegun) + { + var endTime = TimeAt(e.ScreenSpaceMousePosition); + + HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; + HitObject.Duration = Math.Abs(endTime - originalStartTime); + } + else + { + headPiece.Width = tailPiece.Width = SnappedWidth; + headPiece.X = tailPiece.X = SnappedMousePosition.X; + + originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + } + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index afeb0a585e..4b78dd68cb 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // When scrolling upwards our origin is already at the top of the head note (which is the intended location), diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs new file mode 100644 index 0000000000..d76d20f2b8 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -0,0 +1,118 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public abstract class ManiaPlacementBlueprint : PlacementBlueprint, + IRequireHighFrequencyMousePosition // the playfield could be moving behind us + where T : ManiaHitObject + { + protected new T HitObject => (T)base.HitObject; + + protected Column Column; + + /// + /// The current mouse position, snapped to the closest column. + /// + protected Vector2 SnappedMousePosition { get; private set; } + + /// + /// The width of the closest column to the current mouse position. + /// + protected float SnappedWidth { get; private set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + protected ManiaPlacementBlueprint(T hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (Column == null) + return base.OnMouseDown(e); + + HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + HitObject.Column = Column.Index; + + BeginPlacement(); + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + EndPlacement(); + return base.OnMouseUp(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!PlacementBegun) + Column = ColumnAt(e.ScreenSpaceMousePosition); + + if (Column == null) return false; + + SnappedWidth = Column.DrawWidth; + + // Snap to the column + var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); + SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y); + return true; + } + + protected double TimeAt(Vector2 screenSpacePosition) + { + if (Column == null) + return 0; + + var hitObjectContainer = Column.HitObjectContainer; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y; + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; + + return scrollingInfo.Algorithm.TimeAt(hitObjectPos, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + hitObjectContainer.DrawHeight); + } + + protected float PositionAt(double time) + { + var pos = scrollingInfo.Algorithm.PositionAt(time, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + Column.HitObjectContainer.DrawHeight); + + return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; + } + + protected Column ColumnAt(Vector2 screenSpacePosition) + => composer.ColumnAt(applyPositionOffset(screenSpacePosition, false)); + + private Vector2 applyPositionOffset(Vector2 position, bool reverse) + { + position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2; + return position; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 53f9dd8752..f190843f5d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -1,23 +1,80 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class ManiaSelectionBlueprint : SelectionBlueprint { + public Vector2 ScreenSpaceDragPosition { get; private set; } + public Vector2 DragPosition { get; private set; } + + protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + + protected IClock EditorClock { get; private set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + public ManiaSelectionBlueprint(DrawableHitObject hitObject) : base(hitObject) { RelativeSizeAxes = Axes.None; } - public override void AdjustPosition(DragEvent dragEvent) + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) { + EditorClock = clock; + } + + protected override void Update() + { + base.Update(); + + Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return base.OnMouseDown(e); + } + + protected override bool OnDrag(DragEvent e) + { + var result = base.OnDrag(e); + + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return result; + } + + public override void Show() + { + HitObject.AlwaysAlive = true; + base.Show(); + } + + public override void Hide() + { + HitObject.AlwaysAlive = false; + base.Hide(); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs new file mode 100644 index 0000000000..acb43e38ba --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class NotePlacementBlueprint : ManiaPlacementBlueprint + { + public NotePlacementBlueprint() + : base(new Note()) + { + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Y; + + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; + } + + protected override void Update() + { + base.Update(); + + Width = SnappedWidth; + Position = SnappedMousePosition; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 7df7924c51..440a539412 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize; - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs new file mode 100644 index 0000000000..b1872c200f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class HoldNoteCompositionTool : HitObjectCompositionTool + { + public HoldNoteCompositionTool() + : base("Hold") + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs new file mode 100644 index 0000000000..d9de400ac5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Mania.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public interface IManiaHitObjectComposer + { + Column ColumnAt(Vector2 screenSpacePosition); + + int TotalColumns { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3531b81e68..d73ce3966f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -11,33 +10,54 @@ using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaHitObjectComposer : HitObjectComposer + [Cached(Type = typeof(IManiaHitObjectComposer))] + public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { + protected new ManiaEditRulesetContainer RulesetContainer { get; private set; } + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { } + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column ColumnAt(Vector2 screenSpacePosition) => RulesetContainer.GetColumnByPosition(screenSpacePosition); + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + public int TotalColumns => ((ManiaPlayfield)RulesetContainer.Playfield).TotalColumns; + protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) { - var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + RulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(rulesetContainer.ScrollingInfo); + dependencies.CacheAs(RulesetContainer.ScrollingInfo); - return rulesetContainer; + return RulesetContainer; } - protected override IReadOnlyList CompositionTools => Array.Empty(); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new NoteCompositionTool(), + new HoldNoteCompositionTool() + }; + + public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs new file mode 100644 index 0000000000..828f6d87bc --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -0,0 +1,125 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaSelectionHandler : SelectionHandler + { + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + private IClock editorClock; + + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) + { + editorClock = clock; + } + + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + adjustOrigins((ManiaSelectionBlueprint)blueprint); + performDragMovement(dragEvent); + performColumnMovement(dragEvent); + + base.HandleDrag(blueprint, dragEvent); + } + + /// + /// Ensures that the position of hitobjects remains centred to the mouse position. + /// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged. + /// + /// The that received the drag event. + private void adjustOrigins(ManiaSelectionBlueprint reference) + { + var referenceParent = (HitObjectContainer)reference.HitObject.Parent; + + float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y; + float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; + + // Flip the vertical coordinate space when scrolling downwards + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = targetPosition - referenceParent.DrawHeight; + + float movementDelta = targetPosition - reference.HitObject.Position.Y; + + foreach (var b in SelectedBlueprints.OfType()) + b.HitObject.Y += movementDelta; + } + + private void performDragMovement(DragEvent dragEvent) + { + foreach (var b in SelectedBlueprints) + { + var hitObject = b.HitObject; + + var objectParent = (HitObjectContainer)hitObject.Parent; + + // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame + // without the position having been updated by the parenting ScrollingHitObjectContainer + hitObject.Y += dragEvent.Delta.Y; + + float targetPosition; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = -hitObject.Position.Y; + else + targetPosition = hitObject.Position.Y; + + objectParent.Remove(hitObject); + + hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, + editorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + objectParent.DrawHeight); + + objectParent.Add(hitObject); + } + } + + private void performColumnMovement(DragEvent dragEvent) + { + var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition); + var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition); + if (lastColumn == null || currentColumn == null) + return; + + int columnDelta = currentColumn.Index - lastColumn.Index; + if (columnDelta == 0) + return; + + int minColumn = int.MaxValue; + int maxColumn = int.MinValue; + + foreach (var obj in SelectedHitObjects.OfType()) + { + if (obj.Column < minColumn) + minColumn = obj.Column; + if (obj.Column > maxColumn) + maxColumn = obj.Column; + } + + columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + + foreach (var obj in SelectedHitObjects.OfType()) + obj.Column += columnDelta; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs new file mode 100644 index 0000000000..93f49d1cc0 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class NoteCompositionTool : HitObjectCompositionTool + { + public NoteCompositionTool() + : base(nameof(Note)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 19e89c8ec5..392d10b414 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -24,7 +24,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania { public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 6d91c03c13..d4164ec1f6 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter { - public override string ShortenedName => Name; + public override string Acronym => Name; public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 89792956bb..f53943ec85 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -6,20 +6,17 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!topus!" }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index 4790a77cc0..5a8ce05873 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap { public override string Name => "Dual Stages"; - public override string ShortenedName => "DS"; + public override string Acronym => "DS"; public override string Description => @"Double the stages, double the fun!"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 73942cbb53..229170cea8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModFadeIn : Mod { public override string Name => "Fade In"; - public override string ShortenedName => "FI"; + public override string Acronym => "FI"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index c2a4ed444f..11143f715e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 1; public override string Name => "One Key"; - public override string ShortenedName => "1K"; + public override string Acronym => "1K"; public override string Description => @"Play with one key."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 3d78ad449b..0169d4ecac 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 2; public override string Name => "Two Keys"; - public override string ShortenedName => "2K"; + public override string Acronym => "2K"; public override string Description => @"Play with two keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index a96375a81d..12e84eda0f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 3; public override string Name => "Three Keys"; - public override string ShortenedName => "3K"; + public override string Acronym => "3K"; public override string Description => @"Play with three keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 2bd3d08648..ab5ed91d9d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 4; public override string Name => "Four Keys"; - public override string ShortenedName => "4K"; + public override string Acronym => "4K"; public override string Description => @"Play with four keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index e59b2d6e59..be9f0f8066 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 5; public override string Name => "Five Keys"; - public override string ShortenedName => "5K"; + public override string Acronym => "5K"; public override string Description => @"Play with five keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 6a05317f46..571e533078 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 6; public override string Name => "Six Keys"; - public override string ShortenedName => "6K"; + public override string Acronym => "6K"; public override string Description => @"Play with six keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index 7280c345b8..89c6bd997d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 7; public override string Name => "Seven Keys"; - public override string ShortenedName => "7K"; + public override string Acronym => "7K"; public override string Description => @"Play with seven keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index dddef0fa9d..8d043651d3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 8; public override string Name => "Eight Keys"; - public override string ShortenedName => "8K"; + public override string Acronym => "8K"; public override string Description => @"Play with eight keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index 4ef38503a4..20471ebffc 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 9; public override string Name => "Nine Keys"; - public override string ShortenedName => "9K"; + public override string Acronym => "9K"; public override string Description => @"Play with nine keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 847b0037f1..68325b40bf 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModMirror : Mod, IApplicableToRulesetContainer { public override string Name => "Mirror"; - public override string ShortenedName => "MR"; + public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; public override bool Ranked => true; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 0915b80742..b3a3d4280b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModRandom : Mod, IApplicableToRulesetContainer { public override string Name => "Random"; - public override string ShortenedName => "RD"; + public override string Acronym => "RD"; public override ModType Type => ModType.Conversion; public override FontAwesome Icon => FontAwesome.fa_osu_dice; public override string Description => @"Shuffle around the keys!"; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 8c96c6dfe7..70ff7b4124 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -12,6 +12,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public abstract class DrawableManiaHitObject : DrawableHitObject { + /// + /// Whether this should always remain alive. + /// + internal bool AlwaysAlive; + /// /// The which causes this to be hit. /// @@ -34,6 +39,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } + protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; + protected virtual void OnDirectionChanged(ScrollingDirection direction) { Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 4e5bcf64e7..8dbf33c183 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents length-wise portion of a hold note. /// - internal class BodyPiece : Container, IHasAccentColour + public class BodyPiece : Container, IHasAccentColour { private readonly Container subtractionLayer; - private readonly Drawable background; - private readonly BufferedContainer foreground; + protected readonly Drawable Background; + protected readonly BufferedContainer Foreground; private readonly BufferedContainer subtractionContainer; public BodyPiece() @@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Children = new[] { - background = new Box { RelativeSizeAxes = Axes.Both }, - foreground = new BufferedContainer + Background = new Box { RelativeSizeAxes = Axes.Both }, + Foreground = new BufferedContainer { Blending = BlendingMode.Additive, RelativeSizeAxes = Axes.Both, @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Radius = DrawWidth }; - foreground.ForceRedraw(); + Foreground.ForceRedraw(); subtractionContainer.ForceRedraw(); subtractionCache.Validate(); @@ -137,18 +137,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces if (!IsLoaded) return; - foreground.Colour = AccentColour.Opacity(0.5f); - background.Colour = AccentColour.Opacity(0.7f); + Foreground.Colour = AccentColour.Opacity(0.5f); + Background.Colour = AccentColour.Opacity(0.7f); const float animation_length = 50; - foreground.ClearTransforms(false, nameof(foreground.Colour)); + Foreground.ClearTransforms(false, nameof(Foreground.Colour)); if (hitting) { // wait for the next sync point double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (foreground.BeginDelayedSequence(synchronisedOffset)) - foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop(); + using (Foreground.BeginDelayedSequence(synchronisedOffset)) + Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); } subtractionCache.Invalidate(); diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index e183098a51..bb3d060e61 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Objects; @@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaHitObject : HitObject, IHasColumn { - public virtual int Column { get; set; } + public readonly Bindable ColumnBindable = new Bindable(); + + public virtual int Column + { + get => ColumnBindable; + set => ColumnBindable.Value = value; + } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 0eef540d97..c58d66c66a 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; using System.Linq; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Replays { @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); columnActions = new ManiaAction[Beatmap.TotalColumns]; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index ea239bf80f..20b0775c57 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index bc9fd6e06f..04d3bdf5f5 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Mania.Replays diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 2f456f7479..efa4a671a3 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -13,6 +13,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -21,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI private const float column_width = 45; private const float special_column_width = 70; + /// + /// The index of this column as part of the whole playfield. + /// + public readonly int Index; + public readonly Bindable Action = new Bindable(); private readonly ColumnBackground background; @@ -30,8 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly Container explosionContainer; - public Column() + public Column(int index) { + Index = index; + RelativeSizeAxes = Axes.Y; Width = column_width; @@ -137,6 +145,15 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectContainer.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) { if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements) @@ -165,5 +182,9 @@ namespace osu.Game.Rulesets.Mania.UI } public bool OnReleased(ManiaAction action) => false; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border + => DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 85d1c0c4b0..0caac39f21 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI { private readonly List stages = new List(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); + public ManiaPlayfield(List stageDefinitions) { if (stageDefinitions == null) @@ -52,8 +55,42 @@ namespace osu.Game.Rulesets.Mania.UI 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 void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + /// + /// Retrieves a column from a screen-space position. + /// + /// The screen-space position. + /// The column which the lies in. + public Column GetColumnByPosition(Vector2 screenSpacePosition) + { + Column found = null; + + foreach (var stage in stages) + { + foreach (var column in stage.Columns) + { + if (column.ReceivePositionalInputAt(screenSpacePosition)) + { + found = column; + break; + } + } + + if (found != null) + break; + } + + return found; + } + + /// + /// Retrieves the total amount of columns across all stages in this playfield. + /// + public int TotalColumns => stages.Sum(s => s.Columns.Count); + private ManiaStage getStageByColumn(int column) { int sum = 0; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 321dd4e1cb..db65dfae41 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -12,6 +12,7 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Mods; @@ -21,10 +22,10 @@ using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -80,6 +81,13 @@ namespace osu.Game.Rulesets.Mania.UI Config.BindWith(ManiaSetting.ScrollTime, TimeRange); } + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); + protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 73c080ffba..f71b866912 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI /// public class ManiaStage : ScrollingPlayfield { + public const float COLUMN_SPACING = 1; + public const float HIT_TARGET_POSITION = 50; public IReadOnlyList Columns => columnFlow.Children; @@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI private List normalColumnColours = new List(); private Color4 specialColumnColour; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); + private readonly int firstColumnIndex; public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) @@ -84,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) + Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, + Spacing = new Vector2(COLUMN_SPACING, 0) }, } }, @@ -123,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var isSpecial = definition.IsSpecialColumn(i); - var column = new Column + var column = new Column(firstColumnIndex + i) { IsSpecial = isSpecial, Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } @@ -152,11 +156,31 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(h); + + 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) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Remove(h); + + h.OnNewResult -= OnNewResult; + return true; + } + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b0887ac72b..5e66d43c98 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 8431d5d5d0..fab9c27c6d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -16,7 +15,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints : base(hitObject) { } - - public override void AdjustPosition(DragEvent dragEvent) => OsuObject.Position += dragEvent.Delta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index 4bac9d3556..32258572bf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -10,13 +9,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint { - private readonly Slider slider; - public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) : base(hitObject) { - this.slider = slider; - InternalChild = new SliderCirclePiece(slider, position); Select(); @@ -24,7 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. public override bool HandlePositionalInput => false; - - public override void AdjustPosition(DragEvent dragEvent) => slider.Position += dragEvent.Delta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs index 4b98866613..e4bcc20f6a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -20,10 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); - - public override void AdjustPosition(DragEvent dragEvent) - { - // Spinners don't support position adjustments - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a706e1d4be..117af9e853 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; + public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); + protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs new file mode 100644 index 0000000000..0c7e571ef5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -0,0 +1,30 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuSelectionHandler : SelectionHandler + { + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + h.Position += dragEvent.Delta; + } + + base.HandleDrag(blueprint, dragEvent); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 37d5f57fcb..4d371c2e16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModAutopilot : Mod { public override string Name => "Autopilot"; - public override string ShortenedName => "AP"; + public override string Acronym => "AP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 1eb2f59010..7ef01e075c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -7,7 +7,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Mods { @@ -15,12 +16,10 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - Replay = new OsuAutoGenerator(beatmap).Generate() - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, + Replay = new OsuAutoGenerator(beatmap).Generate() + }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 6aa864d9b2..cf0d629367 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModSpunOut : Mod { public override string Name => "Spun Out"; - public override string ShortenedName => "SO"; + public override string Acronym => "SO"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; public override ModType Type => ModType.DifficultyReduction; public override string Description => @"Spinners will be automatically completed."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 139ce4cc4b..968854d8b2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTarget : Mod { public override string Name => "Target"; - public override string ShortenedName => "TP"; + public override string Acronym => "TP"; public override ModType Type => ModType.Conversion; public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; public override string Description => @"Practice keeping up with the beat of the song."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index dcd1896601..6949f44bd1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects { public override string Name => "Transform"; - public override string ShortenedName => "TR"; + public override string Acronym => "TR"; public override FontAwesome Icon => FontAwesome.fa_arrows; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 93f1070231..3f7d43e31f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects { public override string Name => "Wiggle"; - public override string ShortenedName => "WG"; + public override string Acronym => "WG"; public override FontAwesome Icon => FontAwesome.fa_certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 1081f185ad..2ac46a14f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { - public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt; - private readonly ShakeContainer shakeContainer; protected DrawableOsuHitObject(OsuHitObject hitObject) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d0d9479ed1..8e809306a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -217,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { + case ArmedState.Idle: + Expire(true); + break; case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 6736d10dab..d1192e4165 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -11,7 +11,6 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Replays; @@ -20,6 +19,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu { @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index a0d040c40b..eda5be1f0f 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -8,8 +8,8 @@ using osu.Game.Rulesets.Osu.Objects; using System; using System.Linq; using osu.Framework.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 2530644413..6dc5e42258 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -6,9 +6,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using System; using System.Collections.Generic; +using osu.Game.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Replays { @@ -37,13 +37,7 @@ namespace osu.Game.Rulesets.Osu.Replays protected OsuAutoGeneratorBase(Beatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. FrameDelay = ApplyModsToRate(1000.0 / 60.0); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 82dfa7f0ad..74250fb92d 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs index 230709a4aa..7806e88b5c 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a9d39e88b4..9b3da1b8a8 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -39,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Scoring comboResultCounts.Clear(); } - public override void PopulateScore(Score score) + public override void PopulateScore(ScoreInfo score) { base.PopulateScore(score); diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index ea5718bed2..c0e6eae494 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -14,7 +15,6 @@ using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Osu.UI { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 734d5d0ad7..e4960c9680 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index c63bf10542..62111ae74a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -3,22 +3,19 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + protected override Score CreateReplayScore(Beatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "mekkadosu!" }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index e7b2789010..2794a3c166 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -5,10 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Replays { @@ -19,13 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Replays public TaikoAutoGenerator(Beatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); } protected Replay Replay; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index ab7856eb8f..9748cb08be 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -5,6 +5,7 @@ using osu.Game.Rulesets.Replays; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; namespace osu.Game.Rulesets.Taiko.Replays { diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 2177a3cbdc..44fd43d660 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Taiko.Replays diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7d9bc98957..7094f9d71b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -14,9 +14,9 @@ using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko { @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); public override int? LegacyID => 1; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 99c83c243b..8f6e55c0d1 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -16,6 +15,7 @@ using System.Linq; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 616ba132fd..26167cb24a 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.IO int fireCount = 0; // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded += _ => fireCount++; + manager.ItemAdded += (_, __, ___) => fireCount++; manager.ItemRemoved += _ => fireCount++; var imported = loadOszIntoOsu(osu); diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 49494b65b9..42fe4065c7 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(1, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); } [Test] @@ -27,7 +27,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); } @@ -37,7 +37,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is ModIncompatibleWithA); } @@ -63,7 +63,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -86,7 +86,7 @@ namespace osu.Game.Tests.NonVisual var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModAofA); Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); } @@ -94,7 +94,7 @@ namespace osu.Game.Tests.NonVisual private class ModA : Mod { public override string Name => nameof(ModA); - public override string ShortenedName => nameof(ModA); + public override string Acronym => nameof(ModA); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; @@ -103,7 +103,7 @@ namespace osu.Game.Tests.NonVisual private class ModB : Mod { public override string Name => nameof(ModB); - public override string ShortenedName => nameof(ModB); + public override string Acronym => nameof(ModB); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; @@ -112,7 +112,7 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithA : Mod { public override string Name => $"Incompatible With {nameof(ModA)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA) }; @@ -130,7 +130,7 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithAAndB : Mod { public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs new file mode 100644 index 0000000000..66363deb7c --- /dev/null +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -0,0 +1,166 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Scores.IO +{ + public class ImportScoreTest + { + public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; + + [Test] + public void TestBasicImport() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new User { Username = "Test user" }, + Date = DateTimeOffset.Now, + OnlineScoreID = 12345, + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.Combo, imported.Combo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportMods() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); + Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestImportStatistics() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) + { + try + { + var osu = loadOsu(host); + + var toImport = new ScoreInfo + { + Statistics = new Dictionary + { + { HitResult.Perfect, 100 }, + { HitResult.Miss, 50 } + } + }; + + var imported = loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); + Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); + } + finally + { + host.Exit(); + } + } + } + + private ScoreInfo loadIntoOsu(OsuGameBase osu, ScoreInfo score) + { + var beatmapManager = osu.Dependencies.Get(); + + score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + score.Ruleset = new OsuRuleset().RulesetInfo; + + var scoreManager = osu.Dependencies.Get(); + scoreManager.Import(score); + + return scoreManager.GetAllUsableScores().First(); + } + + private string createTemporaryBeatmap() + { + var temp = Path.GetTempFileName() + ".osz"; + File.Copy(TEST_OSZ_PATH, temp, true); + Assert.IsTrue(File.Exists(temp)); + return temp; + } + + private OsuGameBase loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + Task.Run(() => host.Run(osu)); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + var beatmapFile = createTemporaryBeatmap(); + var beatmapManager = osu.Dependencies.Get(); + beatmapManager.Import(beatmapFile); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs index d3098864f4..ec85b33919 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs @@ -16,15 +16,16 @@ using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { [System.ComponentModel.Description("in BeatmapOverlay")] public class TestCaseBeatmapScoresContainer : OsuTestCase { - private readonly IEnumerable scores; - private readonly IEnumerable anotherScores; - private readonly APIScore topScore; + private readonly IEnumerable scores; + private readonly IEnumerable anotherScores; + private readonly APIScoreInfo topScoreInfo; private readonly Box background; public TestCaseBeatmapScoresContainer() @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("scores pack 1", () => scoresContainer.Scores = scores); AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); - AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); + AddStep("only top score", () => scoresContainer.Scores = new[] { topScoreInfo }); AddStep("remove scores", () => scoresContainer.Scores = null); AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual scores = new[] { - new APIScore + new APIScoreInfo { User = new User { @@ -80,7 +81,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new APIScoreInfo { User = new User { @@ -102,7 +103,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new APIScoreInfo { User = new User { @@ -123,7 +124,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new APIScoreInfo { User = new User { @@ -143,7 +144,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScore + new APIScoreInfo { User = new User { @@ -169,7 +170,7 @@ namespace osu.Game.Tests.Visual anotherScores = new[] { - new APIScore + new APIScoreInfo { User = new User { @@ -191,7 +192,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new APIScoreInfo { User = new User { @@ -214,7 +215,7 @@ namespace osu.Game.Tests.Visual TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new APIScoreInfo { User = new User { @@ -230,7 +231,7 @@ namespace osu.Game.Tests.Visual TotalScore = 123456, Accuracy = 0.6543, }, - new APIScore + new APIScoreInfo { User = new User { @@ -251,7 +252,7 @@ namespace osu.Game.Tests.Visual TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new APIScoreInfo { User = new User { @@ -279,7 +280,7 @@ namespace osu.Game.Tests.Visual s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } - topScore = new APIScore + topScoreInfo = new APIScoreInfo { User = new User { @@ -301,9 +302,9 @@ namespace osu.Game.Tests.Visual TotalScore = 987654321, Accuracy = 0.8487, }; - topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Great, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Good, RNG.Next(2000)); + topScoreInfo.Statistics.Add(HitResult.Meh, RNG.Next(2000)); } [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index eddcd16b93..61c2f47e7d 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -16,6 +16,7 @@ using NUnit.Framework; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Framework.Configuration; namespace osu.Game.Tests.Visual { @@ -54,8 +55,9 @@ namespace osu.Game.Tests.Visual linkColour = colours.Blue; var chatManager = new ChannelManager(); - chatManager.AvailableChannels.Add(new Channel { Name = "#english"}); - chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" }); + BindableCollection availableChannels = (BindableCollection)chatManager.AvailableChannels; + availableChannels.Add(new Channel { Name = "#english"}); + availableChannels.Add(new Channel { Name = "#japanese" }); Dependencies.Cache(chatManager); Dependencies.Cache(new ChatOverlay()); diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index 4109b72989..6620fbeef3 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionBox), + typeof(SelectionHandler), typeof(DragBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs new file mode 100644 index 0000000000..33e2df9ff0 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs @@ -0,0 +1,135 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseIdleTracker : ManualInputManagerTestCase + { + private readonly IdleTrackingBox box1; + private readonly IdleTrackingBox box2; + private readonly IdleTrackingBox box3; + private readonly IdleTrackingBox box4; + + public TestCaseIdleTracker() + { + Children = new Drawable[] + { + box1 = new IdleTrackingBox(1000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + box2 = new IdleTrackingBox(2000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + box3 = new IdleTrackingBox(3000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + box4 = new IdleTrackingBox(4000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Orange, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + }; + } + + [Test] + public void TestNudge() + { + AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + + AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); + + AddAssert("check not idle", () => !box1.IsIdle); + AddAssert("check idle", () => box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + } + + [Test] + public void TestMovement() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + + AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre)); + AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => !box3.IsIdle); + AddAssert("check idle", () => !box4.IsIdle); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + [Test] + public void TestTimings() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box1.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box2.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box3.IsIdle, "Wait for idle"); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + private class IdleTrackingBox : CompositeDrawable + { + private readonly IdleTracker idleTracker; + + public bool IsIdle => idleTracker.IsIdle.Value; + + public IdleTrackingBox(double timeToIdle) + { + Box box; + + Alpha = 0.6f; + Scale = new Vector2(0.6f); + + InternalChildren = new Drawable[] + { + idleTracker = new IdleTracker(timeToIdle), + box = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }; + + idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle ? Color4.White : Color4.Black, true); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs index d4a2fcc62c..f7630f0902 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseLeaderboard.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; using osu.Framework.Allocation; @@ -13,6 +12,7 @@ using osuTK; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Tests.Visual { @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual { var scores = new[] { - new Score + new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.X, Accuracy = 1, @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.SH, Accuracy = 1, @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.S, Accuracy = 1, @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.A, Accuracy = 1, @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.B, Accuracy = 0.9826, @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.C, Accuracy = 0.9654, @@ -184,7 +184,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.6025, @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.5140, @@ -220,7 +220,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.F, Accuracy = 0.4222, diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 888bf6250f..87235add37 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -187,7 +187,7 @@ namespace osu.Game.Tests.Visual private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.ShortenedName))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => selectedMods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 1f2d99a7d8..e0ea613534 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using System.Linq; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -19,13 +20,10 @@ namespace osu.Game.Tests.Visual Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value); - // We have the replay - var replay = dummyRulesetContainer.Replay; - // Reset the mods Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - return new ReplayPlayer(replay); + return new ReplayPlayer(new Score { Replay = dummyRulesetContainer.Replay }); } } } diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index ee36fb0afc..dfe1cdbfb0 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Users; @@ -19,7 +20,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { - typeof(Score), + typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), typeof(ResultsPageScore), @@ -40,7 +41,7 @@ namespace osu.Game.Tests.Visual if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - Add(new Results(new Score + Add(new Results(new ScoreInfo { TotalScore = 2845370, Accuracy = 0.98, diff --git a/osu.Game.Tests/Visual/TestCaseRoomSettings.cs b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs new file mode 100644 index 0000000000..40742ce709 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing.Input; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; +using osuTK.Input; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseRoomSettings : ManualInputManagerTestCase + { + private readonly Room room; + private readonly TestRoomSettingsOverlay overlay; + + public TestCaseRoomSettings() + { + room = new Room + { + Name = { Value = "One Testing Room" }, + Availability = { Value = RoomAvailability.Public }, + Type = { Value = new GameTypeTeamVersus() }, + MaxParticipants = { Value = 10 }, + }; + + Add(overlay = new TestRoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.75f, + }); + + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Two Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = null); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.InviteOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeTagTeam()); + apply(); + assertAll(); + AddStep(@"show", overlay.Show); + AddStep(@"set room name", () => room.Name.Value = @"Room Changed Name!"); + AddStep(@"set room availability", () => room.Availability.Value = RoomAvailability.Public); + AddStep(@"set room type", () => room.Type.Value = new GameTypeTag()); + AddStep(@"set room max", () => room.MaxParticipants.Value = 100); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Unsaved Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = 20); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.FriendsOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeVersus()); + AddStep(@"hide", overlay.Hide); + AddWaitStep(5); + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"hide", overlay.Hide); + } + + private void apply() + { + AddStep(@"apply", () => + { + overlay.ClickApplyButton(InputManager); + }); + } + + private void assertAll() + { + AddAssert(@"name == room name", () => overlay.CurrentName == room.Name.Value); + AddAssert(@"max == room max", () => overlay.CurrentMaxParticipants == room.MaxParticipants.Value); + AddAssert(@"availability == room availability", () => overlay.CurrentAvailability == room.Availability.Value); + AddAssert(@"type == room type", () => Equals(overlay.CurrentType, room.Type.Value)); + } + + private class TestRoomSettingsOverlay : RoomSettingsOverlay + { + public string CurrentName + { + get => NameField.Text; + set => NameField.Text = value; + } + + public int? CurrentMaxParticipants + { + get + { + if (int.TryParse(MaxParticipantsField.Text, out int max)) + return max; + + return null; + } + set => MaxParticipantsField.Text = value?.ToString(); + } + + public RoomAvailability CurrentAvailability + { + get => AvailabilityPicker.Current.Value; + set => AvailabilityPicker.Current.Value = value; + } + + public GameType CurrentType + { + get => TypePicker.Current.Value; + set => TypePicker.Current.Value = value; + } + + public TestRoomSettingsOverlay(Room room) : base(room) + { + } + + public void ClickApplyButton(ManualInputManager inputManager) + { + inputManager.MoveMouseTo(ApplyButton); + inputManager.Click(MouseButton.Left); + } + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 508232dbfe..418bcb5ad1 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,20 +1,17 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; +using osu.Game.Database; namespace osu.Game.Beatmaps { - public class BeatmapDifficulty + public class BeatmapDifficulty : IHasPrimaryKey { /// /// The default value used for all difficulty settings except and . /// public const float DEFAULT_DIFFICULTY = 5; - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e1f3bdf54..0534fd9253 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -15,8 +15,6 @@ namespace osu.Game.Beatmaps [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public int BeatmapVersion; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 24c68d392b..8728d776d0 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps public override string[] HandledExtensions => new[] { ".osz" }; + protected override string[] HashableFileTypes => new[] { ".osu" }; + protected override string ImportFromStablePath => "Songs"; private readonly RulesetStore rulesets; @@ -129,19 +131,6 @@ namespace osu.Game.Beatmaps beatmaps.ForEach(b => b.OnlineBeatmapID = null); } - protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) - { - // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); - if (existingHashMatch != null) - { - Undelete(existingHashMatch); - return existingHashMatch; - } - - return null; - } - /// /// Downloads a beatmap. /// This will post notifications tracking progress. @@ -317,20 +306,6 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); - /// - /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. - /// - private string computeBeatmapSetHash(ArchiveReader reader) - { - // for now, concatenate all .osu files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - - return hashable.ComputeSHA2Hash(); - } - protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. @@ -349,7 +324,6 @@ namespace osu.Game.Beatmaps { OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), - Hash = computeBeatmapSetHash(reader), Metadata = beatmap.Metadata, }; } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 57983ec568..7e4dc4d6f5 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -6,14 +6,14 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Game.Database; using osu.Game.Users; namespace osu.Game.Beatmaps { [Serializable] - public class BeatmapMetadata : IEquatable + public class BeatmapMetadata : IEquatable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Title { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index 4fa286cbec..885fcc54f6 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -2,15 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetFileInfo : INamedFileInfo + public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int BeatmapSetInfoID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7a7d010a31..8c541e9344 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -10,7 +10,6 @@ namespace osu.Game.Beatmaps { public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } private int? onlineBeatmapSetID; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs index 160d081b8a..baeeaf81a4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -78,7 +78,7 @@ namespace osu.Game.Beatmaps.Drawables } } - private void setAdded(BeatmapSetInfo s) => Schedule(() => + private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() => { if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) DownloadState.Value = DownloadStatus.Downloaded; diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index 62990991b0..6c2822734b 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -9,7 +9,6 @@ namespace osu.Game.Configuration [Table("Settings")] public class DatabasedSetting : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3686b702c4..50767608af 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework.Extensions; using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; @@ -31,6 +32,8 @@ namespace osu.Game.Database where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { + public delegate void ItemAddedDelegate(TModel model, bool existing, bool silent); + /// /// Set an endpoint for notifications to be posted to. /// @@ -40,7 +43,7 @@ namespace osu.Game.Database /// Fired when a new becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public event Action ItemAdded; + public event ItemAddedDelegate ItemAdded; /// /// Fired when a is removed from the database. @@ -107,7 +110,7 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s)); + ModelStore.ItemAdded += (item, silent) => handleEvent(() => ItemAdded?.Invoke(item, false, silent)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); Files = new FileStore(contextFactory, storage); @@ -203,7 +206,12 @@ namespace osu.Game.Database try { var model = CreateModel(archive); - return model == null ? null : Import(model, archive); + + if (model == null) return null; + + model.Hash = computeHash(archive); + + return Import(model, false, archive); } catch (Exception e) { @@ -212,12 +220,34 @@ namespace osu.Game.Database } } + /// + /// Any file extensions which should be included in hash creation. + /// Generally should include all file types which determine the file's uniqueness. + /// Large files should be avoided if possible. + /// + protected abstract string[] HashableFileTypes { get; } + + /// + /// Create a SHA-2 hash from the provided archive based on file content of all files matching . + /// + private string computeHash(ArchiveReader reader) + { + // for now, concatenate all .osu files in the set to create a unique hash. + MemoryStream hashable = new MemoryStream(); + foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith))) + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + + return hashable.ComputeSHA2Hash(); + } + /// /// Import an item from a . /// /// The model to be imported. + /// Whether the user should be notified fo the import. /// An optional archive to use for model population. - public TModel Import(TModel item, ArchiveReader archive = null) + public TModel Import(TModel item, bool silent = false, ArchiveReader archive = null) { delayEvents(); @@ -235,7 +265,9 @@ namespace osu.Game.Database if (existing != null) { + Undelete(existing); Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + handleEvent(() => ItemAdded?.Invoke(existing, true, silent)); return existing; } @@ -245,7 +277,7 @@ namespace osu.Game.Database Populate(item, archive); // import to store - ModelStore.Add(item); + ModelStore.Add(item, silent); } catch (Exception e) { @@ -471,7 +503,12 @@ namespace osu.Game.Database { } - protected virtual TModel CheckForExisting(TModel model) => null; + /// + /// Check whether an existing model already exists for a new import item. + /// + /// The new model proposed for import. Note that has not yet been run on this model. + /// An existing model which matches the criteria to skip importing, else null. + protected virtual TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); private DbSet queryModel() => ContextFactory.Get().Set(); @@ -485,7 +522,9 @@ namespace osu.Game.Database if (ZipUtils.IsZipArchive(path)) return new ZipArchiveReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), Path.GetFileName(path)); if (Directory.Exists(path)) - return new LegacyFilesystemReader(path); + return new LegacyDirectoryArchiveReader(path); + if (File.Exists(path)) + return new LegacyFileArchiveReader(path); throw new InvalidFormatException($"{path} is not a valid archive"); } } diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index 3aaba37efc..b5ac22efc9 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -11,8 +11,9 @@ namespace osu.Game.Database /// The model representing a file. public interface IHasFiles where TFile : INamedFileInfo - { List Files { get; set; } + + string Hash { get; set; } } } diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index 2ee356baea..c62e78ea23 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -1,10 +1,15 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; + namespace osu.Game.Database { public interface IHasPrimaryKey { + [JsonIgnore] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] int ID { get; set; } } } diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs index 751024d184..04dcdad285 100644 --- a/osu.Game/Database/INamedFileInfo.cs +++ b/osu.Game/Database/INamedFileInfo.cs @@ -10,7 +10,11 @@ namespace osu.Game.Database /// public interface INamedFileInfo { + // An explicit foreign key property isn't required but is recommended and may be helpful to have + int FileInfoID { get; set; } + FileInfo FileInfo { get; set; } + string Filename { get; set; } } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 69a1f57cc4..dc2fe54aac 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -16,7 +16,9 @@ namespace osu.Game.Database public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { - public event Action ItemAdded; + public delegate void ItemAddedDelegate(T model, bool silent); + + public event ItemAddedDelegate ItemAdded; public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) @@ -33,7 +35,8 @@ namespace osu.Game.Database /// Add a to the database. /// /// The item to add. - public void Add(T item) + /// Whether the user should be notified of the addition. + public void Add(T item, bool silent) { using (var usage = ContextFactory.GetForWrite()) { @@ -41,7 +44,7 @@ namespace osu.Game.Database context.Attach(item); } - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, silent); } /// @@ -54,7 +57,7 @@ namespace osu.Game.Database usage.Context.Update(item); ItemRemoved?.Invoke(item); - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, true); } /// @@ -89,7 +92,7 @@ namespace osu.Game.Database item.DeletePending = false; } - ItemAdded?.Invoke(item); + ItemAdded?.Invoke(item, true); return true; } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 20e144c033..db5a2771d4 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Scoring; using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; @@ -27,6 +28,7 @@ namespace osu.Game.Database public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } + public DbSet ScoreInfo { get; set; } private readonly string connectionString; @@ -105,6 +107,9 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.IntAction); @@ -117,6 +122,8 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); modelBuilder.Entity().HasOne(b => b.BaseDifficulty); + + modelBuilder.Entity().HasIndex(b => b.OnlineScoreID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/IO/Archives/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs similarity index 85% rename from osu.Game/IO/Archives/LegacyFilesystemReader.cs rename to osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs index dcaaf41c77..cad056b66a 100644 --- a/osu.Game/IO/Archives/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs @@ -8,13 +8,13 @@ using System.Linq; namespace osu.Game.IO.Archives { /// - /// Reads an extracted legacy beatmap from disk. + /// Reads an archive from a directory on disk. /// - public class LegacyFilesystemReader : ArchiveReader + public class LegacyDirectoryArchiveReader : ArchiveReader { private readonly string path; - public LegacyFilesystemReader(string path) + public LegacyDirectoryArchiveReader(string path) : base(Path.GetFileName(path)) { // re-get full path to standardise with Directory.GetFiles return values below. diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs new file mode 100644 index 0000000000..28b5e628e4 --- /dev/null +++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.IO; + +namespace osu.Game.IO.Archives +{ + /// + /// Reads a file on disk as an archive. + /// Note: In this case, the file is not an extractable archive, use instead. + /// + public class LegacyFileArchiveReader : ArchiveReader + { + private readonly string path; + + public LegacyFileArchiveReader(string path) + : base(Path.GetFileName(path)) + { + // re-get full path to standardise + this.path = Path.GetFullPath(path); + } + + public override Stream GetStream(string name) => File.OpenRead(path); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { Name }; + + public override Stream GetUnderlyingStream() => null; + } +} diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 1804be90a0..edd389bc65 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -1,14 +1,13 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.ComponentModel.DataAnnotations.Schema; using System.IO; +using osu.Game.Database; namespace osu.Game.IO { - public class FileInfo + public class FileInfo : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Hash { get; set; } diff --git a/osu.Game/IPC/ScoreIPCChannel.cs b/osu.Game/IPC/ScoreIPCChannel.cs deleted file mode 100644 index a66b8ce1f3..0000000000 --- a/osu.Game/IPC/ScoreIPCChannel.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using System.Threading.Tasks; -using osu.Framework.Platform; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.IPC -{ - public class ScoreIPCChannel : IpcChannel - { - private readonly ScoreStore scores; - - public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null) - : base(host) - { - this.scores = scores; - MessageReceived += msg => - { - Debug.Assert(scores != null); - ImportAsync(msg.Path).ContinueWith(t => - { - if (t.Exception != null) throw t.Exception; - }, TaskContinuationOptions.OnlyOnFaulted); - }; - } - - public async Task ImportAsync(string path) - { - if (scores == null) - { - //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ScoreImportMessage { Path = path }); - return; - } - - scores.ReadReplayFile(path); - } - } - - public class ScoreImportMessage - { - public string Path; - } -} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index 7a230f32a0..e69dd30822 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -10,7 +10,6 @@ namespace osu.Game.Input.Bindings [Table("KeyBinding")] public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2f5f1aea3f..c58e845a6a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -43,6 +43,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), + new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), }; public IEnumerable InGameKeyBindings => new[] diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs new file mode 100644 index 0000000000..d96fa8bd34 --- /dev/null +++ b/osu.Game/Input/IdleTracker.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Input +{ + /// + /// Track whether the end-user is in an idle state, based on their last interaction with the game. + /// + public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput + { + private readonly double timeToIdle; + + private double lastInteractionTime; + + protected double TimeSpentIdle => Clock.CurrentTime - lastInteractionTime; + + /// + /// Whether the user is currently in an idle state. + /// + public IBindable IsIdle => isIdle; + + private readonly BindableBool isIdle = new BindableBool(); + + /// + /// Intstantiate a new . + /// + /// The length in milliseconds until an idle state should be assumed. + public IdleTracker(double timeToIdle) + { + this.timeToIdle = timeToIdle; + RelativeSizeAxes = Axes.Both; + } + + protected override void Update() + { + base.Update(); + isIdle.Value = TimeSpentIdle > timeToIdle; + } + + public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); + + public bool OnReleased(PlatformAction action) => updateLastInteractionTime(); + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent _: + case KeyUpEvent _: + case MouseDownEvent _: + case MouseUpEvent _: + case MouseMoveEvent _: + return updateLastInteractionTime(); + default: + return base.Handle(e); + } + } + + private bool updateLastInteractionTime() + { + lastInteractionTime = Clock.CurrentTime; + return false; + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("Status"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 663676a6d3..8026847e3b 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -281,6 +281,78 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.Property("ID") @@ -311,10 +383,17 @@ namespace osu.Game.Migrations b.Property("DeletePending"); + b.Property("Hash"); + b.Property("Name"); b.HasKey("ID"); + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + b.ToTable("SkinInfo"); }); @@ -360,6 +439,31 @@ namespace osu.Game.Migrations .HasForeignKey("MetadataID"); }); + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.HasOne("osu.Game.IO.FileInfo", "FileInfo") diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 7892df9aab..f60567a706 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -178,6 +178,7 @@ namespace osu.Game.Online.API AddParameter("grant_type", GrantType); AddParameter("client_id", ClientId); AddParameter("client_secret", ClientSecret); + AddParameter("scope", "*"); base.PrePerform(); } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3be5b91a0d..2751dd956b 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -33,8 +33,8 @@ namespace osu.Game.Online.API.Requests private void onSuccess(APIScores r) { - foreach (APIScore score in r.Scores) - score.ApplyBeatmap(beatmap); + foreach (APIScoreInfo score in r.Scores) + score.Beatmap = beatmap; } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 4d4aa4d957..12d70ea327 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 2c65a37cf8..c38c443ec3 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -4,7 +4,7 @@ using System; using Humanizer; using Newtonsoft.Json; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs similarity index 78% rename from osu.Game/Online/API/Requests/Responses/APIScore.cs rename to osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 25eb32a79f..838c4f95e4 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -7,16 +7,16 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScore : Score + public class APIScoreInfo : ScoreInfo { [JsonProperty(@"score")] - private double totalScore + private int totalScore { set => TotalScore = value; } @@ -33,15 +33,6 @@ namespace osu.Game.Online.API.Requests.Responses set => User = value; } - [JsonProperty(@"replay_data")] - private Replay replay - { - set => Replay = value; - } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - [JsonProperty(@"score_id")] private long onlineScoreID { @@ -104,21 +95,36 @@ namespace osu.Game.Online.API.Requests.Responses } } + [JsonProperty(@"mode_int")] + public int OnlineRulesetID { get; set; } + [JsonProperty(@"mods")] private string[] modStrings { get; set; } - public void ApplyBeatmap(BeatmapInfo beatmap) + public override BeatmapInfo Beatmap { - Beatmap = beatmap; - ApplyRuleset(beatmap.Ruleset); + get => base.Beatmap; + set + { + base.Beatmap = value; + if (Beatmap.Ruleset != null) + Ruleset = value.Ruleset; + } } - public void ApplyRuleset(RulesetInfo ruleset) + public override RulesetInfo Ruleset { - Ruleset = ruleset; + get => base.Ruleset; + set + { + base.Ruleset = value; - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + if (modStrings != null) + { + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray(); + } + } } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APIScores.cs index b4213db253..ccffa3f9ed 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScores.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScores.cs @@ -9,6 +9,6 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScores { [JsonProperty(@"scores")] - public IEnumerable Scores; + public IEnumerable Scores; } } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 73ac7c9df4..29f971078b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -31,6 +30,9 @@ namespace osu.Game.Online.Chat @"#lobby" }; + private readonly BindableCollection availableChannels = new BindableCollection(); + private readonly BindableCollection joinedChannels = new BindableCollection(); + /// /// The currently opened channel /// @@ -39,12 +41,12 @@ namespace osu.Game.Online.Chat /// /// The Channels the player has joined /// - public ObservableCollection JoinedChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableCollection JoinedChannels => joinedChannels; /// /// The channels available for the player to join /// - public ObservableCollection AvailableChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableCollection AvailableChannels => availableChannels; private IAPIProvider api; private ScheduledDelegate fetchMessagesScheduleder; @@ -293,8 +295,8 @@ namespace osu.Game.Online.Chat found.Users.Remove(foundSelf); } - if (joined == null && addToJoined) JoinedChannels.Add(found); - if (available == null && addToAvailable) AvailableChannels.Add(found); + if (joined == null && addToJoined) joinedChannels.Add(found); + if (available == null && addToAvailable) availableChannels.Add(found); return found; } @@ -346,9 +348,10 @@ namespace osu.Game.Online.Chat { if (channel == null) return; - if (channel == CurrentChannel.Value) CurrentChannel.Value = null; + if (channel == CurrentChannel.Value) + CurrentChannel.Value = null; - JoinedChannels.Remove(channel); + joinedChannels.Remove(channel); if (channel.Joined.Value) { diff --git a/osu.Game/Online/Multiplayer/GameType.cs b/osu.Game/Online/Multiplayer/GameType.cs index 750401c067..8d39e8f59d 100644 --- a/osu.Game/Online/Multiplayer/GameType.cs +++ b/osu.Game/Online/Multiplayer/GameType.cs @@ -14,6 +14,9 @@ namespace osu.Game.Online.Multiplayer { public abstract string Name { get; } public abstract Drawable GetIcon(OsuColour colours, float size); + + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); } public class GameTypeTag : GameType diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1b0a0b2210..cd40d4793a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -26,7 +26,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Screens.Play; @@ -36,6 +36,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osuTK.Graphics; using osu.Game.Overlays.Volume; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; @@ -88,6 +89,8 @@ namespace osu.Game public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + private IdleTracker idleTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); private OsuScreen screenStack; @@ -145,7 +148,7 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; - ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); + ScoreManager.ItemAdded += (score, _, silent) => Schedule(() => LoadScore(score, silent)); if (!Host.IsPrimaryInstance) { @@ -245,41 +248,69 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); - protected void LoadScore(Score score) + protected void LoadScore(ScoreInfo score, bool silent) { + if (silent) + return; + scoreLoad?.Cancel(); var menu = intro.ChildScreen; if (menu == null) { - scoreLoad = Schedule(() => LoadScore(score)); + scoreLoad = Schedule(() => LoadScore(score, false)); return; } - if (!menu.IsCurrentScreen) + var databasedScore = ScoreManager.GetScore(score); + var databasedScoreInfo = databasedScore.ScoreInfo; + if (databasedScore.Replay == null) { - menu.MakeCurrent(); - this.Delay(500).Schedule(() => LoadScore(score), out scoreLoad); + Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); return; } - if (score.Beatmap == null) + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + if (databasedBeatmap == null) + { + Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); + return; + } + + if (!currentScreen.AllowExternalScreenChange) { notifications.Post(new SimpleNotification { - Text = @"Tried to load a score for a beatmap we don't have!", - Icon = FontAwesome.fa_life_saver, + Text = $"Click here to watch {databasedScoreInfo.User.Username} on {databasedScoreInfo.Beatmap}", + Activated = () => + { + loadScore(); + return true; + } }); + return; } - ruleset.Value = score.Ruleset; + loadScore(); - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(score.Beatmap); - Beatmap.Value.Mods.Value = score.Mods; + void loadScore() + { + if (!menu.IsCurrentScreen) + { + menu.MakeCurrent(); + this.Delay(500).Schedule(loadScore, out scoreLoad); + return; + } - menu.Push(new PlayerLoader(new ReplayPlayer(score.Replay))); + ruleset.Value = databasedScoreInfo.Ruleset; + + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; + + currentScreen.Push(new PlayerLoader(new ReplayPlayer(databasedScore))); + } } protected override void Dispose(bool isDisposing) @@ -316,6 +347,7 @@ namespace osu.Game }, mainContent = new Container { RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + idleTracker = new IdleTracker(6000) }); loadComponentSingleFile(screenStack = new Loader(), d => @@ -373,6 +405,7 @@ namespace osu.Game Depth = -6, }, overlayContent.Add); + dependencies.Cache(idleTracker); dependencies.Cache(settings); dependencies.Cache(onscreenDisplay); dependencies.Cache(social); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 216bf198e9..cb85a2a660 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -28,7 +28,7 @@ using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Skinning; using osuTK.Input; using DebugUtils = osu.Game.Utils.DebugUtils; @@ -46,14 +46,14 @@ namespace osu.Game protected BeatmapManager BeatmapManager; + protected ScoreManager ScoreManager; + protected SkinManager SkinManager; protected RulesetStore RulesetStore; protected FileStore FileStore; - protected ScoreStore ScoreStore; - protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; @@ -156,14 +156,14 @@ namespace osu.Game dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(contextFactory, Host, BeatmapManager, RulesetStore)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); - fileImporters.Add(ScoreStore); + fileImporters.Add(ScoreManager); fileImporters.Add(SkinManager); var defaultBeatmap = new DummyWorkingBeatmap(this); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs index b172954c43..f643e130aa 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly Box background; - public DrawableScore(int index, APIScore score) + public DrawableScore(int index, APIScoreInfo score) { ScoreModsContainer modsContainer; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index aa785385ba..643839fa88 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; @@ -42,8 +43,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly InfoColumn statistics; private readonly ScoreModsContainer modsContainer; - private APIScore score; - public APIScore Score + private APIScoreInfo score; + public APIScoreInfo Score { get { return score; } set diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 38107c047d..e338d2f90f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -29,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; + private IEnumerable scores; private BeatmapInfo beatmap; - public IEnumerable Scores + public IEnumerable Scores { get { return scores; } set diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 5aff50a68a..6294851415 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + BeatmapSet = res.ToBeatmapSet(rulesets); header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; api.Queue(req); @@ -150,7 +150,7 @@ namespace osu.Game.Overlays { BeatmapSet = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => ShowBeatmapSet(res.ToBeatmapSet(rulesets)); + req.Success += res => BeatmapSet = res.ToBeatmapSet(rulesets); api.Queue(req); Show(); } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 8930a9ff4c..d026540fd9 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using System.Collections.Specialized; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -181,28 +180,6 @@ namespace osu.Game.Overlays channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel); } - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (Channel newChannel in args.NewItems) - { - channelTabControl.AddChannel(newChannel); - } - - break; - case NotifyCollectionChangedAction.Remove: - foreach (Channel removedChannel in args.OldItems) - { - channelTabControl.RemoveChannel(removedChannel); - loadedChannels.Remove(loadedChannels.Find(c => c.Channel == removedChannel)); - } - - break; - } - } - private void currentChannelChanged(Channel channel) { if (channel == null) @@ -322,19 +299,35 @@ namespace osu.Game.Overlays this.channelManager = channelManager; channelManager.CurrentChannel.ValueChanged += currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; + channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; //for the case that channelmanager was faster at fetching the channels than our attachment to CollectionChanged. channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); - joinedChannelsChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, channelManager.JoinedChannels)); + foreach (Channel channel in channelManager.JoinedChannels) + channelTabControl.AddChannel(channel); } - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onChannelAddedToJoinedChannels(IEnumerable channels) { - channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + foreach (Channel channel in channels) + channelTabControl.AddChannel(channel); } + private void onChannelRemovedFromJoinedChannels(IEnumerable channels) + { + foreach (Channel channel in channels) + { + channelTabControl.RemoveChannel(channel); + loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); + } + } + + private void availableChannelsChanged(IEnumerable channels) + => channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -342,8 +335,10 @@ namespace osu.Game.Overlays if (channelManager != null) { channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; + channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 44f7b6d49c..44556a6360 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Direct }; } - private void setAdded(BeatmapSetInfo s) => Schedule(() => + private void setAdded(BeatmapSetInfo s, bool existing, bool silent) => Schedule(() => { if (s.OnlineBeatmapSetID == SetInfo.OnlineBeatmapSetID) progressBar.FadeOut(500); diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index ef7f946859..b619abbc2f 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap) { - beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); + beatmaps.GetAllUsableBeatmapSets().ForEach(b => addBeatmapSet(b, false, false)); beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet; @@ -83,8 +83,11 @@ namespace osu.Game.Overlays.Music beatmapBacking.ValueChanged += _ => updateSelectedSet(); } - private void addBeatmapSet(BeatmapSetInfo obj) => Schedule(() => + private void addBeatmapSet(BeatmapSetInfo obj, bool existing, bool silent) => Schedule(() => { + if (existing) + return; + var newItem = new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) }; items.Add(newItem); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 58e03bd0cd..2dc997d5ed 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -214,7 +214,14 @@ namespace osu.Game.Overlays beatmapSets.Insert(index, beatmapSetInfo); } - private void handleBeatmapAdded(BeatmapSetInfo obj) => Schedule(() => beatmapSets.Add(obj)); + private void handleBeatmapAdded(BeatmapSetInfo obj, bool existing, bool silent) + { + if (existing) + return; + + Schedule(() => beatmapSets.Add(obj)); + } + private void handleBeatmapRemoved(BeatmapSetInfo obj) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == obj.ID)); protected override void LoadComplete() diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs index ddbe10c05c..98a4bdba45 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs @@ -5,7 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double? weight; - public DrawablePerformanceScore(Score score, double? weight = null) + public DrawablePerformanceScore(ScoreInfo score, double? weight = null) : base(score) { this.weight = weight; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index a20054351d..1c39cb309c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -8,17 +8,17 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public abstract class DrawableProfileScore : DrawableProfileRow { private readonly ScoreModsContainer modsContainer; - protected readonly Score Score; + protected readonly ScoreInfo Score; - protected DrawableProfileScore(Score score) + protected DrawableProfileScore(ScoreInfo score) { Score = score; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs index 3a39ef5d61..a48468e4e5 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs @@ -4,13 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public class DrawableTotalScore : DrawableProfileScore { - public DrawableTotalScore(Score score) + public DrawableTotalScore(ScoreInfo score) : base(score) { } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index ed82c62e5c..f92990fc5d 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks request.Success += scores => Schedule(() => { foreach (var s in scores) - s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); + s.Ruleset = Rulesets.GetRuleset(s.RulesetID); ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); ShowMoreLoading.Hide(); @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks MissingText.Hide(); - foreach (APIScore score in scores) + foreach (APIScoreInfo score in scores) { DrawableProfileScore drawableScore; diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 938e2ca2c3..23f35d5d3a 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -71,7 +71,14 @@ namespace osu.Game.Overlays.Settings.Sections } private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); - private void itemAdded(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray()); + + private void itemAdded(SkinInfo s, bool existing, bool silent) + { + if (existing) + return; + + Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray()); + } protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index c67ed5b845..1263ecd303 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Volume private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; - public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 }; private readonly float circleSize; private readonly Color4 meterColour; private readonly string name; @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Volume private set => Bindable.Value = value; } - private const float adjust_step = 0.05f; + private const double adjust_step = 0.05; public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise); public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise); @@ -236,7 +236,7 @@ namespace osu.Game.Overlays.Volume var precision = Bindable.Precision; - while (Math.Abs(scrollAccumulation) > precision) + while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) { Volume += Math.Sign(scrollAccumulation) * precision; scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); diff --git a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs similarity index 95% rename from osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs rename to osu.Game/Replays/Legacy/LegacyReplayFrame.cs index f6de11454c..ac9fd96ae6 100644 --- a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,9 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Rulesets.Replays; using osuTK; -namespace osu.Game.Rulesets.Replays.Legacy +namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { diff --git a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Replays/Legacy/ReplayButtonState.cs similarity index 88% rename from osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs rename to osu.Game/Replays/Legacy/ReplayButtonState.cs index ee09414287..ffeff55e96 100644 --- a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs +++ b/osu.Game/Replays/Legacy/ReplayButtonState.cs @@ -3,7 +3,7 @@ using System; -namespace osu.Game.Rulesets.Replays.Legacy +namespace osu.Game.Replays.Legacy { [Flags] public enum ReplayButtonState diff --git a/osu.Game/Rulesets/Replays/Replay.cs b/osu.Game/Replays/Replay.cs similarity index 77% rename from osu.Game/Rulesets/Replays/Replay.cs rename to osu.Game/Replays/Replay.cs index 8fbe4c8194..bb6d9e7637 100644 --- a/osu.Game/Rulesets/Replays/Replay.cs +++ b/osu.Game/Replays/Replay.cs @@ -2,13 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using osu.Game.Users; +using osu.Game.Rulesets.Replays; -namespace osu.Game.Rulesets.Replays +namespace osu.Game.Replays { public class Replay { - public User User; public List Frames = new List(); } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 0de0a620e3..25fd49ff9b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty { case 0: // Initial-case: Empty current set - yield return new NoModMod(); + yield return new ModNoMod(); break; case 1: yield return currentSet.Single(); diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index ba783ee87b..48a68928de 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -7,7 +7,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Difficulty { @@ -17,11 +17,11 @@ namespace osu.Game.Rulesets.Difficulty protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; - protected readonly Score Score; + protected readonly ScoreInfo Score; protected double TimeRate { get; private set; } = 1; - protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) { Ruleset = ruleset; Score = score; diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs index bc54c907ab..6f81d47431 100644 --- a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs +++ b/osu.Game/Rulesets/Edit/EditRulesetContainer.cs @@ -68,9 +68,9 @@ namespace osu.Game.Rulesets.Edit // Process object var processor = ruleset.CreateBeatmapProcessor(beatmap); - processor.PreProcess(); + processor?.PreProcess(); tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - processor.PostProcess(); + processor?.PostProcess(); // Add visual representation var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); @@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Edit // Process the beatmap var processor = ruleset.CreateBeatmapProcessor(beatmap); - processor.PreProcess(); - processor.PostProcess(); + processor?.PreProcess(); + processor?.PostProcess(); // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 86ecbc0bb0..da7c3dda6a 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - public IEnumerable HitObjects => rulesetContainer.Playfield.AllHitObjects; + public IEnumerable HitObjects => RulesetContainer.Playfield.AllHitObjects; protected readonly Ruleset Ruleset; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - private EditRulesetContainer rulesetContainer; + protected EditRulesetContainer RulesetContainer { get; private set; } private BlueprintContainer blueprintContainer; @@ -54,8 +54,8 @@ namespace osu.Game.Rulesets.Edit try { - rulesetContainer = CreateRulesetContainer(); - rulesetContainer.Clock = framedClock; + RulesetContainer = CreateRulesetContainer(); + RulesetContainer.Clock = framedClock; } catch (Exception e) { @@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - rulesetContainer, + RulesetContainer, layerAboveRuleset } } @@ -140,25 +140,25 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = rulesetContainer.Playfield.Anchor; - l.Origin = rulesetContainer.Playfield.Origin; - l.Position = rulesetContainer.Playfield.Position; - l.Size = rulesetContainer.Playfield.Size; + l.Anchor = RulesetContainer.Playfield.Anchor; + l.Origin = RulesetContainer.Playfield.Origin; + l.Position = RulesetContainer.Playfield.Position; + l.Size = RulesetContainer.Playfield.Size; }); } /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// - public virtual bool CursorInPlacementArea => rulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public virtual bool CursorInPlacementArea => RulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// /// Adds a to the and visualises it. /// /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(rulesetContainer.Add(hitObject)); + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(RulesetContainer.Add(hitObject)); - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(rulesetContainer.Remove(hitObject)); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(RulesetContainer.Remove(hitObject)); internal abstract EditRulesetContainer CreateRulesetContainer(); @@ -171,10 +171,9 @@ namespace osu.Game.Rulesets.Edit public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s - /// and handles hitobject pattern adjustments. + /// Creates a which outlines s and handles movement of selections. /// - public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); + public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); /// /// Creates a which provides a layer above or below the . diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2414a682e9..1d568313ca 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint which governs the creation of a new to actualisation. /// - public abstract class PlacementBlueprint : CompositeDrawable, IStateful, IRequireHighFrequencyMousePosition + public abstract class PlacementBlueprint : CompositeDrawable, IStateful { /// /// Invoked when has changed. @@ -50,6 +49,10 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; + // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle + // on the same frame it is made visible via a PlacementState change. + AlwaysPresent = true; + Alpha = 0; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index b94b6a89a7..ec4df01e55 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Edit /// /// Invoked when this has requested drag. /// - public event Action DragRequested; + public event Action DragRequested; /// /// The which this applies to. @@ -130,12 +130,10 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDrag(DragEvent e) { - DragRequested?.Invoke(e); + DragRequested?.Invoke(this, e); return true; } - public abstract void AdjustPosition(DragEvent dragEvent); - /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs new file mode 100644 index 0000000000..d0c4ce2f4c --- /dev/null +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Mods +{ + public interface IMod + { + /// + /// The shortened name of this mod. + /// + string Acronym { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a991f7e7b0..14ee6b99ec 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -3,57 +3,67 @@ using osu.Game.Graphics; using System; +using Newtonsoft.Json; +using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Mods { /// /// The base class for gameplay modifiers. /// - public abstract class Mod + public abstract class Mod : IMod, IJsonSerializable { /// /// The name of this mod. /// + [JsonIgnore] public abstract string Name { get; } /// /// The shortened name of this mod. /// - public abstract string ShortenedName { get; } + public abstract string Acronym { get; } /// /// The icon of this mod. /// + [JsonIgnore] public virtual FontAwesome Icon => FontAwesome.fa_question; /// /// The type of this mod. /// + [JsonIgnore] public virtual ModType Type => ModType.Fun; /// /// The user readable description of this mod. /// + [JsonIgnore] public virtual string Description => string.Empty; /// /// The score multiplier of this mod. /// + [JsonIgnore] public abstract double ScoreMultiplier { get; } /// /// Returns true if this mod is implemented (and playable). /// + [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; /// /// Returns if this mod is ranked. /// + [JsonIgnore] public virtual bool Ranked => false; /// /// The mods this mod cannot be enabled with. /// + [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 5c03cb9736..932439618d 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -4,10 +4,10 @@ using System; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModAutoplay : Mod, IApplicableFailOverride { public override string Name => "Autoplay"; - public override string ShortenedName => "AT"; + public override string Acronym => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 421e3a54e4..5c4040ee4c 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mods public class ModCinema : ModAutoplay { public override string Name => "Cinema"; - public override string ShortenedName => "CN"; + public override string Acronym => "CN"; public override bool HasImplementation => false; public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; public override string Description => "Watch the video without visual distractions."; diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index f11d0c76ed..5e3ec60be3 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModDaycore : ModHalfTime { public override string Name => "Daycore"; - public override string ShortenedName => "DC"; + public override string Acronym => "DC"; public override FontAwesome Icon => FontAwesome.fa_question; public override string Description => "Whoaaaaa..."; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 0d44495fce..5c1d732995 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModDoubleTime : Mod, IApplicableToClock { public override string Name => "Double Time"; - public override string ShortenedName => "DT"; + public override string Acronym => "DT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 93abb82e5b..03781bea47 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; - public override string ShortenedName => "EZ"; + public override string Acronym => "EZ"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 9388d40146..98da97d6fe 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -20,16 +20,23 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModFlashlight : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor - where T : HitObject + public abstract class ModFlashlight : Mod { public override string Name => "Flashlight"; - public override string ShortenedName => "FL"; + public override string Acronym => "FL"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; + internal ModFlashlight() + { + } + } + + public abstract class ModFlashlight : ModFlashlight, IApplicableToRulesetContainer, IApplicableToScoreProcessor + where T : HitObject + { public const double FLASHLIGHT_FADE_DURATION = 800; protected readonly BindableInt Combo = new BindableInt(); diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 3ec4fb9f9f..2d95b10db1 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHalfTime : Mod, IApplicableToClock { public override string Name => "Half Time"; - public override string ShortenedName => "HT"; + public override string Acronym => "HT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 6379be9bfc..14f997ae8c 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHardRock : Mod, IApplicableToDifficulty { public override string Name => "Hard Rock"; - public override string ShortenedName => "HR"; + public override string Acronym => "HR"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 9b09f0bd6d..b843171521 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects { public override string Name => "Hidden"; - public override string ShortenedName => "HD"; + public override string Acronym => "HD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 162d654965..f4fe1c3831 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNightcore : ModDoubleTime { public override string Name => "Nightcore"; - public override string ShortenedName => "NC"; + public override string Acronym => "NC"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; public override string Description => "Uguuuuuuuu..."; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 7510f62432..223af7d304 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNoFail : Mod, IApplicableFailOverride { public override string Name => "No Fail"; - public override string ShortenedName => "NF"; + public override string Acronym => "NF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; diff --git a/osu.Game/Rulesets/Mods/NoModMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs similarity index 81% rename from osu.Game/Rulesets/Mods/NoModMod.cs rename to osu.Game/Rulesets/Mods/ModNoMod.cs index dcab3538da..c75849149b 100644 --- a/osu.Game/Rulesets/Mods/NoModMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -6,10 +6,10 @@ namespace osu.Game.Rulesets.Mods /// /// Indicates a type of mod that doesn't do anything. /// - public sealed class NoModMod : Mod + public sealed class ModNoMod : Mod { public override string Name => "No Mod"; - public override string ShortenedName => "NM"; + public override string Acronym => "NM"; public override double ScoreMultiplier => 1; } } diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 802890866f..b7ded41024 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModPerfect : ModSuddenDeath { public override string Name => "Perfect"; - public override string ShortenedName => "PF"; + public override string Acronym => "PF"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; public override string Description => "SS or quit."; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 04aa295893..769cde6746 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModRelax : Mod { public override string Name => "Relax"; - public override string ShortenedName => "RX"; + public override string Acronym => "RX"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48f7d496a5..77d411d8fd 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor { public override string Name => "Sudden Death"; - public override string ShortenedName => "SD"; + public override string Acronym => "SD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index b65773e93f..26e09a59e0 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public class MultiMod : Mod { public override string Name => string.Empty; - public override string ShortenedName => string.Empty; + public override string Acronym => string.Empty; public override string Description => string.Empty; public override double ScoreMultiplier => 0; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5490e75c14..8718269eed 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -84,6 +84,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; + public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart; + public readonly Bindable State = new Bindable(); protected DrawableHitObject(HitObject hitObject) diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 3ac0c3297c..1b6f17a5e4 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -3,6 +3,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; +using osu.Game.Replays; namespace osu.Game.Rulesets.Replays { diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index b10c6e1f15..abe1a1520d 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osuTK; using osuTK.Input; diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs index 4ef5f16f39..90aea199c8 100644 --- a/osu.Game/Rulesets/Replays/IAutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/IAutoGenerator.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Game.Replays; + namespace osu.Game.Rulesets.Replays { public interface IAutoGenerator diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index fdd528f296..9ece4b80d5 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Replays.Legacy; namespace osu.Game.Rulesets.Replays.Types { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 82d9945ef7..53e3e40374 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -12,12 +12,12 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets { @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); - public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => null; + public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index a6a311f6eb..6c21b9fd2f 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -2,15 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int? ID { get; set; } public string Name { get; set; } diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs deleted file mode 100644 index a90cd79186..0000000000 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Game.Beatmaps; -using osu.Game.IO.Legacy; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Users; -using SharpCompress.Compressors.LZMA; -using osu.Game.Beatmaps.Legacy; -using System.Linq; - -namespace osu.Game.Rulesets.Scoring.Legacy -{ - public abstract class LegacyScoreParser - { - private IBeatmap currentBeatmap; - private Ruleset currentRuleset; - - public Score Parse(Stream stream) - { - Score score; - - using (SerializationReader sr = new SerializationReader(stream)) - { - currentRuleset = GetRuleset(sr.ReadByte()); - score = new Score { Ruleset = currentRuleset.RulesetInfo }; - - /* score.Pass = true;*/ - var version = sr.ReadInt32(); - - /* score.FileChecksum = */ - currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap; - score.Beatmap = currentBeatmap.BeatmapInfo; - - /* score.PlayerName = */ - score.User = new User { Username = sr.ReadString() }; - /* var localScoreChecksum = */ - sr.ReadString(); - - var count300 = sr.ReadUInt16(); - var count100 = sr.ReadUInt16(); - var count50 = sr.ReadUInt16(); - var countGeki = sr.ReadUInt16(); - var countKatu = sr.ReadUInt16(); - var countMiss = sr.ReadUInt16(); - - score.Statistics[HitResult.Great] = count300; - score.Statistics[HitResult.Good] = count100; - score.Statistics[HitResult.Meh] = count50; - score.Statistics[HitResult.Perfect] = countGeki; - score.Statistics[HitResult.Ok] = countKatu; - score.Statistics[HitResult.Miss] = countMiss; - - score.TotalScore = sr.ReadInt32(); - score.MaxCombo = sr.ReadUInt16(); - /* score.Perfect = */ - sr.ReadBoolean(); - /* score.EnabledMods = (Mods)*/ - score.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); - /* score.HpGraphString = */ - sr.ReadString(); - /* score.Date = */ - sr.ReadDateTime(); - - var compressedReplay = sr.ReadByteArray(); - - if (version >= 20140721) - /*OnlineId =*/ - sr.ReadInt64(); - else if (version >= 20121008) - /*OnlineId =*/ - sr.ReadInt32(); - - switch (score.Ruleset.ID) - { - case 0: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 1: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 2: - { - int totalHits = count50 + count100 + count300 + countMiss + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1; - break; - } - case 3: - { - int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; - break; - } - } - - using (var replayInStream = new MemoryStream(compressedReplay)) - { - byte[] properties = new byte[5]; - if (replayInStream.Read(properties, 0, 5) != 5) - throw new IOException("input .lzma is too short"); - long outSize = 0; - for (int i = 0; i < 8; i++) - { - int v = replayInStream.ReadByte(); - if (v < 0) - throw new IOException("Can't Read 1"); - outSize |= (long)(byte)v << (8 * i); - } - - long compressedSize = replayInStream.Length - replayInStream.Position; - - using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) - using (var reader = new StreamReader(lzma)) - { - score.Replay = new Replay { User = score.User }; - readLegacyReplay(score.Replay, reader); - } - } - } - - return score; - } - - private void readLegacyReplay(Replay replay, StreamReader reader) - { - float lastTime = 0; - - foreach (var l in reader.ReadToEnd().Split(',')) - { - var split = l.Split('|'); - - if (split.Length < 4) - continue; - - if (split[0] == "-12345") - { - // Todo: The seed is provided in split[3], which we'll need to use at some point - continue; - } - - var diff = float.Parse(split[0]); - lastTime += diff; - - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) - continue; - - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); - } - } - - private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) - { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); - if (convertible == null) - throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(legacyFrame, currentBeatmap); - - var frame = (ReplayFrame)convertible; - frame.Time = legacyFrame.Time; - - return frame; - } - - /// - /// Retrieves the for a specific id. - /// - /// The id. - /// The . - protected abstract Ruleset GetRuleset(int rulesetId); - - /// - /// Retrieves the corresponding to an MD5 hash. - /// - /// The MD5 hash. - /// The . - protected abstract WorkingBeatmap GetBeatmap(string md5Hash); - } -} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs deleted file mode 100644 index 02f528791a..0000000000 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Scoring -{ - public class Score - { - public ScoreRank Rank { get; set; } - - public double TotalScore { get; set; } - - public double Accuracy { get; set; } - - public double Health { get; set; } = 1; - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } - - public RulesetInfo Ruleset { get; set; } - - public Mod[] Mods { get; set; } = { }; - - public User User; - - [JsonIgnore] - public Replay Replay; - - public BeatmapInfo Beatmap; - - public long OnlineScoreID; - - public DateTimeOffset Date; - - public Dictionary Statistics = new Dictionary(); - } -} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b0cea7009e..0ebea9c2d0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring { @@ -157,15 +158,14 @@ namespace osu.Game.Rulesets.Scoring /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// - public virtual void PopulateScore(Score score) + public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = TotalScore; + score.TotalScore = (int)Math.Round(TotalScore); score.Combo = Combo; score.MaxCombo = HighestCombo; - score.Accuracy = Accuracy; + score.Accuracy = Math.Round(Accuracy, 4); score.Rank = Rank; score.Date = DateTimeOffset.Now; - score.Health = Health; } } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs deleted file mode 100644 index 091cb29a71..0000000000 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; -using System.IO; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IPC; -using osu.Game.Rulesets.Scoring.Legacy; - -namespace osu.Game.Rulesets.Scoring -{ - public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles - { - private readonly BeatmapManager beatmaps; - private readonly RulesetStore rulesets; - - private const string replay_folder = @"replays"; - - public event Action ScoreImported; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ScoreIPCChannel ipc; - - public ScoreStore(DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) - { - this.beatmaps = beatmaps; - this.rulesets = rulesets; - - if (importHost != null) - ipc = new ScoreIPCChannel(importHost, this); - } - - public string[] HandledExtensions => new[] { ".osr" }; - - public void Import(params string[] paths) - { - foreach (var path in paths) - { - var score = ReadReplayFile(path); - if (score != null) - ScoreImported?.Invoke(score); - } - } - - public Score ReadReplayFile(string replayFilename) - { - if (File.Exists(replayFilename)) - { - using (var stream = File.OpenRead(replayFilename)) - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream); - } - - Logger.Log($"Replay file {replayFilename} cannot be found", LoggingTarget.Information, LogLevel.Error); - return null; - } - } -} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 579462b11c..1e9a19ae70 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.UI /// Remove a DrawableHitObject from this Playfield. /// /// The DrawableHitObject to remove. - public virtual void Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); + public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); /// /// Registers a as a nested . diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 0abd358113..5967cad8d1 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -19,8 +19,8 @@ using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Overlays; +using osu.Game.Replays; using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.UI diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs similarity index 94% rename from osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs index bfb2b7c13b..5866c04192 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs @@ -2,8 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; +using osu.Game.Rulesets; -namespace osu.Game.Rulesets.Scoring.Legacy +namespace osu.Game.Scoring.Legacy { /// /// A which retrieves the applicable and diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs new file mode 100644 index 0000000000..13fe021f95 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -0,0 +1,277 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.IO; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.IO.Legacy; +using osu.Game.Replays; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using SharpCompress.Compressors.LZMA; + +namespace osu.Game.Scoring.Legacy +{ + public abstract class LegacyScoreParser + { + private IBeatmap currentBeatmap; + private Ruleset currentRuleset; + + public Score Parse(Stream stream) + { + var score = new Score + { + ScoreInfo = new ScoreInfo(), + Replay = new Replay() + }; + + using (SerializationReader sr = new SerializationReader(stream)) + { + currentRuleset = GetRuleset(sr.ReadByte()); + score.ScoreInfo = new ScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + + var version = sr.ReadInt32(); + + var workingBeatmap = GetBeatmap(sr.ReadString()); + if (workingBeatmap is DummyWorkingBeatmap) + throw new BeatmapNotFoundException(); + + currentBeatmap = workingBeatmap.Beatmap; + score.ScoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + + score.ScoreInfo.User = new User { Username = sr.ReadString() }; + + // MD5Hash + sr.ReadString(); + + var count300 = (int)sr.ReadUInt16(); + var count100 = (int)sr.ReadUInt16(); + var count50 = (int)sr.ReadUInt16(); + var countGeki = (int)sr.ReadUInt16(); + var countKatu = (int)sr.ReadUInt16(); + var countMiss = (int)sr.ReadUInt16(); + + score.ScoreInfo.Statistics[HitResult.Great] = count300; + score.ScoreInfo.Statistics[HitResult.Good] = count100; + score.ScoreInfo.Statistics[HitResult.Meh] = count50; + score.ScoreInfo.Statistics[HitResult.Perfect] = countGeki; + score.ScoreInfo.Statistics[HitResult.Ok] = countKatu; + score.ScoreInfo.Statistics[HitResult.Miss] = countMiss; + + score.ScoreInfo.TotalScore = sr.ReadInt32(); + score.ScoreInfo.MaxCombo = sr.ReadUInt16(); + + /* score.Perfect = */ + sr.ReadBoolean(); + + score.ScoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + + /* score.HpGraphString = */ + sr.ReadString(); + + score.ScoreInfo.Date = sr.ReadDateTime(); + + var compressedReplay = sr.ReadByteArray(); + + if (version >= 20140721) + score.ScoreInfo.OnlineScoreID = sr.ReadInt64(); + else if (version >= 20121008) + score.ScoreInfo.OnlineScoreID = sr.ReadInt32(); + + if (compressedReplay?.Length > 0) + { + using (var replayInStream = new MemoryStream(compressedReplay)) + { + byte[] properties = new byte[5]; + if (replayInStream.Read(properties, 0, 5) != 5) + throw new IOException("input .lzma is too short"); + long outSize = 0; + for (int i = 0; i < 8; i++) + { + int v = replayInStream.ReadByte(); + if (v < 0) + throw new IOException("Can't Read 1"); + outSize |= (long)(byte)v << (8 * i); + } + + long compressedSize = replayInStream.Length - replayInStream.Position; + + using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) + using (var reader = new StreamReader(lzma)) + readLegacyReplay(score.Replay, reader); + } + } + } + + calculateAccuracy(score.ScoreInfo); + + return score; + } + + private void calculateAccuracy(ScoreInfo score) + { + int countMiss = (int)score.Statistics[HitResult.Miss]; + int count50 = (int)score.Statistics[HitResult.Meh]; + int count100 = (int)score.Statistics[HitResult.Good]; + int count300 = (int)score.Statistics[HitResult.Great]; + int countGeki = (int)score.Statistics[HitResult.Perfect]; + int countKatu = (int)score.Statistics[HitResult.Ok]; + + switch (score.Ruleset.ID) + { + case 0: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (ratio300 > 0.8 && countMiss == 0 || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if (ratio300 > 0.7 && countMiss == 0 || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 1: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (ratio300 > 0.8 && countMiss == 0 || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if (ratio300 > 0.7 && countMiss == 0 || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 2: + { + int totalHits = count50 + count100 + count300 + countMiss + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.98) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.94) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.85) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + case 3: + { + int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.95) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.8) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.7) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + } + } + + private void readLegacyReplay(Replay replay, StreamReader reader) + { + float lastTime = 0; + + foreach (var l in reader.ReadToEnd().Split(',')) + { + var split = l.Split('|'); + + if (split.Length < 4) + continue; + + if (split[0] == "-12345") + { + // Todo: The seed is provided in split[3], which we'll need to use at some point + continue; + } + + var diff = float.Parse(split[0]); + lastTime += diff; + + // Todo: At some point we probably want to rewind and play back the negative-time frames + // but for now we'll achieve equal playback to stable by skipping negative frames + if (diff < 0) + continue; + + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); + } + } + + private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) + { + var convertible = currentRuleset.CreateConvertibleReplayFrame(); + if (convertible == null) + throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); + convertible.ConvertFrom(legacyFrame, currentBeatmap); + + var frame = (ReplayFrame)convertible; + frame.Time = legacyFrame.Time; + + return frame; + } + + /// + /// Retrieves the for a specific id. + /// + /// The id. + /// The . + protected abstract Ruleset GetRuleset(int rulesetId); + + /// + /// Retrieves the corresponding to an MD5 hash. + /// + /// The MD5 hash. + /// The . + protected abstract WorkingBeatmap GetBeatmap(string md5Hash); + + public class BeatmapNotFoundException : Exception + { + public BeatmapNotFoundException() + : base("No corresponding beatmap for the score could be found.") + { + } + } + } +} diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs new file mode 100644 index 0000000000..768c5da7af --- /dev/null +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class LegacyDatabasedScore : Score + { + public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) + { + ScoreInfo = score; + + var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + + using (var stream = store.GetStream(replayFilename)) + Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; + } + } +} diff --git a/osu.Game/Scoring/Score.cs b/osu.Game/Scoring/Score.cs new file mode 100644 index 0000000000..ffbee920cb --- /dev/null +++ b/osu.Game/Scoring/Score.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Replays; + +namespace osu.Game.Scoring +{ + public class Score + { + public ScoreInfo ScoreInfo = new ScoreInfo(); + public Replay Replay = new Replay(); + } +} diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs new file mode 100644 index 0000000000..e5595397b8 --- /dev/null +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Scoring +{ + public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey + { + public int ID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs new file mode 100644 index 0000000000..e6bab194b0 --- /dev/null +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -0,0 +1,138 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + { + public int ID { get; set; } + + public ScoreRank Rank { get; set; } + + public int TotalScore { get; set; } + + [Column(TypeName="DECIMAL(1,4)")] + public double Accuracy { get; set; } + + public double? PP { get; set; } + + public int MaxCombo { get; set; } + + public int Combo { get; set; } + + public int RulesetID { get; set; } + + public virtual RulesetInfo Ruleset { get; set; } + + private Mod[] mods; + + [NotMapped] + public Mod[] Mods + { + get + { + if (mods != null) + return mods; + + if (modsJson == null) + return Array.Empty(); + + return getModsFromRuleset(JsonConvert.DeserializeObject(modsJson)); + } + set + { + modsJson = null; + mods = value; + } + } + + private Mod[] getModsFromRuleset(DeserializedMod[] mods) => Ruleset.CreateInstance().GetAllMods().Where(mod => mods.Any(d => d.Acronym == mod.Acronym)).ToArray(); + + private string modsJson; + + [Column("Mods")] + public string ModsJson + { + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods); + } + set + { + modsJson = value; + + // we potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + mods = null; + } + } + + [JsonIgnore] + public User User; + + [Column("User")] + public string UserString + { + get => User?.Username; + set => User = new User { Username = value }; + } + + [JsonIgnore] + public int BeatmapInfoID { get; set; } + + public virtual BeatmapInfo Beatmap { get; set; } + + public long? OnlineScoreID { get; set; } + + public DateTimeOffset Date { get; set; } + + [JsonIgnore] + public Dictionary Statistics = new Dictionary(); + + [Column("Statistics")] + public string StatisticsJson + { + get => JsonConvert.SerializeObject(Statistics); + set + { + if (value == null) + { + Statistics.Clear(); + return; + } + + Statistics = JsonConvert.DeserializeObject>(value); + } + } + + [JsonIgnore] + public List Files { get; set; } + + public string Hash { get; set; } + + public bool DeletePending { get; set; } + + [Serializable] + protected class DeserializedMod : IMod + { + public string Acronym { get; set; } + } + } +} diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs new file mode 100644 index 0000000000..663f441f2f --- /dev/null +++ b/osu.Game/Scoring/ScoreManager.cs @@ -0,0 +1,64 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class ScoreManager : ArchiveModelManager + { + public override string[] HandledExtensions => new[] { ".osr" }; + + protected override string[] HashableFileTypes => new[] { ".osr" }; + + protected override string ImportFromStablePath => "Replays"; + + private readonly RulesetStore rulesets; + private readonly BeatmapManager beatmaps; + + public ScoreManager(RulesetStore rulesets, BeatmapManager beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + protected override ScoreInfo CreateModel(ArchiveReader archive) + { + if (archive == null) + return null; + + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr")))) + { + try + { + return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).ScoreInfo; + } + catch (LegacyScoreParser.BeatmapNotFoundException e) + { + Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); + return null; + } + } + } + + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps, Files.Store); + + public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + + public IEnumerable QueryScores(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query); + + public ScoreInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs similarity index 94% rename from osu.Game/Rulesets/Scoring/ScoreRank.cs rename to osu.Game/Scoring/ScoreRank.cs index d4113bb08a..efc513c39c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreRank.cs +++ b/osu.Game/Scoring/ScoreRank.cs @@ -3,7 +3,7 @@ using System.ComponentModel; -namespace osu.Game.Rulesets.Scoring +namespace osu.Game.Scoring { public enum ScoreRank { diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs new file mode 100644 index 0000000000..8b99bc0e8d --- /dev/null +++ b/osu.Game/Scoring/ScoreStore.cs @@ -0,0 +1,24 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.Scoring +{ + public class ScoreStore : MutableDatabaseBackedStore + { + public ScoreStore(IDatabaseContextFactory factory, Storage storage) + : base(factory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) + => base.AddIncludesForConsumption(query) + .Include(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(s => s.Beatmap) + .Include(s => s.Ruleset); + } +} diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 7d68f32495..d2e51d1f57 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -13,6 +13,7 @@ using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK.Input; namespace osu.Game.Screens.Edit.Components { @@ -63,6 +64,18 @@ namespace osu.Game.Screens.Edit.Components tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; } + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Space: + togglePause(); + return true; + } + + return base.OnKeyDown(e); + } + private void togglePause() { if (adjustableClock.IsRunning) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 1623ef0100..80bfe21367 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; - private SelectionBox selectionBox; + private SelectionHandler selectionHandler; private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); @@ -37,16 +37,16 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - selectionBox = composer.CreateSelectionBox(); - selectionBox.DeselectAll = deselectAll; + selectionHandler = composer.CreateSelectionHandler(); + selectionHandler.DeselectAll = deselectAll; var dragBox = new DragBox(select); - dragBox.DragEnd += () => selectionBox.UpdateVisibility(); + dragBox.DragEnd += () => selectionHandler.UpdateVisibility(); InternalChildren = new[] { dragBox, - selectionBox, + selectionHandler, selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, dragBox.CreateProxy() @@ -168,19 +168,19 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onBlueprintSelected(SelectionBlueprint blueprint) { - selectionBox.HandleSelected(blueprint); + selectionHandler.HandleSelected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 1); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { - selectionBox.HandleDeselected(blueprint); + selectionHandler.HandleDeselected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 0); } - private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionBox.HandleSelectionRequested(blueprint, state); + private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); - private void onDragRequested(DragEvent dragEvent) => selectionBox.HandleDrag(dragEvent); + private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); private class SelectionBlueprintContainer : Container { diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index aa64c43f3a..5051eb40e3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionBox.BORDER_RADIUS, + BorderThickness = SelectionHandler.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs similarity index 78% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs rename to osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 494b2c6d34..f8ceeedba9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -12,26 +12,31 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A box which surrounds s and provides interactive handles, context menus etc. + /// A component which outlines s and handles movement of selections. /// - public class SelectionBox : CompositeDrawable + public class SelectionHandler : CompositeDrawable { public const float BORDER_RADIUS = 2; + protected IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; + protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); + private Drawable outline; [Resolved] private IPlacementHandler placementHandler { get; set; } - public SelectionBox() + public SelectionHandler() { selectedBlueprints = new List(); @@ -59,12 +64,13 @@ namespace osu.Game.Screens.Edit.Compose.Components #region User Input Handling - public void HandleDrag(DragEvent dragEvent) + /// + /// Handles the selected s being dragged. + /// + /// The that received the drag event. + /// The drag event. + public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) { - // Todo: Various forms of snapping - - foreach (var blueprint in selectedBlueprints) - blueprint.AdjustPosition(dragEvent); } protected override bool OnKeyDown(KeyDownEvent e) @@ -90,19 +96,19 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Bind an action to deselect all selected blueprints. /// - public Action DeselectAll { private get; set; } + internal Action DeselectAll { private get; set; } /// /// Handle a blueprint becoming selected. /// /// The blueprint. - public void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); + internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); /// /// Handle a blueprint becoming deselected. /// /// The blueprint. - public void HandleDeselected(SelectionBlueprint blueprint) + internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); @@ -115,7 +121,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint requesting selection. /// /// The blueprint. - public void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -139,7 +145,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// internal void UpdateVisibility() { diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c4fb9dc419..9bccefc508 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -20,6 +20,7 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; +using osuTK.Input; namespace osu.Game.Screens.Edit { @@ -157,29 +158,19 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } - private void exportBeatmap() + protected override bool OnKeyDown(KeyDownEvent e) { - host.OpenFileExternally(Beatmap.Value.Save()); - } - - private void onModeChanged(EditorScreenMode mode) - { - currentScreen?.Exit(); - - switch (mode) + switch (e.Key) { - case EditorScreenMode.Compose: - currentScreen = new ComposeScreen(); - break; - case EditorScreenMode.Design: - currentScreen = new DesignScreen(); - break; - default: - currentScreen = new EditorScreen(); - break; + case Key.Left: + seek(e, -1); + return true; + case Key.Right: + seek(e, 1); + return true; } - LoadComponentAsync(currentScreen, screenContainer.Add); + return base.OnKeyDown(e); } private double scrollAccumulation; @@ -193,9 +184,9 @@ namespace osu.Game.Screens.Edit while (Math.Abs(scrollAccumulation) > precision) { if (scrollAccumulation > 0) - clock.SeekBackward(!clock.IsRunning); + seek(e, -1); else - clock.SeekForward(!clock.IsRunning); + seek(e, 1); scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); } @@ -224,7 +215,40 @@ namespace osu.Game.Screens.Edit Beatmap.Value.Track.Tempo.Value = 1; Beatmap.Value.Track.Start(); } + return base.OnExiting(next); } + + private void exportBeatmap() => host.OpenFileExternally(Beatmap.Value.Save()); + + private void onModeChanged(EditorScreenMode mode) + { + currentScreen?.Exit(); + + switch (mode) + { + case EditorScreenMode.Compose: + currentScreen = new ComposeScreen(); + break; + case EditorScreenMode.Design: + currentScreen = new DesignScreen(); + break; + default: + currentScreen = new EditorScreen(); + break; + } + + LoadComponentAsync(currentScreen, screenContainer.Add); + } + + private void seek(UIEvent e, int direction) + { + double amount = e.ShiftPressed ? 2 : 1; + + if (direction < 1) + clock.SeekBackward(!clock.IsRunning, amount); + else + clock.SeekForward(!clock.IsRunning, amount); + } } } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 465e3a43e2..aa30b1a9f5 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -68,16 +68,20 @@ namespace osu.Game.Screens.Edit /// Seeks backwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekBackward(bool snapped = false) => seek(-1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount); /// /// Seeks forwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekForward(bool snapped = false) => seek(1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekForward(bool snapped = false, double amount = 1) => seek(1, snapped, amount); - private void seek(int direction, bool snapped) + private void seek(int direction, bool snapped, double amount = 1) { + if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); + var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); if (direction < 0 && timingPoint.Time == CurrentTime) { @@ -87,7 +91,7 @@ namespace osu.Game.Screens.Edit timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; } - double seekAmount = timingPoint.BeatLength / beatDivisor; + double seekAmount = timingPoint.BeatLength / beatDivisor * amount; double seekTime = CurrentTime + seekAmount * direction; if (!snapped || ControlPointInfo.TimingPoints.Count == 0) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f54e3d90a6..ae1f27610b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -8,12 +8,14 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + private readonly IBindable isIdle = new BindableBool(); + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -102,12 +106,22 @@ namespace osu.Game.Screens.Menu private OsuGame game; [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGame game) + private void load(AudioManager audio, OsuGame game, IdleTracker idleTracker) { this.game = game; + + isIdle.ValueChanged += updateIdleState; + if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); + sampleBack = audio.Sample.Get(@"Menu/button-back-select"); } + private void updateIdleState(bool isIdle) + { + if (isIdle && State != ButtonSystemState.Exit) + State = ButtonSystemState.Initial; + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -266,9 +280,6 @@ namespace osu.Game.Screens.Menu protected override void Update() { - //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) - // State = MenuState.Initial; - base.Update(); if (logo != null) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index abda1ff85d..458d882f09 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.Menu protected override bool AllowBackButton => buttons.State != ButtonSystemState.Initial; + public override bool AllowExternalScreenChange => true; + private readonly BackgroundScreenDefault background; private Screen songSelect; diff --git a/osu.Game/Screens/Multi/Screens/Match/Match.cs b/osu.Game/Screens/Multi/Screens/Match/Match.cs index ce3f7825a4..f7d98df60e 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Match.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Match.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; using osu.Game.Screens.Select; using osu.Game.Users; @@ -34,11 +35,15 @@ namespace osu.Game.Screens.Multi.Screens.Match { this.room = room; Header header; + RoomSettingsOverlay settings; Info info; Children = new Drawable[] { - header = new Header(), + header = new Header + { + Depth = -1, + }, info = new Info { Margin = new MarginPadding { Top = Header.HEIGHT }, @@ -48,6 +53,16 @@ namespace osu.Game.Screens.Multi.Screens.Match RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT }, }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = Header.HEIGHT }, + Child = settings = new RoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.9f, + }, + }, }; header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect()); @@ -59,6 +74,20 @@ namespace osu.Game.Screens.Multi.Screens.Match info.Beatmap = b; }, true); + header.Tabs.Current.ValueChanged += t => + { + if (t == MatchHeaderPage.Settings) + settings.Show(); + else + settings.Hide(); + }; + + settings.StateChanged += s => + { + if (s == Visibility.Hidden) + header.Tabs.Current.Value = MatchHeaderPage.Room; + }; + nameBind.BindTo(room.Name); nameBind.BindValueChanged(n => info.Name = n, true); diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs new file mode 100644 index 0000000000..cd8b081b4e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs @@ -0,0 +1,110 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class GameTypePicker : TabControl + { + private const float height = 40; + private const float selection_width = 3; + + protected override TabItem CreateTabItem(GameType value) => new GameTypePickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public GameTypePicker() + { + Height = height + selection_width * 2; + TabContainer.Spacing = new Vector2(10 - selection_width * 2); + + AddItem(new GameTypeTag()); + AddItem(new GameTypeVersus()); + AddItem(new GameTypeTagTeam()); + AddItem(new GameTypeTeamVersus()); + } + + private class GameTypePickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly CircularContainer hover, selection; + + public GameTypePickerItem(GameType value) : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + selection = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + new DrawableGameType(Value) + { + Size = new Vector2(height), + Margin = new MarginPadding(selection_width), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(selection_width), + Child = hover = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.Yellow; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs new file mode 100644 index 0000000000..251bd062ec --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomAvailabilityPicker : TabControl + { + protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public RoomAvailabilityPicker() + { + RelativeSizeAxes = Axes.X; + Height = 35; + + TabContainer.Spacing = new Vector2(10); + + AddItem(RoomAvailability.Public); + AddItem(RoomAvailability.FriendsOnly); + AddItem(RoomAvailability.InviteOnly); + } + + private class RoomAvailabilityPickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly Box hover, selection; + + public RoomAvailabilityPickerItem(RoomAvailability value) : base(value) + { + RelativeSizeAxes = Axes.Y; + Width = 120; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"3d3943"), + }, + selection = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = @"Exo2.0-Bold", + Text = value.GetDescription(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.GreenLight; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs new file mode 100644 index 0000000000..d45ba48f0e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs @@ -0,0 +1,270 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomSettingsOverlay : FocusedOverlayContainer + { + private const float transition_duration = 350; + private const float field_padding = 45; + + private readonly Bindable nameBind = new Bindable(); + private readonly Bindable availabilityBind = new Bindable(); + private readonly Bindable typeBind = new Bindable(); + private readonly Bindable maxParticipantsBind = new Bindable(); + + private readonly Container content; + private readonly OsuSpriteText typeLabel; + + protected readonly OsuTextBox NameField, MaxParticipantsField; + protected readonly RoomAvailabilityPicker AvailabilityPicker; + protected readonly GameTypePicker TypePicker; + protected readonly TriangleButton ApplyButton; + + public RoomSettingsOverlay(Room room) + { + Masking = true; + + Child = content = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 35, Bottom = 75, Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Children = new[] + { + new SectionContainer + { + Padding = new MarginPadding { Right = field_padding / 2 }, + Children = new[] + { + new Section("ROOM NAME") + { + Child = NameField = new SettingsTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("ROOM VISIBILITY") + { + Child = AvailabilityPicker = new RoomAvailabilityPicker(), + }, + new Section("GAME TYPE") + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Children = new Drawable[] + { + TypePicker = new GameTypePicker + { + RelativeSizeAxes = Axes.X, + }, + typeLabel = new OsuSpriteText + { + TextSize = 14, + }, + }, + }, + }, + }, + }, + new SectionContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding { Left = field_padding / 2 }, + Children = new[] + { + new Section("MAX PARTICIPANTS") + { + Child = MaxParticipantsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("PASSWORD (OPTIONAL)") + { + Child = new SettingsPasswordTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + }, + }, + }, + }, + ApplyButton = new ApplySettingsButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(230, 35), + Margin = new MarginPadding { Bottom = 20 }, + Action = apply, + }, + }, + }; + + TypePicker.Current.ValueChanged += t => typeLabel.Text = t.Name; + + nameBind.ValueChanged += n => NameField.Text = n; + availabilityBind.ValueChanged += a => AvailabilityPicker.Current.Value = a; + typeBind.ValueChanged += t => TypePicker.Current.Value = t; + maxParticipantsBind.ValueChanged += m => MaxParticipantsField.Text = m?.ToString(); + + nameBind.BindTo(room.Name); + availabilityBind.BindTo(room.Availability); + typeBind.BindTo(room.Type); + maxParticipantsBind.BindTo(room.MaxParticipants); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + typeLabel.Colour = colours.Yellow; + } + + protected override void PopIn() + { + // reapply the rooms values if the overlay was completely closed + if (content.Y == -1) + { + nameBind.TriggerChange(); + availabilityBind.TriggerChange(); + typeBind.TriggerChange(); + maxParticipantsBind.TriggerChange(); + } + + content.MoveToY(0, transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + content.MoveToY(-1, transition_duration, Easing.InSine); + } + + private void apply() + { + nameBind.Value = NameField.Text; + availabilityBind.Value = AvailabilityPicker.Current.Value; + typeBind.Value = TypePicker.Current.Value; + + if (int.TryParse(MaxParticipantsField.Text, out int max)) + maxParticipantsBind.Value = max; + else + maxParticipantsBind.Value = null; + + Hide(); + } + + private class SettingsTextBox : OsuTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SettingsNumberTextBox : SettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } + + private class SettingsPasswordTextBox : OsuPasswordTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SectionContainer : FillFlowContainer
+ { + public SectionContainer() + { + RelativeSizeAxes = Axes.Both; + Width = 0.5f; + Direction = FillDirection.Vertical; + Spacing = new Vector2(field_padding); + } + } + + private class Section : Container + { + private readonly Container content; + + protected override Container Content => content; + + public Section(string title) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 12, + Font = @"Exo2.0-Bold", + Text = title.ToUpper(), + }, + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + }, + }; + } + } + + private class ApplySettingsButton : TriangleButton + { + public ApplySettingsButton() + { + Text = "Apply"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Yellow; + Triangles.ColourLight = colours.YellowLight; + Triangles.ColourDark = colours.YellowDark; + } + } + } +} diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 00309fbcd5..69f2b6ef9d 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -34,6 +34,8 @@ namespace osu.Game.Screens protected virtual bool AllowBackButton => true; + public virtual bool AllowExternalScreenChange => false; + /// /// Override to create a BackgroundMode for the current screen. /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. diff --git a/osu.Game/Screens/Play/Break/BreakInfo.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs index e5dbb6aa83..22e19ac8e2 100644 --- a/osu.Game/Screens/Play/Break/BreakInfo.cs +++ b/osu.Game/Screens/Play/Break/BreakInfo.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Play.Break diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a220a05971..bf44e9e636 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -27,6 +27,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; @@ -66,6 +67,9 @@ namespace osu.Game.Screens.Play /// private DecoupleableInterpolatingFramedClock adjustableClock; + [Resolved] + private ScoreManager scoreManager { get; set; } + private PauseContainer pauseContainer; private RulesetInfo ruleset; @@ -272,13 +276,10 @@ namespace osu.Game.Screens.Play { if (!IsCurrentScreen) return; - var score = new Score - { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = ruleset - }; - ScoreProcessor.PopulateScore(score); - score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value; + var score = CreateScore(); + if (RulesetContainer.Replay == null) + scoreManager.Import(score, true); + Push(new Results(score)); onCompletionEvent = null; @@ -286,6 +287,20 @@ namespace osu.Game.Screens.Play } } + protected virtual ScoreInfo CreateScore() + { + var score = new ScoreInfo + { + Beatmap = Beatmap.Value.BeatmapInfo, + Ruleset = ruleset, + User = api.LocalUser.Value + }; + + ScoreProcessor.PopulateScore(score); + + return score; + } + private bool onFail() { if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 6e2a2e4c9c..fe77fd57f2 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,23 +1,25 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class ReplayPlayer : Player { - public Replay Replay; + private readonly Score score; - public ReplayPlayer(Replay replay) + public ReplayPlayer(Score score) { - Replay = replay; + this.score = score; } protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer.SetReplay(Replay); + RulesetContainer.SetReplay(score.Replay); } + + protected override ScoreInfo CreateScore() => score.ScoreInfo; } } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 57bb229517..1ff5fc7bfd 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; @@ -18,12 +17,13 @@ using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { public class Results : OsuScreen { - private readonly Score score; + private readonly ScoreInfo score; private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Ranking private const float circle_outer_scale = 0.96f; - public Results(Score score) + public Results(ScoreInfo score) { this.score = score; } diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index f73bcba553..5f68623e54 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -16,14 +16,14 @@ namespace osu.Game.Screens.Ranking { public class ResultsPage : Container { - protected readonly Score Score; + protected readonly ScoreInfo Score; protected readonly WorkingBeatmap Beatmap; private CircularContainer content; private Box fill; protected override Container Content => content; - public ResultsPage(Score score, WorkingBeatmap beatmap) + public ResultsPage(ScoreInfo score, WorkingBeatmap beatmap) { Score = score; Beatmap = beatmap; diff --git a/osu.Game/Screens/Ranking/ResultsPageRanking.cs b/osu.Game/Screens/Ranking/ResultsPageRanking.cs index 3509d536b2..c5a5cc6ad9 100644 --- a/osu.Game/Screens/Ranking/ResultsPageRanking.cs +++ b/osu.Game/Screens/Ranking/ResultsPageRanking.cs @@ -5,16 +5,16 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Select.Leaderboards; using osuTK; using osu.Framework.Graphics.Shapes; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { public class ResultsPageRanking : ResultsPage { - public ResultsPageRanking(Score score, WorkingBeatmap beatmap = null) : base(score, beatmap) + public ResultsPageRanking(ScoreInfo score, WorkingBeatmap beatmap = null) : base(score, beatmap) { } diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index 7f152aaf14..153d154d40 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -24,6 +24,7 @@ using osu.Game.Users; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions; using osu.Framework.Localisation; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { @@ -32,7 +33,7 @@ namespace osu.Game.Screens.Ranking private Container scoreContainer; private ScoreCounter scoreCounter; - public ResultsPageScore(Score score, WorkingBeatmap beatmap) : base(score, beatmap) { } + public ResultsPageScore(ScoreInfo score, WorkingBeatmap beatmap) : base(score, beatmap) { } private FillFlowContainer statisticsContainer; diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs index 0cf1e60304..3258a62adf 100644 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Extensions; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Screens.Select.Leaderboards { diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 0748f68dca..a65cc6f096 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -14,12 +14,12 @@ using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using System.Linq; using osu.Framework.Configuration; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Screens.Select.Leaderboards { @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Select.Leaderboards private readonly IBindable ruleset = new Bindable(); - public Action ScoreSelected; + public Action ScoreSelected; private readonly LoadingAnimation loading; @@ -42,9 +42,9 @@ namespace osu.Game.Screens.Select.Leaderboards private bool scoresLoadedOnce; - private IEnumerable scores; + private IEnumerable scores; - public IEnumerable Scores + public IEnumerable Scores { get { return scores; } set @@ -179,6 +179,9 @@ namespace osu.Game.Screens.Select.Leaderboards private APIAccess api; private BeatmapInfo beatmap; + [Resolved] + private ScoreManager scoreManager { get; set; } + private ScheduledDelegate pendingUpdateScores; public BeatmapInfo Beatmap @@ -216,6 +219,8 @@ namespace osu.Game.Screens.Select.Leaderboards api.OnStateChange -= handleApiStateChange; } + public void RefreshScores() => updateScores(); + private GetScoresRequest getScoresRequest; private void handleApiStateChange(APIState oldState, APIState newState) @@ -242,8 +247,8 @@ namespace osu.Game.Screens.Select.Leaderboards { if (Scope == LeaderboardScope.Local) { - // TODO: get local scores from wherever here. - PlaceholderState = PlaceholderState.NoScores; + Scores = scoreManager.QueryScores(s => s.Beatmap.ID == Beatmap.ID).ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; return; } diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index 19363fe0f9..1ba529c0bf 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -14,8 +14,8 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Screens.Select.Leaderboards @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Select.Leaderboards public static readonly float HEIGHT = 60; public readonly int RankPosition; - public readonly Score Score; + public readonly ScoreInfo Score; private const float corner_radius = 5; private const float edge_margin = 5; @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Select.Leaderboards private Container flagBadgeContainer; private FillFlowContainer modsContainer; - public LeaderboardScore(Score score, int rank) + public LeaderboardScore(ScoreInfo score, int rank) { Score = score; RankPosition = rank; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 1a405190e7..b5d333aee4 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -108,6 +108,8 @@ namespace osu.Game.Screens.Select removeAutoModOnResume = false; } + BeatmapDetails.Leaderboard.RefreshScores(); + Beatmap.Value.Track.Looping = true; base.OnResuming(last); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ff897094ee..f4af4f9068 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -40,6 +40,8 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; + public override bool AllowExternalScreenChange => true; + /// /// Can be null if is false. /// @@ -503,7 +505,7 @@ namespace osu.Game.Screens.Select } } - private void onBeatmapSetAdded(BeatmapSetInfo s) => Carousel.UpdateBeatmapSet(s); + private void onBeatmapSetAdded(BeatmapSetInfo s, bool existing, bool silent) => Carousel.UpdateBeatmapSet(s); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Carousel.RemoveBeatmapSet(s); private void onBeatmapRestored(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); private void onBeatmapHidden(BeatmapInfo b) => Carousel.UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID)); diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index c9a3b0e94c..8f0f2b536a 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -2,15 +2,13 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Skinning { - public class SkinFileInfo : INamedFileInfo + public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int SkinInfoID { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 47dad60d88..60162ed224 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -3,28 +3,30 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; namespace osu.Game.Skinning { public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Name { get; set; } + public string Hash { get; set; } + public string Creator { get; set; } public List Files { get; set; } public bool DeletePending { get; set; } + public string FullName => $"\"{Name}\" by {Creator}"; + public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; - public override string ToString() => $"\"{Name}\" by {Creator}"; + public override string ToString() => FullName; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bd694e443a..ce179d43ef 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -26,8 +26,32 @@ namespace osu.Game.Skinning public override string[] HandledExtensions => new[] { ".osk" }; + protected override string[] HashableFileTypes => new[] { ".ini" }; + protected override string ImportFromStablePath => "Skins"; + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) + : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) + { + this.audio = audio; + + ItemRemoved += removedInfo => + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == CurrentSkinInfo.Value.ID) + CurrentSkinInfo.Value = SkinInfo.Default; + }; + + CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = getSkin(info); + CurrentSkin.ValueChanged += skin => + { + if (skin.SkinInfo != CurrentSkinInfo.Value) + throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); + + SourceChanged?.Invoke(); + }; + } + /// /// Returns a list of all usable s. Includes the special default skin plus all skins from . /// @@ -45,24 +69,13 @@ namespace osu.Game.Skinning /// A list of available . public List GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); - protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo - { - Name = archive.Name - }; + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; protected override void Populate(SkinInfo model, ArchiveReader archive) { base.Populate(model, archive); - populate(model); - } - /// - /// Populate a from its (if possible). - /// - /// - private void populate(SkinInfo model) - { - Skin reference = GetSkin(model); + Skin reference = getSkin(model); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; @@ -80,7 +93,7 @@ namespace osu.Game.Skinning ///
/// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) + private Skin getSkin(SkinInfo skinInfo) { if (skinInfo == SkinInfo.Default) return new DefaultSkin(); @@ -88,28 +101,6 @@ namespace osu.Game.Skinning return new LegacySkin(skinInfo, Files.Store, audio); } - public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) - : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) - { - this.audio = audio; - - ItemRemoved += removedInfo => - { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == CurrentSkinInfo.Value.ID) - CurrentSkinInfo.Value = SkinInfo.Default; - }; - - CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = GetSkin(info); - CurrentSkin.ValueChanged += skin => - { - if (skin.SkinInfo != CurrentSkinInfo.Value) - throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); - - SourceChanged?.Invoke(); - }; - } - /// /// Perform a lookup query on available s. /// diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs index f893d8456b..ca762c6be2 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs @@ -15,18 +15,14 @@ namespace osu.Game.Tests.Visual [Cached(Type = typeof(IPlacementHandler))] public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler { - private readonly Container hitObjectContainer; + protected readonly Container HitObjectContainer; private PlacementBlueprint currentBlueprint; protected PlacementBlueprintTestCase() { Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; - Add(hitObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(new StopwatchClock()) - }); + Add(HitObjectContainer = CreateHitObjectContainer()); } [BackgroundDependencyLoader] @@ -49,7 +45,7 @@ namespace osu.Game.Tests.Visual public void EndPlacement(HitObject hitObject) { - hitObjectContainer.Add(CreateHitObject(hitObject)); + AddHitObject(CreateHitObject(hitObject)); Remove(currentBlueprint); Add(currentBlueprint = CreateBlueprint()); @@ -59,6 +55,10 @@ namespace osu.Game.Tests.Visual { } + protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; + + protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject); + protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract PlacementBlueprint CreateBlueprint(); } diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index 1819fdb2a0..bc251fddbe 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual public double TimeRange { set => scrollingInfo.TimeRange.Value = value; } + public IScrollingInfo ScrollingInfo => scrollingInfo; + [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3022b66762..3c3550189b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,4 +24,4 @@ - \ No newline at end of file +