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

Merge branch 'master' into select_resolution_in_fullscreen_mode

This commit is contained in:
UselessToucan 2018-06-12 18:01:30 +03:00 committed by GitHub
commit b6c0aa3f26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1615 additions and 1056 deletions

View File

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

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{ {
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting> 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) : base(settings, ruleset, variant)
{ {
} }

View File

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

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -24,5 +26,18 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns = KeyCount; 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) if (action != Action)
return false; return false;
var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault(); var nextObject =
hitObject?.PlaySamples(); 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; return true;
} }

View File

@ -10,12 +10,9 @@ using osu.Framework.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays; 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 Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); 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.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty namespace osu.Game.Rulesets.Osu.Difficulty
@ -71,5 +72,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return starRating; 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 OsuModEasy(),
new OsuModNoFail(), new OsuModNoFail(),
new MultiMod new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
{
Mods = new Mod[]
{
new OsuModHalfTime(),
new OsuModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:
return new Mod[] return new Mod[]
{ {
new OsuModHardRock(), new OsuModHardRock(),
new MultiMod new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
{ new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
Mods = new Mod[]
{
new OsuModSuddenDeath(),
new OsuModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new OsuModDoubleTime(),
new OsuModNightcore(),
},
},
new OsuModHidden(), new OsuModHidden(),
new OsuModFlashlight(), new OsuModFlashlight(),
}; };
case ModType.Special: case ModType.Special:
return new Mod[] return new Mod[]
{ {
new OsuModRelax(), new OsuModRelax(),
new OsuModAutopilot(), new OsuModAutopilot(),
new OsuModSpunOut(), new OsuModSpunOut(),
new MultiMod new MultiMod(new OsuModAutoplay(), new ModCinema()),
{
Mods = new Mod[]
{
new OsuModAutoplay(),
new ModCinema(),
},
},
new OsuModTarget(), new OsuModTarget(),
}; };
default: default:
return new Mod[] { }; return new Mod[] { };
} }
@ -161,7 +130,7 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu"; public override string ShortName => "osu";
public override SettingsSubsection CreateSettings() => new OsuSettings(); public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
public override int? LegacyID => 0; public override int? LegacyID => 0;

View File

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

