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

Merge branch 'master' into mania-playfield-inversions

This commit is contained in:
Dean Herbert 2018-06-15 18:33:40 +09:00 committed by GitHub
commit 57011ff13b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
104 changed files with 1726 additions and 1686 deletions

View File

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

View File

@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new CatchRuleset())
{
}
}
}

View File

@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
public override int? LegacyID => 2; public override int? LegacyID => 2;

View File

@ -1,18 +1,19 @@
// 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.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;
namespace osu.Game.Rulesets.Catch.Difficulty namespace osu.Game.Rulesets.Catch.Difficulty
{ {
public class CatchDifficultyCalculator : DifficultyCalculator public class CatchDifficultyCalculator : DifficultyCalculator
{ {
public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap) public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{ {
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0; protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => new DifficultyAttributes(mods, 0);
} }
} }

View File

@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public virtual bool CanBePlated => false; public virtual bool CanBePlated => false;
public virtual bool StaysOnPlate => CanBePlated;
protected DrawableCatchHitObject(CatchHitObject hitObject) protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {

View File

@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
private Pulp pulp; private Pulp pulp;
public override bool StaysOnPlate => false;
public DrawableDroplet(Droplet h) public DrawableDroplet(Droplet h)
: base(h) : base(h)
{ {

View File

@ -124,6 +124,9 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = LastInCombo;
} }
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;

View File

@ -48,6 +48,16 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
{ {
void runAfterLoaded(Action action)
{
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
else
lastPlateableFruit.OnLoadComplete = _ => action();
}
if (judgement.IsHit && fruit.CanBePlated) if (judgement.IsHit && fruit.CanBePlated)
{ {
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
@ -63,21 +73,17 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.LifetimeEnd = double.MaxValue; caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit); MovableCatcher.Add(caughtFruit);
lastPlateableFruit = caughtFruit; lastPlateableFruit = caughtFruit;
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
} }
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (judgement.IsHit) if (judgement.IsHit)
{ runAfterLoaded(() => MovableCatcher.Explode());
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
MovableCatcher.Explode();
else
lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); };
}
else else
MovableCatcher.Drop(); MovableCatcher.Drop();
} }
@ -378,28 +384,31 @@ namespace osu.Game.Rulesets.Catch.UI
var fruit = caughtFruit.ToArray(); var fruit = caughtFruit.ToArray();
foreach (var f in fruit) foreach (var f in fruit)
Explode(f);
}
public void Explode(DrawableHitObject fruit)
{
var originalX = fruit.X * Scale.X;
if (ExplodingFruitTarget != null)
{ {
var originalX = f.X * Scale.X; fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
if (ExplodingFruitTarget != null) caughtFruit.Remove(fruit);
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f); ExplodingFruitTarget.Add(fruit);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
} }
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
fruit.Expire();
} }
private class CatcherSprite : Sprite private class CatcherSprite : Sprite

View File

@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new ManiaRuleset())
{
}
}
}

View File

