1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 18:12:56 +08:00

Merge branch 'master' into beatmap-serialization

This commit is contained in:
smoogipoo 2017-12-21 13:39:39 +09:00
commit c99ea32574
128 changed files with 7392 additions and 2712 deletions

77
.vscode/tasks.json vendored
View File

@ -2,63 +2,70 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558 // See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "2.0.0", "version": "2.0.0",
"command": "msbuild",
"type": "shell",
"suppressTaskName": true,
"args": [
"/property:GenerateFullPaths=true",
"/property:DebugType=portable",
"/verbosity:minimal",
"/m" //parallel compiling support.
],
"tasks": [{ "tasks": [{
"taskName": "Build (Debug)", "label": "Build (Debug)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:GenerateFullPaths=true",
"/p:DebugType=portable",
"/m",
"/v:m"
],
"group": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": true
}, },
"problemMatcher": [ "problemMatcher": "$msCompile"
"$msCompile"
]
}, },
{ {
"taskName": "Build (Release)", "label": "Build (Release)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:Configuration=Release",
"/p:DebugType=portable",
"/p:GenerateFullPaths=true",
"/m",
"/v:m"
],
"group": "build", "group": "build",
"args": [ "problemMatcher": "$msCompile"
"/property:Configuration=Release"
],
"problemMatcher": [
"$msCompile"
]
}, },
{ {
"taskName": "Clean (Debug)", "label": "Clean (Debug)",
"type": "shell",
"command": "msbuild",
"args": [ "args": [
"/target:Clean" "/p:DebugType=portable",
"/p:GenerateFullPaths=true",
"/m",
"/t:Clean",
"/v:m"
], ],
"problemMatcher": [ "problemMatcher": "$msCompile"
"$msCompile"
]
}, },
{ {
"taskName": "Clean (Release)", "label": "Clean (Release)",
"type": "shell",
"command": "msbuild",
"args": [ "args": [
"/target:Clean", "/p:Configuration=Release",
"/property:Configuration=Release" "/p:GenerateFullPaths=true",
"/p:DebugType=portable",
"/m",
"/t:Clean",
"/v:m"
], ],
"problemMatcher": [ "problemMatcher": "$msCompile"
"$msCompile"
]
}, },
{ {
"taskName": "Clean All", "label": "Clean All",
"dependsOn": [ "dependsOn": [
"Clean (Debug)", "Clean (Debug)",
"Clean (Release)" "Clean (Release)"
], ],
"problemMatcher": [ "problemMatcher": "$msCompile"
"$msCompile"
]
} }
] ]
} }

View File

@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Catch
public override string Description => "osu!catch"; public override string Description => "osu!catch";
public override string ShortName => "fruits";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);

View File

@ -90,6 +90,9 @@
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -105,6 +105,8 @@ namespace osu.Game.Rulesets.Mania
public override string Description => "osu!mania"; public override string Description => "osu!mania";
public override string ShortName => "mania";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);

View File

@ -118,6 +118,9 @@
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Osu.Objects.Drawables namespace osu.Game.Rulesets.Osu.Objects.Drawables
{ {
@ -165,6 +166,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
} }
public Drawable ProxiedLayer => initialCircle.ApproachCircle; public Drawable ProxiedLayer => initialCircle.ApproachCircle;
public override Vector2 SelectionPoint => ToScreenSpace(body.Position);
public override Quad SelectionQuad => body.PathDrawQuad;
} }
internal interface ISliderProgress internal interface ISliderProgress

View File

@ -14,6 +14,7 @@ using osu.Game.Configuration;
using OpenTK; using OpenTK;
using OpenTK.Graphics.ES30; using OpenTK.Graphics.ES30;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{ {
@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
} }
} }
public Quad PathDrawQuad => container.ScreenSpaceDrawQuad;
private int textureWidth => (int)PathWidth * 2; private int textureWidth => (int)PathWidth * 2;
private readonly Slider slider; private readonly Slider slider;
@ -182,4 +185,4 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
SetRange(start, end); SetRange(start, end);
} }
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -9,6 +10,8 @@ namespace osu.Game.Rulesets.Osu
{ {
public class OsuInputManager : RulesetInputManager<OsuAction> public class OsuInputManager : RulesetInputManager<OsuAction>
{ {
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique) public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique)
{ {
} }

View File

@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -18,6 +17,8 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
@ -33,21 +34,35 @@ namespace osu.Game.Rulesets.Osu
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
}; };
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap)
{ {
new BeatmapStatistic IEnumerable<HitObject> hitObjects = beatmap.Beatmap.HitObjects;
IEnumerable<HitObject> circles = hitObjects.Where(c => !(c is IHasEndTime));
IEnumerable<HitObject> sliders = hitObjects.Where(s => s is IHasCurve);
IEnumerable<HitObject> spinners = hitObjects.Where(s => s is IHasEndTime && !(s is IHasCurve));
return new[]
{ {
Name = @"Circle count", new BeatmapStatistic
Content = beatmap.Beatmap.HitObjects.Count(h => h is HitCircle).ToString(), {
Icon = FontAwesome.fa_dot_circle_o Name = @"Circle Count",
}, Content = circles.Count().ToString(),
new BeatmapStatistic Icon = FontAwesome.fa_circle_o
{ },
Name = @"Slider count", new BeatmapStatistic
Content = beatmap.Beatmap.HitObjects.Count(h => h is Slider).ToString(), {
Icon = FontAwesome.fa_circle_o Name = @"Slider Count",
} Content = sliders.Count().ToString(),
}; Icon = FontAwesome.fa_circle
},
new BeatmapStatistic
{
Name = @"Spinner Count",
Content = spinners.Count().ToString(),
Icon = FontAwesome.fa_circle
}
};
}
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {
@ -124,6 +139,8 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!"; public override string Description => "osu!";
public override string ShortName => "osu";
public override SettingsSubsection CreateSettings() => new OsuSettings(); public override SettingsSubsection CreateSettings() => new OsuSettings();
public override int LegacyID => 0; public override int LegacyID => 0;

View File

@ -20,8 +20,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{ {
internal class CursorTrail : Drawable internal class CursorTrail : Drawable
{ {
public override bool HandleInput => true;
private int currentIndex; private int currentIndex;
private Shader shader; private Shader shader;

View File

@ -31,6 +31,9 @@ namespace osu.Game.Rulesets.Osu.UI
{ {
get get
{ {
if (Parent == null)
return Vector2.Zero;
var parentSize = Parent.DrawSize; var parentSize = Parent.DrawSize;
var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y); var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y);

View File

@ -123,7 +123,9 @@
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Taiko
public override string Description => "osu!taiko"; public override string Description => "osu!taiko";
public override string ShortName => "taiko";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);

View File

@ -111,6 +111,9 @@
<Private>True</Private> <Private>True</Private>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -0,0 +1,214 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using NUnit.Framework;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Tests.Resources;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class LegacyBeatmapDecoderTest
{
[Test]
public void TestDecodeBeatmapGeneral()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.DecodeBeatmap(stream);
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmap.Metadata;
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(164471, metadata.PreviewTime);
Assert.IsFalse(beatmapInfo.Countdown);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.IsTrue(beatmapInfo.RulesetID == 0);
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
}
}
[Test]
public void TestDecodeBeatmapEditor()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo;
int[] expectedBookmarks =
{
11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351,
95901, 106450, 116999, 119637, 130186, 140735, 151285,
161834, 164471, 175020, 185570, 196119, 206669, 209306
};
Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length);
for (int i = 0; i < expectedBookmarks.Length; i++)
Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]);
Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing);
Assert.AreEqual(4, beatmapInfo.BeatDivisor);
Assert.AreEqual(4, beatmapInfo.GridSize);
Assert.AreEqual(2, beatmapInfo.TimelineZoom);
}
}
[Test]
public void TestDecodeBeatmapMetadata()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.DecodeBeatmap(stream);
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmap.Metadata;
Assert.AreEqual("Renatus", metadata.Title);
Assert.AreEqual("Renatus", metadata.TitleUnicode);
Assert.AreEqual("Soleily", metadata.Artist);
Assert.AreEqual("Soleily", metadata.ArtistUnicode);
Assert.AreEqual("Gamu", metadata.AuthorString);
Assert.AreEqual("Insane", beatmapInfo.Version);
Assert.AreEqual(string.Empty, metadata.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
Assert.AreEqual(241526, metadata.OnlineBeatmapSetID);
}
}
[Test]
public void TestDecodeBeatmapDifficulty()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
Assert.AreEqual(9, difficulty.ApproachRate);
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
Assert.AreEqual(2, difficulty.SliderTickRate);
}
}
[Test]
public void TestDecodeBeatmapEvents()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.DecodeBeatmap(stream);
var metadata = beatmap.Metadata;
var breakPoint = beatmap.Breaks[0];
Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile);
Assert.AreEqual(122474, breakPoint.StartTime);
Assert.AreEqual(140135, breakPoint.EndTime);
Assert.IsTrue(breakPoint.HasEffect);
}
}
[Test]
public void TestDecodeBeatmapTimingPoints()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var beatmap = decoder.DecodeBeatmap(stream);
var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
var timingPoint = controlPoints.TimingPoints[0];
Assert.AreEqual(956, timingPoint.Time);
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature);
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
var difficultyPoint = controlPoints.DifficultyPoints[0];
Assert.AreEqual(116999, difficultyPoint.Time);
Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier);
Assert.AreEqual(34, controlPoints.SoundPoints.Count);
var soundPoint = controlPoints.SoundPoints[0];
Assert.AreEqual(956, soundPoint.Time);
Assert.AreEqual("soft", soundPoint.SampleBank);
Assert.AreEqual(60, soundPoint.SampleVolume);
Assert.AreEqual(8, controlPoints.EffectPoints.Count);
var effectPoint = controlPoints.EffectPoints[0];
Assert.AreEqual(53703, effectPoint.Time);
Assert.IsTrue(effectPoint.KiaiMode);
Assert.IsFalse(effectPoint.OmitFirstBarLine);
}
}
[Test]
public void TestDecodeBeatmapColors()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var comboColors = decoder.DecodeBeatmap(stream).ComboColors;
Color4[] expectedColors =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
};
Assert.AreEqual(expectedColors.Length, comboColors.Count);
for (int i = 0; i < expectedColors.Length; i++)
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
[Test]
public void TestDecodeBeatmapHitObjects()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
var hitObjects = decoder.DecodeBeatmap(stream).HitObjects;
var curveData = hitObjects[0] as IHasCurve;
var positionData = hitObjects[0] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, hitObjects[0].StartTime);
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
positionData = hitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, hitObjects[1].StartTime);
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
}
}
}
}

View File

@ -0,0 +1,90 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using System.Linq;
using NUnit.Framework;
using OpenTK;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class LegacyStoryboardDecoderTest
{
[Test]
public void TestDecodeStoryboardEvents()
{
var decoder = new LegacyBeatmapDecoder();
using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
using (var stream = new StreamReader(resStream))
{
var storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
Assert.IsTrue(storyboard.HasDrawable);
Assert.AreEqual(4, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background);
Assert.AreEqual(16, background.Elements.Count());
Assert.IsTrue(background.EnabledWhenFailing);
Assert.IsTrue(background.EnabledWhenPassing);
Assert.AreEqual("Background", background.Name);
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
Assert.IsNotNull(fail);
Assert.AreEqual(0, fail.Elements.Count());
Assert.IsTrue(fail.EnabledWhenFailing);
Assert.IsFalse(fail.EnabledWhenPassing);
Assert.AreEqual("Fail", fail.Name);
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
Assert.IsNotNull(pass);
Assert.AreEqual(0, pass.Elements.Count());
Assert.IsFalse(pass.EnabledWhenFailing);
Assert.IsTrue(pass.EnabledWhenPassing);
Assert.AreEqual("Pass", pass.Name);
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
Assert.IsNotNull(foreground);
Assert.AreEqual(151, foreground.Elements.Count());
Assert.IsTrue(foreground.EnabledWhenFailing);
Assert.IsTrue(foreground.EnabledWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSample));
Assert.AreEqual(15, spriteCount);
Assert.AreEqual(1, animationCount);
Assert.AreEqual(0, sampleCount);
Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount);
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
Assert.NotNull(sprite);
Assert.IsTrue(sprite.HasCommands);
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin);
Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
var animation = background.Elements.ElementAt(12) as StoryboardAnimation;
Assert.NotNull(animation);
Assert.AreEqual(141175, animation.EndTime);
Assert.AreEqual(10, animation.FrameCount);
Assert.AreEqual(30, animation.FrameDelay);
Assert.IsTrue(animation.HasCommands);
Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition);
Assert.IsTrue(animation.IsDrawable);
Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
Assert.AreEqual(Anchor.Centre, animation.Origin);
Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
Assert.AreEqual(78993, animation.StartTime);
}
}
}
}

View File

@ -1,146 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.IO;
using NUnit.Framework;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.Tests.Resources;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Tests.Beatmaps.Formats
{
[TestFixture]
public class OsuLegacyDecoderTest
{
[Test]
public void TestDecodeMetadata()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var meta = beatmap.BeatmapInfo.Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
Assert.AreEqual("Gamu", meta.AuthorString);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
Assert.AreEqual(164471, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
Assert.AreEqual("Renatus", meta.Title);
Assert.AreEqual("Renatus", meta.TitleUnicode);
}
}
[Test]
public void TestDecodeGeneral()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmapInfo = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
Assert.AreEqual(false, beatmapInfo.Countdown);
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
Assert.IsTrue(beatmapInfo.RulesetID == 0);
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
}
}
[Test]
public void TestDecodeEditor()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream)).BeatmapInfo;
int[] expectedBookmarks =
{
11505, 22054, 32604, 43153, 53703, 64252, 74802, 85351,
95901, 106450, 116999, 119637, 130186, 140735, 151285,
161834, 164471, 175020, 185570, 196119, 206669, 209306
};
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
for (int i = 0; i < expectedBookmarks.Length; i++)
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
Assert.AreEqual(4, beatmap.BeatDivisor);
Assert.AreEqual(4, beatmap.GridSize);
Assert.AreEqual(2, beatmap.TimelineZoom);
}
}
[Test]
public void TestDecodeDifficulty()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
Assert.AreEqual(9, difficulty.ApproachRate);
Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
Assert.AreEqual(2, difficulty.SliderTickRate);
}
}
[Test]
public void TestDecodeColors()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
Color4[] expected =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
};
Assert.AreEqual(expected.Length, beatmap.ComboColors.Count);
for (int i = 0; i < expected.Length; i++)
Assert.AreEqual(expected[i], beatmap.ComboColors[i]);
}
}
[Test]
public void TestDecodeHitObjects()
{
var decoder = new OsuLegacyDecoder();
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var curveData = beatmap.HitObjects[0] as IHasCurve;
var positionData = beatmap.HitObjects[0] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.IsNotNull(curveData);
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL));
positionData = beatmap.HitObjects[1] as IHasPosition;
Assert.IsNotNull(positionData);
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP));
}
}
}
}

View File

@ -50,7 +50,7 @@ namespace osu.Game.Tests.Beatmaps.IO
BeatmapMetadata meta; BeatmapMetadata meta;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
meta = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID); Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.Artist);

View File