View File

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

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Difficulty namespace osu.Game.Rulesets.Taiko.Difficulty
@ -62,6 +63,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return starRating; return starRating;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
private bool calculateStrainValues() private bool calculateStrainValues()
{ {
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. // 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 TaikoModEasy(),
new TaikoModNoFail(), new TaikoModNoFail(),
new MultiMod new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()),
{
Mods = new Mod[]
{
new TaikoModHalfTime(),
new TaikoModDaycore(),
},
},
}; };
case ModType.DifficultyIncrease: case ModType.DifficultyIncrease:
return new Mod[] return new Mod[]
{ {
new TaikoModHardRock(), new TaikoModHardRock(),
new MultiMod new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
{ new MultiMod(new TaikoModDoubleTime(), new TaikoModDaycore()),
Mods = new Mod[]
{
new TaikoModSuddenDeath(),
new TaikoModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModNightcore(),
},
},
new TaikoModHidden(), new TaikoModHidden(),
new TaikoModFlashlight(), new TaikoModFlashlight(),
}; };
case ModType.Special: case ModType.Special:
return new Mod[] return new Mod[]
{ {
new TaikoModRelax(), new TaikoModRelax(),
null, null,
null, null,
new MultiMod new MultiMod(new TaikoModAutoplay(), new ModCinema()),
{
Mods = new Mod[]
{
new TaikoModAutoplay(),
new ModCinema(),
},
},
}; };
default: default:
return new Mod[] { }; return new Mod[] { };
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual(string.Empty, metadata.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); 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 beatmap = decodeAsJson(normal);
var meta = beatmap.BeatmapInfo.Metadata; 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.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); 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); var reader = new ZipArchiveReader(osz);
BeatmapMetadata meta; Beatmap beatmap;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
meta = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
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.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); 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(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
OnlineBeatmapSetID = id,
// Create random metadata, then we can check if sorting works based on these // Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{id.ToString().PadLeft(6, '0')}", Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
Title = $"test set #{id}!", Title = $"test set #{id}!",
@ -503,7 +502,6 @@ namespace osu.Game.Tests.Visual
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
OnlineBeatmapSetID = id,
// Create random metadata, then we can check if sorting works based on these // Create random metadata, then we can check if sorting works based on these
Artist = $"peppy{id.ToString().PadLeft(6, '0')}", Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
Title = $"test set #{id}!", Title = $"test set #{id}!",

View File

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

View File

@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
[TestFixture] [TestFixture]
public class TestCaseEditorComposeTimeline : OsuTestCase 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() public TestCaseEditorComposeTimeline()
{ {
@ -27,11 +27,12 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
State = Visibility.Visible State = Visibility.Visible
}, },
new ScrollableTimeline new TimelineArea
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = 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(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
OnlineBeatmapSetID = 1234 + i,
// Create random metadata, then we can check if sorting works based on these // Create random metadata, then we can check if sorting works based on these
Artist = "MONACA " + RNG.Next(0, 9), Artist = "MONACA " + RNG.Next(0, 9),
Title = "Black Song " + 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Tests.Visual 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", Title = @"Dummy beatmap",
Url = "/b/1337", Url = "/b/1337",
}; };
var dummyUser = new RecentActivity.RecentActivityUser var dummyUser = new APIRecentActivity.RecentActivityUser
{ {
Username = "DummyReborn", Username = "DummyReborn",
Url = "/u/666", Url = "/u/666",
@ -66,61 +67,61 @@ namespace osu.Game.Tests.Visual
return new[] return new[]
{ {
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.Achievement, Type = RecentActivityType.Achievement,
Achievement = new RecentActivity.RecentActivityAchievement Achievement = new APIRecentActivity.RecentActivityAchievement
{ {
Name = @"Feelin' It", Name = @"Feelin' It",
Slug = @"all-secret-feelinit", Slug = @"all-secret-feelinit",
}, },
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapPlaycount, Type = RecentActivityType.BeatmapPlaycount,
Count = 1337, Count = 1337,
Beatmap = dummyBeatmap, Beatmap = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetApprove, Type = RecentActivityType.BeatmapsetApprove,
Approval = BeatmapApproval.Qualified, Approval = BeatmapApproval.Qualified,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetDelete, Type = RecentActivityType.BeatmapsetDelete,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetRevive, Type = RecentActivityType.BeatmapsetRevive,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetRevive, Type = RecentActivityType.BeatmapsetRevive,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetUpdate, Type = RecentActivityType.BeatmapsetUpdate,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.BeatmapsetUpload, Type = RecentActivityType.BeatmapsetUpload,
Beatmapset = dummyBeatmap, Beatmapset = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.Rank, Type = RecentActivityType.Rank,
@ -128,29 +129,29 @@ namespace osu.Game.Tests.Visual
Mode = "osu!", Mode = "osu!",
Beatmap = dummyBeatmap, Beatmap = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.RankLost, Type = RecentActivityType.RankLost,
Mode = "osu!", Mode = "osu!",
Beatmap = dummyBeatmap, Beatmap = dummyBeatmap,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.UsernameChange, Type = RecentActivityType.UsernameChange,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.UserSupportAgain, Type = RecentActivityType.UserSupportAgain,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.UserSupportFirst, Type = RecentActivityType.UserSupportFirst,
}, },
new RecentActivity new APIRecentActivity
{ {
User = dummyUser, User = dummyUser,
Type = RecentActivityType.UserSupportGift, Type = RecentActivityType.UserSupportGift,

View File

@ -6,11 +6,11 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -40,14 +40,13 @@ namespace osu.Game.Tests.Visual
for (int i = 1; i <= 16; i *= 2) for (int i = 1; i <= 16; i *= 2)
{ {
var newDisplay = new BeatmapWaveformGraph var newDisplay = new WaveformGraph
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Resolution = 1f / i, Resolution = 1f / i,
Beatmap = Beatmap
}; };
Beatmap.ValueChanged += b => newDisplay.Beatmap = b; Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
flow.Add(new Container 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; public int BeatmapVersion;
private int? onlineBeatmapID; private int? onlineBeatmapID;
private int? onlineBeatmapSetID;
[JsonProperty("id")] [JsonProperty("id")]
public int? OnlineBeatmapID public int? OnlineBeatmapID
@ -32,19 +31,10 @@ namespace osu.Game.Beatmaps
set { onlineBeatmapID = value > 0 ? value : null; } set { onlineBeatmapID = value > 0 ? value : null; }
} }
[JsonProperty("beatmapset_id")]
[NotMapped]
public int? OnlineBeatmapSetID
{
get { return onlineBeatmapSetID; }
set { onlineBeatmapSetID = value > 0 ? value : null; }
}
[JsonIgnore] [JsonIgnore]
public int BeatmapSetInfoID { get; set; } public int BeatmapSetInfoID { get; set; }
[Required] [Required]
[JsonIgnore]
public BeatmapSetInfo BeatmapSet { get; set; } public BeatmapSetInfo BeatmapSet { get; set; }
public BeatmapMetadata Metadata { 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; (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
BeatmapSet.Hash == other.BeatmapSet.Hash && BeatmapSet.Hash == other.BeatmapSet.Hash &&
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
/// <summary> /// <summary>
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>. /// 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) 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) foreach (BeatmapInfo b in model.Beatmaps)
{
// remove metadata from difficulties where it matches the set
if (model.Metadata.Equals(b.Metadata)) if (model.Metadata.Equals(b.Metadata))
b.Metadata = null; 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) protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
@ -99,18 +118,6 @@ namespace osu.Game.Beatmaps
return existingHashMatch; 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; return null;
} }
@ -306,29 +313,29 @@ namespace osu.Game.Beatmaps
return hashable.ComputeSHA2Hash(); 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. // let's make sure there are actually .osu files to import.
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive."); 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))) 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 return new BeatmapSetInfo
{ {
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID, OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
Beatmaps = new List<BeatmapInfo>(), Beatmaps = new List<BeatmapInfo>(),
Hash = computeBeatmapSetHash(reader), Hash = computeBeatmapSetHash(reader),
Metadata = metadata Metadata = beatmap.Metadata
}; };
} }
/// <summary> /// <summary>
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive. /// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
/// </summary> /// </summary>
private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader) private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
{ {
var beatmapInfos = new List<BeatmapInfo>(); var beatmapInfos = new List<BeatmapInfo>();
@ -348,10 +355,6 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); 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. // 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) if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
beatmap.BeatmapInfo.OnlineBeatmapID = null; beatmap.BeatmapInfo.OnlineBeatmapID = null;
@ -376,6 +379,40 @@ namespace osu.Game.Beatmaps
return beatmapInfos; 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> /// <summary>
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
/// </summary> /// </summary>

View File

@ -17,16 +17,6 @@ namespace osu.Game.Beatmaps
[JsonIgnore] [JsonIgnore]
public int ID { get; set; } 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 Title { get; set; }
public string TitleUnicode { get; set; } public string TitleUnicode { get; set; }
public string Artist { get; set; } public string Artist { get; set; }
@ -82,8 +72,7 @@ namespace osu.Game.Beatmaps
if (other == null) if (other == null)
return false; return false;
return onlineBeatmapSetID == other.onlineBeatmapSetID return Title == other.Title
&& Title == other.Title
&& TitleUnicode == other.TitleUnicode && TitleUnicode == other.TitleUnicode
&& Artist == other.Artist && Artist == other.Artist
&& ArtistUnicode == other.ArtistUnicode && ArtistUnicode == other.ArtistUnicode

View File

@ -22,18 +22,18 @@ namespace osu.Game.Beatmaps
[NotMapped] [NotMapped]
public BeatmapSetOnlineInfo OnlineInfo { get; set; } public BeatmapSetOnlineInfo OnlineInfo { get; set; }
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0;
[NotMapped] [NotMapped]
public bool DeletePending { get; set; } public bool DeletePending { get; set; }
public string Hash { 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 List<BeatmapSetFileInfo> Files { get; set; }
public override string ToString() => Metadata.ToString(); public override string ToString() => Metadata?.ToString() ?? base.ToString();
public bool Protected { get; set; } public bool Protected { get; set; }
} }

