1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:07:23 +08:00

Merge branch 'master' into more-diffcalc-attributes

This commit is contained in:
Dean Herbert 2018-06-25 19:53:22 +09:00 committed by GitHub
commit 22138d39ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 3338 additions and 997 deletions

8
.github/pull_request_template.md vendored Normal file
View File

@ -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.

View File

@ -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"
}
]
}

View File

@ -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"
]
}
]
}

View File

@ -73,7 +73,7 @@ namespace osu.Desktop
}
public StableStorage()
: base(string.Empty)
: base(string.Empty, null)
{
}
}

View File

@ -30,6 +30,7 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup>
<ItemGroup Label="Resources">

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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)
{
}
}
}

View File

@ -1,19 +1,146 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// 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.
/// </summary>
private const double strain_step = 750;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
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<CatchDifficultyHitObject>();
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<CatchHitObject>().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<CatchDifficultyHitObject> 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<CatchDifficultyHitObject> 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>();
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;
}
}
}

View File

@ -0,0 +1,130 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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;
/// <summary>
/// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy
/// </summary>
internal double Strain = 1;
/// <summary>
/// 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
/// </summary>
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);
}
}
}

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboIndex { get; set; }
/// <summary>
/// The distance for a fruit to to next hyper if it's not a hyper.
/// </summary>
public float DistanceToHyperDash { get; set; }
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>

View File

@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Catch.Replays
}
}
public override List<InputState> GetPendingStates()
public override List<IInput> GetPendingInputs()
{
if (!Position.HasValue) return new List<InputState>();
if (!Position.HasValue) return new List<IInput>();
var actions = new List<CatchAction>();
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Replays
else if (Position.Value < CurrentFrame.Position)
actions.Add(CatchAction.MoveLeft);
return new List<InputState>
return new List<IInput>
{
new CatchReplayState
{

View File

@ -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<CatchAction>)?.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<CatchAction>
{
/// <summary>
/// Width of the area that can be used to attempt catches during gameplay.
/// </summary>
internal float CatchWidth => CATCHER_SIZE * Math.Abs(Scale.X);
private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget;
@ -232,15 +238,15 @@ namespace osu.Game.Rulesets.Catch.UI
/// <returns>Whether the catch is possible.</returns>
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)
{

View File

@ -0,0 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Drawable> content;
protected override Container<Drawable> 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;
}
}
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A container which provides a <see cref="IScrollingInfo"/> to children.
/// </summary>
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<IScrollingInfo>(new ScrollingInfo { Direction = { Value = direction }});
return dependencies;
}
private class ScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> RequiredTypes => new[]
{
typeof(Column),
typeof(ColumnBackground),
typeof(ColumnKeyArea),
typeof(ColumnHitObjectArea)
};
private readonly List<Column> columns = new List<Column>();
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
};
}
}
}

View File

@ -1,106 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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,
}
}
}
}
}
}
});
}
}
}

View File

@ -1,185 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<StageDefinition>
{
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 4 },
};
createPlayfield(stages);
});
AddStep("2 + 4 + 2 columns", () =>
{
var stages = new List<StageDefinition>
{
new StageDefinition { Columns = 2 },
new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 2 },
};
createPlayfield(stages);
});
AddStep("1 + 8 + 1 columns", () =>
{
var stages = new List<StageDefinition>
{
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<StageDefinition>
{
new StageDefinition { Columns = cols },
};
return createPlayfield(stages, inverted);
}
private ManiaPlayfield createPlayfield(List<StageDefinition> 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<StageDefinition>
{
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));
}
}
}

View File

@ -0,0 +1,168 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Type> 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<Drawable> 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<DrawableHitObject>())
{
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;
}
}
}
}
}
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ManiaStage> stages = new List<ManiaStage>();
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
};
}
}
}

View File

@ -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);

View File

@ -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
}
}

View File

@ -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)
{

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ManiaScrollingDirection>
{
LabelText = "Scrolling direction",
Bindable = config.GetBindable<ManiaScrollingDirection>(ManiaSetting.ScrollDirection)
}
};
}
}
}