@ -0,0 +1,342 @@
// Copyright (c) 2007-2017 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.IO;
using System.Linq;
using System.Text;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBeatmapCarousel : OsuTestCase
{
private TestBeatmapCarousel carousel;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CarouselItem),
typeof(CarouselGroup),
typeof(CarouselGroupEagerSelect),
typeof(CarouselBeatmap),
typeof(CarouselBeatmapSet),
typeof(DrawableCarouselItem),
typeof(CarouselItemState),
typeof(DrawableCarouselBeatmap),
typeof(DrawableCarouselBeatmapSet),
};
private readonly Stack<BeatmapSetInfo> selectedSets = new Stack<BeatmapSetInfo>();
private BeatmapInfo currentSelection;
private const int set_count = 5;
[BackgroundDependencyLoader]
private void load()
{
Add(carousel = new TestBeatmapCarousel
{
RelativeSizeAxes = Axes.Both,
});
List<BeatmapSetInfo> beatmapSets = new List<BeatmapSetInfo>();
for (int i = 1; i <= set_count; i++)
beatmapSets.Add(createTestBeatmapSet(i));
carousel.SelectionChanged = s => currentSelection = s;
AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load");
testTraversal();
testFiltering();
testRandom();
testAddRemove();
testSorting();
testRemoveAll();
}
private void ensureRandomFetchSuccess() =>
AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet);
private void checkSelected(int set, int? diff = null) =>
AddAssert($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () =>
{
if (diff != null)
return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First();
return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap);
});
private void setSelected(int set, int diff) =>
AddStep($"select set{set} diff{diff}", () =>
carousel.SelectBeatmap(carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff - 1).First()));
private void advanceSelection(bool diff, int direction = 1, int count = 1)
{
if (count == 1)
AddStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff));
else
{
AddRepeatStep($"select {(direction > 0 ? "next" : "prev")} {(diff ? "diff" : "set")}", () =>
carousel.SelectNext(direction, !diff), count);
}
}
private void checkVisibleItemCount(bool diff, int count) =>
AddAssert($"{count} {(diff ? "diffs" : "sets")} visible", () =>
carousel.Items.Count(s => (diff ? s.Item is CarouselBeatmap : s.Item is CarouselBeatmapSet) && s.Item.Visible) == count);
private void nextRandom() =>
AddStep("select random next", () =>
{
carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation;
if (!selectedSets.Any() && carousel.SelectedBeatmap != null)
selectedSets.Push(carousel.SelectedBeatmapSet);
carousel.SelectNextRandom();
selectedSets.Push(carousel.SelectedBeatmapSet);
});
private void ensureRandomDidntRepeat() =>
AddAssert("ensure no repeats", () => selectedSets.Distinct().Count() == selectedSets.Count);
private void prevRandom() => AddStep("select random last", () =>
{
carousel.SelectPreviousRandom();
selectedSets.Pop();
});
/// <summary>
/// Test keyboard traversal
/// </summary>
private void testTraversal()
{
advanceSelection(direction: 1, diff: false);
checkSelected(1, 1);
advanceSelection(direction: 1, diff: true);
checkSelected(1, 2);
advanceSelection(direction: -1, diff: false);
checkSelected(set_count, 1);
advanceSelection(direction: -1, diff: true);
checkSelected(set_count - 1, 3);
advanceSelection(diff: false);
advanceSelection(diff: false);
checkSelected(1, 2);
advanceSelection(direction: -1, diff: true);
advanceSelection(direction: -1, diff: true);
checkSelected(set_count, 3);
}
/// <summary>
/// Test filtering
/// </summary>
private void testFiltering()
{
// basic filtering
setSelected(1, 1);
AddStep("Filter", () => carousel.Filter(new FilterCriteria { SearchText = "set #3!" }, false));
checkVisibleItemCount(diff: false, count: 1);
checkVisibleItemCount(diff: true, count: 3);
checkSelected(3, 1);
advanceSelection(diff: true, count: 4);
checkSelected(3, 2);
AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria()));
AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce");
checkVisibleItemCount(diff: false, count: set_count);
checkVisibleItemCount(diff: true, count: 3);
// test filtering some difficulties (and keeping current beatmap set selected).
setSelected(1, 2);
AddStep("Filter some difficulties", () => carousel.Filter(new FilterCriteria { SearchText = "Normal" }, false));
checkSelected(1, 1);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
checkSelected(1, 1);
AddStep("Filter all", () => carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false));
checkVisibleItemCount(false, 0);
checkVisibleItemCount(true, 0);
AddAssert("Selection is null", () => currentSelection == null);
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
AddAssert("Selection is non-null", () => currentSelection != null);
}
/// <summary>
/// Test random non-repeating algorithm
/// </summary>
private void testRandom()
{
setSelected(1, 1);
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
prevRandom();
ensureRandomFetchSuccess();
prevRandom();
ensureRandomFetchSuccess();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
ensureRandomDidntRepeat();
nextRandom();
AddAssert("ensure repeat", () => selectedSets.Contains(carousel.SelectedBeatmapSet));
}
/// <summary>
/// Test adding and removing beatmap sets
/// </summary>
private void testAddRemove()
{
AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 1)));
AddStep("Add new set", () => carousel.UpdateBeatmapSet(createTestBeatmapSet(set_count + 2)));
checkVisibleItemCount(false, set_count + 2);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 2)));
checkVisibleItemCount(false, set_count + 1);
setSelected(set_count + 1, 1);
AddStep("Remove set", () => carousel.RemoveBeatmapSet(createTestBeatmapSet(set_count + 1)));
checkVisibleItemCount(false, set_count);
checkSelected(set_count);
}
/// <summary>
/// Test sorting
/// </summary>
private void testSorting()
{
AddStep("Sort by author", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Author }, false));
AddAssert("Check zzzzz is at bottom", () => carousel.BeatmapSets.Last().Metadata.AuthorString == "zzzzz");
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
AddAssert($"Check #{set_count} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Title.EndsWith($"#{set_count}!"));
}
private void testRemoveAll()
{
setSelected(2, 1);
AddAssert("Selection is non-null", () => currentSelection != null);
AddStep("Remove selected", () => carousel.RemoveBeatmapSet(carousel.SelectedBeatmapSet));
checkSelected(2);
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First()));
checkSelected(1);
AddUntilStep(() =>
{
if (!carousel.BeatmapSets.Any()) return true;
carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last());
return false;
}, "Remove all");
AddAssert("Selection is null", () => currentSelection == null);
}
private BeatmapSetInfo createTestBeatmapSet(int i)
{
return new BeatmapSetInfo
{
ID = i,
OnlineBeatmapSetID = i,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata
{
OnlineBeatmapSetID = i,
// Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{i.ToString().PadLeft(6, '0')}",
Title = $"test set #{i}!",
AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, i - 1)), 5))
},
Beatmaps = new List<BeatmapInfo>(new[]
{
new BeatmapInfo
{
OnlineBeatmapID = i * 10,
Path = "normal.osu",
Version = "Normal",
StarDifficulty = 2,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
},
new BeatmapInfo
{
OnlineBeatmapID = i * 10 + 1,
Path = "hard.osu",
Version = "Hard",
StarDifficulty = 5,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 5,
}
},
new BeatmapInfo
{
OnlineBeatmapID = i * 10 + 2,
Path = "insane.osu",
Version = "Insane",
StarDifficulty = 6,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 7,
}
},
}),
};
}
private class TestBeatmapCarousel : BeatmapCarousel
{
public new List<DrawableCarouselItem> Items => base.Items;
public bool PendingFilterTask => FilterTask != null;
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual
{
internal class TestCaseBeatmapInfoWedge : OsuTestCase
{
private BeatmapManager beatmaps;
private readonly Random random;
private readonly BeatmapInfoWedge infoWedge;
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
public TestCaseBeatmapInfoWedge()
{
random = new Random(0123);
Add(infoWedge = new BeatmapInfoWedge
{
Size = new Vector2(0.5f, 245),
RelativeSizeAxes = Axes.X,
Margin = new MarginPadding
{
Top = 20,
},
});
AddStep("show", () =>
{
Content.FadeInFromZero(250);
infoWedge.State = Visibility.Visible;
infoWedge.UpdateBeatmap(beatmap);
});
AddStep("hide", () =>
{
infoWedge.State = Visibility.Hidden;
Content.FadeOut(100);
});
AddStep("random beatmap", randomBeatmap);
AddStep("null beatmap", () => infoWedge.UpdateBeatmap(beatmap.Default));
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game, BeatmapManager beatmaps)
{
this.beatmaps = beatmaps;
beatmap.BindTo(game.Beatmap);
}
private void randomBeatmap()
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count == 0)
return;
var b = sets[random.Next(0, sets.Count)].Beatmaps[0];
beatmap.Value = beatmaps.GetWorkingBeatmap(b);
infoWedge.UpdateBeatmap(beatmap);
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2007-2017 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 OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Tests.Visual
{
public class TestCaseEditorSelectionLayer : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(SelectionLayer) };
public TestCaseEditorSelectionLayer()
{
var playfield = new OsuEditPlayfield
{
new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }),
new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }),
new DrawableSlider(new Slider
{
ControlPoints = new List<Vector2>
{
new Vector2(128, 256),
new Vector2(344, 256),
},
Distance = 400,
Position = new Vector2(128, 256),
Velocity = 1,
TickDistance = 100,
Scale = 0.5f
})
};
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(new StopwatchClock()),
Child = playfield
},
new SelectionLayer(playfield)
};
}
}
}

View File

@ -0,0 +1,258 @@
// Copyright (c) 2007-2017 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.ComponentModel;
using System.Linq;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
[Description("player pause/fail screens")]
internal class TestCaseGameplayMenuOverlay : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) };
private FailOverlay failOverlay;
private PauseContainer.PauseOverlay pauseOverlay;
[BackgroundDependencyLoader]
private void load()
{
Add(pauseOverlay = new PauseContainer.PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
Add(failOverlay = new FailOverlay
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
var retryCount = 0;
AddStep("Add retry", () =>
{
retryCount++;
pauseOverlay.Retries = failOverlay.Retries = retryCount;
});
AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
testHideResets();
testEnterWithoutSelection();
testKeyUpFromInitial();
testKeyDownFromInitial();
testKeyUpWrapping();
testKeyDownWrapping();
testMouseSelectionAfterKeySelection();
testKeySelectionAfterMouseSelection();
testMouseDeselectionResets();
testClickSelection();
testEnterKeySelection();
}
/// <summary>
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
/// </summary>
private void testHideResets()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Hover first button", () => failOverlay.Buttons.First().TriggerOnHover(null));
AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected));
}
/// <summary>
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
/// </summary>
private void testEnterWithoutSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Press enter", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter }));
AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow from the initial state selects the last button.
/// </summary>
private void testKeyUpFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow from the initial state selects the first button.
/// </summary>
private void testKeyDownFromInitial()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyUpWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Up arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
/// </summary>
private void testKeyDownWrapping()
{
AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Down arrow", () => failOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => failOverlay.Hide());
}
/// <summary>
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
/// </summary>
private void testMouseSelectionAfterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddStep("Hover second button", () => secondButton.TriggerOnHover(null));
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected);
AddAssert("Second button selected", () => secondButton.Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
/// </summary>
private void testKeySelectionAfterMouseSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => secondButton.TriggerOnHover(null));
AddStep("Up arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Up }));
AddAssert("Second button not selected", () => !secondButton.Selected);
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected);
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
/// </summary>
private void testMouseDeselectionResets()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var secondButton = pauseOverlay.Buttons.Skip(1).First();
AddStep("Hover second button", () => secondButton.TriggerOnHover(null));
AddStep("Unhover second button", () => secondButton.TriggerOnHoverLost(null));
AddStep("Down arrow", () => pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down }));
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition
AddStep("Hide overlay", () => pauseOverlay.Hide());
}
/// <summary>
/// Tests that clicking on a button correctly causes a click event for that button.
/// </summary>
private void testClickSelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
var retryButton = pauseOverlay.Buttons.Skip(1).First();
bool triggered = false;
AddStep("Click retry button", () =>
{
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.TriggerOnClick();
pauseOverlay.OnRetry = lastAction;
});
AddAssert("Action was triggered", () => triggered);
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
}
/// <summary>
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
/// </summary>
private void testEnterKeySelection()
{
AddStep("Show overlay", () => pauseOverlay.Show());
AddStep("Select second button", () =>
{
pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
pauseOverlay.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Down });
});
var retryButton = pauseOverlay.Buttons.Skip(1).First();
bool triggered = false;
AddStep("Press enter", () =>
{
var lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true;
retryButton.TriggerOnKeyDown(null, new KeyDownEventArgs { Key = Key.Enter });
pauseOverlay.OnRetry = lastAction;
});
AddAssert("Action was triggered", () => triggered);
AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden);
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) 2007-2017 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.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Overlays.Profile.Sections;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
internal class TestCaseHistoricalSection : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes =>
new[]
{
typeof(HistoricalSection),
typeof(PaginatedMostPlayedBeatmapContainer),
typeof(DrawableMostPlayedRow),
typeof(DrawableProfileRow)
};
public TestCaseHistoricalSection()
{
HistoricalSection section;
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
});
Add(new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = section = new HistoricalSection(),
});
AddStep("Show peppy", () => section.User.Value = new User { Id = 2 });
AddStep("Show WubWoofWolf", () => section.User.Value = new User { Id = 39828 });
}
}
}

View File

@ -1,57 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
[Description("player pause/fail screens")]
internal class TestCaseMenuOverlays : OsuTestCase
{
public TestCaseMenuOverlays()
{
FailOverlay failOverlay;
PauseContainer.PauseOverlay pauseOverlay;
var retryCount = 0;
Add(pauseOverlay = new PauseContainer.PauseOverlay
{
OnResume = () => Logger.Log(@"Resume"),
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
Add(failOverlay = new FailOverlay
{
OnRetry = () => Logger.Log(@"Retry"),
OnQuit = () => Logger.Log(@"Quit"),
});
AddStep(@"Pause", delegate
{
if (failOverlay.State == Visibility.Visible)
{
failOverlay.Hide();
}
pauseOverlay.Show();
});
AddStep("Fail", delegate
{
if (pauseOverlay.State == Visibility.Visible)
{
pauseOverlay.Hide();
}
failOverlay.Show();
});
AddStep("Add Retry", delegate
{
retryCount++;
pauseOverlay.Retries = retryCount;
failOverlay.Retries = retryCount;
});
}
}
}

View File

@ -8,17 +8,25 @@ using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using System.Linq;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[Description("mod select and icon display")] [Description("mod select and icon display")]
internal class TestCaseMods : OsuTestCase internal class TestCaseMods : OsuTestCase
{ {
private ModSelectOverlay modSelect; private const string unranked_suffix = " (Unranked)";
private ModDisplay modDisplay;
private RulesetStore rulesets; private RulesetStore rulesets;
private ModDisplay modDisplay;
private TestModSelectOverlay modSelect;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
@ -30,7 +38,7 @@ namespace osu.Game.Tests.Visual
{ {
base.LoadComplete(); base.LoadComplete();
Add(modSelect = new ModSelectOverlay Add(modSelect = new TestModSelectOverlay
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
@ -48,9 +56,156 @@ namespace osu.Game.Tests.Visual
modDisplay.Current.BindTo(modSelect.SelectedMods); modDisplay.Current.BindTo(modSelect.SelectedMods);
AddStep("Toggle", modSelect.ToggleVisibility); AddStep("Toggle", modSelect.ToggleVisibility);
AddStep("Hide", modSelect.Hide);
AddStep("Show", modSelect.Show);
foreach (var ruleset in rulesets.AvailableRulesets) foreach (var rulesetInfo in rulesets.AvailableRulesets)
AddStep(ruleset.CreateInstance().Description, () => modSelect.Ruleset.Value = ruleset); {
Ruleset ruleset = rulesetInfo.CreateInstance();
AddStep($"switch to {ruleset.Description}", () => modSelect.Ruleset.Value = rulesetInfo);
switch (ruleset) {
case OsuRuleset or:
testOsuMods(or);
break;
}
}
}
private void testOsuMods(OsuRuleset ruleset)
{
var easierMods = ruleset.GetModsFor(ModType.DifficultyReduction);
var harderMods = ruleset.GetModsFor(ModType.DifficultyIncrease);
var assistMods = ruleset.GetModsFor(ModType.Special);
var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot);
testSingleMod(noFailMod);
testMultiMod(doubleTimeMod);
testIncompatibleMods(noFailMod, autoPilotMod);
testDeselectAll(easierMods.Where(m => !(m is MultiMod)));
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
testMultiplierTextUnranked(autoPilotMod);
}
private void testSingleMod(Mod mod)
{
selectNext(mod);
checkSelected(mod);
selectPrevious(mod);
checkNotSelected(mod);
selectNext(mod);
selectNext(mod);
checkNotSelected(mod);
selectPrevious(mod);
selectPrevious(mod);
checkNotSelected(mod);
}
private void testMultiMod(MultiMod multiMod)
{
foreach (var mod in multiMod.Mods)
{
selectNext(mod);
checkSelected(mod);
}
for (int index = multiMod.Mods.Length - 1; index >= 0; index--)
selectPrevious(multiMod.Mods[index]);
foreach (var mod in multiMod.Mods)
checkNotSelected(mod);
}
private void testIncompatibleMods(Mod modA, Mod modB)
{
selectNext(modA);
checkSelected(modA);
checkNotSelected(modB);
selectNext(modB);
checkSelected(modB);
checkNotSelected(modA);
selectPrevious(modB);
checkNotSelected(modA);
checkNotSelected(modB);
}
private void testDeselectAll(IEnumerable<Mod> mods)
{
foreach (var mod in mods)
selectNext(mod);
AddAssert("check for any selection", () => modSelect.SelectedMods.Value.Any());
AddStep("deselect all", modSelect.DeselectAllButton.Action.Invoke);
AddAssert("check for no selection", () => !modSelect.SelectedMods.Value.Any());
}
private void testMultiplierTextColour(Mod mod, Color4 colour)
{
checkLabelColor(Color4.White);
selectNext(mod);
AddWaitStep(1, "wait for changing colour");
checkLabelColor(colour);
selectPrevious(mod);
AddWaitStep(1, "wait for changing colour");
checkLabelColor(Color4.White);
}
private void testMultiplierTextUnranked(Mod mod)
{
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
selectNext(mod);
AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
selectPrevious(mod);
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
}
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext());
private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectPrevious());
private void checkSelected(Mod mod)
{
AddAssert($"check {mod.Name} is selected", () =>
{
var button = modSelect.GetModButton(mod);
return modSelect.SelectedMods.Value.Single(m => m.Name == mod.Name) != null && button.SelectedMod.GetType() == mod.GetType() && button.Selected;
});
}
private void checkNotSelected(Mod mod)
{
AddAssert($"check {mod.Name} is not selected", () =>
{
var button = modSelect.GetModButton(mod);
return modSelect.SelectedMods.Value.All(m => m.GetType() != mod.GetType()) && button.SelectedMod?.GetType() != mod.GetType();
});
}
private void checkLabelColor(Color4 color) => AddAssert("check label has expected colour", () => modSelect.MultiplierLabel.Colour.AverageColour == color);
private class TestModSelectOverlay : ModSelectOverlay
{
public ModButton GetModButton(Mod mod)
{
var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type);
return section.ButtonsContainer.OfType<ModButton>().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType()));
}
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
public new TriangleButton DeselectAllButton => base.DeselectAllButton;
public new Color4 LowMultiplierColour => base.LowMultiplierColour;
public new Color4 HighMultiplierColour => base.HighMultiplierColour;
} }
} }
} }

View File

