1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:52:53 +08:00

Merge branch 'master' into mania-step-interval

This commit is contained in:
Dan Balasescu 2018-06-13 13:55:27 +09:00 committed by GitHub
commit 0e0e64895a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
96 changed files with 1884 additions and 1240 deletions

View File

@ -1,36 +0,0 @@
# Linux
### 1. Requirements:
Mono >= 5.4.0 (>= 5.8.0 recommended)
Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release.
NuGet >= 4.4.0
msbuild
git
### 2. Cloning project
Clone the entire repository with submodules using
```
git clone https://github.com/ppy/osu --recursive
```
Then restore NuGet packages from the repository
```
nuget restore
```
### 3. Compiling
Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`.
### 4. Optimizing
If you want additional performance you can change build type to Release with
```
msbuild -p:Configuration=Release
```
Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running
```
mono --aot ./osu\!.exe
```
### 5. Troubleshooting
You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run
```
nuget
sudo nuget update -self
```
**Warning** NuGet creates few config files when it's run for the first time.
Do not run NuGet as root on the first run or you might run into very peculiar issues.

View File

@ -26,10 +26,10 @@
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup>
<ItemGroup Label="Resources">

View File

@ -82,56 +82,25 @@ namespace osu.Game.Rulesets.Catch
{
new CatchModEasy(),
new CatchModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new CatchModHalfTime(),
new CatchModDaycore(),
},
},
new MultiMod(new CatchModHalfTime(), new CatchModDaycore())
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new CatchModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new CatchModSuddenDeath(),
new CatchModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new CatchModDoubleTime(),
new CatchModNightcore(),
},
},
new MultiMod(new CatchModSuddenDeath(), new CatchModPerfect()),
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new CatchModRelax(),
null,
null,
new MultiMod
{
Mods = new Mod[]
{
new CatchModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new CatchModAutoplay(), new ModCinema()),
};
default:
return new Mod[] { };
}

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
{
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
@ -141,5 +142,22 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return difficulty;
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new ManiaModDoubleTime(),
new ManiaModHalfTime(),
new ManiaModEasy(),
new ManiaModHardRock(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3(),
new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
};
}
}

View File

@ -15,8 +15,11 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Scoring;
@ -105,78 +108,34 @@ namespace osu.Game.Rulesets.Mania
{
new ManiaModEasy(),
new ManiaModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModHalfTime(),
new ManiaModDaycore(),
},
},
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new ManiaModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModSuddenDeath(),
new ManiaModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new ManiaModDoubleTime(),
new ManiaModNightcore(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new ManiaModFadeIn(),
new ManiaModHidden(),
}
},
new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()),
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new MultiMod
{
Mods = new Mod[]
{
new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3(),
},
},
new MultiMod(new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3()),
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new ManiaModAutoplay(), new ModCinema()),
};
default:
return new Mod[] { };
}
@ -194,6 +153,8 @@ namespace osu.Game.Rulesets.Mania
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
@ -24,5 +26,18 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns = KeyCount;
}
public override Type[] IncompatibleMods => new[]
{
typeof(ManiaModKey1),
typeof(ManiaModKey2),
typeof(ManiaModKey3),
typeof(ManiaModKey4),
typeof(ManiaModKey5),
typeof(ManiaModKey6),
typeof(ManiaModKey7),
typeof(ManiaModKey8),
typeof(ManiaModKey9),
}.Except(new[] { GetType() }).ToArray();
}
}

View File

@ -277,8 +277,13 @@ namespace osu.Game.Rulesets.Mania.UI
if (action != Action)
return false;
var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault();
hitObject?.PlaySamples();
var nextObject =
HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
// fallback to non-alive objects to find next off-screen object
HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjects.Objects.LastOrDefault();
nextObject?.PlaySamples();
return true;
}

View File

@ -10,12 +10,9 @@ using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
@ -103,7 +100,5 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
}
}

View File

@ -8,6 +8,7 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty
@ -71,5 +72,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return starRating;
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new OsuModDoubleTime(),
new OsuModHalfTime(),
new OsuModEasy(),
new OsuModHardRock(),
};
}
}

View File

@ -93,57 +93,26 @@ namespace osu.Game.Rulesets.Osu
{
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModHalfTime(),
new OsuModDaycore(),
},
},
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new OsuModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModSuddenDeath(),
new OsuModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new OsuModDoubleTime(),
new OsuModNightcore(),
},
},
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new OsuModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new OsuModRelax(),
new OsuModAutopilot(),
new OsuModSpunOut(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new OsuModAutoplay(), new ModCinema()),
new OsuModTarget(),
};
default:
return new Mod[] { };
}
@ -161,7 +130,7 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu";
public override SettingsSubsection CreateSettings() => new OsuSettings();
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
public override int? LegacyID => 0;

View File

@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override void PostProcess()
{
connectionLayer.HitObjects = HitObjects.Objects
.Select(d => d.HitObject)
.OrderBy(h => h.StartTime).OfType<OsuHitObject>();
connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
}
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement)

View File

@ -8,10 +8,15 @@ using osu.Game.Overlays.Settings;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuSettings : SettingsSubsection
public class OsuSettings : RulesetSettingsSubsection
{
protected override string Header => "osu!";
public OsuSettings(Ruleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty
@ -62,6 +63,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return starRating;
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
private bool calculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.

View File

@ -84,56 +84,25 @@ namespace osu.Game.Rulesets.Taiko
{
new TaikoModEasy(),
new TaikoModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new TaikoModHalfTime(),
new TaikoModDaycore(),
},
},
new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()),
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new TaikoModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new TaikoModSuddenDeath(),
new TaikoModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModNightcore(),
},
},
new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
new TaikoModHidden(),
new TaikoModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new TaikoModRelax(),
null,
null,
new MultiMod
{
Mods = new Mod[]
{
new TaikoModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new TaikoModAutoplay(), new ModCinema()),
};
default:
return new Mod[] { };
}

View File

@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
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);
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID);
}
}

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = decodeAsJson(normal);
var meta = beatmap.BeatmapInfo.Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);

View File

@ -48,11 +48,14 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var reader = new ZipArchiveReader(osz);
BeatmapMetadata meta;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
meta = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
Beatmap beatmap;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
var meta = beatmap.Metadata;
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);

View File

@ -0,0 +1,152 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class DifficultyAdjustmentModCombinationsTest
{
[Test]
public void TestNoMods()
{
var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(1, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
}
[Test]
public void TestSingleMod()
{
var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(2, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
Assert.IsTrue(combinations[1] is ModA);
}
[Test]
public void TestDoubleMod()
{
var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(4, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
Assert.IsTrue(combinations[1] is ModA);
Assert.IsTrue(combinations[2] is MultiMod);
Assert.IsTrue(combinations[3] is ModB);
Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA);
Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB);
}
[Test]
public void TestIncompatibleMods()
{
var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(3, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
Assert.IsTrue(combinations[1] is ModA);
Assert.IsTrue(combinations[2] is ModIncompatibleWithA);
}
[Test]
public void TestDoubleIncompatibleMods()
{
var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(8, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
Assert.IsTrue(combinations[1] is ModA);
Assert.IsTrue(combinations[2] is MultiMod);
Assert.IsTrue(combinations[3] is ModB);
Assert.IsTrue(combinations[4] is MultiMod);
Assert.IsTrue(combinations[5] is ModIncompatibleWithA);
Assert.IsTrue(combinations[6] is MultiMod);
Assert.IsTrue(combinations[7] is ModIncompatibleWithAAndB);
Assert.IsTrue(((MultiMod)combinations[2]).Mods[0] is ModA);
Assert.IsTrue(((MultiMod)combinations[2]).Mods[1] is ModB);
Assert.IsTrue(((MultiMod)combinations[4]).Mods[0] is ModB);
Assert.IsTrue(((MultiMod)combinations[4]).Mods[1] is ModIncompatibleWithA);
Assert.IsTrue(((MultiMod)combinations[6]).Mods[0] is ModIncompatibleWithA);
Assert.IsTrue(((MultiMod)combinations[6]).Mods[1] is ModIncompatibleWithAAndB);
}
[Test]
public void TestIncompatibleThroughBaseType()
{
var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations();
Assert.AreEqual(3, combinations.Length);
Assert.IsTrue(combinations[0] is NoModMod);
Assert.IsTrue(combinations[1] is ModAofA);
Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA);
}
private class ModA : Mod
{
public override string Name => nameof(ModA);
public override string ShortenedName => nameof(ModA);
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) };
}
private class ModB : Mod
{
public override string Name => nameof(ModB);
public override string ShortenedName => nameof(ModB);
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) };
}
private class ModIncompatibleWithA : Mod
{
public override string Name => $"Incompatible With {nameof(ModA)}";
public override string ShortenedName => $"Incompatible With {nameof(ModA)}";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA) };
}
private class ModAofA : ModA
{
}
private class ModIncompatibleWithAofA : ModIncompatibleWithA
{
// Incompatible through base type
}
private class ModIncompatibleWithAAndB : Mod
{
public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) };
}
private class TestDifficultyCalculator : DifficultyCalculator
{
public TestDifficultyCalculator(params Mod[] mods)
: base(null)
{
DifficultyAdjustmentMods = mods;
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => throw new NotImplementedException();
protected override Mod[] DifficultyAdjustmentMods { get; }
}
}
}

View File

@ -449,7 +449,6 @@ namespace osu.Game.Tests.Visual
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata
{
OnlineBeatmapSetID = id,
// Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
Title = $"test set #{id}!",
@ -503,7 +502,6 @@ namespace osu.Game.Tests.Visual
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata
{
OnlineBeatmapSetID = id,
// Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
Title = $"test set #{id}!",

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.BeatmapSet.Scores;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@ -15,6 +14,7 @@ using osu.Game.Users;
using System.Collections.Generic;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual
@ -22,9 +22,9 @@ namespace osu.Game.Tests.Visual
[System.ComponentModel.Description("in BeatmapOverlay")]
public class TestCaseBeatmapScoresContainer : OsuTestCase
{
private readonly IEnumerable<OnlineScore> scores;
private readonly IEnumerable<OnlineScore> anotherScores;
private readonly OnlineScore topScore;
private readonly IEnumerable<APIScore> scores;
private readonly IEnumerable<APIScore> anotherScores;
private readonly APIScore topScore;
private readonly Box background;
public TestCaseBeatmapScoresContainer()
@ -52,12 +52,12 @@ namespace osu.Game.Tests.Visual
AddStep("remove scores", () => scoresContainer.Scores = null);
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapSetID = 1, OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
scores = new[]
{
new OnlineScore
new APIScore
{
User = new User
{
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 1234567890,
Accuracy = 1,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 1234789,
Accuracy = 0.9997,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 12345678,
Accuracy = 0.9854,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 1234567,
Accuracy = 0.8765,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -169,7 +169,7 @@ namespace osu.Game.Tests.Visual
anotherScores = new[]
{
new OnlineScore
new APIScore
{
User = new User
{
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 1234789,
Accuracy = 0.9997,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 1234567890,
Accuracy = 1,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 123456,
Accuracy = 0.6543,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -251,7 +251,7 @@ namespace osu.Game.Tests.Visual
TotalScore = 12345678,
Accuracy = 0.9854,
},
new OnlineScore
new APIScore
{
User = new User
{
@ -279,7 +279,7 @@ namespace osu.Game.Tests.Visual
s.Statistics.Add(HitResult.Meh, RNG.Next(2000));
}
topScore = new OnlineScore
topScore = new APIScore
{
User = new User
{

View File

@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseEditorComposeTimeline : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) };
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
public TestCaseEditorComposeTimeline()
{
@ -27,11 +27,12 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.TopCentre,
State = Visibility.Visible
},
new ScrollableTimeline
new TimelineArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1000, 100)
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.8f, 100)
}
};
}

View File

@ -122,7 +122,6 @@ namespace osu.Game.Tests.Visual
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata
{
OnlineBeatmapSetID = 1234 + i,
// Create random metadata, then we can check if sorting works based on these
Artist = "MONACA " + RNG.Next(0, 9),
Title = "Black Song " + RNG.Next(0, 9),

View File

@ -12,6 +12,7 @@ using osu.Game.Overlays.Profile.Sections.Recent;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.Visual
{
@ -49,15 +50,15 @@ namespace osu.Game.Tests.Visual
};
}
private IEnumerable<RecentActivity> createDummyActivities()
private IEnumerable<APIRecentActivity> createDummyActivities()
{
var dummyBeatmap = new RecentActivity.RecentActivityBeatmap
var dummyBeatmap = new APIRecentActivity.RecentActivityBeatmap
{
Title = @"Dummy beatmap",
Url = "/b/1337",
};
var dummyUser = new RecentActivity.RecentActivityUser
var dummyUser = new APIRecentActivity.RecentActivityUser
{
Username = "DummyReborn",
Url = "/u/666",
@ -66,61 +67,61 @@ namespace osu.Game.Tests.Visual
return new[]
{
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.Achievement,
Achievement = new RecentActivity.RecentActivityAchievement
Achievement = new APIRecentActivity.RecentActivityAchievement
{
Name = @"Feelin' It",
Slug = @"all-secret-feelinit",
},
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapPlaycount,
Count = 1337,
Beatmap = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove,
Approval = BeatmapApproval.Qualified,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetDelete,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetRevive,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetRevive,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetUpdate,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.BeatmapsetUpload,
Beatmapset = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.Rank,
@ -128,29 +129,29 @@ namespace osu.Game.Tests.Visual
Mode = "osu!",
Beatmap = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.RankLost,
Mode = "osu!",
Beatmap = dummyBeatmap,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.UsernameChange,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.UserSupportAgain,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.UserSupportFirst,
},
new RecentActivity
new APIRecentActivity
{
User = dummyUser,
Type = RecentActivityType.UserSupportGift,

View File

@ -6,11 +6,11 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
@ -40,14 +40,13 @@ namespace osu.Game.Tests.Visual
for (int i = 1; i <= 16; i *= 2)
{
var newDisplay = new BeatmapWaveformGraph
var newDisplay = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Resolution = 1f / i,
Beatmap = Beatmap
};
Beatmap.ValueChanged += b => newDisplay.Beatmap = b;
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
flow.Add(new Container
{

View File

@ -0,0 +1,142 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase
{
private readonly ZoomableScrollContainer scrollContainer;
private readonly Drawable innerBox;
public TestCaseZoomableScrollContainer()
{
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = 250,
Width = 0.75f,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(30)
},
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
}
},
new MenuCursor()
};
scrollContainer.Add(innerBox = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f))
});
}
[Test]
public void TestZoom0()
{
reset();
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box width = 1x", () => Precision.AlmostEquals(boxQuad.Size, scrollQuad.Size));
}
[Test]
public void TestZoom10()
{
reset();
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre));
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
}
[Test]
public void TestMouseZoomInOnceOutOnce()
{
reset();
// Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
// Scroll out at 0.25
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
}
[Test]
public void TestMouseZoomInTwiceOutTwice()
{
reset();
// Scroll in at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
// Scroll in at 0.6
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
// Very hard to determine actual position, so approximate
AddAssert("Box at correct position (1)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X));
AddAssert("Box at correct position (2)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.3f * boxQuad.Size.X));
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
// Scroll out at 0.6
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
// Scroll out at 0.25
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
}
private void reset()
{
AddStep("Reset", () =>
{
scrollContainer.Zoom = 0;
scrollContainer.ScrollTo(0, false);
});
}
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
private Quad boxQuad => innerBox.ScreenSpaceDrawQuad;
}
}

View File

@ -23,7 +23,6 @@ namespace osu.Game.Beatmaps
public int BeatmapVersion;
private int? onlineBeatmapID;
private int? onlineBeatmapSetID;
[JsonProperty("id")]
public int? OnlineBeatmapID
@ -32,19 +31,10 @@ namespace osu.Game.Beatmaps
set { onlineBeatmapID = value > 0 ? value : null; }
}
[JsonProperty("beatmapset_id")]
[NotMapped]
public int? OnlineBeatmapSetID
{
get { return onlineBeatmapSetID; }
set { onlineBeatmapSetID = value > 0 ? value : null; }
}
[JsonIgnore]
public int BeatmapSetInfoID { get; set; }
[Required]
[JsonIgnore]
public BeatmapSetInfo BeatmapSet { get; set; }
public BeatmapMetadata Metadata { get; set; }
@ -141,8 +131,8 @@ namespace osu.Game.Beatmaps
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
/// <summary>
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.

View File

@ -81,12 +81,31 @@ namespace osu.Game.Beatmaps
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
{
model.Beatmaps = createBeatmapDifficulties(model, archive);
model.Beatmaps = createBeatmapDifficulties(archive);
// remove metadata from difficulties where it matches the set
foreach (BeatmapInfo b in model.Beatmaps)
{
// remove metadata from difficulties where it matches the set
if (model.Metadata.Equals(b.Metadata))
b.Metadata = null;
// by setting the model here, we can update the noline set id below.
b.BeatmapSet = model;
fetchAndPopulateOnlineIDs(b);
}
// check if a set already exists with the same online id, delete if it does.
if (model.OnlineBeatmapSetID != null)
{
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
if (existingOnlineId != null)
{
Delete(existingOnlineId);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
}
}
}
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
@ -99,18 +118,6 @@ namespace osu.Game.Beatmaps
return existingHashMatch;
}
// check if a set already exists with the same online id
if (model.OnlineBeatmapSetID != null)
{
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
if (existingOnlineId != null)
{
Delete(existingOnlineId);
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
}
}
return null;
}
@ -306,29 +313,29 @@ namespace osu.Game.Beatmaps
return hashable.ComputeSHA2Hash();
}
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
{
// let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
BeatmapMetadata metadata;
Beatmap beatmap;
using (var stream = new StreamReader(reader.GetStream(mapName)))
metadata = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
return new BeatmapSetInfo
{
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(),
Hash = computeBeatmapSetHash(reader),
Metadata = metadata
Metadata = beatmap.Metadata
};
}
/// <summary>
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
/// </summary>
private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
{
var beatmapInfos = new List<BeatmapInfo>();
@ -348,10 +355,6 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
// ensure we have the same online set ID as the set itself.
beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
beatmap.BeatmapInfo.OnlineBeatmapID = null;
@ -376,6 +379,40 @@ namespace osu.Game.Beatmaps
return beatmapInfos;
}
/// <summary>
/// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
/// </summary>
/// <param name="beatmap">The beatmap to populate.</param>
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
/// <returns>True if population was successful.</returns>
private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false)
{
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
return true;
Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
try
{
var req = new GetBeatmapRequest(beatmap);
req.Perform(api);
var res = req.Result;
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
return true;
}
catch (Exception e)
{
Logger.Log($"Failed ({e})", LoggingTarget.Database);
return false;
}
}
/// <summary>
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
/// </summary>

View File

@ -17,16 +17,6 @@ namespace osu.Game.Beatmaps
[JsonIgnore]
public int ID { get; set; }
private int? onlineBeatmapSetID;
[NotMapped]
[JsonProperty(@"id")]
public int? OnlineBeatmapSetID
{
get { return onlineBeatmapSetID; }
set { onlineBeatmapSetID = value > 0 ? value : null; }
}
public string Title { get; set; }
public string TitleUnicode { get; set; }
public string Artist { get; set; }
@ -82,8 +72,7 @@ namespace osu.Game.Beatmaps
if (other == null)
return false;
return onlineBeatmapSetID == other.onlineBeatmapSetID
&& Title == other.Title
return Title == other.Title
&& TitleUnicode == other.TitleUnicode
&& Artist == other.Artist
&& ArtistUnicode == other.ArtistUnicode

View File

@ -22,18 +22,18 @@ namespace osu.Game.Beatmaps
[NotMapped]
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0;
[NotMapped]
public bool DeletePending { get; set; }
public string Hash { get; set; }
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
public List<BeatmapSetFileInfo> Files { get; set; }
public override string ToString() => Metadata.ToString();
public override string ToString() => Metadata?.ToString() ?? base.ToString();
public bool Protected { get; set; }
}

View File

@ -0,0 +1,84 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
/// <summary>
/// A component to allow downloading of a beatmap set. Automatically handles state syncing between other instances.
/// </summary>
public class BeatmapSetDownloader : Component
{
private readonly BeatmapSetInfo set;
private readonly bool noVideo;
private BeatmapManager beatmaps;
/// <summary>
/// Whether the associated beatmap set has been downloading (by this instance or any other instance).
/// </summary>
public readonly BindableBool Downloaded = new BindableBool();
public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false)
{
this.set = set;
this.noVideo = noVideo;
}
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
this.beatmaps = beatmaps;
beatmaps.ItemAdded += setAdded;
beatmaps.ItemRemoved += setRemoved;
// initial value
if (set.OnlineBeatmapSetID != null)
Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= setAdded;
beatmaps.ItemRemoved -= setRemoved;
}
}
/// <summary>
/// Begin downloading the associated beatmap set.
/// </summary>
/// <returns>True if downloading began. False if an existing download is active or completed.</returns>
public bool Download()
{
if (Downloaded.Value)
return false;
if (beatmaps.GetExistingDownload(set) != null)
return false;
beatmaps.Download(set, noVideo);
return true;
}
private void setAdded(BeatmapSetInfo s)
{
if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
Downloaded.Value = true;
}
private void setRemoved(BeatmapSetInfo s)
{
if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID)
Downloaded.Value = false;
}
}
}

View File

@ -34,7 +34,8 @@ namespace osu.Game.Beatmaps.Formats
private readonly int offset;
public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version)
public LegacyBeatmapDecoder(int version = LATEST_VERSION)
: base(version)
{
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
offset = FormatVersion < 5 ? 24 : 0;
@ -135,6 +136,7 @@ namespace osu.Game.Beatmaps.Formats
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
break;
}
break;
case @"LetterboxInBreaks":
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
@ -207,8 +209,7 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
break;
case @"BeatmapSetID":
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) };
break;
}
}

View File

@ -13,13 +13,13 @@ namespace osu.Game.Configuration
{
private readonly SettingsStore settings;
private readonly int variant;
private readonly int? variant;
private readonly List<DatabasedSetting> databasedSettings;
private readonly RulesetInfo ruleset;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0)
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
{
this.settings = settings;
this.ruleset = ruleset;

View File

@ -5,6 +5,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Platform;
using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select;
namespace osu.Game.Configuration
@ -80,6 +81,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.FloatingComments, false);
Set(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised);
Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential);
Set(OsuSetting.IncreaseFirstObjectVisibility, true);
@ -147,6 +150,7 @@ namespace osu.Game.Configuration
SongSelectRightMouseScroll,
BeatmapSkins,
BeatmapHitsounds,
IncreaseFirstObjectVisibility
IncreaseFirstObjectVisibility,
ScoreDisplayMode
}
}

View File

@ -14,16 +14,19 @@ namespace osu.Game.Online.API
{
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
public T Result => ((JsonWebRequest<T>)WebRequest).ResponseObject;
protected APIRequest()
{
base.Success += onSuccess;
}
private void onSuccess()
{
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
}
private void onSuccess() => Success?.Invoke(Result);
/// <summary>
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
public new event APISuccessHandler<T> Success;
}
@ -52,7 +55,16 @@ namespace osu.Game.Online.API
protected APIAccess API;
protected WebRequest WebRequest;
/// <summary>
/// Invoked on successful completion of an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
public event APISuccessHandler Success;
/// <summary>
/// Invoked on failure to complete an API request.
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
public event APIFailureHandler Failure;
private bool cancelled;

View File

@ -1,149 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using System;
namespace osu.Game.Online.API.Requests
{
public class APIResponseBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
{
[JsonProperty(@"covers")]
private BeatmapSetOnlineCovers covers { get; set; }
[JsonProperty(@"preview_url")]
private string preview { get; set; }
[JsonProperty(@"play_count")]
private int playCount { get; set; }
[JsonProperty(@"favourite_count")]
private int favouriteCount { get; set; }
[JsonProperty(@"bpm")]
private double bpm { get; set; }
[JsonProperty(@"video")]
private bool hasVideo { get; set; }
[JsonProperty(@"storyboard")]
private bool hasStoryboard { get; set; }
[JsonProperty(@"status")]
private BeatmapSetOnlineStatus status { get; set; }
[JsonProperty(@"submitted_date")]
private DateTimeOffset submitted { get; set; }
[JsonProperty(@"ranked_date")]
private DateTimeOffset ranked { get; set; }
[JsonProperty(@"last_updated")]
private DateTimeOffset lastUpdated { get; set; }
[JsonProperty(@"user_id")]
private long creatorId {
set { Author.Id = value; }
}
[JsonProperty(@"beatmaps")]
private IEnumerable<APIResponseBeatmap> beatmaps { get; set; }
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
return new BeatmapSetInfo
{
OnlineBeatmapSetID = OnlineBeatmapSetID,
Metadata = this,
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = covers,
Preview = preview,
PlayCount = playCount,
FavouriteCount = favouriteCount,
BPM = bpm,
Status = status,
HasVideo = hasVideo,
HasStoryboard = hasStoryboard,
Submitted = submitted,
Ranked = ranked,
LastUpdated = lastUpdated,
},
Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
};
}
private class APIResponseBeatmap : BeatmapMetadata
{
[JsonProperty(@"id")]
private int onlineBeatmapID { get; set; }
[JsonProperty(@"playcount")]
private int playCount { get; set; }
[JsonProperty(@"passcount")]
private int passCount { get; set; }
[JsonProperty(@"mode_int")]
private int ruleset { get; set; }
[JsonProperty(@"difficulty_rating")]
private double starDifficulty { get; set; }
[JsonProperty(@"drain")]
private float drainRate { get; set; }
[JsonProperty(@"cs")]
private float circleSize { get; set; }
[JsonProperty(@"ar")]
private float approachRate { get; set; }
[JsonProperty(@"accuracy")]
private float overallDifficulty { get; set; }
[JsonProperty(@"total_length")]
private double length { get; set; }
[JsonProperty(@"count_circles")]
private int circleCount { get; set; }
[JsonProperty(@"count_sliders")]
private int sliderCount { get; set; }
[JsonProperty(@"version")]
private string version { get; set; }
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
{
return new BeatmapInfo
{
Metadata = this,
Ruleset = rulesets.GetRuleset(ruleset),
StarDifficulty = starDifficulty,
OnlineBeatmapID = onlineBeatmapID,
Version = version,
BaseDifficulty = new BeatmapDifficulty
{
DrainRate = drainRate,
CircleSize = circleSize,
ApproachRate = approachRate,
OverallDifficulty = overallDifficulty,
},
OnlineInfo = new BeatmapOnlineInfo
{
PlayCount = playCount,
PassCount = passCount,
Length = length,
CircleCount = circleCount,
SliderCount = sliderCount,
},
};
}
}
}
}

View File

@ -1,46 +1,20 @@
// Copyright (c) 2007-2018 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.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetBeatmapDetailsRequest : APIRequest<GetBeatmapDetailsResponse>
public class GetBeatmapDetailsRequest : APIRequest<APIBeatmapMetrics>
{
private readonly BeatmapInfo beatmap;
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
}
protected override string Target => $@"beatmaps/{lookupString}";
}
public class GetBeatmapDetailsResponse : BeatmapMetrics
{
//the online API returns some metrics as a nested object.
[JsonProperty(@"failtimes")]
private BeatmapMetrics failTimes
{
set
{
Fails = value.Fails;
Retries = value.Retries;
}
}
//and other metrics in the beatmap set.
[JsonProperty(@"beatmapset")]
private BeatmapMetrics beatmapSet
{
set
{
Ratings = value.Ratings;
}
}
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}";
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetBeatmapRequest : APIRequest<APIBeatmap>
{
private readonly BeatmapInfo beatmap;
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
public GetBeatmapRequest(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
}
protected override string Target => $@"beatmaps/{lookupString}";
}
}

View File

@ -1,9 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetBeatmapSetRequest : APIRequest<APIResponseBeatmapSet>
public class GetBeatmapSetRequest : APIRequest<APIBeatmapSet>
{
private readonly int id;
private readonly BeatmapSetLookupType type;

View File

@ -2,20 +2,15 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Users;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetScoresRequest : APIRequest<GetScoresResponse>
public class GetScoresRequest : APIRequest<APIScores>
{
private readonly BeatmapInfo beatmap;
private readonly LeaderboardScope scope;
@ -36,9 +31,9 @@ namespace osu.Game.Online.API.Requests
Success += onSuccess;
}
private void onSuccess(GetScoresResponse r)
private void onSuccess(APIScores r)
{
foreach (OnlineScore score in r.Scores)
foreach (APIScore score in r.Scores)
score.ApplyBeatmap(beatmap);
}
@ -55,112 +50,4 @@ namespace osu.Game.Online.API.Requests
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores";
}
public class GetScoresResponse
{
[JsonProperty(@"scores")]
public IEnumerable<OnlineScore> Scores;
}
public class OnlineScore : Score
{
[JsonProperty(@"score")]
private double totalScore
{
set { TotalScore = value; }
}
[JsonProperty(@"max_combo")]
private int maxCombo
{
set { MaxCombo = value; }
}
[JsonProperty(@"user")]
private User user
{
set { User = value; }
}
[JsonProperty(@"replay_data")]
private Replay replay
{
set { Replay = value; }
}
[JsonProperty(@"mode_int")]
public int OnlineRulesetID { get; set; }
[JsonProperty(@"score_id")]
private long onlineScoreID
{
set { OnlineScoreID = value; }
}
[JsonProperty(@"created_at")]
private DateTimeOffset date
{
set { Date = value; }
}
[JsonProperty(@"beatmap")]
private BeatmapInfo beatmap
{
set { Beatmap = value; }
}
[JsonProperty(@"beatmapset")]
private BeatmapMetadata metadata
{
set { Beatmap.Metadata = value; }
}
[JsonProperty(@"statistics")]
private Dictionary<string, object> jsonStats
{
set
{
foreach (var kvp in value)
{
HitResult newKey;
switch (kvp.Key)
{
case @"count_300":
newKey = HitResult.Great;
break;
case @"count_100":
newKey = HitResult.Good;
break;
case @"count_50":
newKey = HitResult.Meh;
break;
case @"count_miss":
newKey = HitResult.Miss;
break;
default:
continue;
}
Statistics.Add(newKey, kvp.Value);
}
}
}
[JsonProperty(@"mods")]
private string[] modStrings { get; set; }
public void ApplyBeatmap(BeatmapInfo beatmap)
{
Beatmap = beatmap;
ApplyRuleset(beatmap.Ruleset);
}
public void ApplyRuleset(RulesetInfo ruleset)
{
Ruleset = ruleset;
// Evaluate the mod string
Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray();
}
}
}

View File

@ -3,10 +3,11 @@
using Humanizer;
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUserBeatmapsRequest : APIRequest<List<APIResponseBeatmapSet>>
public class GetUserBeatmapsRequest : APIRequest<List<APIBeatmapSet>>
{
private readonly long userId;
private readonly int offset;

View File

@ -1,14 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<MostPlayedBeatmap>>
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<APIUserMostPlayedBeatmap>>
{
private readonly long userId;
private readonly int offset;
@ -21,28 +19,4 @@ namespace osu.Game.Online.API.Requests
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

@ -1,15 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Rulesets.Scoring;
using Humanizer;
using System;
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUserRecentActivitiesRequest : APIRequest<List<RecentActivity>>
public class GetUserRecentActivitiesRequest : APIRequest<List<APIRecentActivity>>
{
private readonly long userId;
private readonly int offset;
@ -23,86 +20,6 @@ namespace osu.Game.Online.API.Requests
protected override string Target => $"users/{userId}/recent_activity?offset={offset}";
}
public class RecentActivity
{
[JsonProperty("id")]
public int ID;
[JsonProperty("createdAt")]
public DateTimeOffset CreatedAt;
[JsonProperty]
private string type
{
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize());
}
public RecentActivityType Type;
[JsonProperty]
private string scoreRank
{
set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value);
}
public ScoreRank ScoreRank;
[JsonProperty("rank")]
public int Rank;
[JsonProperty("approval")]
public BeatmapApproval Approval;
[JsonProperty("count")]
public int Count;
[JsonProperty("mode")]
public string Mode;
[JsonProperty("beatmap")]
public RecentActivityBeatmap Beatmap;
[JsonProperty("beatmapset")]
public RecentActivityBeatmap Beatmapset;
[JsonProperty("user")]
public RecentActivityUser User;
[JsonProperty("achievement")]
public RecentActivityAchievement Achievement;
public class RecentActivityBeatmap
{
[JsonProperty("title")]
public string Title;
[JsonProperty("url")]
public string Url;
}
public class RecentActivityUser
{
[JsonProperty("username")]
public string Username;
[JsonProperty("url")]
public string Url;
[JsonProperty("previousUsername")]
public string PreviousUsername;
}
public class RecentActivityAchievement
{
[JsonProperty("slug")]
public string Slug;
[JsonProperty("name")]
public string Name;
}
}
public enum RecentActivityType
{
Achievement,

View File

@ -2,10 +2,11 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUserScoresRequest : APIRequest<List<OnlineScore>>
public class GetUserScoresRequest : APIRequest<List<APIScore>>
{
private readonly long userId;
private readonly ScoreType type;

View File

@ -2,19 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Game.Users;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetUsersRequest : APIRequest<List<RankingEntry>>
public class GetUsersRequest : APIRequest<List<APIUser>>
{
protected override string Target => @"rankings/osu/performance";
}
public class RankingEntry
{
[JsonProperty]
public User User;
}
}

View File

@ -0,0 +1,85 @@
// Copyright (c) 2007-2018 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;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIBeatmap : BeatmapMetadata
{
[JsonProperty(@"id")]
public int OnlineBeatmapID { get; set; }
[JsonProperty(@"beatmapset_id")]
public int OnlineBeatmapSetID { get; set; }
[JsonProperty(@"playcount")]
private int playCount { get; set; }
[JsonProperty(@"passcount")]
private int passCount { get; set; }
[JsonProperty(@"mode_int")]
private int ruleset { get; set; }
[JsonProperty(@"difficulty_rating")]
private double starDifficulty { get; set; }
[JsonProperty(@"drain")]
private float drainRate { get; set; }
[JsonProperty(@"cs")]
private float circleSize { get; set; }
[JsonProperty(@"ar")]
private float approachRate { get; set; }
[JsonProperty(@"accuracy")]
private float overallDifficulty { get; set; }
[JsonProperty(@"total_length")]
private double length { get; set; }
[JsonProperty(@"count_circles")]
private int circleCount { get; set; }
[JsonProperty(@"count_sliders")]
private int sliderCount { get; set; }
[JsonProperty(@"version")]
private string version { get; set; }
public BeatmapInfo ToBeatmap(RulesetStore rulesets)
{
return new BeatmapInfo
{
Metadata = this,
Ruleset = rulesets.GetRuleset(ruleset),
StarDifficulty = starDifficulty,
OnlineBeatmapID = OnlineBeatmapID,
BeatmapSet = new BeatmapSetInfo
{
OnlineBeatmapSetID = OnlineBeatmapSetID,
},
Version = version,
BaseDifficulty = new BeatmapDifficulty
{
DrainRate = drainRate,
CircleSize = circleSize,
ApproachRate = approachRate,
OverallDifficulty = overallDifficulty,
},
OnlineInfo = new BeatmapOnlineInfo
{
PlayCount = playCount,
PassCount = passCount,
Length = length,
CircleCount = circleCount,
SliderCount = sliderCount,
},
};
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2007-2018 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;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIBeatmapMetrics : BeatmapMetrics
{
//the online API returns some metrics as a nested object.
[JsonProperty(@"failtimes")]
private BeatmapMetrics failTimes
{
set
{
Fails = value.Fails;
Retries = value.Retries;
}
}
//and other metrics in the beatmap set.
[JsonProperty(@"beatmapset")]
private BeatmapMetrics beatmapSet
{
set => Ratings = value.Ratings;
}
}
}

View File

@ -0,0 +1,90 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIBeatmapSet : BeatmapMetadata // todo: this is a bit wrong...
{
[JsonProperty(@"covers")]
private BeatmapSetOnlineCovers covers { get; set; }
private int? onlineBeatmapSetID;
[JsonProperty(@"id")]
public int? OnlineBeatmapSetID
{
get { return onlineBeatmapSetID; }
set { onlineBeatmapSetID = value > 0 ? value : null; }
}
[JsonProperty(@"preview_url")]
private string preview { get; set; }
[JsonProperty(@"play_count")]
private int playCount { get; set; }
[JsonProperty(@"favourite_count")]
private int favouriteCount { get; set; }
[JsonProperty(@"bpm")]
private double bpm { get; set; }
[JsonProperty(@"video")]
private bool hasVideo { get; set; }
[JsonProperty(@"storyboard")]
private bool hasStoryboard { get; set; }
[JsonProperty(@"status")]
private BeatmapSetOnlineStatus status { get; set; }
[JsonProperty(@"submitted_date")]
private DateTimeOffset submitted { get; set; }
[JsonProperty(@"ranked_date")]
private DateTimeOffset ranked { get; set; }
[JsonProperty(@"last_updated")]
private DateTimeOffset lastUpdated { get; set; }
[JsonProperty(@"user_id")]
private long creatorId
{
set { Author.Id = value; }
}
[JsonProperty(@"beatmaps")]
private IEnumerable<APIBeatmap> beatmaps { get; set; }
public BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
return new BeatmapSetInfo
{
OnlineBeatmapSetID = OnlineBeatmapSetID,
Metadata = this,
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = covers,
Preview = preview,
PlayCount = playCount,
FavouriteCount = favouriteCount,
BPM = bpm,
Status = status,
HasVideo = hasVideo,
HasStoryboard = hasStoryboard,
Submitted = submitted,
Ranked = ranked,
LastUpdated = lastUpdated,
},
Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(),
};
}
}
}

View File

@ -0,0 +1,89 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using Humanizer;
using Newtonsoft.Json;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIRecentActivity
{
[JsonProperty("id")]
public int ID;
[JsonProperty("createdAt")]
public DateTimeOffset CreatedAt;
[JsonProperty]
private string type
{
set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize());
}
public RecentActivityType Type;
[JsonProperty]
private string scoreRank
{
set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value);
}
public ScoreRank ScoreRank;
[JsonProperty("rank")]
public int Rank;
[JsonProperty("approval")]
public BeatmapApproval Approval;
[JsonProperty("count")]
public int Count;
[JsonProperty("mode")]
public string Mode;
[JsonProperty("beatmap")]
public RecentActivityBeatmap Beatmap;
[JsonProperty("beatmapset")]
public RecentActivityBeatmap Beatmapset;
[JsonProperty("user")]
public RecentActivityUser User;
[JsonProperty("achievement")]
public RecentActivityAchievement Achievement;
public class RecentActivityBeatmap
{
[JsonProperty("title")]
public string Title;
[JsonProperty("url")]
public string Url;
}
public class RecentActivityUser
{
[JsonProperty("username")]
public string Username;
[JsonProperty("url")]
public string Url;
[JsonProperty("previousUsername")]
public string PreviousUsername;
}
public class RecentActivityAchievement
{
[JsonProperty("slug")]
public string Slug;
[JsonProperty("name")]
public string Name;
}
}
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIScore : Score
{
[JsonProperty(@"score")]
private double totalScore
{
set => TotalScore = value;
}
[JsonProperty(@"max_combo")]
private int maxCombo
{
set => MaxCombo = value;
}
[JsonProperty(@"user")]
private User user
{
set => User = value;
}
[JsonProperty(@"replay_data")]
private Replay replay
{
set => Replay = value;
}
[JsonProperty(@"mode_int")]
public int OnlineRulesetID { get; set; }
[JsonProperty(@"score_id")]
private long onlineScoreID
{
set => OnlineScoreID = value;
}
[JsonProperty(@"created_at")]
private DateTimeOffset date
{
set => Date = value;
}
[JsonProperty(@"beatmap")]
private BeatmapInfo beatmap
{
set => Beatmap = value;
}
[JsonProperty(@"beatmapset")]
private BeatmapMetadata metadata
{
set => Beatmap.Metadata = value;
}
[JsonProperty(@"statistics")]
private Dictionary<string, object> jsonStats
{
set
{
foreach (var kvp in value)
{
HitResult newKey;
switch (kvp.Key)
{
case @"count_300":
newKey = HitResult.Great;
break;
case @"count_100":
newKey = HitResult.Good;
break;
case @"count_50":
newKey = HitResult.Meh;
break;
case @"count_miss":
newKey = HitResult.Miss;
break;
default:
continue;
}
Statistics.Add(newKey, kvp.Value);
}
}
}
[JsonProperty(@"mods")]
private string[] modStrings { get; set; }
public void ApplyBeatmap(BeatmapInfo beatmap)
{
Beatmap = beatmap;
ApplyRuleset(beatmap.Ruleset);
}
public void ApplyRuleset(RulesetInfo ruleset)
{
Ruleset = ruleset;
// Evaluate the mod string
Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray();
}
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIScores
{
[JsonProperty(@"scores")]
public IEnumerable<APIScore> Scores;
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 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.Users;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIUser
{
[JsonProperty]
public User User;
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 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;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIUserMostPlayedBeatmap
{
[JsonProperty("beatmap_id")]
public int BeatmapID;
[JsonProperty("count")]
public int PlayCount;
[JsonProperty]
private BeatmapInfo beatmap;
[JsonProperty]
private APIBeatmapSet beatmapSet;
public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
{
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
beatmap.BeatmapSet = setInfo;
beatmap.Metadata = setInfo.Metadata;
return beatmap;
}
}
}

View File

@ -3,13 +3,14 @@
using System.Collections.Generic;
using System.ComponentModel;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIResponseBeatmapSet>>
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIBeatmapSet>>
{
private readonly string query;
private readonly RulesetInfo ruleset;

View File

@ -56,6 +56,8 @@ namespace osu.Game
protected SettingsStore SettingsStore;
protected RulesetConfigCache RulesetConfigCache;
protected MenuCursorContainer MenuCursorContainer;
private Container content;
@ -123,6 +125,7 @@ namespace osu.Game
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
dependencies.Cache(new OsuColour());
fileImporters.Add(BeatmapManager);

View File

@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
@ -11,10 +13,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{
public class DownloadButton : HeaderButton
{
public DownloadButton(string title, string subtitle)
public DownloadButton(BeatmapSetInfo set, bool noVideo = false)
{
Width = 120;
BeatmapSetDownloader downloader;
Add(new Container
{
Depth = -1,
@ -22,6 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
Padding = new MarginPadding { Horizontal = 10 },
Children = new Drawable[]
{
downloader = new BeatmapSetDownloader(set, noVideo),
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
@ -32,13 +36,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{
new OsuSpriteText
{
Text = title,
Text = "Download",
TextSize = 13,
Font = @"Exo2.0-Bold",
},
new OsuSpriteText
{
Text = subtitle,
Text = set.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty,
TextSize = 11,
Font = @"Exo2.0-Bold",
},
@ -54,6 +58,25 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
},
},
});
Action = () =>
{
if (!downloader.Download())
{
Content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine);
}
};
downloader.Downloaded.ValueChanged += d =>
{
if (d)
this.FadeOut(200);
else
this.FadeIn(200);
};
}
}
}

View File

@ -35,8 +35,6 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly BeatmapSetOnlineStatusPill onlineStatusPill;
public Details Details;
private BeatmapManager beatmaps;
public readonly BeatmapPicker Picker;
private BeatmapSetInfo beatmapSet;
@ -68,8 +66,24 @@ namespace osu.Game.Overlays.BeatmapSet
downloadButtonsContainer.FadeIn(transition_duration);
favouriteButton.FadeIn(transition_duration);
noVideoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 0 : 1, transition_duration);
videoButtons.FadeTo(BeatmapSet.OnlineInfo.HasVideo ? 1 : 0, transition_duration);
if (BeatmapSet.OnlineInfo.HasVideo)
{
videoButtons.Children = new[]
{
new DownloadButton(BeatmapSet),
new DownloadButton(BeatmapSet, true),
};
videoButtons.FadeIn(transition_duration);
noVideoButtons.FadeOut(transition_duration);
}
else
{
noVideoButtons.Child = new DownloadButton(BeatmapSet);
noVideoButtons.FadeIn(transition_duration);
videoButtons.FadeOut(transition_duration);
}
}
else
{
@ -192,27 +206,12 @@ namespace osu.Game.Overlays.BeatmapSet
{
RelativeSizeAxes = Axes.Both,
Alpha = 0f,
Child = new DownloadButton("Download", @"")
{
Action = () => download(false),
},
},
videoButtons = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Spacing = new Vector2(buttons_spacing),
Alpha = 0f,
Children = new[]
{
new DownloadButton("Download", "with Video")
{
Action = () => download(false),
},
new DownloadButton("Download", "without Video")
{
Action = () => download(true),
},
},
},
},
},
@ -248,41 +247,10 @@ namespace osu.Game.Overlays.BeatmapSet
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, BeatmapManager beatmaps)
private void load(OsuColour colours)
{
tabsBg.Colour = colours.Gray3;
this.beatmaps = beatmaps;
beatmaps.ItemAdded += handleBeatmapAdd;
updateDisplay();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd;
}
private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() =>
{
if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
downloadButtonsContainer.FadeOut(transition_duration);
});
private void download(bool noVideo)
{
if (beatmaps.GetExistingDownload(BeatmapSet) != null)
{
downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine).Then();
return;
}
beatmaps.Download(BeatmapSet, noVideo);
}
}
}

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private readonly Box background;
public DrawableScore(int index, OnlineScore score)
public DrawableScore(int index, APIScore score)
{
ScoreModsContainer modsContainer;

View File

@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -42,8 +42,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
private readonly InfoColumn statistics;
private readonly ScoreModsContainer modsContainer;
private OnlineScore score;
public OnlineScore Score
private APIScore score;
public APIScore Score
{
get { return score; }
set

View File

@ -11,6 +11,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.BeatmapSet.Scores
{
@ -28,10 +29,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration);
}
private IEnumerable<OnlineScore> scores;
private IEnumerable<APIScore> scores;
private BeatmapInfo beatmap;
public IEnumerable<OnlineScore> Scores
public IEnumerable<APIScore> Scores
{
get { return scores; }
set

View File

@ -166,14 +166,13 @@ namespace osu.Game.Overlays.Direct
},
},
},
new DownloadButton
new DownloadButton(SetInfo)
{
Size = new Vector2(30),
Margin = new MarginPadding(horizontal_padding),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Colour = colours.Gray5,
Action = StartDownload
},
},
},

View File

@ -12,28 +12,31 @@ using osu.Game.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Beatmaps;
namespace osu.Game.Overlays.Direct
{
public class DirectListPanel : DirectPanel
{
private const float transition_duration = 120;
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private const float height = 70;
private PlayButton playButton;
private Box progressBar;
private Container downloadContainer;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap)
{
RelativeSizeAxes = Axes.X;
Height = height;
}
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation, OsuColour colours)
{
@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Direct
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = 120,
LayoutDuration = transition_duration,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
@ -104,53 +107,69 @@ namespace osu.Game.Overlays.Direct
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding },
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = transition_duration,
Children = new Drawable[]
{
new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0)
downloadContainer = new Container
{
Margin = new MarginPadding { Right = 1 },
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Alpha = 0,
Child = new DownloadButton(SetInfo)
{
Size = new Vector2(height - vertical_padding * 2),
Margin = new MarginPadding { Left = vertical_padding },
},
},
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0)
{
Text = "mapped by ",
TextSize = 14,
Margin = new MarginPadding { Right = 1 },
},
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = "mapped by ",
TextSize = 14,
},
new OsuSpriteText
{
Text = SetInfo.Metadata.Author.Username,
TextSize = 14,
Font = @"Exo2.0-SemiBoldItalic",
},
},
},
new OsuSpriteText
{
Text = SetInfo.Metadata.Author.Username,
Text = $"from {SetInfo.Metadata.Source}",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
TextSize = 14,
Font = @"Exo2.0-SemiBoldItalic",
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
},
},
},
new OsuSpriteText
{
Text = $"from {SetInfo.Metadata.Source}",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
TextSize = 14,
Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f,
},
},
},
new DownloadButton
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Size = new Vector2(height - vertical_padding * 2),
Action = StartDownload
},
},
},
progressBar = new Box
@ -165,5 +184,17 @@ namespace osu.Game.Overlays.Direct
},
});
}
protected override bool OnHover(InputState state)
{
downloadContainer.FadeIn(transition_duration, Easing.InOutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
downloadContainer.FadeOut(transition_duration, Easing.InOutQuint);
base.OnHoverLost(state);
}
}
}

View File

@ -147,22 +147,6 @@ namespace osu.Game.Overlays.Direct
protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo);
protected void StartDownload()
{
if (beatmaps.GetExistingDownload(SetInfo) != null)
{
// we already have an active download running.
content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine).Then();
return;
}
beatmaps.Download(SetInfo);
}
private void attachDownload(DownloadBeatmapSetRequest request)
{
if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID)

View File

@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using OpenTK;
@ -13,10 +15,12 @@ namespace osu.Game.Overlays.Direct
{
private readonly SpriteIcon icon;
public DownloadButton()
public DownloadButton(BeatmapSetInfo set, bool noVideo = false)
{
BeatmapSetDownloader downloader;
Children = new Drawable[]
{
downloader = new BeatmapSetDownloader(set, noVideo),
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
@ -25,6 +29,25 @@ namespace osu.Game.Overlays.Direct
Icon = FontAwesome.fa_osu_chevron_down_o,
},
};
Action = () =>
{
if (!downloader.Download())
{
Content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine);
}
};
downloader.Downloaded.ValueChanged += d =>
{
if (d)
this.FadeOut(200);
else
this.FadeIn(200);
};
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)

View File

@ -28,7 +28,6 @@ namespace osu.Game.Overlays
private APIAccess api;
private RulesetStore rulesets;
private BeatmapManager beatmaps;
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
@ -177,24 +176,14 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps)
private void load(OsuColour colours, APIAccess api, RulesetStore rulesets)
{
this.api = api;
this.rulesets = rulesets;
this.beatmaps = beatmaps;
resultCountsContainer.Colour = colours.Yellow;
beatmaps.ItemAdded += setAdded;
}
private void setAdded(BeatmapSetInfo set) => Schedule(() =>
{
// if a new map was imported, we should remove it from search results (download completed etc.)
panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire();
BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID);
});
private void updateResultCounts()
{
resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint);
@ -297,9 +286,7 @@ namespace osu.Game.Overlays
{
Task.Run(() =>
{
var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList();
var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList();
var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList();
var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList();
// may not need scheduling; loads async internally.
Schedule(() =>
@ -323,14 +310,6 @@ namespace osu.Game.Overlays
private int distinctCount(List<string> list) => list.Distinct().ToArray().Length;
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (beatmaps != null)
beatmaps.ItemAdded -= setAdded;
}
public class ResultCounts
{
public readonly int Artists;

View File

@ -107,13 +107,20 @@ namespace osu.Game.Overlays.Profile.Header
visibleBadge = 0;
badgeFlowContainer.Clear();
foreach (var badge in badges)
for (var index = 0; index < badges.Length; index++)
{
LoadComponentAsync(new DrawableBadge(badge)
int displayIndex = index;
LoadComponentAsync(new DrawableBadge(badges[index])
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}, badgeFlowContainer.Add);
}, asyncBadge =>
{
badgeFlowContainer.Add(asyncBadge);
// load in stable order regardless of async load order.
badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex);
});
}
}

View File

@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Sections
{
Action = () =>
{
if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value);
if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value);
};
Child = new FillFlowContainer

View File

@ -8,6 +8,7 @@ using osu.Game.Online.API.Requests;
using osu.Game.Users;
using System;
using System.Linq;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.Profile.Sections.Ranks
{
@ -49,7 +50,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
MissingText.Hide();
foreach (OnlineScore score in scores)
foreach (APIScore score in scores)
{
DrawableProfileScore drawableScore;

View File

@ -8,6 +8,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Chat;
using osu.Game.Screens.Select.Leaderboards;
@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
{
private APIAccess api;
private readonly RecentActivity activity;
private readonly APIRecentActivity activity;
private LinkFlowContainer content;
public DrawableRecentActivity(RecentActivity activity)
public DrawableRecentActivity(APIRecentActivity activity)
{
this.activity = activity;
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
using System.Linq;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.Profile.Sections.Recent
{
@ -36,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
MissingText.Hide();
foreach (RecentActivity activity in activities)
foreach (APIRecentActivity activity in activities)
{
ItemsContainer.Add(new DrawableRecentActivity(activity));
}

View File

@ -0,0 +1,35 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Settings
{
/// <summary>
/// A <see cref="SettingsSubsection"/> which provides subclasses with the <see cref="IRulesetConfigManager"/>
/// from the <see cref="Ruleset"/>'s <see cref="Ruleset.CreateConfig()"/>.
/// </summary>
public abstract class RulesetSettingsSubsection : SettingsSubsection
{
private readonly Ruleset ruleset;
protected RulesetSettingsSubsection(Ruleset ruleset)
{
this.ruleset = ruleset;
}
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
var config = dependencies.Get<RulesetConfigCache>().GetConfigFor(ruleset);
if (config != null)
dependencies.Cache(config);
return dependencies;
}
}
}

View File

@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
@ -38,6 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
LabelText = "Always show key overlay",
Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay)
},
new SettingsEnumDropdown<ScoringMode>
{
LabelText = "Score display mode",
Bindable = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode)
}
};
}
}

View File

@ -167,9 +167,25 @@ namespace osu.Game.Overlays.Volume
private set => Bindable.Value = value;
}
public void Increase() => Volume += 0.05f;
private const float adjust_step = 0.05f;
public void Decrease() => Volume -= 0.05f;
public void Increase() => adjust(1);
public void Decrease() => adjust(-1);
private void adjust(int direction)
{
float amount = adjust_step * direction;
var mouse = GetContainingInputManager().CurrentState.Mouse;
if (mouse.HasPreciseScroll)
{
float scrollDelta = mouse.ScrollDelta.Y;
if (scrollDelta != 0)
amount *= Math.Abs(scrollDelta / 10);
}
Volume += amount;
}
public bool OnPressed(GlobalAction action)
{

View File

@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Configuration
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
where T : struct
{
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant)
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
@ -36,6 +37,44 @@ namespace osu.Game.Rulesets.Difficulty
{
}
/// <summary>
/// Creates all <see cref="Mod"/> combinations which adjust the <see cref="Beatmap"/> difficulty.
/// </summary>
public Mod[] CreateDifficultyAdjustmentModCombinations()
{
return createDifficultyAdjustmentModCombinations(Enumerable.Empty<Mod>(), DifficultyAdjustmentMods).ToArray();
IEnumerable<Mod> createDifficultyAdjustmentModCombinations(IEnumerable<Mod> currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0)
{
// Initial-case: Empty current set
if (currentSetCount == 0)
yield return new NoModMod();
if (currentSetCount == 1)
yield return currentSet.Single();
if (currentSetCount > 1)
yield return new MultiMod(currentSet.ToArray());
// Apply mods in the adjustment set recursively. Using the entire adjustment set would result in duplicate multi-mod mod
// combinations in further recursions, so a moving subset is used to eliminate this effect
for (int i = adjustmentSetStart; i < adjustmentSet.Length; i++)
{
var adjustmentMod = adjustmentSet[i];
if (currentSet.Any(c => c.IncompatibleMods.Any(m => m.IsInstanceOfType(adjustmentMod))))
continue;
foreach (var combo in createDifficultyAdjustmentModCombinations(currentSet.Append(adjustmentMod), adjustmentSet, currentSetCount + 1, i + 1))
yield return combo;
}
}
}
/// <summary>
/// Retrieves all <see cref="Mod"/>s which adjust the <see cref="Beatmap"/> difficulty.
/// </summary>
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
}

View File

@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mods
public virtual void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
// todo: fix ordering of objects so we don't have to do this (#2740).
foreach (var d in drawables.Reverse().Skip(IncreaseFirstObjectVisibility ? 1 : 0))
foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility ? 1 : 0))
d.ApplyCustomUpdateState += ApplyHiddenState;
}

View File

@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mods
{
DifficultyReduction,
DifficultyIncrease,
Special,
Special
}
}

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
namespace osu.Game.Rulesets.Mods
{
public class MultiMod : Mod
@ -10,6 +13,13 @@ namespace osu.Game.Rulesets.Mods
public override string Description => string.Empty;
public override double ScoreMultiplier => 0;
public Mod[] Mods;
public Mod[] Mods { get; }
public MultiMod(params Mod[] mods)
{
Mods = mods;
}
public override Type[] IncompatibleMods => Mods.SelectMany(m => m.IncompatibleMods).ToArray();
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mods
{
/// <summary>
/// Indicates a type of mod that doesn't do anything.
/// </summary>
public sealed class NoModMod : Mod
{
public override string Name => "No Mod";
public override string ShortenedName => "NM";
public override double ScoreMultiplier => 1;
}
}

View File

@ -15,6 +15,8 @@ using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets
@ -69,7 +71,13 @@ namespace osu.Game.Rulesets
public abstract string Description { get; }
public virtual SettingsSubsection CreateSettings() => null;
public virtual RulesetSettingsSubsection CreateSettings() => null;
/// <summary>
/// Creates the <see cref="IRulesetConfigManager"/> for this <see cref="Ruleset"/>.
/// </summary>
/// <param name="settings">The <see cref="SettingsStore"/> to store the settings.</param>
public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null;
/// <summary>
/// Do not override this unless you are a legacy mode.

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets
{
/// <summary>
/// A cache that provides a single <see cref="IRulesetConfigManager"/> per-ruleset.
/// This is done to support referring to and updating ruleset configs from multiple locations in the absence of inter-config bindings.
/// </summary>
public class RulesetConfigCache : Component
{
private readonly Dictionary<int, IRulesetConfigManager> configCache = new Dictionary<int, IRulesetConfigManager>();
private readonly SettingsStore settingsStore;
public RulesetConfigCache(SettingsStore settingsStore)
{
this.settingsStore = settingsStore;
}
/// <summary>
/// Retrieves the <see cref="IRulesetConfigManager"/> for a <see cref="Ruleset"/>.
/// </summary>
/// <param name="ruleset">The <see cref="Ruleset"/> to retrieve the <see cref="IRulesetConfigManager"/> for.</param>
/// <returns>The <see cref="IRulesetConfigManager"/> defined by <paramref name="ruleset"/>, null if <paramref name="ruleset"/> doesn't define one.</returns>
/// <exception cref="InvalidOperationException">If <paramref name="ruleset"/> doesn't have a valid <see cref="RulesetInfo.ID"/>.</exception>
public IRulesetConfigManager GetConfigFor(Ruleset ruleset)
{
if (ruleset.RulesetInfo.ID == null)
throw new InvalidOperationException("The provided ruleset doesn't have a valid id.");
if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing))
return existing;
return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore);
}
}
}

View File

@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public readonly BindableInt HighestCombo = new BindableInt();
/// <summary>
/// The <see cref="ScoringMode"/> used to calculate scores.
/// </summary>
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
/// <summary>
/// Whether all <see cref="Judgement"/>s have been processed.
/// </summary>
@ -169,8 +174,6 @@ namespace osu.Game.Rulesets.Scoring
private const double combo_portion = 0.7;
private const double max_score = 1000000;
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
protected sealed override bool HasCompleted => JudgedHits == MaxHits;
protected int MaxHits { get; private set; }
@ -199,16 +202,18 @@ namespace osu.Game.Rulesets.Scoring
if (maxBaseScore == 0 || maxHighestCombo == 0)
{
Mode.Value = ScoringMode.Exponential;
Mode.Value = ScoringMode.Classic;
Mode.Disabled = true;
}
Mode.ValueChanged += _ => updateScore();
}
/// <summary>
/// Simulates an autoplay of <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>
/// by adding <see cref="Judgement"/>s for each <see cref="HitObject"/> in the <see cref="Beatmap{TObject}"/>.
/// <para>
/// This is required for <see cref="ScoringMode.Standardised"/> to work, otherwise <see cref="ScoringMode.Exponential"/> will be used.
/// This is required for <see cref="ScoringMode.Standardised"/> to work, otherwise <see cref="ScoringMode.Classic"/> will be used.
/// </para>
/// </summary>
/// <param name="beatmap">The <see cref="Beatmap{TObject}"/> containing the <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/>.</param>
@ -295,8 +300,9 @@ namespace osu.Game.Rulesets.Scoring
case ScoringMode.Standardised:
TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore;
break;
case ScoringMode.Exponential:
TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2);
case ScoringMode.Classic:
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
TotalScore.Value = bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25);
break;
}
}
@ -322,6 +328,6 @@ namespace osu.Game.Rulesets.Scoring
public enum ScoringMode
{
Standardised,
Exponential
Classic
}
}

View File

@ -11,8 +11,8 @@ namespace osu.Game.Rulesets.UI
{
public class HitObjectContainer : CompositeDrawable
{
public virtual IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>();
public virtual IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>();
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
public virtual void Add(DrawableHitObject hitObject) => AddInternal(hitObject);
public virtual bool Remove(DrawableHitObject hitObject) => RemoveInternal(hitObject);

View File

@ -73,11 +73,6 @@ namespace osu.Game.Rulesets.UI
private IRulesetConfigManager rulesetConfig;
private OnScreenDisplay onScreenDisplay;
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
/// <summary>
/// A visual representation of a <see cref="Rulesets.Ruleset"/>.
/// </summary>
@ -90,18 +85,20 @@ namespace osu.Game.Rulesets.UI
Cursor = CreateCursor();
}
[BackgroundDependencyLoader(true)]
private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings)
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
this.onScreenDisplay = onScreenDisplay;
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
rulesetConfig = CreateConfig(Ruleset, settings);
onScreenDisplay = dependencies.Get<OnScreenDisplay>();
rulesetConfig = dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset);
if (rulesetConfig != null)
{
dependencies.Cache(rulesetConfig);
onScreenDisplay?.BeginTracking(this, rulesetConfig);
}
return dependencies;
}
public abstract ScoreProcessor CreateScoreProcessor();
@ -136,8 +133,6 @@ namespace osu.Game.Rulesets.UI
/// </summary>
protected virtual CursorContainer CreateCursor() => null;
protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null;
/// <summary>
/// Creates a Playfield.
/// </summary>

View File

@ -121,6 +121,8 @@ namespace osu.Game.Rulesets.UI
/// </summary>
private bool validState;
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
private bool isAttached => replayInputHandler != null && !UseParentState;
private const int max_catch_up_updates_per_frame = 50;

View File

@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
Child = new TimelineArea { RelativeSizeAxes = Axes.Both }
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},

View File

@ -1,31 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class BeatmapWaveformGraph : CompositeDrawable
{
public WorkingBeatmap Beatmap { set => graph.Waveform = value.Waveform; }
private readonly WaveformGraph graph;
public BeatmapWaveformGraph()
{
InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both };
}
/// <summary>
/// Gets or sets the <see cref="WaveformGraph.Resolution"/>.
/// </summary>
public float Resolution
{
get { return graph.Resolution; }
set { graph.Resolution = value; }
}
}
}

View File

@ -1,126 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class ScrollableTimeline : CompositeDrawable
{
private readonly ScrollingTimelineContainer timelineContainer;
public ScrollableTimeline()
{
Masking = true;
CornerRadius = 5;
OsuCheckbox hitObjectsCheckbox;
OsuCheckbox hitSoundsCheckbox;
OsuCheckbox waveformCheckbox;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("111")
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222")
},
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Y,
Width = 160,
Padding = new MarginPadding { Horizontal = 15 },
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4),
Children = new[]
{
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
}
}
}
},
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("333")
},
new Container<TimelineButton>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[]
{
new TimelineButton
{
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_plus,
Action = () => timelineContainer.Zoom++
},
new TimelineButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_minus,
Action = () => timelineContainer.Zoom--
},
}
}
}
},
timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y }
}
}
};
hitObjectsCheckbox.Current.Value = true;
hitSoundsCheckbox.Current.Value = true;
waveformCheckbox.Current.Value = true;
timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current);
}
protected override void Update()
{
base.Update();
timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1);
}
}
}

View File

@ -1,151 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class ScrollingTimelineContainer : ScrollContainer
{
public readonly Bindable<bool> HitObjectsVisible = new Bindable<bool>();
public readonly Bindable<bool> HitSoundsVisible = new Bindable<bool>();
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private readonly BeatmapWaveformGraph waveform;
public ScrollingTimelineContainer()
: base(Direction.Horizontal)
{
Masking = true;
Add(waveform = new BeatmapWaveformGraph
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222"),
Depth = float.MaxValue
});
Content.AutoSizeAxes = Axes.None;
Content.RelativeSizeAxes = Axes.Both;
WaveformVisible.ValueChanged += waveformVisibilityChanged;
Zoom = 10;
}
[BackgroundDependencyLoader]
private void load(IBindableBeatmap beatmap)
{
this.beatmap.BindTo(beatmap);
this.beatmap.BindValueChanged(beatmapChanged, true);
}
private void beatmapChanged(WorkingBeatmap beatmap) => waveform.Beatmap = beatmap;
private float minZoom = 1;
/// <summary>
/// The minimum zoom level allowed.
/// </summary>
public float MinZoom
{
get { return minZoom; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
if (minZoom == value)
return;
minZoom = value;
// Update the zoom level
Zoom = Zoom;
}
}
private float maxZoom = 30;
/// <summary>
/// The maximum zoom level allowed.
/// </summary>
public float MaxZoom
{
get { return maxZoom; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
if (maxZoom == value)
return;
maxZoom = value;
// Update the zoom level
Zoom = Zoom;
}
}
private float zoom = 1;
/// <summary>
/// The current zoom level.
/// </summary>
public float Zoom
{
get { return zoom; }
set
{
value = MathHelper.Clamp(value, MinZoom, MaxZoom);
if (zoom == value)
return;
zoom = value;
// Make the zoom target default to the center of the graph if it hasn't been set
if (relativeContentZoomTarget == null)
relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X;
if (localZoomTarget == null)
localZoomTarget = DrawSize.X / 2;
Content.ResizeWidthTo(Zoom);
// Update the scroll position to focus on the zoom target
float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value;
ScrollTo(scrollPos, false);
relativeContentZoomTarget = null;
localZoomTarget = null;
}
}
/// <summary>
/// Zoom target as a relative position in the <see cref="ScrollingTimelineContainer.Content"/> space.
/// </summary>
private float? relativeContentZoomTarget;
/// <summary>
/// Zoom target as a position in our local space.
/// </summary>
private float? localZoomTarget;
protected override bool OnScroll(InputState state)
{
if (!state.Keyboard.ControlPressed)
return base.OnScroll(state);
relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X;
localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X;
Zoom += state.Mouse.ScrollDelta.Y;
return true;
}
private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class Timeline : ZoomableScrollContainer
{
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
public Timeline()
{
ZoomDuration = 200;
ZoomEasing = Easing.OutQuint;
Zoom = 10;
}
private WaveformGraph waveform;
[BackgroundDependencyLoader]
private void load(IBindableBeatmap beatmap)
{
Child = waveform = new WaveformGraph
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222"),
Depth = float.MaxValue
};
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
Beatmap.BindTo(beatmap);
}
protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform);
waveform.Waveform = Beatmap.Value.Waveform;
}
protected override void Update()
{
base.Update();
// We want time = 0 to be at the centre of the container when scrolled to the start
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
}
}
}

View File

@ -0,0 +1,128 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class TimelineArea : CompositeDrawable
{
private readonly Timeline timeline;
public TimelineArea()
{
Masking = true;
CornerRadius = 5;
OsuCheckbox hitObjectsCheckbox;
OsuCheckbox hitSoundsCheckbox;
OsuCheckbox waveformCheckbox;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("111")
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222")
},
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Y,
Width = 160,
Padding = new MarginPadding { Horizontal = 15 },
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4),
Children = new[]
{
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
}
}
}
},
new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("333")
},
new Container<TimelineButton>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[]
{
new TimelineButton
{
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_plus,
Action = () => timeline.Zoom++
},
new TimelineButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_minus,
Action = () => timeline.Zoom--
},
}
}
}
},
timeline = new Timeline { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Distributed),
}
}
};
hitObjectsCheckbox.Current.Value = true;
hitSoundsCheckbox.Current.Value = true;
waveformCheckbox.Current.Value = true;
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
}
}
}

View File

@ -0,0 +1,174 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using OpenTK;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class ZoomableScrollContainer : ScrollContainer
{
/// <summary>
/// The time to zoom into/out of a point.
/// All user scroll input will be overwritten during the zoom transform.
/// </summary>
public double ZoomDuration;
/// <summary>
/// The easing with which to transform the zoom.
/// </summary>
public Easing ZoomEasing;
private readonly Container zoomedContent;
protected override Container<Drawable> Content => zoomedContent;
private float currentZoom = 1;
public ZoomableScrollContainer()
: base(Direction.Horizontal)
{
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
}
private int minZoom = 1;
/// <summary>
/// The minimum zoom level allowed.
/// </summary>
public int MinZoom
{
get => minZoom;
set
{
if (value < 1)
throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
minZoom = value;
if (Zoom < value)
Zoom = value;
}
}
private int maxZoom = 60;
/// <summary>
/// The maximum zoom level allowed.
/// </summary>
public int MaxZoom
{
get => maxZoom;
set
{
if (value < 1)
throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
maxZoom = value;
if (Zoom > value)
Zoom = value;
}
}
/// <summary>
/// Gets or sets the content zoom level of this <see cref="ZoomableScrollContainer"/>.
/// </summary>
public float Zoom
{
get => zoomTarget;
set
{
value = MathHelper.Clamp(value, MinZoom, MaxZoom);
if (IsLoaded)
setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
else
currentZoom = zoomTarget = value;
}
}
protected override void Update()
{
base.Update();
zoomedContent.Width = DrawWidth * currentZoom;
}
protected override bool OnScroll(InputState state)
{
if (!state.Keyboard.ControlPressed)
return base.OnScroll(state);
setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
return true;
}
private float zoomTarget = 1;
private void setZoomTarget(float newZoom, float focusPoint)
{
zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom);
transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing);
}
private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None)
=> this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing));
private class TransformZoom : Transform<float, ZoomableScrollContainer>
{
/// <summary>
/// The focus point in absolute coordinates local to the content.
/// </summary>
private readonly float focusPoint;
/// <summary>
/// The size of the content.
/// </summary>
private readonly float contentSize;
/// <summary>
/// The scroll offset at the start of the transform.
/// </summary>
private readonly float scrollOffset;
/// <summary>
/// Transforms <see cref="TimeTimelinem"/> to a new value.
/// </summary>
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
/// <param name="contentSize">The size of the content.</param>
/// <param name="scrollOffset">The scroll offset at the start of the transform.</param>
public TransformZoom(float focusPoint, float contentSize, float scrollOffset)
{
this.focusPoint = focusPoint;
this.contentSize = contentSize;
this.scrollOffset = scrollOffset;
}
public override string TargetMember => nameof(currentZoom);
private float valueAt(double time)
{
if (time < StartTime) return StartValue;
if (time >= EndTime) return EndValue;
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
}
protected override void Apply(ZoomableScrollContainer d, double time)
{
float newZoom = valueAt(time);
float focusOffset = focusPoint - scrollOffset;
float expectedWidth = d.DrawWidth * newZoom;
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
d.currentZoom = newZoom;
d.ScrollTo(targetOffset, false);
}
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
}
}
}

View File

@ -18,7 +18,8 @@ namespace osu.Game.Screens.Play
{
private const int duration = 100;
private Bindable<bool> showKeyCounter;
public readonly Bindable<bool> Visible = new Bindable<bool>(true);
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
public KeyCounterCollection()
{
@ -46,9 +47,10 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
showKeyCounter = config.GetBindable<bool>(OsuSetting.KeyOverlay);
showKeyCounter.ValueChanged += keyCounterVisibility => this.FadeTo(keyCounterVisibility ? 1 : 0, duration);
showKeyCounter.TriggerChange();
config.BindWith(OsuSetting.KeyOverlay, configVisibility);
Visible.BindValueChanged(_ => updateVisibility());
configVisibility.BindValueChanged(_ => updateVisibility(), true);
}
//further: change default values here and in KeyCounter if needed, instead of passing them in every constructor
@ -111,6 +113,8 @@ namespace osu.Game.Screens.Play
}
}
private void updateVisibility() => this.FadeTo(Visible.Value || configVisibility.Value ? 1 : 0, duration);
public override bool HandleKeyboardInput => receptor == null;
public override bool HandleMouseInput => receptor == null;

View File

@ -158,6 +158,7 @@ namespace osu.Game.Screens.Play
userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor();
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
Children = new Drawable[]
{
@ -228,6 +229,7 @@ namespace osu.Game.Screens.Play
};
hudOverlay.HoldToQuit.Action = Exit;
hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
if (ShowStoryboard)
initializeStoryboard(false);

View File

@ -15,12 +15,12 @@
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Humanizer" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2018.607.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="ppy.osu.Framework" Version="2018.611.1" />
<PackageReference Include="SharpCompress" Version="0.18.1" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@ -11,9 +11,9 @@
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="DeepEqual" Version="1.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />
</ItemGroup>
@ -25,4 +25,4 @@
<PropertyGroup>
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
</Project>
</Project>