View File

@ -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;
}

View File

@ -1,8 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
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)

View File

@ -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

View File

@ -1,12 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
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
{

View File

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Mania.Replays
protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<ManiaAction> { PressedActions = CurrentFrame.Actions } };
}
}

View File

@ -1,50 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ManiaAction>, IHasAccentColour
public class Column : ManiaScrollingPlayfield, IKeyBindingHandler<ManiaAction>, 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<Drawable> Content => content;
private readonly Container<Drawable> content;
protected override Container<Drawable> 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;
}
/// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary>
private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
{
public Func<ManiaAction, bool> Pressed;
public Func<ManiaAction, bool> 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)

View File

@ -0,0 +1,106 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ManiaAction>, IHasAccentColour
{
public ManiaAction Action;
private Box background;
private Box backgroundOverlay;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[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;
}
}
}

View File

@ -0,0 +1,99 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Drawable> content;
protected override Container<Drawable> Content => content;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
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),
};
}
}
}

View File

@ -0,0 +1,122 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ManiaAction>, IHasAccentColour
{
private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3;
public ManiaAction Action;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
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;
}
}
}

View File

@ -23,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.UI
{
bool isTick = judgedObject is DrawableHoldNoteTick;
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.X;

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// The direction <see cref="HitObject"/>s should scroll in.
/// </summary>
IBindable<ScrollingDirection> Direction { get; }
}
}

View File

@ -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
{
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public List<Column> Columns => stages.SelectMany(x => x.Columns).ToList();
private readonly List<ManiaStage> stages = new List<ManiaStage>();
public ManiaPlayfield(List<StageDefinition> stageDefinitions)
: base(ScrollingDirection.Up)
public ManiaPlayfield(ScrollingDirection direction, List<StageDefinition> 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;

View File

@ -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<BarLine> BarLines;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
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<IScrollingInfo>(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<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
public ManiaScrollingPlayfield(ScrollingDirection direction)
: base(direction)
{
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction => Direction.Value = direction, true);
}
}
}

View File

@ -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
/// <summary>
/// A collection of <see cref="Column"/>s.
/// </summary>
internal class ManiaStage : ScrollingPlayfield
internal class ManiaStage : ManiaScrollingPlayfield
{
public const float HIT_TARGET_POSITION = 50;
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
public IReadOnlyList<Column> Columns => columnFlow.Children;
private readonly FillFlowContainer<Column> columnFlow;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
protected override Container<Drawable> Content => barLineContainer;
private readonly Container<Drawable> barLineContainer;
public Container<DrawableManiaJudgement> Judgements => judgements;
private readonly JudgementContainer<DrawableManiaJudgement> 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<DrawableManiaJudgement>
@ -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;
}
}
}

View File

@ -30,13 +30,16 @@ namespace osu.Game.Rulesets.Osu.Replays
}
}
public override List<InputState> GetPendingStates()
public override List<IInput> GetPendingInputs()
{
return new List<InputState>
return new List<IInput>
{
new MousePositionAbsoluteInput
{
Position = GamefieldToScreenSpace(Position ?? Vector2.Zero)
},
new ReplayState<OsuAction>
{
Mouse = new ReplayMouseState(GamefieldToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = CurrentFrame.Actions
}
};

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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;
}
/// <summary>
/// Computes the numeric result value for the combo portion of the score.
/// </summary>
/// <param name="result">The result to compute the value for.</param>
/// <returns>The numeric result value.</returns>
protected override int NumericResultFor(HitResult result) => 0;
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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<Swell>
{
/// <summary>
/// A judgement is only displayed when the user has complete the swell (either a hit or miss).
/// </summary>
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;
/// <summary>
/// The amount of times the user has hit this swell.
/// </summary>
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()

View File

@ -17,6 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Replays
protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
public override List<InputState> GetPendingStates() => new List<InputState> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
public override List<IInput> GetPendingInputs() => new List<IInput> { new ReplayState<TaikoAction> { PressedActions = CurrentFrame.Actions } };
}
}

View File