@ -82,7 +82,11 @@ namespace osu.Game.Tests.Visual
private void sendProgress2() private void sendProgress2()
{ {
var n = new ProgressNotification { Text = @"Downloading Haitai..." }; var n = new ProgressNotification
{
Text = @"Downloading Haitai...",
CompletionText = "Downloaded Haitai!",
};
manager.Post(n); manager.Post(n);
progressingNotifications.Add(n); progressingNotifications.Add(n);
} }
@ -91,7 +95,11 @@ namespace osu.Game.Tests.Visual
private void sendProgress1() private void sendProgress1()
{ {
var n = new ProgressNotification { Text = @"Uploading to BSS..." }; var n = new ProgressNotification
{
Text = @"Uploading to BSS...",
CompletionText = "Uploaded to BSS!",
};
manager.Post(n); manager.Post(n);
progressingNotifications.Add(n); progressingNotifications.Add(n);
} }

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
using osu.Game.Tests.Platform; using osu.Game.Tests.Platform;
@ -26,10 +27,28 @@ namespace osu.Game.Tests.Visual
private DependencyContainer dependencies; private DependencyContainer dependencies;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SongSelect),
typeof(BeatmapCarousel),
typeof(CarouselItem),
typeof(CarouselGroup),
typeof(CarouselGroupEagerSelect),
typeof(CarouselBeatmap),
typeof(CarouselBeatmapSet),
typeof(DrawableCarouselItem),
typeof(CarouselItemState),
typeof(DrawableCarouselBeatmap),
typeof(DrawableCarouselBeatmapSet),
};
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(BeatmapManager baseManager)
{ {
PlaySongSelect songSelect; PlaySongSelect songSelect;
@ -43,7 +62,10 @@ namespace osu.Game.Tests.Visual
Func<OsuDbContext> contextFactory = () => context; Func<OsuDbContext> contextFactory = () => context;
dependencies.Cache(rulesets = new RulesetStore(contextFactory)); dependencies.Cache(rulesets = new RulesetStore(contextFactory));
dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)); dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
{
DefaultBeatmap = baseManager.GetWorkingBeatmap(null)
});
for (int i = 0; i < 100; i += 10) for (int i = 0; i < 100; i += 10)
manager.Import(createTestBeatmapSet(i)); manager.Import(createTestBeatmapSet(i));

View File

@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
storyboardContainer.Clock = decoupledClock; storyboardContainer.Clock = decoupledClock;
storyboard = working.Beatmap.Storyboard.CreateDrawable(beatmapBacking); storyboard = working.Storyboard.CreateDrawable(beatmapBacking);
storyboard.Passing = false; storyboard.Passing = false;
storyboardContainer.Add(storyboard); storyboardContainer.Add(storyboard);

View File

@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
{ {
internal class TestCaseUserRanks : OsuTestCase internal class TestCaseUserRanks : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableScore), typeof(RanksSection) }; public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
public TestCaseUserRanks() public TestCaseUserRanks()
{ {

View File

@ -87,12 +87,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\Formats\OsuJsonDecoderTest.cs" /> <Compile Include="Beatmaps\Formats\OsuJsonDecoderTest.cs" />
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoderTest.cs" />
<Compile Include="Beatmaps\IO\OszArchiveReaderTest.cs" /> <Compile Include="Beatmaps\IO\OszArchiveReaderTest.cs" />
<Compile Include="Beatmaps\IO\ImportBeatmapTest.cs" /> <Compile Include="Beatmaps\IO\ImportBeatmapTest.cs" />
<Compile Include="Resources\Resource.cs" /> <Compile Include="Resources\Resource.cs" />
<Compile Include="Beatmaps\Formats\OsuLegacyDecoderTest.cs" /> <Compile Include="Beatmaps\Formats\LegacyBeatmapDecoderTest.cs" />
<Compile Include="Visual\TestCaseBeatmapCarousel.cs" />
<Compile Include="Visual\TestCaseBeatmapDetailArea.cs" /> <Compile Include="Visual\TestCaseBeatmapDetailArea.cs" />
<Compile Include="Visual\TestCaseBeatmapDetails.cs" /> <Compile Include="Visual\TestCaseBeatmapDetails.cs" />
<Compile Include="Visual\TestCaseBeatmapInfoWedge.cs" />
<Compile Include="Visual\TestCaseBeatmapOptionsOverlay.cs" /> <Compile Include="Visual\TestCaseBeatmapOptionsOverlay.cs" />
<Compile Include="Visual\TestCaseBeatmapScoresContainer.cs" /> <Compile Include="Visual\TestCaseBeatmapScoresContainer.cs" />
<Compile Include="Visual\TestCaseBeatmapSetOverlay.cs" /> <Compile Include="Visual\TestCaseBeatmapSetOverlay.cs" />
@ -111,8 +114,10 @@
<Compile Include="Visual\TestCaseEditorComposeTimeline.cs" /> <Compile Include="Visual\TestCaseEditorComposeTimeline.cs" />
<Compile Include="Visual\TestCaseEditorMenuBar.cs" /> <Compile Include="Visual\TestCaseEditorMenuBar.cs" />
<Compile Include="Visual\TestCaseEditorSummaryTimeline.cs" /> <Compile Include="Visual\TestCaseEditorSummaryTimeline.cs" />
<Compile Include="Visual\TestCaseEditorSelectionLayer.cs" />
<Compile Include="Visual\TestCaseGamefield.cs" /> <Compile Include="Visual\TestCaseGamefield.cs" />
<Compile Include="Visual\TestCaseGraph.cs" /> <Compile Include="Visual\TestCaseGraph.cs" />
<Compile Include="Visual\TestCaseHistoricalSection.cs" />
<Compile Include="Visual\TestCaseIconButton.cs" /> <Compile Include="Visual\TestCaseIconButton.cs" />
<Compile Include="Visual\TestCaseIntroSequence.cs" /> <Compile Include="Visual\TestCaseIntroSequence.cs" />
<Compile Include="Visual\TestCaseKeyConfiguration.cs" /> <Compile Include="Visual\TestCaseKeyConfiguration.cs" />
@ -120,7 +125,7 @@
<Compile Include="Visual\TestCaseLeaderboard.cs" /> <Compile Include="Visual\TestCaseLeaderboard.cs" />
<Compile Include="Visual\TestCaseMedalOverlay.cs" /> <Compile Include="Visual\TestCaseMedalOverlay.cs" />
<Compile Include="Visual\TestCaseButtonSystem.cs" /> <Compile Include="Visual\TestCaseButtonSystem.cs" />
<Compile Include="Visual\TestCaseMenuOverlays.cs" /> <Compile Include="Visual\TestCaseGameplayMenuOverlay.cs" />
<Compile Include="Visual\TestCaseMods.cs" /> <Compile Include="Visual\TestCaseMods.cs" />
<Compile Include="Visual\TestCaseMusicController.cs" /> <Compile Include="Visual\TestCaseMusicController.cs" />
<Compile Include="Visual\TestCaseNotificationOverlay.cs" /> <Compile Include="Visual\TestCaseNotificationOverlay.cs" />
@ -150,6 +155,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Resources\Soleily - Renatus %28Gamu%29 [Insane].osu" /> <EmbeddedResource Include="Resources\Soleily - Renatus %28Gamu%29 [Insane].osu" />
<EmbeddedResource Include="Resources\Himeringo - Yotsuya-san ni Yoroshiku %28RLC%29 [Winber1%27s Extreme].osu" />
<EmbeddedResource Include="Resources\Within Temptation - The Unforgiving %28Armin%29 [Marathon].osu" /> <EmbeddedResource Include="Resources\Within Temptation - The Unforgiving %28Armin%29 [Marathon].osu" />
<EmbeddedResource Include="Resources\Kozato snow - Rengetsu Ouka %28_Kiva%29 [Yuki YukI].osu" /> <EmbeddedResource Include="Resources\Kozato snow - Rengetsu Ouka %28_Kiva%29 [Yuki YukI].osu" />
</ItemGroup> </ItemGroup>

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Storyboards;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Game.IO.Serialization.Converters; using osu.Game.IO.Serialization.Converters;
@ -46,11 +45,6 @@ namespace osu.Game.Beatmaps
[JsonIgnore] [JsonIgnore]
public double TotalBreakTime => Breaks.Sum(b => b.Duration); public double TotalBreakTime => Breaks.Sum(b => b.Duration);
/// <summary>
/// The Beatmap's Storyboard.
/// </summary>
public Storyboard Storyboard = new Storyboard();
/// <summary> /// <summary>
/// Constructs a new beatmap. /// Constructs a new beatmap.
/// </summary> /// </summary>
@ -62,7 +56,6 @@ namespace osu.Game.Beatmaps
Breaks = original?.Breaks ?? Breaks; Breaks = original?.Breaks ?? Breaks;
ComboColors = original?.ComboColors ?? ComboColors; ComboColors = original?.ComboColors ?? ComboColors;
HitObjects = original?.HitObjects ?? HitObjects; HitObjects = original?.HitObjects ?? HitObjects;
Storyboard = original?.Storyboard ?? Storyboard;
if (original == null && Metadata == null) if (original == null && Metadata == null)
{ {

View File

@ -121,9 +121,12 @@ namespace osu.Game.Beatmaps
// Metadata // Metadata
public string Version { get; set; } public string Version { get; set; }
[JsonProperty("difficulty_rating")]
public double StarDifficulty { get; set; } public double StarDifficulty { get; set; }
public bool ShouldSerializeStarDifficulty() => false; public bool ShouldSerializeStarDifficulty() => false;
public override string ToString() => $"{Metadata} [{Version}]";
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {
if (ID == 0 || other?.ID == 0) if (ID == 0 || other?.ID == 0)

View File

@ -26,6 +26,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -134,6 +135,7 @@ namespace osu.Game.Beatmaps
var notification = new ProgressNotification var notification = new ProgressNotification
{ {
Text = "Beatmap import is initialising...", Text = "Beatmap import is initialising...",
CompletionText = "Import successful!",
Progress = 0, Progress = 0,
State = ProgressNotificationState.Active, State = ProgressNotificationState.Active,
}; };
@ -245,8 +247,9 @@ namespace osu.Game.Beatmaps
return; return;
} }
ProgressNotification downloadNotification = new ProgressNotification var downloadNotification = new ProgressNotification
{ {
CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!",
Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}",
}; };
@ -374,12 +377,9 @@ namespace osu.Game.Beatmaps
/// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns> /// <returns>A <see cref="WorkingBeatmap"/> instance correlating to the provided <see cref="BeatmapInfo"/>.</returns>
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null)
{ {
if (beatmapInfo == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
return DefaultBeatmap; return DefaultBeatmap;
if (beatmapInfo.BeatmapSet == null)
throw new InvalidOperationException($@"Beatmap set {beatmapInfo.BeatmapSetInfoID} is not in the local database.");
if (beatmapInfo.Metadata == null) if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
@ -495,17 +495,23 @@ namespace osu.Game.Beatmaps
BeatmapMetadata metadata; BeatmapMetadata metadata;
using (var stream = new StreamReader(reader.GetStream(mapName))) using (var stream = new StreamReader(reader.GetStream(mapName)))
metadata = BeatmapDecoder.GetDecoder(stream).Decode(stream).Metadata; metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
// check if a set already exists with the same online id. // check if a set already exists with the same online id.
beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID) ?? new BeatmapSetInfo if (metadata.OnlineBeatmapSetID != null)
{ beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID);
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(), if (beatmapSet == null)
Hash = hash, beatmapSet = new BeatmapSetInfo
Files = fileInfos, {
Metadata = metadata OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
}; Beatmaps = new List<BeatmapInfo>(),
Hash = hash,
Files = fileInfos,
Metadata = metadata
};
var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu")); var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
@ -518,19 +524,20 @@ namespace osu.Game.Beatmaps
raw.CopyTo(ms); raw.CopyTo(ms);
ms.Position = 0; ms.Position = 0;
var decoder = BeatmapDecoder.GetDecoder(sr); var decoder = Decoder.GetDecoder(sr);
Beatmap beatmap = decoder.Decode(sr); Beatmap beatmap = decoder.DecodeBeatmap(sr);
beatmap.BeatmapInfo.Path = name; beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID); var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
if (existing == null) if (existing == null)
{ {
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary // Exclude beatmap-metadata if it's equal to beatmapset-metadata
beatmap.BeatmapInfo.Metadata = null; if (metadata.Equals(beatmap.Metadata))
beatmap.BeatmapInfo.Metadata = null;
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
@ -639,23 +646,11 @@ namespace osu.Game.Beatmaps
{ {
try try
{ {
Beatmap beatmap;
BeatmapDecoder decoder;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
{ {
decoder = BeatmapDecoder.GetDecoder(stream); Decoder decoder = Decoder.GetDecoder(stream);
beatmap = decoder.Decode(stream); return decoder.DecodeBeatmap(stream);
} }
if (beatmap == null || BeatmapSetInfo.StoryboardFile == null)
return beatmap;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
decoder.Decode(stream, beatmap);
return beatmap;
} }
catch catch
{ {
@ -694,6 +689,28 @@ namespace osu.Game.Beatmaps
} }
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile))); protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
protected override Storyboard GetStoryboard()
{
if (BeatmapInfo?.Path == null && BeatmapSetInfo?.StoryboardFile == null)
return new Storyboard();
try
{
Decoder decoder;
using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo?.Path))))
decoder = Decoder.GetDecoder(stream);
// try for .osb first and fall back to .osu
string storyboardFile = BeatmapSetInfo.StoryboardFile ?? BeatmapInfo.Path;
using (var stream = new StreamReader(store.GetStream(getPathForFile(storyboardFile))))
return decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
}
catch
{
return new Storyboard();
}
}
} }
/// <summary> /// <summary>
@ -721,6 +738,7 @@ namespace osu.Game.Beatmaps
var notification = new ProgressNotification var notification = new ProgressNotification
{ {
Progress = 0, Progress = 0,
CompletionText = "Deleted all beatmaps!",
State = ProgressNotificationState.Active, State = ProgressNotificationState.Active,
}; };

View File

@ -11,7 +11,7 @@ using osu.Game.Users;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
[Serializable] [Serializable]
public class BeatmapMetadata public class BeatmapMetadata : IEquatable<BeatmapMetadata>
{ {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[JsonIgnore] [JsonIgnore]
@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
public string AudioFile { get; set; } public string AudioFile { get; set; }
public string BackgroundFile { get; set; } public string BackgroundFile { get; set; }
public override string ToString() => $"{Artist} - {Title} ({Author})";
[JsonIgnore] [JsonIgnore]
public string[] SearchableTerms => new[] public string[] SearchableTerms => new[]
{ {
@ -74,5 +76,23 @@ namespace osu.Game.Beatmaps
Source, Source,
Tags Tags
}.Where(s => !string.IsNullOrEmpty(s)).ToArray(); }.Where(s => !string.IsNullOrEmpty(s)).ToArray();
public bool Equals(BeatmapMetadata other)
{
if (other == null)
return false;
return onlineBeatmapSetID == other.onlineBeatmapSetID
&& Title == other.Title
&& TitleUnicode == other.TitleUnicode
&& Artist == other.Artist
&& ArtistUnicode == other.ArtistUnicode
&& AuthorString == other.AuthorString
&& Source == other.Source
&& Tags == other.Tags
&& PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile
&& BackgroundFile == other.BackgroundFile;
}
} }
} }

View File

@ -33,6 +33,8 @@ namespace osu.Game.Beatmaps
public List<BeatmapSetFileInfo> Files { get; set; } public List<BeatmapSetFileInfo> Files { get; set; }
public override string ToString() => Metadata.ToString();
public bool Protected { get; set; } public bool Protected { get; set; }
} }
} }

View File

@ -32,6 +32,18 @@ namespace osu.Game.Beatmaps
{ {
var context = GetContext(); var context = GetContext();
foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
{
// If we detect a new metadata object it'll be attached to the current context so it can be reused
// to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
// of the corresponding table (.Set<BeatmapMetadata>()) for matching entries to our criteria.
var contextMetadata = context.Set<BeatmapMetadata>().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
if (contextMetadata != null)
beatmap.Metadata = contextMetadata;
else
context.BeatmapMetadata.Attach(beatmap.Metadata);
}
context.BeatmapSetInfo.Attach(beatmapSet); context.BeatmapSetInfo.Attach(beatmapSet);
context.SaveChanges(); context.SaveChanges();

View File

@ -1,138 +0,0 @@
// Copyright (c) 2007-2017 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.Framework;
using osu.Framework.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
public class BeatmapGroup : IStateful<BeatmapGroupState>
{
public event Action<BeatmapGroupState> StateChanged;
public BeatmapPanel SelectedPanel;
/// <summary>
/// Fires when one of our difficulties was selected. Will fire on first expand.
/// </summary>
public Action<BeatmapGroup, BeatmapPanel> SelectionChanged;
/// <summary>
/// Fires when one of our difficulties is clicked when already selected. Should start playing the map.
/// </summary>
public Action<BeatmapInfo> StartRequested;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public Action<BeatmapInfo> EditRequested;
public BeatmapSetHeader Header;
public List<BeatmapPanel> BeatmapPanels;
public BeatmapSetInfo BeatmapSet;
private BeatmapGroupState state;
public BeatmapGroupState State
{
get { return state; }
set
{
state = value;
switch (value)
{
case BeatmapGroupState.Expanded:
Header.State = PanelSelectedState.Selected;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected;
break;
case BeatmapGroupState.Collapsed:
Header.State = PanelSelectedState.NotSelected;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
case BeatmapGroupState.Hidden:
Header.State = PanelSelectedState.Hidden;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
}
StateChanged?.Invoke(state);
}
}
public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager)
{
if (beatmapSet == null)
throw new ArgumentNullException(nameof(beatmapSet));
if (manager == null)
throw new ArgumentNullException(nameof(manager));
BeatmapSet = beatmapSet;
WorkingBeatmap beatmap = manager.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault());
Header = new BeatmapSetHeader(beatmap)
{
GainedSelection = headerGainedSelection,
DeleteRequested = b => DeleteRequested(b),
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
RelativeSizeAxes = Axes.X,
};
BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p),
StartRequested = p => StartRequested?.Invoke(p.Beatmap),
EditRequested = p => EditRequested?.Invoke(p.Beatmap),
RelativeSizeAxes = Axes.X,
}).ToList();
Header.AddDifficultyIcons(BeatmapPanels);
}
private void headerGainedSelection(BeatmapSetHeader panel)
{
State = BeatmapGroupState.Expanded;
//we want to make sure one of our children is selected in the case none have been selected yet.
if (SelectedPanel == null)
BeatmapPanels.First().State = PanelSelectedState.Selected;
}
private void panelGainedSelection(BeatmapPanel panel)
{
try
{
if (SelectedPanel == panel) return;
if (SelectedPanel != null)
SelectedPanel.State = PanelSelectedState.NotSelected;
SelectedPanel = panel;
}
finally
{
State = BeatmapGroupState.Expanded;
SelectionChanged?.Invoke(this, SelectedPanel);
}
}
}
public enum BeatmapGroupState
{
Collapsed,
Expanded,
Hidden,
}
}