View File

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

View File

@ -13,13 +13,13 @@ namespace osu.Game.Configuration
{ {
private readonly SettingsStore settings; private readonly SettingsStore settings;
private readonly int variant; private readonly int? variant;
private readonly List<DatabasedSetting> databasedSettings; private readonly List<DatabasedSetting> databasedSettings;
private readonly RulesetInfo ruleset; 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.settings = settings;
this.ruleset = ruleset; this.ruleset = ruleset;

View File

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

View File

@ -14,16 +14,19 @@ namespace osu.Game.Online.API
{ {
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri); protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
public T Result => ((JsonWebRequest<T>)WebRequest).ResponseObject;
protected APIRequest() protected APIRequest()
{ {
base.Success += onSuccess; base.Success += onSuccess;
} }
private void onSuccess() private void onSuccess() => Success?.Invoke(Result);
{
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
}
/// <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; public new event APISuccessHandler<T> Success;
} }
@ -52,7 +55,16 @@ namespace osu.Game.Online.API
protected APIAccess API; protected APIAccess API;
protected WebRequest WebRequest; 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; 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; public event APIFailureHandler Failure;
private bool cancelled; 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>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetBeatmapDetailsRequest : APIRequest<GetBeatmapDetailsResponse> public class GetBeatmapDetailsRequest : APIRequest<APIBeatmapMetrics>
{ {
private readonly BeatmapInfo beatmap; 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) public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
{ {
this.beatmap = beatmap; this.beatmap = beatmap;
} }
protected override string Target => $@"beatmaps/{lookupString}"; protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}";
}
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;
}
}
} }
} }

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

