diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..221e4746cb
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,8 @@
+Add any details pertaining to developers above the break.
+
+- [ ] Depends on #PR
+- Closes #ISSUE
+
+---
+
+Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed.
\ No newline at end of file
diff --git a/osu.Desktop.Deploy/.vscode/launch.json b/osu.Desktop.Deploy/.vscode/launch.json
deleted file mode 100644
index 8c35d211bd..0000000000
--- a/osu.Desktop.Deploy/.vscode/launch.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- // Use IntelliSense to learn about possible attributes.
- // Hover to view descriptions of existing attributes.
- // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
- "version": "0.2.0",
- "configurations": [{
- "name": "Deploy (Debug)",
- "request": "launch",
- "type": "mono",
- "program": "${workspaceRoot}/bin/Debug/net471/osu.Desktop.Deploy.exe",
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Debug)",
- "runtimeExecutable": null,
- "env": {},
- "console": "internalConsole"
- },
- {
- "name": "Deploy (Release)",
- "request": "launch",
- "type": "clr",
- "program": "${workspaceRoot}/bin/Release/net471/osu.Desktop.Deploy.exe",
- "cwd": "${workspaceRoot}",
- "preLaunchTask": "Build (Release)",
- "runtimeExecutable": null,
- "env": {},
- "console": "internalConsole"
- }
- ]
-}
\ No newline at end of file
diff --git a/osu.Desktop.Deploy/.vscode/tasks.json b/osu.Desktop.Deploy/.vscode/tasks.json
deleted file mode 100644
index 35bf9e7a0e..0000000000
--- a/osu.Desktop.Deploy/.vscode/tasks.json
+++ /dev/null
@@ -1,64 +0,0 @@
-{
- // See https://go.microsoft.com/fwlink/?LinkId=733558
- // for the documentation about the tasks.json format
- "version": "2.0.0",
- "command": "msbuild",
- "type": "shell",
- "suppressTaskName": true,
- "args": [
- "/property:GenerateFullPaths=true",
- "/property:DebugType=portable",
- "/verbosity:minimal",
- "/m" //parallel compiling support.
- ],
- "tasks": [{
- "taskName": "Build (Debug)",
- "group": {
- "kind": "build",
- "isDefault": true
- },
- "problemMatcher": [
- "$msCompile"
- ]
- },
- {
- "taskName": "Build (Release)",
- "group": "build",
- "args": [
- "/property:Configuration=Release"
- ],
- "problemMatcher": [
- "$msCompile"
- ]
- },
- {
- "taskName": "Clean (Debug)",
- "args": [
- "/target:Clean"
- ],
- "problemMatcher": [
- "$msCompile"
- ]
- },
- {
- "taskName": "Clean (Release)",
- "args": [
- "/target:Clean",
- "/property:Configuration=Release"
- ],
- "problemMatcher": [
- "$msCompile"
- ]
- },
- {
- "taskName": "Clean All",
- "dependsOn": [
- "Clean (Debug)",
- "Clean (Release)"
- ],
- "problemMatcher": [
- "$msCompile"
- ]
- }
- ]
-}
\ No newline at end of file
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 844db4a80f..64adcecba4 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -73,7 +73,7 @@ namespace osu.Desktop
}
public StableStorage()
- : base(string.Empty)
+ : base(string.Empty, null)
{
}
}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 3cf95e9b3e..29d3b0e394 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -30,6 +30,7 @@
+
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs
new file mode 100644
index 0000000000..f6535380c8
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.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.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Catch.Difficulty
+{
+ public class CatchDifficultyAttributes : DifficultyAttributes
+ {
+ public double ApproachRate;
+ public int MaxCombo;
+
+ public CatchDifficultyAttributes(Mod[] mods, double starRating)
+ : base(mods, starRating)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 562374087e..3d1013aad3 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -1,19 +1,146 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
+
+ ///
+ /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
+ /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
+ /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
+ ///
+ private const double strain_step = 750;
+
+ ///
+ /// The weighting of each strain value decays to this number * it's previous value
+ ///
+ private const double decay_weight = 0.94;
+
+ private const double star_scaling_factor = 0.145;
+
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
- protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => new DifficultyAttributes(mods, 0);
+ protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
+ {
+ if (!beatmap.HitObjects.Any())
+ return new CatchDifficultyAttributes(mods, 0);
+
+ var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty);
+ float halfCatchWidth = catcher.CatchWidth * 0.5f;
+
+ var difficultyHitObjects = new List();
+
+ foreach (var hitObject in beatmap.HitObjects)
+ {
+ // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations.
+ if (hitObject is Fruit)
+ {
+ difficultyHitObjects.Add(new CatchDifficultyHitObject((CatchHitObject)hitObject, halfCatchWidth));
+ }
+ if (hitObject is JuiceStream)
+ difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth)));
+ }
+
+ difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
+
+ if (!calculateStrainValues(difficultyHitObjects, timeRate))
+ return new CatchDifficultyAttributes(mods, 0);
+
+ // this is the same as osu!, so there's potential to share the implementation... maybe
+ double preEmpt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
+ double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor;
+
+ return new CatchDifficultyAttributes(mods, starRating)
+ {
+ ApproachRate = preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0,
+ MaxCombo = difficultyHitObjects.Count
+ };
+ }
+
+ private bool calculateStrainValues(List objects, double timeRate)
+ {
+ CatchDifficultyHitObject lastObject = null;
+
+ if (!objects.Any()) return false;
+
+ // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
+ foreach (var currentObject in objects)
+ {
+ if (lastObject != null)
+ currentObject.CalculateStrains(lastObject, timeRate);
+
+ lastObject = currentObject;
+ }
+
+ return true;
+ }
+
+ private double calculateDifficulty(List objects, double timeRate)
+ {
+ // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods
+ double actualStrainStep = strain_step * timeRate;
+
+ // Find the highest strain value within each strain step
+ var highestStrains = new List();
+ double intervalEndTime = actualStrainStep;
+ double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
+
+ CatchDifficultyHitObject previousHitObject = null;
+ foreach (CatchDifficultyHitObject hitObject in objects)
+ {
+ // While we are beyond the current interval push the currently available maximum to our strain list
+ while (hitObject.BaseHitObject.StartTime > intervalEndTime)
+ {
+ highestStrains.Add(maximumStrain);
+
+ // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
+ // until the beginning of the next interval.
+ if (previousHitObject == null)
+ {
+ maximumStrain = 0;
+ }
+ else
+ {
+ double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
+ maximumStrain = previousHitObject.Strain * decay;
+ }
+
+ // Go to the next time interval
+ intervalEndTime += actualStrainStep;
+ }
+
+ // Obtain maximum strain
+ maximumStrain = Math.Max(hitObject.Strain, maximumStrain);
+
+ previousHitObject = hitObject;
+ }
+
+ // Build the weighted sum over the highest strains for each interval
+ double difficulty = 0;
+ double weight = 1;
+ highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
+
+ foreach (double strain in highestStrains)
+ {
+ difficulty += weight * strain;
+ weight *= decay_weight;
+ }
+
+ return difficulty;
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
new file mode 100644
index 0000000000..720c1d8653
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs
@@ -0,0 +1,130 @@
+// 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.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Catch.Difficulty
+{
+ public class CatchDifficultyHitObject
+ {
+ internal static readonly double DECAY_BASE = 0.20;
+ private const float normalized_hitobject_radius = 41.0f;
+ private const float absolute_player_positioning_error = 16f;
+ private readonly float playerPositioningError;
+
+ internal CatchHitObject BaseHitObject;
+
+ ///
+ /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
+ ///
+ internal double Strain = 1;
+
+ ///
+ /// This is required to keep track of lazy player movement (always moving only as far as necessary)
+ /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated
+ ///
+ internal float PlayerPositionOffset;
+ internal float LastMovement;
+
+ internal float NormalizedPosition;
+ internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset;
+
+ internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf)
+ {
+ BaseHitObject = baseHitObject;
+
+ // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
+ float scalingFactor = normalized_hitobject_radius / catcherWidthHalf;
+
+ playerPositioningError = absolute_player_positioning_error; // * scalingFactor;
+ NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
+ }
+
+ private const double direction_change_bonus = 12.5;
+ internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate)
+ {
+ // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
+ // See Taiko feedback thread.
+ double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
+ double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000);
+
+ // Update new position with lazy movement.
+ PlayerPositionOffset =
+ MathHelper.Clamp(
+ previousHitObject.ActualNormalizedPosition,
+ NormalizedPosition - (normalized_hitobject_radius - playerPositioningError),
+ NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player.
+ - NormalizedPosition; // Subtract HitObject position to obtain offset
+
+ LastMovement = DistanceTo(previousHitObject);
+ double addition = spacingWeight(LastMovement);
+
+ if (NormalizedPosition < previousHitObject.NormalizedPosition)
+ {
+ LastMovement = -LastMovement;
+ }
+
+ CatchHitObject previousHitCircle = previousHitObject.BaseHitObject;
+
+ double additionBonus = 0;
+ double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25));
+
+ // Direction changes give an extra point!
+ if (Math.Abs(LastMovement) > 0.1)
+ {
+ if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement))
+ {
+ double bonus = direction_change_bonus / sqrtTime;
+
+ // Weight bonus by how
+ double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError;
+
+ // We want time to play a role twice here!
+ addition += bonus * bonusFactor;
+
+ // Bonus for tougher direction switches and "almost" hyperdashes at this point
+ if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
+ {
+ additionBonus += 0.3 * bonusFactor;
+ }
+ }
+
+ // Base bonus for every movement, giving some weight to streams.
+ addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime;
+ }
+
+ // Bonus for "almost" hyperdashes at corner points
+ if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
+ {
+ if (!previousHitCircle.HyperDash)
+ {
+ additionBonus += 1.0;
+ }
+ else
+ {
+ // After a hyperdash we ARE in the correct position. Always!
+ PlayerPositionOffset = 0;
+ }
+
+ addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
+ }
+
+ addition *= 850.0 / Math.Max(timeElapsed, 25);
+
+ Strain = previousHitObject.Strain * decay + addition;
+ }
+
+ private static double spacingWeight(float distance)
+ {
+ return Math.Pow(distance, 1.3) / 500;
+ }
+
+ internal float DistanceTo(CatchDifficultyHitObject other)
+ {
+ return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 548813fbd2..d55cdac115 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; }
+ ///
+ /// The distance for a fruit to to next hyper if it's not a hyper.
+ ///
+ public float DistanceToHyperDash { get; set; }
+
///
/// The next fruit starts a new combo. Used for explodey.
///
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 6a9d1bdbc7..8d3d898655 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Replays
}
}
- public override List GetPendingStates()
+ public override List GetPendingInputs()
{
- if (!Position.HasValue) return new List();
+ if (!Position.HasValue) return new List();
var actions = new List();
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
else if (Position.Value < CurrentFrame.Position)
actions.Add(CatchAction.MoveLeft);
- return new List
+ return new List
{
new CatchReplayState
{
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 24edcbfc98..ceb05d349f 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using OpenTK;
using OpenTK.Graphics;
@@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
base.UpdateAfterChildren();
- var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState;
+ var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
if (state?.CatcherX != null)
MovableCatcher.X = state.CatcherX.Value;
@@ -105,6 +106,11 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler
{
+ ///
+ /// Width of the area that can be used to attempt catches during gameplay.
+ ///
+ internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
+
private Container caughtFruit;
public Container ExplodingFruitTarget;
@@ -232,15 +238,15 @@ namespace osu.Game.Rulesets.Catch.UI
/// Whether the catch is possible.
public bool AttemptCatch(CatchHitObject fruit)
{
- double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f;
+ float halfCatchWidth = CatchWidth * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
var validCatch =
- catchObjectPosition >= catcherPosition - halfCatcherWidth &&
- catchObjectPosition <= catcherPosition + halfCatcherWidth;
+ catchObjectPosition >= catcherPosition - halfCatchWidth &&
+ catchObjectPosition <= catcherPosition + halfCatchWidth;
if (validCatch && fruit.HyperDash)
{
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
new file mode 100644
index 0000000000..75c8fc7e79
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs
@@ -0,0 +1,45 @@
+// 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.Framework.Graphics.Containers;
+using osu.Framework.Input.Bindings;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public abstract class ManiaInputTestCase : OsuTestCase
+ {
+ private readonly Container content;
+ protected override Container Content => content ?? base.Content;
+
+ protected ManiaInputTestCase(int keys)
+ {
+ base.Content.Add(content = new LocalInputManager(keys));
+ }
+
+ private class LocalInputManager : ManiaInputManager
+ {
+ public LocalInputManager(int variant)
+ : base(new ManiaRuleset().RulesetInfo, variant)
+ {
+ }
+
+ protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new LocalKeyBindingContainer(ruleset, variant, unique);
+
+ private class LocalKeyBindingContainer : RulesetKeyBindingContainer
+ {
+ public LocalKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+
+ protected override void ReloadMappings()
+ {
+ KeyBindings = DefaultKeyBindings;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
new file mode 100644
index 0000000000..78a98e83e8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/ScrollingTestContainer.cs
@@ -0,0 +1,37 @@
+// 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.Containers;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ ///
+ /// A container which provides a to children.
+ ///
+ public class ScrollingTestContainer : Container
+ {
+ private readonly ScrollingDirection direction;
+
+ public ScrollingTestContainer(ScrollingDirection direction)
+ {
+ this.direction = direction;
+ }
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(new ScrollingInfo { Direction = { Value = direction }});
+ return dependencies;
+ }
+
+ private class ScrollingInfo : IScrollingInfo
+ {
+ public readonly Bindable Direction = new Bindable();
+ IBindable IScrollingInfo.Direction => Direction;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
new file mode 100644
index 0000000000..72f0b046b6
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs
@@ -0,0 +1,111 @@
+// 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 NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Mania.UI.Components;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseColumn : ManiaInputTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(Column),
+ typeof(ColumnBackground),
+ typeof(ColumnKeyArea),
+ typeof(ColumnHitObjectArea)
+ };
+
+ private readonly List columns = new List();
+
+ public TestCaseColumn()
+ : base(2)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(20, 0),
+ Children = new[]
+ {
+ createColumn(ScrollingDirection.Up, ManiaAction.Key1),
+ createColumn(ScrollingDirection.Down, ManiaAction.Key2)
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("note", createNote);
+ AddStep("hold note", createHoldNote);
+ }
+
+ private void createNote()
+ {
+ for (int i = 0; i < columns.Count; i++)
+ {
+ var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ columns[i].Add(new DrawableNote(obj, columns[i].Action));
+ }
+ }
+
+ private void createHoldNote()
+ {
+ for (int i = 0; i < columns.Count; i++)
+ {
+ var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ columns[i].Add(new DrawableHoldNote(obj, columns[i].Action));
+ }
+ }
+
+ private Drawable createColumn(ScrollingDirection direction, ManiaAction action)
+ {
+ var column = new Column(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Height = 0.85f,
+ AccentColour = Color4.OrangeRed,
+ Action = action,
+ VisibleTimeRange = { Value = 2000 }
+ };
+
+ columns.Add(column);
+
+ return new ScrollingTestContainer(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Child = column
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
deleted file mode 100644
index a4109722d4..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaHitObjects.cs
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Tests.Visual;
-using OpenTK;
-using OpenTK.Graphics;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestCaseManiaHitObjects : OsuTestCase
- {
- public TestCaseManiaHitObjects()
- {
- Note note1 = new Note();
- Note note2 = new Note();
- HoldNote holdNote = new HoldNote { StartTime = 1000 };
-
- note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- Add(new FillFlowContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10, 0),
- // Imagine that the containers containing the drawable notes are the "columns"
- Children = new Drawable[]
- {
- new Container
- {
- Name = "Normal note column",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Width = 50,
- Children = new[]
- {
- new Container
- {
- Name = "Timing section",
- RelativeSizeAxes = Axes.Both,
- RelativeChildSize = new Vector2(1, 10000),
- Children = new[]
- {
- new DrawableNote(note1, ManiaAction.Key1)
- {
- Y = 5000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red
- },
- new DrawableNote(note2, ManiaAction.Key1)
- {
- Y = 6000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red
- }
- }
- }
- }
- },
- new Container
- {
- Name = "Hold note column",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Y,
- Width = 50,
- Children = new[]
- {
- new Container
- {
- Name = "Timing section",
- RelativeSizeAxes = Axes.Both,
- RelativeChildSize = new Vector2(1, 10000),
- Children = new[]
- {
- new DrawableHoldNote(holdNote, ManiaAction.Key1)
- {
- Y = 5000,
- Height = 1000,
- LifetimeStart = double.MinValue,
- LifetimeEnd = double.MaxValue,
- AccentColour = Color4.Red,
- }
- }
- }
- }
- }
- }
- });
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
deleted file mode 100644
index b064d82a23..0000000000
--- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs
+++ /dev/null
@@ -1,185 +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 System.Linq;
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Timing;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Configuration;
-using osu.Game.Rulesets.Mania.Beatmaps;
-using osu.Game.Rulesets.Mania.Configuration;
-using osu.Game.Rulesets.Mania.Judgements;
-using osu.Game.Rulesets.Mania.Objects;
-using osu.Game.Rulesets.Mania.Objects.Drawables;
-using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.Scoring;
-using osu.Game.Tests.Visual;
-
-namespace osu.Game.Rulesets.Mania.Tests
-{
- [TestFixture]
- public class TestCaseManiaPlayfield : OsuTestCase
- {
- private const double start_time = 500;
- private const double duration = 500;
-
- protected override double TimePerAction => 200;
-
- private RulesetInfo maniaRuleset;
-
- public TestCaseManiaPlayfield()
- {
- var rng = new Random(1337);
-
- AddStep("1 column", () => createPlayfield(1));
- AddStep("4 columns", () => createPlayfield(4));
- AddStep("5 columns", () => createPlayfield(5));
- AddStep("8 columns", () => createPlayfield(8));
- AddStep("4 + 4 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 4 },
- new StageDefinition { Columns = 4 },
- };
- createPlayfield(stages);
- });
-
- AddStep("2 + 4 + 2 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 2 },
- new StageDefinition { Columns = 4 },
- new StageDefinition { Columns = 2 },
- };
- createPlayfield(stages);
- });
-
- AddStep("1 + 8 + 1 columns", () =>
- {
- var stages = new List
- {
- new StageDefinition { Columns = 1 },
- new StageDefinition { Columns = 8 },
- new StageDefinition { Columns = 1 },
- };
- createPlayfield(stages);
- });
-
- AddStep("Reversed", () => createPlayfield(4, true));
-
- AddStep("Notes with input", () => createPlayfieldWithNotes());
- AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
- AddStep("Notes with gravity", () => createPlayfieldWithNotes());
- AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
-
- AddStep("Hit explosion", () =>
- {
- var playfield = createPlayfield(4);
-
- int col = rng.Next(0, 4);
-
- var note = new Note { Column = col };
- note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- var drawableNote = new DrawableNote(note, ManiaAction.Key1)
- {
- AccentColour = playfield.Columns.ElementAt(col).AccentColour
- };
-
- playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
- playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
- });
- }
-
- [BackgroundDependencyLoader]
- private void load(RulesetStore rulesets, SettingsStore settings)
- {
- maniaRuleset = rulesets.GetRuleset(3);
-
- Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
- }
-
- private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
- {
- var stages = new List
- {
- new StageDefinition { Columns = cols },
- };
-
- return createPlayfield(stages, inverted);
- }
-
- private ManiaPlayfield createPlayfield(List stages, bool inverted = false)
- {
- Clear();
-
- var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
- Add(inputManager);
-
- ManiaPlayfield playfield;
-
- inputManager.Add(playfield = new ManiaPlayfield(stages)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- });
-
- playfield.Inverted.Value = inverted;
-
- return playfield;
- }
-
- private void createPlayfieldWithNotes(bool inverted = false)
- {
- Clear();
-
- var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
-
- var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
- Add(inputManager);
-
- ManiaPlayfield playfield;
- var stages = new List
- {
- new StageDefinition { Columns = 4 },
- };
-
- inputManager.Add(playfield = new ManiaPlayfield(stages)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Clock = new FramedClock(rateAdjustClock)
- });
-
- playfield.Inverted.Value = inverted;
-
- for (double t = start_time; t <= start_time + duration; t += 100)
- {
- var note1 = new Note { StartTime = t, Column = 0 };
- var note2 = new Note { StartTime = t, Column = 3 };
-
- note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
- playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
- }
-
- var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
- var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
-
- holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
- playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
- }
- }
-}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
new file mode 100644
index 0000000000..4fdfac93b7
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs
@@ -0,0 +1,168 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Tests.Visual;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseNotes : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(DrawableNote),
+ typeof(DrawableHoldNote)
+ };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(20),
+ Children = new[]
+ {
+ createNoteDisplay(ScrollingDirection.Down),
+ createNoteDisplay(ScrollingDirection.Up),
+ createHoldNoteDisplay(ScrollingDirection.Down),
+ createHoldNoteDisplay(ScrollingDirection.Up),
+ }
+ };
+ }
+
+ private Drawable createNoteDisplay(ScrollingDirection direction)
+ {
+ var note = new Note { StartTime = 999999999 };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new ScrollingTestContainer(direction)
+ {
+ AutoSizeAxes = Axes.Both,
+ Child = new NoteContainer(direction, $"note, scrolling {direction.ToString().ToLower()}")
+ {
+ Child = new DrawableNote(note, ManiaAction.Key1) { AccentColour = Color4.OrangeRed }
+ }
+ };
+ }
+
+ private Drawable createHoldNoteDisplay(ScrollingDirection direction)
+ {
+ var note = new HoldNote { StartTime = 999999999, Duration = 1000 };
+ note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ return new ScrollingTestContainer(direction)
+ {
+ AutoSizeAxes = Axes.Both,
+ Child = new NoteContainer(direction, $"hold note, scrolling {direction.ToString().ToLower()}")
+ {
+ Child = new DrawableHoldNote(note, ManiaAction.Key1)
+ {
+ RelativeSizeAxes = Axes.Both,
+ AccentColour = Color4.OrangeRed,
+ }
+ }
+ };
+ }
+
+ private class NoteContainer : Container
+ {
+ private readonly Container content;
+ protected override Container Content => content;
+
+ private readonly ScrollingDirection direction;
+
+ public NoteContainer(ScrollingDirection direction, string description)
+ {
+ this.direction = direction;
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(0, 10),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Width = 45,
+ Height = 100,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Both,
+ Width = 1.25f,
+ Colour = Color4.Black.Opacity(0.5f)
+ },
+ content = new Container { RelativeSizeAxes = Axes.Both }
+ }
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ TextSize = 14,
+ Text = description
+ }
+ }
+ };
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ foreach (var obj in content.OfType())
+ {
+ if (!(obj.HitObject is IHasEndTime endTime))
+ continue;
+
+ if (!obj.HasNestedHitObjects)
+ continue;
+
+ foreach (var nested in obj.NestedHitObjects)
+ {
+ double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration;
+ switch (direction)
+ {
+ case ScrollingDirection.Up:
+ nested.Y = (float)(finalPosition * content.DrawHeight);
+ break;
+ case ScrollingDirection.Down:
+ nested.Y = (float)(-finalPosition * content.DrawHeight);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
new file mode 100644
index 0000000000..9aff853ffd
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs
@@ -0,0 +1,121 @@
+// 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 NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ [TestFixture]
+ public class TestCaseStage : ManiaInputTestCase
+ {
+ private const int columns = 4;
+
+ private readonly List stages = new List();
+
+ public TestCaseStage()
+ : base(columns)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Spacing = new Vector2(20, 0),
+ Children = new[]
+ {
+ createStage(ScrollingDirection.Up, ManiaAction.Key1),
+ createStage(ScrollingDirection.Down, ManiaAction.Key3)
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("note", createNote);
+ AddStep("hold note", createHoldNote);
+ AddStep("minor bar line", () => createBarLine(false));
+ AddStep("major bar line", () => createBarLine(true));
+ }
+
+ private void createNote()
+ {
+ foreach (var stage in stages)
+ {
+ for (int i = 0; i < stage.Columns.Count; i++)
+ {
+ var obj = new Note { Column = i, StartTime = Time.Current + 2000 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(new DrawableNote(obj, stage.Columns[i].Action));
+ }
+ }
+ }
+
+ private void createHoldNote()
+ {
+ foreach (var stage in stages)
+ {
+ for (int i = 0; i < stage.Columns.Count; i++)
+ {
+ var obj = new HoldNote { Column = i, StartTime = Time.Current + 2000, Duration = 500 };
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(new DrawableHoldNote(obj, stage.Columns[i].Action));
+ }
+ }
+ }
+
+ private void createBarLine(bool major)
+ {
+ foreach (var stage in stages)
+ {
+ var obj = new BarLine
+ {
+ StartTime = Time.Current + 2000,
+ ControlPoint = new TimingControlPoint(),
+ BeatIndex = major ? 0 : 1
+ };
+
+ obj.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ stage.Add(obj);
+ }
+ }
+
+ private Drawable createStage(ScrollingDirection direction, ManiaAction action)
+ {
+ var specialAction = ManiaAction.Special1;
+
+ var stage = new ManiaStage(direction, 0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
+ stages.Add(stage);
+
+ return new ScrollingTestContainer(direction)
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.X,
+ Child = stage
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index f60958d581..c6fa465a0f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -329,7 +329,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
break;
}
- bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
+ bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
index d9e360081d..3e9c9feba1 100644
--- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
+++ b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs
@@ -4,6 +4,7 @@
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
+using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Configuration
{
@@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
+ Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
@@ -29,6 +31,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaSetting
{
- ScrollTime
+ ScrollTime,
+ ScrollDirection
}
}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs
new file mode 100644
index 0000000000..c7f6890b93
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.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.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Mania.Difficulty
+{
+ public class ManiaDifficultyAttributes : DifficultyAttributes
+ {
+ public double GreatHitWindow;
+
+ public ManiaDifficultyAttributes(Mod[] mods, double starRating)
+ : base(mods, starRating)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index 5fa113224d..7b4d4b12ed 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new ManiaDifficultyAttributes(mods, 0);
+
var difficultyHitObjects = new List();
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
@@ -50,9 +53,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
+
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
- return new DifficultyAttributes(mods, starRating);
+ return new ManiaDifficultyAttributes(mods, starRating)
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate
+ };
}
private bool calculateStrainValues(List objects, double timeRate)
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index b6089b830b..1f26bda1b2 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
+ protected new ManiaDifficultyAttributes Attributes => (ManiaDifficultyAttributes)base.Attributes;
+
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
@@ -105,14 +107,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeAccuracyValue(double strainValue)
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- if (hitWindowGreat <= 0)
+ if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
+ double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index ac5fbfdde0..3d58e63da5 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -16,6 +16,7 @@ using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
+using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
@@ -155,6 +156,8 @@ namespace osu.Game.Rulesets.Mania
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
+ public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this);
+
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs
new file mode 100644
index 0000000000..54a7bf954d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.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 osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Overlays.Settings;
+using osu.Game.Rulesets.Mania.Configuration;
+using osu.Game.Rulesets.Mania.UI;
+
+namespace osu.Game.Rulesets.Mania
+{
+ public class ManiaSettingsSubsection : RulesetSettingsSubsection
+ {
+ protected override string Header => "osu!mania";
+
+ public ManiaSettingsSubsection(ManiaRuleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ManiaConfigManager config)
+ {
+ Children = new Drawable[]
+ {
+ new SettingsEnumDropdown
+ {
+ LabelText = "Scrolling direction",
+ Bindable = config.GetBindable(ManiaSetting.ScrollDirection)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index e008fa952e..ce0276f759 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -75,6 +76,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddNested(tail);
}
+ protected override void OnDirectionChanged(ScrollingDirection direction)
+ {
+ base.OnDirectionChanged(direction);
+
+ bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ }
+
public override Color4 AccentColour
{
get { return base.AccentColour; }
@@ -100,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.Update();
// Make the body piece not lie under the head note
- bodyPiece.Y = head.Height / 2;
+ bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * head.Height / 2;
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2;
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index fbf1ee8460..1271fae0c1 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -1,8 +1,12 @@
// 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.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -16,18 +20,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public new TObject HitObject;
+ protected readonly IBindable Direction = new Bindable();
+
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
: base(hitObject)
{
- Anchor = Anchor.TopCentre;
- Origin = Anchor.TopCentre;
-
HitObject = hitObject;
if (action != null)
Action = action.Value;
}
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ Direction.BindTo(scrollingInfo.Direction);
+ Direction.BindValueChanged(OnDirectionChanged, true);
+ }
+
+ protected virtual void OnDirectionChanged(ScrollingDirection direction)
+ {
+ Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ }
+
protected override void UpdateState(ArmedState state)
{
switch (state)
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 3de0a9c5cc..fb4aa74ad1 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -9,6 +9,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -28,14 +29,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
CornerRadius = 5;
Masking = true;
- InternalChildren = new Drawable[]
- {
- headPiece = new NotePiece
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre
- }
- };
+ InternalChild = headPiece = new NotePiece();
+ }
+
+ protected override void OnDirectionChanged(ScrollingDirection direction)
+ {
+ base.OnDirectionChanged(direction);
+
+ headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
}
public override Color4 AccentColour
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
index 9ebeb91e0c..2c74f5b168 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs
@@ -1,12 +1,16 @@
// 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 OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
{
@@ -18,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public const float NOTE_HEIGHT = 10;
private const float head_colour_height = 6;
+ private readonly IBindable direction = new Bindable();
+
private readonly Box colouredBox;
public NotePiece()
@@ -33,8 +39,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
},
colouredBox = new Box
{
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
Height = head_colour_height,
Alpha = 0.2f
@@ -42,6 +46,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
};
}
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
+ }, true);
+ }
+
private Color4 accentColour;
public Color4 AccentColour
{
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index c71db745e0..29eeb1cab5 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
- public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
+ public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index 9c1830e642..e731ce9195 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -1,50 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK;
using OpenTK.Graphics;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Colour;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
-using System;
using System.Linq;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
- public class Column : ScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
+ public class Column : ManiaScrollingPlayfield, IKeyBindingHandler, IHasAccentColour
{
- private const float key_icon_size = 10;
- private const float key_icon_corner_radius = 3;
- private const float key_icon_border_radius = 2;
-
- private const float hit_target_height = 10;
- private const float hit_target_bar_height = 2;
-
private const float column_width = 45;
private const float special_column_width = 70;
- public ManiaAction Action;
+ private ManiaAction action;
- private readonly Box background;
- private readonly Box backgroundOverlay;
- private readonly Container hitTargetBar;
- private readonly Container keyIcon;
+ public ManiaAction Action
+ {
+ get => action;
+ set
+ {
+ if (action == value)
+ return;
+ action = value;
+
+ background.Action = value;
+ keyArea.Action = value;
+ }
+ }
+
+ private readonly ColumnBackground background;
+ private readonly ColumnKeyArea keyArea;
+ private readonly ColumnHitObjectArea hitObjectArea;
internal readonly Container TopLevelContainer;
private readonly Container explosionContainer;
- protected override Container Content => content;
- private readonly Container content;
+ protected override Container Content => hitObjectArea;
- public Column()
- : base(ScrollingDirection.Up)
+ public Column(ScrollingDirection direction)
+ : base(direction)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;
@@ -52,71 +53,21 @@ namespace osu.Game.Rulesets.Mania.UI
Masking = true;
CornerRadius = 5;
- InternalChildren = new Drawable[]
+ background = new ColumnBackground { RelativeSizeAxes = Axes.Both };
+
+ Container hitTargetContainer;
+
+ InternalChildren = new[]
{
- background = new Box
- {
- Name = "Background",
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.3f
- },
- backgroundOverlay = new Box
- {
- Name = "Background Gradient Overlay",
- RelativeSizeAxes = Axes.Both,
- Height = 0.5f,
- Anchor = Anchor.TopLeft,
- Origin = Anchor.TopLeft,
- Blending = BlendingMode.Additive,
- Alpha = 0
- },
- new Container
+ // For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
+ background.CreateProxy(),
+ hitTargetContainer = new Container
{
Name = "Hit target + hit objects",
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Top = ManiaStage.HIT_TARGET_POSITION },
Children = new Drawable[]
{
- new Container
- {
- Name = "Hit target",
- RelativeSizeAxes = Axes.X,
- Height = hit_target_height,
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Background",
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black
- },
- hitTargetBar = new Container
- {
- Name = "Bar",
- RelativeSizeAxes = Axes.X,
- Height = hit_target_bar_height,
- Masking = true,
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both
- }
- }
- }
- }
- },
- content = new Container
- {
- Name = "Hit objects",
- RelativeSizeAxes = Axes.Both,
- },
- // For column lighting, we need to capture input events before the notes
- new InputTarget
- {
- Pressed = onPressed,
- Released = onReleased
- },
+ hitObjectArea = new ColumnHitObjectArea { RelativeSizeAxes = Axes.Both },
explosionContainer = new Container
{
Name = "Hit explosions",
@@ -124,46 +75,27 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
},
- new Container
+ keyArea = new ColumnKeyArea
{
- Name = "Key",
RelativeSizeAxes = Axes.X,
Height = ManiaStage.HIT_TARGET_POSITION,
- Children = new Drawable[]
- {
- new Box
- {
- Name = "Key gradient",
- RelativeSizeAxes = Axes.Both,
- Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
- Alpha = 0.5f
- },
- keyIcon = new Container
- {
- Name = "Key icon",
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Size = new Vector2(key_icon_size),
- Masking = true,
- CornerRadius = key_icon_corner_radius,
- BorderThickness = 2,
- BorderColour = Color4.White, // Not true
- Children = new[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true
- }
- }
- }
- }
},
+ background,
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
TopLevelContainer.Add(explosionContainer.CreateProxy());
+
+ Direction.BindValueChanged(d =>
+ {
+ hitTargetContainer.Padding = new MarginPadding
+ {
+ Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0,
+ Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0,
+ };
+
+ keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ }, true);
}
public override Axes RelativeSizeAxes => Axes.Y;
@@ -192,22 +124,9 @@ namespace osu.Game.Rulesets.Mania.UI
return;
accentColour = value;
- background.Colour = accentColour;
- backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0));
-
- hitTargetBar.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
-
- keyIcon.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Radius = 5,
- Colour = accentColour.Opacity(0.5f),
- };
+ background.AccentColour = value;
+ keyArea.AccentColour = value;
+ hitObjectArea.AccentColour = value;
}
}
@@ -228,48 +147,10 @@ namespace osu.Game.Rulesets.Mania.UI
if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return;
- explosionContainer.Add(new HitExplosion(judgedObject));
- }
-
- private bool onPressed(ManiaAction action)
- {
- if (action == Action)
+ explosionContainer.Add(new HitExplosion(judgedObject)
{
- backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
- keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
- }
-
- return false;
- }
-
- private bool onReleased(ManiaAction action)
- {
- if (action == Action)
- {
- backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
- keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
- }
-
- return false;
- }
-
- ///
- /// This is a simple container which delegates various input events that have to be captured before the notes.
- ///
- private class InputTarget : Container, IKeyBindingHandler
- {
- public Func Pressed;
- public Func Released;
-
- public InputTarget()
- {
- RelativeSizeAxes = Axes.Both;
- AlwaysPresent = true;
- Alpha = 0;
- }
-
- public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
- public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
+ Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
+ });
}
public bool OnPressed(ManiaAction action)
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
new file mode 100644
index 0000000000..9b744bd254
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs
@@ -0,0 +1,106 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnBackground : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
+ {
+ public ManiaAction Action;
+
+ private Box background;
+ private Box backgroundOverlay;
+
+ private readonly IBindable direction = new Bindable();
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ InternalChildren = new[]
+ {
+ background = new Box
+ {
+ Name = "Background",
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.3f
+ },
+ backgroundOverlay = new Box
+ {
+ Name = "Background Gradient Overlay",
+ RelativeSizeAxes = Axes.Both,
+ Height = 0.5f,
+ Blending = BlendingMode.Additive,
+ Alpha = 0
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+ updateColours();
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ background.Colour = AccentColour;
+
+ var brightPoint = AccentColour.Opacity(0.6f);
+ var dimPoint = AccentColour.Opacity(0);
+
+ backgroundOverlay.Colour = ColourInfo.GradientVertical(
+ direction.Value == ScrollingDirection.Up ? brightPoint : dimPoint,
+ direction.Value == ScrollingDirection.Up ? dimPoint : brightPoint);
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == Action)
+ backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public bool OnReleased(ManiaAction action)
+ {
+ if (action == Action)
+ backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
new file mode 100644
index 0000000000..b5dfb0949a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs
@@ -0,0 +1,99 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnHitObjectArea : Container, IHasAccentColour
+ {
+ private const float hit_target_height = 10;
+ private const float hit_target_bar_height = 2;
+
+ private Container content;
+ protected override Container Content => content;
+
+ private readonly IBindable direction = new Bindable();
+
+ private Container hitTargetLine;
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ Drawable hitTargetBar;
+
+ InternalChildren = new[]
+ {
+ hitTargetBar = new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_height,
+ Colour = Color4.Black
+ },
+ hitTargetLine = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = hit_target_bar_height,
+ Masking = true,
+ Child = new Box { RelativeSizeAxes = Axes.Both }
+ },
+ content = new Container
+ {
+ Name = "Hit objects",
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
+
+ hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
+ hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ hitTargetLine.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
new file mode 100644
index 0000000000..4ce1614310
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs
@@ -0,0 +1,122 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.UI.Scrolling;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Mania.UI.Components
+{
+ public class ColumnKeyArea : CompositeDrawable, IKeyBindingHandler, IHasAccentColour
+ {
+ private const float key_icon_size = 10;
+ private const float key_icon_corner_radius = 3;
+
+ public ManiaAction Action;
+
+ private readonly IBindable direction = new Bindable();
+
+ private Container keyIcon;
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ Drawable gradient;
+
+ InternalChildren = new[]
+ {
+ gradient = new Box
+ {
+ Name = "Key gradient",
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.5f
+ },
+ keyIcon = new Container
+ {
+ Name = "Key icon",
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(key_icon_size),
+ Masking = true,
+ CornerRadius = key_icon_corner_radius,
+ BorderThickness = 2,
+ BorderColour = Color4.White, // Not true
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ }
+ };
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction =>
+ {
+ gradient.Colour = ColourInfo.GradientVertical(
+ direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0),
+ direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black);
+ }, true);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ updateColours();
+ }
+
+ private Color4 accentColour;
+
+ public Color4 AccentColour
+ {
+ get => accentColour;
+ set
+ {
+ if (accentColour == value)
+ return;
+ accentColour = value;
+
+ updateColours();
+ }
+ }
+
+ private void updateColours()
+ {
+ if (!IsLoaded)
+ return;
+
+ keyIcon.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Radius = 5,
+ Colour = accentColour.Opacity(0.5f),
+ };
+ }
+
+ public bool OnPressed(ManiaAction action)
+ {
+ if (action == Action)
+ keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
+ return false;
+ }
+
+ public bool OnReleased(ManiaAction action)
+ {
+ if (action == Action)
+ keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
+ return false;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
index bf8db63137..c74745868a 100644
--- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs
@@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
bool isTick = judgedObject is DrawableHoldNoteTick;
- Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;
diff --git a/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs b/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
new file mode 100644
index 0000000000..ee65e9f1a5
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/IScrollingInfo.cs
@@ -0,0 +1,17 @@
+// 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.Objects;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public interface IScrollingInfo
+ {
+ ///
+ /// The direction s should scroll in.
+ ///
+ IBindable Direction { get; }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
index 4b936fc7f9..2f8ad7b17e 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
@@ -17,18 +16,13 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
- public class ManiaPlayfield : ScrollingPlayfield
+ public class ManiaPlayfield : ManiaScrollingPlayfield
{
- ///
- /// Whether this playfield should be inverted. This flips everything inside the playfield.
- ///
- public readonly Bindable Inverted = new Bindable(true);
-
public List Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List stages = new List();
- public ManiaPlayfield(List stageDefinitions)
- : base(ScrollingDirection.Up)
+ public ManiaPlayfield(ScrollingDirection direction, List stageDefinitions)
+ : base(direction)
{
if (stageDefinitions == null)
throw new ArgumentNullException(nameof(stageDefinitions));
@@ -36,8 +30,6 @@ namespace osu.Game.Rulesets.Mania.UI
if (stageDefinitions.Count <= 0)
throw new ArgumentException("Can't have zero or fewer stages.");
- Inverted.Value = true;
-
GridContainer playfieldGrid;
InternalChild = playfieldGrid = new GridContainer
{
@@ -50,9 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI
int firstColumnIndex = 0;
for (int i = 0; i < stageDefinitions.Count; i++)
{
- var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
+ var newStage = new ManiaStage(direction, firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
- newStage.Inverted.BindTo(Inverted);
playfieldGrid.Content[0][i] = newStage;
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index a3145d6035..fa6fba0cd8 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
@@ -12,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -33,6 +35,9 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable BarLines;
+ private readonly Bindable configDirection = new Bindable();
+ private ScrollingInfo scrollingInfo;
+
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@@ -65,12 +70,24 @@ namespace osu.Game.Rulesets.Mania.UI
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(ManiaConfigManager config)
{
BarLines.ForEach(Playfield.Add);
+
+ config.BindWith(ManiaSetting.ScrollDirection, configDirection);
+ configDirection.BindValueChanged(d => scrollingInfo.Direction.Value = (ScrollingDirection)d, true);
}
- protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(scrollingInfo = new ScrollingInfo());
+ return dependencies;
+ }
+
+ protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(scrollingInfo.Direction, Beatmap.Stages)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -100,5 +117,11 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
+
+ private class ScrollingInfo : IScrollingInfo
+ {
+ public readonly Bindable Direction = new Bindable();
+ IBindable IScrollingInfo.Direction => Direction;
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs
new file mode 100644
index 0000000000..0fc9c1920a
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.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.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public enum ManiaScrollingDirection
+ {
+ Up = ScrollingDirection.Up,
+ Down = ScrollingDirection.Down
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
new file mode 100644
index 0000000000..f1ff0665cd
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingPlayfield.cs
@@ -0,0 +1,26 @@
+// 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.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ public class ManiaScrollingPlayfield : ScrollingPlayfield
+ {
+ private readonly IBindable direction = new Bindable();
+
+ public ManiaScrollingPlayfield(ScrollingDirection direction)
+ : base(direction)
+ {
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IScrollingInfo scrollingInfo)
+ {
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(direction => Direction.Value = direction, true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index cb93613c7d..7b68582944 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -24,20 +23,15 @@ namespace osu.Game.Rulesets.Mania.UI
///
/// A collection of s.
///
- internal class ManiaStage : ScrollingPlayfield
+ internal class ManiaStage : ManiaScrollingPlayfield
{
public const float HIT_TARGET_POSITION = 50;
- ///
- /// Whether this playfield should be inverted. This flips everything inside the playfield.
- ///
- public readonly Bindable Inverted = new Bindable(true);
-
public IReadOnlyList Columns => columnFlow.Children;
private readonly FillFlowContainer columnFlow;
- protected override Container Content => content;
- private readonly Container content;
+ protected override Container Content => barLineContainer;
+ private readonly Container barLineContainer;
public Container Judgements => judgements;
private readonly JudgementContainer judgements;
@@ -49,8 +43,8 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly int firstColumnIndex;
- public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
- : base(ScrollingDirection.Up)
+ public ManiaStage(ScrollingDirection direction, int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
+ : base(direction)
{
this.firstColumnIndex = firstColumnIndex;
@@ -106,13 +100,12 @@ namespace osu.Game.Rulesets.Mania.UI
Width = 1366, // Bar lines should only be masked on the vertical axis
BypassAutoSizeAxes = Axes.Both,
Masking = true,
- Child = content = new Container
+ Child = barLineContainer = new Container
{
Name = "Bar lines",
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
- Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
}
},
judgements = new JudgementContainer
@@ -131,7 +124,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(direction)
{
IsSpecial = isSpecial,
Action = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++
@@ -140,14 +133,14 @@ namespace osu.Game.Rulesets.Mania.UI
AddColumn(column);
}
- Inverted.ValueChanged += invertedChanged;
- Inverted.TriggerChange();
- }
-
- private void invertedChanged(bool newValue)
- {
- Scale = new Vector2(1, newValue ? -1 : 1);
- Judgements.Scale = Scale;
+ Direction.BindValueChanged(d =>
+ {
+ barLineContainer.Padding = new MarginPadding
+ {
+ Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0,
+ Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0,
+ };
+ }, true);
}
public void AddColumn(Column c)
@@ -218,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Due to masking differences, it is not possible to get the width of the columns container automatically
// While masking on effectively only the Y-axis, so we need to set the width of the bar line container manually
- content.Width = columnFlow.Width;
+ barLineContainer.Width = columnFlow.Width;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
index 50a259ae55..2b42eed0c5 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
@@ -10,6 +10,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
public double AimStrain;
public double SpeedStrain;
+ public double ApproachRate;
+ public double OverallDifficulty;
+ public int MaxCombo;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 400afbc043..62fafd8196 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -25,6 +25,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new OsuDifficultyAttributes(mods, 0);
+
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate);
Skill[] skills =
{
@@ -58,10 +61,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
+ // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate;
+ double preEmpt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate;
+
+ int maxCombo = beatmap.HitObjects.Count();
+ // Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
+ maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1);
+
return new OsuDifficultyAttributes(mods, starRating)
{
AimStrain = aimRating,
- SpeedStrain = speedRating
+ SpeedStrain = speedRating,
+ ApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5,
+ OverallDifficulty = (80 - hitWindowGreat) / 6,
+ MaxCombo = maxCombo
};
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index 3ab3cc879a..d4a60dd52f 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -22,16 +22,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private Mod[] mods;
- ///
- /// Approach rate adjusted by mods.
- ///
- private double realApproachRate;
-
- ///
- /// Overall difficulty adjusted by mods.
- ///
- private double realOverallDifficulty;
-
private double accuracy;
private int scoreMaxCombo;
private int countGreat;
@@ -63,13 +53,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => !m.Ranked))
return 0;
- // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
-
- realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
- realOverallDifficulty = (80 - hitWindowGreat) / 6;
-
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -94,8 +77,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
- categoryRatings.Add("OD", realOverallDifficulty);
- categoryRatings.Add("AR", realApproachRate);
+ categoryRatings.Add("OD", Attributes.OverallDifficulty);
+ categoryRatings.Add("AR", Attributes.ApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo);
}
@@ -120,22 +103,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
double approachRateFactor = 1.0f;
- if (realApproachRate > 10.33f)
- approachRateFactor += 0.45f * (realApproachRate - 10.33f);
- else if (realApproachRate < 8.0f)
+ if (Attributes.ApproachRate > 10.33f)
+ approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f);
+ else if (Attributes.ApproachRate < 8.0f)
{
// HD is worth more with lower ar!
if (mods.Any(h => h is OsuModHidden))
- approachRateFactor += 0.02f * (8.0f - realApproachRate);
+ approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate);
else
- approachRateFactor += 0.01f * (8.0f - realApproachRate);
+ approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate);
}
aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
- aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
+ aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight))
{
@@ -146,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
+ aimValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return aimValue;
}
@@ -172,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
- speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
+ speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;
return speedValue;
}
@@ -194,7 +177,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
+ double accuracyValue = Math.Pow(1.52163f, Attributes.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
index f9e5bfa89b..2eed41d13f 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
@@ -30,13 +30,16 @@ namespace osu.Game.Rulesets.Osu.Replays
}
}
- public override List GetPendingStates()
+ public override List GetPendingInputs()
{
- return new List
+ return new List
{
+ new MousePositionAbsoluteInput
+ {
+ Position = GamefieldToScreenSpace(Position ?? Vector2.Zero)
+ },
new ReplayState
{
- Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = CurrentFrame.Actions
}
};
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
new file mode 100644
index 0000000000..d18d03b1db
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.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.Difficulty;
+using osu.Game.Rulesets.Mods;
+
+namespace osu.Game.Rulesets.Taiko.Difficulty
+{
+ public class TaikoDifficultyAttributes : DifficultyAttributes
+ {
+ public double GreatHitWindow;
+ public int MaxCombo;
+
+ public TaikoDifficultyAttributes(Mod[] mods, double starRating)
+ : base(mods, starRating)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 473c205293..8b527c2c82 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -34,6 +35,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
+ if (!beatmap.HitObjects.Any())
+ return new TaikoDifficultyAttributes(mods, 0);
+
var difficultyHitObjects = new List();
foreach (var hitObject in beatmap.HitObjects)
@@ -47,7 +51,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
- return new DifficultyAttributes(mods, starRating);
+ return new TaikoDifficultyAttributes(mods, starRating)
+ {
+ // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
+ GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate,
+ MaxCombo = beatmap.HitObjects.Count(h => h is Hit)
+ };
}
private bool calculateStrainValues(List objects, double timeRate)
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 53cfb4fd0f..f530b6725c 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -8,13 +8,12 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
{
public class TaikoPerformanceCalculator : PerformanceCalculator
{
- private readonly int beatmapMaxCombo;
+ protected new TaikoDifficultyAttributes Attributes => (TaikoDifficultyAttributes)base.Attributes;
private Mod[] mods;
private int countGreat;
@@ -25,7 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
- beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
}
public override double Calculate(Dictionary categoryDifficulty = null)
@@ -78,8 +76,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
strainValue *= Math.Pow(0.985, countMiss);
// Combo scaling
- if (beatmapMaxCombo > 0)
- strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
+ if (Attributes.MaxCombo > 0)
+ strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(Attributes.MaxCombo, 0.5), 1.0);
if (mods.Any(m => m is ModHidden))
strainValue *= 1.025;
@@ -94,14 +92,12 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeAccuracyValue()
{
- // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
- double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
- if (hitWindowGreat <= 0)
+ if (Attributes.GreatHitWindow <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
- double accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
+ double accValue = Math.Pow(150.0 / Attributes.GreatHitWindow, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
new file mode 100644
index 0000000000..608f1f9be2
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Taiko.Judgements
+{
+ public class TaikoIntermediateSwellJudgement : TaikoJudgement
+ {
+ public override HitResult MaxResult => HitResult.Perfect;
+
+ public override bool AffectsCombo => false;
+
+ public TaikoIntermediateSwellJudgement()
+ {
+ Final = false;
+ }
+
+ ///
+ /// Computes the numeric result value for the combo portion of the score.
+ ///
+ /// The result to compute the value for.
+ /// The numeric result value.
+ protected override int NumericResultFor(HitResult result) => 0;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 519b56a3ed..bb9cd02b14 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (State.Value)
{
case ArmedState.Idle:
+ SecondHitAllowed = false;
+ validKeyPressed = false;
+
UnproxyContent();
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
break;
@@ -95,8 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
break;
case ArmedState.Hit:
// If we're far enough away from the left stage, we should bring outselves in front of it
- if (X >= -0.05f)
- ProxyContent();
+ ProxyContent();
var flash = circlePiece?.FlashBox;
if (flash != null)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
index c416d50062..b431d35e16 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHitStrong.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Judgements;
@@ -46,6 +47,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
AddJudgement(new TaikoStrongHitJudgement { Result = HitResult.Great });
}
+ protected override void UpdateState(ArmedState state)
+ {
+ base.UpdateState(state);
+
+ switch (state)
+ {
+ case ArmedState.Idle:
+ firstHitTime = 0;
+ firstKeyHeld = false;
+ break;
+ }
+ }
+
public override bool OnReleased(TaikoAction action)
{
if (action == firstHitAction)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index df36a475d6..408b37e377 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -20,6 +19,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableSwell : DrawableTaikoHitObject
{
+ ///
+ /// A judgement is only displayed when the user has complete the swell (either a hit or miss).
+ ///
+ public override bool DisplayJudgement => AllJudged;
+
private const float target_ring_thick_border = 1.4f;
private const float target_ring_thin_border = 1f;
private const float target_ring_scale = 5f;
@@ -29,11 +33,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
- ///
- /// The amount of times the user has hit this swell.
- ///
- private int userHits;
-
private readonly SwellSymbolPiece symbol;
public DrawableSwell(Swell swell)
@@ -129,9 +128,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
if (userTriggered)
{
- userHits++;
+ AddJudgement(new TaikoIntermediateSwellJudgement());
- var completion = (float)userHits / HitObject.RequiredHits;
+ var completion = (float)Judgements.Count / HitObject.RequiredHits;
expandingRing
.FadeTo(expandingRing.Alpha + MathHelper.Clamp(completion / 16, 0.1f, 0.6f), 50)
@@ -142,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
expandingRing.ScaleTo(1f + Math.Min(target_ring_scale - 1f, (target_ring_scale - 1f) * completion * 1.3f), 260, Easing.OutQuint);
- if (userHits == HitObject.RequiredHits)
+ if (Judgements.Count == HitObject.RequiredHits)
AddJudgement(new TaikoJudgement { Result = HitResult.Great });
}
else
@@ -151,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return;
//TODO: THIS IS SHIT AND CAN'T EXIST POST-TAIKO WORLD CUP
- AddJudgement(userHits > HitObject.RequiredHits / 2
+ AddJudgement(Judgements.Count > HitObject.RequiredHits / 2
? new TaikoJudgement { Result = HitResult.Good }
: new TaikoJudgement { Result = HitResult.Miss });
}
@@ -162,23 +161,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
const float preempt = 100;
const float out_transition_time = 300;
- double untilStartTime = HitObject.StartTime - Time.Current;
- double untilJudgement = untilStartTime + (Judgements.FirstOrDefault()?.TimeOffset ?? 0) + HitObject.Duration;
-
- targetRing.Delay(untilStartTime - preempt).ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
- this.Delay(untilJudgement).FadeOut(out_transition_time, Easing.Out);
-
switch (state)
{
case ArmedState.Idle:
UnproxyContent();
+ expandingRing.FadeTo(0);
+ using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true))
+ targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint);
break;
+ case ArmedState.Miss:
case ArmedState.Hit:
- bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
+ this.FadeOut(out_transition_time, Easing.Out);
+ bodyContainer.ScaleTo(1.4f, out_transition_time);
+
+ Expire();
break;
}
-
- Expire();
}
protected override void Update()
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
index 6ccbd575e5..eae033401e 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
@@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
- public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
+ public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 7b4978694b..609fd27bb4 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
{
+ new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
+ new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.D, TaikoAction.LeftRim),
new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
new KeyBinding(InputKey.J, TaikoAction.RightCentre),
new KeyBinding(InputKey.K, TaikoAction.RightRim),
- new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
- new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
- new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
- new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
};
public override IEnumerable ConvertLegacyMods(LegacyMods mods)
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 313c205981..999e33e51a 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -69,11 +69,7 @@ namespace osu.Game.Rulesets.Taiko.UI
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
- double bl = currentPoint.BeatLength;
- if (bl < 800)
- bl *= (int)currentPoint.TimeSignature;
-
- time += bl;
+ time += currentPoint.BeatLength * (int)currentPoint.TimeSignature;
currentBeat++;
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseLounge.cs b/osu.Game.Tests/Visual/TestCaseLounge.cs
index b96d705d5c..174873b011 100644
--- a/osu.Game.Tests/Visual/TestCaseLounge.cs
+++ b/osu.Game.Tests/Visual/TestCaseLounge.cs
@@ -8,6 +8,8 @@ using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
+using osu.Game.Screens;
+using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Screens.Lounge;
using osu.Game.Users;
@@ -165,6 +167,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"set rooms", () => lounge.Rooms = rooms);
selectAssert(1);
AddStep(@"open room 1", () => clickRoom(1));
+ AddUntilStep(() => lounge.ChildScreen?.IsCurrentScreen == true, "wait until room current");
AddStep(@"make lounge current", lounge.MakeCurrent);
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
filterAssert(string.Empty, LoungeTab.Public, 2);
@@ -198,6 +201,8 @@ namespace osu.Game.Tests.Visual
private class TestLounge : Lounge
{
+ protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
+
public IEnumerable ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
public Room SelectedRoom => Inspector.Room;
diff --git a/osu.Game.Tests/Visual/TestCaseMatch.cs b/osu.Game.Tests/Visual/TestCaseMatch.cs
new file mode 100644
index 0000000000..bb22358425
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMatch.cs
@@ -0,0 +1,142 @@
+// 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.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Multi.Screens.Match;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMatch : OsuTestCase
+ {
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ Room room = new Room
+ {
+ Name = { Value = @"One Awesome Room" },
+ Status = { Value = new RoomStatusOpen() },
+ Availability = { Value = RoomAvailability.Public },
+ Type = { Value = new GameTypeTeamVersus() },
+ Beatmap =
+ {
+ Value = new BeatmapInfo
+ {
+ StarDifficulty = 5.02,
+ Ruleset = rulesets.GetRuleset(1),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"Paradigm Shift",
+ Artist = @"Morimori Atsushi",
+ AuthorString = @"eiri-",
+ },
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/765055/covers/cover.jpg?1526955337",
+ },
+ },
+ },
+ },
+ },
+ MaxParticipants = { Value = 5 },
+ Participants =
+ {
+ Value = new[]
+ {
+ new User
+ {
+ Username = @"eiri-",
+ Id = 3388410,
+ Country = new Country { FlagName = @"US" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3388410/00a8486a247831e1cc4375db519f611ac970bda8bc0057d78b0f540ea38c3e58.jpeg",
+ IsSupporter = true,
+ },
+ new User
+ {
+ Username = @"Nepuri",
+ Id = 6637817,
+ Country = new Country { FlagName = @"DE" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/6637817/9085fc60248b6b5327a72c1dcdecf2dbedba810ae0ab6bcf7224e46b1339632a.jpeg",
+ IsSupporter = true,
+ },
+ new User
+ {
+ Username = @"goheegy",
+ Id = 8057655,
+ Country = new Country { FlagName = @"GB" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8057655/21cec27c25a11dc197a4ec6a74253dbabb495949b0e0697113352f12007018c5.jpeg",
+ },
+ new User
+ {
+ Username = @"Alumetri",
+ Id = 5371497,
+ Country = new Country { FlagName = @"RU" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5371497/e023b8c7fbe3613e64bd4856703517ea50fbed8a5805dc9acda9efe9897c67e2.jpeg",
+ },
+ }
+ },
+ };
+
+ Match match = new Match(room);
+
+ AddStep(@"show", () => Add(match));
+ AddStep(@"null beatmap", () => room.Beatmap.Value = null);
+ AddStep(@"change name", () => room.Name.Value = @"Two Awesome Rooms");
+ AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
+ AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
+ AddStep(@"change type", () => room.Type.Value = new GameTypeTag());
+ AddStep(@"change beatmap", () => room.Beatmap.Value = new BeatmapInfo
+ {
+ StarDifficulty = 4.33,
+ Ruleset = rulesets.GetRuleset(2),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"Yasashisa no Riyuu",
+ Artist = @"ChouCho",
+ AuthorString = @"celerih",
+ },
+ BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/685391/covers/cover.jpg?1524597970",
+ },
+ },
+ },
+ });
+
+ AddStep(@"null max participants", () => room.MaxParticipants.Value = null);
+ AddStep(@"change participants", () => room.Participants.Value = new[]
+ {
+ new User
+ {
+ Username = @"Spectator",
+ Id = 702598,
+ Country = new Country { FlagName = @"KR" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/702598/3bbf4cb8b8d2cf8b03145000a975ff27e191ab99b0920832e7dd67386280e288.jpeg",
+ IsSupporter = true,
+ },
+ new User
+ {
+ Username = @"celerih",
+ Id = 4696296,
+ Country = new Country { FlagName = @"CA" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/4696296/7f8500731d0ac66d5472569d146a7be07d9460273361913f22c038867baddaef.jpeg",
+ },
+ });
+
+ AddStep(@"exit", match.Exit);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/TestCaseMatchHeader.cs
new file mode 100644
index 0000000000..34f98f97c2
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMatchHeader.cs
@@ -0,0 +1,43 @@
+// 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.Game.Beatmaps;
+using osu.Game.Screens.Multi.Screens.Match;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMatchHeader : OsuTestCase
+ {
+ public TestCaseMatchHeader()
+ {
+ Header header = new Header();
+ Add(header);
+
+ AddStep(@"set beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/760757/covers/cover.jpg?1526944540",
+ },
+ },
+ });
+
+ AddStep(@"change beatmap set", () => header.BeatmapSet = new BeatmapSetInfo
+ {
+ OnlineInfo = new BeatmapSetOnlineInfo
+ {
+ Covers = new BeatmapSetOnlineCovers
+ {
+ Cover = @"https://assets.ppy.sh/beatmaps/761883/covers/cover.jpg?1525557400",
+ },
+ },
+ });
+
+ AddStep(@"null beatmap set", () => header.BeatmapSet = null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/TestCaseMatchInfo.cs
new file mode 100644
index 0000000000..205da6932f
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMatchInfo.cs
@@ -0,0 +1,57 @@
+// 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.Allocation;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Multi.Screens.Match;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMatchInfo : OsuTestCase
+ {
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ Info info = new Info();
+ Add(info);
+
+ AddStep(@"set name", () => info.Name = @"Room Name?");
+ AddStep(@"set availability", () => info.Availability = RoomAvailability.FriendsOnly);
+ AddStep(@"set status", () => info.Status = new RoomStatusPlaying());
+ AddStep(@"set beatmap", () => info.Beatmap = new BeatmapInfo
+ {
+ StarDifficulty = 2.4,
+ Ruleset = rulesets.GetRuleset(0),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"My Song",
+ Artist = @"VisualTests",
+ AuthorString = @"osu!lazer",
+ },
+ });
+
+ AddStep(@"set type", () => info.Type = new GameTypeTagTeam());
+
+ AddStep(@"change name", () => info.Name = @"Room Name!");
+ AddStep(@"change availability", () => info.Availability = RoomAvailability.InviteOnly);
+ AddStep(@"change status", () => info.Status = new RoomStatusOpen());
+ AddStep(@"null beatmap", () => info.Beatmap = null);
+ AddStep(@"change type", () => info.Type = new GameTypeTeamVersus());
+ AddStep(@"change beatmap", () => info.Beatmap = new BeatmapInfo
+ {
+ StarDifficulty = 4.2,
+ Ruleset = rulesets.GetRuleset(3),
+ Metadata = new BeatmapMetadata
+ {
+ Title = @"Your Song",
+ Artist = @"Tester",
+ AuthorString = @"Someone",
+ },
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
new file mode 100644
index 0000000000..d6ae07252b
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs
@@ -0,0 +1,56 @@
+// 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.Game.Screens.Multi.Screens.Match;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseMatchParticipants : OsuTestCase
+ {
+ public TestCaseMatchParticipants()
+ {
+ Participants participants;
+ Add(participants = new Participants
+ {
+ RelativeSizeAxes = Axes.Both,
+ });
+
+ AddStep(@"set max to null", () => participants.Max = null);
+ AddStep(@"set users", () => participants.Users = new[]
+ {
+ new User
+ {
+ Username = @"Feppla",
+ Id = 4271601,
+ Country = new Country { FlagName = @"SE" },
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
+ IsSupporter = true,
+ },
+ new User
+ {
+ Username = @"Xilver",
+ Id = 3099689,
+ Country = new Country { FlagName = @"IL" },
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
+ IsSupporter = true,
+ },
+ new User
+ {
+ Username = @"Wucki",
+ Id = 5287410,
+ Country = new Country { FlagName = @"FI" },
+ CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
+ IsSupporter = true,
+ },
+ });
+
+ AddStep(@"set max", () => participants.Max = 10);
+ AddStep(@"clear users", () => participants.Users = new User[] { });
+ AddStep(@"set max to null", () => participants.Max = null);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
new file mode 100644
index 0000000000..8c12589c6f
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs
@@ -0,0 +1,26 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Graphics.Containers;
+using osu.Game.Screens.Backgrounds;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseParallaxContainer : OsuTestCase
+ {
+ public TestCaseParallaxContainer()
+ {
+ ParallaxContainer parallax;
+
+ Add(parallax = new ParallaxContainer
+ {
+ Child = new BackgroundScreenDefault { Alpha = 0.8f }
+ });
+
+ AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
+ AddStep("high parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * 10);
+ AddStep("no parallax", () => parallax.ParallaxAmount = 0);
+ AddStep("negative parallax", () => parallax.ParallaxAmount = -ParallaxContainer.DEFAULT_PARALLAX_AMOUNT);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
new file mode 100644
index 0000000000..d711d501fe
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs
@@ -0,0 +1,127 @@
+// 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.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner
+ {
+ private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager();
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(trackManager);
+ dependencies.CacheAs(this);
+ return dependencies;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddInternal(trackManager);
+ }
+
+ [Test]
+ public void TestStartStop()
+ {
+ PreviewTrack track = null;
+
+ AddStep("get track", () => track = getOwnedTrack());
+ AddStep("start", () => track.Start());
+ AddAssert("started", () => track.IsRunning);
+ AddStep("stop", () => track.Stop());
+ AddAssert("stopped", () => !track.IsRunning);
+ }
+
+ [Test]
+ public void TestStartMultipleTracks()
+ {
+ PreviewTrack track1 = null;
+ PreviewTrack track2 = null;
+
+ AddStep("get tracks", () =>
+ {
+ track1 = getOwnedTrack();
+ track2 = getOwnedTrack();
+ });
+
+ AddStep("start track 1", () => track1.Start());
+ AddStep("start track 2", () => track2.Start());
+ AddAssert("track 1 stopped", () => !track1.IsRunning);
+ AddAssert("track 2 started", () => track2.IsRunning);
+ }
+
+ [Test]
+ public void TestCancelFromOwner()
+ {
+ PreviewTrack track = null;
+
+ AddStep("get track", () => track = getOwnedTrack());
+ AddStep("start", () => track.Start());
+ AddStep("stop by owner", () => trackManager.StopAnyPlaying(this));
+ AddAssert("stopped", () => !track.IsRunning);
+ }
+
+ [Test]
+ public void TestCancelFromNonOwner()
+ {
+ TestTrackOwner owner = null;
+ PreviewTrack track = null;
+
+ AddStep("get track", () => AddInternal(owner = new TestTrackOwner(track = getTrack())));
+ AddStep("start", () => track.Start());
+ AddStep("attempt stop", () => trackManager.StopAnyPlaying(this));
+ AddAssert("not stopped", () => track.IsRunning);
+ AddStep("stop by true owner", () => trackManager.StopAnyPlaying(owner));
+ AddAssert("stopped", () => !track.IsRunning);
+ }
+
+ private PreviewTrack getTrack() => trackManager.Get(null);
+
+ private PreviewTrack getOwnedTrack()
+ {
+ var track = getTrack();
+
+ AddInternal(track);
+
+ return track;
+ }
+
+ private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner
+ {
+ public TestTrackOwner(PreviewTrack track)
+ {
+ AddInternal(track);
+ }
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ {
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(this);
+ return dependencies;
+ }
+ }
+
+ private class TestPreviewTrackManager : PreviewTrackManager
+ {
+ protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TestPreviewTrack(beatmapSetInfo, trackManager);
+
+ protected class TestPreviewTrack : TrackManagerPreviewTrack
+ {
+ public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
+ : base(beatmapSetInfo, trackManager)
+ {
+ }
+
+ protected override Track GetTrack() => new TrackVirtual { Length = 100000 };
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
index 8f90b6d87e..9b48ec17bd 100644
--- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual
private class TestPlayfield : ScrollingPlayfield
{
- public readonly ScrollingDirection Direction;
+ public new readonly ScrollingDirection Direction;
public TestPlayfield(ScrollingDirection direction)
: base(direction)
diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs
new file mode 100644
index 0000000000..f166096601
--- /dev/null
+++ b/osu.Game/Audio/IPreviewTrackOwner.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+namespace osu.Game.Audio
+{
+ ///
+ /// Interface for objects that can own s.
+ ///
+ ///
+ /// s can cancel the currently playing through the
+ /// global if they're the owner of the playing .
+ ///
+ public interface IPreviewTrackOwner
+ {
+ }
+}
diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs
new file mode 100644
index 0000000000..3c9122b941
--- /dev/null
+++ b/osu.Game/Audio/PreviewTrack.cs
@@ -0,0 +1,103 @@
+// 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.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Threading;
+
+namespace osu.Game.Audio
+{
+ public abstract class PreviewTrack : Component
+ {
+ ///
+ /// Invoked when this has stopped playing.
+ ///
+ public event Action Stopped;
+
+ ///
+ /// Invoked when this has started playing.
+ ///
+ public event Action Started;
+
+ private Track track;
+ private bool hasStarted;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ track = GetTrack();
+ }
+
+ ///
+ /// Length of the track.
+ ///
+ public double Length => track?.Length ?? 0;
+
+ ///
+ /// The current track time.
+ ///
+ public double CurrentTime => track?.CurrentTime ?? 0;
+
+ ///
+ /// Whether the track is loaded.
+ ///
+ public bool TrackLoaded => track?.IsLoaded ?? false;
+
+ ///
+ /// Whether the track is playing.
+ ///
+ public bool IsRunning => track?.IsRunning ?? false;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ // Todo: Track currently doesn't signal its completion, so we have to handle it manually
+ if (hasStarted && track.HasCompleted)
+ Stop();
+ }
+
+ private ScheduledDelegate startDelegate;
+
+ ///
+ /// Starts playing this .
+ ///
+ public void Start() => startDelegate = Schedule(() =>
+ {
+ if (track == null)
+ return;
+
+ if (hasStarted)
+ return;
+ hasStarted = true;
+
+ track.Restart();
+ Started?.Invoke();
+ });
+
+ ///
+ /// Stops playing this .
+ ///
+ public void Stop()
+ {
+ startDelegate?.Cancel();
+
+ if (track == null)
+ return;
+
+ if (!hasStarted)
+ return;
+ hasStarted = false;
+
+ track.Stop();
+ Stopped?.Invoke();
+ }
+
+ ///
+ /// Retrieves the audio track.
+ ///
+ protected abstract Track GetTrack();
+ }
+}
diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs
new file mode 100644
index 0000000000..07fbe86ff4
--- /dev/null
+++ b/osu.Game/Audio/PreviewTrackManager.cs
@@ -0,0 +1,107 @@
+// 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.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Configuration;
+using osu.Framework.Graphics;
+using osu.Framework.IO.Stores;
+using osu.Game.Beatmaps;
+
+namespace osu.Game.Audio
+{
+ ///
+ /// A central store for the retrieval of s.
+ ///
+ public class PreviewTrackManager : Component
+ {
+ private readonly BindableDouble muteBindable = new BindableDouble();
+
+ private AudioManager audio;
+ private TrackManager trackManager;
+
+ private TrackManagerPreviewTrack current;
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio, FrameworkConfigManager config)
+ {
+ trackManager = new TrackManager(new OnlineStore());
+
+ this.audio = audio;
+ audio.AddItem(trackManager);
+
+ config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume);
+ }
+
+ ///
+ /// Retrieves a for a .
+ ///
+ /// The to retrieve the preview track for.
+ /// The playable .
+ public PreviewTrack Get(BeatmapSetInfo beatmapSetInfo)
+ {
+ var track = CreatePreviewTrack(beatmapSetInfo, trackManager);
+
+ track.Started += () =>
+ {
+ current?.Stop();
+ current = track;
+ audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable);
+ };
+
+ track.Stopped += () =>
+ {
+ current = null;
+ audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
+ };
+
+ return track;
+ }
+
+ ///
+ /// Stops any currently playing .
+ ///
+ ///
+ /// Only the immediate owner (an object that implements ) of the playing
+ /// can globally stop the currently playing . The object holding a reference to the
+ /// can always stop the themselves through .
+ ///
+ /// The which may be the owner of the .
+ public void StopAnyPlaying(IPreviewTrackOwner source)
+ {
+ if (current == null || current.Owner != source)
+ return;
+
+ current.Stop();
+ current = null;
+ }
+
+ ///
+ /// Creates the .
+ ///
+ protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TrackManagerPreviewTrack(beatmapSetInfo, trackManager);
+
+ protected class TrackManagerPreviewTrack : PreviewTrack
+ {
+ public IPreviewTrackOwner Owner { get; private set; }
+
+ private readonly BeatmapSetInfo beatmapSetInfo;
+ private readonly TrackManager trackManager;
+
+ public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager)
+ {
+ this.beatmapSetInfo = beatmapSetInfo;
+ this.trackManager = trackManager;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(IPreviewTrackOwner owner)
+ {
+ Owner = owner;
+ }
+
+ protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3");
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 5e3b66646b..50428cc5e6 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -389,6 +389,9 @@ namespace osu.Game.Beatmaps
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
return true;
+ if (api.State != APIState.Online)
+ return false;
+
Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
try
diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
index 0528f7b3ae..d6b6595b69 100644
--- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs
@@ -8,20 +8,32 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Configuration;
+using osu.Game.Audio;
using osu.Game.Overlays;
namespace osu.Game.Graphics.Containers
{
- public class OsuFocusedOverlayContainer : FocusedOverlayContainer
+ public class OsuFocusedOverlayContainer : FocusedOverlayContainer, IPreviewTrackOwner
{
private SampleChannel samplePopIn;
private SampleChannel samplePopOut;
+ private PreviewTrackManager previewTrackManager;
+
protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
- [BackgroundDependencyLoader(true)]
- private void load(OsuGame osuGame, AudioManager audio)
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
+ var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+ dependencies.CacheAs(this);
+ return dependencies;
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load(OsuGame osuGame, AudioManager audio, PreviewTrackManager previewTrackManager)
+ {
+ this.previewTrackManager = previewTrackManager;
+
if (osuGame != null)
OverlayActivationMode.BindTo(osuGame.OverlayActivationMode);
@@ -66,5 +78,11 @@ namespace osu.Game.Graphics.Containers
break;
}
}
+
+ protected override void PopOut()
+ {
+ base.PopOut();
+ previewTrackManager.StopAnyPlaying(this);
+ }
}
}
diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs
index dc635ce7e7..8e1e5d54fa 100644
--- a/osu.Game/Graphics/Containers/ParallaxContainer.cs
+++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs
@@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers
{
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
+ ///
+ /// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
+ ///
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
private Bindable parallaxEnabled;
@@ -45,7 +48,7 @@ namespace osu.Game.Graphics.Containers
if (!parallaxEnabled)
{
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint);
- content.Scale = new Vector2(1 + ParallaxAmount);
+ content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount));
}
};
}
@@ -69,7 +72,7 @@ namespace osu.Game.Graphics.Containers
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
content.Position = Interpolation.ValueAt(elapsed, content.Position, offset, 0, 1000, Easing.OutQuint);
- content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + ParallaxAmount), 0, 1000, Easing.OutQuint);
+ content.Scale = Interpolation.ValueAt(elapsed, content.Scale, new Vector2(1 + System.Math.Abs(ParallaxAmount)), 0, 1000, Easing.OutQuint);
}
firstUpdate = false;
diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs
index 5454dd0c9f..57a2e5df6d 100644
--- a/osu.Game/Input/Handlers/ReplayInputHandler.cs
+++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs
@@ -32,16 +32,14 @@ namespace osu.Game.Input.Handlers
public override int Priority => 0;
- public class ReplayState : InputState
+ public class ReplayState : IInput
where T : struct
{
public List PressedActions;
- public override InputState Clone()
+ public void Apply(InputState state, IInputStateChangeHandler handler)
{
- var clone = (ReplayState)base.Clone();
- clone.PressedActions = new List(PressedActions);
- return clone;
+ handler.HandleCustomInput(state, this);
}
}
}
diff --git a/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs
new file mode 100644
index 0000000000..aaa11e88b6
--- /dev/null
+++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.Designer.cs
@@ -0,0 +1,376 @@
+//
+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("20180621044111_UpdateTaikoDefaultBindings")]
+ partial class UpdateTaikoDefaultBindings
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
+
+ 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("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.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("Name");
+
+ b.HasKey("ID");
+
+ 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/20180621044111_UpdateTaikoDefaultBindings.cs b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs
new file mode 100644
index 0000000000..98ce5def08
--- /dev/null
+++ b/osu.Game/Migrations/20180621044111_UpdateTaikoDefaultBindings.cs
@@ -0,0 +1,19 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using osu.Framework.Logging;
+
+namespace osu.Game.Migrations
+{
+ public partial class UpdateTaikoDefaultBindings : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID = 1");
+ Logger.Log("osu!taiko bindings have been reset due to new defaults", LoggingTarget.Runtime, LogLevel.Important);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // we can't really tell if these should be restored or not, so let's just not do so.
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index d750d50b5b..bd80cb743b 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -1,11 +1,9 @@
//
+using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
-using System;
namespace osu.Game.Migrations
{
@@ -16,7 +14,7 @@ namespace osu.Game.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "2.0.3-rtm-10026");
+ .HasAnnotation("ProductVersion", "2.1.1-rtm-30846");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 36c76851c6..ba8685b5b2 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -101,6 +101,8 @@ namespace osu.Game
public OsuGame(string[] args = null)
{
this.args = args;
+
+ forwardLoggedErrorsToNotifications();
}
public void ToggleSettings() => settings.ToggleVisibility();
@@ -305,8 +307,6 @@ namespace osu.Game
Depth = -6,
}, overlayContent.Add);
- forwardLoggedErrorsToNotifications();
-
dependencies.Cache(settings);
dependencies.Cache(onscreenDisplay);
dependencies.Cache(social);
@@ -394,31 +394,40 @@ namespace osu.Game
private void forwardLoggedErrorsToNotifications()
{
- int recentErrorCount = 0;
+ int recentLogCount = 0;
const double debounce = 5000;
Logger.NewEntry += entry =>
{
- if (entry.Level < LogLevel.Error || entry.Target == null) return;
+ if (entry.Level < LogLevel.Important || entry.Target == null) return;
- if (recentErrorCount < 2)
+ const int short_term_display_limit = 3;
+
+ if (recentLogCount < short_term_display_limit)
{
- notifications.Post(new SimpleNotification
+ Schedule(() => notifications.Post(new SimpleNotification
{
- Icon = FontAwesome.fa_bomb,
- Text = (recentErrorCount == 0 ? entry.Message : "Subsequent errors occurred and have been logged.") + "\nClick to view log files.",
+ Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb,
+ Text = entry.Message,
+ }));
+ }
+ else if (recentLogCount == short_term_display_limit)
+ {
+ Schedule(() => notifications.Post(new SimpleNotification
+ {
+ Icon = FontAwesome.fa_ellipsis_h,
+ Text = "Subsequent messages have been logged. Click to view log files.",
Activated = () =>
{
Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer();
return true;
}
- });
+ }));
}
- Interlocked.Increment(ref recentErrorCount);
-
- Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentErrorCount), debounce);
+ Interlocked.Increment(ref recentLogCount);
+ Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentLogCount), debounce);
};
}
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index a5779a2293..246229a794 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -21,6 +21,7 @@ using osu.Game.Online.API;
using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
+using osu.Game.Audio;
using osu.Game.Database;
using osu.Game.Graphics.Textures;
using osu.Game.Input;
@@ -187,6 +188,10 @@ namespace osu.Game
KeyBindingStore.Register(globalBinding);
dependencies.Cache(globalBinding);
+
+ PreviewTrackManager previewTrackManager;
+ dependencies.Cache(previewTrackManager = new PreviewTrackManager());
+ Add(previewTrackManager);
}
protected override void LoadComplete()
diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
index 08a99f1aea..505b7a7540 100644
--- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
+++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs
@@ -2,13 +2,13 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
-using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
private readonly Box bg, progress;
private readonly PlayButton playButton;
- private Track preview => playButton.Preview;
+ private PreviewTrack preview => playButton.Preview;
public Bindable Playing => playButton.Playing;
public BeatmapSetInfo BeatmapSet
@@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
},
};
- Action = () => Playing.Value = !Playing.Value;
+ Action = () => playButton.TriggerOnClick();
Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100);
}
@@ -89,12 +89,6 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
progress.Width = 0;
}
- protected override void Dispose(bool isDisposing)
- {
- Playing.Value = false;
- base.Dispose(isDisposing);
- }
-
protected override bool OnHover(InputState state)
{
bg.FadeColour(Color4.Black.Opacity(0.5f), 100);
diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs
index 5264caf936..ccd0fa04ab 100644
--- a/osu.Game/Overlays/BeatmapSet/Details.cs
+++ b/osu.Game/Overlays/BeatmapSet/Details.cs
@@ -102,8 +102,6 @@ namespace osu.Game.Overlays.BeatmapSet
updateDisplay();
}
- public void StopPreview() => preview.Playing.Value = false;
-
private class DetailBox : Container
{
private readonly Container content;
diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs
index 096f7bb63c..88f0a72ddf 100644
--- a/osu.Game/Overlays/BeatmapSetOverlay.cs
+++ b/osu.Game/Overlays/BeatmapSetOverlay.cs
@@ -1,9 +1,8 @@
// 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 OpenTK;
-using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -15,9 +14,10 @@ using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet;
-using osu.Game.Rulesets;
using osu.Game.Overlays.BeatmapSet.Scores;
-using System.Linq;
+using osu.Game.Rulesets;
+using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Overlays
{
@@ -124,8 +124,6 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
base.PopOut();
- header.Details.StopPreview();
-
FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null);
}
diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs
index e767f6ec83..e63c290ce5 100644
--- a/osu.Game/Overlays/Direct/DirectPanel.cs
+++ b/osu.Game/Overlays/Direct/DirectPanel.cs
@@ -4,22 +4,22 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
-using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using OpenTK.Graphics;
-using osu.Framework.Input;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests;
-using osu.Framework.Configuration;
-using osu.Framework.Audio.Track;
+using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct
{
@@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Direct
private BeatmapManager beatmaps;
private BeatmapSetOverlay beatmapSetOverlay;
- public Track Preview => PlayButton.Preview;
+ public PreviewTrack Preview => PlayButton.Preview;
public Bindable PreviewPlaying => PlayButton.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
@@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Direct
{
base.Update();
- if (PreviewPlaying && Preview != null && Preview.IsLoaded)
+ if (PreviewPlaying && Preview != null && Preview.TrackLoaded)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
@@ -141,7 +141,6 @@ namespace osu.Game.Overlays.Direct
protected override bool OnClick(InputState state)
{
ShowInformation();
- PreviewPlaying.Value = false;
return true;
}
diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs
index 131083c6ff..4b91a3d700 100644
--- a/osu.Game/Overlays/Direct/PlayButton.cs
+++ b/osu.Game/Overlays/Direct/PlayButton.cs
@@ -1,25 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK.Graphics;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
-using osu.Framework.IO.Stores;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
+using OpenTK.Graphics;
namespace osu.Game.Overlays.Direct
{
public class PlayButton : Container
{
- public readonly Bindable Playing = new Bindable();
- public Track Preview { get; private set; }
+ public readonly BindableBool Playing = new BindableBool();
+ public PreviewTrack Preview { get; private set; }
private BeatmapSetInfo beatmapSet;
@@ -31,9 +29,11 @@ namespace osu.Game.Overlays.Direct
if (value == beatmapSet) return;
beatmapSet = value;
- Playing.Value = false;
- trackLoader = null;
+ Preview?.Stop();
+ Preview?.Expire();
Preview = null;
+
+ Playing.Value = false;
}
}
@@ -41,8 +41,6 @@ namespace osu.Game.Overlays.Direct
private readonly SpriteIcon icon;
private readonly LoadingAnimation loadingAnimation;
- private readonly BindableDouble muteBindable = new BindableDouble();
-
private const float transition_duration = 500;
private bool loading
@@ -50,15 +48,9 @@ namespace osu.Game.Overlays.Direct
set
{
if (value)
- {
loadingAnimation.Show();
- icon.FadeOut(transition_duration * 5, Easing.OutQuint);
- }
else
- {
loadingAnimation.Hide();
- icon.FadeIn(transition_duration, Easing.OutQuint);
- }
}
}
@@ -78,19 +70,22 @@ namespace osu.Game.Overlays.Direct
loadingAnimation = new LoadingAnimation(),
});
- Playing.ValueChanged += updatePreviewTrack;
+ Playing.ValueChanged += playingStateChanged;
}
+ private PreviewTrackManager previewTrackManager;
+
[BackgroundDependencyLoader]
- private void load(OsuColour colour, AudioManager audio)
+ private void load(OsuColour colour, PreviewTrackManager previewTrackManager)
{
+ this.previewTrackManager = previewTrackManager;
+
hoverColour = colour.Yellow;
- this.audio = audio;
}
protected override bool OnClick(InputState state)
{
- Playing.Value = !Playing.Value;
+ Playing.Toggle();
return true;
}
@@ -107,44 +102,44 @@ namespace osu.Game.Overlays.Direct
base.OnHoverLost(state);
}
- protected override void Update()
+ private void playingStateChanged(bool playing)
{
- base.Update();
-
- if (Preview?.HasCompleted ?? false)
- {
- Playing.Value = false;
- Preview = null;
- }
- }
-
- private void updatePreviewTrack(bool playing)
- {
- if (playing && BeatmapSet == null)
- {
- Playing.Value = false;
- return;
- }
-
icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play;
icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint);
if (playing)
{
- if (Preview == null)
+ if (BeatmapSet == null)
{
- beginAudioLoad();
+ Playing.Value = false;
return;
}
- Preview.Restart();
+ if (Preview != null)
+ {
+ Preview.Start();
+ return;
+ }
- audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable);
+ loading = true;
+
+ LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
+ {
+ // beatmapset may have changed.
+ if (Preview != preview)
+ return;
+
+ AddInternal(preview);
+ loading = false;
+ preview.Stopped += () => Playing.Value = false;
+
+ // user may have changed their mind.
+ if (Playing)
+ preview.Start();
+ });
}
else
{
- audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable);
-
Preview?.Stop();
loading = false;
}
@@ -155,64 +150,5 @@ namespace osu.Game.Overlays.Direct
base.Dispose(isDisposing);
Playing.Value = false;
}
-
- private TrackLoader trackLoader;
- private AudioManager audio;
-
- private void beginAudioLoad()
- {
- if (trackLoader != null)
- {
- Preview = trackLoader.Preview;
- Playing.TriggerChange();
- return;
- }
-
- loading = true;
-
- LoadComponentAsync(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3"),
- d =>
- {
- // We may have been replaced by another loader
- if (trackLoader != d) return;
-
- Preview = d?.Preview;
- updatePreviewTrack(Playing);
- loading = false;
-
- Add(trackLoader);
- });
- }
-
- private class TrackLoader : Drawable
- {
- private readonly string preview;
-
- public Track Preview;
- private TrackManager trackManager;
-
- public TrackLoader(string preview)
- {
- this.preview = preview;
- }
-
- [BackgroundDependencyLoader]
- private void load(AudioManager audio, FrameworkConfigManager config)
- {
- // create a local trackManager to bypass the mute we are applying above.
- audio.AddItem(trackManager = new TrackManager(new OnlineStore()));
-
- // add back the user's music volume setting (since we are no longer in the global TrackManager's hierarchy).
- config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume);
-
- Preview = trackManager.Get(preview);
- }
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
- trackManager?.Dispose();
- }
- }
}
}
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index b33f271986..423211659d 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -4,12 +4,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
-using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Threading;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct;
using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets;
+using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays
@@ -32,7 +33,6 @@ namespace osu.Game.Overlays
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
private FillFlowContainer panels;
- private DirectPanel playing;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
@@ -176,10 +176,11 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, APIAccess api, RulesetStore rulesets)
+ private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, PreviewTrackManager previewTrackManager)
{
this.api = api;
this.rulesets = rulesets;
+ this.previewTrackManager = previewTrackManager;
resultCountsContainer.Colour = colours.Yellow;
}
@@ -206,12 +207,6 @@ namespace osu.Game.Overlays
panels.FadeOut(200);
panels.Expire();
panels = null;
-
- if (playing != null)
- {
- playing.PreviewPlaying.Value = false;
- playing = null;
- }
}
if (BeatmapSets == null) return;
@@ -242,17 +237,6 @@ namespace osu.Game.Overlays
{
if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels);
-
- foreach (DirectPanel panel in p.Children)
- panel.PreviewPlaying.ValueChanged += newValue =>
- {
- if (newValue)
- {
- if (playing != null && playing != panel)
- playing.PreviewPlaying.Value = false;
- playing = panel;
- }
- };
});
}
@@ -261,6 +245,7 @@ namespace osu.Game.Overlays
private readonly Bindable currentQuery = new Bindable();
private ScheduledDelegate queryChangedDebounce;
+ private PreviewTrackManager previewTrackManager;
private void updateSearch()
{
@@ -277,6 +262,8 @@ namespace osu.Game.Overlays
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
+ previewTrackManager.StopAnyPlaying(this);
+
getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty,
((FilterControl)Filter).Ruleset.Value,
Filter.DisplayStyleControl.Dropdown.Current.Value,
@@ -300,14 +287,6 @@ namespace osu.Game.Overlays
api.Queue(getSetsRequest);
}
- protected override void PopOut()
- {
- base.PopOut();
-
- if (playing != null)
- playing.PreviewPlaying.Value = false;
- }
-
private int distinctCount(List list) => list.Distinct().ToArray().Length;
public class ResultCounts
diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs
index a78bc8da81..25a832941e 100644
--- a/osu.Game/Overlays/Notifications/SimpleNotification.cs
+++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Notifications
}
});
- Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 16)
+ Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 14)
{
Colour = OsuColour.Gray(128),
AutoSizeAxes = Axes.Y,
diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
index 3fec9d8697..0b06acd426 100644
--- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.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;
-using OpenTK;
+using System.Linq;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct;
using osu.Game.Users;
-using System.Linq;
+using OpenTK;
namespace osu.Game.Overlays.Profile.Sections.Beatmaps
{
@@ -18,10 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
private readonly BeatmapSetType type;
- private DirectPanel currentlyPlaying;
-
- public event Action BeganPlayingPreview;
-
public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, string header, string missing = "None... yet.")
: base(user, header, missing)
{
@@ -56,28 +51,10 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
var panel = new DirectGridPanel(s.ToBeatmapSet(Rulesets));
ItemsContainer.Add(panel);
-
- panel.PreviewPlaying.ValueChanged += isPlaying =>
- {
- StopPlayingPreview();
-
- if (isPlaying)
- {
- BeganPlayingPreview?.Invoke(this);
- currentlyPlaying = panel;
- }
- };
}
};
Api.Queue(req);
}
-
- public void StopPlayingPreview()
- {
- if (currentlyPlaying == null) return;
- currentlyPlaying.PreviewPlaying.Value = false;
- currentlyPlaying = null;
- }
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs
index 92abd20f93..367d096c16 100644
--- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.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.Linq;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Sections.Beatmaps;
@@ -22,15 +21,6 @@ namespace osu.Game.Overlays.Profile.Sections
new PaginatedBeatmapContainer(BeatmapSetType.Unranked, User, "Pending Beatmaps"),
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, "Graveyarded Beatmaps"),
};
-
- foreach (var paginatedBeatmapContainer in Children.OfType())
- {
- paginatedBeatmapContainer.BeganPlayingPreview += _ =>
- {
- foreach (var bc in Children.OfType())
- bc.StopPlayingPreview();
- };
- }
}
}
}
diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs
index a4dd0c9ec3..745f2f3def 100644
--- a/osu.Game/Overlays/UserProfileOverlay.cs
+++ b/osu.Game/Overlays/UserProfileOverlay.cs
@@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
-using OpenTK;
-using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -18,6 +16,8 @@ using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Users;
+using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Overlays
{
diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs
index b62c639ee3..d0aa58e668 100644
--- a/osu.Game/Overlays/Volume/MuteButton.cs
+++ b/osu.Game/Overlays/Volume/MuteButton.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Volume
protected override bool OnHover(InputState state)
{
this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint);
- return true;
+ return false;
}
protected override void OnHoverLost(InputState state)
diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs
index 0e43945f8c..1d392e6ee8 100644
--- a/osu.Game/Overlays/Volume/VolumeMeter.cs
+++ b/osu.Game/Overlays/Volume/VolumeMeter.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
+using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
@@ -232,12 +233,13 @@ namespace osu.Game.Overlays.Volume
{
float amount = adjust_step * direction;
- var mouse = GetContainingInputManager().CurrentState.Mouse;
- if (mouse.HasPreciseScroll)
+ // handle the case where the OnPressed action was actually a mouse wheel.
+ // this allows for precise wheel handling.
+ var state = GetContainingInputManager().CurrentState;
+ if (state.Mouse?.ScrollDelta.Y != 0)
{
- float scrollDelta = mouse.ScrollDelta.Y;
- if (scrollDelta != 0)
- amount *= Math.Abs(scrollDelta / 10);
+ OnScroll(state);
+ return;
}
Volume += amount;
@@ -260,6 +262,34 @@ namespace osu.Game.Overlays.Volume
return false;
}
+ // because volume precision is set to 0.01, this local is required to keep track of more precise adjustments and only apply when possible.
+ private double scrollAmount;
+
+ protected override bool OnScroll(InputState state)
+ {
+ scrollAmount += adjust_step * state.Mouse.ScrollDelta.Y * (state.Mouse.HasPreciseScroll ? 0.1f : 1);
+
+ if (Math.Abs(scrollAmount) < Bindable.Precision)
+ return true;
+
+ Volume += scrollAmount;
+ scrollAmount = 0;
+ return true;
+ }
+
public bool OnReleased(GlobalAction action) => false;
+
+ private const float transition_length = 500;
+
+ protected override bool OnHover(InputState state)
+ {
+ this.ScaleTo(1.04f, transition_length, Easing.OutExpo);
+ return false;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ this.ScaleTo(1f, transition_length, Easing.OutExpo);
+ }
}
}
diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs
index f922c507f7..1c9c615bbb 100644
--- a/osu.Game/Overlays/VolumeOverlay.cs
+++ b/osu.Game/Overlays/VolumeOverlay.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
using osu.Framework.Threading;
using osu.Game.Graphics;
using osu.Game.Input.Bindings;
@@ -86,16 +87,10 @@ namespace osu.Game.Overlays
{
base.LoadComplete();
- volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged();
- volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged();
- volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged();
- muteButton.Current.ValueChanged += _ => settingChanged();
- }
-
- private void settingChanged()
- {
- Show();
- schedulePopOut();
+ volumeMeterMaster.Bindable.ValueChanged += _ => Show();
+ volumeMeterEffect.Bindable.ValueChanged += _ => Show();
+ volumeMeterMusic.Bindable.ValueChanged += _ => Show();
+ muteButton.Current.ValueChanged += _ => Show();
}
public bool Adjust(GlobalAction action)
@@ -127,6 +122,14 @@ namespace osu.Game.Overlays
private ScheduledDelegate popOutDelegate;
+ public override void Show()
+ {
+ if (State == Visibility.Visible)
+ schedulePopOut();
+
+ base.Show();
+ }
+
protected override void PopIn()
{
ClearTransforms();
@@ -140,10 +143,33 @@ namespace osu.Game.Overlays
this.FadeOut(100);
}
+ protected override bool OnMouseMove(InputState state)
+ {
+ // keep the scheduled event correctly timed as long as we have movement.
+ schedulePopOut();
+ return base.OnMouseMove(state);
+ }
+
+ protected override bool OnHover(InputState state)
+ {
+ schedulePopOut();
+ return true;
+ }
+
+ protected override void OnHoverLost(InputState state)
+ {
+ schedulePopOut();
+ base.OnHoverLost(state);
+ }
+
private void schedulePopOut()
{
popOutDelegate?.Cancel();
- this.Delay(1000).Schedule(Hide, out popOutDelegate);
+ this.Delay(1000).Schedule(() =>
+ {
+ if (!IsHovered)
+ Hide();
+ }, out popOutDelegate);
}
}
}
diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
index 0f5490a182..f13d96b35e 100644
--- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
+++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Replays
return true;
}
- public override List GetPendingStates() => new List