@ -27,14 +27,12 @@ namespace osu.Game.Rulesets.Taiko
public override IEnumerable<KeyBinding> 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<Mod> ConvertLegacyMods(LegacyMods mods)

View File

@ -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++;
}
}

View File

@ -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<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
public Room SelectedRoom => Inspector.Room;

View File

@ -0,0 +1,142 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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",
},
});
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}
}
}

View File

@ -0,0 +1,127 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<IPreviewTrackOwner>(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<IPreviewTrackOwner>(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 };
}
}
}
}

View File

@ -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)

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Audio
{
/// <summary>
/// Interface for objects that can own <see cref="IPreviewTrack"/>s.
/// </summary>
/// <remarks>
/// <see cref="IPreviewTrackOwner"/>s can cancel the currently playing <see cref="PreviewTrack"/> through the
/// global <see cref="PreviewTrackManager"/> if they're the owner of the playing <see cref="PreviewTrack"/>.
/// </remarks>
public interface IPreviewTrackOwner
{
}
}

View File

@ -0,0 +1,103 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// Invoked when this <see cref="PreviewTrack"/> has stopped playing.
/// </summary>
public event Action Stopped;
/// <summary>
/// Invoked when this <see cref="PreviewTrack"/> has started playing.
/// </summary>
public event Action Started;
private Track track;
private bool hasStarted;
[BackgroundDependencyLoader]
private void load()
{
track = GetTrack();
}
/// <summary>
/// Length of the track.
/// </summary>
public double Length => track?.Length ?? 0;
/// <summary>
/// The current track time.
/// </summary>
public double CurrentTime => track?.CurrentTime ?? 0;
/// <summary>
/// Whether the track is loaded.
/// </summary>
public bool TrackLoaded => track?.IsLoaded ?? false;
/// <summary>
/// Whether the track is playing.
/// </summary>
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;
/// <summary>
/// Starts playing this <see cref="PreviewTrack"/>.
/// </summary>
public void Start() => startDelegate = Schedule(() =>
{
if (track == null)
return;
if (hasStarted)
return;
hasStarted = true;
track.Restart();
Started?.Invoke();
});
/// <summary>
/// Stops playing this <see cref="PreviewTrack"/>.
/// </summary>
public void Stop()
{
startDelegate?.Cancel();
if (track == null)
return;
if (!hasStarted)
return;
hasStarted = false;
track.Stop();
Stopped?.Invoke();
}
/// <summary>
/// Retrieves the audio track.
/// </summary>
protected abstract Track GetTrack();
}
}

View File

@ -0,0 +1,107 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A central store for the retrieval of <see cref="PreviewTrack"/>s.
/// </summary>
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);
}
/// <summary>
/// Retrieves a <see cref="PreviewTrack"/> for a <see cref="BeatmapSetInfo"/>.
/// </summary>
/// <param name="beatmapSetInfo">The <see cref="BeatmapSetInfo"/> to retrieve the preview track for.</param>
/// <returns>The playable <see cref="PreviewTrack"/>.</returns>
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;
}
/// <summary>
/// Stops any currently playing <see cref="PreviewTrack"/>.
/// </summary>
/// <remarks>
/// Only the immediate owner (an object that implements <see cref="IPreviewTrackOwner"/>) of the playing <see cref="PreviewTrack"/>
/// can globally stop the currently playing <see cref="PreviewTrack"/>. The object holding a reference to the <see cref="PreviewTrack"/>
/// can always stop the <see cref="PreviewTrack"/> themselves through <see cref="PreviewTrack.Stop()"/>.
/// </remarks>
/// <param name="source">The <see cref="IPreviewTrackOwner"/> which may be the owner of the <see cref="PreviewTrack"/>.</param>
public void StopAnyPlaying(IPreviewTrackOwner source)
{
if (current == null || current.Owner != source)
return;
current.Stop();
current = null;
}
/// <summary>
/// Creates the <see cref="TrackManagerPreviewTrack"/>.
/// </summary>
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");
}
}
}

View File

@ -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

View File

