mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 16:32:54 +08:00
Merge branch 'master' into fix-taiko-hitexplosions
This commit is contained in:
commit
30f7837b03
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.
|
|
@ -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
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
|||||||
{
|
{
|
||||||
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
|
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
|
||||||
{
|
{
|
||||||
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
|
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
||||||
: base(settings, ruleset, variant)
|
: base(settings, ruleset, variant)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,11 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty;
|
using osu.Game.Rulesets.Mania.Difficulty;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
@ -150,6 +153,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
|
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
|
||||||
|
|
||||||
|
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
|
||||||
|
|
||||||
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
public ManiaRuleset(RulesetInfo rulesetInfo = null)
|
||||||
: base(rulesetInfo)
|
: base(rulesetInfo)
|
||||||
{
|
{
|
||||||
|
@ -10,12 +10,9 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.MathUtils;
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Input.Handlers;
|
using osu.Game.Input.Handlers;
|
||||||
using osu.Game.Rulesets.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
@ -103,7 +100,5 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
|
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
public override string ShortName => "osu";
|
public override string ShortName => "osu";
|
||||||
|
|
||||||
public override SettingsSubsection CreateSettings() => new OsuSettings();
|
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
|
||||||
|
|
||||||
public override int? LegacyID => 0;
|
public override int? LegacyID => 0;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -8,10 +8,15 @@ using osu.Game.Overlays.Settings;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.UI
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
{
|
{
|
||||||
public class OsuSettings : SettingsSubsection
|
public class OsuSettings : RulesetSettingsSubsection
|
||||||
{
|
{
|
||||||
protected override string Header => "osu!";
|
protected override string Header => "osu!";
|
||||||
|
|
||||||
|
public OsuSettings(Ruleset ruleset)
|
||||||
|
: base(ruleset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
@ -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(),
|
||||||
};
|
};
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(string.Empty, metadata.Source);
|
Assert.AreEqual(string.Empty, metadata.Source);
|
||||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||||
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID);
|
||||||
Assert.AreEqual(241526, metadata.OnlineBeatmapSetID);
|
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var beatmap = decodeAsJson(normal);
|
var beatmap = decodeAsJson(normal);
|
||||||
var meta = beatmap.BeatmapInfo.Metadata;
|
var meta = beatmap.BeatmapInfo.Metadata;
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
Assert.AreEqual("Soleily", meta.Artist);
|
||||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||||
|
@ -48,11 +48,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
{
|
{
|
||||||
var reader = new ZipArchiveReader(osz);
|
var reader = new ZipArchiveReader(osz);
|
||||||
|
|
||||||
BeatmapMetadata meta;
|
Beatmap beatmap;
|
||||||
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
|
||||||
meta = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
|
|
||||||
|
|
||||||
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
|
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
||||||
|
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
|
||||||
|
|
||||||
|
var meta = beatmap.Metadata;
|
||||||
|
|
||||||
|
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID);
|
||||||
Assert.AreEqual("Soleily", meta.Artist);
|
Assert.AreEqual("Soleily", meta.Artist);
|
||||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||||
|
@ -449,7 +449,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = id,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
||||||
Title = $"test set #{id}!",
|
Title = $"test set #{id}!",
|
||||||
@ -503,7 +502,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = id,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
Artist = $"peppy{id.ToString().PadLeft(6, '0')}",
|
||||||
Title = $"test set #{id}!",
|
Title = $"test set #{id}!",
|
||||||
|
@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
AddStep("remove scores", () => scoresContainer.Scores = null);
|
AddStep("remove scores", () => scoresContainer.Scores = null);
|
||||||
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
|
AddStep("resize to big", () => container.ResizeWidthTo(1, 300));
|
||||||
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
|
AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300));
|
||||||
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapSetID = 1, OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
|
AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo });
|
||||||
|
|
||||||
|
|
||||||
scores = new[]
|
scores = new[]
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestCaseEditorComposeTimeline : OsuTestCase
|
public class TestCaseEditorComposeTimeline : OsuTestCase
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) };
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
||||||
|
|
||||||
public TestCaseEditorComposeTimeline()
|
public TestCaseEditorComposeTimeline()
|
||||||
{
|
{
|
||||||
@ -27,11 +27,12 @@ namespace osu.Game.Tests.Visual
|
|||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
State = Visibility.Visible
|
State = Visibility.Visible
|
||||||
},
|
},
|
||||||
new ScrollableTimeline
|
new TimelineArea
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(1000, 100)
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Size = new Vector2(0.8f, 100)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,6 @@ namespace osu.Game.Tests.Visual
|
|||||||
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
|
||||||
Metadata = new BeatmapMetadata
|
Metadata = new BeatmapMetadata
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = 1234 + i,
|
|
||||||
// Create random metadata, then we can check if sorting works based on these
|
// Create random metadata, then we can check if sorting works based on these
|
||||||
Artist = "MONACA " + RNG.Next(0, 9),
|
Artist = "MONACA " + RNG.Next(0, 9),
|
||||||
Title = "Black Song " + RNG.Next(0, 9),
|
Title = "Black Song " + RNG.Next(0, 9),
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,11 @@ using OpenTK;
|
|||||||
using OpenTK.Graphics;
|
using OpenTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -40,14 +40,13 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
for (int i = 1; i <= 16; i *= 2)
|
for (int i = 1; i <= 16; i *= 2)
|
||||||
{
|
{
|
||||||
var newDisplay = new BeatmapWaveformGraph
|
var newDisplay = new WaveformGraph
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Resolution = 1f / i,
|
Resolution = 1f / i,
|
||||||
Beatmap = Beatmap
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Beatmap.ValueChanged += b => newDisplay.Beatmap = b;
|
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
||||||
|
|
||||||
flow.Add(new Container
|
flow.Add(new Container
|
||||||
{
|
{
|
||||||
|
142
osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs
Normal file
142
osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Cursor;
|
||||||
|
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
|
using OpenTK;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
using OpenTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual
|
||||||
|
{
|
||||||
|
public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase
|
||||||
|
{
|
||||||
|
private readonly ZoomableScrollContainer scrollContainer;
|
||||||
|
private readonly Drawable innerBox;
|
||||||
|
|
||||||
|
public TestCaseZoomableScrollContainer()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 250,
|
||||||
|
Width = 0.75f,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.Gray(30)
|
||||||
|
},
|
||||||
|
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new MenuCursor()
|
||||||
|
};
|
||||||
|
|
||||||
|
scrollContainer.Add(innerBox = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZoom0()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
AddAssert("Box width = 1x", () => Precision.AlmostEquals(boxQuad.Size, scrollQuad.Size));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestZoom10()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
|
||||||
|
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre));
|
||||||
|
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMouseZoomInOnceOutOnce()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
|
||||||
|
// Scroll in at 0.25
|
||||||
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||||
|
|
||||||
|
// Scroll out at 0.25
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMouseZoomInTwiceOutTwice()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
|
||||||
|
// Scroll in at 0.25
|
||||||
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
|
||||||
|
// Scroll in at 0.6
|
||||||
|
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
|
||||||
|
// Very hard to determine actual position, so approximate
|
||||||
|
AddAssert("Box at correct position (1)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X));
|
||||||
|
AddAssert("Box at correct position (2)", () => Precision.DefinitelyBigger(scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X, boxQuad.TopLeft.X + 0.3f * boxQuad.Size.X));
|
||||||
|
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
|
||||||
|
|
||||||
|
// Scroll out at 0.6
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
|
||||||
|
// Scroll out at 0.25
|
||||||
|
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||||
|
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||||
|
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||||
|
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||||
|
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
AddStep("Reset", () =>
|
||||||
|
{
|
||||||
|
scrollContainer.Zoom = 0;
|
||||||
|
scrollContainer.ScrollTo(0, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad;
|
||||||
|
private Quad boxQuad => innerBox.ScreenSpaceDrawQuad;
|
||||||
|
}
|
||||||
|
}
|
@ -23,7 +23,6 @@ namespace osu.Game.Beatmaps
|
|||||||
public int BeatmapVersion;
|
public int BeatmapVersion;
|
||||||
|
|
||||||
private int? onlineBeatmapID;
|
private int? onlineBeatmapID;
|
||||||
private int? onlineBeatmapSetID;
|
|
||||||
|
|
||||||
[JsonProperty("id")]
|
[JsonProperty("id")]
|
||||||
public int? OnlineBeatmapID
|
public int? OnlineBeatmapID
|
||||||
@ -32,19 +31,10 @@ namespace osu.Game.Beatmaps
|
|||||||
set { onlineBeatmapID = value > 0 ? value : null; }
|
set { onlineBeatmapID = value > 0 ? value : null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonProperty("beatmapset_id")]
|
|
||||||
[NotMapped]
|
|
||||||
public int? OnlineBeatmapSetID
|
|
||||||
{
|
|
||||||
get { return onlineBeatmapSetID; }
|
|
||||||
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int BeatmapSetInfoID { get; set; }
|
public int BeatmapSetInfoID { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[JsonIgnore]
|
|
||||||
public BeatmapSetInfo BeatmapSet { get; set; }
|
public BeatmapSetInfo BeatmapSet { get; set; }
|
||||||
|
|
||||||
public BeatmapMetadata Metadata { get; set; }
|
public BeatmapMetadata Metadata { get; set; }
|
||||||
@ -141,8 +131,8 @@ namespace osu.Game.Beatmaps
|
|||||||
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
|
(Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile;
|
||||||
|
|
||||||
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
||||||
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
||||||
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
|
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
|
||||||
|
@ -81,12 +81,31 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
||||||
{
|
{
|
||||||
model.Beatmaps = createBeatmapDifficulties(model, archive);
|
model.Beatmaps = createBeatmapDifficulties(archive);
|
||||||
|
|
||||||
// remove metadata from difficulties where it matches the set
|
|
||||||
foreach (BeatmapInfo b in model.Beatmaps)
|
foreach (BeatmapInfo b in model.Beatmaps)
|
||||||
|
{
|
||||||
|
// remove metadata from difficulties where it matches the set
|
||||||
if (model.Metadata.Equals(b.Metadata))
|
if (model.Metadata.Equals(b.Metadata))
|
||||||
b.Metadata = null;
|
b.Metadata = null;
|
||||||
|
|
||||||
|
// by setting the model here, we can update the noline set id below.
|
||||||
|
b.BeatmapSet = model;
|
||||||
|
|
||||||
|
fetchAndPopulateOnlineIDs(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a set already exists with the same online id, delete if it does.
|
||||||
|
if (model.OnlineBeatmapSetID != null)
|
||||||
|
{
|
||||||
|
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
|
||||||
|
if (existingOnlineId != null)
|
||||||
|
{
|
||||||
|
Delete(existingOnlineId);
|
||||||
|
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
||||||
|
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
|
protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
|
||||||
@ -99,18 +118,6 @@ namespace osu.Game.Beatmaps
|
|||||||
return existingHashMatch;
|
return existingHashMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if a set already exists with the same online id
|
|
||||||
if (model.OnlineBeatmapSetID != null)
|
|
||||||
{
|
|
||||||
var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
|
|
||||||
if (existingOnlineId != null)
|
|
||||||
{
|
|
||||||
Delete(existingOnlineId);
|
|
||||||
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
|
||||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,29 +313,29 @@ namespace osu.Game.Beatmaps
|
|||||||
return hashable.ComputeSHA2Hash();
|
return hashable.ComputeSHA2Hash();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
|
||||||
{
|
{
|
||||||
// let's make sure there are actually .osu files to import.
|
// let's make sure there are actually .osu files to import.
|
||||||
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
||||||
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
||||||
|
|
||||||
BeatmapMetadata metadata;
|
Beatmap beatmap;
|
||||||
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
||||||
metadata = Decoder.GetDecoder<Beatmap>(stream).Decode(stream).Metadata;
|
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
|
||||||
|
|
||||||
return new BeatmapSetInfo
|
return new BeatmapSetInfo
|
||||||
{
|
{
|
||||||
OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
|
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID,
|
||||||
Beatmaps = new List<BeatmapInfo>(),
|
Beatmaps = new List<BeatmapInfo>(),
|
||||||
Hash = computeBeatmapSetHash(reader),
|
Hash = computeBeatmapSetHash(reader),
|
||||||
Metadata = metadata
|
Metadata = beatmap.Metadata
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
|
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
|
||||||
{
|
{
|
||||||
var beatmapInfos = new List<BeatmapInfo>();
|
var beatmapInfos = new List<BeatmapInfo>();
|
||||||
|
|
||||||
@ -348,10 +355,6 @@ namespace osu.Game.Beatmaps
|
|||||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||||
|
|
||||||
// ensure we have the same online set ID as the set itself.
|
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
|
||||||
beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
|
||||||
|
|
||||||
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
|
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
|
||||||
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
|
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||||
@ -376,6 +379,40 @@ namespace osu.Game.Beatmaps
|
|||||||
return beatmapInfos;
|
return beatmapInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query the API to populate mising OnlineBeatmapID / OnlineBeatmapSetID properties.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="beatmap">The beatmap to populate.</param>
|
||||||
|
/// <param name="force">Whether to re-query if the provided beatmap already has populated values.</param>
|
||||||
|
/// <returns>True if population was successful.</returns>
|
||||||
|
private bool fetchAndPopulateOnlineIDs(BeatmapInfo beatmap, bool force = false)
|
||||||
|
{
|
||||||
|
if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
Logger.Log("Attempting online lookup for IDs...", LoggingTarget.Database);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var req = new GetBeatmapRequest(beatmap);
|
||||||
|
|
||||||
|
req.Perform(api);
|
||||||
|
|
||||||
|
var res = req.Result;
|
||||||
|
|
||||||
|
Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database);
|
||||||
|
|
||||||
|
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||||
|
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Log($"Failed ({e})", LoggingTarget.Database);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
/// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -17,16 +17,6 @@ namespace osu.Game.Beatmaps
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
|
|
||||||
private int? onlineBeatmapSetID;
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
[JsonProperty(@"id")]
|
|
||||||
public int? OnlineBeatmapSetID
|
|
||||||
{
|
|
||||||
get { return onlineBeatmapSetID; }
|
|
||||||
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string TitleUnicode { get; set; }
|
public string TitleUnicode { get; set; }
|
||||||
public string Artist { get; set; }
|
public string Artist { get; set; }
|
||||||
@ -82,8 +72,7 @@ namespace osu.Game.Beatmaps
|
|||||||
if (other == null)
|
if (other == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return onlineBeatmapSetID == other.onlineBeatmapSetID
|
return Title == other.Title
|
||||||
&& Title == other.Title
|
|
||||||
&& TitleUnicode == other.TitleUnicode
|
&& TitleUnicode == other.TitleUnicode
|
||||||
&& Artist == other.Artist
|
&& Artist == other.Artist
|
||||||
&& ArtistUnicode == other.ArtistUnicode
|
&& ArtistUnicode == other.ArtistUnicode
|
||||||
|
@ -22,18 +22,18 @@ namespace osu.Game.Beatmaps
|
|||||||
[NotMapped]
|
[NotMapped]
|
||||||
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
public BeatmapSetOnlineInfo OnlineInfo { get; set; }
|
||||||
|
|
||||||
public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
|
public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0;
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool DeletePending { get; set; }
|
public bool DeletePending { get; set; }
|
||||||
|
|
||||||
public string Hash { get; set; }
|
public string Hash { get; set; }
|
||||||
|
|
||||||
public string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
|
public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename;
|
||||||
|
|
||||||
public List<BeatmapSetFileInfo> Files { get; set; }
|
public List<BeatmapSetFileInfo> Files { get; set; }
|
||||||
|
|
||||||
public override string ToString() => Metadata.ToString();
|
public override string ToString() => Metadata?.ToString() ?? base.ToString();
|
||||||
|
|
||||||
public bool Protected { get; set; }
|
public bool Protected { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
private readonly int offset;
|
private readonly int offset;
|
||||||
|
|
||||||
public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version)
|
public LegacyBeatmapDecoder(int version = LATEST_VERSION)
|
||||||
|
: base(version)
|
||||||
{
|
{
|
||||||
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
// BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
|
||||||
offset = FormatVersion < 5 ? 24 : 0;
|
offset = FormatVersion < 5 ? 24 : 0;
|
||||||
@ -135,6 +136,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case @"LetterboxInBreaks":
|
case @"LetterboxInBreaks":
|
||||||
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1;
|
||||||
@ -207,8 +209,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value);
|
||||||
break;
|
break;
|
||||||
case @"BeatmapSetID":
|
case @"BeatmapSetID":
|
||||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value);
|
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) };
|
||||||
metadata.OnlineBeatmapSetID = int.Parse(pair.Value);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,13 @@ namespace osu.Game.Configuration
|
|||||||
{
|
{
|
||||||
private readonly SettingsStore settings;
|
private readonly SettingsStore settings;
|
||||||
|
|
||||||
private readonly int variant;
|
private readonly int? variant;
|
||||||
|
|
||||||
private readonly List<DatabasedSetting> databasedSettings;
|
private readonly List<DatabasedSetting> databasedSettings;
|
||||||
|
|
||||||
private readonly RulesetInfo ruleset;
|
private readonly RulesetInfo ruleset;
|
||||||
|
|
||||||
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0)
|
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null)
|
||||||
{
|
{
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.ruleset = ruleset;
|
this.ruleset = ruleset;
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -14,16 +14,19 @@ namespace osu.Game.Online.API
|
|||||||
{
|
{
|
||||||
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
|
||||||
|
|
||||||
|
public T Result => ((JsonWebRequest<T>)WebRequest).ResponseObject;
|
||||||
|
|
||||||
protected APIRequest()
|
protected APIRequest()
|
||||||
{
|
{
|
||||||
base.Success += onSuccess;
|
base.Success += onSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSuccess()
|
private void onSuccess() => Success?.Invoke(Result);
|
||||||
{
|
|
||||||
Success?.Invoke(((JsonWebRequest<T>)WebRequest).ResponseObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on successful completion of an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public new event APISuccessHandler<T> Success;
|
public new event APISuccessHandler<T> Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +55,16 @@ namespace osu.Game.Online.API
|
|||||||
protected APIAccess API;
|
protected APIAccess API;
|
||||||
protected WebRequest WebRequest;
|
protected WebRequest WebRequest;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on successful completion of an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public event APISuccessHandler Success;
|
public event APISuccessHandler Success;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on failure to complete an API request.
|
||||||
|
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||||
|
/// </summary>
|
||||||
public event APIFailureHandler Failure;
|
public event APIFailureHandler Failure;
|
||||||
|
|
||||||
private bool cancelled;
|
private bool cancelled;
|
||||||
|
@ -10,13 +10,11 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
private readonly BeatmapInfo beatmap;
|
private readonly BeatmapInfo beatmap;
|
||||||
|
|
||||||
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
|
||||||
|
|
||||||
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
public GetBeatmapDetailsRequest(BeatmapInfo beatmap)
|
||||||
{
|
{
|
||||||
this.beatmap = beatmap;
|
this.beatmap = beatmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Target => $@"beatmaps/{lookupString}";
|
protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
22
osu.Game/Online/API/Requests/GetBeatmapRequest.cs
Normal file
22
osu.Game/Online/API/Requests/GetBeatmapRequest.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class GetBeatmapRequest : APIRequest<APIBeatmap>
|
||||||
|
{
|
||||||
|
private readonly BeatmapInfo beatmap;
|
||||||
|
|
||||||
|
private string lookupString => beatmap.OnlineBeatmapID > 0 ? beatmap.OnlineBeatmapID.ToString() : $@"lookup?checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path)}";
|
||||||
|
|
||||||
|
public GetBeatmapRequest(BeatmapInfo beatmap)
|
||||||
|
{
|
||||||
|
this.beatmap = beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => $@"beatmaps/{lookupString}";
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,10 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public class APIBeatmap : BeatmapMetadata
|
public class APIBeatmap : BeatmapMetadata
|
||||||
{
|
{
|
||||||
[JsonProperty(@"id")]
|
[JsonProperty(@"id")]
|
||||||
private int onlineBeatmapID { get; set; }
|
public int OnlineBeatmapID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"beatmapset_id")]
|
||||||
|
public int OnlineBeatmapSetID { get; set; }
|
||||||
|
|
||||||
[JsonProperty(@"playcount")]
|
[JsonProperty(@"playcount")]
|
||||||
private int playCount { get; set; }
|
private int playCount { get; set; }
|
||||||
@ -55,7 +58,11 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
Metadata = this,
|
Metadata = this,
|
||||||
Ruleset = rulesets.GetRuleset(ruleset),
|
Ruleset = rulesets.GetRuleset(ruleset),
|
||||||
StarDifficulty = starDifficulty,
|
StarDifficulty = starDifficulty,
|
||||||
OnlineBeatmapID = onlineBeatmapID,
|
OnlineBeatmapID = OnlineBeatmapID,
|
||||||
|
BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineBeatmapSetID = OnlineBeatmapSetID,
|
||||||
|
},
|
||||||
Version = version,
|
Version = version,
|
||||||
BaseDifficulty = new BeatmapDifficulty
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,15 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty(@"covers")]
|
[JsonProperty(@"covers")]
|
||||||
private BeatmapSetOnlineCovers covers { get; set; }
|
private BeatmapSetOnlineCovers covers { get; set; }
|
||||||
|
|
||||||
|
private int? onlineBeatmapSetID;
|
||||||
|
|
||||||
|
[JsonProperty(@"id")]
|
||||||
|
public int? OnlineBeatmapSetID
|
||||||
|
{
|
||||||
|
get { return onlineBeatmapSetID; }
|
||||||
|
set { onlineBeatmapSetID = value > 0 ? value : null; }
|
||||||
|
}
|
||||||
|
|
||||||
[JsonProperty(@"preview_url")]
|
[JsonProperty(@"preview_url")]
|
||||||
private string preview { get; set; }
|
private string preview { get; set; }
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
{
|
{
|
||||||
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
|
||||||
beatmap.BeatmapSet = setInfo;
|
beatmap.BeatmapSet = setInfo;
|
||||||
beatmap.OnlineBeatmapSetID = setInfo.OnlineBeatmapSetID;
|
|
||||||
beatmap.Metadata = setInfo.Metadata;
|
beatmap.Metadata = setInfo.Metadata;
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
protected SettingsStore SettingsStore;
|
protected SettingsStore SettingsStore;
|
||||||
|
|
||||||
|
protected RulesetConfigCache RulesetConfigCache;
|
||||||
|
|
||||||
protected MenuCursorContainer MenuCursorContainer;
|
protected MenuCursorContainer MenuCursorContainer;
|
||||||
|
|
||||||
private Container content;
|
private Container content;
|
||||||
@ -123,6 +125,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
|
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
|
||||||
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
|
||||||
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
|
||||||
|
dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore));
|
||||||
dependencies.Cache(new OsuColour());
|
dependencies.Cache(new OsuColour());
|
||||||
|
|
||||||
fileImporters.Add(BeatmapManager);
|
fileImporters.Add(BeatmapManager);
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Profile.Sections
|
|||||||
{
|
{
|
||||||
Action = () =>
|
Action = () =>
|
||||||
{
|
{
|
||||||
if (beatmap.OnlineBeatmapSetID.HasValue) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.OnlineBeatmapSetID.Value);
|
if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
|
35
osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs
Normal file
35
osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="SettingsSubsection"/> which provides subclasses with the <see cref="IRulesetConfigManager"/>
|
||||||
|
/// from the <see cref="Ruleset"/>'s <see cref="Ruleset.CreateConfig()"/>.
|
||||||
|
/// </summary>
|
||||||
|
public abstract class RulesetSettingsSubsection : SettingsSubsection
|
||||||
|
{
|
||||||
|
private readonly Ruleset ruleset;
|
||||||
|
|
||||||
|
protected RulesetSettingsSubsection(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
this.ruleset = ruleset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DependencyContainer dependencies;
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
|
{
|
||||||
|
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
|
||||||
|
var config = dependencies.Get<RulesetConfigCache>().GetConfigFor(ruleset);
|
||||||
|
if (config != null)
|
||||||
|
dependencies.Cache(config);
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,8 @@ namespace osu.Game.Rulesets.Configuration
|
|||||||
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
|
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
|
||||||
where T : struct
|
where T : struct
|
||||||
{
|
{
|
||||||
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant)
|
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
|
||||||
|
: base(settings, ruleset, variant)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ using osu.Game.Rulesets.Replays.Types;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets
|
namespace osu.Game.Rulesets
|
||||||
@ -69,7 +71,13 @@ namespace osu.Game.Rulesets
|
|||||||
|
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
|
|
||||||
public virtual SettingsSubsection CreateSettings() => null;
|
public virtual RulesetSettingsSubsection CreateSettings() => null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the <see cref="IRulesetConfigManager"/> for this <see cref="Ruleset"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settings">The <see cref="SettingsStore"/> to store the settings.</param>
|
||||||
|
public virtual IRulesetConfigManager CreateConfig(SettingsStore settings) => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Do not override this unless you are a legacy mode.
|
/// Do not override this unless you are a legacy mode.
|
||||||
|
43
osu.Game/Rulesets/RulesetConfigCache.cs
Normal file
43
osu.Game/Rulesets/RulesetConfigCache.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Configuration;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A cache that provides a single <see cref="IRulesetConfigManager"/> per-ruleset.
|
||||||
|
/// This is done to support referring to and updating ruleset configs from multiple locations in the absence of inter-config bindings.
|
||||||
|
/// </summary>
|
||||||
|
public class RulesetConfigCache : Component
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, IRulesetConfigManager> configCache = new Dictionary<int, IRulesetConfigManager>();
|
||||||
|
private readonly SettingsStore settingsStore;
|
||||||
|
|
||||||
|
public RulesetConfigCache(SettingsStore settingsStore)
|
||||||
|
{
|
||||||
|
this.settingsStore = settingsStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the <see cref="IRulesetConfigManager"/> for a <see cref="Ruleset"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ruleset">The <see cref="Ruleset"/> to retrieve the <see cref="IRulesetConfigManager"/> for.</param>
|
||||||
|
/// <returns>The <see cref="IRulesetConfigManager"/> defined by <paramref name="ruleset"/>, null if <paramref name="ruleset"/> doesn't define one.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">If <paramref name="ruleset"/> doesn't have a valid <see cref="RulesetInfo.ID"/>.</exception>
|
||||||
|
public IRulesetConfigManager GetConfigFor(Ruleset ruleset)
|
||||||
|
{
|
||||||
|
if (ruleset.RulesetInfo.ID == null)
|
||||||
|
throw new InvalidOperationException("The provided ruleset doesn't have a valid id.");
|
||||||
|
|
||||||
|
if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing))
|
||||||
|
return existing;
|
||||||
|
|
||||||
|
return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -73,11 +73,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private IRulesetConfigManager rulesetConfig;
|
private IRulesetConfigManager rulesetConfig;
|
||||||
private OnScreenDisplay onScreenDisplay;
|
private OnScreenDisplay onScreenDisplay;
|
||||||
|
|
||||||
private DependencyContainer dependencies;
|
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
|
||||||
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A visual representation of a <see cref="Rulesets.Ruleset"/>.
|
/// A visual representation of a <see cref="Rulesets.Ruleset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -90,18 +85,20 @@ namespace osu.Game.Rulesets.UI
|
|||||||
Cursor = CreateCursor();
|
Cursor = CreateCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||||
private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings)
|
|
||||||
{
|
{
|
||||||
this.onScreenDisplay = onScreenDisplay;
|
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||||
|
|
||||||
rulesetConfig = CreateConfig(Ruleset, settings);
|
onScreenDisplay = dependencies.Get<OnScreenDisplay>();
|
||||||
|
|
||||||
|
rulesetConfig = dependencies.Get<RulesetConfigCache>().GetConfigFor(Ruleset);
|
||||||
if (rulesetConfig != null)
|
if (rulesetConfig != null)
|
||||||
{
|
{
|
||||||
dependencies.Cache(rulesetConfig);
|
dependencies.Cache(rulesetConfig);
|
||||||
onScreenDisplay?.BeginTracking(this, rulesetConfig);
|
onScreenDisplay?.BeginTracking(this, rulesetConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ScoreProcessor CreateScoreProcessor();
|
public abstract ScoreProcessor CreateScoreProcessor();
|
||||||
@ -136,8 +133,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual CursorContainer CreateCursor() => null;
|
protected virtual CursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a Playfield.
|
/// Creates a Playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = 5 },
|
Padding = new MarginPadding { Right = 5 },
|
||||||
Child = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
|
Child = new TimelineArea { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
|
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
|
||||||
},
|
},
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Audio;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|
||||||
{
|
|
||||||
public class BeatmapWaveformGraph : CompositeDrawable
|
|
||||||
{
|
|
||||||
public WorkingBeatmap Beatmap { set => graph.Waveform = value.Waveform; }
|
|
||||||
|
|
||||||
private readonly WaveformGraph graph;
|
|
||||||
|
|
||||||
public BeatmapWaveformGraph()
|
|
||||||
{
|
|
||||||
InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the <see cref="WaveformGraph.Resolution"/>.
|
|
||||||
/// </summary>
|
|
||||||
public float Resolution
|
|
||||||
{
|
|
||||||
get { return graph.Resolution; }
|
|
||||||
set { graph.Resolution = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using OpenTK;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|
||||||
{
|
|
||||||
public class ScrollableTimeline : CompositeDrawable
|
|
||||||
{
|
|
||||||
private readonly ScrollingTimelineContainer timelineContainer;
|
|
||||||
|
|
||||||
public ScrollableTimeline()
|
|
||||||
{
|
|
||||||
Masking = true;
|
|
||||||
CornerRadius = 5;
|
|
||||||
|
|
||||||
OsuCheckbox hitObjectsCheckbox;
|
|
||||||
OsuCheckbox hitSoundsCheckbox;
|
|
||||||
OsuCheckbox waveformCheckbox;
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.FromHex("111")
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.FromHex("222")
|
|
||||||
},
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Width = 160,
|
|
||||||
Padding = new MarginPadding { Horizontal = 15 },
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Spacing = new Vector2(0, 4),
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
|
|
||||||
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
|
|
||||||
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.FromHex("333")
|
|
||||||
},
|
|
||||||
new Container<TimelineButton>
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
AutoSizeAxes = Axes.X,
|
|
||||||
Masking = true,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new TimelineButton
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Height = 0.5f,
|
|
||||||
Icon = FontAwesome.fa_search_plus,
|
|
||||||
Action = () => timelineContainer.Zoom++
|
|
||||||
},
|
|
||||||
new TimelineButton
|
|
||||||
{
|
|
||||||
Anchor = Anchor.BottomLeft,
|
|
||||||
Origin = Anchor.BottomLeft,
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
|
||||||
Height = 0.5f,
|
|
||||||
Icon = FontAwesome.fa_search_minus,
|
|
||||||
Action = () => timelineContainer.Zoom--
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
hitObjectsCheckbox.Current.Value = true;
|
|
||||||
hitSoundsCheckbox.Current.Value = true;
|
|
||||||
waveformCheckbox.Current.Value = true;
|
|
||||||
|
|
||||||
timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using OpenTK;
|
|
||||||
using osu.Framework.Configuration;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Input;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|
||||||
{
|
|
||||||
public class ScrollingTimelineContainer : ScrollContainer
|
|
||||||
{
|
|
||||||
public readonly Bindable<bool> HitObjectsVisible = new Bindable<bool>();
|
|
||||||
public readonly Bindable<bool> HitSoundsVisible = new Bindable<bool>();
|
|
||||||
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
|
||||||
|
|
||||||
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
|
||||||
|
|
||||||
private readonly BeatmapWaveformGraph waveform;
|
|
||||||
|
|
||||||
public ScrollingTimelineContainer()
|
|
||||||
: base(Direction.Horizontal)
|
|
||||||
{
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
Add(waveform = new BeatmapWaveformGraph
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = OsuColour.FromHex("222"),
|
|
||||||
Depth = float.MaxValue
|
|
||||||
});
|
|
||||||
|
|
||||||
Content.AutoSizeAxes = Axes.None;
|
|
||||||
Content.RelativeSizeAxes = Axes.Both;
|
|
||||||
|
|
||||||
WaveformVisible.ValueChanged += waveformVisibilityChanged;
|
|
||||||
|
|
||||||
Zoom = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(IBindableBeatmap beatmap)
|
|
||||||
{
|
|
||||||
this.beatmap.BindTo(beatmap);
|
|
||||||
this.beatmap.BindValueChanged(beatmapChanged, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void beatmapChanged(WorkingBeatmap beatmap) => waveform.Beatmap = beatmap;
|
|
||||||
|
|
||||||
private float minZoom = 1;
|
|
||||||
/// <summary>
|
|
||||||
/// The minimum zoom level allowed.
|
|
||||||
/// </summary>
|
|
||||||
public float MinZoom
|
|
||||||
{
|
|
||||||
get { return minZoom; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value));
|
|
||||||
if (minZoom == value)
|
|
||||||
return;
|
|
||||||
minZoom = value;
|
|
||||||
|
|
||||||
// Update the zoom level
|
|
||||||
Zoom = Zoom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float maxZoom = 30;
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum zoom level allowed.
|
|
||||||
/// </summary>
|
|
||||||
public float MaxZoom
|
|
||||||
{
|
|
||||||
get { return maxZoom; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(value));
|
|
||||||
if (maxZoom == value)
|
|
||||||
return;
|
|
||||||
maxZoom = value;
|
|
||||||
|
|
||||||
// Update the zoom level
|
|
||||||
Zoom = Zoom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float zoom = 1;
|
|
||||||
/// <summary>
|
|
||||||
/// The current zoom level.
|
|
||||||
/// </summary>
|
|
||||||
public float Zoom
|
|
||||||
{
|
|
||||||
get { return zoom; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
value = MathHelper.Clamp(value, MinZoom, MaxZoom);
|
|
||||||
if (zoom == value)
|
|
||||||
return;
|
|
||||||
zoom = value;
|
|
||||||
|
|
||||||
// Make the zoom target default to the center of the graph if it hasn't been set
|
|
||||||
if (relativeContentZoomTarget == null)
|
|
||||||
relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X;
|
|
||||||
if (localZoomTarget == null)
|
|
||||||
localZoomTarget = DrawSize.X / 2;
|
|
||||||
|
|
||||||
Content.ResizeWidthTo(Zoom);
|
|
||||||
|
|
||||||
// Update the scroll position to focus on the zoom target
|
|
||||||
float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value;
|
|
||||||
ScrollTo(scrollPos, false);
|
|
||||||
|
|
||||||
relativeContentZoomTarget = null;
|
|
||||||
localZoomTarget = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zoom target as a relative position in the <see cref="ScrollingTimelineContainer.Content"/> space.
|
|
||||||
/// </summary>
|
|
||||||
private float? relativeContentZoomTarget;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zoom target as a position in our local space.
|
|
||||||
/// </summary>
|
|
||||||
private float? localZoomTarget;
|
|
||||||
|
|
||||||
protected override bool OnScroll(InputState state)
|
|
||||||
{
|
|
||||||
if (!state.Keyboard.ControlPressed)
|
|
||||||
return base.OnScroll(state);
|
|
||||||
|
|
||||||
relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X;
|
|
||||||
localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X;
|
|
||||||
|
|
||||||
Zoom += state.Mouse.ScrollDelta.Y;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
57
osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
Normal file
57
osu.Game/Screens/Edit/Screens/Compose/Timeline/Timeline.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||||
|
{
|
||||||
|
public class Timeline : ZoomableScrollContainer
|
||||||
|
{
|
||||||
|
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
||||||
|
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
|
public Timeline()
|
||||||
|
{
|
||||||
|
ZoomDuration = 200;
|
||||||
|
ZoomEasing = Easing.OutQuint;
|
||||||
|
Zoom = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private WaveformGraph waveform;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IBindableBeatmap beatmap)
|
||||||
|
{
|
||||||
|
Child = waveform = new WaveformGraph
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.FromHex("222"),
|
||||||
|
Depth = float.MaxValue
|
||||||
|
};
|
||||||
|
|
||||||
|
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
|
||||||
|
|
||||||
|
Beatmap.BindTo(beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Beatmap.BindValueChanged(b => waveform.Waveform = b.Waveform);
|
||||||
|
waveform.Waveform = Beatmap.Value.Waveform;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// We want time = 0 to be at the centre of the container when scrolled to the start
|
||||||
|
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs
Normal file
128
osu.Game/Screens/Edit/Screens/Compose/Timeline/TimelineArea.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using OpenTK;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||||
|
{
|
||||||
|
public class TimelineArea : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Timeline timeline;
|
||||||
|
|
||||||
|
public TimelineArea()
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 5;
|
||||||
|
|
||||||
|
OsuCheckbox hitObjectsCheckbox;
|
||||||
|
OsuCheckbox hitSoundsCheckbox;
|
||||||
|
OsuCheckbox waveformCheckbox;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.FromHex("111")
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.FromHex("222")
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Width = 160,
|
||||||
|
Padding = new MarginPadding { Horizontal = 15 },
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 4),
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
|
||||||
|
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
|
||||||
|
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = OsuColour.FromHex("333")
|
||||||
|
},
|
||||||
|
new Container<TimelineButton>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
Masking = true,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
new TimelineButton
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Height = 0.5f,
|
||||||
|
Icon = FontAwesome.fa_search_plus,
|
||||||
|
Action = () => timeline.Zoom++
|
||||||
|
},
|
||||||
|
new TimelineButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Height = 0.5f,
|
||||||
|
Icon = FontAwesome.fa_search_minus,
|
||||||
|
Action = () => timeline.Zoom--
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
timeline = new Timeline { RelativeSizeAxes = Axes.Both }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
new Dimension(GridSizeMode.Distributed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
hitObjectsCheckbox.Current.Value = true;
|
||||||
|
hitSoundsCheckbox.Current.Value = true;
|
||||||
|
waveformCheckbox.Current.Value = true;
|
||||||
|
|
||||||
|
timeline.WaveformVisible.BindTo(waveformCheckbox.Current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Transforms;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using OpenTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||||
|
{
|
||||||
|
public class ZoomableScrollContainer : ScrollContainer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The time to zoom into/out of a point.
|
||||||
|
/// All user scroll input will be overwritten during the zoom transform.
|
||||||
|
/// </summary>
|
||||||
|
public double ZoomDuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The easing with which to transform the zoom.
|
||||||
|
/// </summary>
|
||||||
|
public Easing ZoomEasing;
|
||||||
|
|
||||||
|
private readonly Container zoomedContent;
|
||||||
|
protected override Container<Drawable> Content => zoomedContent;
|
||||||
|
|
||||||
|
private float currentZoom = 1;
|
||||||
|
|
||||||
|
public ZoomableScrollContainer()
|
||||||
|
: base(Direction.Horizontal)
|
||||||
|
{
|
||||||
|
base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y });
|
||||||
|
}
|
||||||
|
|
||||||
|
private int minZoom = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum zoom level allowed.
|
||||||
|
/// </summary>
|
||||||
|
public int MinZoom
|
||||||
|
{
|
||||||
|
get => minZoom;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 1)
|
||||||
|
throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value));
|
||||||
|
minZoom = value;
|
||||||
|
|
||||||
|
if (Zoom < value)
|
||||||
|
Zoom = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int maxZoom = 60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum zoom level allowed.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxZoom
|
||||||
|
{
|
||||||
|
get => maxZoom;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < 1)
|
||||||
|
throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value));
|
||||||
|
maxZoom = value;
|
||||||
|
|
||||||
|
if (Zoom > value)
|
||||||
|
Zoom = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the content zoom level of this <see cref="ZoomableScrollContainer"/>.
|
||||||
|
/// </summary>
|
||||||
|
public float Zoom
|
||||||
|
{
|
||||||
|
get => zoomTarget;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
value = MathHelper.Clamp(value, MinZoom, MaxZoom);
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
setZoomTarget(value, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
|
||||||
|
else
|
||||||
|
currentZoom = zoomTarget = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
zoomedContent.Width = DrawWidth * currentZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnScroll(InputState state)
|
||||||
|
{
|
||||||
|
if (!state.Keyboard.ControlPressed)
|
||||||
|
return base.OnScroll(state);
|
||||||
|
|
||||||
|
setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float zoomTarget = 1;
|
||||||
|
private void setZoomTarget(float newZoom, float focusPoint)
|
||||||
|
{
|
||||||
|
zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom);
|
||||||
|
transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None)
|
||||||
|
=> this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing));
|
||||||
|
|
||||||
|
private class TransformZoom : Transform<float, ZoomableScrollContainer>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The focus point in absolute coordinates local to the content.
|
||||||
|
/// </summary>
|
||||||
|
private readonly float focusPoint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The size of the content.
|
||||||
|
/// </summary>
|
||||||
|
private readonly float contentSize;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The scroll offset at the start of the transform.
|
||||||
|
/// </summary>
|
||||||
|
private readonly float scrollOffset;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms <see cref="TimeTimelinem"/> to a new value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="focusPoint">The focus point in absolute coordinates local to the content.</param>
|
||||||
|
/// <param name="contentSize">The size of the content.</param>
|
||||||
|
/// <param name="scrollOffset">The scroll offset at the start of the transform.</param>
|
||||||
|
public TransformZoom(float focusPoint, float contentSize, float scrollOffset)
|
||||||
|
{
|
||||||
|
this.focusPoint = focusPoint;
|
||||||
|
this.contentSize = contentSize;
|
||||||
|
this.scrollOffset = scrollOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string TargetMember => nameof(currentZoom);
|
||||||
|
|
||||||
|
private float valueAt(double time)
|
||||||
|
{
|
||||||
|
if (time < StartTime) return StartValue;
|
||||||
|
if (time >= EndTime) return EndValue;
|
||||||
|
|
||||||
|
return Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Apply(ZoomableScrollContainer d, double time)
|
||||||
|
{
|
||||||
|
float newZoom = valueAt(time);
|
||||||
|
|
||||||
|
float focusOffset = focusPoint - scrollOffset;
|
||||||
|
float expectedWidth = d.DrawWidth * newZoom;
|
||||||
|
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;
|
||||||
|
|
||||||
|
d.currentZoom = newZoom;
|
||||||
|
d.ScrollTo(targetOffset, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.1.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2018.608.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2018.611.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.18.1" />
|
<PackageReference Include="SharpCompress" Version="0.18.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.10.1" />
|
<PackageReference Include="NUnit" Version="3.10.1" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user