View File

@ -11,21 +11,44 @@ namespace osu.Game.Beatmaps.Drawables
public class BeatmapSetCover : Sprite public class BeatmapSetCover : Sprite
{ {
private readonly BeatmapSetInfo set; private readonly BeatmapSetInfo set;
public BeatmapSetCover(BeatmapSetInfo set) private readonly BeatmapSetCoverType type;
public BeatmapSetCover(BeatmapSetInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover)
{ {
if (set == null) if (set == null)
throw new ArgumentNullException(nameof(set)); throw new ArgumentNullException(nameof(set));
this.set = set; this.set = set;
this.type = type;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures)
{ {
string resource = set.OnlineInfo.Covers.Cover; string resource = null;
switch (type)
{
case BeatmapSetCoverType.Cover:
resource = set.OnlineInfo.Covers.Cover;
break;
case BeatmapSetCoverType.Card:
resource = set.OnlineInfo.Covers.Card;
break;
case BeatmapSetCoverType.List:
resource = set.OnlineInfo.Covers.List;
break;
}
if (resource != null) if (resource != null)
Texture = textures.Get(resource); Texture = textures.Get(resource);
} }
} }
public enum BeatmapSetCoverType
{
Cover,
Card,
List,
}
} }

View File

@ -63,6 +63,8 @@ namespace osu.Game.Beatmaps
public override string Description => "dummy"; public override string Description => "dummy";
public override string ShortName => "dummy";
public DummyRuleset(RulesetInfo rulesetInfo) public DummyRuleset(RulesetInfo rulesetInfo)
: base(rulesetInfo) : base(rulesetInfo)
{ {

View File

@ -1,75 +0,0 @@
// Copyright (c) 2007-2017 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.IO;
namespace osu.Game.Beatmaps.Formats
{
public abstract class BeatmapDecoder
{
private static readonly Dictionary<string, Type> decoders = new Dictionary<string, Type>();
static BeatmapDecoder()
{
OsuLegacyDecoder.Register();
OsuJsonDecoder.Register();
}
public static BeatmapDecoder GetDecoder(StreamReader stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
string line;
do { line = stream.ReadLine()?.Trim(); }
while (line != null && line.Length == 0);
if (line == null || !decoders.ContainsKey(line))
throw new IOException(@"Unknown file format");
try
{
return (BeatmapDecoder)Activator.CreateInstance(decoders[line], line);
}
catch
{
// As a default case, try a parameterless constructor
return (BeatmapDecoder)Activator.CreateInstance(decoders[line]);
}
}
protected static void AddDecoder<T>(string magic) where T : BeatmapDecoder
{
decoders[magic] = typeof(T);
}
public virtual Beatmap Decode(StreamReader stream)
{
return ParseFile(stream);
}
public virtual void Decode(StreamReader stream, Beatmap beatmap)
{
ParseFile(stream, beatmap);
}
protected virtual Beatmap ParseFile(StreamReader stream)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata(),
BaseDifficulty = new BeatmapDifficulty(),
},
};
ParseFile(stream, beatmap);
return beatmap;
}
protected abstract void ParseFile(StreamReader stream, Beatmap beatmap);
}
}

View File

@ -0,0 +1,80 @@
// Copyright (c) 2007-2017 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.IO;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps.Formats
{
public abstract class Decoder
{
private static readonly Dictionary<string, Type> decoders = new Dictionary<string, Type>();
static Decoder()
{
LegacyDecoder.Register();
}
/// <summary>
/// Retrieves a <see cref="Decoder"/> to parse a <see cref="Beatmap"/>.
/// </summary>
/// <param name="stream">A stream pointing to the <see cref="Beatmap"/>.</param>
public static Decoder GetDecoder(StreamReader stream)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
string line;
do
{ line = stream.ReadLine()?.Trim(); }
while (line != null && line.Length == 0);
if (line == null || !decoders.ContainsKey(line))
throw new IOException(@"Unknown file format");
return (Decoder)Activator.CreateInstance(decoders[line], line);
}
/// <summary>
/// Adds the <see cref="Decoder"/> to the list of <see cref="Beatmap"/> and <see cref="Storyboard"/> decoder.
/// </summary>
/// <typeparam name="T">Type to decode a <see cref="Beatmap"/> with.</typeparam>
/// <param name="version">A string representation of the version.</param>
protected static void AddDecoder<T>(string version) where T : Decoder
{
decoders[version] = typeof(T);
}
/// <summary>
/// Retrieves a <see cref="Decoder"/> to parse a <see cref="Storyboard"/>
/// </summary>
public abstract Decoder GetStoryboardDecoder();
public virtual Beatmap DecodeBeatmap(StreamReader stream)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata(),
BaseDifficulty = new BeatmapDifficulty(),
},
};
ParseBeatmap(stream, beatmap);
return beatmap;
}
protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap);
public virtual Storyboard DecodeStoryboard(StreamReader stream)
{
var storyboard = new Storyboard();
ParseStoryboard(stream, storyboard);
return storyboard;
}
protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard);
}
}

View File

@ -0,0 +1,421 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Globalization;
using System.IO;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
using System.Collections.Generic;
namespace osu.Game.Beatmaps.Formats
{
public class LegacyBeatmapDecoder : LegacyDecoder
{
private Beatmap beatmap;
private bool hasCustomColours;
private ConvertHitObjectParser parser;
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
public LegacyBeatmapDecoder()
{
}
public LegacyBeatmapDecoder(string header)
{
BeatmapVersion = int.Parse(header.Substring(17));
}
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
this.beatmap = beatmap;
this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion;
ParseContent(stream);
foreach (var hitObject in this.beatmap.HitObjects)
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
}
protected override bool ShouldSkipLine(string line)
{
if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"))
return true;
return false;
}
protected override void ProcessSection(Section section, string line)
{
switch (section)
{
case Section.General:
handleGeneral(line);
break;
case Section.Editor:
handleEditor(line);
break;
case Section.Metadata:
handleMetadata(line);
break;
case Section.Difficulty:
handleDifficulty(line);
break;
case Section.Events:
handleEvents(line);
break;
case Section.TimingPoints:
handleTimingPoints(line);
break;
case Section.Colours:
handleColours(line);
break;
case Section.HitObjects:
handleHitObjects(line);
break;
case Section.Variables:
handleVariables(line);
break;
}
}
private void handleGeneral(string line)
{
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
{
case @"AudioFilename":
metadata.AudioFile = pair.Value;
break;
case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
break;
case @"PreviewTime":
metadata.PreviewTime = int.Parse(pair.Value);
break;
case @"Countdown":
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
break;
case @"SampleSet":
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
break;
case @"SampleVolume":
defaultSampleVolume = int.Parse(pair.Value);
break;
case @"StackLeniency":
beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"Mode":
beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
break;
case 1:
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
break;
case 2:
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
break;
case 3:
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
break;
}
break;
case @"LetterboxInBreaks":
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
break;
case @"SpecialStyle":
beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
break;
case @"WidescreenStoryboard":
beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
break;
}
}
private void handleEditor(string line)
{
var pair = splitKeyVal(line, ':');
switch (pair.Key)
{
case @"Bookmarks":
beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
break;
case @"DistanceSpacing":
beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"BeatDivisor":
beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
break;
case @"GridSize":
beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
break;
case @"TimelineZoom":
beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
}
}
private void handleMetadata(string line)
{
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
{
case @"Title":
metadata.Title = pair.Value;
break;
case @"TitleUnicode":
metadata.TitleUnicode = pair.Value;
break;
case @"Artist":
metadata.Artist = pair.Value;
break;
case @"ArtistUnicode":
metadata.ArtistUnicode = pair.Value;
break;
case @"Creator":
metadata.AuthorString = pair.Value;
break;
case @"Version":
beatmap.BeatmapInfo.Version = pair.Value;
break;
case @"Source":
beatmap.BeatmapInfo.Metadata.Source = pair.Value;
break;
case @"Tags":
beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
break;
case @"BeatmapID":
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
break;
case @"BeatmapSetID":
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
break;
}
}
private void handleDifficulty(string line)
{
var pair = splitKeyVal(line, ':');
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
{
case @"HPDrainRate":
difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"CircleSize":
difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"OverallDifficulty":
difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"ApproachRate":
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderMultiplier":
difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderTickRate":
difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
}
}
private void handleEvents(string line)
{
DecodeVariables(ref line);
string[] split = line.Split(',');
EventType type;
if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}");
switch (type)
{
case EventType.Background:
string filename = split[2].Trim('"');
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
break;
case EventType.Break:
var breakEvent = new BreakPeriod
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
};
if (!breakEvent.HasEffect)
return;
beatmap.Breaks.Add(breakEvent);
break;
}
}
private void handleTimingPoints(string line)
{
string[] split = line.Split(',');
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
if (split.Length >= 3)
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
LegacySampleBank sampleSet = defaultSampleBank;
if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]);
//SampleBank sampleBank = SampleBank.Default;
//if (split.Length >= 5)
// sampleBank = (SampleBank)int.Parse(split[4]);
int sampleVolume = defaultSampleVolume;
if (split.Length >= 6)
sampleVolume = int.Parse(split[5]);
bool timingChange = true;
if (split.Length >= 7)
timingChange = split[6][0] == '1';
bool kiaiMode = false;
bool omitFirstBarSignature = false;
if (split.Length >= 8)
{
int effectFlags = int.Parse(split[7]);
kiaiMode = (effectFlags & 1) > 0;
omitFirstBarSignature = (effectFlags & 8) > 0;
}
string stringSampleSet = sampleSet.ToString().ToLower();
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange)
{
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
TimeSignature = timeSignature
});
}
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
{
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{
Time = time,
SpeedMultiplier = speedMultiplier
});
}
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
{
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
{
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
{
Time = time,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
}
}
private void handleColours(string line)
{
var pair = splitKeyVal(line, ':');
string[] split = pair.Value.Split(',');
if (split.Length != 3)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
byte r, g, b;
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
if (!hasCustomColours)
{
beatmap.ComboColors.Clear();
hasCustomColours = true;
}
// Note: the combo index specified in the beatmap is discarded
if (pair.Key.StartsWith(@"Combo"))
{
beatmap.ComboColors.Add(new Color4
{
R = r / 255f,
G = g / 255f,
B = b / 255f,
A = 1f,
});
}
}
private void handleHitObjects(string line)
{
// If the ruleset wasn't specified, assume the osu!standard ruleset.
if (parser == null)
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
var obj = parser.Parse(line);
if (obj != null)
beatmap.HitObjects.Add(obj);
}
private void handleVariables(string line)
{
var pair = splitKeyVal(line, '=');
Variables[pair.Key] = pair.Value;
}
private KeyValuePair<string, string> splitKeyVal(string line, char separator)
{
var split = line.Trim().Split(new[] { separator }, 2);
return new KeyValuePair<string, string>
(
split[0].Trim(),
split.Length > 1 ? split[1].Trim() : string.Empty
);
}
}
}

View File

@ -0,0 +1,163 @@
// Copyright (c) 2007-2017 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.IO;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps.Formats
{
public abstract class LegacyDecoder : Decoder
{
public static void Register()
{
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v14");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v13");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v12");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v11");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v10");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v9");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v8");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v7");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v6");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v5");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v4");
AddDecoder<LegacyBeatmapDecoder>(@"osu file format v3");
// TODO: differences between versions
}
protected int BeatmapVersion;
protected readonly Dictionary<string, string> Variables = new Dictionary<string, string>();
public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion);
public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream));
protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
{
throw new NotImplementedException();
}
protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
{
throw new NotImplementedException();
}
protected void ParseContent(StreamReader stream)
{
Section section = Section.None;
string line;
while ((line = stream.ReadLine()) != null)
{
if (ShouldSkipLine(line))
continue;
// It's already set in ParseBeatmap... why do it again?
//if (line.StartsWith(@"osu file format v"))
//{
// Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
// continue;
//}
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
{
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
throw new InvalidDataException($@"Unknown osu section {line}");
continue;
}
ProcessSection(section, line);
}
}
protected virtual bool ShouldSkipLine(string line)
{
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
return true;
return false;
}
protected abstract void ProcessSection(Section section, string line);
/// <summary>
/// Decodes any beatmap variables present in a line into their real values.
/// </summary>
/// <param name="line">The line which may contains variables.</param>
protected void DecodeVariables(ref string line)
{
while (line.IndexOf('$') >= 0)
{
string origLine = line;
string[] split = line.Split(',');
for (int i = 0; i < split.Length; i++)
{
var item = split[i];
if (item.StartsWith("$") && Variables.ContainsKey(item))
split[i] = Variables[item];
}
line = string.Join(",", split);
if (line == origLine)
break;
}
}
protected enum Section
{
None,
General,
Editor,
Metadata,
Difficulty,
Events,
TimingPoints,
Colours,
HitObjects,
Variables,
}
internal enum LegacySampleBank
{
None = 0,
Normal = 1,
Soft = 2,
Drum = 3
}
internal enum EventType
{
Background = 0,
Video = 1,
Break = 2,
Colour = 3,
Sprite = 4,
Sample = 5,
Animation = 6
}
internal enum LegacyOrigins
{
TopLeft,
Centre,
CentreLeft,
TopRight,
BottomCentre,
TopCentre,
Custom,
CentreRight,
BottomLeft,
BottomRight
};
internal enum StoryLayer
{
Background = 0,
Fail = 1,
Pass = 2,
Foreground = 3
}
}
}

View File

@ -0,0 +1,271 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Globalization;
using System.IO;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.IO.File;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps.Formats
{
public class LegacyStoryboardDecoder : LegacyDecoder
{
private Storyboard storyboard;
private StoryboardSprite storyboardSprite;
private CommandTimelineGroup timelineGroup;
public LegacyStoryboardDecoder()
{
}
public LegacyStoryboardDecoder(int beatmapVersion)
{
BeatmapVersion = beatmapVersion;
}
protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
if (storyboard == null)
throw new ArgumentNullException(nameof(storyboard));
this.storyboard = storyboard;
ParseContent(stream);
}
protected override void ProcessSection(Section section, string line)
{
switch (section)
{
case Section.Events:
handleEvents(line);
break;
}
}
private void handleEvents(string line)
{
var depth = 0;
while (line.StartsWith(" ") || line.StartsWith("_"))
{
++depth;
line = line.Substring(1);
}
DecodeVariables(ref line);
string[] split = line.Split(',');
if (depth == 0)
{
storyboardSprite = null;
EventType type;
if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}");
switch (type)
{
case EventType.Sprite:
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
storyboard.GetLayer(layer).Add(storyboardSprite);
}
break;
case EventType.Animation:
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
var frameCount = int.Parse(split[6]);
var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
storyboard.GetLayer(layer).Add(storyboardSprite);
}
break;
case EventType.Sample:
{
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
var layer = parseLayer(split[2]);
var path = cleanFilename(split[3]);
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
}
break;
}
}
else
{
if (depth < 2)
timelineGroup = storyboardSprite?.TimelineGroup;
var commandType = split[0];
switch (commandType)
{
case "T":
{
var triggerName = split[1];
var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
}
break;
case "L":
{
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
var loopCount = int.Parse(split[2]);
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
}
break;
default:
{
if (string.IsNullOrEmpty(split[3]))
split[3] = split[2];
var easing = (Easing)int.Parse(split[1]);
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
switch (commandType)
{
case "F":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "S":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
}
break;
case "V":
{
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
}
break;
case "R":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
}
break;
case "M":
{
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
}
break;
case "MX":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "MY":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "C":
{
var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
timelineGroup?.Colour.Add(easing, startTime, endTime,
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
}
break;
case "P":
{
var type = split[4];
switch (type)
{
case "A":
timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit);
break;
case "H":
timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
break;
case "V":
timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
break;
}
}
break;
default:
throw new InvalidDataException($@"Unknown command type: {commandType}");
}
}
break;
}
}
}
private string parseLayer(string value) => Enum.Parse(typeof(StoryLayer), value).ToString();
private Anchor parseOrigin(string value)
{
var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
switch (origin)
{
case LegacyOrigins.TopLeft:
return Anchor.TopLeft;
case LegacyOrigins.TopCentre:
return Anchor.TopCentre;
case LegacyOrigins.TopRight:
return Anchor.TopRight;
case LegacyOrigins.CentreLeft:
return Anchor.CentreLeft;
case LegacyOrigins.Centre:
return Anchor.Centre;
case LegacyOrigins.CentreRight:
return Anchor.CentreRight;
case LegacyOrigins.BottomLeft:
return Anchor.BottomLeft;
case LegacyOrigins.BottomCentre:
return Anchor.BottomCentre;
case LegacyOrigins.BottomRight:
return Anchor.BottomRight;
}
throw new InvalidDataException($@"Unknown origin: {value}");
}
private string cleanFilename(string path) => FileSafety.PathStandardise(path.Trim('\"'));
}
}

View File

