mirror of
https://github.com/ppy/osu.git
synced 2024-11-06 09:07:25 +08:00
Merge branch 'master' into timeline-markers
This commit is contained in:
commit
48613ea1f6
36
COMPILING.md
36
COMPILING.md
@ -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.
|
|
@ -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())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -29,47 +29,36 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const double decay_weight = 0.9;
|
private const double decay_weight = 0.9;
|
||||||
|
|
||||||
/// <summary>
|
private readonly bool isForCurrentRuleset;
|
||||||
/// HitObjects are stored as a member variable.
|
|
||||||
/// </summary>
|
|
||||||
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
|
|
||||||
|
|
||||||
public ManiaDifficultyCalculator(IBeatmap beatmap)
|
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||||
: base(beatmap)
|
: base(ruleset, beatmap)
|
||||||
{
|
{
|
||||||
|
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +69,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 +77,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 +87,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)
|
||||||
@ -143,21 +132,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
return difficulty;
|
return difficulty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
protected override Mod[] DifficultyAdjustmentMods
|
||||||
{
|
{
|
||||||
new ManiaModDoubleTime(),
|
get
|
||||||
new ManiaModHalfTime(),
|
{
|
||||||
new ManiaModEasy(),
|
var mods = new Mod[]
|
||||||
new ManiaModHardRock(),
|
{
|
||||||
new ManiaModKey1(),
|
new ManiaModDoubleTime(),
|
||||||
new ManiaModKey2(),
|
new ManiaModHalfTime(),
|
||||||
new ManiaModKey3(),
|
new ManiaModEasy(),
|
||||||
new ManiaModKey4(),
|
new ManiaModHardRock(),
|
||||||
new ManiaModKey5(),
|
};
|
||||||
new ManiaModKey6(),
|
|
||||||
new ManiaModKey7(),
|
if (isForCurrentRuleset)
|
||||||
new ManiaModKey8(),
|
return mods;
|
||||||
new ManiaModKey9(),
|
|
||||||
};
|
// if we are a convert, we can be played in any key mod.
|
||||||
|
return mods.Concat(new Mod[]
|
||||||
|
{
|
||||||
|
new ManiaModKey1(),
|
||||||
|
new ManiaModKey2(),
|
||||||
|
new ManiaModKey3(),
|
||||||
|
new ManiaModKey4(),
|
||||||
|
new ManiaModKey5(),
|
||||||
|
new ManiaModKey6(),
|
||||||
|
new ManiaModKey7(),
|
||||||
|
new ManiaModKey8(),
|
||||||
|
new ManiaModKey9(),
|
||||||
|
}).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -29,7 +29,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)
|
||||||
{
|
{
|
||||||
@ -147,7 +147,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;
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
19
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Normal file
19
osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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[]
|
||||||
|
@ -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) +
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
switch (State.Value)
|
switch (State.Value)
|
||||||
{
|
{
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
|
UnproxyContent();
|
||||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Miss:
|
case ArmedState.Miss:
|
||||||
@ -93,6 +94,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
.Expire();
|
.Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
|
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||||
|
if (X >= -0.05f)
|
||||||
|
ProxyContent();
|
||||||
|
|
||||||
var flash = circlePiece?.FlashBox;
|
var flash = circlePiece?.FlashBox;
|
||||||
if (flash != null)
|
if (flash != null)
|
||||||
{
|
{
|
||||||
|
@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime.
|
|
||||||
/// This is only ever invoked once.
|
|
||||||
/// </summary>
|
|
||||||
public event Action OnStart;
|
|
||||||
|
|
||||||
private const float target_ring_thick_border = 1.4f;
|
private const float target_ring_thick_border = 1.4f;
|
||||||
private const float target_ring_thin_border = 1f;
|
private const float target_ring_thin_border = 1f;
|
||||||
private const float target_ring_scale = 5f;
|
private const float target_ring_scale = 5f;
|
||||||
@ -40,7 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int userHits;
|
private int userHits;
|
||||||
|
|
||||||
private bool hasStarted;
|
|
||||||
private readonly SwellSymbolPiece symbol;
|
private readonly SwellSymbolPiece symbol;
|
||||||
|
|
||||||
public DrawableSwell(Swell swell)
|
public DrawableSwell(Swell swell)
|
||||||
@ -48,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
FillMode = FillMode.Fit;
|
FillMode = FillMode.Fit;
|
||||||
|
|
||||||
AddInternal(bodyContainer = new Container
|
Content.Add(bodyContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
@ -177,6 +170,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case ArmedState.Idle:
|
||||||
|
UnproxyContent();
|
||||||
|
break;
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
||||||
break;
|
break;
|
||||||
@ -195,11 +191,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
X = Math.Max(0, X);
|
X = Math.Max(0, X);
|
||||||
|
|
||||||
double t = Math.Min(HitObject.StartTime, Time.Current);
|
double t = Math.Min(HitObject.StartTime, Time.Current);
|
||||||
if (t == HitObject.StartTime && !hasStarted)
|
if (t == HitObject.StartTime)
|
||||||
{
|
ProxyContent();
|
||||||
OnStart?.Invoke();
|
|
||||||
hasStarted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? lastWasCentre;
|
private bool? lastWasCentre;
|
||||||
|
@ -9,10 +9,75 @@ using OpenTK;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||||
{
|
{
|
||||||
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
public abstract class DrawableTaikoHitObject : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
||||||
|
{
|
||||||
|
protected readonly Container Content;
|
||||||
|
private readonly Container proxiedContent;
|
||||||
|
|
||||||
|
private readonly Container nonProxiedContent;
|
||||||
|
|
||||||
|
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
|
||||||
|
: base(hitObject)
|
||||||
|
{
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
nonProxiedContent = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
},
|
||||||
|
proxiedContent = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="proxiedContent"/> is proxied into an upper layer. We don't want to get masked away otherwise <see cref="proxiedContent"/> would too.
|
||||||
|
/// </summary>
|
||||||
|
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||||
|
|
||||||
|
private bool isProxied;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
|
||||||
|
/// Does nothing is content is already proxied.
|
||||||
|
/// </summary>
|
||||||
|
protected void ProxyContent()
|
||||||
|
{
|
||||||
|
if (isProxied) return;
|
||||||
|
isProxied = true;
|
||||||
|
|
||||||
|
nonProxiedContent.Remove(Content);
|
||||||
|
proxiedContent.Add(Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves <see cref="Content"/> to the normal hitobject layer.
|
||||||
|
/// Does nothing is content is not currently proxied.
|
||||||
|
/// </summary>
|
||||||
|
protected void UnproxyContent()
|
||||||
|
{
|
||||||
|
if (!isProxied) return;
|
||||||
|
isProxied = false;
|
||||||
|
|
||||||
|
proxiedContent.Remove(Content);
|
||||||
|
nonProxiedContent.Add(Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a proxy for the content of this <see cref="DrawableTaikoHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
|
||||||
|
|
||||||
|
public abstract bool OnPressed(TaikoAction action);
|
||||||
|
public virtual bool OnReleased(TaikoAction action) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject
|
||||||
where TaikoHitType : TaikoHitObject
|
where TaikoHitType : TaikoHitObject
|
||||||
{
|
{
|
||||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||||
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
InternalChild = MainPiece = CreateMainPiece();
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
MainPiece.KiaiMode = HitObject.Kiai;
|
MainPiece.KiaiMode = HitObject.Kiai;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override string SampleNamespace => "Taiko";
|
protected override string SampleNamespace => "Taiko";
|
||||||
|
|
||||||
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
||||||
|
|
||||||
public abstract bool OnPressed(TaikoAction action);
|
|
||||||
|
|
||||||
public virtual bool OnReleased(TaikoAction action) => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,10 +216,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
if (barline != null)
|
if (barline != null)
|
||||||
barlineContainer.Add(barline.CreateProxy());
|
barlineContainer.Add(barline.CreateProxy());
|
||||||
|
|
||||||
// Swells should be moved at the very top of the playfield when they reach the hit target
|
var taikoObject = h as DrawableTaikoHitObject;
|
||||||
var swell = h as DrawableSwell;
|
if (taikoObject != null)
|
||||||
if (swell != null)
|
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||||
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
||||||
@ -244,19 +243,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (judgedObject.X >= -0.05f && judgedObject is DrawableHit)
|
|
||||||
{
|
|
||||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
|
||||||
// Todo: The following try-catch is temporary for replay rewinding support
|
|
||||||
try
|
|
||||||
{
|
|
||||||
topLevelHitContainer.Add(judgedObject.CreateProxy());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
||||||
|
|
||||||
if (judgedObject.HitObject.Kiai)
|
if (judgedObject.HitObject.Kiai)
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ImportBeatmapTest
|
public class ImportBeatmapTest
|
||||||
{
|
{
|
||||||
private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestImportWhenClosed()
|
public void TestImportWhenClosed()
|
||||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
private string createTemporaryBeatmap()
|
private string createTemporaryBeatmap()
|
||||||
{
|
{
|
||||||
var temp = Path.GetTempFileName() + ".osz";
|
var temp = Path.GetTempFileName() + ".osz";
|
||||||
File.Copy(osz_path, temp, true);
|
File.Copy(TEST_OSZ_PATH, temp, true);
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,17 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Overlays;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -23,15 +29,23 @@ namespace osu.Game.Tests.Visual
|
|||||||
typeof(CentreMarker)
|
typeof(CentreMarker)
|
||||||
};
|
};
|
||||||
|
|
||||||
public TestCaseEditorComposeTimeline()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new MusicController
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
AutoSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.TopCentre,
|
Direction = FillDirection.Vertical,
|
||||||
State = Visibility.Visible
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new StartStopButton(),
|
||||||
|
new AudioVisualiser(),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TimelineArea
|
new TimelineArea
|
||||||
{
|
{
|
||||||
@ -42,5 +56,85 @@ namespace osu.Game.Tests.Visual
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class AudioVisualiser : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Drawable marker;
|
||||||
|
|
||||||
|
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
private IAdjustableClock adjustableClock;
|
||||||
|
|
||||||
|
public AudioVisualiser()
|
||||||
|
{
|
||||||
|
Size = new Vector2(250, 25);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0.25f,
|
||||||
|
},
|
||||||
|
marker = new Box
|
||||||
|
{
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Width = 2,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
|
||||||
|
{
|
||||||
|
this.adjustableClock = adjustableClock;
|
||||||
|
this.beatmap.BindTo(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (beatmap.Value.Track.IsLoaded)
|
||||||
|
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StartStopButton : Button
|
||||||
|
{
|
||||||
|
private IAdjustableClock adjustableClock;
|
||||||
|
private bool started;
|
||||||
|
|
||||||
|
public StartStopButton()
|
||||||
|
{
|
||||||
|
BackgroundColour = Color4.SlateGray;
|
||||||
|
Size = new Vector2(100, 50);
|
||||||
|
Text = "Start";
|
||||||
|
|
||||||
|
Action = onClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IAdjustableClock adjustableClock)
|
||||||
|
{
|
||||||
|
this.adjustableClock = adjustableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onClick()
|
||||||
|
{
|
||||||
|
if (started)
|
||||||
|
{
|
||||||
|
adjustableClock.Stop();
|
||||||
|
Text = "Start";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adjustableClock.Start();
|
||||||
|
Text = "Stop";
|
||||||
|
}
|
||||||
|
|
||||||
|
started = !started;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ 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;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
FillFlowContainer flow;
|
FillFlowContainer flow;
|
||||||
Child = flow = new FillFlowContainer
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 10),
|
Spacing = new Vector2(0, 10),
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new MusicController
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Y = 100,
|
|
||||||
State = Visibility.Visible
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 1; i <= 16; i *= 2)
|
for (int i = 1; i <= 16; i *= 2)
|
||||||
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Resolution = 1f / i,
|
Resolution = 1f / i,
|
||||||
|
Waveform = Beatmap.Value.Waveform,
|
||||||
};
|
};
|
||||||
|
|
||||||
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
|
||||||
|
|
||||||
flow.Add(new Container
|
flow.Add(new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.IO.Archives;
|
||||||
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="WorkingBeatmap"/> that is used for testcases that include waveforms.
|
||||||
|
/// </summary>
|
||||||
|
public class WaveformTestBeatmap : WorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly ZipArchiveReader reader;
|
||||||
|
private readonly FileStream stream;
|
||||||
|
|
||||||
|
public WaveformTestBeatmap()
|
||||||
|
: base(new BeatmapInfo())
|
||||||
|
{
|
||||||
|
stream = File.OpenRead(ImportBeatmapTest.TEST_OSZ_PATH);
|
||||||
|
reader = new ZipArchiveReader(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
stream?.Dispose();
|
||||||
|
reader?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap GetBeatmap() => createTestBeatmap();
|
||||||
|
|
||||||
|
protected override Texture GetBackground() => null;
|
||||||
|
|
||||||
|
protected override Waveform GetWaveform() => new Waveform(getAudioStream());
|
||||||
|
|
||||||
|
protected override Track GetTrack() => new TrackBass(getAudioStream());
|
||||||
|
|
||||||
|
private Stream getAudioStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".mp3")));
|
||||||
|
private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")));
|
||||||
|
|
||||||
|
private Beatmap createTestBeatmap()
|
||||||
|
{
|
||||||
|
using (var beatmapStream = getBeatmapStream())
|
||||||
|
using (var beatmapReader = new StreamReader(beatmapStream))
|
||||||
|
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -366,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;
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Normal file
19
osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -229,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);
|
||||||
|
@ -16,7 +16,8 @@ using osu.Game.Rulesets.Objects;
|
|||||||
namespace osu.Game.Tests.Beatmaps
|
namespace osu.Game.Tests.Beatmaps
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public abstract class BeatmapConversionTest<TConvertValue>
|
public abstract class BeatmapConversionTest<TConvertMapping, TConvertValue>
|
||||||
|
where TConvertMapping : ConvertMapping<TConvertValue>, IEquatable<TConvertMapping>, new()
|
||||||
where TConvertValue : IEquatable<TConvertValue>
|
where TConvertValue : IEquatable<TConvertValue>
|
||||||
{
|
{
|
||||||
private const string resource_namespace = "Testing.Beatmaps";
|
private const string resource_namespace = "Testing.Beatmaps";
|
||||||
@ -59,9 +60,13 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
else if (objectCounter >= expectedMapping.Objects.Count)
|
else if (objectCounter >= expectedMapping.Objects.Count)
|
||||||
Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
|
Assert.Fail($"The conversion generated a hitobject, but should not have, for hitobject at time: {ourMapping.StartTime}:\n"
|
||||||
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
|
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
|
||||||
else if (!EqualityComparer<TConvertValue>.Default.Equals(expectedMapping.Objects[objectCounter], ourMapping.Objects[objectCounter]))
|
else if (!expectedMapping.Equals(ourMapping))
|
||||||
|
Assert.Fail($"The conversion mapping differed for object at time {expectedMapping.StartTime}:\n"
|
||||||
|
+ $"Expected {JsonConvert.SerializeObject(expectedMapping)}\n"
|
||||||
|
+ $"Received: {JsonConvert.SerializeObject(ourMapping)}\n");
|
||||||
|
else if (!expectedMapping.Objects[objectCounter].Equals(ourMapping.Objects[objectCounter]))
|
||||||
{
|
{
|
||||||
Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}\n"
|
Assert.Fail($"The conversion generated differing hitobjects for object at time: {expectedMapping.StartTime}:\n"
|
||||||
+ $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
|
+ $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"
|
||||||
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
|
+ $"Received: {JsonConvert.SerializeObject(ourMapping.Objects[objectCounter])}\n");
|
||||||
}
|
}
|
||||||
@ -84,19 +89,22 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
|
beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
|
||||||
|
|
||||||
var result = new ConvertResult();
|
var result = new ConvertResult();
|
||||||
|
|
||||||
var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
|
var converter = rulesetInstance.CreateBeatmapConverter(beatmap);
|
||||||
|
|
||||||
converter.ObjectConverted += (orig, converted) =>
|
converter.ObjectConverted += (orig, converted) =>
|
||||||
{
|
{
|
||||||
converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
|
converted.ForEach(h => h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty));
|
||||||
|
|
||||||
var mapping = new ConvertMapping { StartTime = orig.StartTime };
|
var mapping = CreateConvertMapping();
|
||||||
|
mapping.StartTime = orig.StartTime;
|
||||||
|
|
||||||
foreach (var obj in converted)
|
foreach (var obj in converted)
|
||||||
mapping.Objects.AddRange(CreateConvertValue(obj));
|
mapping.Objects.AddRange(CreateConvertValue(obj));
|
||||||
result.Mappings.Add(mapping);
|
result.Mappings.Add(mapping);
|
||||||
};
|
};
|
||||||
|
|
||||||
converter.Convert();
|
IBeatmap convertedBeatmap = converter.Convert();
|
||||||
|
rulesetInstance.CreateBeatmapProcessor(convertedBeatmap)?.PostProcess();
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -128,21 +136,54 @@ namespace osu.Game.Tests.Beatmaps
|
|||||||
return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
|
return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
|
/// <summary>
|
||||||
protected abstract Ruleset CreateRuleset();
|
/// Creates the conversion mapping for a <see cref="HitObject"/>. A conversion mapping stores important information about the conversion process.
|
||||||
|
/// This is generated _after_ the <see cref="HitObject"/> has been converted.
|
||||||
|
/// <para>
|
||||||
|
/// This should be used to validate the integrity of the conversion process after a conversion has occurred.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
protected virtual TConvertMapping CreateConvertMapping() => new TConvertMapping();
|
||||||
|
|
||||||
private class ConvertMapping
|
/// <summary>
|
||||||
{
|
/// Creates the conversion value for a <see cref="HitObject"/>. A conversion value stores information about the converted <see cref="HitObject"/>.
|
||||||
[JsonProperty]
|
/// <para>
|
||||||
public double StartTime;
|
/// This should be used to validate the integrity of the converted <see cref="HitObject"/>.
|
||||||
[JsonProperty]
|
/// </para>
|
||||||
public List<TConvertValue> Objects = new List<TConvertValue>();
|
/// </summary>
|
||||||
}
|
/// <param name="hitObject">The converted <see cref="HitObject"/>.</param>
|
||||||
|
protected abstract IEnumerable<TConvertValue> CreateConvertValue(HitObject hitObject);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="Ruleset"/> applicable to this <see cref="BeatmapConversionTest{TConvertMapping,TConvertValue}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract Ruleset CreateRuleset();
|
||||||
|
|
||||||
private class ConvertResult
|
private class ConvertResult
|
||||||
{
|
{
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public List<ConvertMapping> Mappings = new List<ConvertMapping>();
|
public List<TConvertMapping> Mappings = new List<TConvertMapping>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class BeatmapConversionTest<TConvertValue> : BeatmapConversionTest<ConvertMapping<TConvertValue>, TConvertValue>
|
||||||
|
where TConvertValue : IEquatable<TConvertValue>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ConvertMapping<TConvertValue> : IEquatable<ConvertMapping<TConvertValue>>
|
||||||
|
where TConvertValue : IEquatable<TConvertValue>
|
||||||
|
{
|
||||||
|
[JsonProperty]
|
||||||
|
public double StartTime;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<TConvertValue> Objects = new List<TConvertValue>();
|
||||||
|
|
||||||
|
[JsonProperty("Objects")]
|
||||||
|
private List<TConvertValue> setObjects { set => Objects = value; }
|
||||||
|
|
||||||
|
public virtual bool Equals(ConvertMapping<TConvertValue> other) => StartTime.Equals(other?.StartTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,20 @@ namespace osu.Game.Tests.Visual
|
|||||||
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
|
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
|
||||||
|
dependencies.Cache(BeatDivisor);
|
||||||
|
dependencies.CacheAs<IFrameBasedClock>(Clock);
|
||||||
|
dependencies.CacheAs<IAdjustableClock>(Clock);
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Dependencies.Cache(BeatDivisor);
|
|
||||||
Dependencies.CacheAs<IFrameBasedClock>(Clock);
|
|
||||||
Dependencies.CacheAs<IAdjustableClock>(Clock);
|
|
||||||
|
|
||||||
Beatmap.BindValueChanged(beatmapChanged, true);
|
Beatmap.BindValueChanged(beatmapChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,403 +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 OpenTK;
|
|
||||||
using OpenTK.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Caching;
|
|
||||||
using osu.Framework.Configuration;
|
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Cursor;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
|
||||||
{
|
|
||||||
public abstract class TestCasePerformancePoints : OsuTestCase
|
|
||||||
{
|
|
||||||
protected TestCasePerformancePoints(Ruleset ruleset)
|
|
||||||
{
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.5f,
|
|
||||||
},
|
|
||||||
new ScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new BeatmapList(ruleset, Beatmap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.5f,
|
|
||||||
},
|
|
||||||
new ScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new StarRatingGrid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.5f,
|
|
||||||
},
|
|
||||||
new ScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Child = new PerformanceList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 20),
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 20)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BeatmapList : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly Container<BeatmapDisplay> beatmapDisplays;
|
|
||||||
private readonly Ruleset ruleset;
|
|
||||||
private readonly BindableBeatmap beatmapBindable;
|
|
||||||
|
|
||||||
public BeatmapList(Ruleset ruleset, BindableBeatmap beatmapBindable)
|
|
||||||
{
|
|
||||||
this.ruleset = ruleset;
|
|
||||||
this.beatmapBindable = beatmapBindable;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
InternalChild = beatmapDisplays = new FillFlowContainer<BeatmapDisplay>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(0, 4)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(BeatmapManager beatmaps)
|
|
||||||
{
|
|
||||||
var sets = beatmaps.GetAllUsableBeatmapSets();
|
|
||||||
var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID == null || b.RulesetID == ruleset.LegacyID);
|
|
||||||
|
|
||||||
allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b, beatmapBindable)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BeatmapDisplay : CompositeDrawable, IHasTooltip
|
|
||||||
{
|
|
||||||
private readonly OsuSpriteText text;
|
|
||||||
private readonly BeatmapInfo beatmap;
|
|
||||||
|
|
||||||
private readonly BindableBeatmap beatmapBindable;
|
|
||||||
|
|
||||||
private BeatmapManager beatmaps;
|
|
||||||
|
|
||||||
private bool isSelected;
|
|
||||||
|
|
||||||
public string TooltipText => text.Text;
|
|
||||||
|
|
||||||
public BeatmapDisplay(BeatmapInfo beatmap, BindableBeatmap beatmapBindable)
|
|
||||||
{
|
|
||||||
this.beatmap = beatmap;
|
|
||||||
this.beatmapBindable = beatmapBindable;
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
|
||||||
InternalChild = text = new OsuSpriteText();
|
|
||||||
|
|
||||||
this.beatmapBindable.ValueChanged += beatmapChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(BeatmapManager beatmaps)
|
|
||||||
{
|
|
||||||
this.beatmaps = beatmaps;
|
|
||||||
|
|
||||||
var working = beatmaps.GetWorkingBeatmap(beatmap);
|
|
||||||
text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beatmapChanged(WorkingBeatmap newBeatmap)
|
|
||||||
{
|
|
||||||
if (isSelected)
|
|
||||||
this.FadeColour(Color4.White, 100);
|
|
||||||
isSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnClick(InputState state)
|
|
||||||
{
|
|
||||||
if (beatmapBindable.Value.BeatmapInfo.ID == beatmap.ID)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
beatmapBindable.Value = beatmaps.GetWorkingBeatmap(beatmap);
|
|
||||||
isSelected = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool OnHover(InputState state)
|
|
||||||
{
|
|
||||||
if (isSelected)
|
|
||||||
return false;
|
|
||||||
this.FadeColour(Color4.Yellow, 100);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnHoverLost(InputState state)
|
|
||||||
{
|
|
||||||
if (isSelected)
|
|
||||||
return;
|
|
||||||
this.FadeColour(Color4.White, 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PerformanceList : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly FillFlowContainer<PerformanceDisplay> scores;
|
|
||||||
private APIAccess api;
|
|
||||||
|
|
||||||
private readonly IBindable<WorkingBeatmap> currentBeatmap = new Bindable<WorkingBeatmap>();
|
|
||||||
|
|
||||||
public PerformanceList()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
InternalChild = scores = new FillFlowContainer<PerformanceDisplay>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(0, 4)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(IBindableBeatmap beatmap, APIAccess api)
|
|
||||||
{
|
|
||||||
this.api = api;
|
|
||||||
|
|
||||||
if (!api.IsLoggedIn)
|
|
||||||
{
|
|
||||||
InternalChild = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Text = "Please sign in to see online scores",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBeatmap.ValueChanged += beatmapChanged;
|
|
||||||
currentBeatmap.BindTo(beatmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GetScoresRequest lastRequest;
|
|
||||||
private void beatmapChanged(WorkingBeatmap newBeatmap)
|
|
||||||
{
|
|
||||||
if (!IsAlive) return;
|
|
||||||
|
|
||||||
lastRequest?.Cancel();
|
|
||||||
scores.Clear();
|
|
||||||
|
|
||||||
if (!api.IsLoggedIn)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo, newBeatmap.BeatmapInfo.Ruleset);
|
|
||||||
lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
|
|
||||||
api.Queue(lastRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PerformanceDisplay : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly OsuSpriteText text;
|
|
||||||
|
|
||||||
private readonly Score score;
|
|
||||||
private readonly IBeatmap beatmap;
|
|
||||||
|
|
||||||
public PerformanceDisplay(Score score, IBeatmap beatmap)
|
|
||||||
{
|
|
||||||
this.score = score;
|
|
||||||
this.beatmap = beatmap;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
InternalChild = text = new OsuSpriteText();
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
|
|
||||||
var calculator = ruleset.CreatePerformanceCalculator(beatmap, score);
|
|
||||||
if (calculator == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var attributes = new Dictionary<string, double>();
|
|
||||||
double performance = calculator.Calculate(attributes);
|
|
||||||
|
|
||||||
text.Text = $"{score.User.Username} -> online: {score.PP:n2}pp | local: {performance:n2}pp";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class StarRatingGrid : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly FillFlowContainer<OsuCheckbox> modFlow;
|
|
||||||
private readonly OsuSpriteText totalText;
|
|
||||||
private readonly FillFlowContainer categoryTexts;
|
|
||||||
|
|
||||||
public StarRatingGrid()
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
InternalChild = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
modFlow = new FillFlowContainer<OsuCheckbox>
|
|
||||||
{
|
|
||||||
Name = "Checkbox flow",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(4, 4)
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Name = "Information display",
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(0, 4),
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
totalText = new OsuSpriteText { TextSize = 24 },
|
|
||||||
categoryTexts = new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Vertical
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(IBindableBeatmap beatmap)
|
|
||||||
{
|
|
||||||
beatmap.ValueChanged += beatmapChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cached informationCache = new Cached();
|
|
||||||
|
|
||||||
private Ruleset ruleset;
|
|
||||||
private WorkingBeatmap beatmap;
|
|
||||||
|
|
||||||
private void beatmapChanged(WorkingBeatmap newBeatmap)
|
|
||||||
{
|
|
||||||
beatmap = newBeatmap;
|
|
||||||
|
|
||||||
modFlow.Clear();
|
|
||||||
|
|
||||||
ruleset = newBeatmap.BeatmapInfo.Ruleset.CreateInstance();
|
|
||||||
foreach (var mod in ruleset.GetAllMods())
|
|
||||||
{
|
|
||||||
var checkBox = new OsuCheckbox
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.None,
|
|
||||||
Width = 50,
|
|
||||||
LabelText = mod.ShortenedName
|
|
||||||
};
|
|
||||||
|
|
||||||
checkBox.Current.ValueChanged += v => informationCache.Invalidate();
|
|
||||||
modFlow.Add(checkBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
informationCache.Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
if (ruleset == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!informationCache.IsValid)
|
|
||||||
{
|
|
||||||
totalText.Text = string.Empty;
|
|
||||||
categoryTexts.Clear();
|
|
||||||
|
|
||||||
var allMods = ruleset.GetAllMods().ToList();
|
|
||||||
Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
|
|
||||||
|
|
||||||
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
|
|
||||||
if (diffCalc != null)
|
|
||||||
{
|
|
||||||
var categories = new Dictionary<string, double>();
|
|
||||||
double totalSr = diffCalc.Calculate(categories);
|
|
||||||
|
|
||||||
totalText.Text = $"Star rating: {totalSr:n2}";
|
|
||||||
foreach (var kvp in categories)
|
|
||||||
categoryTexts.Add(new OsuSpriteText { Text = $"{kvp.Key}: {kvp.Value:n2}" });
|
|
||||||
}
|
|
||||||
|
|
||||||
informationCache.Validate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Lists;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
@ -51,6 +53,28 @@ namespace osu.Game.Tests.Visual
|
|||||||
Player p = null;
|
Player p = null;
|
||||||
AddStep(r.Name, () => p = loadPlayerFor(r));
|
AddStep(r.Name, () => p = loadPlayerFor(r));
|
||||||
AddUntilStep(() => ContinueCondition(p));
|
AddUntilStep(() => ContinueCondition(p));
|
||||||
|
|
||||||
|
AddAssert("no leaked beatmaps", () =>
|
||||||
|
{
|
||||||
|
p = null;
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
workingWeakReferences.ForEachAlive(_ => count++);
|
||||||
|
return count == 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no leaked players", () =>
|
||||||
|
{
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
playerWeakReferences.ForEachAlive(_ => count++);
|
||||||
|
return count == 1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,21 +83,32 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
|
protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo);
|
||||||
|
|
||||||
|
private readonly WeakList<WorkingBeatmap> workingWeakReferences = new WeakList<WorkingBeatmap>();
|
||||||
|
private readonly WeakList<Player> playerWeakReferences = new WeakList<Player>();
|
||||||
|
|
||||||
private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
|
private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance());
|
||||||
|
|
||||||
private Player loadPlayerFor(Ruleset r)
|
private Player loadPlayerFor(Ruleset r)
|
||||||
{
|
{
|
||||||
var beatmap = CreateBeatmap(r);
|
var beatmap = CreateBeatmap(r);
|
||||||
|
var working = new TestWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
Beatmap.Value = new TestWorkingBeatmap(beatmap);
|
workingWeakReferences.Add(working);
|
||||||
|
|
||||||
|
Beatmap.Value = working;
|
||||||
Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) };
|
Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) };
|
||||||
|
|
||||||
if (Player != null)
|
Player?.Exit();
|
||||||
Remove(Player);
|
|
||||||
|
|
||||||
var player = CreatePlayer(r);
|
var player = CreatePlayer(r);
|
||||||
|
|
||||||
LoadComponentAsync(player, LoadScreen);
|
playerWeakReferences.Add(player);
|
||||||
|
|
||||||
|
LoadComponentAsync(player, p =>
|
||||||
|
{
|
||||||
|
Player = p;
|
||||||
|
LoadScreen(p);
|
||||||
|
});
|
||||||
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user