@ -58,6 +58,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public override Pattern Generate() public override Pattern Generate()
{ {
if (TotalColumns == 1)
{
var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, endTime);
return pattern;
}
if (spanCount > 1) if (spanCount > 1)
{ {
if (segmentDuration <= 90) if (segmentDuration <= 90)

View File

@ -77,10 +77,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
convertType |= PatternType.LowProbability; convertType |= PatternType.LowProbability;
if ((convertType & PatternType.KeepSingle) == 0)
{
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
else
convertType |= PatternType.Gathered;
}
} }
public override Pattern Generate() public override Pattern Generate()
{ {
if (TotalColumns == 1)
{
var pattern = new Pattern();
addToPattern(pattern, 0);
return pattern;
}
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
@ -346,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToCentre = false; addToCentre = false;
if ((convertType & PatternType.ForceNotStack) > 0) if ((convertType & PatternType.ForceNotStack) > 0)
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3); return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
switch (TotalColumns) switch (TotalColumns)
{ {

View File

@ -29,47 +29,33 @@ namespace osu.Game.Rulesets.Mania.Difficulty
/// </summary> /// </summary>
private const double decay_weight = 0.9; private const double decay_weight = 0.9;
/// <summary> public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
/// HitObjects are stored as a member variable. : base(ruleset, beatmap)
/// </summary>
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public ManiaDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{ {
} }
public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
difficultyHitObjects.AddRange(Beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime)); difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
if (!calculateStrainValues()) if (!calculateStrainValues(difficultyHitObjects, timeRate))
return 0; return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty() * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
if (categoryDifficulty != null) return new DifficultyAttributes(mods, starRating);
categoryDifficulty["Strain"] = starRating;
return starRating;
} }
private bool calculateStrainValues() private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
{ {
// 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.
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) using (var hitObjectsEnumerator = objects.GetEnumerator())
{ {
if (!hitObjectsEnumerator.MoveNext()) if (!hitObjectsEnumerator.MoveNext())
return false; return false;
@ -80,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
while (hitObjectsEnumerator.MoveNext()) while (hitObjectsEnumerator.MoveNext())
{ {
var next = hitObjectsEnumerator.Current; var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate); next?.CalculateStrains(current, timeRate);
current = next; current = next;
} }
@ -88,9 +74,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
} }
} }
private double calculateDifficulty() private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
{ {
double actualStrainStep = strain_step * TimeRate; double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step // Find the highest strain value within each strain step
List<double> highestStrains = new List<double>(); List<double> highestStrains = new List<double>();
@ -98,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
ManiaHitObjectDifficulty previousHitObject = null; ManiaHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects) foreach (var hitObject in objects)
{ {
// While we are beyond the current interval push the currently available maximum to our strain list // While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime) while (hitObject.BaseHitObject.StartTime > intervalEndTime)
@ -159,5 +145,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
new ManiaModKey8(), new ManiaModKey8(),
new ManiaModKey9(), new ManiaModKey9(),
}; };
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
} }
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeStrainValue() private double computeStrainValue()
{ {
// Obtain strain difficulty // Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes["Strain"] / 0.2) - 4.0, 2.2) / 135.0; double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more // Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania
{ {
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{ {
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
public override int? LegacyID => 3; public override int? LegacyID => 3;

View File

@ -158,8 +158,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

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mania.UI
{ {
internal class HitExplosion : CompositeDrawable internal class HitExplosion : CompositeDrawable
{ {
public override bool RemoveWhenNotAlive => true;
private readonly CircularContainer circle; private readonly CircularContainer circle;
public HitExplosion(DrawableHitObject judgedObject) public HitExplosion(DrawableHitObject judgedObject)

View File

@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new OsuRuleset())
{
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
public double AimStrain;
public double SpeedStrain;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}

View File

@ -2,7 +2,7 @@
// 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 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;
@ -18,31 +18,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private const int section_length = 400; private const int section_length = 400;
private const double difficulty_multiplier = 0.0675; private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(IBeatmap beatmap) public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(beatmap) : base(ruleset, beatmap)
{ {
} }
public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
Skill[] skills = Skill[] skills =
{ {
new Aim(), new Aim(),
new Speed() new Speed()
}; };
double sectionLength = section_length * TimeRate; double sectionLength = section_length * timeRate;
// The first object doesn't generate a strain, so we begin with an incremented section end // The first object doesn't generate a strain, so we begin with an incremented section end
double currentSectionEnd = 2 * sectionLength; double currentSectionEnd = 2 * sectionLength;
foreach (OsuDifficultyHitObject h in beatmap) foreach (OsuDifficultyHitObject h in difficultyBeatmap)
{ {
while (h.BaseObject.StartTime > currentSectionEnd) while (h.BaseObject.StartTime > currentSectionEnd)
{ {
@ -61,16 +56,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
if (categoryDifficulty != null) return new OsuDifficultyAttributes(mods, starRating)
{ {
categoryDifficulty.Add("Aim", aimRating); AimStrain = aimRating,
categoryDifficulty.Add("Speed", speedRating); SpeedStrain = speedRating
} };
return starRating;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[] protected override Mod[] DifficultyAdjustmentMods => new Mod[]

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{ {
public class OsuPerformanceCalculator : PerformanceCalculator public class OsuPerformanceCalculator : PerformanceCalculator
{ {
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
private readonly int countHitCircles; private readonly int countHitCircles;
private readonly int beatmapMaxCombo; private readonly int beatmapMaxCombo;
@ -37,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeAimValue() private double computeAimValue()
{ {
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more // Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
@ -151,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
private double computeSpeedValue() private double computeSpeedValue()
{ {
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f; double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more // Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +

View File

@ -120,9 +120,9 @@ namespace osu.Game.Rulesets.Osu
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);

View File

@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}; };
this.beatmap.BindTo(beatmap); this.beatmap.BindTo(beatmap);
beatmap.ValueChanged += v => calculateScale(); this.beatmap.ValueChanged += v => calculateScale();
cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize); cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += v => calculateScale(); cursorScale.ValueChanged += v => calculateScale();

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

@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new TaikoRuleset())
{
}
}
}

View File

@ -27,54 +27,33 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// </summary> /// </summary>
private const double decay_weight = 0.9; private const double decay_weight = 0.9;
/// <summary> public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
/// HitObjects are stored as a member variable. : base(ruleset, beatmap)
/// </summary>
private readonly List<TaikoHitObjectDifficulty> difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
public TaikoDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{ {
} }
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods) protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
: base(beatmap, mods)
{ {
} var difficultyHitObjects = new List<TaikoHitObjectDifficulty>();
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) foreach (var hitObject in beatmap.HitObjects)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
foreach (var hitObject in Beatmap.HitObjects)
difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject)); difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues()) return 0; if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty() * star_scaling_factor; double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
if (categoryDifficulty != null) return new DifficultyAttributes(mods, starRating);
categoryDifficulty["Strain"] = starRating;
return starRating;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[] private bool calculateStrainValues(List<TaikoHitObjectDifficulty> objects, double timeRate)
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
private bool calculateStrainValues()
{ {
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<TaikoHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) using (var hitObjectsEnumerator = objects.GetEnumerator())
{ {
if (!hitObjectsEnumerator.MoveNext()) return false; if (!hitObjectsEnumerator.MoveNext()) return false;
@ -84,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
while (hitObjectsEnumerator.MoveNext()) while (hitObjectsEnumerator.MoveNext())
{ {
var next = hitObjectsEnumerator.Current; var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate); next?.CalculateStrains(current, timeRate);
current = next; current = next;
} }
@ -92,9 +71,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
} }
} }
private double calculateDifficulty() private double calculateDifficulty(List<TaikoHitObjectDifficulty> objects, double timeRate)
{ {
double actualStrainStep = strain_step * TimeRate; double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step // Find the highest strain value within each strain step
List<double> highestStrains = new List<double>(); List<double> highestStrains = new List<double>();
@ -102,7 +81,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
TaikoHitObjectDifficulty previousHitObject = null; TaikoHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects) foreach (var hitObject in objects)
{ {
// While we are beyond the current interval push the currently available maximum to our strain list // While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime) while (hitObject.BaseHitObject.StartTime > intervalEndTime)
@ -144,5 +123,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
return difficulty; return difficulty;
} }
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new TaikoModDoubleTime(),
new TaikoModHalfTime(),
new TaikoModEasy(),
new TaikoModHardRock(),
};
} }
} }

View File

@ -22,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private int countMeh; private int countMeh;
private int countMiss; private int countMiss;
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score) : base(ruleset, beatmap, score)
{ {
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit); beatmapMaxCombo = Beatmap.HitObjects.Count(h => h is Hit);
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private double computeStrainValue() private double computeStrainValue()
{ {
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0; double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0;
// Longer maps are worth more // Longer maps are worth more
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0); double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);

View File

@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko
{ {
new TaikoModHardRock(), new TaikoModHardRock(),
new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()), new MultiMod(new TaikoModSuddenDeath(), new TaikoModPerfect()),
new MultiMod(new TaikoModDoubleTime(), new TaikoModDaycore()), new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
new TaikoModHidden(), new TaikoModHidden(),
new TaikoModFlashlight(), new TaikoModFlashlight(),
}; };
@ -114,9 +114,9 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 1; public override int? LegacyID => 1;

View File

@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary> /// </summary>
internal class HitExplosion : CircularContainer internal class HitExplosion : CircularContainer
{ {
public override bool RemoveWhenNotAlive => true;
public readonly DrawableHitObject JudgedObject; public readonly DrawableHitObject JudgedObject;
private readonly Box innerFill; private readonly Box innerFill;
@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI
this.ScaleTo(3f, 1000, Easing.OutQuint); this.ScaleTo(3f, 1000, Easing.OutQuint);
this.FadeOut(500); this.FadeOut(500);
Expire(); Expire(true);
} }
/// <summary> /// <summary>

View File

@ -14,6 +14,8 @@ namespace osu.Game.Rulesets.Taiko.UI
{ {
public class KiaiHitExplosion : CircularContainer public class KiaiHitExplosion : CircularContainer
{ {
public override bool RemoveWhenNotAlive => true;
public readonly DrawableHitObject JudgedObject; public readonly DrawableHitObject JudgedObject;
private readonly bool isRim; private readonly bool isRim;
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint); this.ScaleTo(new Vector2(1, 3f), 500, Easing.OutQuint);
this.FadeOut(250); this.FadeOut(250);
Expire(); Expire(true);
} }
} }
} }

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