@ -1,781 +0,0 @@
// Copyright (c) 2007-2017 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.Globalization;
using System.IO;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Storyboards;
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.IO.File;
namespace osu.Game.Beatmaps.Formats
{
public class OsuLegacyDecoder : BeatmapDecoder
{
public static void Register()
{
AddDecoder<OsuLegacyDecoder>(@"osu file format v14");
AddDecoder<OsuLegacyDecoder>(@"osu file format v13");
AddDecoder<OsuLegacyDecoder>(@"osu file format v12");
AddDecoder<OsuLegacyDecoder>(@"osu file format v11");
AddDecoder<OsuLegacyDecoder>(@"osu file format v10");
AddDecoder<OsuLegacyDecoder>(@"osu file format v9");
AddDecoder<OsuLegacyDecoder>(@"osu file format v8");
AddDecoder<OsuLegacyDecoder>(@"osu file format v7");
AddDecoder<OsuLegacyDecoder>(@"osu file format v6");
AddDecoder<OsuLegacyDecoder>(@"osu file format v5");
AddDecoder<OsuLegacyDecoder>(@"osu file format v4");
AddDecoder<OsuLegacyDecoder>(@"osu file format v3");
// TODO: differences between versions
}
private ConvertHitObjectParser parser;
private readonly Dictionary<string, string> variables = new Dictionary<string, string>();
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
private readonly int beatmapVersion;
public OsuLegacyDecoder()
{
}
public OsuLegacyDecoder(string header)
{
beatmapVersion = int.Parse(header.Substring(17));
}
private enum Section
{
None,
General,
Editor,
Metadata,
Difficulty,
Events,
TimingPoints,
Colours,
HitObjects,
Variables,
}
private void handleGeneral(Beatmap beatmap, string line)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
{
case @"AudioFilename":
metadata.AudioFile = pair.Value;
break;
case @"AudioLeadIn":
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
break;
case @"PreviewTime":
metadata.PreviewTime = int.Parse(pair.Value);
break;
case @"Countdown":
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
break;
case @"SampleSet":
defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value);
break;
case @"SampleVolume":
defaultSampleVolume = int.Parse(pair.Value);
break;
case @"StackLeniency":
beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"Mode":
beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value);
switch (beatmap.BeatmapInfo.RulesetID)
{
case 0:
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
break;
case 1:
parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser();
break;
case 2:
parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser();
break;
case 3:
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
break;
}
break;
case @"LetterboxInBreaks":
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
break;
case @"SpecialStyle":
beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1;
break;
case @"WidescreenStoryboard":
beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1;
break;
}
}
private void handleEditor(Beatmap beatmap, string line)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, ':');
switch (pair.Key)
{
case @"Bookmarks":
beatmap.BeatmapInfo.StoredBookmarks = pair.Value;
break;
case @"DistanceSpacing":
beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"BeatDivisor":
beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value);
break;
case @"GridSize":
beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value);
break;
case @"TimelineZoom":
beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
}
}
private void handleMetadata(Beatmap beatmap, string line)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, ':');
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
{
case @"Title":
metadata.Title = pair.Value;
break;
case @"TitleUnicode":
metadata.TitleUnicode = pair.Value;
break;
case @"Artist":
metadata.Artist = pair.Value;
break;
case @"ArtistUnicode":
metadata.ArtistUnicode = pair.Value;
break;
case @"Creator":
metadata.AuthorString = pair.Value;
break;
case @"Version":
beatmap.BeatmapInfo.Version = pair.Value;
break;
case @"Source":
beatmap.BeatmapInfo.Metadata.Source = pair.Value;
break;
case @"Tags":
beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
break;
case @"BeatmapID":
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
break;
case @"BeatmapSetID":
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
break;
}
}
private void handleDifficulty(Beatmap beatmap, string line)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, ':');
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
{
case @"HPDrainRate":
difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"CircleSize":
difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"OverallDifficulty":
difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"ApproachRate":
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderMultiplier":
difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderTickRate":
difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
}
}
/// <summary>
/// Decodes any beatmap variables present in a line into their real values.
/// </summary>
/// <param name="line">The line which may contains variables.</param>
private void decodeVariables(ref string line)
{
if (line == null)
throw new ArgumentNullException(nameof(line));
while (line.IndexOf('$') >= 0)
{
string origLine = line;
string[] split = line.Split(',');
for (int i = 0; i < split.Length; i++)
{
var item = split[i];
if (item.StartsWith("$") && variables.ContainsKey(item))
split[i] = variables[item];
}
line = string.Join(",", split);
if (line == origLine) break;
}
}
private void handleEvents(Beatmap beatmap, string line, ref StoryboardSprite storyboardSprite, ref CommandTimelineGroup timelineGroup)
{
if (line == null)
throw new ArgumentNullException(nameof(line));
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
var depth = 0;
while (line.StartsWith(" ") || line.StartsWith("_"))
{
++depth;
line = line.Substring(1);
}
decodeVariables(ref line);
string[] split = line.Split(',');
if (depth == 0)
{
storyboardSprite = null;
EventType type;
if (!Enum.TryParse(split[0], out type))
throw new InvalidDataException($@"Unknown event type {split[0]}");
switch (type)
{
case EventType.Video:
case EventType.Background:
string filename = split[2].Trim('"');
if (type == EventType.Background)
beatmap.BeatmapInfo.Metadata.BackgroundFile = filename;
break;
case EventType.Break:
var breakEvent = new BreakPeriod
{
StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
};
if (!breakEvent.HasEffect)
return;
beatmap.Breaks.Add(breakEvent);
break;
case EventType.Sprite:
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
}
break;
case EventType.Animation:
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
var frameCount = int.Parse(split[6]);
var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
beatmap.Storyboard.GetLayer(layer).Add(storyboardSprite);
}
break;
case EventType.Sample:
{
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
var layer = parseLayer(split[2]);
var path = cleanFilename(split[3]);
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
beatmap.Storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
}
break;
}
}
else
{
if (depth < 2)
timelineGroup = storyboardSprite?.TimelineGroup;
var commandType = split[0];
switch (commandType)
{
case "T":
{
var triggerName = split[1];
var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
}
break;
case "L":
{
var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
var loopCount = int.Parse(split[2]);
timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
}
break;
default:
{
if (string.IsNullOrEmpty(split[3]))
split[3] = split[2];
var easing = (Easing)int.Parse(split[1]);
var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
switch (commandType)
{
case "F":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "S":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
}
break;
case "V":
{
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
}
break;
case "R":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
}
break;
case "M":
{
var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
}
break;
case "MX":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "MY":
{
var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
}
break;
case "C":
{
var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
timelineGroup?.Colour.Add(easing, startTime, endTime,
new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
}
break;
case "P":
{
var type = split[4];
switch (type)
{
case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break;
case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break;
case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break;
}
}
break;
default:
throw new InvalidDataException($@"Unknown command type: {commandType}");
}
}
break;
}
}
}
private static string cleanFilename(string path)
=> FileSafety.PathStandardise(path.Trim('\"'));
private static Anchor parseOrigin(string value)
{
var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value);
switch (origin)
{
case LegacyOrigins.TopLeft: return Anchor.TopLeft;
case LegacyOrigins.TopCentre: return Anchor.TopCentre;
case LegacyOrigins.TopRight: return Anchor.TopRight;
case LegacyOrigins.CentreLeft: return Anchor.CentreLeft;
case LegacyOrigins.Centre: return Anchor.Centre;
case LegacyOrigins.CentreRight: return Anchor.CentreRight;
case LegacyOrigins.BottomLeft: return Anchor.BottomLeft;
case LegacyOrigins.BottomCentre: return Anchor.BottomCentre;
case LegacyOrigins.BottomRight: return Anchor.BottomRight;
}
throw new InvalidDataException($@"Unknown origin: {value}");
}
private static string parseLayer(string value)
=> Enum.Parse(typeof(StoryLayer), value).ToString();
private void handleTimingPoints(Beatmap beatmap, string line)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
string[] split = line.Split(',');
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
if (split.Length >= 3)
timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]);
LegacySampleBank sampleSet = defaultSampleBank;
if (split.Length >= 4)
sampleSet = (LegacySampleBank)int.Parse(split[3]);
//SampleBank sampleBank = SampleBank.Default;
//if (split.Length >= 5)
// sampleBank = (SampleBank)int.Parse(split[4]);
int sampleVolume = defaultSampleVolume;
if (split.Length >= 6)
sampleVolume = int.Parse(split[5]);
bool timingChange = true;
if (split.Length >= 7)
timingChange = split[6][0] == '1';
bool kiaiMode = false;
bool omitFirstBarSignature = false;
if (split.Length >= 8)
{
int effectFlags = int.Parse(split[7]);
kiaiMode = (effectFlags & 1) > 0;
omitFirstBarSignature = (effectFlags & 8) > 0;
}
string stringSampleSet = sampleSet.ToString().ToLower();
if (stringSampleSet == @"none")
stringSampleSet = @"normal";
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time);
SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
if (timingChange)
{
beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint
{
Time = time,
BeatLength = beatLength,
TimeSignature = timeSignature
});
}
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
{
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{
Time = time,
SpeedMultiplier = speedMultiplier
});
}
if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume)
{
beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint
{
Time = time,
SampleBank = stringSampleSet,
SampleVolume = sampleVolume
});
}
if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine)
{
beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint
{
Time = time,
KiaiMode = kiaiMode,
OmitFirstBarLine = omitFirstBarSignature
});
}
}
private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, ':');
string[] split = pair.Value.Split(',');
if (split.Length != 3)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
byte r, g, b;
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
if (!hasCustomColours)
{
beatmap.ComboColors.Clear();
hasCustomColours = true;
}
// Note: the combo index specified in the beatmap is discarded
if (pair.Key.StartsWith(@"Combo"))
{
beatmap.ComboColors.Add(new Color4
{
R = r / 255f,
G = g / 255f,
B = b / 255f,
A = 1f,
});
}
}
private void handleVariables(string line)
{
if (line == null)
throw new ArgumentNullException(nameof(line));
var pair = splitKeyVal(line, '=');
variables[pair.Key] = pair.Value;
}
protected override Beatmap ParseFile(StreamReader stream)
{
return new LegacyBeatmap(base.ParseFile(stream));
}
public override Beatmap Decode(StreamReader stream)
{
return new LegacyBeatmap(base.Decode(stream));
}
protected override void ParseFile(StreamReader stream, Beatmap beatmap)
{
if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap));
if (stream == null)
throw new ArgumentNullException(nameof(stream));
beatmap.BeatmapInfo.BeatmapVersion = beatmapVersion;
Section section = Section.None;
bool hasCustomColours = false;
StoryboardSprite storyboardSprite = null;
CommandTimelineGroup timelineGroup = null;
string line;
while ((line = stream.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
continue;
if (line.StartsWith("//"))
continue;
if (line.StartsWith(@"osu file format v"))
{
beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
continue;
}
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
{
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
throw new InvalidDataException($@"Unknown osu section {line}");
continue;
}
switch (section)
{
case Section.General:
handleGeneral(beatmap, line);
break;
case Section.Editor:
handleEditor(beatmap, line);
break;
case Section.Metadata:
handleMetadata(beatmap, line);
break;
case Section.Difficulty:
handleDifficulty(beatmap, line);
break;
case Section.Events:
handleEvents(beatmap, line, ref storyboardSprite, ref timelineGroup);
break;
case Section.TimingPoints:
handleTimingPoints(beatmap, line);
break;
case Section.Colours:
handleColours(beatmap, line, ref hasCustomColours);
break;
case Section.HitObjects:
// If the ruleset wasn't specified, assume the osu!standard ruleset.
if (parser == null)
parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser();
var obj = parser.Parse(line);
if (obj != null)
beatmap.HitObjects.Add(obj);
break;
case Section.Variables:
handleVariables(line);
break;
}
}
foreach (var hitObject in beatmap.HitObjects)
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
}
private KeyValuePair<string, string> splitKeyVal(string line, char separator)
{
if (line == null)
throw new ArgumentNullException(nameof(line));
var split = line.Trim().Split(new[] { separator }, 2);
return new KeyValuePair<string, string>
(
split[0].Trim(),
split.Length > 1 ? split[1].Trim() : string.Empty
);
}
internal enum LegacySampleBank
{
None = 0,
Normal = 1,
Soft = 2,
Drum = 3
}
internal enum EventType
{
Background = 0,
Video = 1,
Break = 2,
Colour = 3,
Sprite = 4,
Sample = 5,
Animation = 6
}
internal enum LegacyOrigins
{
TopLeft,
Centre,
CentreLeft,
TopRight,
BottomCentre,
TopCentre,
Custom,
CentreRight,
BottomLeft,
BottomRight
};
internal enum StoryLayer
{
Background = 0,
Fail = 1,
Pass = 2,
Foreground = 3
}
}
}

View File

@ -9,6 +9,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -34,12 +35,14 @@ namespace osu.Game.Beatmaps
background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed); background = new AsyncLazy<Texture>(populateBackground, b => b == null || !b.IsDisposed);
track = new AsyncLazy<Track>(populateTrack); track = new AsyncLazy<Track>(populateTrack);
waveform = new AsyncLazy<Waveform>(populateWaveform); waveform = new AsyncLazy<Waveform>(populateWaveform);
storyboard = new AsyncLazy<Storyboard>(populateStoryboard);
} }
protected abstract Beatmap GetBeatmap(); protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground(); protected abstract Texture GetBackground();
protected abstract Track GetTrack(); protected abstract Track GetTrack();
protected virtual Waveform GetWaveform() => new Waveform(); protected virtual Waveform GetWaveform() => new Waveform();
protected virtual Storyboard GetStoryboard() => new Storyboard();
public bool BeatmapLoaded => beatmap.IsResultAvailable; public bool BeatmapLoaded => beatmap.IsResultAvailable;
public Beatmap Beatmap => beatmap.Value.Result; public Beatmap Beatmap => beatmap.Value.Result;
@ -84,6 +87,13 @@ namespace osu.Game.Beatmaps
private Waveform populateWaveform() => GetWaveform(); private Waveform populateWaveform() => GetWaveform();
public bool StoryboardLoaded => storyboard.IsResultAvailable;
public Storyboard Storyboard => storyboard.Value.Result;
public async Task<Storyboard> GetStoryboardAsync() => await storyboard.Value;
private readonly AsyncLazy<Storyboard> storyboard;
private Storyboard populateStoryboard() => GetStoryboard();
public void TransferTo(WorkingBeatmap other) public void TransferTo(WorkingBeatmap other)
{ {
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
@ -97,6 +107,7 @@ namespace osu.Game.Beatmaps
{ {
if (BackgroundLoaded) Background?.Dispose(); if (BackgroundLoaded) Background?.Dispose();
if (WaveformLoaded) Waveform?.Dispose(); if (WaveformLoaded) Waveform?.Dispose();
if (StoryboardLoaded) Storyboard?.Dispose();
} }
/// <summary> /// <summary>

View File

@ -20,7 +20,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); Set(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
@ -108,7 +108,7 @@ namespace osu.Game.Configuration
SaveUsername, SaveUsername,
DisplayStarsMinimum, DisplayStarsMinimum,
DisplayStarsMaximum, DisplayStarsMaximum,
SelectionRandomType, RandomSelectAlgorithm,
SnakingInSliders, SnakingInSliders,
SnakingOutSliders, SnakingOutSliders,
ShowFpsDisplay, ShowFpsDisplay,

View File

@ -5,11 +5,11 @@ using System.ComponentModel;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
public enum SelectionRandomType public enum RandomSelectAlgorithm
{ {
[Description("Never repeat")] [Description("Never repeat")]
RandomPermutation, RandomPermutation,
[Description("Random")] [Description("Random")]
Random Random
} }
} }

View File

@ -93,6 +93,7 @@ namespace osu.Game.Database
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount); modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.Available); modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.Available);
modelBuilder.Entity<RulesetInfo>().HasIndex(b => b.ShortName).IsUnique();
modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty); modelBuilder.Entity<BeatmapInfo>().HasOne(b => b.BaseDifficulty);
} }

View File

@ -15,14 +15,19 @@ namespace osu.Game.Graphics
{ {
public class SpriteIcon : CompositeDrawable public class SpriteIcon : CompositeDrawable
{ {
private readonly Sprite spriteShadow; private Sprite spriteShadow;
private readonly Sprite spriteMain; private Sprite spriteMain;
private Cached layout = new Cached(); private Cached layout = new Cached();
private readonly Container shadowVisibility; private Container shadowVisibility;
public SpriteIcon() private FontStore store;
[BackgroundDependencyLoader]
private void load(FontStore store)
{ {
this.store = store;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
shadowVisibility = new Container shadowVisibility = new Container
@ -39,7 +44,7 @@ namespace osu.Game.Graphics
Y = 2, Y = 2,
Colour = new Color4(0f, 0f, 0f, 0.2f), Colour = new Color4(0f, 0f, 0f, 0.2f),
}, },
Alpha = 0, Alpha = shadow ? 1 : 0,
}, },
spriteMain = new Sprite spriteMain = new Sprite
{ {
@ -49,14 +54,7 @@ namespace osu.Game.Graphics
FillMode = FillMode.Fit FillMode = FillMode.Fit
}, },
}; };
}
private FontStore store;
[BackgroundDependencyLoader]
private void load(FontStore store)
{
this.store = store;
updateTexture(); updateTexture();
} }
@ -105,12 +103,15 @@ namespace osu.Game.Graphics
} }
} }
private bool shadow;
public bool Shadow public bool Shadow
{ {
get { return spriteShadow.IsPresent; } get { return shadow; }
set set
{ {
shadowVisibility.Alpha = value ? 1 : 0; shadow = value;
if (shadowVisibility != null)
shadowVisibility.Alpha = value ? 1 : 0;
} }
} }

