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 b62e9997d4..30f4979255 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/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.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/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.Tests/Visual/TestCaseLounge.cs b/osu.Game.Tests/Visual/TestCaseLounge.cs
index c5e0d1c4bf..59cf59bb52 100644
--- a/osu.Game.Tests/Visual/TestCaseLounge.cs
+++ b/osu.Game.Tests/Visual/TestCaseLounge.cs
@@ -167,6 +167,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"set rooms", () => lounge.Rooms = rooms);
selectAssert(1);
AddStep(@"open room 1", () => clickRoom(1));
+ AddUntilStep(() => !lounge.IsCurrentScreen, "wait until room current");
AddStep(@"make lounge current", lounge.MakeCurrent);
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
filterAssert(string.Empty, LoungeTab.Public, 2);
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/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 4443e7bc3e..fdd7bc825d 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();
@@ -299,8 +301,6 @@ namespace osu.Game
Depth = -6,
}, overlayContent.Add);
- forwardLoggedErrorsToNotifications();
-
dependencies.Cache(settings);
dependencies.Cache(onscreenDisplay);
dependencies.Cache(social);
@@ -388,31 +388,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 b8f4dcb61f..cae5352739 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;
@@ -192,6 +193,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();
+ public override List GetPendingInputs() => new List();
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
public bool AtFirstFrame => currentFrameIndex == 0;
@@ -119,7 +119,8 @@ namespace osu.Game.Rulesets.Replays
{
public ReplayKeyboardState(List keys)
{
- Keys = keys;
+ foreach (var key in keys)
+ Keys.Add(key);
}
}
}
diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs
index 58a66a5224..f8c4fff5b8 100644
--- a/osu.Game/Rulesets/UI/RulesetInputManager.cs
+++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs
@@ -15,6 +15,7 @@ using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play;
using OpenTK.Input;
+using static osu.Game.Input.Handlers.ReplayInputHandler;
namespace osu.Game.Rulesets.UI
{
@@ -29,6 +30,18 @@ namespace osu.Game.Rulesets.UI
}
}
+ protected override InputState CreateInitialState()
+ {
+ var state = base.CreateInitialState();
+ return new RulesetInputManagerInputState
+ {
+ Mouse = state.Mouse,
+ Keyboard = state.Keyboard,
+ Joystick = state.Joystick,
+ LastReplayState = null
+ };
+ }
+
protected readonly KeyBindingContainer KeyBindingContainer;
protected override Container Content => KeyBindingContainer;
@@ -42,13 +55,18 @@ namespace osu.Game.Rulesets.UI
private List lastPressedActions = new List();
- protected override void HandleNewState(InputState state)
+ public override void HandleCustomInput(InputState state, IInput input)
{
- base.HandleNewState(state);
+ if (!(input is ReplayState replayState))
+ {
+ base.HandleCustomInput(state, input);
+ return;
+ }
- var replayState = state as ReplayInputHandler.ReplayState;
-
- if (replayState == null) return;
+ if (state is RulesetInputManagerInputState inputState)
+ {
+ inputState.LastReplayState = replayState;
+ }
// Here we handle states specifically coming from a replay source.
// These have extra action information rather than keyboard keys or mouse buttons.
@@ -80,7 +98,7 @@ namespace osu.Game.Rulesets.UI
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
replayInputHandler = value;
- UseParentState = replayInputHandler == null;
+ UseParentInput = replayInputHandler == null;
if (replayInputHandler != null)
AddHandler(replayInputHandler);
@@ -123,7 +141,7 @@ namespace osu.Game.Rulesets.UI
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
- private bool isAttached => replayInputHandler != null && !UseParentState;
+ private bool isAttached => replayInputHandler != null && !UseParentInput;
private const int max_catch_up_updates_per_frame = 50;
@@ -267,4 +285,10 @@ namespace osu.Game.Rulesets.UI
{
void Attach(KeyCounterCollection keyCounter);
}
+
+ public class RulesetInputManagerInputState : InputState
+ where T : struct
+ {
+ public ReplayState LastReplayState;
+ }
}
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 2a2111c8d6..7eeabd3e5e 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -185,9 +185,9 @@ namespace osu.Game.Screens.Edit
protected override bool OnScroll(InputState state)
{
if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0)
- clock.SeekBackward(true);
+ clock.SeekBackward(!clock.IsRunning);
else
- clock.SeekForward(true);
+ clock.SeekForward(!clock.IsRunning);
return true;
}
diff --git a/osu.Game/Screens/Play/HUD/QuitButton.cs b/osu.Game/Screens/Play/HUD/QuitButton.cs
index d0aa0dad92..29382c25f3 100644
--- a/osu.Game/Screens/Play/HUD/QuitButton.cs
+++ b/osu.Game/Screens/Play/HUD/QuitButton.cs
@@ -2,6 +2,7 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
@@ -182,14 +183,14 @@ namespace osu.Game.Screens.Play.HUD
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
- if (!pendingAnimation && state.Mouse.Buttons.Count == 1)
+ if (!pendingAnimation && state.Mouse.Buttons.Count() == 1)
BeginConfirm();
return true;
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
- if (state.Mouse.Buttons.Count == 0)
+ if (!state.Mouse.Buttons.Any())
AbortConfirm();
return true;
}
diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs
index 738bb2d642..217af8eb77 100644
--- a/osu.Game/Tests/OsuTestBrowser.cs
+++ b/osu.Game/Tests/OsuTestBrowser.cs
@@ -3,6 +3,7 @@
using osu.Framework.Platform;
using osu.Framework.Testing;
+using osu.Game.Graphics;
using osu.Game.Screens.Backgrounds;
namespace osu.Game.Tests
@@ -13,7 +14,11 @@ namespace osu.Game.Tests
{
base.LoadComplete();
- LoadComponentAsync(new BackgroundScreenDefault { Depth = 10 }, AddInternal);
+ LoadComponentAsync(new BackgroundScreenDefault
+ {
+ Colour = OsuColour.Gray(0.5f),
+ Depth = 10
+ }, AddInternal);
// Have to construct this here, rather than in the constructor, because
// we depend on some dependencies to be loaded within OsuGameBase.load().
diff --git a/osu.Game/Tests/Platform/TestStorage.cs b/osu.Game/Tests/Platform/TestStorage.cs
index 883aae2184..5b31c7b4d0 100644
--- a/osu.Game/Tests/Platform/TestStorage.cs
+++ b/osu.Game/Tests/Platform/TestStorage.cs
@@ -7,7 +7,7 @@ namespace osu.Game.Tests.Platform
{
public class TestStorage : DesktopStorage
{
- public TestStorage(string baseName) : base(baseName)
+ public TestStorage(string baseName) : base(baseName, null)
{
}
diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs
index 132a655c15..01676ad56e 100644
--- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs
+++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual
///
protected void ReturnUserInput()
{
- AddStep("Return user input", () => InputManager.UseParentState = true);
+ AddStep("Return user input", () => InputManager.UseParentInput = true);
}
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f0bc330994..9cc538f70f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
-
+