@ -2,8 +2,8 @@
// 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 NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -139,14 +139,14 @@ namespace osu.Game.Tests.NonVisual
private class TestDifficultyCalculator : DifficultyCalculator private class TestDifficultyCalculator : DifficultyCalculator
{ {
public TestDifficultyCalculator(params Mod[] mods) public TestDifficultyCalculator(params Mod[] mods)
: base(null) : base(null, null)
{ {
DifficultyAdjustmentMods = mods; DifficultyAdjustmentMods = mods;
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => throw new NotImplementedException();
protected override Mod[] DifficultyAdjustmentMods { get; } protected override Mod[] DifficultyAdjustmentMods { get; }
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException();
} }
} }
} }

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

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Overlays.Volume; using osu.Game.Overlays.Volume;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -17,13 +18,21 @@ namespace osu.Game.Tests.Visual
{ {
VolumeMeter meter; VolumeMeter meter;
MuteButton mute; MuteButton mute;
Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue)); Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue) { Position = new Vector2(10) });
AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
Add(new VolumeMeter("BIG", 250, Color4.Red)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Position = new Vector2(10),
});
Add(mute = new MuteButton Add(mute = new MuteButton
{ {
Margin = new MarginPadding { Top = 200 } Margin = new MarginPadding { Top = 200 }
}); });
AddSliderStep("master volume", 0, 10, 0, i => meter.Bindable.Value = i * 0.1);
AddToggleStep("mute", b => mute.Current.Value = b); AddToggleStep("mute", b => mute.Current.Value = b);
} }
} }

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;
@ -363,8 +366,7 @@ namespace osu.Game.Beatmaps
if (ruleset != null) if (ruleset != null)
{ {
// TODO: this should be done in a better place once we actually need to dynamically update it. // TODO: this should be done in a better place once we actually need to dynamically update it.
var converted = new DummyConversionBeatmap(beatmap).GetPlayableBeatmap(ruleset); beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(new DummyConversionBeatmap(beatmap)).Calculate().StarRating;
beatmap.BeatmapInfo.StarDifficulty = ruleset.CreateInstance().CreateDifficultyCalculator(converted).Calculate();
} }
else else
beatmap.BeatmapInfo.StarDifficulty = 0; beatmap.BeatmapInfo.StarDifficulty = 0;
@ -376,6 +378,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