View File

@ -12,6 +12,8 @@ using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Framework.Configuration;
using osu.Framework.Input;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -22,62 +24,7 @@ namespace osu.Game.Graphics.UserInterface
private const float glow_fade_duration = 250; private const float glow_fade_duration = 250;
private const float click_duration = 200; private const float click_duration = 200;
private Color4 buttonColour; public readonly BindableBool Selected = new BindableBool();
public Color4 ButtonColour
{
get
{
return buttonColour;
}
set
{
buttonColour = value;
updateGlow();
colourContainer.Colour = value;
}
}
private Color4 backgroundColour = OsuColour.Gray(34);
public Color4 BackgroundColour
{
get
{
return backgroundColour;
}
set
{
backgroundColour = value;
background.Colour = value;
}
}
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
spriteText.Text = Text;
}
}
private float textSize = 28;
public float TextSize
{
get
{
return textSize;
}
set
{
textSize = value;
spriteText.TextSize = value;
}
}
private readonly Container backgroundContainer; private readonly Container backgroundContainer;
private readonly Container colourContainer; private readonly Container colourContainer;
@ -89,71 +36,6 @@ namespace osu.Game.Graphics.UserInterface
private readonly SpriteText spriteText; private readonly SpriteText spriteText;
private Vector2 hoverSpacing => new Vector2(3f, 0f); private Vector2 hoverSpacing => new Vector2(3f, 0f);
private bool didClick; // Used for making sure that the OnMouseDown animation can call instead of OnHoverLost's when clicking
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos);
protected override bool OnClick(Framework.Input.InputState state)
{
didClick = true;
colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In);
flash();
this.Delay(click_duration).Schedule(delegate
{
colourContainer.ResizeTo(new Vector2(0.8f, 1f));
spriteText.Spacing = Vector2.Zero;
glowContainer.FadeOut();
});
return base.OnClick(state);
}
protected override bool OnHover(Framework.Input.InputState state)
{
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic);
glowContainer.FadeIn(glow_fade_duration, Easing.Out);
base.OnHover(state);
return true;
}
protected override void OnHoverLost(Framework.Input.InputState state)
{
if (!didClick)
{
colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic);
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
glowContainer.FadeOut(glow_fade_duration, Easing.Out);
}
didClick = false;
}
private void flash()
{
var flash = new Box
{
RelativeSizeAxes = Axes.Both
};
colourContainer.Add(flash);
flash.Colour = ButtonColour;
flash.Blending = BlendingMode.Additive;
flash.Alpha = 0.3f;
flash.FadeOutFromOne(click_duration);
flash.Expire();
}
private void updateGlow()
{
leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour);
centerGlow.Colour = ButtonColour;
rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f));
}
public DialogButton() public DialogButton()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -268,6 +150,135 @@ namespace osu.Game.Graphics.UserInterface
}; };
updateGlow(); updateGlow();
Selected.ValueChanged += selectionChanged;
}
private Color4 buttonColour;
public Color4 ButtonColour
{
get
{
return buttonColour;
}
set
{
buttonColour = value;
updateGlow();
colourContainer.Colour = value;
}
}
private Color4 backgroundColour = OsuColour.Gray(34);
public Color4 BackgroundColour
{
get
{
return backgroundColour;
}
set
{
backgroundColour = value;
background.Colour = value;
}
}
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
spriteText.Text = Text;
}
}
private float textSize = 28;
public float TextSize
{
get
{
return textSize;
}
set
{
textSize = value;
spriteText.TextSize = value;
}
}
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceiveMouseInputAt(screenSpacePos);
protected override bool OnClick(InputState state)
{
colourContainer.ResizeTo(new Vector2(1.5f, 1f), click_duration, Easing.In);
flash();
this.Delay(click_duration).Schedule(delegate
{
colourContainer.ResizeTo(new Vector2(0.8f, 1f));
spriteText.Spacing = Vector2.Zero;
glowContainer.FadeOut();
});
return base.OnClick(state);
}
protected override bool OnHover(InputState state)
{
base.OnHover(state);
Selected.Value = true;
return true;
}
protected override void OnHoverLost(InputState state)
{
base.OnHoverLost(state);
Selected.Value = false;
}
private void selectionChanged(bool isSelected)
{
if (isSelected)
{
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic);
colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic);
glowContainer.FadeIn(glow_fade_duration, Easing.Out);
}
else
{
colourContainer.ResizeTo(new Vector2(0.8f, 1f), hover_duration, Easing.OutElastic);
spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
glowContainer.FadeOut(glow_fade_duration, Easing.Out);
}
}
private void flash()
{
var flash = new Box
{
RelativeSizeAxes = Axes.Both
};
colourContainer.Add(flash);
flash.Colour = ButtonColour;
flash.Blending = BlendingMode.Additive;
flash.Alpha = 0.3f;
flash.FadeOutFromOne(click_duration);
flash.Expire();
}
private void updateGlow()
{
leftGlow.Colour = ColourInfo.GradientHorizontal(new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f), ButtonColour);
centerGlow.Colour = ButtonColour;
rightGlow.Colour = ColourInfo.GradientHorizontal(ButtonColour, new Color4(ButtonColour.R, ButtonColour.G, ButtonColour.B, 0f));
} }
} }
} }

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using System; using System;
using System.Linq;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -72,16 +73,9 @@ namespace osu.Game.Graphics.UserInterface
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(star_spacing), Spacing = new Vector2(star_spacing),
ChildrenEnumerable = Enumerable.Range(0, StarCount).Select(i => new Star { Alpha = minStarAlpha })
} }
}; };
for (int i = 0; i < StarCount; i++)
{
stars.Add(new Star
{
Alpha = minStarAlpha,
});
}
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -147,15 +141,12 @@ namespace osu.Game.Graphics.UserInterface
{ {
Size = new Vector2(star_size); Size = new Vector2(star_size);
Children = new[] Child = Icon = new SpriteIcon
{ {
Icon = new SpriteIcon Size = new Vector2(star_size),
{ Icon = FontAwesome.fa_star,
Size = new Vector2(star_size), Anchor = Anchor.Centre,
Icon = FontAwesome.fa_star, Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}; };
} }
} }

View File

@ -14,7 +14,7 @@ namespace osu.Game.Input.Bindings
/// A KeyBindingInputManager with a database backing for custom overrides. /// A KeyBindingInputManager with a database backing for custom overrides.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the custom action.</typeparam> /// <typeparam name="T">The type of the custom action.</typeparam>
public abstract class DatabasedKeyBindingInputManager<T> : KeyBindingInputManager<T> public class DatabasedKeyBindingInputManager<T> : KeyBindingContainer<T>
where T : struct where T : struct
{ {
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;
@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param> /// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param> /// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
/// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param> /// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param>
protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
: base(simultaneousMode) : base(simultaneousMode)
{ {
this.ruleset = ruleset; this.ruleset = ruleset;

View File

@ -27,7 +27,7 @@ namespace osu.Game.Input
} }
} }
public void Register(KeyBindingInputManager manager) => insertDefaults(manager.DefaultKeyBindings); public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings);
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null) private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
{ {

View File

@ -0,0 +1,307 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database;
using System;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20171209034410_AddRulesetInfoShortName")]
partial class AddRulesetInfoShortName
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
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<float>("SliderMultiplier");
b.Property<float>("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")
.IsUnique();
b.HasIndex("MD5Hash")
.IsUnique();
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.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("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.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");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace osu.Game.Migrations
{
public partial class AddRulesetInfoShortName : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ShortName",
table: "RulesetInfo",
type: "TEXT",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_RulesetInfo_ShortName",
table: "RulesetInfo",
column: "ShortName",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_RulesetInfo_ShortName",
table: "RulesetInfo");
migrationBuilder.DropColumn(
name: "ShortName",
table: "RulesetInfo");
}
}
}

View File

@ -247,10 +247,15 @@ namespace osu.Game.Migrations
b.Property<string>("Name"); b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID"); b.HasKey("ID");
b.HasIndex("Available"); b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo"); b.ToTable("RulesetInfo");
}); });

View File

@ -10,7 +10,7 @@ using System;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetBeatmapSetsResponse : BeatmapMetadata // todo: this is a bit wrong... public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
{ {
[JsonProperty(@"covers")] [JsonProperty(@"covers")]
private BeatmapSetOnlineCovers covers { get; set; } private BeatmapSetOnlineCovers covers { get; set; }
@ -45,7 +45,7 @@ namespace osu.Game.Online.API.Requests
} }
[JsonProperty(@"beatmaps")] [JsonProperty(@"beatmaps")]
private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; } private IEnumerable<APIResponseBeatmap> beatmaps { get; set; }
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{ {
@ -65,11 +65,11 @@ namespace osu.Game.Online.API.Requests
Ranked = ranked, Ranked = ranked,
LastUpdated = lastUpdated, LastUpdated = lastUpdated,
}, },
Beatmaps = beatmaps.Select(b => b.ToBeatmap(rulesets)).ToList(), Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
}; };
} }
private class GetBeatmapSetsBeatmapResponse : BeatmapMetadata private class APIResponseBeatmap : BeatmapMetadata
{ {
[JsonProperty(@"id")] [JsonProperty(@"id")]
private int onlineBeatmapID { get; set; } private int onlineBeatmapID { get; set; }

View File

@ -3,7 +3,7 @@
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetBeatmapSetRequest : APIRequest<GetBeatmapSetsResponse> public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet>
{ {
private readonly int beatmapSetId; private readonly int beatmapSetId;

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserBeatmapsRequest : APIRequest<List<GetBeatmapSetsResponse>> public class GetUserBeatmapsRequest : APIRequest<List<APIResponseBeatmapSet>>
{ {
private readonly long userId; private readonly long userId;
private readonly int offset; private readonly int offset;
@ -24,7 +24,6 @@ namespace osu.Game.Online.API.Requests
public enum BeatmapSetType public enum BeatmapSetType
{ {
MostPlayed,
Favourite, Favourite,
RankedAndApproved, RankedAndApproved,
Unranked, Unranked,

View File

@ -0,0 +1,48 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using System.Collections.Generic;
namespace osu.Game.Online.API.Requests
{
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<MostPlayedBeatmap>>
{
private readonly long userId;
private readonly int offset;
public GetUserMostPlayedBeatmapsRequest(long userId, int offset = 0)
{
this.userId = userId;
this.offset = offset;
}
protected override string Target => $@"users/{userId}/beatmapsets/most_played?offset={offset}";
}
public class MostPlayedBeatmap
{
[JsonProperty("beatmap_id")]
public int BeatmapID;
[JsonProperty("count")]
public int PlayCount;
[JsonProperty]
private BeatmapInfo beatmap;
[JsonProperty]
private APIResponseBeatmapSet beatmapSet;
public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
{
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
beatmap.BeatmapSet = setInfo;
beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
beatmap.Metadata = setInfo.Metadata;
return beatmap;
}
}
}

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<GetBeatmapSetsResponse>> public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIResponseBeatmapSet>>
{ {
private readonly string query; private readonly string query;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;

View File

@ -14,10 +14,10 @@ namespace osu.Game.Overlays.BeatmapSet
public DownloadButton(string title, string subtitle) public DownloadButton(string title, string subtitle)
{ {
Width = 120; Width = 120;
RelativeSizeAxes = Axes.Y;
Child = new Container Add(new Container
{ {
Depth = -1,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 10 }, Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[] Children = new Drawable[]
@ -53,7 +53,7 @@ namespace osu.Game.Overlays.BeatmapSet
Margin = new MarginPadding { Right = 5 }, Margin = new MarginPadding { Right = 5 },
}, },
}, },
}; });
} }
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -15,13 +16,12 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
public readonly Bindable<bool> Favourited = new Bindable<bool>(); public readonly Bindable<bool> Favourited = new Bindable<bool>();
public FavouriteButton() [BackgroundDependencyLoader]
private void load()
{ {
RelativeSizeAxes = Axes.Y;
Container pink; Container pink;
SpriteIcon icon; SpriteIcon icon;
Children = new Drawable[] AddRange(new Drawable[]
{ {
pink = new Container pink = new Container
{ {
@ -51,7 +51,7 @@ namespace osu.Game.Overlays.BeatmapSet
Size = new Vector2(18), Size = new Vector2(18),
Shadow = false, Shadow = false,
}, },
}; });
Favourited.ValueChanged += value => Favourited.ValueChanged += value =>
{ {

View File

@ -2,44 +2,27 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Containers; using osu.Framework.Allocation;
namespace osu.Game.Overlays.BeatmapSet namespace osu.Game.Overlays.BeatmapSet
{ {
public class HeaderButton : OsuClickableContainer public class HeaderButton : TriangleButton
{ {
private readonly Container content;
protected override Container<Drawable> Content => content;
public HeaderButton() public HeaderButton()
{ {
CornerRadius = 3; Height = 0;
Masking = true; RelativeSizeAxes = Axes.Y;
}
InternalChildren = new Drawable[] [BackgroundDependencyLoader]
{ private void load()
new Box {
{ BackgroundColour = OsuColour.FromHex(@"094c5f");
RelativeSizeAxes = Axes.Both, Triangles.ColourLight = OsuColour.FromHex(@"0f7c9b");
Colour = OsuColour.FromHex(@"094c5f"), Triangles.ColourDark = OsuColour.FromHex(@"094c5f");
}, Triangles.TriangleScale = 1.5f;
new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourLight = OsuColour.FromHex(@"0f7c9b"),
ColourDark = OsuColour.FromHex(@"094c5f"),
TriangleScale = 1.5f,
},
content = new Container
{
RelativeSizeAxes = Axes.Both,
},
};
} }
} }
} }

View File

@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Direct
{ {
base.Update(); base.Update();
if (PreviewPlaying && Preview != null) if (PreviewPlaying && Preview != null && Preview.IsLoaded)
{ {
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
} }

View File

@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Direct
public enum DirectSortCriteria public enum DirectSortCriteria
{ {
Relevance,
Title, Title,
Artist, Artist,
Creator, Creator,

View File

@ -43,6 +43,7 @@ namespace osu.Game.Overlays
protected override SearchableListFilterControl<DirectSortCriteria, RankStatus> CreateFilterControl() => new FilterControl(); protected override SearchableListFilterControl<DirectSortCriteria, RankStatus> CreateFilterControl() => new FilterControl();
private IEnumerable<BeatmapSetInfo> beatmapSets; private IEnumerable<BeatmapSetInfo> beatmapSets;
public IEnumerable<BeatmapSetInfo> BeatmapSets public IEnumerable<BeatmapSetInfo> BeatmapSets
{ {
get { return beatmapSets; } get { return beatmapSets; }
@ -69,6 +70,7 @@ namespace osu.Game.Overlays
} }
private ResultCounts resultAmounts; private ResultCounts resultAmounts;
public ResultCounts ResultAmounts public ResultCounts ResultAmounts
{ {
get { return resultAmounts; } get { return resultAmounts; }
@ -115,7 +117,23 @@ namespace osu.Game.Overlays
}, },
}; };
Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; }; Filter.Search.Current.ValueChanged += text =>
{
if (text != string.Empty)
{
Header.Tabs.Current.Value = DirectTab.Search;
if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked)
Filter.Tabs.Current.Value = DirectSortCriteria.Relevance;
}
else
{
Header.Tabs.Current.Value = DirectTab.NewestMaps;
if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance)
Filter.Tabs.Current.Value = DirectSortCriteria.Ranked;
}
};
((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch); ((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch);
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels;
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch); Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch);
@ -271,9 +289,9 @@ namespace osu.Game.Overlays
if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return;
getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty,
((FilterControl)Filter).Ruleset.Value, ((FilterControl)Filter).Ruleset.Value,
Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.DisplayStyleControl.Dropdown.Current.Value,
Filter.Tabs.Current.Value); //todo: sort direction (?) Filter.Tabs.Current.Value); //todo: sort direction (?)
getSetsRequest.Success += response => getSetsRequest.Success += response =>
{ {

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding
public override FontAwesome Icon => FontAwesome.fa_osu_hot; public override FontAwesome Icon => FontAwesome.fa_osu_hot;
public override string Header => "Global"; public override string Header => "Global";
public GlobalKeyBindingsSection(KeyBindingInputManager manager) public GlobalKeyBindingsSection(KeyBindingContainer manager)
{ {
Add(new DefaultBindingsSubsection(manager)); Add(new DefaultBindingsSubsection(manager));
} }
@ -21,7 +21,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
protected override string Header => string.Empty; protected override string Header => string.Empty;
public DefaultBindingsSubsection(KeyBindingInputManager manager) public DefaultBindingsSubsection(KeyBindingContainer manager)
: base(null) : base(null)
{ {
Defaults = manager.DefaultKeyBindings; Defaults = manager.DefaultKeyBindings;

View File

@ -17,22 +17,21 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
public class ModSelectOverlay : WaveOverlayContainer public class ModSelectOverlay : WaveOverlayContainer
{ {
private const int button_duration = 700;
private const int ranked_multiplier_duration = 700;
private const float content_width = 0.8f; private const float content_width = 0.8f;
private Color4 lowMultiplierColour, highMultiplierColour; protected Color4 LowMultiplierColour, HighMultiplierColour;
private readonly OsuSpriteText rankedLabel; protected readonly TriangleButton DeselectAllButton;
private readonly OsuSpriteText multiplierLabel; protected readonly OsuSpriteText MultiplierLabel;
private readonly FillFlowContainer rankedMultiplerContainer; private readonly FillFlowContainer footerContainer;
private readonly FillFlowContainer<ModSection> modSectionsContainer; protected readonly FillFlowContainer<ModSection> ModSectionsContainer;
public readonly Bindable<IEnumerable<Mod>> SelectedMods = new Bindable<IEnumerable<Mod>>(); public readonly Bindable<IEnumerable<Mod>> SelectedMods = new Bindable<IEnumerable<Mod>>();
@ -42,7 +41,7 @@ namespace osu.Game.Overlays.Mods
{ {
var instance = newRuleset.CreateInstance(); var instance = newRuleset.CreateInstance();
foreach (ModSection section in modSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
section.Mods = instance.GetModsFor(section.ModType); section.Mods = instance.GetModsFor(section.ModType);
refreshSelectedMods(); refreshSelectedMods();
} }
@ -50,8 +49,8 @@ namespace osu.Game.Overlays.Mods
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets)
{ {
lowMultiplierColour = colours.Red; LowMultiplierColour = colours.Red;
highMultiplierColour = colours.Green; HighMultiplierColour = colours.Green;
if (osu != null) if (osu != null)
Ruleset.BindTo(osu.Ruleset); Ruleset.BindTo(osu.Ruleset);
@ -66,14 +65,14 @@ namespace osu.Game.Overlays.Mods
{ {
base.PopOut(); base.PopOut();
rankedMultiplerContainer.MoveToX(rankedMultiplerContainer.DrawSize.X, APPEAR_DURATION, Easing.InSine); footerContainer.MoveToX(footerContainer.DrawSize.X, DISAPPEAR_DURATION, Easing.InSine);
rankedMultiplerContainer.FadeOut(APPEAR_DURATION, Easing.InSine); footerContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine);
foreach (ModSection section in modSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
{ {
section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), APPEAR_DURATION, Easing.InSine); section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.MoveToX(100f, APPEAR_DURATION, Easing.InSine); section.ButtonsContainer.MoveToX(100f, DISAPPEAR_DURATION, Easing.InSine);
section.ButtonsContainer.FadeOut(APPEAR_DURATION, Easing.InSine); section.ButtonsContainer.FadeOut(DISAPPEAR_DURATION, Easing.InSine);
} }
} }
@ -81,27 +80,28 @@ namespace osu.Game.Overlays.Mods
{ {
base.PopIn(); base.PopIn();
rankedMultiplerContainer.MoveToX(0, ranked_multiplier_duration, Easing.OutQuint); footerContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint);
rankedMultiplerContainer.FadeIn(ranked_multiplier_duration, Easing.OutQuint); footerContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint);
foreach (ModSection section in modSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
{ {
section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), button_duration, Easing.OutQuint); section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.MoveToX(0, button_duration, Easing.OutQuint); section.ButtonsContainer.MoveToX(0, APPEAR_DURATION, Easing.OutQuint);
section.ButtonsContainer.FadeIn(button_duration, Easing.OutQuint); section.ButtonsContainer.FadeIn(APPEAR_DURATION, Easing.OutQuint);
} }
} }
public void DeselectAll() public void DeselectAll()
{ {
foreach (ModSection section in modSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectAll(); section.DeselectAll();
refreshSelectedMods();
} }
public void DeselectTypes(Type[] modTypes) public void DeselectTypes(Type[] modTypes)
{ {
if (modTypes.Length == 0) return; if (modTypes.Length == 0) return;
foreach (ModSection section in modSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectTypes(modTypes); section.DeselectTypes(modTypes);
} }
@ -114,7 +114,7 @@ namespace osu.Game.Overlays.Mods
private void refreshSelectedMods() private void refreshSelectedMods()
{ {
SelectedMods.Value = modSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray();
double multiplier = 1.0; double multiplier = 1.0;
bool ranked = true; bool ranked = true;
@ -129,16 +129,16 @@ namespace osu.Game.Overlays.Mods
// 1.05x // 1.05x
// 1.20x // 1.20x
multiplierLabel.Text = $"{multiplier:N2}x"; MultiplierLabel.Text = $"{multiplier:N2}x";
string rankedString = ranked ? "Ranked" : "Unranked"; if (!ranked)
rankedLabel.Text = $@"{rankedString}, Score Multiplier: "; MultiplierLabel.Text += " (Unranked)";
if (multiplier > 1.0) if (multiplier > 1.0)
multiplierLabel.FadeColour(highMultiplierColour, 200); MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0) else if (multiplier < 1.0)
multiplierLabel.FadeColour(lowMultiplierColour, 200); MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else else
multiplierLabel.FadeColour(Color4.White, 200); MultiplierLabel.FadeColour(Color4.White, 200);
} }
public ModSelectOverlay() public ModSelectOverlay()
@ -232,7 +232,7 @@ namespace osu.Game.Overlays.Mods
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = @"Others are just for fun", Text = @"Others are just for fun.",
TextSize = 18, TextSize = 18,
Shadow = true, Shadow = true,
}, },
@ -241,7 +241,7 @@ namespace osu.Game.Overlays.Mods
}, },
}, },
// Body // Body
modSectionsContainer = new FillFlowContainer<ModSection> ModSectionsContainer = new FillFlowContainer<ModSection>
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -289,7 +289,7 @@ namespace osu.Game.Overlays.Mods
Colour = new Color4(172, 20, 116, 255), Colour = new Color4(172, 20, 116, 255),
Alpha = 0.5f, Alpha = 0.5f,
}, },
rankedMultiplerContainer = new FillFlowContainer footerContainer = new FillFlowContainer
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
@ -299,26 +299,42 @@ namespace osu.Game.Overlays.Mods
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Padding = new MarginPadding Padding = new MarginPadding
{ {
Top = 20, Vertical = 15
Bottom = 20,
}, },
Children = new Drawable[] Children = new Drawable[]
{ {
rankedLabel = new OsuSpriteText DeselectAllButton = new TriangleButton
{ {
Text = @"Ranked, Score Multiplier: ", Width = 180,
Text = "Deselect All",
Action = DeselectAll,
Margin = new MarginPadding
{
Right = 20
}
},
new OsuSpriteText
{
Text = @"Score Multiplier: ",
TextSize = 30, TextSize = 30,
Shadow = true, Shadow = true,
Margin = new MarginPadding
{
Top = 5
}
}, },
multiplierLabel = new OsuSpriteText MultiplierLabel = new OsuSpriteText
{ {
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
Text = @"1.00x",
TextSize = 30, TextSize = 30,
Shadow = true, Shadow = true,
}, Margin = new MarginPadding
}, {
}, Top = 5
}
}
}
}
}, },
}, },
}, },