@ -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<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(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<IPreviewTrackOwner>(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);
}
}
}

View File

@ -16,6 +16,9 @@ namespace osu.Game.Graphics.Containers
{
public const float DEFAULT_PARALLAX_AMOUNT = 0.02f;
/// <summary>
/// The amount of parallax movement. Negative values will reverse the direction of parallax relative to user input.
/// </summary>
public float ParallaxAmount = DEFAULT_PARALLAX_AMOUNT;
private Bindable<bool> 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;

View File

@ -32,16 +32,14 @@ namespace osu.Game.Input.Handlers
public override int Priority => 0;
public class ReplayState<T> : InputState
public class ReplayState<T> : IInput
where T : struct
{
public List<T> PressedActions;
public override InputState Clone()
public void Apply(InputState state, IInputStateChangeHandler handler)
{
var clone = (ReplayState<T>)base.Clone();
clone.PressedActions = new List<T>(PressedActions);
return clone;
handler.HandleCustomInput(state, this);
}
}
}

View File

@ -0,0 +1,376 @@
// <auto-generated />
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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("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<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("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
}
}
}

View File

@ -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.
}
}
}

View File

@ -1,11 +1,9 @@
// <auto-generated />
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 =>
{

View File

@ -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);
};
}

View File

@ -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()

View File

@ -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<bool> 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);

View File

@ -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;

View File

@ -1,9 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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);
}

View File

@ -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<bool> 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;
}

View File

@ -1,25 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<bool> Playing = new Bindable<bool>();
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();
}
}
}
}

View File

@ -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<DirectPanel> 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<string> currentQuery = new Bindable<string>();
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<string> list) => list.Distinct().ToArray().Length;
public class ResultCounts

View File

@ -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,

View File

@ -1,14 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<PaginatedBeatmapContainer> BeganPlayingPreview;
public PaginatedBeatmapContainer(BeatmapSetType type, Bindable<User> 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;
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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>())
{
paginatedBeatmapContainer.BeganPlayingPreview += _ =>
{
foreach (var bc in Children.OfType<PaginatedBeatmapContainer>())
bc.StopPlayingPreview();
};
}
}
}
}

View File

@ -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
{

View File

@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Volume
protected override bool OnHover(InputState state)
{
this.TransformTo<MuteButton, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint);
return true;
return false;
}
protected override void OnHoverLost(InputState state)

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Replays
return true;
}
public override List<InputState> GetPendingStates() => new List<InputState>();
public override List<IInput> GetPendingInputs() => new List<IInput>();
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
public bool AtFirstFrame => currentFrameIndex == 0;
@ -119,7 +119,8 @@ namespace osu.Game.Rulesets.Replays
{
public ReplayKeyboardState(List<Key> keys)
{
Keys = keys;
foreach (var key in keys)
Keys.Add(key);
}
}
}

View File

@ -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,26 +30,43 @@ namespace osu.Game.Rulesets.UI
}
}
protected override InputState CreateInitialState()
{
var state = base.CreateInitialState();
return new RulesetInputManagerInputState<T>
{
Mouse = state.Mouse,
Keyboard = state.Keyboard,
Joystick = state.Joystick,
LastReplayState = null
};
}
protected readonly KeyBindingContainer<T> KeyBindingContainer;
protected override Container<Drawable> Content => KeyBindingContainer;
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
{
InternalChild = KeyBindingContainer = new RulesetKeyBindingContainer(ruleset, variant, unique);
InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique);
}
#region Action mapping (for replays)
private List<T> lastPressedActions = new List<T>();
protected override void HandleNewState(InputState state)
public override void HandleCustomInput(InputState state, IInput input)
{
base.HandleNewState(state);
if (!(input is ReplayState<T> replayState))
{
base.HandleCustomInput(state, input);
return;
}
var replayState = state as ReplayInputHandler.ReplayState<T>;
if (replayState == null) return;
if (state is RulesetInputManagerInputState<T> 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;
@ -249,6 +267,9 @@ namespace osu.Game.Rulesets.UI
}
#endregion
protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new RulesetKeyBindingContainer(ruleset, variant, unique);
}
/// <summary>
@ -267,4 +288,10 @@ namespace osu.Game.Rulesets.UI
{
void Attach(KeyCounterCollection keyCounter);
}
public class RulesetInputManagerInputState<T> : InputState
where T : struct
{
public ReplayState<T> LastReplayState;
}
}