@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap }; public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new DummyBeatmapConverter { Beatmap = beatmap };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => null; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null;
public override string Description => "dummy"; public override string Description => "dummy";

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

@ -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);
@ -148,5 +151,6 @@ namespace osu.Game.Configuration
BeatmapSkins, BeatmapSkins,
BeatmapHitsounds, BeatmapHitsounds,
IncreaseFirstObjectVisibility, IncreaseFirstObjectVisibility,
ScoreDisplayMode
} }
} }

View File

@ -181,24 +181,6 @@ namespace osu.Game.Database
} }
} }
public void Migrate() public void Migrate() => Database.Migrate();
{
try
{
Database.Migrate();
}
catch (Exception e)
{
throw new MigrationFailedException(e);
}
}
}
public class MigrationFailedException : Exception
{
public MigrationFailedException(Exception exception)
: base("sqlite-net migration failed", exception)
{
}
} }
} }

View File

@ -33,7 +33,7 @@ namespace osu.Game.Graphics.Containers
/// <summary> /// <summary>
/// Whether mouse input should be blocked screen-wide while this overlay is visible. /// Whether mouse input should be blocked screen-wide while this overlay is visible.
/// Performing mouse actions outside of the valid extents will hide the overlay but pass the events through. /// Performing mouse actions outside of the valid extents will hide the overlay.
/// </summary> /// </summary>
public virtual bool BlockScreenWideMouse => BlockPassThroughMouse; public virtual bool BlockScreenWideMouse => BlockPassThroughMouse;

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

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