View File

@ -251,7 +251,7 @@ namespace osu.Game.Overlays
playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
if (track.HasCompleted && !beatmapBacking.Disabled && playlist.BeatmapSets.Any()) if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && playlist.BeatmapSets.Any())
next(); next();
} }
else else

View File

@ -22,6 +22,8 @@ namespace osu.Game.Overlays.Notifications
} }
} }
public string CompletionText { get; set; } = "Task has completed!";
public float Progress public float Progress
{ {
get { return progressBar.Progress; } get { return progressBar.Progress; }
@ -87,7 +89,7 @@ namespace osu.Game.Overlays.Notifications
protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification protected virtual Notification CreateCompletionNotification() => new ProgressCompletionNotification
{ {
Activated = CompletionClickAction, Activated = CompletionClickAction,
Text = "Task has completed!" Text = CompletionText
}; };
protected virtual void Completed() protected virtual void Completed()

View File

@ -53,6 +53,7 @@ namespace osu.Game.Overlays.Profile
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = cover_height, Height = cover_height,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new Box new Box
@ -324,8 +325,7 @@ namespace osu.Game.Overlays.Profile
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(200), OnLoadComplete = d => d.FadeInFromZero(200),
Depth = float.MaxValue, Depth = float.MaxValue,
}, }, coverContainer.Add);
coverContainer.Add);
if (user.IsSupporter) supporterTag.Show(); if (user.IsSupporter) supporterTag.Show();
@ -382,7 +382,7 @@ namespace osu.Game.Overlays.Profile
} }
tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location);
tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Intrerests); tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests);
tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation);
infoTextRight.NewParagraph(); infoTextRight.NewParagraph();
if (!string.IsNullOrEmpty(user.Twitter)) if (!string.IsNullOrEmpty(user.Twitter))
@ -514,7 +514,7 @@ namespace osu.Game.Overlays.Profile
{ {
set set
{ {
if(value != null) if (value != null)
content.Action = () => Process.Start(value); content.Action = () => Process.Start(value);
} }
} }

View File

@ -81,12 +81,12 @@ namespace osu.Game.Overlays.Profile
{ {
rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank"; rankText.Text = user.Statistics.Rank > 0 ? $"#{user.Statistics.Rank:#,0}" : "no rank";
performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty;
relativeText.Text = $"{user.Country?.FullName} #{user.CountryRank:#,0}"; relativeText.Text = user.CountryRank > 0 ? $"{user.Country?.FullName} #{user.CountryRank:#,0}" : $"{user.Country?.FullName}";
} }
private void showHistoryRankTexts(int dayIndex) private void showHistoryRankTexts(int dayIndex)
{ {
rankText.Text = $"#{ranks[dayIndex]:#,0}"; rankText.Text = ranks[dayIndex] > 0 ? $"#{ranks[dayIndex]:#,0}" : "no rank";
dayIndex++; dayIndex++;
relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago"; relativeText.Text = dayIndex == ranks.Length ? "Now" : $"{ranks.Length - dayIndex} days ago";
//plural should be handled in a general way //plural should be handled in a general way
@ -99,7 +99,7 @@ namespace osu.Game.Overlays.Profile
{ {
graph.Colour = colours.Yellow; graph.Colour = colours.Yellow;
// use logarithmic coordinates // use logarithmic coordinates
graph.Values = ranks.Select(x => -(float)Math.Log(x)); graph.Values = ranks.Select(x => x == 0 ? float.MinValue : -(float)Math.Log(x));
graph.SetStaticBallPosition(); graph.SetStaticBallPosition();
} }

View File

@ -0,0 +1,63 @@
// Copyright (c) 2007-2017 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Profile.Sections
{
/// <summary>
/// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see <see cref="DrawableProfileRow"/>).
/// </summary>
public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip
{
private readonly BeatmapInfo beatmap;
public BeatmapMetadataContainer(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
AutoSizeAxes = Axes.Both;
TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}";
}
public string TooltipText { get; }
[BackgroundDependencyLoader(true)]
private void load(LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay)
{
Action = () =>
{
if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value);
};
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Current = locale.GetUnicodePreference(
$"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
$"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "
),
TextSize = 15,
Font = "Exo2.0-SemiBoldItalic",
},
new OsuSpriteText
{
Current = locale.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
TextSize = 12,
Padding = new MarginPadding { Top = 3 },
Font = "Exo2.0-RegularItalic",
},
},
};
}
}
}

View File

@ -0,0 +1,124 @@
// Copyright (c) 2007-2017 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Profile.Sections
{
public abstract class DrawableProfileRow : Container
{
private const int fade_duration = 200;
private Box underscoreLine;
private readonly Box coloredBackground;
private readonly Container background;
/// <summary>
/// A visual element displayed to the left of <see cref="LeftFlowContainer"/> content.
/// </summary>
protected abstract Drawable CreateLeftVisual();
protected FillFlowContainer LeftFlowContainer { get; private set; }
protected FillFlowContainer RightFlowContainer { get; private set; }
protected override Container<Drawable> Content { get; }
protected DrawableProfileRow()
{
RelativeSizeAxes = Axes.X;
Height = 60;
InternalChildren = new Drawable[]
{
background = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 3,
Alpha = 0,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 1f,
Colour = Color4.Black.Opacity(0.2f),
},
Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
},
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 0.97f,
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour)
{
AddRange(new Drawable[]
{
underscoreLine = new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
CreateLeftVisual(),
LeftFlowContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10 },
Direction = FillDirection.Vertical,
},
}
},
RightFlowContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Vertical,
},
});
coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
}
protected override bool OnClick(InputState state) => true;
protected override bool OnHover(InputState state)
{
background.FadeIn(fade_duration, Easing.OutQuint);
underscoreLine.FadeOut(fade_duration, Easing.OutQuint);
return true;
}
protected override void OnHoverLost(InputState state)
{
background.FadeOut(fade_duration, Easing.OutQuint);
underscoreLine.FadeIn(fade_duration, Easing.OutQuint);
base.OnHoverLost(state);
}
}
}

View File

@ -0,0 +1,105 @@
// Copyright (c) 2007-2017 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.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
public class DrawableMostPlayedRow : DrawableProfileRow
{
private readonly BeatmapInfo beatmap;
private readonly int playCount;
private OsuHoverContainer mapperContainer;
public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount)
{
this.beatmap = beatmap;
this.playCount = playCount;
}
protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint)
})
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.None,
Size = new Vector2(80, 50),
};
[BackgroundDependencyLoader(true)]
private void load(UserProfileOverlay profileOverlay)
{
LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap));
LeftFlowContainer.Add(new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = @"mapped by ",
TextSize = 12,
},
mapperContainer = new OsuHoverContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = beatmap.Metadata.AuthorString,
TextSize = 12,
Font = @"Exo2.0-MediumItalic"
}
}
},
}
});
RightFlowContainer.Add(new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Text = playCount.ToString(),
TextSize = 18,
Font = @"Exo2.0-SemiBoldItalic"
},
new OsuSpriteText
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Text = @"times played ",
TextSize = 12,
Font = @"Exo2.0-RegularItalic"
},
}
});
if (profileOverlay != null)
mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author);
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) 2007-2017 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.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
public class PaginatedMostPlayedBeatmapContainer : PaginatedContainer
{
public PaginatedMostPlayedBeatmapContainer(Bindable<User> user)
:base(user, "Most Played Beatmaps", "No records. :(")
{
ItemsPerPage = 5;
ItemsContainer.Direction = FillDirection.Vertical;
}
protected override void ShowMore()
{
base.ShowMore();
var req = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage);
req.Success += beatmaps =>
{
ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0);
ShowMoreLoading.Hide();
if (!beatmaps.Any() && VisiblePages == 1)
{
MissingText.Show();
return;
}
MissingText.Hide();
foreach (var beatmap in beatmaps)
{
ItemsContainer.Add(new DrawableMostPlayedRow(beatmap.GetBeatmapInfo(Rulesets), beatmap.PlayCount));
}
};
Api.Queue(req);
}
}
}

View File