View File

@ -2,20 +2,15 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; 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.Game.Screens.Select.Leaderboards;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetScoresRequest : APIRequest<GetScoresResponse> public class GetScoresRequest : APIRequest<APIScores>
{ {
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
private readonly LeaderboardScope scope; private readonly LeaderboardScope scope;
@ -36,9 +31,9 @@ namespace osu.Game.Online.API.Requests
Success += onSuccess; 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); score.ApplyBeatmap(beatmap);
} }
@ -55,112 +50,4 @@ namespace osu.Game.Online.API.Requests
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores"; 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 Humanizer;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserBeatmapsRequest : APIRequest<List<APIResponseBeatmapSet>> public class GetUserBeatmapsRequest : APIRequest<List<APIBeatmapSet>>
{ {
private readonly long userId; private readonly long userId;
private readonly int offset; private readonly int offset;

View File

@ -1,14 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<MostPlayedBeatmap>> public class GetUserMostPlayedBeatmapsRequest : APIRequest<List<APIUserMostPlayedBeatmap>>
{ {
private readonly long userId; private readonly long userId;
private readonly int offset; 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}"; 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>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Rulesets.Scoring;
using Humanizer;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class GetUserRecentActivitiesRequest : APIRequest<List<RecentActivity>> public class GetUserRecentActivitiesRequest : APIRequest<List<APIRecentActivity>>
{ {
private readonly long userId; private readonly long userId;
private readonly int offset; private readonly int offset;
@ -23,86 +20,6 @@ namespace osu.Game.Online.API.Requests
protected override string Target => $"users/{userId}/recent_activity?offset={offset}"; 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 public enum RecentActivityType
{ {
Achievement, Achievement,

View File

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

View File

@ -2,19 +2,12 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests 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"; 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.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Direct; using osu.Game.Overlays.Direct;
using osu.Game.Rulesets; using osu.Game.Rulesets;
namespace osu.Game.Online.API.Requests namespace osu.Game.Online.API.Requests
{ {
public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIResponseBeatmapSet>> public class SearchBeatmapSetsRequest : APIRequest<IEnumerable<APIBeatmapSet>>
{ {
private readonly string query; private readonly string query;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Users; using osu.Game.Users;
using System.Linq; using System.Linq;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.Profile.Sections.Recent namespace osu.Game.Overlays.Profile.Sections.Recent
{ {
@ -36,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent
MissingText.Hide(); MissingText.Hide();
foreach (RecentActivity activity in activities) foreach (APIRecentActivity activity in activities)
{ {
ItemsContainer.Add(new DrawableRecentActivity(activity)); 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.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
@ -38,6 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
LabelText = "Always show key overlay", LabelText = "Always show key overlay",
Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay) Bindable = config.GetBindable<bool>(OsuSetting.KeyOverlay)
}, },
new SettingsEnumDropdown<ScoringMode>
{
LabelText = "Score display mode",
Bindable = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode)
}
}; };
} }
} }

View File

@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Configuration
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
where T : struct 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>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions; 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); 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) 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.Skip(IncreaseFirstObjectVisibility ? 1 : 0))
foreach (var d in drawables.Reverse().Skip(IncreaseFirstObjectVisibility ? 1 : 0))
d.ApplyCustomUpdateState += ApplyHiddenState; d.ApplyCustomUpdateState += ApplyHiddenState;
} }