@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Direct
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = $"{SetInfo.Metadata.Source}", Text = SetInfo.Metadata.Source,
TextSize = 14, TextSize = 14,
Shadow = false, Shadow = false,
Colour = colours.Gray5, Colour = colours.Gray5,

View File

@ -160,7 +160,7 @@ namespace osu.Game.Overlays.Direct
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = $"from {SetInfo.Metadata.Source}", Text = SetInfo.Metadata.Source,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
TextSize = 14, TextSize = 14,

View File

@ -66,34 +66,6 @@ namespace osu.Game.Overlays
AlwaysPresent = true; AlwaysPresent = true;
} }
private Vector2 dragStart;
protected override bool OnDragStart(InputState state)
{
base.OnDragStart(state);
dragStart = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
if (base.OnDrag(state)) return true;
Vector2 change = state.Mouse.Position - dragStart;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
dragContainer.MoveTo(change);
return true;
}
protected override bool OnDragEnd(InputState state)
{
dragContainer.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(state);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation)
{ {
@ -103,7 +75,7 @@ namespace osu.Game.Overlays
Children = new Drawable[] Children = new Drawable[]
{ {
dragContainer = new Container dragContainer = new DragContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -470,5 +442,36 @@ namespace osu.Game.Overlays
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }
} }
private class DragContainer : Container
{
private Vector2 dragStart;
protected override bool OnDragStart(InputState state)
{
base.OnDragStart(state);
dragStart = state.Mouse.Position;
return true;
}
protected override bool OnDrag(InputState state)
{
if (base.OnDrag(state)) return true;
Vector2 change = state.Mouse.Position - dragStart;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
this.MoveTo(change);
return true;
}
protected override bool OnDragEnd(InputState state)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(state);
}
}
} }
} }

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

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