@ -1,7 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Overlays.Profile.Sections.Ranks;
namespace osu.Game.Overlays.Profile.Sections namespace osu.Game.Overlays.Profile.Sections
@ -14,7 +16,11 @@ namespace osu.Game.Overlays.Profile.Sections
public HistoricalSection() public HistoricalSection()
{ {
Child = new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("); Children = new Drawable[]
{
new PaginatedMostPlayedBeatmapContainer(User),
new PaginatedScoreContainer(ScoreType.Recent, User, "Recent Plays (24h)", "No performance records. :("),
};
} }
} }
} }

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Profile.Sections.Ranks namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
public class DrawablePerformanceScore : DrawableScore public class DrawablePerformanceScore : DrawableProfileScore
{ {
private readonly double? weight; private readonly double? weight;
@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
private void load(OsuColour colour) private void load(OsuColour colour)
{ {
double pp = Score.PP ?? 0; double pp = Score.PP ?? 0;
Stats.Add(new OsuSpriteText RightFlowContainer.Add(new OsuSpriteText
{ {
Text = $"{pp:0}pp", Text = $"{pp:0}pp",
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
if (weight.HasValue) if (weight.HasValue)
{ {
Stats.Add(new OsuSpriteText RightFlowContainer.Add(new OsuSpriteText
{ {
Text = $"weighted: {pp * weight:0}pp ({weight:P0})", Text = $"weighted: {pp * weight:0}pp ({weight:P0})",
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,

View File

@ -0,0 +1,75 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public abstract class DrawableProfileScore : DrawableProfileRow
{
private readonly FillFlowContainer metadata;
private readonly ScoreModsContainer modsContainer;
protected readonly Score Score;
protected DrawableProfileScore(Score score)
{
Score = score;
RelativeSizeAxes = Axes.X;
Height = 60;
Children = new Drawable[]
{
modsContainer = new ScoreModsContainer
{
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Width = 60,
Margin = new MarginPadding { Right = 160 }
}
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour)
{
RightFlowContainer.Add(new OsuSpriteText
{
Text = $"accuracy: {Score.Accuracy:P2}",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = colour.GrayA,
TextSize = 11,
Font = "Exo2.0-RegularItalic",
Depth = -1,
});
LeftFlowContainer.Add(new BeatmapMetadataContainer(Score.Beatmap));
LeftFlowContainer.Add(new OsuSpriteText
{
Text = Score.Date.LocalDateTime.ToShortDateString(),
TextSize = 11,
Colour = OsuColour.Gray(0xAA),
});
foreach (Mod mod in Score.Mods)
modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) });
}
protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank)
{
RelativeSizeAxes = Axes.Y,
Width = 60,
FillMode = FillMode.Fit,
};
}
}

View File

@ -1,195 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.Localisation;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
public abstract class DrawableScore : Container
{
private const int fade_duration = 200;
protected readonly FillFlowContainer<OsuSpriteText> Stats;
private readonly FillFlowContainer metadata;
private readonly ScoreModsContainer modsContainer;
protected readonly Score Score;
private readonly Box underscoreLine;
private readonly Box coloredBackground;
private readonly Container background;
protected DrawableScore(Score score)
{
Score = score;
RelativeSizeAxes = Axes.X;
Height = 60;
Children = new Drawable[]
{
background = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 3,
Alpha = 0,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 1f,
Colour = Color4.Black.Opacity(0.2f),
},
Child = coloredBackground = new Box { RelativeSizeAxes = Axes.Both }
},
new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 0.97f,
Children = new Drawable[]
{
underscoreLine = new Box
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new DrawableRank(score.Rank)
{
RelativeSizeAxes = Axes.Y,
Width = 60,
FillMode = FillMode.Fit,
},
Stats = new FillFlowContainer<OsuSpriteText>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Vertical,
},
metadata = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 70 },
Direction = FillDirection.Vertical,
Child = new OsuSpriteText
{
Text = score.Date.LocalDateTime.ToShortDateString(),
TextSize = 11,
Colour = OsuColour.Gray(0xAA),
Depth = -1,
},
},
modsContainer = new ScoreModsContainer
{
AutoSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Width = 60,
Margin = new MarginPadding { Right = 160 }
}
}
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colour, LocalisationEngine locale, BeatmapSetOverlay beatmapSetOverlay)
{
coloredBackground.Colour = underscoreLine.Colour = colour.Gray4;
Stats.Add(new OsuSpriteText
{
Text = $"accuracy: {Score.Accuracy:P2}",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Colour = colour.GrayA,
TextSize = 11,
Font = "Exo2.0-RegularItalic",
Depth = -1,
});
metadata.Add(new MetadataContainer(Score.Beatmap.Metadata.Title, Score.Beatmap.Metadata.Artist)
{
AutoSizeAxes = Axes.Both,
Action = () =>
{
if (Score.Beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.ShowBeatmapSet(Score.Beatmap.OnlineBeatmapSetID.Value);
},
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new OsuSpriteText
{
Current = locale.GetUnicodePreference(
$"{Score.Beatmap.Metadata.TitleUnicode ?? Score.Beatmap.Metadata.Title} [{Score.Beatmap.Version}] ",
$"{Score.Beatmap.Metadata.Title ?? Score.Beatmap.Metadata.TitleUnicode} [{Score.Beatmap.Version}] "
),
TextSize = 15,
Font = "Exo2.0-SemiBoldItalic",
},
new OsuSpriteText
{
Current = locale.GetUnicodePreference(Score.Beatmap.Metadata.ArtistUnicode, Score.Beatmap.Metadata.Artist),
TextSize = 12,
Padding = new MarginPadding { Top = 3 },
Font = "Exo2.0-RegularItalic",
},
},
},
});
foreach (Mod mod in Score.Mods)
modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) });
}
protected override bool OnClick(InputState state) => true;
protected override bool OnHover(InputState state)
{
background.FadeIn(fade_duration, Easing.OutQuint);
underscoreLine.FadeOut(fade_duration, Easing.OutQuint);
return true;
}
protected override void OnHoverLost(InputState state)
{
background.FadeOut(fade_duration, Easing.OutQuint);
underscoreLine.FadeIn(fade_duration, Easing.OutQuint);
base.OnHoverLost(state);
}
private class MetadataContainer : OsuHoverContainer, IHasTooltip
{
public string TooltipText { get; set; }
public MetadataContainer(string title, string artist)
{
TooltipText = $"{artist} - {title}";
}
}
}
}

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Profile.Sections.Ranks namespace osu.Game.Overlays.Profile.Sections.Ranks
{ {
public class DrawableTotalScore : DrawableScore public class DrawableTotalScore : DrawableProfileScore
{ {
public DrawableTotalScore(Score score) public DrawableTotalScore(Score score)
: base(score) : base(score)
@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Stats.Add(new OsuSpriteText RightFlowContainer.Add(new OsuSpriteText
{ {
Text = Score.TotalScore.ToString("#,###"), Text = Score.TotalScore.ToString("#,###"),
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,

View File

@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
foreach (OnlineScore score in scores) foreach (OnlineScore score in scores)
{ {
DrawableScore drawableScore; DrawableProfileScore drawableScore;
switch (type) switch (type)
{ {

View File

@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum), Bindable = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 1f KeyboardStep = 1f
}, },
new SettingsEnumDropdown<SelectionRandomType> new SettingsEnumDropdown<RandomSelectAlgorithm>
{ {
LabelText = "Random beatmap selection", LabelText = "Random selection algorithm",
Bindable = config.GetBindable<SelectionRandomType>(OsuSetting.SelectionRandomType), Bindable = config.GetBindable<RandomSelectAlgorithm>(OsuSetting.RandomSelectAlgorithm),
} }
}; };
} }

View File

@ -97,6 +97,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue;
} }
} }
public SensitivitySetting()
{
KeyboardStep = 0.01f;
}
} }
private class SensitivitySlider : OsuSliderBar<double> private class SensitivitySlider : OsuSliderBar<double>
@ -105,8 +110,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
public SensitivitySlider() public SensitivitySlider()
{ {
KeyboardStep = 0.01f;
Current.ValueChanged += newValue => Current.ValueChanged += newValue =>
{ {
if (!isDragging && Sensitivity != null) if (!isDragging && Sensitivity != null)
@ -133,4 +136,4 @@ namespace osu.Game.Overlays.Settings.Sections.Input
public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x");
} }
} }
} }

View File

@ -159,8 +159,6 @@ namespace osu.Game.Overlays.Settings
public string TooltipText => "Revert to default"; public string TooltipText => "Revert to default";
public override bool HandleInput => true;
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true; protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => true;

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
@ -77,7 +78,8 @@ namespace osu.Game.Rulesets.Edit
Alpha = 0, Alpha = 0,
AlwaysPresent = true, AlwaysPresent = true,
}, },
rulesetContainer rulesetContainer,
new SelectionLayer(rulesetContainer.Playfield)
} }
} }
}, },

View File

@ -0,0 +1,105 @@
// Copyright (c) 2007-2017 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
/// <summary>
/// Represents a marker visible on the border of a <see cref="HandleContainer"/> which exposes
/// properties that are used to resize a <see cref="HitObjectSelectionBox"/>.
/// </summary>
public class Handle : CompositeDrawable
{
private const float marker_size = 10;
/// <summary>
/// Invoked when this <see cref="Handle"/> requires the current drag rectangle.
/// </summary>
public Func<RectangleF> GetDragRectangle;
/// <summary>
/// Invoked when this <see cref="Handle"/> wants to update the drag rectangle.
/// </summary>
public Action<RectangleF> UpdateDragRectangle;
/// <summary>
/// Invoked when this <see cref="Handle"/> has finished updates to the drag rectangle.
/// </summary>
public Action FinishDrag;
private Color4 normalColour;
private Color4 hoverColour;
public Handle()
{
Size = new Vector2(marker_size);
InternalChild = new CircularContainer
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both }
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = normalColour = colours.Yellow;
hoverColour = colours.YellowDarker;
}
protected override bool OnDragStart(InputState state) => true;
protected override bool OnDrag(InputState state)
{
var currentRectangle = GetDragRectangle();
float left = currentRectangle.Left;
float right = currentRectangle.Right;
float top = currentRectangle.Top;
float bottom = currentRectangle.Bottom;
// Apply modifications to the capture rectangle
if ((Anchor & Anchor.y0) > 0)
top += state.Mouse.Delta.Y;
else if ((Anchor & Anchor.y2) > 0)
bottom += state.Mouse.Delta.Y;
if ((Anchor & Anchor.x0) > 0)
left += state.Mouse.Delta.X;
else if ((Anchor & Anchor.x2) > 0)
right += state.Mouse.Delta.X;
UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom));
return true;
}
protected override bool OnDragEnd(InputState state)
{
FinishDrag();
return true;
}
protected override bool OnHover(InputState state)
{
this.FadeColour(hoverColour, 100);
return true;
}
protected override void OnHoverLost(InputState state)
{
this.FadeColour(normalColour, 100);
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
/// <summary>
/// A <see cref="CompositeDrawable"/> that has <see cref="Handle"/>s around its border.
/// </summary>
public class HandleContainer : CompositeDrawable
{
/// <summary>
/// Invoked when a <see cref="Handle"/> requires the current drag rectangle.
/// </summary>
public Func<RectangleF> GetDragRectangle;
/// <summary>
/// Invoked when a <see cref="Handle"/> wants to update the drag rectangle.
/// </summary>
public Action<RectangleF> UpdateDragRectangle;
/// <summary>
/// Invoked when a <see cref="Handle"/> has finished updates to the drag rectangle.
/// </summary>
public Action FinishDrag;
public HandleContainer()
{
InternalChildren = new Drawable[]
{
new Handle
{
Anchor = Anchor.TopLeft,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.TopRight,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.CentreRight,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.BottomRight,
Origin = Anchor.Centre
},
new Handle
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.Centre
},
new OriginHandle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
}
};
InternalChildren.OfType<Handle>().ForEach(m =>
{
m.GetDragRectangle = () => GetDragRectangle();
m.UpdateDragRectangle = r => UpdateDragRectangle(r);
m.FinishDrag = () => FinishDrag();
});
}
}
}

View File

@ -0,0 +1,178 @@
// Copyright (c) 2007-2017 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Configuration;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
/// <summary>
/// A box that represents a drag selection.
/// </summary>
public class HitObjectSelectionBox : CompositeDrawable
{
public readonly Bindable<SelectionInfo> Selection = new Bindable<SelectionInfo>();
/// <summary>
/// The <see cref="DrawableHitObject"/>s that can be selected through a drag-selection.
/// </summary>
public IEnumerable<DrawableHitObject> CapturableObjects;
private readonly Container borderMask;
private readonly Drawable background;
private readonly HandleContainer handles;
private Color4 captureFinishedColour;
private readonly Vector2 startPos;
/// <summary>
/// Creates a new <see cref="HitObjectSelectionBox"/>.
/// </summary>
/// <param name="startPos">The point at which the drag was initiated, in the parent's coordinates.</param>
public HitObjectSelectionBox(Vector2 startPos)
{
this.startPos = startPos;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(-1),
Child = borderMask = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderColour = Color4.White,
BorderThickness = 2,
MaskingSmoothness = 1,
Child = background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f,
AlwaysPresent = true
},
}
},
handles = new HandleContainer
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
GetDragRectangle = () => dragRectangle,
UpdateDragRectangle = updateDragRectangle,
FinishDrag = FinishCapture
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
captureFinishedColour = colours.Yellow;
}
/// <summary>
/// The secondary corner of the drag selection box. A rectangle will be fit between the starting position and this value.
/// </summary>
public Vector2 DragEndPosition { set => updateDragRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, value.X, value.Y)); }
private RectangleF dragRectangle;
private void updateDragRectangle(RectangleF rectangle)
{
dragRectangle = rectangle;
Position = new Vector2(
Math.Min(rectangle.Left, rectangle.Right),
Math.Min(rectangle.Top, rectangle.Bottom));
Size = new Vector2(
Math.Max(rectangle.Left, rectangle.Right) - Position.X,
Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y);
}
private readonly List<DrawableHitObject> capturedHitObjects = new List<DrawableHitObject>();
/// <summary>
/// Processes hitobjects to determine which ones are captured by the drag selection.
/// Captured hitobjects will be enclosed by the drag selection upon <see cref="FinishCapture"/>.
/// </summary>
public void BeginCapture()
{
capturedHitObjects.Clear();
foreach (var obj in CapturableObjects)
{
if (!obj.IsAlive || !obj.IsPresent)
continue;
if (ScreenSpaceDrawQuad.Contains(obj.SelectionPoint))
capturedHitObjects.Add(obj);
}
}
/// <summary>
/// Encloses hitobjects captured through <see cref="BeginCapture"/> in the drag selection box.
/// </summary>
public void FinishCapture()
{
if (capturedHitObjects.Count == 0)
{
Hide();
return;
}
// Move the rectangle to cover the hitobjects
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
var bottomRight = new Vector2(float.MinValue, float.MinValue);
foreach (var obj in capturedHitObjects)
{
topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft));
bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight));
}
topLeft -= new Vector2(5);
bottomRight += new Vector2(5);
this.MoveTo(topLeft, 200, Easing.OutQuint)
.ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint);
dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y);
borderMask.BorderThickness = 3;
borderMask.FadeColour(captureFinishedColour, 200);
// Transform into markers to let the user modify the drag selection further.
background.Delay(50).FadeOut(200);
handles.FadeIn(200);
Selection.Value = new SelectionInfo
{
Objects = capturedHitObjects,
SelectionQuad = Parent.ToScreenSpace(dragRectangle)
};
}
private bool isActive = true;
public override bool HandleInput => isActive;
public override void Hide()
{
isActive = false;
this.FadeOut(400, Easing.OutQuint).Expire();
Selection.Value = null;
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2017 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using OpenTK;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
/// <summary>
/// Represents the origin of a <see cref="HandleContainer"/>.
/// </summary>
public class OriginHandle : CompositeDrawable
{
private const float marker_size = 10;
private const float line_width = 2;
public OriginHandle()
{
Size = new Vector2(marker_size);
InternalChildren = new[]
{
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = line_width
},
new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = line_width
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2017 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.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
public class SelectionInfo
{
/// <summary>
/// The objects that are captured by the selection.
/// </summary>
public IEnumerable<DrawableHitObject> Objects;
/// <summary>
/// The screen space quad of the selection box surrounding <see cref="Objects"/>.
/// </summary>
public Quad SelectionQuad;
}
}

View File

@ -0,0 +1,61 @@
// Copyright (c) 2007-2017 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.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
public class SelectionLayer : CompositeDrawable
{
public readonly Bindable<SelectionInfo> Selection = new Bindable<SelectionInfo>();
private readonly Playfield playfield;
public SelectionLayer(Playfield playfield)
{
this.playfield = playfield;
RelativeSizeAxes = Axes.Both;
}
private HitObjectSelectionBox selectionBoxBox;
protected override bool OnDragStart(InputState state)
{
// Hide the previous drag box - we won't be working with it any longer
selectionBoxBox?.Hide();
AddInternal(selectionBoxBox = new HitObjectSelectionBox(ToLocalSpace(state.Mouse.NativeState.Position))
{
CapturableObjects = playfield.HitObjects.Objects,
});
Selection.BindTo(selectionBoxBox.Selection);
return true;
}
protected override bool OnDrag(InputState state)
{
selectionBoxBox.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position);
selectionBoxBox.BeginCapture();
return true;
}
protected override bool OnDragEnd(InputState state)
{
selectionBoxBox.FinishCapture();
return true;
}
protected override bool OnClick(InputState state)
{
selectionBoxBox?.Hide();
return true;
}
}
}

View File

@ -14,6 +14,8 @@ using osu.Game.Audio;
using System.Linq; using System.Linq;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using OpenTK;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Rulesets.Objects.Drawables namespace osu.Game.Rulesets.Objects.Drawables
{ {
@ -38,6 +40,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
{ {
HitObject = hitObject; HitObject = hitObject;
} }
/// <summary>
/// The screen-space point that causes this <see cref="DrawableHitObject"/> to be selected in the Editor.
/// </summary>
public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
/// <summary>
/// The screen-space quad that outlines this <see cref="DrawableHitObject"/> for selections in the Editor.
/// </summary>
public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
} }
public abstract class DrawableHitObject<TObject> : DrawableHitObject public abstract class DrawableHitObject<TObject> : DrawableHitObject

View File

@ -177,8 +177,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
string[] split = str.Split(':'); string[] split = str.Split(':');
var bank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]); var bank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[0]);
var addbank = (OsuLegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]); var addbank = (LegacyDecoder.LegacySampleBank)Convert.ToInt32(split[1]);
// Let's not implement this for now, because this doesn't fit nicely into the bank structure // Let's not implement this for now, because this doesn't fit nicely into the bank structure
//string sampleFile = split2.Length > 4 ? split2[4] : string.Empty; //string sampleFile = split2.Length > 4 ? split2[4] : string.Empty;

View File

@ -23,13 +23,12 @@ namespace osu.Game.Rulesets
public virtual IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { }; public virtual IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new BeatmapStatistic[] { };
public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>() public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
// Get all mod types as an IEnumerable<ModType> // Confine all mods of each mod type into a single IEnumerable<Mod>
.SelectMany(GetModsFor) .SelectMany(GetModsFor)
// Confine all mods of each mod type into a single IEnumerable<Mod> // Filter out all null mods
.Where(mod => mod != null) .Where(mod => mod != null)
// Filter out all null mods // Resolve MultiMods as their .Mods property
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod });
// Resolve MultiMods as their .Mods property
public abstract IEnumerable<Mod> GetModsFor(ModType type); public abstract IEnumerable<Mod> GetModsFor(ModType type);
@ -66,6 +65,11 @@ namespace osu.Game.Rulesets
/// </summary> /// </summary>
public virtual int LegacyID => -1; public virtual int LegacyID => -1;
/// <summary>
/// A unique short name to reference this ruleset in online requests.
/// </summary>
public abstract string ShortName { get; }
/// <summary> /// <summary>
/// A list of available variant ids. /// A list of available variant ids.
/// </summary> /// </summary>

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets
public string Name { get; set; } public string Name { get; set; }
public string ShortName { get; set; }
public string InstantiationInfo { get; set; } public string InstantiationInfo { get; set; }
[JsonIgnore] [JsonIgnore]

View File

@ -83,7 +83,11 @@ namespace osu.Game.Rulesets
{ {
try try
{ {
r.CreateInstance(); var instance = r.CreateInstance();
r.Name = instance.Description;
r.ShortName = instance.ShortName;
r.Available = true; r.Available = true;
} }
catch catch
@ -117,6 +121,7 @@ namespace osu.Game.Rulesets
private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo private RulesetInfo createRulesetInfo(Ruleset ruleset) => new RulesetInfo
{ {
Name = ruleset.Description, Name = ruleset.Description,
ShortName = ruleset.ShortName,
InstantiationInfo = ruleset.GetType().AssemblyQualifiedName, InstantiationInfo = ruleset.GetType().AssemblyQualifiedName,
ID = ruleset.LegacyID ID = ruleset.LegacyID
}; };

Some files were not shown because too many files have changed in this diff Show More