View File

@ -29,17 +29,16 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary>
protected readonly SortedList<MultiplierControlPoint> ControlPoints = new SortedList<MultiplierControlPoint>();
private readonly ScrollingDirection direction;
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
private Cached initialStateCache = new Cached();
public ScrollingHitObjectContainer(ScrollingDirection direction)
public ScrollingHitObjectContainer()
{
this.direction = direction;
RelativeSizeAxes = Axes.Both;
TimeRange.ValueChanged += v => initialStateCache.Invalidate();
TimeRange.ValueChanged += _ => initialStateCache.Invalidate();
Direction.ValueChanged += _ => initialStateCache.Invalidate();
}
private ISpeedChangeVisualiser speedChangeVisualiser;
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
if (!initialStateCache.IsValid)
{
speedChangeVisualiser.ComputeInitialStates(Objects, direction, TimeRange, DrawSize);
speedChangeVisualiser.ComputeInitialStates(Objects, Direction, TimeRange, DrawSize);
initialStateCache.Validate();
}
}
@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
base.UpdateAfterChildrenLife();
// We need to calculate this as soon as possible after lifetimes so that hitobjects get the final say in their positions
speedChangeVisualiser.UpdatePositions(AliveObjects, direction, Time.Current, TimeRange, DrawSize);
speedChangeVisualiser.UpdatePositions(AliveObjects, Direction, Time.Current, TimeRange, DrawSize);
}
}
}

View File

@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// </summary>
public new ScrollingHitObjectContainer HitObjects => (ScrollingHitObjectContainer)base.HitObjects;
private readonly ScrollingDirection direction;
protected readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
/// <summary>
/// Creates a new <see cref="ScrollingPlayfield"/>.
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null)
: base(customWidth, customHeight)
{
this.direction = direction;
Direction.Value = direction;
}
[BackgroundDependencyLoader]
@ -99,6 +99,11 @@ namespace osu.Game.Rulesets.UI.Scrolling
return false;
}
protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(direction);
protected sealed override HitObjectContainer CreateHitObjectContainer()
{
var container = new ScrollingHitObjectContainer();
container.Direction.BindTo(Direction);
return container;
}
}
}

View File

@ -93,6 +93,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers
/// <returns>A positive value indicating the position at <paramref name="time"/>.</returns>
private double positionAt(double time, double timeRange)
{
if (controlPoints.Count == 0)
return time / timeRange;
double length = 0;
// We need to consider all timing points until the specified time and not just the currently-active one,

View File

@ -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;
}

View File

@ -164,7 +164,7 @@ namespace osu.Game.Screens.Multi.Screens.Lounge
// open the room if its selected and is clicked again
if (room.State == SelectionState.Selected)
Push(new Match());
Push(new Match.Match(room.Room));
}
private class RoomsFilterContainer : FillFlowContainer<DrawableRoom>, IHasFilterableChildren

View File

@ -1,37 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens
{
public class Match : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(MatchSongSelect),
typeof(Player),
};
protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
Background.FadeColour(Color4.DarkGray, 500);
}
protected override bool OnExiting(Screen next)
{
Background.FadeColour(Color4.White, 500);
return base.OnExiting(next);
}
}
}

View File