View File

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

View File

@ -1,6 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public class MultiMod : Mod public class MultiMod : Mod
@ -10,6 +13,13 @@ namespace osu.Game.Rulesets.Mods
public override string Description => string.Empty; public override string Description => string.Empty;
public override double ScoreMultiplier => 0; 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.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
@ -69,7 +71,13 @@ namespace osu.Game.Rulesets
public abstract string Description { get; } 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> /// <summary>
/// Do not override this unless you are a legacy mode. /// 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> /// </summary>
public readonly BindableInt HighestCombo = new BindableInt(); 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> /// <summary>
/// Whether all <see cref="Judgement"/>s have been processed. /// Whether all <see cref="Judgement"/>s have been processed.
/// </summary> /// </summary>
@ -169,8 +174,6 @@ namespace osu.Game.Rulesets.Scoring
private const double combo_portion = 0.7; private const double combo_portion = 0.7;
private const double max_score = 1000000; private const double max_score = 1000000;
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
protected sealed override bool HasCompleted => JudgedHits == MaxHits; protected sealed override bool HasCompleted => JudgedHits == MaxHits;
protected int MaxHits { get; private set; } protected int MaxHits { get; private set; }
@ -199,16 +202,18 @@ namespace osu.Game.Rulesets.Scoring
if (maxBaseScore == 0 || maxHighestCombo == 0) if (maxBaseScore == 0 || maxHighestCombo == 0)
{ {
Mode.Value = ScoringMode.Exponential; Mode.Value = ScoringMode.Classic;
Mode.Disabled = true; Mode.Disabled = true;
} }
Mode.ValueChanged += _ => updateScore();
} }
/// <summary> /// <summary>
/// Simulates an autoplay of <see cref="HitObject"/>s that will be judged by this <see cref="ScoreProcessor{TObject}"/> /// 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}"/>. /// by adding <see cref="Judgement"/>s for each <see cref="HitObject"/> in the <see cref="Beatmap{TObject}"/>.
/// <para> /// <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> /// </para>
/// </summary> /// </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> /// <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: case ScoringMode.Standardised:
TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore;
break; break;
case ScoringMode.Exponential: case ScoringMode.Classic:
TotalScore.Value = (baseScore + bonusScore) * Math.Log(HighestCombo + 1, 2); // 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; break;
} }
} }
@ -322,6 +328,6 @@ namespace osu.Game.Rulesets.Scoring
public enum ScoringMode public enum ScoringMode
{ {
Standardised, Standardised,
Exponential Classic
} }
} }

View File

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

View File

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

View File

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

@ -158,6 +158,7 @@ namespace osu.Game.Screens.Play
userAudioOffset.TriggerChange(); userAudioOffset.TriggerChange();
ScoreProcessor = RulesetContainer.CreateScoreProcessor(); ScoreProcessor = RulesetContainer.CreateScoreProcessor();
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
Children = new Drawable[] Children = new Drawable[]
{ {

View File

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