@ -24,6 +24,8 @@ namespace osu.Game.Overlays.Volume
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction> public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
{ {
private CircularProgress volumeCircle; private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow;
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 };
private readonly float circleSize; private readonly float circleSize;
private readonly Color4 meterColour; private readonly Color4 meterColour;
@ -44,90 +46,143 @@ namespace osu.Game.Overlays.Volume
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Add(new Container Color4 backgroundColour = colours.Gray1;
{
Size = new Vector2(120, 20),
CornerRadius = 10,
Masking = true,
Margin = new MarginPadding { Left = circleSize + 10 },
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray1,
Alpha = 0.9f,
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = "Exo2.0-Bold",
Text = name
}
}
});
CircularProgress bgProgress; CircularProgress bgProgress;
Add(new CircularContainer const float progress_start_radius = 0.75f;
const float progress_size = 0.03f;
const float progress_end_radius = progress_start_radius + progress_size;
const float blur_amount = 5;
Children = new Drawable[]
{ {
Masking = true, new Container
Size = new Vector2(circleSize),
Children = new Drawable[]
{ {
new Box Size = new Vector2(circleSize),
Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, new BufferedContainer
Colour = colours.Gray1,
Alpha = 0.9f,
},
bgProgress = new CircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = 0.05f,
Rotation = 180,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colours.Gray2,
Size = new Vector2(0.8f)
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Padding = new MarginPadding(-Blur.KernelSize(5)),
Rotation = 180,
Child = (volumeCircle = new CircularProgress
{ {
Alpha = 0.9f,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
InnerRadius = 0.05f, Children = new Drawable[]
{
new Circle
{
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour,
},
new CircularContainer
{
Masking = true,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(progress_end_radius),
Children = new Drawable[]
{
bgProgress = new CircularProgress
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Rotation = 180,
Colour = backgroundColour,
},
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Name = "Progress under covers for smoothing",
RelativeSizeAxes = Axes.Both,
Rotation = 180,
Child = volumeCircle = new CircularProgress
{
RelativeSizeAxes = Axes.Both,
}
},
}
},
new Circle
{
Name = "Inner Cover",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Colour = backgroundColour,
Size = new Vector2(progress_start_radius),
},
new Container
{
Name = "Progress overlay for glow",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(progress_start_radius + progress_size / 1.5f),
Rotation = 180,
Padding = new MarginPadding(-Blur.KernelSize(blur_amount)),
Child = (volumeCircleGlow = new CircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = progress_size * 0.8f,
}).WithEffect(new GlowEffect
{
Colour = meterColour,
BlurSigma = new Vector2(blur_amount),
Strength = 5,
PadExtent = true
}),
},
},
},
maxGlow = (text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = "Venera",
TextSize = 0.16f * circleSize
}).WithEffect(new GlowEffect }).WithEffect(new GlowEffect
{ {
Colour = meterColour, Colour = Color4.Transparent,
Strength = 2, PadExtent = true,
PadExtent = true })
}), }
}, },
maxGlow = (text = new OsuSpriteText new Container
{
Size = new Vector2(120, 20),
CornerRadius = 10,
Masking = true,
Margin = new MarginPadding { Left = circleSize + 10 },
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Children = new Drawable[]
{ {
Anchor = Anchor.Centre, new Box
Origin = Anchor.Centre, {
Font = "Venera", Alpha = 0.9f,
TextSize = 0.16f * circleSize RelativeSizeAxes = Axes.Both,
}).WithEffect(new GlowEffect Colour = backgroundColour,
{ },
Colour = Color4.Transparent, new OsuSpriteText
PadExtent = true, {
}) Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = "Exo2.0-Bold",
Text = name
}
}
} }
}); };
Bindable.ValueChanged += newVolume =>
Bindable.ValueChanged += newVolume => { this.TransformTo("DisplayVolume", newVolume, 400, Easing.OutQuint); }; {
this.TransformTo("DisplayVolume",
newVolume,
400,
Easing.OutQuint);
};
bgProgress.Current.Value = 0.75f; bgProgress.Current.Value = 0.75f;
} }
@ -158,6 +213,7 @@ namespace osu.Game.Overlays.Volume
} }
volumeCircle.Current.Value = displayVolume * 0.75f; volumeCircle.Current.Value = displayVolume * 0.75f;
volumeCircleGlow.Current.Value = displayVolume * 0.75f;
} }
} }

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Difficulty
{
public class DifficultyAttributes
{
public readonly Mod[] Mods;
public readonly double StarRating;
public DifficultyAttributes(Mod[] mods, double starRating)
{
Mods = mods;
StarRating = starRating;
}
}
}

View File

@ -13,28 +13,44 @@ namespace osu.Game.Rulesets.Difficulty
{ {
public abstract class DifficultyCalculator public abstract class DifficultyCalculator
{ {
protected readonly IBeatmap Beatmap; private readonly Ruleset ruleset;
protected readonly Mod[] Mods; private readonly WorkingBeatmap beatmap;
protected double TimeRate { get; private set; } = 1; protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
protected DifficultyCalculator(IBeatmap beatmap, Mod[] mods = null)
{ {
Beatmap = beatmap; this.ruleset = ruleset;
Mods = mods ?? new Mod[0]; this.beatmap = beatmap;
ApplyMods(Mods);
} }
protected virtual void ApplyMods(Mod[] mods) /// <summary>
/// Calculates the difficulty of the beatmap using a specific mod combination.
/// </summary>
/// <param name="mods">The mods that should be applied to the beatmap.</param>
/// <returns>A structure describing the difficulty of the beatmap.</returns>
public DifficultyAttributes Calculate(params Mod[] mods)
{ {
beatmap.Mods.Value = mods;
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
var clock = new StopwatchClock(); var clock = new StopwatchClock();
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock)); mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock));
TimeRate = clock.Rate;
return Calculate(playableBeatmap, mods, clock.Rate);
} }
protected virtual void PreprocessHitObjects() /// <summary>
/// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap.
/// </summary>
/// <returns>A collection of structures describing the difficulty of the beatmap for each mod combination.</returns>
public IEnumerable<DifficultyAttributes> CalculateAll()
{ {
foreach (var combination in CreateDifficultyAdjustmentModCombinations())
{
if (combination is MultiMod multi)
yield return Calculate(multi.Mods);
else
yield return Calculate(combination);
}
} }
/// <summary> /// <summary>
@ -75,6 +91,13 @@ namespace osu.Game.Rulesets.Difficulty
/// </summary> /// </summary>
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>(); protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty<Mod>();
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null); /// <summary>
/// Calculates the difficulty of a <see cref="Beatmap"/> using a specific <see cref="Mod"/> combination.
/// </summary>
/// <param name="beatmap">The <see cref="IBeatmap"/> to compute the difficulty for.</param>
/// <param name="mods">The <see cref="Mod"/>s that should be applied.</param>
/// <param name="timeRate">The rate of time in <paramref name="beatmap"/>.</param>
/// <returns>A structure containing the difficulty attributes.</returns>
protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate);
} }
} }

View File

@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Difficulty
{ {
public abstract class PerformanceCalculator public abstract class PerformanceCalculator
{ {
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>(); protected readonly DifficultyAttributes Attributes;
protected IDictionary<string, double> Attributes => attributes;
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
protected readonly IBeatmap Beatmap; protected readonly IBeatmap Beatmap;
@ -22,14 +21,15 @@ namespace osu.Game.Rulesets.Difficulty
protected double TimeRate { get; private set; } = 1; protected double TimeRate { get; private set; } = 1;
protected PerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score) protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
{ {
Ruleset = ruleset; Ruleset = ruleset;
Beatmap = beatmap;
Score = score; Score = score;
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods); beatmap.Mods.Value = score.Mods;
diffCalc.Calculate(attributes); Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo);
Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods);
ApplyMods(score.Mods); ApplyMods(score.Mods);
} }

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

@ -61,9 +61,9 @@ namespace osu.Game.Rulesets
public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null; public virtual IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => null;
public abstract DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null); public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap);
public virtual PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => null; public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => null;
public virtual HitObjectComposer CreateHitObjectComposer() => null; public virtual HitObjectComposer CreateHitObjectComposer() => null;

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
{ {
/// <summary> /// <summary>
/// A cache that provides a single <see cref="IRulesetConfigManager"/> per-ruleset /// 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. /// This is done to support referring to and updating ruleset configs from multiple locations in the absence of inter-config bindings.
/// </summary> /// </summary>
public class RulesetConfigCache : Component public class RulesetConfigCache : Component

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

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// <summary> /// <summary>
/// The step increase/decrease of the span of time visible by the length of the scrolling axes. /// The step increase/decrease of the span of time visible by the length of the scrolling axes.
/// </summary> /// </summary>
private const double time_span_step = 50; private const double time_span_step = 200;
/// <summary> /// <summary>
/// The span of time that is visible by the length of the scrolling axes. /// The span of time that is visible by the length of the scrolling axes.
@ -88,10 +88,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
switch (args.Key) switch (args.Key)
{ {
case Key.Minus: case Key.Minus:
this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 200, Easing.OutQuint); this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange + time_span_step, 600, Easing.OutQuint);
break; break;
case Key.Plus: case Key.Plus:
this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 200, Easing.OutQuint); this.TransformBindableTo(VisibleTimeRange, VisibleTimeRange - time_span_step, 600, Easing.OutQuint);
break; break;
} }
} }

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

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

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[]
{ {
@ -228,6 +229,7 @@ namespace osu.Game.Screens.Play
}; };
hudOverlay.HoldToQuit.Action = Exit; hudOverlay.HoldToQuit.Action = Exit;
hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded);
if (ShowStoryboard) if (ShowStoryboard)
initializeStoryboard(false); initializeStoryboard(false);

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