@ -0,0 +1,184 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
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;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList;
using OpenTK.Graphics;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Header : Container
{
public const float HEIGHT = 200;
private readonly Box tabStrip;
private readonly UpdateableBeatmapSetCover cover;
public readonly PageTabControl<MatchHeaderPage> Tabs;
public BeatmapSetInfo BeatmapSet
{
set => cover.BeatmapSet = value;
}
public Action OnRequestSelectBeatmap;
public Header()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
BeatmapSelectButton beatmapButton;
Children = new Drawable[]
{
cover = new UpdateableBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
Masking = true,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black.Opacity(0.5f)),
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 200,
Padding = new MarginPadding { Vertical = 5 },
Child = beatmapButton = new BeatmapSelectButton
{
RelativeSizeAxes = Axes.Both,
},
},
Tabs = new PageTabControl<MatchHeaderPage>
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
},
},
},
};
beatmapButton.Action = () => OnRequestSelectBeatmap?.Invoke();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
}
private class BeatmapSelectButton : OsuClickableContainer
{
private const float corner_radius = 5;
private const float bg_opacity = 0.5f;
private const float transition_duration = 100;
private readonly Box bg;
private readonly Container border;
public BeatmapSelectButton()
{
Masking = true;
CornerRadius = corner_radius;
Children = new Drawable[]
{
bg = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = bg_opacity,
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = @"Exo2.0-Bold",
Text = "Select Beatmap",
},
border = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
BorderThickness = 4,
Alpha = 0,
Child = new Box // needs a child to show the border
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
border.BorderColour = colours.Yellow;
}
protected override bool OnHover(InputState state)
{
border.FadeIn(transition_duration);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
border.FadeOut(transition_duration);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
bg.FadeTo(0.75f, 1000, Easing.Out);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
bg.FadeTo(bg_opacity, transition_duration);
return base.OnMouseUp(state, args);
}
}
}
public enum MatchHeaderPage
{
Settings,
Room,
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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;
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.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Info : Container
{
public const float HEIGHT = 128;
private readonly OsuSpriteText name, availabilityStatus;
private readonly BeatmapTypeInfo beatmapTypeInfo;
private readonly ReadyButton readyButton;
private OsuColour colours;
public Bindable<bool> Ready => readyButton.Ready;
public string Name
{
set { name.Text = value; }
}
private RoomAvailability availability;
public RoomAvailability Availability
{
set
{
if (value == availability) return;
availability = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
private RoomStatus status;
public RoomStatus Status
{
set
{
if (value == status) return;
status = value;
if (IsLoaded)
updateAvailabilityStatus();
}
}
public BeatmapInfo Beatmap
{
set { beatmapTypeInfo.Beatmap = value; }
}
public GameType Type
{
set { beatmapTypeInfo.Type = value; }
}
public Info()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"28242d"),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Padding = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
name = new OsuSpriteText
{
TextSize = 30,
},
availabilityStatus = new OsuSpriteText
{
TextSize = 14,
},
},
},
beatmapTypeInfo = new BeatmapTypeInfo
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
},
},
},
readyButton = new ReadyButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Size = new Vector2(200, 1),
Padding = new MarginPadding { Vertical = 10 },
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
this.colours = colours;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAvailabilityStatus();
}
private void updateAvailabilityStatus()
{
if (status != null)
{
availabilityStatus.FadeColour(status.GetAppropriateColour(colours), 100);
availabilityStatus.Text = $"{availability.GetDescription()}, {status.Message}";
}
}
private class ReadyButton : TriangleButton
{
public readonly Bindable<bool> Ready = new Bindable<bool>();
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Light",
TextSize = 30,
};
[BackgroundDependencyLoader]
private void load()
{
BackgroundColour = OsuColour.FromHex(@"1187aa");
Triangles.ColourLight = OsuColour.FromHex(@"277b9c");
Triangles.ColourDark = OsuColour.FromHex(@"1f6682");
Triangles.TriangleScale = 1.5f;
Container active;
Add(active = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
Blending = BlendingMode.Additive,
},
});
Action = () => Ready.Value = !Ready.Value;
Ready.BindValueChanged(value =>
{
if (value)
{
Text = "Not Ready";
active.FadeIn(200);
}
else
{
Text = "Ready";
active.FadeOut(200);
}
}, true);
}
}
}
}

View File

@ -0,0 +1,81 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Select;
using osu.Game.Users;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Match : MultiplayerScreen
{
private readonly Room room;
private readonly Participants participants;
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
private readonly Bindable<RoomAvailability> availabilityBind = new Bindable<RoomAvailability>();
private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
protected override Container<Drawable> TransitionContent => participants;
public override string Type => "room";
public override string Title => room.Name.Value;
public Match(Room room)
{
this.room = room;
Header header;
Info info;
Children = new Drawable[]
{
header = new Header(),
info = new Info
{
Margin = new MarginPadding { Top = Header.HEIGHT },
},
participants = new Participants
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT },
},
};
header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect());
beatmapBind.BindTo(room.Beatmap);
beatmapBind.BindValueChanged(b =>
{
header.BeatmapSet = b?.BeatmapSet;
info.Beatmap = b;
}, true);
nameBind.BindTo(room.Name);
nameBind.BindValueChanged(n => info.Name = n, true);
statusBind.BindTo(room.Status);
statusBind.BindValueChanged(s => info.Status = s, true);
availabilityBind.BindTo(room.Availability);
availabilityBind.BindValueChanged(a => info.Availability = a, true);
typeBind.BindTo(room.Type);
typeBind.BindValueChanged(t => info.Type = t, true);
maxParticipantsBind.BindTo(room.MaxParticipants);
maxParticipantsBind.BindValueChanged(m => { participants.Max = m; }, true);
participantsBind.BindTo(room.Participants);
participantsBind.BindValueChanged(p => participants.Users = p, true);
}
}
}

View File

@ -0,0 +1,74 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
using OpenTK;
namespace osu.Game.Screens.Multi.Screens.Match
{
public class Participants : Container
{
private readonly ParticipantCount count;
private readonly FillFlowContainer<UserPanel> usersFlow;
public IEnumerable<User> Users
{
set {
usersFlow.Children = value.Select(u => new UserPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
OnLoadComplete = d => d.FadeInFromZero(60),
}).ToList();
count.Count = value.Count();
}
}
public int? Max
{
set => count.Max = value;
}
public Participants()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
count = new ParticipantCount
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
usersFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding { Top = 40 },
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
},
},
},
},
};
}
}
}

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
namespace osu.Game.Screens.Multi.Screens
{
public class MatchCreate : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(Match)
};
public MatchCreate()
{
ValidForResume = false;
}
}
}

View File

@ -10,9 +10,6 @@ namespace osu.Game.Screens.Multi.Screens
{
public abstract class MultiplayerScreen : OsuScreen
{
private const Easing in_easing = Easing.OutQuint;
private const Easing out_easing = Easing.InSine;
protected virtual Container<Drawable> TransitionContent => Content;
/// <summary>
@ -24,16 +21,15 @@ namespace osu.Game.Screens.Multi.Screens
{
base.OnEntering(last);
TransitionContent.MoveToX(200);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, in_easing);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
Content.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
}
protected override bool OnExiting(Screen next)
{
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing);
TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, out_easing);
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
return base.OnExiting(next);
}
@ -42,16 +38,16 @@ namespace osu.Game.Screens.Multi.Screens
{
base.OnResuming(last);
Content.FadeIn(WaveContainer.APPEAR_DURATION, in_easing);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing);
Content.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
}
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing);
TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, out_easing);
Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint);
}
}
}

View File

@ -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;
}

View File

@ -7,12 +7,7 @@ namespace osu.Game.Screens.Select
{
protected override bool OnStart()
{
Schedule(() =>
{
// needs to be scheduled else we enter an infinite feedback loop.
if (IsCurrentScreen) Exit();
});
if (IsCurrentScreen) Exit();
return true;
}
}

View File

@ -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().

View File

@ -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)
{
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual
/// </summary>
protected void ReturnUserInput()
{
AddStep("Return user input", () => InputManager.UseParentState = true);
AddStep("Return user input", () => InputManager.UseParentInput = true);
}
}
}

View File

@ -18,7 +18,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="ppy.osu.Framework" Version="2018.619.0" />
<PackageReference Include="ppy.osu.Framework" Version="2018.622.0" />
<PackageReference Include="SharpCompress" Version="0.18.1" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />