diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index ff930b07a3..2bff304fba 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,9 +1,11 @@
-osu!lazer is currently in early stages of development and is not yet ready for end users. Please avoid creating issues or bugs if you do not personally intend to fix them. Some acceptable topics include:
+osu!lazer is currently still under heavy development!
+Please ensure that you are making an issue for one of the following:
+
+- A bug with currently implemented features (not features that don't exist)
+- A feature you are considering adding, so we can collaborate on feedback and design.
- Discussions about technical design decisions
-- Bugs that you have found and are personally willing and able to fix
-- TODO lists of smaller tasks around larger features
-
-Basically, issues are not a place for you to get help. They are a place for developers to collaborate on the game.
If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide.
+
+Screenshots and log files are highly welcomed.
\ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 9cf68803a2..e63f6ea55c 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -12,7 +12,7 @@ install:
- cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
- cmd: choco install nvika -y
- - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.3/CodeFileSanity.exe
+ - cmd: appveyor DownloadFile https://github.com/peppy/CodeFileSanity/releases/download/v0.2.4/CodeFileSanity.exe
before_build:
- cmd: CodeFileSanity.exe
- cmd: nuget restore -verbosity quiet
@@ -20,6 +20,10 @@ build:
project: osu.sln
parallel: true
verbosity: minimal
+test:
+ assemblies:
+ only:
+ - 'osu.Desktop\**\*.dll'
after_build:
- cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors
\ No newline at end of file
diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs
index 88d6ffbca7..e5e0702e6d 100644
--- a/osu.Desktop.Deploy/Program.cs
+++ b/osu.Desktop.Deploy/Program.cs
@@ -39,7 +39,7 @@ namespace osu.Desktop.Deploy
///
/// How many previous build deltas we want to keep when publishing.
///
- private const int keep_delta_count = 3;
+ private const int keep_delta_count = 4;
private static string codeSigningCmd => string.IsNullOrEmpty(codeSigningPassword) ? "" : $"-n \"/a /f {codeSigningCertPath} /p {codeSigningPassword} /t http://timestamp.comodoca.com/authenticode\"";
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index 22ef8f1a34..d8c1d47efb 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -109,16 +109,11 @@ namespace osu.Desktop
{
var filePaths = new[] { e.FileName };
- if (filePaths.All(f => Path.GetExtension(f) == @".osz"))
- Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning);
- else if (filePaths.All(f => Path.GetExtension(f) == @".osr"))
- Task.Run(() =>
- {
- var score = ScoreStore.ReadReplayFile(filePaths.First());
- Schedule(() => LoadScore(score));
- });
- }
+ var firstExtension = Path.GetExtension(filePaths.First());
- private static readonly string[] allowed_extensions = { @".osz", @".osr" };
+ if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
+
+ Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
+ }
}
}
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index a91e728ce7..e878167536 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Runtime;
using osu.Framework;
using osu.Framework.Platform;
using osu.Game.IPC;
@@ -18,6 +19,9 @@ namespace osu.Desktop
// required to initialise native SQLite libraries on some platforms.
SQLitePCL.Batteries_V2.Init();
+ if (!RuntimeInfo.IsMono)
+ useMulticoreJit();
+
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
@@ -25,7 +29,7 @@ namespace osu.Desktop
{
if (!host.IsPrimaryInstance)
{
- var importer = new BeatmapIPCChannel(host);
+ var importer = new ArchiveImportIPCChannel(host);
// Restore the cwd so relative paths given at the command line work correctly
Directory.SetCurrentDirectory(cwd);
foreach (var file in args)
@@ -47,8 +51,16 @@ namespace osu.Desktop
break;
}
}
+
return 0;
}
}
+
+ private static void useMulticoreJit()
+ {
+ var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"));
+ ProfileOptimization.SetProfileRoot(directory.FullName);
+ ProfileOptimization.StartProfile("Startup.Profile");
+ }
}
}
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index bb7d382cee..316a5443ef 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -16,11 +16,9 @@
en-AU
-
-
-
-
-
+
+
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index d3012b1981..1bebe9dae0 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -16,29 +16,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public override void PostProcess(Beatmap beatmap)
{
- if (beatmap.ComboColors.Count == 0)
- return;
-
- int index = 0;
- int colourIndex = 0;
-
- CatchHitObject lastObj = null;
-
initialiseHyperDash(beatmap.HitObjects);
+ base.PostProcess(beatmap);
+
+ int index = 0;
foreach (var obj in beatmap.HitObjects)
- {
- if (obj.NewCombo)
- {
- if (lastObj != null) lastObj.LastInCombo = true;
- colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count;
- }
-
obj.IndexInBeatmap = index++;
- obj.ComboColour = beatmap.ComboColors[colourIndex];
-
- lastObj = obj;
- }
}
private void initialiseHyperDash(List objects)
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 5e70239c7c..4dbe65b3ce 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Catch.Replays;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Catch
{
@@ -99,7 +101,9 @@ namespace osu.Game.Rulesets.Catch
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
- public override int LegacyID => 2;
+ public override int? LegacyID => 2;
+
+ public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
public CatchRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
index 124af06d56..8eb8fd8435 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
index 5c025bdea0..07bc8b825a 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs
@@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModEasy : ModEasy
{
+ public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
index 303fa6011d..947990cce5 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
index ed33bf7124..9479c9d9b0 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
public class CatchModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1.12;
- public override bool Ranked => true;
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
index 981ebda9eb..14291f744c 100644
--- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs
@@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHidden : ModHidden
{
- public override string Description => @"Play with fading notes for a slight score advantage.";
+ public override string Description => @"Play with fading fruits.";
public override double ScoreMultiplier => 1.06;
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
index 89bd73f8fb..487345019b 100644
--- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs
@@ -3,7 +3,6 @@
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
@@ -32,25 +31,11 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana
{
Samples = Samples,
- ComboColour = getNextComboColour(),
StartTime = i,
X = RNG.NextSingle()
});
}
- private Color4 getNextComboColour()
- {
- switch (RNG.Next(0, 3))
- {
- default:
- return new Color4(255, 240, 0, 255);
- case 1:
- return new Color4(255, 192, 0, 255);
- case 2:
- return new Color4(214, 221, 28, 255);
- }
- }
-
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index 559bf47842..1a0ccc9b1e 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -5,24 +5,25 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
- public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
+ public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
{
public const double OBJECT_RADIUS = 44;
public float X { get; set; }
- public Color4 ComboColour { get; set; }
-
public int IndexInBeatmap { get; set; }
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; }
+ public int IndexInCurrentCombo { get; set; }
+
+ public int ComboIndex { get; set; }
+
///
/// The next fruit starts a new combo. Used for explodey.
///
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
index 7b0370ef88..3c6ec0703d 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Origin = Anchor.BottomLeft;
X = 0;
- Child = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
+ InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
foreach (var b in s.NestedHitObjects.Cast())
AddNested(getVisualRepresentation?.Invoke(b));
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
index 8d56fc1081..582946ff00 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs
@@ -8,6 +8,8 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -57,6 +59,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
}
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+
+ if (HitObject is IHasComboInformation combo)
+ AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
+ }
+
private const float preempt = 1000;
protected override void UpdateState(ArmedState state)
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
index c2b0552ab3..719cf0a110 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs
@@ -5,28 +5,39 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : PalpableCatchHitObject
{
+ private Pulp pulp;
+
public DrawableDroplet(Droplet h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4;
- AccentColour = h.ComboColour;
Masking = false;
}
[BackgroundDependencyLoader]
private void load()
{
- Child = new Pulp
+ InternalChild = pulp = new Pulp
{
- AccentColour = AccentColour,
Size = Size
};
}
+
+ public override Color4 AccentColour
+ {
+ get { return base.AccentColour; }
+ set
+ {
+ base.AccentColour = value;
+ pulp.AccentColour = AccentColour;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
index 93a1483f6f..03c2444d8c 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
- AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@@ -33,7 +32,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
[BackgroundDependencyLoader]
private void load()
{
- Children = new[]
+ // todo: this should come from the skin.
+ AccentColour = colourForRrepesentation(HitObject.VisualRepresentation);
+
+ InternalChildren = new[]
{
createPulp(HitObject.VisualRepresentation),
border = new Circle
@@ -65,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
if (HitObject.HyperDash)
{
- Add(new Pulp
+ AddInternal(new Pulp
{
RelativePositionAxes = Axes.Both,
Anchor = Anchor.Centre,
@@ -273,5 +275,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
}
+
+ private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
+ {
+ switch (representation)
+ {
+ default:
+ case FruitVisualRepresentation.Pear:
+ return new Color4(17, 136, 170, 255);
+ case FruitVisualRepresentation.Grape:
+ return new Color4(204, 102, 0, 255);
+ case FruitVisualRepresentation.Raspberry:
+ return new Color4(121, 9, 13, 255);
+ case FruitVisualRepresentation.Pineapple:
+ return new Color4(102, 136, 0, 255);
+ case FruitVisualRepresentation.Banana:
+ switch (RNG.Next(0, 3))
+ {
+ default:
+ return new Color4(255, 240, 0, 255);
+ case 1:
+ return new Color4(255, 192, 0, 255);
+ case 2:
+ return new Color4(214, 221, 28, 255);
+ }
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
index 965ca62674..b3532e2473 100644
--- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Origin = Anchor.BottomLeft;
X = 0;
- Child = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, };
+ InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, };
foreach (var o in s.NestedHitObjects.Cast())
AddNested(getVisualRepresentation?.Invoke(o));
@@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
var catchObject = (DrawableCatchHitObject)h;
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
- catchObject.AccentColour = HitObject.ComboColour;
dropletContainer.Add(h);
base.AddNested(h);
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index be1e360fce..29ad3c3956 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -60,74 +60,75 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Fruit
{
Samples = Samples,
- ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
- for (var span = 0; span < this.SpanCount(); span++)
+ double lastDropletTime = StartTime;
+
+ for (int span = 0; span < this.SpanCount(); span++)
{
var spanStartTime = StartTime + span * spanDuration;
var reversed = span % 2 == 1;
- for (var d = tickDistance; d <= length; d += tickDistance)
+ for (double d = 0; d <= length; d += tickDistance)
{
- if (d > length - minDistanceFromEnd)
- break;
-
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
- var lastTickTime = spanStartTime + timeProgress * spanDuration;
- AddNested(new Droplet
+ double time = spanStartTime + timeProgress * spanDuration;
+
+ double tinyTickInterval = time - lastDropletTime;
+ while (tinyTickInterval > 100)
+ tinyTickInterval /= 2;
+
+ for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
{
- StartTime = lastTickTime,
- ComboColour = ComboColour,
- X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
+ double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
+
+ AddNested(new TinyDroplet
{
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
- }
+ StartTime = t,
+ X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
+ Samples = new List(Samples.Select(s => new SampleInfo
+ {
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }))
+ });
+ }
- double tinyTickInterval = tickDistance / length * spanDuration;
- while (tinyTickInterval > 100)
- tinyTickInterval /= 2;
-
- for (double t = 0; t < spanDuration; t += tinyTickInterval)
- {
- double progress = reversed ? 1 - t / spanDuration : t / spanDuration;
-
- AddNested(new TinyDroplet
+ if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
{
- StartTime = spanStartTime + t,
- ComboColour = ComboColour,
- X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
- Samples = new List(Samples.Select(s => new SampleInfo
+ AddNested(new Droplet
{
- Bank = s.Bank,
- Name = @"slidertick",
- Volume = s.Volume
- }))
- });
+ StartTime = time,
+ X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
+ Samples = new List(Samples.Select(s => new SampleInfo
+ {
+ Bank = s.Bank,
+ Name = @"slidertick",
+ Volume = s.Volume
+ }))
+ });
+ }
+
+ lastDropletTime = time;
}
AddNested(new Fruit
{
Samples = Samples,
- ComboColour = ComboColour,
StartTime = spanStartTime + spanDuration,
- X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
+ X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
}
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
- public float EndX => Curve.PositionAt(this.ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
+ public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime;
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
index f8ca75fae9..f1503a14ee 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Catch.Replays
}
else if (h.HyperDash)
{
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition, ReplayButtonState.Right1));
+ Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
}
else if (dashRequired)
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Replays
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, ReplayButtonState.Left1));
+ Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
}
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Replays
{
double timeBefore = positionChange / movement_speed;
- Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition, ReplayButtonState.Right1));
+ Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
index 2f296a2504..9c9b06fcea 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs
@@ -3,37 +3,51 @@
using System.Collections.Generic;
using osu.Framework.Input;
+using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays
{
- public class CatchFramedReplayInputHandler : FramedReplayInputHandler
+ public class CatchFramedReplayInputHandler : FramedReplayInputHandler
{
public CatchFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
+ protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
+
+ protected float? Position
+ {
+ get
+ {
+ if (!HasFrames)
+ return null;
+
+ return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
+ }
+ }
+
public override List GetPendingStates()
{
if (!Position.HasValue) return new List();
- var action = new List();
+ var actions = new List();
- if (CurrentFrame.ButtonState == ReplayButtonState.Left1)
- action.Add(CatchAction.Dash);
+ if (CurrentFrame.Dashing)
+ actions.Add(CatchAction.Dash);
- if (Position.Value.X > CurrentFrame.Position.X)
- action.Add(CatchAction.MoveRight);
- else if (Position.Value.X < CurrentFrame.Position.X)
- action.Add(CatchAction.MoveLeft);
+ if (Position.Value > CurrentFrame.Position)
+ actions.Add(CatchAction.MoveRight);
+ else if (Position.Value < CurrentFrame.Position)
+ actions.Add(CatchAction.MoveLeft);
return new List
{
new CatchReplayState
{
- PressedActions = action,
- CatcherX = Position.Value.X
+ PressedActions = actions,
+ CatcherX = Position.Value
},
};
}
diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
index 0194fc93a4..b444b0d7ba 100644
--- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
+++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs
@@ -1,17 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Replays.Legacy;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Catch.Replays
{
- public class CatchReplayFrame : ReplayFrame
+ public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
- public override bool IsImportant => MouseX > 0;
+ public float Position;
+ public bool Dashing;
- public CatchReplayFrame(double time, float? x = null, ReplayButtonState button = ReplayButtonState.None)
- : base(time, x ?? -1, null, button)
+ public CatchReplayFrame()
{
}
+
+ public CatchReplayFrame(double time, float? position = null, bool dashing = false)
+ : base(time)
+ {
+ Position = position ?? -1;
+ Dashing = dashing;
+ }
+
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ {
+ Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
+ Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
+ }
}
}
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..9357d3b75c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,957 @@
+{
+ "Mappings": [{
+ "StartTime": 500.0,
+ "Objects": [{
+ "StartTime": 500.0,
+ "Position": 96.0
+ }, {
+ "StartTime": 562.0,
+ "Position": 100.84
+ }, {
+ "StartTime": 625.0,
+ "Position": 125.0
+ }, {
+ "StartTime": 687.0,
+ "Position": 152.84
+ }, {
+ "StartTime": 750.0,
+ "Position": 191.0
+ }, {
+ "StartTime": 812.0,
+ "Position": 212.84
+ }, {
+ "StartTime": 875.0,
+ "Position": 217.0
+ }, {
+ "StartTime": 937.0,
+ "Position": 234.84
+ }, {
+ "StartTime": 1000.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 1062.0,
+ "Position": 267.84
+ }, {
+ "StartTime": 1125.0,
+ "Position": 284.0
+ }, {
+ "StartTime": 1187.0,
+ "Position": 311.84
+ }, {
+ "StartTime": 1250.0,
+ "Position": 350.0
+ }, {
+ "StartTime": 1312.0,
+ "Position": 359.84
+ }, {
+ "StartTime": 1375.0,
+ "Position": 367.0
+ }, {
+ "StartTime": 1437.0,
+ "Position": 400.84
+ }, {
+ "StartTime": 1500.0,
+ "Position": 416.0
+ }, {
+ "StartTime": 1562.0,
+ "Position": 377.159973
+ }, {
+ "StartTime": 1625.0,
+ "Position": 367.0
+ }, {
+ "StartTime": 1687.0,
+ "Position": 374.159973
+ }, {
+ "StartTime": 1750.0,
+ "Position": 353.0
+ }, {
+ "StartTime": 1812.0,
+ "Position": 329.159973
+ }, {
+ "StartTime": 1875.0,
+ "Position": 288.0
+ }, {
+ "StartTime": 1937.0,
+ "Position": 259.159973
+ }, {
+ "StartTime": 2000.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 2058.0,
+ "Position": 232.44
+ }, {
+ "StartTime": 2116.0,
+ "Position": 222.879974
+ }, {
+ "StartTime": 2174.0,
+ "Position": 185.319992
+ }, {
+ "StartTime": 2232.0,
+ "Position": 177.76001
+ }, {
+ "StartTime": 2290.0,
+ "Position": 162.200012
+ }, {
+ "StartTime": 2348.0,
+ "Position": 158.639984
+ }, {
+ "StartTime": 2406.0,
+ "Position": 111.079994
+ }, {
+ "StartTime": 2500.0,
+ "Position": 96.0
+ }]
+ }, {
+ "StartTime": 3000.0,
+ "Objects": [{
+ "StartTime": 3000.0,
+ "Position": 18.0
+ }, {
+ "StartTime": 3062.0,
+ "Position": 482.0
+ }, {
+ "StartTime": 3125.0,
+ "Position": 243.0
+ }, {
+ "StartTime": 3187.0,
+ "Position": 332.0
+ }, {
+ "StartTime": 3250.0,
+ "Position": 477.0
+ }, {
+ "StartTime": 3312.0,
+ "Position": 376.0
+ }, {
+ "StartTime": 3375.0,
+ "Position": 104.0
+ }, {
+ "StartTime": 3437.0,
+ "Position": 156.0
+ }, {
+ "StartTime": 3500.0,
+ "Position": 135.0
+ }, {
+ "StartTime": 3562.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 3625.0,
+ "Position": 360.0
+ }, {
+ "StartTime": 3687.0,
+ "Position": 199.0
+ }, {
+ "StartTime": 3750.0,
+ "Position": 239.0
+ }, {
+ "StartTime": 3812.0,
+ "Position": 326.0
+ }, {
+ "StartTime": 3875.0,
+ "Position": 393.0
+ }, {
+ "StartTime": 3937.0,
+ "Position": 470.0
+ }, {
+ "StartTime": 4000.0,
+ "Position": 136.0
+ }]
+ }, {
+ "StartTime": 4500.0,
+ "Objects": [{
+ "StartTime": 4500.0,
+ "Position": 317.0
+ }, {
+ "StartTime": 4562.0,
+ "Position": 354.0
+ }, {
+ "StartTime": 4625.0,
+ "Position": 414.0
+ }, {
+ "StartTime": 4687.0,
+ "Position": 39.0
+ }, {
+ "StartTime": 4750.0,
+ "Position": 172.0
+ }, {
+ "StartTime": 4812.0,
+ "Position": 479.0
+ }, {
+ "StartTime": 4875.0,
+ "Position": 18.0
+ }, {
+ "StartTime": 4937.0,
+ "Position": 151.0
+ }, {
+ "StartTime": 5000.0,
+ "Position": 342.0
+ }, {
+ "StartTime": 5062.0,
+ "Position": 400.0
+ }, {
+ "StartTime": 5125.0,
+ "Position": 420.0
+ }, {
+ "StartTime": 5187.0,
+ "Position": 90.0
+ }, {
+ "StartTime": 5250.0,
+ "Position": 220.0
+ }, {
+ "StartTime": 5312.0,
+ "Position": 80.0
+ }, {
+ "StartTime": 5375.0,
+ "Position": 421.0
+ }, {
+ "StartTime": 5437.0,
+ "Position": 473.0
+ }, {
+ "StartTime": 5500.0,
+ "Position": 97.0
+ }]
+ }, {
+ "StartTime": 6000.0,
+ "Objects": [{
+ "StartTime": 6000.0,
+ "Position": 105.0
+ }, {
+ "StartTime": 6062.0,
+ "Position": 249.0
+ }, {
+ "StartTime": 6125.0,
+ "Position": 163.0
+ }, {
+ "StartTime": 6187.0,
+ "Position": 194.0
+ }, {
+ "StartTime": 6250.0,
+ "Position": 106.0
+ }, {
+ "StartTime": 6312.0,
+ "Position": 212.0
+ }, {
+ "StartTime": 6375.0,
+ "Position": 257.0
+ }, {
+ "StartTime": 6437.0,
+ "Position": 461.0
+ }, {
+ "StartTime": 6500.0,
+ "Position": 79.0
+ }]
+ }, {
+ "StartTime": 7000.0,
+ "Objects": [{
+ "StartTime": 7000.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 7062.0,
+ "Position": 294.84
+ }, {
+ "StartTime": 7125.0,
+ "Position": 279.0
+ }, {
+ "StartTime": 7187.0,
+ "Position": 309.84
+ }, {
+ "StartTime": 7250.0,
+ "Position": 336.0
+ }, {
+ "StartTime": 7312.0,
+ "Position": 322.16
+ }, {
+ "StartTime": 7375.0,
+ "Position": 308.0
+ }, {
+ "StartTime": 7437.0,
+ "Position": 263.16
+ }, {
+ "StartTime": 7500.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 7562.0,
+ "Position": 261.84
+ }, {
+ "StartTime": 7625.0,
+ "Position": 277.0
+ }, {
+ "StartTime": 7687.0,
+ "Position": 318.84
+ }, {
+ "StartTime": 7750.0,
+ "Position": 336.0
+ }, {
+ "StartTime": 7803.0,
+ "Position": 305.04
+ }, {
+ "StartTime": 7857.0,
+ "Position": 307.76
+ }, {
+ "StartTime": 7910.0,
+ "Position": 297.8
+ }, {
+ "StartTime": 8000.0,
+ "Position": 256.0
+ }]
+ }, {
+ "StartTime": 8500.0,
+ "Objects": [{
+ "StartTime": 8500.0,
+ "Position": 32.0
+ }, {
+ "StartTime": 8562.0,
+ "Position": 22.8515015
+ }, {
+ "StartTime": 8625.0,
+ "Position": 28.5659637
+ }, {
+ "StartTime": 8687.0,
+ "Position": 50.3433228
+ }, {
+ "StartTime": 8750.0,
+ "Position": 56.58974
+ }, {
+ "StartTime": 8812.0,
+ "Position": 64.23422
+ }, {
+ "StartTime": 8875.0,
+ "Position": 67.7117844
+ }, {
+ "StartTime": 8937.0,
+ "Position": 90.52607
+ }, {
+ "StartTime": 9000.0,
+ "Position": 101.81015
+ }, {
+ "StartTime": 9062.0,
+ "Position": 113.478188
+ }, {
+ "StartTime": 9125.0,
+ "Position": 159.414444
+ }, {
+ "StartTime": 9187.0,
+ "Position": 155.1861
+ }, {
+ "StartTime": 9250.0,
+ "Position": 179.600418
+ }, {
+ "StartTime": 9312.0,
+ "Position": 212.293015
+ }, {
+ "StartTime": 9375.0,
+ "Position": 197.2076
+ }, {
+ "StartTime": 9437.0,
+ "Position": 243.438324
+ }, {
+ "StartTime": 9500.0,
+ "Position": 237.2304
+ }, {
+ "StartTime": 9562.0,
+ "Position": 241.253983
+ }, {
+ "StartTime": 9625.0,
+ "Position": 258.950623
+ }, {
+ "StartTime": 9687.0,
+ "Position": 253.3786
+ }, {
+ "StartTime": 9750.0,
+ "Position": 270.8865
+ }, {
+ "StartTime": 9812.0,
+ "Position": 244.38974
+ }, {
+ "StartTime": 9875.0,
+ "Position": 242.701874
+ }, {
+ "StartTime": 9937.0,
+ "Position": 256.2331
+ }, {
+ "StartTime": 10000.0,
+ "Position": 270.339874
+ }, {
+ "StartTime": 10062.0,
+ "Position": 275.9349
+ }, {
+ "StartTime": 10125.0,
+ "Position": 297.2969
+ }, {
+ "StartTime": 10187.0,
+ "Position": 307.834137
+ }, {
+ "StartTime": 10250.0,
+ "Position": 321.6449
+ }, {
+ "StartTime": 10312.0,
+ "Position": 357.746338
+ }, {
+ "StartTime": 10375.0,
+ "Position": 358.21875
+ }, {
+ "StartTime": 10437.0,
+ "Position": 394.943
+ }, {
+ "StartTime": 10500.0,
+ "Position": 401.0588
+ }, {
+ "StartTime": 10558.0,
+ "Position": 418.21347
+ }, {
+ "StartTime": 10616.0,
+ "Position": 424.6034
+ }, {
+ "StartTime": 10674.0,
+ "Position": 455.835754
+ }, {
+ "StartTime": 10732.0,
+ "Position": 477.5042
+ }, {
+ "StartTime": 10790.0,
+ "Position": 476.290955
+ }, {
+ "StartTime": 10848.0,
+ "Position": 470.943237
+ }, {
+ "StartTime": 10906.0,
+ "Position": 503.3372
+ }, {
+ "StartTime": 10999.0,
+ "Position": 508.166229
+ }]
+ }, {
+ "StartTime": 11500.0,
+ "Objects": [{
+ "StartTime": 11500.0,
+ "Position": 321.0
+ }, {
+ "StartTime": 11562.0,
+ "Position": 17.0
+ }, {
+ "StartTime": 11625.0,
+ "Position": 173.0
+ }, {
+ "StartTime": 11687.0,
+ "Position": 170.0
+ }, {
+ "StartTime": 11750.0,
+ "Position": 447.0
+ }, {
+ "StartTime": 11812.0,
+ "Position": 218.0
+ }, {
+ "StartTime": 11875.0,
+ "Position": 394.0
+ }, {
+ "StartTime": 11937.0,
+ "Position": 46.0
+ }, {
+ "StartTime": 12000.0,
+ "Position": 480.0
+ }]
+ }, {
+ "StartTime": 12500.0,
+ "Objects": [{
+ "StartTime": 12500.0,
+ "Position": 512.0
+ }, {
+ "StartTime": 12562.0,
+ "Position": 491.3132
+ }, {
+ "StartTime": 12625.0,
+ "Position": 484.3089
+ }, {
+ "StartTime": 12687.0,
+ "Position": 454.6221
+ }, {
+ "StartTime": 12750.0,
+ "Position": 433.617767
+ }, {
+ "StartTime": 12812.0,
+ "Position": 399.930969
+ }, {
+ "StartTime": 12875.0,
+ "Position": 395.926666
+ }, {
+ "StartTime": 12937.0,
+ "Position": 361.239868
+ }, {
+ "StartTime": 13000.0,
+ "Position": 353.235535
+ }, {
+ "StartTime": 13062.0,
+ "Position": 314.548767
+ }, {
+ "StartTime": 13125.0,
+ "Position": 315.544434
+ }, {
+ "StartTime": 13187.0,
+ "Position": 288.857635
+ }, {
+ "StartTime": 13250.0,
+ "Position": 254.853333
+ }, {
+ "StartTime": 13312.0,
+ "Position": 239.166534
+ }, {
+ "StartTime": 13375.0,
+ "Position": 240.1622
+ }, {
+ "StartTime": 13437.0,
+ "Position": 212.4754
+ }, {
+ "StartTime": 13500.0,
+ "Position": 194.471069
+ }, {
+ "StartTime": 13562.0,
+ "Position": 161.784271
+ }, {
+ "StartTime": 13625.0,
+ "Position": 145.779968
+ }, {
+ "StartTime": 13687.0,
+ "Position": 129.09314
+ }, {
+ "StartTime": 13750.0,
+ "Position": 104.088837
+ }, {
+ "StartTime": 13812.0,
+ "Position": 95.40204
+ }, {
+ "StartTime": 13875.0,
+ "Position": 61.3977356
+ }, {
+ "StartTime": 13937.0,
+ "Position": 56.710907
+ }, {
+ "StartTime": 14000.0,
+ "Position": 35.7066345
+ }, {
+ "StartTime": 14062.0,
+ "Position": 5.019806
+ }, {
+ "StartTime": 14125.0,
+ "Position": 0.0
+ }, {
+ "StartTime": 14187.0,
+ "Position": 39.7696266
+ }, {
+ "StartTime": 14250.0,
+ "Position": 23.0119171
+ }, {
+ "StartTime": 14312.0,
+ "Position": 75.94882
+ }, {
+ "StartTime": 14375.0,
+ "Position": 98.19112
+ }, {
+ "StartTime": 14437.0,
+ "Position": 82.12803
+ }, {
+ "StartTime": 14500.0,
+ "Position": 118.370323
+ }, {
+ "StartTime": 14562.0,
+ "Position": 149.307236
+ }, {
+ "StartTime": 14625.0,
+ "Position": 168.549515
+ }, {
+ "StartTime": 14687.0,
+ "Position": 190.486435
+ }, {
+ "StartTime": 14750.0,
+ "Position": 186.728714
+ }, {
+ "StartTime": 14812.0,
+ "Position": 199.665634
+ }, {
+ "StartTime": 14875.0,
+ "Position": 228.907928
+ }, {
+ "StartTime": 14937.0,
+ "Position": 264.844849
+ }, {
+ "StartTime": 15000.0,
+ "Position": 271.087128
+ }, {
+ "StartTime": 15062.0,
+ "Position": 290.024017
+ }, {
+ "StartTime": 15125.0,
+ "Position": 302.266327
+ }, {
+ "StartTime": 15187.0,
+ "Position": 344.203247
+ }, {
+ "StartTime": 15250.0,
+ "Position": 356.445526
+ }, {
+ "StartTime": 15312.0,
+ "Position": 359.382446
+ }, {
+ "StartTime": 15375.0,
+ "Position": 401.624725
+ }, {
+ "StartTime": 15437.0,
+ "Position": 388.561646
+ }, {
+ "StartTime": 15500.0,
+ "Position": 423.803925
+ }, {
+ "StartTime": 15562.0,
+ "Position": 425.740845
+ }, {
+ "StartTime": 15625.0,
+ "Position": 449.983124
+ }, {
+ "StartTime": 15687.0,
+ "Position": 468.920044
+ }, {
+ "StartTime": 15750.0,
+ "Position": 492.162323
+ }, {
+ "StartTime": 15812.0,
+ "Position": 506.784332
+ }, {
+ "StartTime": 15875.0,
+ "Position": 474.226227
+ }, {
+ "StartTime": 15937.0,
+ "Position": 482.978638
+ }, {
+ "StartTime": 16000.0,
+ "Position": 446.420532
+ }, {
+ "StartTime": 16058.0,
+ "Position": 418.4146
+ }, {
+ "StartTime": 16116.0,
+ "Position": 425.408844
+ }, {
+ "StartTime": 16174.0,
+ "Position": 383.402924
+ }, {
+ "StartTime": 16232.0,
+ "Position": 363.397156
+ }, {
+ "StartTime": 16290.0,
+ "Position": 343.391235
+ }, {
+ "StartTime": 16348.0,
+ "Position": 328.385468
+ }, {
+ "StartTime": 16406.0,
+ "Position": 322.3797
+ }, {
+ "StartTime": 16500.0,
+ "Position": 291.1977
+ }]
+ }, {
+ "StartTime": 17000.0,
+ "Objects": [{
+ "StartTime": 17000.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 17062.0,
+ "Position": 228.16
+ }, {
+ "StartTime": 17125.0,
+ "Position": 234.0
+ }, {
+ "StartTime": 17187.0,
+ "Position": 202.16
+ }, {
+ "StartTime": 17250.0,
+ "Position": 176.0
+ }, {
+ "StartTime": 17312.0,
+ "Position": 210.84
+ }, {
+ "StartTime": 17375.0,
+ "Position": 221.0
+ }, {
+ "StartTime": 17437.0,
+ "Position": 219.84
+ }, {
+ "StartTime": 17500.0,
+ "Position": 256.0
+ }, {
+ "StartTime": 17562.0,
+ "Position": 219.16
+ }, {
+ "StartTime": 17625.0,
+ "Position": 228.0
+ }, {
+ "StartTime": 17687.0,
+ "Position": 203.16
+ }, {
+ "StartTime": 17750.0,
+ "Position": 176.0
+ }, {
+ "StartTime": 17803.0,
+ "Position": 174.959991
+ }, {
+ "StartTime": 17857.0,
+ "Position": 214.23999
+ }, {
+ "StartTime": 17910.0,
+ "Position": 228.200012
+ }, {
+ "StartTime": 18000.0,
+ "Position": 256.0
+ }]
+ }, {
+ "StartTime": 18500.0,
+ "Objects": [{
+ "StartTime": 18500.0,
+ "Position": 362.0
+ }, {
+ "StartTime": 18559.0,
+ "Position": 249.0
+ }, {
+ "StartTime": 18618.0,
+ "Position": 357.0
+ }, {
+ "StartTime": 18678.0,
+ "Position": 167.0
+ }, {
+ "StartTime": 18737.0,
+ "Position": 477.0
+ }, {
+ "StartTime": 18796.0,
+ "Position": 411.0
+ }, {
+ "StartTime": 18856.0,
+ "Position": 254.0
+ }, {
+ "StartTime": 18915.0,
+ "Position": 308.0
+ }, {
+ "StartTime": 18975.0,
+ "Position": 399.0
+ }, {
+ "StartTime": 19034.0,
+ "Position": 176.0
+ }, {
+ "StartTime": 19093.0,
+ "Position": 14.0
+ }, {
+ "StartTime": 19153.0,
+ "Position": 258.0
+ }, {
+ "StartTime": 19212.0,
+ "Position": 221.0
+ }, {
+ "StartTime": 19271.0,
+ "Position": 481.0
+ }, {
+ "StartTime": 19331.0,
+ "Position": 92.0
+ }, {
+ "StartTime": 19390.0,
+ "Position": 211.0
+ }, {
+ "StartTime": 19450.0,
+ "Position": 135.0
+ }]
+ }, {
+ "StartTime": 19875.0,
+ "Objects": [{
+ "StartTime": 19875.0,
+ "Position": 216.0
+ }, {
+ "StartTime": 19937.0,
+ "Position": 215.307053
+ }, {
+ "StartTime": 20000.0,
+ "Position": 236.036865
+ }, {
+ "StartTime": 20062.0,
+ "Position": 236.312088
+ }, {
+ "StartTime": 20125.0,
+ "Position": 235.838928
+ }, {
+ "StartTime": 20187.0,
+ "Position": 269.9743
+ }, {
+ "StartTime": 20250.0,
+ "Position": 285.999146
+ }, {
+ "StartTime": 20312.0,
+ "Position": 283.669067
+ }, {
+ "StartTime": 20375.0,
+ "Position": 317.446747
+ }, {
+ "StartTime": 20437.0,
+ "Position": 330.750275
+ }, {
+ "StartTime": 20500.0,
+ "Position": 344.0156
+ }, {
+ "StartTime": 20562.0,
+ "Position": 318.472168
+ }, {
+ "StartTime": 20625.0,
+ "Position": 309.165466
+ }, {
+ "StartTime": 20687.0,
+ "Position": 317.044617
+ }, {
+ "StartTime": 20750.0,
+ "Position": 280.457367
+ }, {
+ "StartTime": 20812.0,
+ "Position": 272.220581
+ }, {
+ "StartTime": 20875.0,
+ "Position": 270.3294
+ }, {
+ "StartTime": 20937.0,
+ "Position": 262.57605
+ }, {
+ "StartTime": 21000.0,
+ "Position": 244.803329
+ }, {
+ "StartTime": 21062.0,
+ "Position": 215.958359
+ }, {
+ "StartTime": 21125.0,
+ "Position": 177.79332
+ }, {
+ "StartTime": 21187.0,
+ "Position": 190.948349
+ }, {
+ "StartTime": 21250.0,
+ "Position": 158.78334
+ }, {
+ "StartTime": 21312.0,
+ "Position": 136.93837
+ }, {
+ "StartTime": 21375.0,
+ "Position": 119.121056
+ }, {
+ "StartTime": 21437.0,
+ "Position": 132.387573
+ }, {
+ "StartTime": 21500.0,
+ "Position": 124.503014
+ }, {
+ "StartTime": 21562.0,
+ "Position": 118.749374
+ }, {
+ "StartTime": 21625.0,
+ "Position": 123.165535
+ }, {
+ "StartTime": 21687.0,
+ "Position": 96.02999
+ }, {
+ "StartTime": 21750.0,
+ "Position": 118.547928
+ }, {
+ "StartTime": 21812.0,
+ "Position": 128.856232
+ }, {
+ "StartTime": 21875.0,
+ "Position": 124.28746
+ }, {
+ "StartTime": 21937.0,
+ "Position": 150.754929
+ }, {
+ "StartTime": 22000.0,
+ "Position": 149.528732
+ }, {
+ "StartTime": 22062.0,
+ "Position": 145.1691
+ }, {
+ "StartTime": 22125.0,
+ "Position": 182.802155
+ }, {
+ "StartTime": 22187.0,
+ "Position": 178.6452
+ }, {
+ "StartTime": 22250.0,
+ "Position": 213.892181
+ }, {
+ "StartTime": 22312.0,
+ "Position": 218.713028
+ }, {
+ "StartTime": 22375.0,
+ "Position": 240.4715
+ }, {
+ "StartTime": 22437.0,
+ "Position": 239.371887
+ }, {
+ "StartTime": 22500.0,
+ "Position": 261.907257
+ }, {
+ "StartTime": 22562.0,
+ "Position": 314.353119
+ }, {
+ "StartTime": 22625.0,
+ "Position": 299.273376
+ }, {
+ "StartTime": 22687.0,
+ "Position": 356.98288
+ }, {
+ "StartTime": 22750.0,
+ "Position": 339.078552
+ }, {
+ "StartTime": 22812.0,
+ "Position": 377.8958
+ }, {
+ "StartTime": 22875.0,
+ "Position": 398.054047
+ }, {
+ "StartTime": 22937.0,
+ "Position": 398.739441
+ }, {
+ "StartTime": 23000.0,
+ "Position": 407.178467
+ }, {
+ "StartTime": 23062.0,
+ "Position": 444.8687
+ }, {
+ "StartTime": 23125.0,
+ "Position": 417.069977
+ }, {
+ "StartTime": 23187.0,
+ "Position": 454.688477
+ }, {
+ "StartTime": 23250.0,
+ "Position": 428.9612
+ }, {
+ "StartTime": 23312.0,
+ "Position": 441.92807
+ }, {
+ "StartTime": 23375.0,
+ "Position": 439.749878
+ }, {
+ "StartTime": 23433.0,
+ "Position": 455.644684
+ }, {
+ "StartTime": 23491.0,
+ "Position": 440.7359
+ }, {
+ "StartTime": 23549.0,
+ "Position": 430.0944
+ }, {
+ "StartTime": 23607.0,
+ "Position": 420.796173
+ }, {
+ "StartTime": 23665.0,
+ "Position": 435.897461
+ }, {
+ "StartTime": 23723.0,
+ "Position": 418.462555
+ }, {
+ "StartTime": 23781.0,
+ "Position": 405.53775
+ }, {
+ "StartTime": 23874.0,
+ "Position": 408.720825
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs
new file mode 100644
index 0000000000..e40510b71b
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Tests/CatchBeatmapConversionTest.cs
@@ -0,0 +1,67 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class CatchBeatmapConversionTest : BeatmapConversionTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
+
+ [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
+ public new void Test(string name)
+ {
+ base.Test(name);
+ }
+
+ protected override IEnumerable CreateConvertValue(HitObject hitObject)
+ {
+ if (hitObject is JuiceStream stream)
+ {
+ foreach (var nested in stream.NestedHitObjects)
+ {
+ yield return new ConvertValue
+ {
+ StartTime = nested.StartTime,
+ Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
+ };
+ }
+ }
+ else
+ {
+ yield return new ConvertValue
+ {
+ StartTime = hitObject.StartTime,
+ Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
+ };
+ }
+ }
+
+ protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter();
+ }
+
+ public struct ConvertValue : IEquatable
+ {
+ ///
+ /// A sane value to account for osu!stable using ints everwhere.
+ ///
+ private const float conversion_lenience = 2;
+
+ public double StartTime;
+ public float Position;
+
+ public bool Equals(ConvertValue other)
+ => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+ && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs
index e23e7633ca..ec9dd15673 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseBananaShower.cs
@@ -12,7 +12,6 @@ using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer
{
public override IReadOnlyList RequiredTypes => new[]
@@ -29,16 +28,14 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap()
+ protected override Beatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 6,
- }
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Ruleset = ruleset.RulesetInfo
}
};
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
index dbd5e5b36c..efebfa9739 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchPlayer.cs
@@ -6,7 +6,6 @@ using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{
public TestCaseCatchPlayer() : base(new CatchRuleset())
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
index b9fa38f74e..8e5843f40a 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatchStacker.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{
public TestCaseCatchStacker()
@@ -16,19 +15,18 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap()
+ protected override Beatmap CreateBeatmap(Ruleset ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
- BaseDifficulty = new BeatmapDifficulty
- {
- CircleSize = 6,
- }
+ BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
+ Ruleset = ruleset.RulesetInfo
}
};
+
for (int i = 0; i < 512; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
index a2d18520d7..0329921c92 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseCatcherArea.cs
@@ -13,7 +13,6 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseCatcherArea : OsuTestCase
{
private RulesetInfo catchRuleset;
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseFruitObjects.cs
index d406231cc9..595ca6cb24 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseFruitObjects.cs
@@ -6,17 +6,15 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osu.Game.Tests.Visual;
using OpenTK;
-using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseFruitObjects : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
@@ -62,8 +60,6 @@ namespace osu.Game.Rulesets.Catch.Tests
Scale = 1.5f,
};
- fruit.ComboColour = colourForRrepesentation(fruit.VisualRepresentation);
-
return new DrawableFruit(fruit)
{
Anchor = Anchor.Centre,
@@ -74,31 +70,5 @@ namespace osu.Game.Rulesets.Catch.Tests
LifetimeEnd = double.PositiveInfinity,
};
}
-
- private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
- {
- switch (representation)
- {
- default:
- case FruitVisualRepresentation.Pear:
- return new Color4(17, 136, 170, 255);
- case FruitVisualRepresentation.Grape:
- return new Color4(204, 102, 0, 255);
- case FruitVisualRepresentation.Raspberry:
- return new Color4(121, 9, 13, 255);
- case FruitVisualRepresentation.Pineapple:
- return new Color4(102, 136, 0, 255);
- case FruitVisualRepresentation.Banana:
- switch (RNG.Next(0, 3))
- {
- default:
- return new Color4(255, 240, 0, 255);
- case 1:
- return new Color4(255, 192, 0, 255);
- case 2:
- return new Color4(214, 221, 28, 255);
- }
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
index 59659b3d0d..7564adea8c 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCaseHyperdash.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
{
public TestCaseHyperdash()
@@ -16,9 +15,10 @@ namespace osu.Game.Rulesets.Catch.Tests
{
}
- protected override Beatmap CreateBeatmap()
+ protected override Beatmap CreateBeatmap(Ruleset ruleset)
{
- var beatmap = new Beatmap();
+ var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
+
for (int i = 0; i < 512; i++)
if (i % 5 < 3)
diff --git a/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
index 725eb5cf76..2be6dd005d 100644
--- a/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
+++ b/osu.Game.Rulesets.Catch/Tests/TestCasePerformancePoints.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
index 956a524121..41dd7fdf4e 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs
@@ -3,6 +3,7 @@
using osu.Framework.Input;
using osu.Game.Beatmaps;
+using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override BeatmapProcessor CreateBeatmapProcessor() => new CatchBeatmapProcessor();
diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
index 7c548f70d4..bf2f9db4a8 100644
--- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs
@@ -54,7 +54,6 @@ namespace osu.Game.Rulesets.Catch.UI
if (caughtFruit == null) return;
- caughtFruit.AccentColour = fruit.AccentColour;
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 9922d4c8ad..4734e40803 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield break;
}
- var objects = IsForCurrentRuleset ? generateSpecific(original) : generateConverted(original);
+ var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap);
if (objects == null)
yield break;
@@ -110,10 +110,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// Method that generates hit objects for osu!mania specific beatmaps.
///
/// The original hit object.
+ /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.
/// The hit objects generated.
- private IEnumerable generateSpecific(HitObject original)
+ private IEnumerable generateSpecific(HitObject original, Beatmap originalBeatmap)
{
- var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern);
+ var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate();
lastPattern = newPattern;
@@ -125,26 +126,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// Method that generates hit objects for non-osu!mania beatmaps.
///
/// The original hit object.
+ /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.
/// The hit objects generated.
- private IEnumerable generateConverted(HitObject original)
+ private IEnumerable generateConverted(HitObject original, Beatmap originalBeatmap)
{
var endTimeData = original as IHasEndTime;
var distanceData = original as IHasDistance;
var positionData = original as IHasPosition;
- // Following lines currently commented out to appease resharper
-
Patterns.PatternGenerator conversion = null;
if (distanceData != null)
- conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
+ conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
else if (endTimeData != null)
- conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
+ conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap);
else if (positionData != null)
{
computeDensity(original.StartTime);
- conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair);
+ conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position);
}
@@ -153,10 +153,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return null;
Pattern newPattern = conversion.Generate();
- lastPattern = newPattern;
- var stairPatternGenerator = conversion as HitObjectPatternGenerator;
- lastStair = stairPatternGenerator?.StairType ?? lastStair;
+ lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
+ lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
return newPattern.HitObjects;
}
@@ -166,8 +165,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
///
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{
- public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
- : base(random, hitObject, beatmap, previousPattern)
+ public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+ : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index a102781e70..28cf119833 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -29,11 +30,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType;
- public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
- : base(random, hitObject, beatmap, previousPattern)
+ public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
+ : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
convertType = PatternType.None;
- if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
+ if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance;
@@ -305,19 +306,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p4 = 0;
break;
case 3:
- p2 = Math.Max(p2, 0.1);
+ p2 = Math.Min(p2, 0.1);
p3 = 0;
p4 = 0;
break;
case 4:
- p2 = Math.Max(p2, 0.3);
- p3 = Math.Max(p3, 0.04);
+ p2 = Math.Min(p2, 0.3);
+ p3 = Math.Min(p3, 0.04);
p4 = 0;
break;
case 5:
- p2 = Math.Max(p2, 0.34);
- p3 = Math.Max(p3, 0.1);
- p4 = Math.Max(p4, 0.03);
+ p2 = Math.Min(p2, 0.34);
+ p3 = Math.Min(p3, 0.1);
+ p4 = Math.Min(p4, 0.03);
break;
}
@@ -396,17 +397,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime);
- int noteCount = 1;
+ int nextColumn = Random.Next(RandomStart, TotalColumns);
+ int noteCount;
if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0);
else if (ConversionDifficulty > 4)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0);
else if (ConversionDifficulty > 2.5)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0);
+ else
+ noteCount = 0;
noteCount = Math.Min(TotalColumns - 1, noteCount);
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
- int nextColumn = Random.Next(RandomStart, TotalColumns);
var rowPattern = new Pattern();
for (int i = 0; i <= spanCount; i++)
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
index 278a4c4aab..ffbabba75a 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs
@@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using System.Linq;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
@@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
private readonly double endTime;
- public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap)
- : base(random, hitObject, beatmap, new Pattern())
+ public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap)
+ : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{
var endtimeData = HitObject as IHasEndTime;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
index c4ef23a982..e126534c54 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using OpenTK;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects;
@@ -19,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType;
- public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
- : base(random, hitObject, beatmap, previousPattern)
+ public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap)
+ : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
@@ -308,20 +309,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p5 = 0;
break;
case 3:
- p2 = Math.Max(p2, 0.1);
+ p2 = Math.Min(p2, 0.1);
p3 = 0;
p4 = 0;
p5 = 0;
break;
case 4:
- p2 = Math.Max(p2, 0.23);
- p3 = Math.Max(p3, 0.04);
+ p2 = Math.Min(p2, 0.23);
+ p3 = Math.Min(p3, 0.04);
p4 = 0;
p5 = 0;
break;
case 5:
- p3 = Math.Max(p3, 0.15);
- p4 = Math.Max(p4, 0.03);
+ p3 = Math.Min(p3, 0.15);
+ p4 = Math.Min(p4, 0.03);
p5 = 0;
break;
}
@@ -355,23 +356,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p3 = 0;
break;
case 3:
- centreProbability = Math.Max(centreProbability, 0.03);
- p2 = Math.Max(p2, 0.1);
+ centreProbability = Math.Min(centreProbability, 0.03);
+ p2 = 0;
p3 = 0;
break;
case 4:
centreProbability = 0;
- p2 = Math.Max(p2 * 2, 0.2);
+ p2 = Math.Min(p2 * 2, 0.2);
p3 = 0;
break;
case 5:
- centreProbability = Math.Max(centreProbability, 0.03);
+ centreProbability = Math.Min(centreProbability, 0.03);
p3 = 0;
break;
case 6:
centreProbability = 0;
- p2 = Math.Max(p2 * 2, 0.5);
- p3 = Math.Max(p3 * 2, 0.15);
+ p2 = Math.Min(p2 * 2, 0.5);
+ p3 = Math.Min(p3 * 2, 0.15);
break;
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index 5f98749f0c..501950cdcd 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -25,14 +25,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
///
protected readonly FastRandom Random;
- protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
+ ///
+ /// The beatmap which is being converted from.
+ ///
+ protected readonly Beatmap OriginalBeatmap;
+
+ protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern)
{
if (random == null) throw new ArgumentNullException(nameof(random));
- if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
- if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
+ if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
Random = random;
+ OriginalBeatmap = originalBeatmap;
+
RandomStart = TotalColumns == 8 ? 1 : 0;
}
@@ -94,17 +100,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (conversionDifficulty != null)
return conversionDifficulty.Value;
- HitObject lastObject = Beatmap.HitObjects.LastOrDefault();
- HitObject firstObject = Beatmap.HitObjects.FirstOrDefault();
+ HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
+ HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
- drainTime -= Beatmap.TotalBreakTime;
+ drainTime -= OriginalBeatmap.TotalBreakTime;
if (drainTime == 0)
- drainTime = 10000;
+ drainTime = 10000000;
- BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty;
- conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
+ // We need this in seconds
+ drainTime /= 1000;
+
+ BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
+ conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value;
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
index aeefc2f396..cb500735f7 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs
@@ -14,5 +14,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// The number of s which this stage contains.
///
public int Columns;
+
+ ///
+ /// Whether the column index is a special column for this stage.
+ ///
+ /// The 0-based column index.
+ /// Whether the column is a special column.
+ public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
}
}
diff --git a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
index 75a8543548..f4e3d54a3d 100644
--- a/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/ManiaDifficultyCalculator.cs
@@ -4,18 +4,142 @@
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mods;
+using System;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Mania
{
- public class ManiaDifficultyCalculator : DifficultyCalculator
+ internal class ManiaDifficultyCalculator : DifficultyCalculator
{
+ private const double star_scaling_factor = 0.018;
+
+ ///
+ /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step.
+ /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
+ /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
+ ///
+ private const double strain_step = 400;
+
+ ///
+ /// The weighting of each strain value decays to this number * it's previous value
+ ///
+ private const double decay_weight = 0.9;
+
+ ///
+ /// HitObjects are stored as a member variable.
+ ///
+ private readonly List difficultyHitObjects = new List();
+
public ManiaDifficultyCalculator(Beatmap beatmap)
: base(beatmap)
{
}
- public override double Calculate(Dictionary categoryDifficulty = null) => 0;
+ public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods)
+ : base(beatmap, mods)
+ {
+ }
+
+ public override double Calculate(Dictionary categoryDifficulty = null)
+ {
+ // Fill our custom DifficultyHitObject class, that carries additional information
+ difficultyHitObjects.Clear();
+
+ int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
+
+ foreach (var hitObject in Beatmap.HitObjects)
+ difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount));
+
+ // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
+ difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
+
+ if (!calculateStrainValues())
+ return 0;
+
+ double starRating = calculateDifficulty() * star_scaling_factor;
+
+ categoryDifficulty?.Add("Strain", starRating);
+
+ return starRating;
+ }
+
+ private bool calculateStrainValues()
+ {
+ // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
+ using (List.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
+ {
+ if (!hitObjectsEnumerator.MoveNext())
+ return false;
+
+ ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current;
+
+ // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
+ while (hitObjectsEnumerator.MoveNext())
+ {
+ var next = hitObjectsEnumerator.Current;
+ next?.CalculateStrains(current, TimeRate);
+ current = next;
+ }
+
+ return true;
+ }
+ }
+
+ private double calculateDifficulty()
+ {
+ double actualStrainStep = strain_step * TimeRate;
+
+ // Find the highest strain value within each strain step
+ List highestStrains = new List();
+ double intervalEndTime = actualStrainStep;
+ double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
+
+ ManiaHitObjectDifficulty previousHitObject = null;
+ foreach (var hitObject in difficultyHitObjects)
+ {
+ // While we are beyond the current interval push the currently available maximum to our strain list
+ while (hitObject.BaseHitObject.StartTime > intervalEndTime)
+ {
+ highestStrains.Add(maximumStrain);
+
+ // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
+ // until the beginning of the next interval.
+ if (previousHitObject == null)
+ {
+ maximumStrain = 0;
+ }
+ else
+ {
+ double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
+ double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
+ maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay;
+ }
+
+ // Go to the next time interval
+ intervalEndTime += actualStrainStep;
+ }
+
+ // Obtain maximum strain
+ double strain = hitObject.IndividualStrain + hitObject.OverallStrain;
+ maximumStrain = Math.Max(strain, maximumStrain);
+
+ previousHitObject = hitObject;
+ }
+
+ // Build the weighted sum over the highest strains for each interval
+ double difficulty = 0;
+ double weight = 1;
+ highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
+
+ foreach (double strain in highestStrains)
+ {
+ difficulty += weight * strain;
+ weight *= decay_weight;
+ }
+
+ return difficulty;
+ }
protected override BeatmapConverter CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
}
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 3bfb4d3e44..e135e14001 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -12,6 +12,8 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Mania
{
@@ -89,6 +91,7 @@ namespace osu.Game.Rulesets.Mania
},
new ManiaModRandom(),
new ManiaModDualStages(),
+ new ManiaModMirror(),
new MultiMod
{
Mods = new Mod[]
@@ -110,9 +113,11 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
- public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap);
+ public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
- public override int LegacyID => 3;
+ public override int? LegacyID => 3;
+
+ public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
index e14473c478..c8277af415 100644
--- a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
+++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs
@@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
///
internal class FastRandom
{
- private const double uint_to_real = 1.0 / (uint.MaxValue + 1.0);
+ private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087;
private const uint z = 3579807591;
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils
/// Generates a random double value within the range [0, 1).
///
/// The random value.
- public double NextDouble() => uint_to_real * NextUInt();
+ public double NextDouble() => int_to_real * Next();
private uint bitBuffer;
private int bitIndex = 32;
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
index 3c5179cef0..9ceb0ab7ea 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mods;
@@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
return new Score
{
User = new User { Username = "osu!topus!" },
- Replay = new ManiaAutoGenerator(beatmap).Generate(),
+ Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
};
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
index 7c7dc5e4f7..99f49e6620 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.3;
+ public override double ScoreMultiplier => 0.5;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
index 64ce86e748..a9d77988c8 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDoubleTime : ModDoubleTime
{
- public override double ScoreMultiplier => 1.0;
+ public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
index 3330d87e88..a1f9e0290e 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs
@@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages";
public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!";
- public override double ScoreMultiplier => 1;
- public override bool Ranked => false;
+ public override double ScoreMultiplier => 0;
public void ApplyToBeatmapConverter(BeatmapConverter beatmapConverter)
{
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
index 1faed5e1c0..0b3e851c64 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs
@@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModEasy : ModEasy
{
+ public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
index 03442507d6..ca5667a400 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs
@@ -9,10 +9,11 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModFadeIn : Mod
{
- public override string Name => "FadeIn";
+ public override string Name => "Fade In";
public override string ShortenedName => "FI";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden;
public override ModType Type => ModType.DifficultyIncrease;
+ public override string Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
index 89eb02268e..8d8693d11f 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModFlashlight : ModFlashlight
{
- public override double ScoreMultiplier => 1.0;
+ public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
index 2f8404609f..c00bb4275a 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.3;
+ public override double ScoreMultiplier => 0.5;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
index 91edbaf0cf..8b77ea4c25 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHardRock : ModHardRock
{
- public override double ScoreMultiplier => 1.0;
+ public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
index c2fc07da89..9317dba19f 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs
@@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHidden : ModHidden
{
- public override string Description => @"The notes fade out before you hit them!";
- public override double ScoreMultiplier => 1.0;
+ public override string Description => @"Keys fade out before you hit them!";
+ public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs
index 8a6943d99b..c0107e3758 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey1 : ManiaKeyMod
{
public override int KeyCount => 1;
- public override string Name => "1K";
+ public override string Name => "One Key";
+ public override string ShortenedName => "1K";
+ public override string Description => @"Play with one key.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs
index 553827ac1c..11dbe0ba76 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey2 : ManiaKeyMod
{
public override int KeyCount => 2;
- public override string Name => "2K";
+ public override string Name => "Two Keys";
+ public override string ShortenedName => "2K";
+ public override string Description => @"Play with two keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs
index ef048c848e..94ad53d8ea 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey3 : ManiaKeyMod
{
public override int KeyCount => 3;
- public override string Name => "3K";
+ public override string Name => "Three Keys";
+ public override string ShortenedName => "3K";
+ public override string Description => @"Play with three keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs
index 9c713d920f..d9c27c5ef1 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey4 : ManiaKeyMod
{
public override int KeyCount => 4;
- public override string Name => "4K";
+ public override string Name => "Four Keys";
+ public override string ShortenedName => "4K";
+ public override string Description => @"Play with four keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs
index a83faf4627..e54bae93a7 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey5 : ManiaKeyMod
{
public override int KeyCount => 5;
- public override string Name => "5K";
+ public override string Name => "Five Keys";
+ public override string ShortenedName => "5K";
+ public override string Description => @"Play with five keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs
index d7df901048..9c3bdf46b9 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey6 : ManiaKeyMod
{
public override int KeyCount => 6;
- public override string Name => "6K";
+ public override string Name => "Six Keys";
+ public override string ShortenedName => "6K";
+ public override string Description => @"Play with six keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs
index 4a3f9857e5..f17ac80be5 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey7 : ManiaKeyMod
{
public override int KeyCount => 7;
- public override string Name => "7K";
+ public override string Name => "Seven Keys";
+ public override string ShortenedName => "7K";
+ public override string Description => @"Play with seven keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs
index 22c301fb7a..36a6fc838f 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey8 : ManiaKeyMod
{
public override int KeyCount => 8;
- public override string Name => "8K";
+ public override string Name => "Eight Keys";
+ public override string ShortenedName => "8K";
+ public override string Description => @"Play with eight keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs
index b2a0bc4ddf..10f03e2480 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs
@@ -6,6 +6,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModKey9 : ManiaKeyMod
{
public override int KeyCount => 9;
- public override string Name => "9K";
+ public override string Name => "Nine Keys";
+ public override string ShortenedName => "9K";
+ public override string Description => @"Play with nine keys.";
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
new file mode 100644
index 0000000000..cfa5ef88b8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using System.Linq;
+
+namespace osu.Game.Rulesets.Mania.Mods
+{
+ public class ManiaModMirror : Mod, IApplicableToRulesetContainer
+ {
+ public override string Name => "Mirror";
+ public override string ShortenedName => "MR";
+ public override ModType Type => ModType.Special;
+ public override double ScoreMultiplier => 1;
+ public override bool Ranked => true;
+
+ public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
+ {
+ var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns;
+
+ rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
index a977eef5e3..a007224b74 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore
{
- public override double ScoreMultiplier => 1.0;
+ public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
index a6cbad44d7..df0f9a5437 100644
--- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
+++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs
@@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Random";
public override string ShortenedName => "RD";
public override FontAwesome Icon => FontAwesome.fa_osu_dice;
- public override string Description => @"Shuffle around the notes!";
- public override double ScoreMultiplier => 1;
+ public override string Description => @"Shuffle around the keys!";
+ public override double ScoreMultiplier => 0;
public void ApplyToRulesetContainer(RulesetContainer rulesetContainer)
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
index 91c83a62f0..83d67c855e 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
Height = 1;
- Add(new Box
+ AddInternal(new Box
{
Name = "Bar line",
Anchor = Anchor.BottomCentre,
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (isMajor)
{
- Add(new EquilateralTriangle
+ AddInternal(new EquilateralTriangle
{
Name = "Left triangle",
Anchor = Anchor.BottomLeft,
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Rotation = 90
});
- Add(new EquilateralTriangle
+ AddInternal(new EquilateralTriangle
{
Name = "Right triangle",
Anchor = Anchor.BottomRight,
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 5a9ff592bc..c3d6a69a72 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
@@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly GlowPiece glowPiece;
private readonly BodyPiece bodyPiece;
- private readonly Container tickContainer;
private readonly Container fullHeightContainer;
///
@@ -40,9 +38,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
+ Container tickContainer;
RelativeSizeAxes = Axes.X;
- AddRange(new Drawable[]
+ InternalChildren = new Drawable[]
{
// The hit object itself cannot be used for various elements because the tail overshoots it
// So a specialized container that is updated to contain the tail height is used
@@ -57,7 +56,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
},
- tickContainer = new Container { RelativeSizeAxes = Axes.Both },
+ tickContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ ChildrenEnumerable = HitObject.NestedHitObjects.OfType().Select(tick => new DrawableHoldNoteTick(tick)
+ {
+ HoldStartTime = () => holdStartTime
+ })
+ },
head = new DrawableHeadNote(this, action)
{
Anchor = Anchor.TopCentre,
@@ -68,18 +74,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
- });
+ };
- foreach (var tick in HitObject.NestedHitObjects.OfType())
- {
- var drawableTick = new DrawableHoldNoteTick(tick)
- {
- HoldStartTime = () => holdStartTime
- };
-
- tickContainer.Add(drawableTick);
- AddNested(drawableTick);
- }
+ foreach (var tick in tickContainer)
+ AddNested(tick);
AddNested(head);
AddNested(tail);
@@ -90,12 +88,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
- if (base.AccentColour == value)
- return;
base.AccentColour = value;
- tickContainer.Children.ForEach(t => t.AccentColour = value);
-
glowPiece.AccentColour = value;
bodyPiece.AccentColour = value;
head.AccentColour = value;
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
index f9c0b96d37..b50a5e897e 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
Size = new Vector2(1);
- Children = new[]
+ InternalChildren = new[]
{
glowContainer = new CircularContainer
{
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index 0a1624b464..3aec8d25f9 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
-using OpenTK.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@@ -28,16 +27,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != null)
Action = action.Value;
}
-
- public override Color4 AccentColour
- {
- get { return base.AccentColour; }
- set
- {
- if (base.AccentColour == value)
- return;
- base.AccentColour = value;
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
index 8944978bdd..c171325fb2 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
laneGlowPiece = new LaneGlowPiece
{
@@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
- if (base.AccentColour == value)
- return;
base.AccentColour = value;
-
- laneGlowPiece.AccentColour = value;
- GlowPiece.AccentColour = value;
- headPiece.AccentColour = value;
+ laneGlowPiece.AccentColour = AccentColour;
+ GlowPiece.AccentColour = AccentColour;
+ headPiece.AccentColour = AccentColour;
}
}
diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs
new file mode 100644
index 0000000000..2b59279972
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs
@@ -0,0 +1,113 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Objects.Types;
+using System;
+
+namespace osu.Game.Rulesets.Mania.Objects
+{
+ internal class ManiaHitObjectDifficulty
+ {
+ ///
+ /// Factor by how much individual / overall strain decays per second.
+ ///
+ ///
+ /// These values are results of tweaking a lot and taking into account general feedback.
+ ///
+ internal const double INDIVIDUAL_DECAY_BASE = 0.125;
+ internal const double OVERALL_DECAY_BASE = 0.30;
+
+ internal ManiaHitObject BaseHitObject;
+
+ private readonly int beatmapColumnCount;
+
+
+ private readonly double endTime;
+ private readonly double[] heldUntil;
+
+ ///
+ /// Measures jacks or more generally: repeated presses of the same button
+ ///
+ private readonly double[] individualStrains;
+
+ internal double IndividualStrain
+ {
+ get
+ {
+ return individualStrains[BaseHitObject.Column];
+ }
+
+ set
+ {
+ individualStrains[BaseHitObject.Column] = value;
+ }
+ }
+
+ ///
+ /// Measures note density in a way
+ ///
+ internal double OverallStrain = 1;
+
+ public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount)
+ {
+ BaseHitObject = baseHitObject;
+
+ endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime;
+
+ beatmapColumnCount = columnCount;
+ heldUntil = new double[beatmapColumnCount];
+ individualStrains = new double[beatmapColumnCount];
+
+ for (int i = 0; i < beatmapColumnCount; ++i)
+ {
+ individualStrains[i] = 0;
+ heldUntil[i] = 0;
+ }
+ }
+
+ internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate)
+ {
+ // TODO: Factor in holds
+ double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
+ double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000);
+ double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000);
+
+ double holdFactor = 1.0; // Factor to all additional strains in case something else is held
+ double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly
+
+ // Fill up the heldUntil array
+ for (int i = 0; i < beatmapColumnCount; ++i)
+ {
+ heldUntil[i] = previousHitObject.heldUntil[i];
+
+ // If there is at least one other overlapping end or note, then we get an addition, buuuuuut...
+ if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i])
+ {
+ holdAddition = 1.0;
+ }
+
+ // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1
+ if (endTime == heldUntil[i])
+ {
+ holdAddition = 0;
+ }
+
+ // We give a slight bonus to everything if something is held meanwhile
+ if (heldUntil[i] > endTime)
+ {
+ holdFactor = 1.25;
+ }
+
+ // Decay individual strains
+ individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay;
+ }
+
+ heldUntil[BaseHitObject.Column] = endTime;
+
+ // Increase individual strain in own column
+ IndividualStrain += 2.0 * holdFactor;
+
+ OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
index 6f6217f540..5a992bb970 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
@@ -3,7 +3,7 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
@@ -15,10 +15,31 @@ namespace osu.Game.Rulesets.Mania.Replays
{
public const double RELEASE_DELAY = 20;
- public ManiaAutoGenerator(Beatmap beatmap)
+ public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap;
+
+ private readonly ManiaAction[] columnActions;
+
+ public ManiaAutoGenerator(ManiaBeatmap beatmap)
: base(beatmap)
{
Replay = new Replay { User = new User { Username = @"Autoplay" } };
+
+ columnActions = new ManiaAction[Beatmap.TotalColumns];
+
+ var normalAction = ManiaAction.Key1;
+ var specialAction = ManiaAction.Special1;
+ int totalCounter = 0;
+ foreach (var stage in Beatmap.Stages)
+ {
+ for (int i = 0; i < stage.Columns; i++)
+ {
+ if (stage.IsSpecialColumn(i))
+ columnActions[totalCounter] = specialAction++;
+ else
+ columnActions[totalCounter] = normalAction++;
+ totalCounter++;
+ }
+ }
}
protected Replay Replay;
@@ -30,18 +51,18 @@ namespace osu.Game.Rulesets.Mania.Replays
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
- int activeColumns = 0;
+ var actions = new List();
foreach (var group in pointGroups)
{
foreach (var point in group)
{
if (point is HitPoint)
- activeColumns |= 1 << point.Column;
+ actions.Add(columnActions[point.Column]);
if (point is ReleasePoint)
- activeColumns ^= 1 << point.Column;
+ actions.Remove(columnActions[point.Column]);
}
- Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
+ Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
}
return Replay;
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
index fd084f138f..3541561418 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs
@@ -4,40 +4,19 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input;
-using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
{
- internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
+ internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
{
- private readonly ManiaRulesetContainer container;
-
- public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
+ public ManiaFramedReplayInputHandler(Replay replay)
: base(replay)
{
- this.container = container;
}
- private ManiaPlayfield playfield;
- public override List GetPendingStates()
- {
- var actions = new List();
+ protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any();
- if (playfield == null)
- playfield = (ManiaPlayfield)container.Playfield;
-
- int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
- int counter = 0;
- while (activeColumns > 0)
- {
- if ((activeColumns & 1) > 0)
- actions.Add(playfield.Columns.ElementAt(counter).Action);
- counter++;
- activeColumns >>= 1;
- }
-
- return new List { new ReplayState { PressedActions = actions } };
- }
+ public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
}
}
diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
index e5c5ac9eeb..9990f89b99 100644
--- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
+++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs
@@ -1,17 +1,59 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Replays.Legacy;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Mania.Replays
{
- public class ManiaReplayFrame : ReplayFrame
+ public class ManiaReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
- public override bool IsImportant => MouseX > 0;
+ public List Actions = new List();
- public ManiaReplayFrame(double time, int activeColumns)
- : base(time, activeColumns, null, ReplayButtonState.None)
+ public ManiaReplayFrame()
{
}
+
+ public ManiaReplayFrame(double time, params ManiaAction[] actions)
+ : base(time)
+ {
+ Actions.AddRange(actions);
+ }
+
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ {
+ // We don't need to fully convert, just create the converter
+ var converter = new ManiaBeatmapConverter(beatmap.BeatmapInfo.RulesetID == 3, beatmap);
+
+ // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
+ // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
+
+ var stage = new StageDefinition { Columns = converter.TargetColumns };
+
+ var normalAction = ManiaAction.Key1;
+ var specialAction = ManiaAction.Special1;
+
+ int activeColumns = (int)(legacyFrame.MouseX ?? 0);
+ int counter = 0;
+ while (activeColumns > 0)
+ {
+ var isSpecial = stage.IsSpecialColumn(counter);
+
+ if ((activeColumns & 1) > 0)
+ Actions.Add(isSpecial ? specialAction : normalAction);
+
+ if (isSpecial)
+ specialAction++;
+ else
+ normalAction++;
+
+ counter++;
+ activeColumns >>= 1;
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..d593b2b052
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,103 @@
+{
+ "Mappings": [{
+ "StartTime": 500,
+ "Objects": [{
+ "StartTime": 500,
+ "EndTime": 2500,
+ "Column": 0
+ },
+ {
+ "StartTime": 1500,
+ "EndTime": 2500,
+ "Column": 1
+ }
+ ]
+ },
+ {
+ "StartTime": 3000,
+ "Objects": [{
+ "StartTime": 3000,
+ "EndTime": 4000,
+ "Column": 2
+ }]
+ },
+ {
+ "StartTime": 4500,
+ "Objects": [{
+ "StartTime": 4500,
+ "EndTime": 5500,
+ "Column": 4
+ }]
+ },
+ {
+ "StartTime": 6000,
+ "Objects": [{
+ "StartTime": 6000,
+ "EndTime": 6500,
+ "Column": 2
+ }]
+ },
+ {
+ "StartTime": 7000,
+ "Objects": [{
+ "StartTime": 7000,
+ "EndTime": 8000,
+ "Column": 2
+ }]
+ },
+ {
+ "StartTime": 8500,
+ "Objects": [{
+ "StartTime": 8500,
+ "EndTime": 11000,
+ "Column": 0
+ }]
+ },
+ {
+ "StartTime": 11500,
+ "Objects": [{
+ "StartTime": 11500,
+ "EndTime": 12000,
+ "Column": 1
+ }]
+ },
+ {
+ "StartTime": 12500,
+ "Objects": [{
+ "StartTime": 12500,
+ "EndTime": 16500,
+ "Column": 4
+ }]
+ },
+ {
+ "StartTime": 17000,
+ "Objects": [{
+ "StartTime": 17000,
+ "EndTime": 18000,
+ "Column": 2
+ }]
+ },
+ {
+ "StartTime": 18500,
+ "Objects": [{
+ "StartTime": 18500,
+ "EndTime": 19450,
+ "Column": 0
+ }]
+ },
+ {
+ "StartTime": 19875,
+ "Objects": [{
+ "StartTime": 19875,
+ "EndTime": 23875,
+ "Column": 1
+ },
+ {
+ "StartTime": 19875,
+ "EndTime": 23875,
+ "Column": 0
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs
new file mode 100644
index 0000000000..9d55ab643d
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Tests/ManiaBeatmapConversionTest.cs
@@ -0,0 +1,60 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class ManiaBeatmapConversionTest : BeatmapConversionTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
+
+ private bool isForCurrentRuleset;
+
+ [NonParallelizable]
+ [TestCase("basic", false)]
+ public void Test(string name, bool isForCurrentRuleset)
+ {
+ this.isForCurrentRuleset = isForCurrentRuleset;
+ base.Test(name);
+ }
+
+ protected override IEnumerable CreateConvertValue(HitObject hitObject)
+ {
+ yield return new ConvertValue
+ {
+ StartTime = hitObject.StartTime,
+ EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+ Column = ((ManiaHitObject)hitObject).Column
+ };
+ }
+
+ protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
+ }
+
+ public struct ConvertValue : IEquatable
+ {
+ ///
+ /// A sane value to account for osu!stable using ints everwhere.
+ ///
+ private const float conversion_lenience = 2;
+
+ public double StartTime;
+ public double EndTime;
+ public int Column;
+
+ public bool Equals(ConvertValue other)
+ => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+ && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+ && Column == other.Column;
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
index 81c6c5c9d5..2453d8281a 100644
--- a/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseAutoGeneration.cs
@@ -1,15 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Linq;
using NUnit.Framework;
-using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseAutoGeneration : OsuTestCase
{
[Test]
@@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - |
// | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@@ -27,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
- Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@@ -40,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * |
// | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
@@ -48,8 +50,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 0 has not been pressed");
- Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 0 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | - |
// | | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
@@ -68,8 +70,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
- Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@@ -81,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | * |
// | | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
@@ -90,8 +92,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
- Assert.AreEqual(3, generated.Frames[1].MouseX, "Keys 1 and 2 have not been pressed");
- Assert.AreEqual(0, generated.Frames[2].MouseX, "Keys 1 and 2 have not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@@ -102,7 +104,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | - | |
// | | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
@@ -113,10 +115,10 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
- Assert.AreEqual(0, generated.Frames[2].MouseX, "Key 1 has not been released");
- Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 2 has not been pressed");
- Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@@ -129,7 +131,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
@@ -140,10 +142,11 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
- Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
- Assert.AreEqual(3, generated.Frames[2].MouseX, "Keys 1 and 2 have not been pressed");
- Assert.AreEqual(2, generated.Frames[3].MouseX, "Key 1 has not been released");
- Assert.AreEqual(0, generated.Frames[4].MouseX, "Key 2 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
+ Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@@ -155,7 +158,7 @@ namespace osu.Game.Rulesets.Mania.Tests
// | * | |
// | | |
- var beatmap = new Beatmap();
+ var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
@@ -165,9 +168,12 @@ namespace osu.Game.Rulesets.Mania.Tests
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
- Assert.AreEqual(1, generated.Frames[1].MouseX, "Key 1 has not been pressed");
- Assert.AreEqual(2, generated.Frames[2].MouseX, "Key 1 has not been released or key 2 has not been pressed");
- Assert.AreEqual(0, generated.Frames[3].MouseX, "Keys 1 and 2 have not been released");
+ Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
+ Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
+ Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
}
+
+ private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
}
}
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
index 0e6d40dc67..fe8749e830 100644
--- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaHitObjects.cs
@@ -13,7 +13,6 @@ using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseManiaHitObjects : OsuTestCase
{
public TestCaseManiaHitObjects()
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
index 7d35ab2f4d..4793b1ce94 100644
--- a/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCaseManiaPlayfield.cs
@@ -8,7 +8,9 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@@ -19,7 +21,6 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseManiaPlayfield : OsuTestCase
{
private const double start_time = 500;
@@ -92,10 +93,17 @@ namespace osu.Game.Rulesets.Mania.Tests
});
}
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
+
[BackgroundDependencyLoader]
- private void load(RulesetStore rulesets)
+ private void load(RulesetStore rulesets, SettingsStore settings)
{
maniaRuleset = rulesets.GetRuleset(3);
+
+ dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
}
private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
diff --git a/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
index c76816db6a..3c776a2f4c 100644
--- a/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
+++ b/osu.Game.Rulesets.Mania/Tests/TestCasePerformancePoints.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
index 8a03f5a785..f50f077c76 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs
@@ -1,17 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI
{
internal class DrawableManiaJudgement : DrawableJudgement
{
- public DrawableManiaJudgement(Judgement judgement)
- : base(judgement)
+ public DrawableManiaJudgement(Judgement judgement, DrawableHitObject judgedObject)
+ : base(judgement, judgedObject)
{
- JudgementText.TextSize = 25;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ if (JudgementText != null)
+ JudgementText.TextSize = 25;
}
protected override void LoadComplete()
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
index 436d5c1ea6..3ecfee1e8c 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs
@@ -11,6 +11,7 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
+using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
@@ -101,9 +102,9 @@ namespace osu.Game.Rulesets.Mania.UI
return null;
}
- protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
+ protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
}
diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
index ebd73d7dca..d4ca704829 100644
--- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
+++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
@@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Container content;
public Container Judgements => judgements;
- private readonly Container judgements;
+ private readonly JudgementContainer judgements;
private readonly Container topLevelContainer;
@@ -48,13 +49,11 @@ namespace osu.Game.Rulesets.Mania.UI
private Color4 specialColumnColour;
private readonly int firstColumnIndex;
- private readonly StageDefinition definition;
public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction)
: base(ScrollingDirection.Up)
{
this.firstColumnIndex = firstColumnIndex;
- this.definition = definition;
Name = "Stage";
@@ -116,7 +115,7 @@ namespace osu.Game.Rulesets.Mania.UI
Padding = new MarginPadding { Top = HIT_TARGET_POSITION }
}
},
- judgements = new Container
+ judgements = new JudgementContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
@@ -131,7 +130,7 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < definition.Columns; i++)
{
- var isSpecial = isSpecialColumn(i);
+ var isSpecial = definition.IsSpecialColumn(i);
var column = new Column
{
IsSpecial = isSpecial,
@@ -160,13 +159,6 @@ namespace osu.Game.Rulesets.Mania.UI
AddNested(c);
}
- ///
- /// Whether the column index is a special column for this playfield.
- ///
- /// The 0-based column index.
- /// Whether the column is a special column.
- private bool isSpecialColumn(int column) => definition.Columns % 2 == 1 && column == definition.Columns / 2;
-
public override void Add(DrawableHitObject h)
{
var maniaObject = (ManiaHitObject)h.HitObject;
@@ -180,7 +172,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
judgements.Clear();
- judgements.Add(new DrawableManiaJudgement(judgement)
+ judgements.Add(new DrawableManiaJudgement(judgement, judgedObject)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
index 3dad5b508c..42b22a71ec 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs
@@ -13,24 +13,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
public override void PostProcess(Beatmap beatmap)
{
applyStacking(beatmap);
-
- if (beatmap.ComboColors.Count == 0)
- return;
-
- int comboIndex = 0;
- int colourIndex = 0;
-
- foreach (var obj in beatmap.HitObjects)
- {
- if (obj.NewCombo)
- {
- comboIndex = 0;
- colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count;
- }
-
- obj.IndexInCurrentCombo = comboIndex++;
- obj.ComboColour = beatmap.ComboColors[colourIndex];
- }
+ base.PostProcess(beatmap);
}
private void applyStacking(Beatmap beatmap)
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs
new file mode 100644
index 0000000000..b48dd73bb5
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Allocation;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+
+namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
+{
+ public class HitCircleMask : HitObjectMask
+ {
+ public HitCircleMask(DrawableHitCircle hitCircle)
+ : base(hitCircle)
+ {
+ Origin = Anchor.Centre;
+
+ Position = hitCircle.Position;
+ Size = hitCircle.Size;
+ Scale = hitCircle.Scale;
+
+ AddInternal(new RingPiece());
+
+ hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Colour = colours.Yellow;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
new file mode 100644
index 0000000000..586b516a11
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
@@ -0,0 +1,56 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
+{
+ public class SliderCircleMask : HitObjectMask
+ {
+ public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider)
+ : this(sliderHead, Vector2.Zero, slider)
+ {
+ }
+
+ public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider)
+ : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider)
+ {
+ }
+
+ private readonly DrawableOsuHitObject hitObject;
+
+ private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider)
+ : base(hitObject)
+ {
+ this.hitObject = hitObject;
+
+ Origin = Anchor.Centre;
+
+ Position = position;
+ Size = slider.HeadCircle.Size;
+ Scale = slider.HeadCircle.Scale;
+
+ AddInternal(new RingPiece());
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ Colour = colours.Yellow;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ RelativeAnchorPosition = hitObject.RelativeAnchorPosition;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
new file mode 100644
index 0000000000..53f02617cd
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
@@ -0,0 +1,63 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
+{
+ public class SliderMask : HitObjectMask
+ {
+ private readonly SliderBody body;
+ private readonly DrawableSlider slider;
+
+ public SliderMask(DrawableSlider slider)
+ : base(slider)
+ {
+ this.slider = slider;
+
+ Position = slider.Position;
+
+ var sliderObject = (Slider)slider.HitObject;
+
+ InternalChildren = new Drawable[]
+ {
+ body = new SliderBody(sliderObject)
+ {
+ AccentColour = Color4.Transparent,
+ PathWidth = sliderObject.Scale * 64
+ },
+ new SliderCircleMask(slider.HeadCircle, slider),
+ new SliderCircleMask(slider.TailCircle, slider),
+ };
+
+ sliderObject.PositionChanged += _ => Position = slider.Position;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ body.BorderColour = colours.Yellow;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ Size = slider.Size;
+ OriginPosition = slider.OriginPosition;
+
+ // Need to cause one update
+ body.UpdateProgress(0);
+ }
+
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
index 56efc25fa5..a8d895bc1d 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
+using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override Playfield CreatePlayfield() => new OsuEditPlayfield();
+ protected override Vector2 PlayfieldArea => Vector2.One;
+
protected override CursorContainer CreateCursor() => null;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 6652a5fde2..026c85d909 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -2,10 +2,15 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Edit
@@ -25,5 +30,20 @@ namespace osu.Game.Rulesets.Osu.Edit
new HitObjectCompositionTool(),
new HitObjectCompositionTool()
};
+
+ protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both };
+
+ public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case DrawableHitCircle circle:
+ return new HitCircleMask(circle);
+ case DrawableSlider slider:
+ return new SliderMask(slider);
+ }
+
+ return base.CreateMaskFor(hitObject);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
index eb90338e2f..987bb28932 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
index 80c83bf5d8..d842b607c6 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs
@@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModEasy : ModEasy
{
+ public override string Description => @"Larger circles, more forgiving HP drain, less accuracy required, and three lives!";
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
index 7d009b0344..1b9291bcf3 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
index dfbe9ad021..74c3585d3d 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
@@ -12,7 +14,6 @@ namespace osu.Game.Rulesets.Osu.Mods
public class OsuModHardRock : ModHardRock, IApplicableToHitObject
{
public override double ScoreMultiplier => 1.06;
- public override bool Ranked => true;
public void ApplyToHitObject(OsuHitObject hitObject)
{
@@ -22,8 +23,14 @@ namespace osu.Game.Rulesets.Osu.Mods
if (slider == null)
return;
+ slider.HeadCircle.Position = new Vector2(slider.HeadCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.HeadCircle.Position.Y);
+ slider.TailCircle.Position = new Vector2(slider.TailCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.TailCircle.Position.Y);
+
+ slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
+ slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
+
var newControlPoints = new List();
- slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, OsuPlayfield.BASE_SIZE.Y - c.Y)));
+ slider.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, -c.Y)));
slider.ControlPoints = newControlPoints;
slider.Curve?.Calculate(); // Recalculate the slider curve
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index b4dd08eadb..1117b5bbd5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -1,19 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
{
- public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
+ public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
@@ -24,7 +26,10 @@ namespace osu.Game.Rulesets.Osu.Mods
foreach (var d in drawables.OfType())
{
d.ApplyCustomUpdateState += ApplyHiddenState;
+
d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier;
+ foreach (var h in d.HitObject.NestedHitObjects.OfType())
+ h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier;
}
}
@@ -33,30 +38,37 @@ namespace osu.Game.Rulesets.Osu.Mods
if (!(drawable is DrawableOsuHitObject d))
return;
- var fadeOutStartTime = d.HitObject.StartTime - d.HitObject.TimePreempt + d.HitObject.TimeFadein;
- var fadeOutDuration = d.HitObject.TimePreempt * fade_out_duration_multiplier;
+ var h = d.HitObject;
+
+ var fadeOutStartTime = h.StartTime - h.TimePreempt + h.TimeFadein;
+ var fadeOutDuration = h.TimePreempt * fade_out_duration_multiplier;
// new duration from completed fade in to end (before fading out)
- var longFadeDuration = ((d.HitObject as IHasEndTime)?.EndTime ?? d.HitObject.StartTime) - fadeOutStartTime;
+ var longFadeDuration = ((h as IHasEndTime)?.EndTime ?? h.StartTime) - fadeOutStartTime;
switch (drawable)
{
case DrawableHitCircle circle:
// we don't want to see the approach circle
- circle.ApproachCircle.Hide();
+ using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ circle.ApproachCircle.Hide();
// fade out immediately after fade in.
using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true))
- {
circle.FadeOut(fadeOutDuration);
- }
break;
case DrawableSlider slider:
using (slider.BeginAbsoluteSequence(fadeOutStartTime, true))
- {
slider.Body.FadeOut(longFadeDuration, Easing.Out);
- }
+
+ break;
+ case DrawableSliderTick sliderTick:
+ // slider ticks fade out over up to one second
+ var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000);
+
+ using (sliderTick.BeginAbsoluteSequence(sliderTick.HitObject.StartTime - tickFadeOutDuration, true))
+ sliderTick.FadeOut(tickFadeOutDuration);
break;
case DrawableSpinner spinner:
@@ -66,9 +78,7 @@ namespace osu.Game.Rulesets.Osu.Mods
spinner.Background.Hide();
using (spinner.BeginAbsoluteSequence(fadeOutStartTime + longFadeDuration, true))
- {
spinner.FadeOut(fadeOutDuration);
- }
break;
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
index 057916c04b..c9def8c8cf 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModRelax : ModRelax
{
- public override string Description => "You don't need to click.\nGive your clicking/tapping finger a break from the heat of things.";
+ public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things.";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray();
}
}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
index 18b212f781..401e56a3c8 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Spun Out";
public override string ShortenedName => "SO";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout;
- public override string Description => @"Spinners will be automatically completed";
+ public override string Description => @"Spinners will be automatically completed.";
public override double ScoreMultiplier => 0.9;
public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(OsuModAutopilot) };
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index b2b5130be3..613fbc4e32 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
@@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Name => "Target";
public override string ShortenedName => "TP";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_target;
- public override string Description => @"";
+ public override string Description => @"Practice keeping up with the beat of the song.";
public override double ScoreMultiplier => 1;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
index 959c87bbba..9066a9ef92 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs
@@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -21,22 +22,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number;
private readonly GlowPiece glow;
- public DrawableHitCircle(HitCircle h) : base(h)
+ public DrawableHitCircle(HitCircle h)
+ : base(h)
{
Origin = Anchor.Centre;
Position = HitObject.StackedPosition;
Scale = new Vector2(h.Scale);
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
- glow = new GlowPiece
- {
- Colour = AccentColour
- },
+ glow = new GlowPiece(),
circle = new CirclePiece
{
- Colour = AccentColour,
Hit = () =>
{
if (AllJudged)
@@ -52,20 +50,31 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ring = new RingPiece(),
flash = new FlashPiece(),
- explode = new ExplodePiece
- {
- Colour = AccentColour,
- },
+ explode = new ExplodePiece(),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
Scale = new Vector2(4),
- Colour = AccentColour,
}
};
//may not be so correct
Size = circle.DrawSize;
+
+ HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
+ }
+
+ public override Color4 AccentColour
+ {
+ get { return base.AccentColour; }
+ set
+ {
+ base.AccentColour = value;
+ explode.Colour = AccentColour;
+ glow.Colour = AccentColour;
+ circle.Colour = AccentColour;
+ ApproachCircle.Colour = AccentColour;
+ }
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index c8e42fa44f..d4d89c2aa3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -5,6 +5,9 @@ using System.ComponentModel;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using System.Linq;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Skinning;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
- AccentColour = HitObject.ComboColour;
Alpha = 0;
}
@@ -35,6 +37,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
+ protected override void SkinChanged(ISkinSource skin, bool allowFallback)
+ {
+ base.SkinChanged(skin, allowFallback);
+
+ if (HitObject is IHasComboInformation combo)
+ AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
+ }
+
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdateCurrentState(ArmedState state)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
index 716f4b629b..1468c82b57 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs
@@ -2,24 +2,24 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
-using osu.Game.Rulesets.Osu.Judgements;
using OpenTK;
using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuJudgement : DrawableJudgement
{
- public DrawableOsuJudgement(OsuJudgement judgement)
- : base(judgement)
+ public DrawableOsuJudgement(Judgement judgement, DrawableHitObject judgedObject)
+ : base(judgement, judgedObject)
{
}
protected override void LoadComplete()
{
if (Judgement.Result != HitResult.Miss)
- JudgementText.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
+ JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.LoadComplete();
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
index 79a4714e33..94179f30d3 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Blending = BlendingMode.Additive;
Origin = Anchor.Centre;
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
new SpriteIcon
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 5795bb8405..3872821b96 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -7,11 +7,13 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
-using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
+using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -21,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly List components = new List();
public readonly DrawableHitCircle HeadCircle;
+ public readonly DrawableSliderTail TailCircle;
+
public readonly SliderBody Body;
public readonly SliderBall Ball;
@@ -29,29 +33,28 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
slider = s;
- DrawableSliderTail tail;
+ Position = s.StackedPosition;
+
Container ticks;
Container repeatPoints;
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
Body = new SliderBody(s)
{
- AccentColour = AccentColour,
- Position = s.StackedPosition,
PathWidth = s.Scale * 64,
},
- ticks = new Container(),
- repeatPoints = new Container(),
+ ticks = new Container { RelativeSizeAxes = Axes.Both },
+ repeatPoints = new Container { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s)
{
+ BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale),
- AccentColour = AccentColour,
AlwaysPresent = true,
Alpha = 0
},
- HeadCircle = new DrawableHitCircle(s.HeadCircle),
- tail = new DrawableSliderTail(s.TailCircle)
+ HeadCircle = new DrawableSliderHead(s, s.HeadCircle),
+ TailCircle = new DrawableSliderTail(s, s.TailCircle)
};
components.Add(Body);
@@ -59,15 +62,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddNested(HeadCircle);
- AddNested(tail);
- components.Add(tail);
+ AddNested(TailCircle);
+ components.Add(TailCircle);
foreach (var tick in s.NestedHitObjects.OfType())
{
- var drawableTick = new DrawableSliderTick(tick)
- {
- Position = tick.Position
- };
+ var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position };
ticks.Add(drawableTick);
components.Add(drawableTick);
@@ -76,18 +76,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType())
{
- var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
- {
- Position = repeatPoint.Position
- };
+ var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position };
repeatPoints.Add(drawableRepeatPoint);
components.Add(drawableRepeatPoint);
AddNested(drawableRepeatPoint);
}
+
+ HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
+ }
+
+ public override Color4 AccentColour
+ {
+ get { return base.AccentColour; }
+ set
+ {
+ base.AccentColour = value;
+ Body.AccentColour = AccentColour;
+ Ball.AccentColour = AccentColour;
+ }
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn);
+ config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut);
}
- private int currentSpan;
public bool Tracking;
protected override void Update()
@@ -96,21 +112,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Tracking = Ball.Tracking;
- double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
+ double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
- int span = slider.SpanAt(progress);
- progress = slider.ProgressAt(progress);
-
- if (span > currentSpan)
- currentSpan = span;
-
- //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
- if (!HeadCircle.IsHit)
- HeadCircle.Position = slider.Curve.PositionAt(progress);
-
- foreach (var c in components.OfType()) c.UpdateProgress(progress, span);
+ foreach (var c in components.OfType()) c.UpdateProgress(completionProgress);
foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType()) t.Tracking = Ball.Tracking;
+
+ Size = Body.Size;
+ OriginPosition = Body.PathOffset;
+
+ if (DrawSize != Vector2.Zero)
+ {
+ var childAnchorPosition = Vector2.Divide(OriginPosition, DrawSize);
+ foreach (var obj in NestedHitObjects)
+ obj.RelativeAnchorPosition = childAnchorPosition;
+ Ball.RelativeAnchorPosition = childAnchorPosition;
+ }
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
@@ -153,11 +170,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
}
+
+ Expire(true);
}
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
- public override Vector2 SelectionPoint => ToScreenSpace(Body.Position);
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos);
+
+ public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition);
public override Quad SelectionQuad => Body.PathDrawQuad;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
new file mode 100644
index 0000000000..cf36d5fc14
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs
@@ -0,0 +1,32 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Game.Rulesets.Objects.Types;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables
+{
+ public class DrawableSliderHead : DrawableHitCircle
+ {
+ private readonly Slider slider;
+
+ public DrawableSliderHead(Slider slider, HitCircle h)
+ : base(h)
+ {
+ this.slider = slider;
+
+ Position = HitObject.Position - slider.Position;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
+
+ //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
+ if (!IsHit)
+ Position = slider.CurvePositionAt(completionProgress);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
index 8835fc2b29..b277e7df7a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs
@@ -16,11 +16,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public bool Tracking { get; set; }
- public DrawableSliderTail(HitCircle hitCircle)
+ public DrawableSliderTail(Slider slider, HitCircle hitCircle)
: base(hitCircle)
{
- AlwaysPresent = true;
+ Origin = Anchor.Centre;
+
RelativeSizeAxes = Axes.Both;
+ FillMode = FillMode.Fit;
+
+ AlwaysPresent = true;
+
+ Position = HitObject.Position - slider.Position;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
index 41d73a745a..22bf63814c 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs
@@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking
{
- private const double anim_duration = 150;
+ public const double ANIM_DURATION = 150;
public bool Tracking { get; set; }
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
BorderThickness = 2;
BorderColour = Color4.White;
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
new Box
{
@@ -50,10 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdatePreemptState()
{
- this.Animate(
- d => d.FadeIn(anim_duration),
- d => d.ScaleTo(0.5f).ScaleTo(1f, anim_duration * 4, Easing.OutElasticHalf)
- );
+ this.FadeOut().FadeIn(ANIM_DURATION);
+ this.ScaleTo(0.5f).ScaleTo(1f, ANIM_DURATION * 4, Easing.OutElasticHalf);
}
protected override void UpdateCurrentState(ArmedState state)
@@ -64,12 +62,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.Delay(HitObject.TimePreempt).FadeOut();
break;
case ArmedState.Miss:
- this.FadeOut(anim_duration)
- .FadeColour(Color4.Red, anim_duration / 2);
+ this.FadeOut(ANIM_DURATION);
+ this.FadeColour(Color4.Red, ANIM_DURATION / 2);
break;
case ArmedState.Hit:
- this.FadeOut(anim_duration, Easing.OutQuint)
- .ScaleTo(Scale * 1.5f, anim_duration, Easing.Out);
+ this.FadeOut(ANIM_DURATION, Easing.OutQuint);
+ this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out);
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 722ab4c6d5..2705c213d9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Spinner = s;
- Children = new Drawable[]
+ InternalChildren = new Drawable[]
{
circleContainer = new Container
{
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
index 61e9027157..51f8b7026a 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs
@@ -6,30 +6,24 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class ApproachCircle : Container
{
- private readonly Sprite approachCircle;
-
public ApproachCircle()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- AutoSizeAxes = Axes.Both;
-
- Children = new Drawable[]
- {
- approachCircle = new Sprite()
- };
+ RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- approachCircle.Texture = textures.Get(@"Play/osu/approachcircle");
+ Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) });
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
index 286df14056..e7b6598cf2 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs
@@ -2,20 +2,16 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
-using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
+using osu.Game.Skinning;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class CirclePiece : Container, IKeyBindingHandler
{
- private readonly Sprite disc;
-
public Func Hit;
public CirclePiece()
@@ -27,26 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- Children = new Drawable[]
- {
- disc = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre
- },
- new TrianglesPiece
- {
- RelativeSizeAxes = Axes.Both,
- Blending = BlendingMode.Additive,
- Alpha = 0.5f,
- }
- };
- }
-
- [BackgroundDependencyLoader]
- private void load(TextureStore textures)
- {
- disc.Texture = textures.Get(@"Play/osu/disc");
+ InternalChild = new SkinnableDrawable("Play/osu/hitcircle", _ => new DefaultCirclePiece());
}
public bool OnPressed(OsuAction action)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
new file mode 100644
index 0000000000..61f73b6d66
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs
@@ -0,0 +1,35 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
+{
+ public class DefaultCirclePiece : Container
+ {
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ RelativeSizeAxes = Axes.Both;
+ Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = textures.Get(@"Play/osu/disc"),
+ },
+ new TrianglesPiece
+ {
+ RelativeSizeAxes = Axes.Both,
+ Blending = BlendingMode.Additive,
+ Alpha = 0.5f,
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
index 9be951e29c..28552e6c36 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Game.Skinning;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@@ -19,15 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingMode.Additive;
Alpha = 0;
- Children = new Drawable[]
+ Child = new SkinnableDrawable("Play/osu/hitcircle-explode", _ => new TrianglesPiece
{
- new TrianglesPiece
- {
- Blending = BlendingMode.Additive,
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.2f,
- }
- };
+ Blending = BlendingMode.Additive,
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.2f,
+ }, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
index 56faa335b1..50dc473750 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -14,22 +15,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Size = new Vector2(128);
- Masking = true;
- CornerRadius = Size.X / 2;
-
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
Blending = BlendingMode.Additive;
Alpha = 0;
- Children = new Drawable[]
+ Child = new SkinnableDrawable("Play/osu/hitcircle-flash", name => new CircularContainer
{
- new Box
+ Masking = true,
+ RelativeSizeAxes = Axes.Both,
+ Child = new Box
{
RelativeSizeAxes = Axes.Both
}
- };
+ }, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
index 9a1208f998..211e138b65 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs
@@ -6,34 +6,30 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
public class GlowPiece : Container
{
- private readonly Sprite layer;
-
public GlowPiece()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
-
- Children = new[]
- {
- layer = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Blending = BlendingMode.Additive,
- Alpha = 0.5f
- }
- };
+ RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
- layer.Texture = textures.Get(@"Play/osu/ring-glow");
+ Child = new SkinnableDrawable("Play/osu/ring-glow", name => new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = textures.Get(name),
+ Blending = BlendingMode.Additive,
+ Alpha = 0.5f
+ }, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
index afbf00f320..0c1fd4c364 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Children = new Drawable[]
{
- new CircularContainer
+ new SkinnableDrawable("Play/osu/number-glow", name => new CircularContainer
{
Masking = true,
Origin = Anchor.Centre,
@@ -38,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Radius = 60,
Colour = Color4.White.Opacity(0.5f),
},
- Children = new[]
- {
- new Box()
- }
- },
+ Child = new Box()
+ }, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText
{
Text = @"1",
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
index 2347927f2e..12cc0dc5d9 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics.Containers;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@@ -15,24 +16,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
Size = new Vector2(128);
- Masking = true;
- CornerRadius = Size.X / 2;
-
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
- BorderThickness = 10;
- BorderColour = Color4.White;
-
- Children = new Drawable[]
+ InternalChild = new SkinnableDrawable("Play/osu/hitcircleoverlay", _ => new Container
{
- new Box
+ Masking = true,
+ CornerRadius = Size.X / 2,
+ BorderThickness = 10,
+ BorderColour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
{
- AlwaysPresent = true,
- Alpha = 0,
- RelativeSizeAxes = Axes.Both
+ new Box
+ {
+ AlwaysPresent = true,
+ Alpha = 0,
+ RelativeSizeAxes = Axes.Both
+ }
}
- };
+ });
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
index 2fda299389..1921c51889 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs
@@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
+using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using OpenTK.Graphics;
@@ -139,9 +140,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
- public void UpdateProgress(double progress, int span)
+ public void UpdateProgress(double completionProgress)
{
- Position = slider.Curve.PositionAt(progress);
+ Position = slider.CurvePositionAt(completionProgress);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
index 901d1c568d..c59c22c771 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures;
-using osu.Game.Configuration;
using OpenTK;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
@@ -30,10 +29,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
set { path.PathWidth = value; }
}
+ ///
+ /// Offset in absolute coordinates from the start of the curve.
+ ///
+ public Vector2 PathOffset { get; private set; }
+
+ public readonly List CurrentCurve = new List();
+
+ public readonly Bindable SnakingIn = new Bindable();
+ public readonly Bindable SnakingOut = new Bindable();
+
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
- private Color4 accentColour;
+ private Color4 accentColour = Color4.White;
///
/// Used to colour the path.
///
@@ -46,8 +55,26 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return;
accentColour = value;
- if (LoadState == LoadState.Ready)
- Schedule(reloadTexture);
+ if (LoadState >= LoadState.Ready)
+ reloadTexture();
+ }
+ }
+
+ private Color4 borderColour = Color4.White;
+ ///
+ /// Used to colour the path border.
+ ///
+ public new Color4 BorderColour
+ {
+ get { return borderColour; }
+ set
+ {
+ if (borderColour == value)
+ return;
+ borderColour = value;
+
+ if (LoadState >= LoadState.Ready)
+ reloadTexture();
}
}
@@ -55,6 +82,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
private int textureWidth => (int)PathWidth * 2;
+ private Vector2 topLeftOffset;
+
private readonly Slider slider;
public SliderBody(Slider s)
{
@@ -64,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
container = new BufferedContainer
{
+ RelativeSizeAxes = Axes.Both,
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
@@ -78,6 +108,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
container.Attach(RenderbufferInternalFormat.DepthComponent16);
}
+ public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos);
+
public void SetRange(double p0, double p1)
{
if (p0 > p1)
@@ -85,26 +117,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (updateSnaking(p0, p1))
{
- // Autosizing does not give us the desired behaviour here.
- // We want the container to have the same size as the slider,
- // and to be positioned such that the slider head is at (0,0).
- container.Size = path.Size;
- container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - CurrentCurve[0]);
+ // The path is generated such that its size encloses it. This change of size causes the path
+ // to move around while snaking, so we need to offset it to make sure it maintains the
+ // same position as when it is fully snaked.
+ var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
+ path.Position = topLeftOffset - newTopLeftOffset;
container.ForceRedraw();
}
}
- private Bindable snakingIn;
- private Bindable snakingOut;
-
[BackgroundDependencyLoader]
- private void load(OsuConfigManager config)
+ private void load()
{
- snakingIn = config.GetBindable(OsuSetting.SnakingInSliders);
- snakingOut = config.GetBindable(OsuSetting.SnakingOutSliders);
-
reloadTexture();
+ computeSize();
}
private void reloadTexture()
@@ -128,10 +155,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (progress <= border_portion)
{
- bytes[i * 4] = 255;
- bytes[i * 4 + 1] = 255;
- bytes[i * 4 + 2] = 255;
- bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * 255);
+ bytes[i * 4] = (byte)(BorderColour.R * 255);
+ bytes[i * 4 + 1] = (byte)(BorderColour.G * 255);
+ bytes[i * 4 + 2] = (byte)(BorderColour.B * 255);
+ bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255));
}
else
{
@@ -146,9 +173,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
texture.SetData(upload);
path.Texture = texture;
+
+ container.ForceRedraw();
+ }
+
+ private void computeSize()
+ {
+ // Generate the entire curve
+ slider.Curve.GetPathToProgress(CurrentCurve, 0, 1);
+ foreach (Vector2 p in CurrentCurve)
+ path.AddVertex(p);
+
+ Size = path.Size;
+
+ topLeftOffset = path.PositionInBoundingBox(Vector2.Zero);
+ PathOffset = path.PositionInBoundingBox(CurrentCurve[0]);
}
- public readonly List CurrentCurve = new List();
private bool updateSnaking(double p0, double p1)
{
if (SnakedStart == p0 && SnakedEnd == p1) return false;
@@ -160,26 +201,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
path.ClearVertices();
foreach (Vector2 p in CurrentCurve)
- path.AddVertex(p - CurrentCurve[0]);
+ path.AddVertex(p);
return true;
}
- public void UpdateProgress(double progress, int span)
+ public void UpdateProgress(double completionProgress)
{
+ var span = slider.SpanAt(completionProgress);
+ var spanProgress = slider.ProgressAt(completionProgress);
+
double start = 0;
- double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
+ double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
if (span >= slider.SpanCount() - 1)
{
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{
start = 0;
- end = snakingOut ? progress : 1;
+ end = SnakingOut ? spanProgress : 1;
}
else
{
- start = snakingOut ? progress : 0;
+ start = SnakingOut ? spanProgress : 0;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs
index 54f783b664..a0566eaf17 100644
--- a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs
+++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs
@@ -5,6 +5,10 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public interface ISliderProgress
{
- void UpdateProgress(double progress, int span);
+ ///
+ /// Updates the progress of this element along the slider.
+ ///
+ /// Amount of the slider completed.
+ void UpdateProgress(double completionProgress);
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 9b9d88f0f6..c00c30ced9 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -1,23 +1,39 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
-using OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects
{
- public abstract class OsuHitObject : HitObject, IHasCombo, IHasPosition
+ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
public const double OBJECT_RADIUS = 64;
+ public event Action PositionChanged;
+
public double TimePreempt = 600;
public double TimeFadein = 400;
- public Vector2 Position { get; set; }
+ private Vector2 position;
+
+ public Vector2 Position
+ {
+ get => position;
+ set
+ {
+ if (position == value)
+ return;
+ position = value;
+
+ PositionChanged?.Invoke(value);
+ }
+ }
+
public float X => Position.X;
public float Y => Position.Y;
@@ -35,10 +51,14 @@ namespace osu.Game.Rulesets.Osu.Objects
public float Scale { get; set; } = 1;
- public Color4 ComboColour { get; set; } = Color4.Gray;
public virtual bool NewCombo { get; set; }
+
public int IndexInCurrentCombo { get; set; }
+ public int ComboIndex { get; set; }
+
+ public bool LastInCombo { get; set; }
+
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -48,5 +68,7 @@ namespace osu.Game.Rulesets.Osu.Objects
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
+
+ public virtual void OffsetPosition(Vector2 offset) => Position += offset;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index 5dd3d7aa89..469c4ddcb4 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -3,7 +3,6 @@
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
-using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Objects;
using System.Linq;
@@ -23,8 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime;
- public Vector2 StackedPositionAt(double t) => this.PositionAt(t) + StackOffset;
- public override Vector2 EndPosition => this.PositionAt(1);
+ public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
+ public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
public SliderCurve Curve { get; } = new SliderCurve();
@@ -66,18 +65,6 @@ namespace osu.Game.Rulesets.Osu.Objects
///
public double SpanDuration => Duration / this.SpanCount();
- private int stackHeight;
-
- public override int StackHeight
- {
- get { return stackHeight; }
- set
- {
- stackHeight = value;
- Curve.Offset = StackOffset;
- }
- }
-
public double Velocity;
public double TickDistance;
@@ -108,22 +95,22 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createSliderEnds()
{
- HeadCircle = new HitCircle
+ HeadCircle = new SliderCircle(this)
{
StartTime = StartTime,
- Position = StackedPosition,
- IndexInCurrentCombo = IndexInCurrentCombo,
- ComboColour = ComboColour,
+ Position = Position,
Samples = Samples,
- SampleControlPoint = SampleControlPoint
+ SampleControlPoint = SampleControlPoint,
+ IndexInCurrentCombo = IndexInCurrentCombo,
+ ComboIndex = ComboIndex,
};
- TailCircle = new HitCircle
+ TailCircle = new SliderCircle(this)
{
StartTime = EndTime,
- Position = StackedEndPosition,
+ Position = EndPosition,
IndexInCurrentCombo = IndexInCurrentCombo,
- ComboColour = ComboColour
+ ComboIndex = ComboIndex,
};
AddNested(HeadCircle);
@@ -132,14 +119,16 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createTicks()
{
- if (TickDistance == 0) return;
-
var length = Curve.Distance;
- var tickDistance = Math.Min(TickDistance, length);
+ var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
+
+ if (tickDistance == 0) return;
var minDistanceFromEnd = Velocity * 0.01;
- for (var span = 0; span < this.SpanCount(); span++)
+ var spanCount = this.SpanCount();
+
+ for (var span = 0; span < spanCount; span++)
{
var spanStartTime = StartTime + span * SpanDuration;
var reversed = span % 2 == 1;
@@ -168,10 +157,9 @@ namespace osu.Game.Rulesets.Osu.Objects
SpanIndex = span,
SpanStartTime = spanStartTime,
StartTime = spanStartTime + timeProgress * SpanDuration,
- Position = Curve.PositionAt(distanceProgress),
+ Position = Position + Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
- ComboColour = ComboColour,
Samples = sampleList
});
}
@@ -187,10 +175,9 @@ namespace osu.Game.Rulesets.Osu.Objects
RepeatIndex = repeatIndex,
SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration,
- Position = Curve.PositionAt(repeat % 2),
+ Position = Position + Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
- ComboColour = ComboColour,
Samples = new List(RepeatSamples[repeatIndex])
});
}
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs
new file mode 100644
index 0000000000..1e83d02735
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Objects
+{
+ public class SliderCircle : HitCircle
+ {
+ private readonly Slider slider;
+
+ public SliderCircle(Slider slider)
+ {
+ this.slider = slider;
+ }
+
+ public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 2f238bb74b..b30e4cb932 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SpinsRequired = (int)(Duration / 1000 * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5));
// spinning doesn't match 1:1 with stable, so let's fudge them easier for the time being.
- SpinsRequired = (int)(SpinsRequired * 0.6);
+ SpinsRequired = (int)Math.Max(1, SpinsRequired * 0.6);
}
}
}
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index b38f95694f..d407835a96 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -19,6 +19,8 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu.Replays;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Osu
{
@@ -143,7 +145,9 @@ namespace osu.Game.Rulesets.Osu
public override SettingsSubsection CreateSettings() => new OsuSettings();
- public override int LegacyID => 0;
+ public override int? LegacyID => 0;
+
+ public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();
public OsuRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index a22ac6aed1..7aa4108428 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -6,7 +6,7 @@ using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using System;
-using System.Diagnostics;
+using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Replays;
@@ -64,9 +64,9 @@ namespace osu.Game.Rulesets.Osu.Replays
{
buttonIndex = 0;
- AddFrameToReplay(new ReplayFrame(-100000, 256, 500, ReplayButtonState.None));
- AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, 256, 500, ReplayButtonState.None));
- AddFrameToReplay(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, 256, 192, ReplayButtonState.None));
+ AddFrameToReplay(new OsuReplayFrame(-100000, new Vector2(256, 500)));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
+ AddFrameToReplay(new OsuReplayFrame(Beatmap.HitObjects[0].StartTime - 1500, new Vector2(256, 500)));
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
@@ -91,18 +91,18 @@ namespace osu.Game.Rulesets.Osu.Replays
// Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Miss) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
- if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
- if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
else if (h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh) > endTime + h.HitWindows.HalfWindowFor(HitResult.Meh) + 50)
{
- if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new ReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), prev.StackedEndPosition.X, prev.StackedEndPosition.Y, ReplayButtonState.None));
- if (!(h is Spinner)) AddFrameToReplay(new ReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), h.StackedPosition.X, h.StackedPosition.Y, ReplayButtonState.None));
+ if (!(prev is Spinner) && h.StartTime - endTime < 1000) AddFrameToReplay(new OsuReplayFrame(endTime + h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y)));
+ if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - h.HitWindows.HalfWindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y)));
}
}
@@ -118,9 +118,9 @@ namespace osu.Game.Rulesets.Osu.Replays
// TODO: Shouldn't the spinner always spin in the same direction?
if (h is Spinner)
{
- calcSpinnerStartPosAndDirection(Frames[Frames.Count - 1].Position, out startPosition, out spinnerDirection);
+ calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[Frames.Count - 1]).Position, out startPosition, out spinnerDirection);
- Vector2 spinCentreOffset = SPINNER_CENTRE - Frames[Frames.Count - 1].Position;
+ Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[Frames.Count - 1]).Position;
if (spinCentreOffset.Length > SPIN_RADIUS)
{
@@ -192,13 +192,13 @@ namespace osu.Game.Rulesets.Osu.Replays
private void moveToHitObject(OsuHitObject h, Vector2 targetPos, Easing easing)
{
- ReplayFrame lastFrame = Frames[Frames.Count - 1];
+ OsuReplayFrame lastFrame = (OsuReplayFrame)Frames[Frames.Count - 1];
// Wait until Auto could "see and react" to the next note.
double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime);
if (waitTime > lastFrame.Time)
{
- lastFrame = new ReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
+ lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions };
AddFrameToReplay(lastFrame);
}
@@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Replays
for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay)
{
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPos, lastFrame.Time, h.StartTime, easing);
- AddFrameToReplay(new ReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
+ AddFrameToReplay(new OsuReplayFrame((int)time, new Vector2(currentPosition.X, currentPosition.Y)) { Actions = lastFrame.Actions });
}
buttonIndex = 0;
@@ -231,14 +231,14 @@ namespace osu.Game.Rulesets.Osu.Replays
{
// Time to insert the first frame which clicks the object
// Here we mainly need to determine which button to use
- ReplayButtonState button = buttonIndex % 2 == 0 ? ReplayButtonState.Left1 : ReplayButtonState.Right1;
+ var action = buttonIndex % 2 == 0 ? OsuAction.LeftButton : OsuAction.RightButton;
- ReplayFrame startFrame = new ReplayFrame(h.StartTime, startPosition.X, startPosition.Y, button);
+ var startFrame = new OsuReplayFrame(h.StartTime, new Vector2(startPosition.X, startPosition.Y), action);
// TODO: Why do we delay 1 ms if the object is a spinner? There already is KEY_UP_DELAY from hEndTime.
double hEndTime = ((h as IHasEndTime)?.EndTime ?? h.StartTime) + KEY_UP_DELAY;
int endDelay = h is Spinner ? 1 : 0;
- ReplayFrame endFrame = new ReplayFrame(hEndTime + endDelay, h.StackedEndPosition.X, h.StackedEndPosition.Y, ReplayButtonState.None);
+ var endFrame = new OsuReplayFrame(hEndTime + endDelay, new Vector2(h.StackedEndPosition.X, h.StackedEndPosition.Y));
// Decrement because we want the previous frame, not the next one
int index = FindInsertionIndex(startFrame) - 1;
@@ -248,19 +248,18 @@ namespace osu.Game.Rulesets.Osu.Replays
// Do we have a previous frame? No need to check for < replay.Count since we decremented!
if (index >= 0)
{
- ReplayFrame previousFrame = Frames[index];
- var previousButton = previousFrame.ButtonState;
+ var previousFrame = (OsuReplayFrame)Frames[index];
+ var previousActions = previousFrame.Actions;
// If a button is already held, then we simply alternate
- if (previousButton != ReplayButtonState.None)
+ if (previousActions.Any())
{
- Debug.Assert(previousButton != (ReplayButtonState.Left1 | ReplayButtonState.Right1), "Previous button state was not Left1 nor Right1 despite only using those two states.");
-
// Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
- if (previousButton == button)
+ if (previousActions.Contains(action))
{
- button = (ReplayButtonState.Left1 | ReplayButtonState.Right1) & ~button;
- startFrame.ButtonState = button;
+ action = action == OsuAction.LeftButton ? OsuAction.RightButton : OsuAction.LeftButton;
+ startFrame.Actions.Clear();
+ startFrame.Actions.Add(action);
}
// We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
@@ -272,9 +271,14 @@ namespace osu.Game.Rulesets.Osu.Replays
// After alternating we need to keep holding the other button in the future rather than the previous one.
for (int j = index + 1; j < Frames.Count; ++j)
{
+ var frame = (OsuReplayFrame)Frames[j];
+
// Don't affect frames which stop pressing a button!
- if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
- Frames[j].ButtonState = button;
+ if (j < Frames.Count - 1 || frame.Actions.SequenceEqual(previousActions))
+ {
+ frame.Actions.Clear();
+ frame.Actions.Add(action);
+ }
}
}
}
@@ -298,16 +302,15 @@ namespace osu.Game.Rulesets.Osu.Replays
t = ApplyModsToTime(j - h.StartTime) * spinnerDirection;
Vector2 pos = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
- AddFrameToReplay(new ReplayFrame((int)j, pos.X, pos.Y, button));
+ AddFrameToReplay(new OsuReplayFrame((int)j, new Vector2(pos.X, pos.Y), action));
}
t = ApplyModsToTime(s.EndTime - h.StartTime) * spinnerDirection;
Vector2 endPosition = SPINNER_CENTRE + CirclePosition(t / 20 + angle, SPIN_RADIUS);
- AddFrameToReplay(new ReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
+ AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(endPosition.X, endPosition.Y), action));
- endFrame.MouseX = endPosition.X;
- endFrame.MouseY = endPosition.Y;
+ endFrame.Position = endPosition;
}
else if (h is Slider)
{
@@ -315,11 +318,11 @@ namespace osu.Game.Rulesets.Osu.Replays
for (double j = FrameDelay; j < s.Duration; j += FrameDelay)
{
- Vector2 pos = s.PositionAt(j / s.Duration);
- AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
+ Vector2 pos = s.StackedPositionAt(j / s.Duration);
+ AddFrameToReplay(new OsuReplayFrame(h.StartTime + j, new Vector2(pos.X, pos.Y), action));
}
- AddFrameToReplay(new ReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button));
+ AddFrameToReplay(new OsuReplayFrame(s.EndTime, new Vector2(s.StackedEndPosition.X, s.StackedEndPosition.Y), action));
}
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
new file mode 100644
index 0000000000..bcdfe07417
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs
@@ -0,0 +1,36 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Replays.Legacy;
+using osu.Game.Rulesets.Replays.Types;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Replays
+{
+ public class OsuReplayFrame : ReplayFrame, IConvertibleReplayFrame
+ {
+ public Vector2 Position;
+ public List Actions = new List();
+
+ public OsuReplayFrame()
+ {
+ }
+
+ public OsuReplayFrame(double time, Vector2 position, params OsuAction[] actions)
+ : base(time)
+ {
+ Position = position;
+ Actions.AddRange(actions);
+ }
+
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ {
+ Position = legacyFrame.Position;
+ if (legacyFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
+ if (legacyFrame.MouseRight) Actions.Add(OsuAction.RightButton);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
index 63c9111190..0a61b0f199 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs
@@ -2,32 +2,42 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Input;
+using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Replays
{
- public class OsuReplayInputHandler : FramedReplayInputHandler
+ public class OsuReplayInputHandler : FramedReplayInputHandler
{
public OsuReplayInputHandler(Replay replay)
: base(replay)
{
}
+ protected override bool IsImportant(OsuReplayFrame frame) => frame.Actions.Any();
+
+ protected Vector2? Position
+ {
+ get
+ {
+ if (!HasFrames)
+ return null;
+
+ return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
+ }
+ }
+
public override List GetPendingStates()
{
- List actions = new List();
-
- if (CurrentFrame?.MouseLeft ?? false) actions.Add(OsuAction.LeftButton);
- if (CurrentFrame?.MouseRight ?? false) actions.Add(OsuAction.RightButton);
-
return new List
{
new ReplayState
{
Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)),
- PressedActions = actions
+ PressedActions = CurrentFrame.Actions
}
};
}
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..b82fddbe79
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,124 @@
+{
+ "Mappings": [{
+ "StartTime": 500,
+ "Objects": [{
+ "StartTime": 500,
+ "EndTime": 2500,
+ "StartX": 96,
+ "StartY": 192,
+ "EndX": 96,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 3000,
+ "Objects": [{
+ "StartTime": 3000,
+ "EndTime": 4000,
+ "StartX": 256,
+ "StartY": 192,
+ "EndX": 256,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 4500,
+ "Objects": [{
+ "StartTime": 4500,
+ "EndTime": 5500,
+ "StartX": 256,
+ "StartY": 192,
+ "EndX": 256,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 6000,
+ "Objects": [{
+ "StartTime": 6000,
+ "EndTime": 6500,
+ "StartX": 256,
+ "StartY": 192,
+ "EndX": 256,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 7000,
+ "Objects": [{
+ "StartTime": 7000,
+ "EndTime": 8000,
+ "StartX": 256,
+ "StartY": 128,
+ "EndX": 256,
+ "EndY": 128
+ }]
+ },
+ {
+ "StartTime": 8500,
+ "Objects": [{
+ "StartTime": 8500,
+ "EndTime": 10999,
+ "StartX": 32,
+ "StartY": 192,
+ "EndX": 508.166229,
+ "EndY": 153.299271
+ }]
+ },
+ {
+ "StartTime": 11500,
+ "Objects": [{
+ "StartTime": 11500,
+ "EndTime": 12000,
+ "StartX": 256,
+ "StartY": 192,
+ "EndX": 256,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 12500,
+ "Objects": [{
+ "StartTime": 12500,
+ "EndTime": 16500,
+ "StartX": 512,
+ "StartY": 320,
+ "EndX": 291.1977,
+ "EndY": 40.799427
+ }]
+ },
+ {
+ "StartTime": 17000,
+ "Objects": [{
+ "StartTime": 17000,
+ "EndTime": 18000,
+ "StartX": 256,
+ "StartY": 256,
+ "EndX": 256,
+ "EndY": 256
+ }]
+ },
+ {
+ "StartTime": 18500,
+ "Objects": [{
+ "StartTime": 18500,
+ "EndTime": 19450,
+ "StartX": 256,
+ "StartY": 192,
+ "EndX": 256,
+ "EndY": 192
+ }]
+ },
+ {
+ "StartTime": 19875,
+ "Objects": [{
+ "StartTime": 19875,
+ "EndTime": 23874,
+ "StartX": 216,
+ "StartY": 231,
+ "EndX": 408.720825,
+ "EndY": 339.810455
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
new file mode 100644
index 0000000000..7fe038658c
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve-expected-conversion.json
@@ -0,0 +1,13 @@
+{
+ "Mappings": [{
+ "StartTime": 118858,
+ "Objects": [{
+ "StartTime": 118858,
+ "EndTime": 119088,
+ "StartX": 219,
+ "StartY": 215,
+ "EndX": 239.6507,
+ "EndY": 29.1437378
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu
new file mode 100644
index 0000000000..8c3edc9571
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/colinear-perfect-curve.osu
@@ -0,0 +1,15 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4.2
+OverallDifficulty:9
+ApproachRate:9.8
+SliderMultiplier:1.87
+SliderTickRate:1
+
+[TimingPoints]
+49051,230.769230769231,4,2,1,15,1,0
+
+[HitObjects]
+219,215,118858,2,0,P|224:170|244:-10,1,187,8|2,0:0|0:0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
index 67b96f1fd9..d41331e3bd 100644
--- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
+++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
@@ -68,6 +68,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss);
}
+ private const double harshness = 0.01;
+
protected override void OnNewJudgement(Judgement judgement)
{
base.OnNewJudgement(judgement);
@@ -83,15 +85,15 @@ namespace osu.Game.Rulesets.Osu.Scoring
switch (judgement.Result)
{
case HitResult.Great:
- Health.Value += (10.2 - hpDrainRate) * 0.02;
+ Health.Value += (10.2 - hpDrainRate) * harshness;
break;
case HitResult.Good:
- Health.Value += (8 - hpDrainRate) * 0.02;
+ Health.Value += (8 - hpDrainRate) * harshness;
break;
case HitResult.Meh:
- Health.Value += (4 - hpDrainRate) * 0.02;
+ Health.Value += (4 - hpDrainRate) * harshness;
break;
/*case HitResult.SliderTick:
@@ -99,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
break;*/
case HitResult.Miss:
- Health.Value -= hpDrainRate * 0.04;
+ Health.Value -= hpDrainRate * (harshness * 2);
break;
}
}
diff --git a/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs
new file mode 100644
index 0000000000..59c59dc0e3
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Tests/OsuBeatmapConversionTest.cs
@@ -0,0 +1,70 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Tests.Beatmaps;
+using OpenTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class OsuBeatmapConversionTest : BeatmapConversionTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
+
+ [TestCase("basic")]
+ [TestCase("colinear-perfect-curve")]
+ public new void Test(string name)
+ {
+ base.Test(name);
+ }
+
+ protected override IEnumerable CreateConvertValue(HitObject hitObject)
+ {
+ var startPosition = (hitObject as IHasPosition)?.Position ?? new Vector2(256, 192);
+ var endPosition = (hitObject as Slider)?.EndPosition ?? startPosition;
+
+ yield return new ConvertValue
+ {
+ StartTime = hitObject.StartTime,
+ EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+ StartX = startPosition.X,
+ StartY = startPosition.Y,
+ EndX = endPosition.X,
+ EndY = endPosition.Y
+ };
+ }
+
+ protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new OsuBeatmapConverter();
+ }
+
+ public struct ConvertValue : IEquatable
+ {
+ ///
+ /// A sane value to account for osu!stable using ints everwhere.
+ ///
+ private const double conversion_lenience = 2;
+
+ public double StartTime;
+ public double EndTime;
+ public float StartX;
+ public float StartY;
+ public float EndX;
+ public float EndY;
+
+ public bool Equals(ConvertValue other)
+ => Precision.AlmostEquals(StartTime, other.StartTime)
+ && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+ && Precision.AlmostEquals(StartX, other.StartX)
+ && Precision.AlmostEquals(StartY, other.StartY, conversion_lenience)
+ && Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
+ && Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs
new file mode 100644
index 0000000000..a11f32935e
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseEditor.cs
@@ -0,0 +1,17 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestCaseEditor : EditorTestCase
+ {
+ public TestCaseEditor()
+ : base(new OsuRuleset())
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs
new file mode 100644
index 0000000000..273422f2e9
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs
@@ -0,0 +1,33 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Cursor;
+using osu.Game.Graphics.Cursor;
+using osu.Game.Rulesets.Osu.UI.Cursor;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ [TestFixture]
+ public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
+ {
+ private GameplayCursor cursor;
+
+ public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) };
+
+ public CursorContainer Cursor => cursor;
+
+ public bool ProvidingUserCursor => true;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both });
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs
index 77c70c68cd..b0cfa43f15 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircle.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@@ -11,17 +10,17 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
-using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
using osu.Game.Rulesets.Mods;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseHitCircle : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
@@ -61,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value,
- ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs
index 1f64de496d..f030c6db60 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseHitCircleHidden.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseHitCircleHidden : TestCaseHitCircle
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
index 500347c874..b6dca3f1cb 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCasePerformancePoints.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs
index 93085df975..e819d8fff5 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
@@ -16,6 +15,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@@ -24,7 +24,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseSlider : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
@@ -90,10 +90,15 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Catmull Slider", () => testCatmull());
AddStep("Catmull Slider 1 Repeat", () => testCatmull(1));
AddStep("Catmull Slider 2 Repeats", () => testCatmull(2));
+
+ AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
+ AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
}
private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
+ private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
+
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
@@ -106,21 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests
private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15);
- private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2)
+ private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0)
{
var slider = new Slider
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
- ComboColour = Color4.LightSeaGreen,
ControlPoints = new List
{
- new Vector2(-(distance / 2), 0),
- new Vector2(distance / 2, 0),
+ Vector2.Zero,
+ new Vector2(distance, 0),
},
Distance = distance,
RepeatCount = repeats,
- RepeatSamples = createEmptySamples(repeats)
+ RepeatSamples = createEmptySamples(repeats),
+ StackHeight = stackHeight
};
addSlider(slider, circleSize, speedMultiplier);
@@ -132,12 +137,11 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ComboColour = Color4.LightSeaGreen,
ControlPoints = new List
{
- new Vector2(-200, 0),
- new Vector2(0, 200),
- new Vector2(200, 0)
+ Vector2.Zero,
+ new Vector2(200, 200),
+ new Vector2(400, 0)
},
Distance = 600,
RepeatCount = repeats,
@@ -156,15 +160,14 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ComboColour = Color4.LightSeaGreen,
ControlPoints = new List
{
- new Vector2(-200, 0),
- new Vector2(-50, 75),
- new Vector2(0, 100),
- new Vector2(100, -200),
+ Vector2.Zero,
+ new Vector2(150, 75),
new Vector2(200, 0),
- new Vector2(230, 0)
+ new Vector2(300, -200),
+ new Vector2(400, 0),
+ new Vector2(430, 0)
},
Distance = 793.4417,
RepeatCount = repeats,
@@ -183,14 +186,13 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
- ComboColour = Color4.LightSeaGreen,
ControlPoints = new List
{
- new Vector2(-200, 0),
- new Vector2(-50, 75),
- new Vector2(0, 100),
- new Vector2(100, -200),
- new Vector2(230, 0)
+ Vector2.Zero,
+ new Vector2(150, 75),
+ new Vector2(200, 100),
+ new Vector2(300, -200),
+ new Vector2(430, 0)
},
Distance = 480,
RepeatCount = repeats,
@@ -209,10 +211,9 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
- ComboColour = Color4.LightSeaGreen,
ControlPoints = new List
{
- new Vector2(0, 0),
+ Vector2.Zero,
new Vector2(-200, 0),
new Vector2(0, 0),
new Vector2(0, -200),
@@ -239,14 +240,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
- ComboColour = Color4.LightSeaGreen,
CurveType = CurveType.Catmull,
ControlPoints = new List
{
- new Vector2(-100, 0),
- new Vector2(-50, -50),
- new Vector2(50, 50),
- new Vector2(100, 0)
+ Vector2.Zero,
+ new Vector2(50, -50),
+ new Vector2(150, 50),
+ new Vector2(200, 0)
},
Distance = 300,
RepeatCount = repeats,
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs
index bd4be1675b..57b719464f 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSliderHidden.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseSliderHidden : TestCaseSlider
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs
index f2d031633b..d3620bcbda 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinner.cs
@@ -17,7 +17,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseSpinner : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs
index 7764f3d4af..75b3b4c763 100644
--- a/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs
+++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSpinnerHidden.cs
@@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseSpinnerHidden : TestCaseSpinner
{
public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList();
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 8be55f7b4b..3203df16e2 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -3,9 +3,9 @@
using System;
using System.Diagnostics;
+using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
@@ -14,11 +14,12 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Timing;
using OpenTK;
+using OpenTK.Graphics;
using OpenTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
- internal class CursorTrail : Drawable
+ internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
{
private int currentIndex;
@@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private float time;
+ public override bool IsPresent => true;
+
private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
private const int max_sprites = 2048;
@@ -96,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
const int fade_clock_reset_threshold = 1000000;
- time = (float)(Time.Current - timeOffset) / 500f;
+ time = (float)(Time.Current - timeOffset) / 300f;
if (time > fade_clock_reset_threshold)
resetTime();
}
@@ -115,14 +118,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override bool OnMouseMove(InputState state)
{
+ Vector2 pos = state.Mouse.NativeState.Position;
+
if (lastPosition == null)
{
- lastPosition = state.Mouse.NativeState.Position;
+ lastPosition = pos;
resampler.AddPosition(lastPosition.Value);
return base.OnMouseMove(state);
}
- foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position))
+ foreach (Vector2 pos2 in resampler.AddPosition(pos))
{
Trace.Assert(lastPosition.HasValue);
@@ -163,7 +168,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private class TrailDrawNodeSharedData
{
- public VertexBuffer VertexBuffer;
+ public VertexBuffer VertexBuffer;
}
private class TrailDrawNode : DrawNode
@@ -189,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override void Draw(Action vertexAction)
{
if (Shared.VertexBuffer == null)
- Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw);
+ Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw);
Shader.GetUniform("g_FadeClock").Value = Time;
@@ -206,17 +211,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
int end = start;
Vector2 pos = Parts[i].Position;
- ColourInfo colour = DrawInfo.Colour;
- colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A;
- colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A;
- colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A;
- colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A;
+ float time = Parts[i].Time;
Texture.DrawQuad(
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
- colour,
+ DrawInfo.Colour,
null,
- v => Shared.VertexBuffer.Vertices[end++] = v);
+ v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
+ {
+ Position = v.Position,
+ TexturePosition = v.TexturePosition,
+ Time = time + 1,
+ Colour = v.Colour,
+ });
Parts[i].WasUpdated = false;
}
@@ -241,5 +248,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Shader.Unbind();
}
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TexturedTrailVertex : IEquatable, IVertex
+ {
+ [VertexMember(2, VertexAttribPointerType.Float)]
+ public Vector2 Position;
+ [VertexMember(4, VertexAttribPointerType.Float)]
+ public Color4 Colour;
+ [VertexMember(2, VertexAttribPointerType.Float)]
+ public Vector2 TexturePosition;
+ [VertexMember(1, VertexAttribPointerType.Float)]
+ public float Time;
+
+ public bool Equals(TexturedTrailVertex other)
+ {
+ return Position.Equals(other.Position)
+ && TexturePosition.Equals(other.TexturePosition)
+ && Colour.Equals(other.Colour)
+ && Time.Equals(other.Time);
+ }
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
index 0aeb14514d..ac81d93309 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Skinning;
using OpenTK;
using OpenTK.Graphics;
@@ -20,118 +21,24 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
protected override Drawable CreateCursor() => new OsuCursor();
+ protected override Container Content => fadeContainer;
+
+ private readonly Container fadeContainer;
+
public GameplayCursor()
{
- Add(new CursorTrail { Depth = 1 });
+ InternalChild = fadeContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new CursorTrail { Depth = 1 }
+ }
+ };
}
private int downCount;
- public class OsuCursor : Container
- {
- private Container cursorContainer;
-
- private Bindable cursorScale;
- private Bindable autoCursorScale;
- private Bindable beatmap;
-
- public OsuCursor()
- {
- Origin = Anchor.Centre;
- Size = new Vector2(42);
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuConfigManager config, OsuGameBase game)
- {
- Children = new Drawable[]
- {
- cursorContainer = new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 6,
- BorderColour = Color4.White,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Pink.Opacity(0.5f),
- Radius = 5,
- },
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- BorderThickness = Size.X / 3,
- BorderColour = Color4.White.Opacity(0.5f),
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0,
- AlwaysPresent = true,
- },
- },
- },
- new CircularContainer
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Scale = new Vector2(0.1f),
- Masking = true,
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.White,
- },
- },
- },
- }
- },
- };
-
- beatmap = game.Beatmap.GetBoundCopy();
- beatmap.ValueChanged += v => calculateScale();
-
- cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
- cursorScale.ValueChanged += v => calculateScale();
-
- autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
- autoCursorScale.ValueChanged += v => calculateScale();
-
- calculateScale();
- }
-
- private void calculateScale()
- {
- float scale = (float)cursorScale.Value;
-
- if (autoCursorScale && beatmap.Value != null)
- {
- // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
- }
-
- cursorContainer.Scale = new Vector2(scale);
- }
- }
-
public bool OnPressed(OsuAction action)
{
switch (action)
@@ -160,16 +67,123 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
return false;
}
+ public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input.
+
protected override void PopIn()
{
- ActiveCursor.FadeTo(1, 250, Easing.OutQuint);
+ fadeContainer.FadeTo(1, 300, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint);
}
protected override void PopOut()
{
- ActiveCursor.FadeTo(0, 250, Easing.OutQuint);
- ActiveCursor.ScaleTo(0.6f, 250, Easing.In);
+ fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
+ ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint);
+ }
+
+ public class OsuCursor : Container
+ {
+ private Drawable cursorContainer;
+
+ private Bindable cursorScale;
+ private Bindable autoCursorScale;
+ private Bindable beatmap;
+
+ public OsuCursor()
+ {
+ Origin = Anchor.Centre;
+ Size = new Vector2(42);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, OsuGameBase game)
+ {
+ Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = Size.X / 6,
+ BorderColour = Color4.White,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Pink.Opacity(0.5f),
+ Radius = 5,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ BorderThickness = Size.X / 3,
+ BorderColour = Color4.White.Opacity(0.5f),
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true,
+ },
+ },
+ },
+ new CircularContainer
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Scale = new Vector2(0.1f),
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.White,
+ },
+ },
+ },
+ }
+ }, restrictSize: false)
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ beatmap = game.Beatmap.GetBoundCopy();
+ beatmap.ValueChanged += v => calculateScale();
+
+ cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize);
+ cursorScale.ValueChanged += v => calculateScale();
+
+ autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize);
+ autoCursorScale.ValueChanged += v => calculateScale();
+
+ calculateScale();
+ }
+
+ private void calculateScale()
+ {
+ float scale = (float)cursorScale.Value;
+
+ if (autoCursorScale && beatmap.Value != null)
+ {
+ // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
+ scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY);
+ }
+
+ cursorContainer.Scale = new Vector2(scale);
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 17521f8992..98a8096678 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.UI
public class OsuPlayfield : Playfield
{
private readonly Container approachCircles;
- private readonly Container judgementLayer;
+ private readonly JudgementContainer judgementLayer;
private readonly ConnectionRenderer connectionLayer;
// Todo: This should not be a thing, but is currently required for the editor
@@ -27,21 +27,8 @@ namespace osu.Game.Rulesets.Osu.UI
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
- public override Vector2 Size
- {
- get
- {
- if (Parent == null)
- return Vector2.Zero;
-
- var parentSize = Parent.DrawSize;
- var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y);
-
- return new Vector2(aspectSize.X / parentSize.X, aspectSize.Y / parentSize.Y) * base.Size;
- }
- }
-
- public OsuPlayfield() : base(BASE_SIZE.X)
+ public OsuPlayfield()
+ : base(BASE_SIZE.X)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
@@ -53,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.UI
RelativeSizeAxes = Axes.Both,
Depth = 2,
},
- judgementLayer = new Container
+ judgementLayer = new JudgementContainer
{
RelativeSizeAxes = Axes.Both,
Depth = 1,
@@ -88,16 +75,13 @@ namespace osu.Game.Rulesets.Osu.UI
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
- var osuJudgement = (OsuJudgement)judgement;
- var osuObject = (OsuHitObject)judgedObject.HitObject;
-
if (!judgedObject.DisplayJudgement)
return;
- DrawableOsuJudgement explosion = new DrawableOsuJudgement(osuJudgement)
+ DrawableOsuJudgement explosion = new DrawableOsuJudgement(judgement, judgedObject)
{
Origin = Anchor.Centre,
- Position = osuObject.StackedEndPosition + osuJudgement.PositionOffset
+ Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition + ((OsuJudgement)judgement).PositionOffset
};
judgementLayer.Add(explosion);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
index 526348062f..b825ba73b7 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using OpenTK;
using osu.Game.Beatmaps;
+using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
@@ -48,9 +49,13 @@ namespace osu.Game.Rulesets.Osu.UI
return null;
}
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
- protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f);
+ protected override Vector2 GetAspectAdjustedSize()
+ {
+ var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y);
+ return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y);
+ }
protected override CursorContainer CreateCursor() => new GameplayCursor();
}
diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
index 5493a5029b..afa3d162f4 100644
--- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
+++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs
@@ -2,10 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Audio
{
@@ -14,7 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Audio
private readonly ControlPointInfo controlPoints;
private readonly Dictionary mappings = new Dictionary();
- public DrumSampleMapping(ControlPointInfo controlPoints, AudioManager audio)
+ public readonly List Sounds = new List();
+
+ public DrumSampleMapping(ControlPointInfo controlPoints)
{
this.controlPoints = controlPoints;
@@ -27,20 +28,34 @@ namespace osu.Game.Rulesets.Taiko.Audio
foreach (var s in samplePoints)
{
+ var centre = s.GetSampleInfo();
+ var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP);
+
+ // todo: this is ugly
+ centre.Namespace = "taiko";
+ rim.Namespace = "taiko";
+
mappings[s.Time] = new DrumSample
{
- Centre = s.GetSampleInfo().GetChannel(audio.Sample, "Taiko"),
- Rim = s.GetSampleInfo(SampleInfo.HIT_CLAP).GetChannel(audio.Sample, "Taiko")
+ Centre = addSound(centre),
+ Rim = addSound(rim)
};
}
}
+ private SkinnableSound addSound(SampleInfo sampleInfo)
+ {
+ var drawable = new SkinnableSound(sampleInfo);
+ Sounds.Add(drawable);
+ return drawable;
+ }
+
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
public class DrumSample
{
- public SampleChannel Centre;
- public SampleChannel Rim;
+ public SkinnableSound Centre;
+ public SkinnableSound Rim;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index e5fe288f20..9d6b5b5ce4 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -101,16 +101,16 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
- // For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but
- // only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here.
- if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
- speedAdjustedBeatLength *= speedAdjustment;
-
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
+ // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
+ // only uses it for tick rate if beatmap version < 8
+ if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
+ speedAdjustedBeatLength *= speedAdjustment;
+
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
index c50878c6a3..703e6b4f1c 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModDaycore : ModDaycore
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
index 1c5e43f411..be6510459e 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs
@@ -7,5 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModEasy : ModEasy
{
+ public override string Description => @"Beats move slower, less accuracy required, and three lives!";
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
index 9813f8b78e..6542b5a844 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModHalfTime : ModHalfTime
{
- public override double ScoreMultiplier => 0.5;
+ public override double ScoreMultiplier => 0.3;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
index ba304c41d8..435a0c1613 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
public class TaikoModHardRock : ModHardRock
{
public override double ScoreMultiplier => 1.06;
- public override bool Ranked => true;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
index b0ad43b851..be987a1773 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs
@@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModHidden : ModHidden
{
- public override string Description => @"The notes fade out before you hit them!";
+ public override string Description => @"Beats fade out before you hit them!";
public override double ScoreMultiplier => 1.06;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs
index ec2385bfba..d5ad04f595 100644
--- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs
+++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs
@@ -7,6 +7,6 @@ namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModRelax : ModRelax
{
- public override string Description => @"Relax! You will no longer get dizzyfied by ninja-like spinners, demanding drumrolls or unexpected katu's.";
+ public override string Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's.";
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
index cf6aa7d895..d3a38289a8 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeSizeAxes = Axes.Y;
Width = tracker_width;
- Children = new[]
+ InternalChildren = new[]
{
Tracker = new Box
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
index 23c34e9863..19a6e4eac2 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableBarLineMajor(BarLine barLine)
: base(barLine)
{
- Add(triangleContainer = new Container
+ AddInternal(triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
index 29d464f614..2bb2d478c3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs
@@ -20,11 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public class DrawableDrumRoll : DrawableTaikoHitObject
{
///
- /// Number of rolling hits required to reach the dark/final accent colour.
+ /// Number of rolling hits required to reach the dark/final colour.
///
- private const int rolling_hits_for_dark_accent = 5;
-
- private Color4 accentDarkColour;
+ private const int rolling_hits_for_engaged_colour = 5;
///
/// Rolling number of tick hits. This increases for hits and decreases for misses.
@@ -53,11 +51,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(TaikoAction action) => false;
+ private Color4 colourIdle;
+ private Color4 colourEngaged;
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- MainPiece.AccentColour = AccentColour = colours.YellowDark;
- accentDarkColour = colours.YellowDarker;
+ MainPiece.AccentColour = colourIdle = colours.YellowDark;
+ colourEngaged = colours.YellowDarker;
}
private void onTickJudgement(DrawableHitObject obj, Judgement judgement)
@@ -67,10 +68,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
else
rollingHits--;
- rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent);
+ rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
- Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1);
- MainPiece.FadeAccent(newAccent, 100);
+ Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
+ MainPiece.FadeAccent(newColour, 100);
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
@@ -82,8 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return;
int countHit = NestedHitObjects.Count(o => o.IsHit);
-
- if (countHit > HitObject.RequiredGoodHits)
+ if (countHit >= HitObject.RequiredGoodHits)
{
AddJudgement(new TaikoJudgement { Result = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good });
if (HitObject.IsStrong)
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
index bc5abce245..65a4e7bd95 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
switch (state)
{
case ArmedState.Hit:
- Content.ScaleTo(0, 100, Easing.OutQuint).Expire();
+ this.ScaleTo(0, 100, Easing.OutQuint).Expire();
break;
}
}
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index 63e6cfb297..75e1e2a247 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
const float gravity_time = 300;
const float gravity_travel_height = 200;
- Content.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
+ this.ScaleTo(0.8f, gravity_time * 2, Easing.OutQuad);
this.MoveToY(-gravity_travel_height, gravity_time, Easing.Out)
.Then()
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
index c9e488764c..37f1300d47 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
FillMode = FillMode.Fit;
- Add(bodyContainer = new Container
+ AddInternal(bodyContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = 1,
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index e57c2f9944..f20ad5b4aa 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeSizeAxes = Axes.Both;
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
- Add(MainPiece = CreateMainPiece());
+ InternalChild = MainPiece = CreateMainPiece();
MainPiece.KiaiMode = HitObject.Kiai;
}
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
index 002159439d..1a556fe01d 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs
@@ -35,15 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
bool hitButton = true;
- Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None));
- Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None));
+ Frames.Add(new TaikoReplayFrame(-100000));
+ Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000));
for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{
TaikoHitObject h = Beatmap.HitObjects[i];
- ReplayButtonState button;
-
IHasEndTime endTimeData = h as IHasEndTime;
double endTime = endTimeData?.EndTime ?? h.StartTime;
@@ -59,24 +57,26 @@ namespace osu.Game.Rulesets.Taiko.Replays
double hitRate = Math.Min(swell_hit_speed, swell.Duration / req);
for (double j = h.StartTime; j < endTime; j += hitRate)
{
+ TaikoAction action;
+
switch (d)
{
default:
case 0:
- button = ReplayButtonState.Left1;
+ action = TaikoAction.LeftCentre;
break;
case 1:
- button = ReplayButtonState.Right1;
+ action = TaikoAction.LeftRim;
break;
case 2:
- button = ReplayButtonState.Left2;
+ action = TaikoAction.RightCentre;
break;
case 3:
- button = ReplayButtonState.Right2;
+ action = TaikoAction.RightRim;
break;
}
- Frames.Add(new TaikoReplayFrame(j, button));
+ Frames.Add(new TaikoReplayFrame(j, action));
d = (d + 1) % 4;
if (++count == req)
break;
@@ -86,39 +86,39 @@ namespace osu.Game.Rulesets.Taiko.Replays
{
foreach (var tick in drumRoll.NestedHitObjects.OfType())
{
- Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2));
+ Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre));
hitButton = !hitButton;
}
}
else if (hit != null)
{
+ TaikoAction[] actions;
+
if (hit is CentreHit)
{
- if (h.IsStrong)
- button = ReplayButtonState.Right1 | ReplayButtonState.Right2;
- else
- button = hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2;
+ actions = h.IsStrong
+ ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
+ : new[] { hitButton ? TaikoAction.LeftCentre : TaikoAction.RightCentre };
}
else
{
- if (h.IsStrong)
- button = ReplayButtonState.Left1 | ReplayButtonState.Left2;
- else
- button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
+ actions = h.IsStrong
+ ? new[] { TaikoAction.LeftRim, TaikoAction.RightRim }
+ : new[] { hitButton ? TaikoAction.LeftRim : TaikoAction.RightRim };
}
- Frames.Add(new TaikoReplayFrame(h.StartTime, button));
+ Frames.Add(new TaikoReplayFrame(h.StartTime, actions));
}
else
throw new InvalidOperationException("Unknown hit object type.");
- Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY, ReplayButtonState.None));
+ Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY));
if (i < Beatmap.HitObjects.Count - 1)
{
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime)
- Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None));
+ Frames.Add(new TaikoReplayFrame(waitTime));
}
hitButton = !hitButton;
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
index 05e10b6fce..c80bddc304 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs
@@ -3,31 +3,20 @@
using osu.Game.Rulesets.Replays;
using System.Collections.Generic;
+using System.Linq;
using osu.Framework.Input;
namespace osu.Game.Rulesets.Taiko.Replays
{
- internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler
+ internal class TaikoFramedReplayInputHandler : FramedReplayInputHandler
{
public TaikoFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
- public override List GetPendingStates()
- {
- var actions = new List();
+ protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any();
- if (CurrentFrame?.MouseRight1 == true)
- actions.Add(TaikoAction.LeftCentre);
- if (CurrentFrame?.MouseRight2 == true)
- actions.Add(TaikoAction.RightCentre);
- if (CurrentFrame?.MouseLeft1 == true)
- actions.Add(TaikoAction.LeftRim);
- if (CurrentFrame?.MouseLeft2 == true)
- actions.Add(TaikoAction.RightRim);
-
- return new List { new ReplayState { PressedActions = actions } };
- }
+ public override List GetPendingStates() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } };
}
}
diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
index 0c60cdc109..6cd63f6c70 100644
--- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
+++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs
@@ -1,17 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Replays.Legacy;
+using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Taiko.Replays
{
- public class TaikoReplayFrame : ReplayFrame
+ public class TaikoReplayFrame : ReplayFrame, IConvertibleReplayFrame
{
- public override bool IsImportant => MouseLeft || MouseRight;
+ public List Actions = new List();
- public TaikoReplayFrame(double time, ReplayButtonState buttons)
- : base(time, null, null, buttons)
+ public TaikoReplayFrame()
{
}
+
+ public TaikoReplayFrame(double time, params TaikoAction[] actions)
+ : base(time)
+ {
+ Actions.AddRange(actions);
+ }
+
+ public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
+ {
+ if (legacyFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
+ if (legacyFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
+ if (legacyFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
+ if (legacyFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
+ }
}
}
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json
new file mode 100644
index 0000000000..5c9310fec7
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic-expected-conversion.json
@@ -0,0 +1,209 @@
+{
+ "Mappings": [{
+ "StartTime": 500,
+ "Objects": [{
+ "StartTime": 500,
+ "EndTime": 2499,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 3000,
+ "Objects": [{
+ "StartTime": 3000,
+ "EndTime": 4000,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": true,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 4500,
+ "Objects": [{
+ "StartTime": 4500,
+ "EndTime": 5500,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": true,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 6000,
+ "Objects": [{
+ "StartTime": 6000,
+ "EndTime": 6500,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": true,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 7000,
+ "Objects": [{
+ "StartTime": 7000,
+ "EndTime": 7000,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 7249,
+ "EndTime": 7249,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 7499,
+ "EndTime": 7499,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 7749,
+ "EndTime": 7749,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 7999,
+ "EndTime": 7999,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 8500,
+ "Objects": [{
+ "StartTime": 8500,
+ "EndTime": 10999,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 11500,
+ "Objects": [{
+ "StartTime": 11500,
+ "EndTime": 12000,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": true,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 12500,
+ "Objects": [{
+ "StartTime": 12500,
+ "EndTime": 16499,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 17000,
+ "Objects": [{
+ "StartTime": 17000,
+ "EndTime": 17000,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 17249,
+ "EndTime": 17249,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 17499,
+ "EndTime": 17499,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 17749,
+ "EndTime": 17749,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ },
+ {
+ "StartTime": 17999,
+ "EndTime": 17999,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 18500,
+ "Objects": [{
+ "StartTime": 18500,
+ "EndTime": 19450,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": true,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 19875,
+ "Objects": [{
+ "StartTime": 19875,
+ "EndTime": 23874,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu
new file mode 100644
index 0000000000..40b4409760
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/basic.osu
@@ -0,0 +1,27 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4
+OverallDifficulty:7
+ApproachRate:8.3
+SliderMultiplier:1.6
+SliderTickRate:1
+
+[TimingPoints]
+500,500,4,2,1,50,1,0
+13426,-100,4,3,1,45,0,0
+14884,-100,4,2,1,50,0,0
+
+[HitObjects]
+96,192,500,6,0,L|416:192,2,320
+256,192,3000,12,0,4000,0:0:0:0:
+256,192,4500,12,0,5500,0:0:0:0:
+256,192,6000,12,0,6500,0:0:0:0:
+256,128,7000,6,0,L|352:128,4,80
+32,192,8500,6,0,B|32:384|256:384|256:192|256:192|256:0|512:0|512:192,1,800
+256,192,11500,12,0,12000,0:0:0:0:
+512,320,12500,6,0,B|0:256|0:256|512:96|512:96|256:32,1,1280
+256,256,17000,6,0,L|160:256,4,80
+256,192,18500,12,0,19450,0:0:0:0:
+216,231,19875,6,0,B|216:135|280:135|344:135|344:199|344:263|248:327|248:327|120:327|120:327|56:39|408:39|408:39|472:150|408:342,1,1280
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll-expected-conversion.json
new file mode 100644
index 0000000000..fc7d466c1b
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll-expected-conversion.json
@@ -0,0 +1,87 @@
+{
+ "Mappings": [{
+ "StartTime": 6590,
+ "Objects": [{
+ "StartTime": 6590,
+ "EndTime": 8320,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 8436,
+ "Objects": [{
+ "StartTime": 8436,
+ "EndTime": 10166,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 10282,
+ "Objects": [{
+ "StartTime": 10282,
+ "EndTime": 12012,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 12128,
+ "Objects": [{
+ "StartTime": 12128,
+ "EndTime": 13858,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 41666,
+ "Objects": [{
+ "StartTime": 41666,
+ "EndTime": 42589,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 62666,
+ "Objects": [{
+ "StartTime": 62666,
+ "EndTime": 63127,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ },
+ {
+ "StartTime": 208743,
+ "Objects": [{
+ "StartTime": 208743,
+ "EndTime": 209204,
+ "IsRim": false,
+ "IsCentre": false,
+ "IsDrumRoll": true,
+ "IsSwell": false,
+ "IsStrong": false
+ }]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll.osu
new file mode 100644
index 0000000000..4c493b47d4
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-generating-drumroll.osu
@@ -0,0 +1,25 @@
+osu file format v14
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:4.2
+OverallDifficulty:9
+ApproachRate:9.8
+SliderMultiplier:1.87
+SliderTickRate:1
+
+[TimingPoints]
+6590,461.538461538462,4,2,2,15,1,0
+6590,-200,4,2,2,15,0,0
+49051,230.769230769231,4,2,1,15,1,0
+62666,-200,4,2,1,60,0,0
+197666,-100,4,2,1,85,0,1
+
+[HitObjects]
+88,104,6590,6,0,B|176:156|256:108|256:108|336:60|423:112,1,350.625,6|0,0:0|0:0,0:0:0:0:
+396,213,8436,2,0,P|277:247|376:172,1,350.625,6|0,0:0|0:0,0:0:0:0:
+472,220,10282,2,0,P|456:288|220:300,1,350.625,6|0,0:0|0:0,0:0:0:0:
+277,200,12128,2,0,P|398:225|276:244,1,350.625,6|0,0:0|0:0,0:0:0:0:
+268,229,41666,2,0,L|473:210,1,187,2|2,0:0|0:0,0:0:0:0:
+133,342,62666,2,0,B|132:316|132:316|128:316|128:316|130:295|130:295|126:296|126:296|129:275|129:275|125:275|125:275|127:254|127:254|123:255|123:255|125:234|125:234|121:234|121:234|123:213|123:213|119:214|119:214|121:193|121:193|118:193|118:193|118:172,1,187,8|8,0:0|0:0,0:0:0:0:
+481,338,208743,6,0,P|492:262|383:195,2,187,2|8|2,0:0|0:0|0:0,0:0:0:0:
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 50cc80db50..0a9719f27b 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -10,6 +10,8 @@ using osu.Game.Rulesets.UI;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.Replays.Types;
+using osu.Game.Rulesets.Taiko.Replays;
namespace osu.Game.Rulesets.Taiko
{
@@ -101,7 +103,9 @@ namespace osu.Game.Rulesets.Taiko
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
- public override int LegacyID => 1;
+ public override int? LegacyID => 1;
+
+ public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
public TaikoRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
diff --git a/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs
new file mode 100644
index 0000000000..385e041ace
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Tests/TaikoBeatmapConversionTest.cs
@@ -0,0 +1,73 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.MathUtils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Taiko.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects;
+using osu.Game.Tests.Beatmaps;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ public class TaikoBeatmapConversionTest : BeatmapConversionTest
+ {
+ protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
+
+ private bool isForCurrentRuleset;
+
+ [NonParallelizable]
+ [TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
+ [TestCase("slider-generating-drumroll", false)]
+ public void Test(string name, bool isForCurrentRuleset)
+ {
+ this.isForCurrentRuleset = isForCurrentRuleset;
+ base.Test(name);
+ }
+
+ protected override IEnumerable CreateConvertValue(HitObject hitObject)
+ {
+ yield return new ConvertValue
+ {
+ StartTime = hitObject.StartTime,
+ EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
+ IsRim = hitObject is RimHit,
+ IsCentre = hitObject is CentreHit,
+ IsDrumRoll = hitObject is DrumRoll,
+ IsSwell = hitObject is Swell,
+ IsStrong = ((TaikoHitObject)hitObject).IsStrong
+ };
+ }
+
+ protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new TaikoBeatmapConverter(isForCurrentRuleset);
+ }
+
+ public struct ConvertValue : IEquatable
+ {
+ ///
+ /// A sane value to account for osu!stable using ints everwhere.
+ ///
+ private const float conversion_lenience = 2;
+
+ public double StartTime;
+ public double EndTime;
+ public bool IsRim;
+ public bool IsCentre;
+ public bool IsDrumRoll;
+ public bool IsSwell;
+ public bool IsStrong;
+
+ public bool Equals(ConvertValue other)
+ => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
+ && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
+ && IsRim == other.IsRim
+ && IsCentre == other.IsCentre
+ && IsDrumRoll == other.IsDrumRoll
+ && IsSwell == other.IsSwell
+ && IsStrong == other.IsStrong;
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs
index c7201150e9..80721271d6 100644
--- a/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseInputDrum.cs
@@ -15,7 +15,7 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCaseInputDrum : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs b/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
index 3d2d97b6d3..f6b0ceb7bd 100644
--- a/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCasePerformancePoints.cs
@@ -5,7 +5,7 @@ using NUnit.Framework;
namespace osu.Game.Rulesets.Taiko.Tests
{
- [Ignore("getting CI working")]
+ [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
diff --git a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
index fd396c201d..3fd16ed1b5 100644
--- a/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/Tests/TestCaseTaikoPlayfield.cs
@@ -25,7 +25,6 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
- [Ignore("getting CI working")]
public class TestCaseTaikoPlayfield : OsuTestCase
{
private const double default_duration = 1000;
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
index c0e8bd1b5a..6274232ffd 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs
@@ -15,17 +15,14 @@ namespace osu.Game.Rulesets.Taiko.UI
///
public class DrawableTaikoJudgement : DrawableJudgement
{
- public readonly DrawableHitObject JudgedObject;
-
///
/// Creates a new judgement text.
///
/// The object which is being judged.
/// The judgement to visualise.
- public DrawableTaikoJudgement(DrawableHitObject judgedObject, Judgement judgement)
- : base(judgement)
+ public DrawableTaikoJudgement(Judgement judgement, DrawableHitObject judgedObject)
+ : base(judgement, judgedObject)
{
- JudgedObject = judgedObject;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 98f20fd558..b918f495fc 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -4,7 +4,6 @@
using System;
using OpenTK;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -34,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader]
- private void load(AudioManager audio)
+ private void load()
{
- var sampleMappings = new DrumSampleMapping(controlPoints, audio);
+ var sampleMappings = new DrumSampleMapping(controlPoints);
Children = new Drawable[]
{
@@ -63,6 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI
CentreAction = TaikoAction.RightCentre
}
};
+
+ AddRangeInternal(sampleMappings.Sounds);
}
///
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
index 49c87f7480..75aaceaecb 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs
@@ -16,6 +16,7 @@ using System.Linq;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Container hitExplosionContainer;
private readonly Container kiaiExplosionContainer;
- private readonly Container judgementContainer;
+ private readonly JudgementContainer judgementContainer;
protected override Container Content => content;
private readonly Container content;
@@ -131,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET },
Blending = BlendingMode.Additive
},
- judgementContainer = new Container
+ judgementContainer = new JudgementContainer
{
Name = "Judgements",
RelativeSizeAxes = Axes.Y,
@@ -227,7 +228,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
if (judgedObject.DisplayJudgement && judgementContainer.FirstOrDefault(j => j.JudgedObject == judgedObject) == null)
{
- judgementContainer.Add(new DrawableTaikoJudgement(judgedObject, judgement)
+ judgementContainer.Add(new DrawableTaikoJudgement(judgement, judgedObject)
{
Anchor = judgement.IsHit ? Anchor.TopLeft : Anchor.CentreLeft,
Origin = judgement.IsHit ? Anchor.BottomCentre : Anchor.Centre,
diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
index 1b9821d698..eb282c53ca 100644
--- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.Taiko.Replays;
using OpenTK;
using System.Linq;
using osu.Framework.Input;
+using osu.Game.Input.Handlers;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Taiko.UI
@@ -78,7 +79,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
- protected override Vector2 GetPlayfieldAspectAdjust()
+ protected override Vector2 GetAspectAdjustedSize()
{
const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768;
const float default_aspect = 16f / 9f;
@@ -88,6 +89,8 @@ namespace osu.Game.Rulesets.Taiko.UI
return new Vector2(1, default_relative_height * aspectAdjust);
}
+ protected override Vector2 PlayfieldArea => Vector2.One;
+
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
@@ -131,6 +134,6 @@ namespace osu.Game.Rulesets.Taiko.UI
return null;
}
- protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index 21bbc4993c..bc878b599b 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
+using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats
{
@@ -20,11 +21,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapGeneral()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var beatmap = decoder.DecodeBeatmap(stream);
+ var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmap.Metadata;
@@ -47,7 +48,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var beatmapInfo = decoder.DecodeBeatmap(stream).BeatmapInfo;
+ var beatmapInfo = decoder.Decode(stream).BeatmapInfo;
int[] expectedBookmarks =
{
@@ -72,7 +73,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var beatmap = decoder.DecodeBeatmap(stream);
+ var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
var metadata = beatmap.Metadata;
@@ -96,13 +97,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var difficulty = decoder.DecodeBeatmap(stream).BeatmapInfo.BaseDifficulty;
+ var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
Assert.AreEqual(9, difficulty.ApproachRate);
- Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
+ Assert.AreEqual(1.8, difficulty.SliderMultiplier);
Assert.AreEqual(2, difficulty.SliderTickRate);
}
}
@@ -110,11 +111,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapEvents()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var beatmap = decoder.DecodeBeatmap(stream);
+ var beatmap = decoder.Decode(stream);
var metadata = beatmap.Metadata;
var breakPoint = beatmap.Breaks[0];
@@ -128,11 +129,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapTimingPoints()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var beatmap = decoder.DecodeBeatmap(stream);
+ var beatmap = decoder.Decode(stream);
var controlPoints = beatmap.ControlPointInfo;
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
@@ -163,11 +164,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapColors()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacySkinDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var comboColors = decoder.DecodeBeatmap(stream).ComboColors;
+ var comboColors = decoder.Decode(stream).ComboColours;
Color4[] expectedColors =
{
@@ -187,11 +188,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapHitObjects()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
- var hitObjects = decoder.DecodeBeatmap(stream).HitObjects;
+ var hitObjects = decoder.Decode(stream).HitObjects;
var curveData = hitObjects[0] as IHasCurve;
var positionData = hitObjects[0] as IHasPosition;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index dce6c0f55b..1c0801c634 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -18,11 +18,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeStoryboardEvents()
{
- var decoder = new LegacyBeatmapDecoder();
+ var decoder = new LegacyStoryboardDecoder();
using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
using (var stream = new StreamReader(resStream))
{
- var storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(stream);
+ var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable);
Assert.AreEqual(4, storyboard.Layers.Count());
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 186bd44640..f37672b5cc 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -12,7 +12,6 @@ using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Resources;
using OpenTK;
-using OpenTK.Graphics;
namespace osu.Game.Tests.Beatmaps.Formats
{
@@ -85,28 +84,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
Assert.AreEqual(9, difficulty.ApproachRate);
- Assert.AreEqual(1.8f, difficulty.SliderMultiplier);
+ Assert.AreEqual(1.8, difficulty.SliderMultiplier);
Assert.AreEqual(2, difficulty.SliderTickRate);
}
- [Test]
- public void TestDecodeColors()
- {
- var beatmap = decodeAsJson(normal);
- Color4[] expected =
- {
- new Color4(142, 199, 255, 255),
- new Color4(255, 128, 128, 255),
- new Color4(128, 255, 255, 255),
- new Color4(128, 255, 128, 255),
- new Color4(255, 187, 255, 255),
- new Color4(255, 177, 140, 255),
- };
- Assert.AreEqual(expected.Length, beatmap.ComboColors.Count);
- for (int i = 0; i < expected.Length; i++)
- Assert.AreEqual(expected[i], beatmap.ComboColors[i]);
- }
-
[Test]
public void TestDecodeHitObjects()
{
@@ -159,7 +140,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var sr = new StreamReader(stream))
{
- var legacyDecoded = new LegacyBeatmapDecoder().DecodeBeatmap(sr);
+ var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new StreamReader(ms))
@@ -168,7 +149,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
sw.Flush();
ms.Position = 0;
- return (legacyDecoded, new JsonBeatmapDecoder().DecodeBeatmap(sr2));
+ return (legacyDecoded, new JsonBeatmapDecoder().Decode(sr2));
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 0b49bc8bb9..6428881b54 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -26,19 +26,124 @@ namespace osu.Game.Tests.Beatmaps.IO
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
{
- var osu = loadOsu(host);
+ try
+ {
+ loadOszIntoOsu(loadOsu(host));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
- var temp = prepareTempCopy(osz_path);
+ [Test]
+ public void TestImportThenDelete()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
- Assert.IsTrue(File.Exists(temp));
+ var imported = loadOszIntoOsu(osu);
- osu.Dependencies.Get().Import(temp);
+ deleteBeatmapSet(imported, osu);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
- ensureLoaded(osu);
+ [Test]
+ public void TestImportThenImport()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
- waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+ var imported = loadOszIntoOsu(osu);
+ var importedSecondTime = loadOszIntoOsu(osu);
- host.Exit();
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID == importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+
+ var manager = osu.Dependencies.Get();
+
+ Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
+ Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestImportThenImportDifferentHash()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ var imported = loadOszIntoOsu(osu);
+
+ //var change = manager.QueryBeatmapSets(_ => true).First();
+ imported.Hash += "-changed";
+ manager.Update(imported);
+
+ var importedSecondTime = loadOszIntoOsu(osu);
+
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID != importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
+
+ Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
+ Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestImportThenDeleteThenImport()
+ {
+ //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
+ {
+ try
+ {
+ var osu = loadOsu(host);
+
+ var imported = loadOszIntoOsu(osu);
+
+ deleteBeatmapSet(imported, osu);
+
+ var importedSecondTime = loadOszIntoOsu(osu);
+
+ // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
+ Assert.IsTrue(imported.ID == importedSecondTime.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+ }
+ finally
+ {
+ host.Exit();
+ }
}
}
@@ -50,24 +155,28 @@ namespace osu.Game.Tests.Beatmaps.IO
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("host", true))
using (HeadlessGameHost client = new CleanRunHeadlessGameHost("client", true))
{
- Assert.IsTrue(host.IsPrimaryInstance);
- Assert.IsFalse(client.IsPrimaryInstance);
+ try
+ {
+ Assert.IsTrue(host.IsPrimaryInstance);
+ Assert.IsFalse(client.IsPrimaryInstance);
- var osu = loadOsu(host);
+ var osu = loadOsu(host);
- var temp = prepareTempCopy(osz_path);
+ var temp = prepareTempCopy(osz_path);
+ Assert.IsTrue(File.Exists(temp));
- Assert.IsTrue(File.Exists(temp));
+ var importer = new ArchiveImportIPCChannel(client);
+ if (!importer.ImportAsync(temp).Wait(10000))
+ Assert.Fail(@"IPC took too long to send");
- var importer = new BeatmapIPCChannel(client);
- if (!importer.ImportAsync(temp).Wait(10000))
- Assert.Fail(@"IPC took too long to send");
+ ensureLoaded(osu);
- ensureLoaded(osu);
-
- waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
-
- host.Exit();
+ waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
+ }
+ finally
+ {
+ host.Exit();
+ }
}
}
@@ -76,25 +185,53 @@ namespace osu.Game.Tests.Beatmaps.IO
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
{
- var osu = loadOsu(host);
-
- var temp = prepareTempCopy(osz_path);
-
- Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
-
- using (File.OpenRead(temp))
- osu.Dependencies.Get().Import(temp);
-
- ensureLoaded(osu);
-
- File.Delete(temp);
-
- Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
-
- host.Exit();
+ try
+ {
+ var osu = loadOsu(host);
+ var temp = prepareTempCopy(osz_path);
+ Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
+ using (File.OpenRead(temp))
+ osu.Dependencies.Get().Import(temp);
+ ensureLoaded(osu);
+ File.Delete(temp);
+ Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
+ }
+ finally
+ {
+ host.Exit();
+ }
}
}
+ private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu)
+ {
+ var temp = prepareTempCopy(osz_path);
+
+ Assert.IsTrue(File.Exists(temp));
+
+ var manager = osu.Dependencies.Get();
+
+ manager.Import(temp);
+
+ var imported = manager.GetAllUsableBeatmapSets();
+
+ ensureLoaded(osu);
+
+ waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
+
+ return imported.FirstOrDefault();
+ }
+
+ private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
+ {
+ var manager = osu.Dependencies.Get();
+ manager.Delete(imported);
+
+ Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
+ Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
+ Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
+ }
+
private string prepareTempCopy(string path)
{
var temp = Path.GetTempFileName();
@@ -105,65 +242,55 @@ namespace osu.Game.Tests.Beatmaps.IO
{
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
-
waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time");
-
return osu;
}
private void ensureLoaded(OsuGameBase osu, int timeout = 60000)
{
IEnumerable resultSets = null;
-
var store = osu.Dependencies.Get();
-
waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(),
@"BeatmapSet did not import to the database in allocated time.", timeout);
//ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
-
IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0);
IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526);
//if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
waitForOrAssert(() => queryBeatmaps().Count() == 12,
@"Beatmaps did not import to the database in allocated time", timeout);
-
waitForOrAssert(() => queryBeatmapSets().Count() == 1,
@"BeatmapSet did not import to the database in allocated time", timeout);
-
int countBeatmapSetBeatmaps = 0;
int countBeatmaps = 0;
-
waitForOrAssert(() =>
- (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
- (countBeatmaps = queryBeatmaps().Count()),
+ (countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
+ (countBeatmaps = queryBeatmaps().Count()),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
var set = queryBeatmapSets().First();
-
foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID));
-
Assert.IsTrue(set.Beatmaps.Count > 0);
-
var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
-
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
-
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
-
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Count > 0);
}
private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000)
{
- Action waitAction = () => { while (!result()) Thread.Sleep(200); };
+ Action waitAction = () =>
+ {
+ while (!result()) Thread.Sleep(200);
+ };
+
Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage);
}
}
diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
index 44eb385e22..29d25accbb 100644
--- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs
@@ -5,9 +5,9 @@ using System.IO;
using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.IO;
using osu.Game.Tests.Resources;
using osu.Game.Beatmaps.Formats;
+using osu.Game.IO.Archives;
namespace osu.Game.Tests.Beatmaps.IO
{
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
- var reader = new OszArchiveReader(osz);
+ var reader = new ZipArchiveReader(osz);
string[] expected =
{
"Soleily - Renatus (Deif) [Platter].osu",
@@ -46,11 +46,11 @@ namespace osu.Game.Tests.Beatmaps.IO
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
- var reader = new OszArchiveReader(osz);
+ var reader = new ZipArchiveReader(osz);
BeatmapMetadata meta;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
- meta = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
+ meta = Decoder.GetDecoder(stream).Decode(stream).Metadata;
Assert.AreEqual(241526, meta.OnlineBeatmapSetID);
Assert.AreEqual("Soleily", meta.Artist);
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
Assert.AreEqual("Deif", meta.AuthorString);
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
- Assert.AreEqual(164471, meta.PreviewTime);
+ Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime);
Assert.AreEqual(string.Empty, meta.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
Assert.AreEqual("Renatus", meta.Title);
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{
using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz"))
{
- var reader = new OszArchiveReader(osz);
+ var reader = new ZipArchiveReader(osz);
using (var stream = new StreamReader(
reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
{
diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs
index 912dbc4056..e633d121ca 100644
--- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs
+++ b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs
@@ -1,8 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
+
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseAllPlayers : TestCasePlayer
{
}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs
new file mode 100644
index 0000000000..04a662426f
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs
@@ -0,0 +1,28 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Edit.Screens.Compose;
+using OpenTK;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseBeatDivisorControl : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) };
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = new BeatDivisorControl(new BindableBeatDivisor())
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(90, 90)
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
index f081d090c8..66cee634f5 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -17,6 +18,7 @@ using osu.Framework.Lists;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBeatSyncedContainer : OsuTestCase
{
private readonly MusicController mc;
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
index 4a65d12977..c68e548f44 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs
@@ -6,20 +6,24 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
+using osu.Game.Rulesets;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Carousel;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBeatmapCarousel : OsuTestCase
{
private TestBeatmapCarousel carousel;
+ private RulesetStore rulesets;
public override IReadOnlyList RequiredTypes => new[]
{
@@ -44,8 +48,10 @@ namespace osu.Game.Tests.Visual
private const int set_count = 5;
[BackgroundDependencyLoader]
- private void load()
+ private void load(RulesetStore rulesets)
{
+ this.rulesets = rulesets;
+
Add(carousel = new TestBeatmapCarousel
{
RelativeSizeAxes = Axes.Both,
@@ -60,7 +66,9 @@ namespace osu.Game.Tests.Visual
AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
- AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load");
+ bool changed = false;
+ carousel.BeatmapSetsChanged = () => changed = true;
+ AddUntilStep(() => changed, "Wait for load");
testTraversal();
testFiltering();
@@ -71,6 +79,7 @@ namespace osu.Game.Tests.Visual
testRemoveAll();
testEmptyTraversal();
testHiding();
+ testSelectingFilteredRuleset();
}
private void ensureRandomFetchSuccess() =>
@@ -205,6 +214,12 @@ namespace osu.Game.Tests.Visual
checkVisibleItemCount(true, 0);
AddAssert("Selection is null", () => currentSelection == null);
+ advanceSelection(true);
+ AddAssert("Selection is null", () => currentSelection == null);
+
+ advanceSelection(false);
+ AddAssert("Selection is null", () => currentSelection == null);
+
AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false));
AddAssert("Selection is non-null", () => currentSelection != null);
@@ -353,6 +368,41 @@ namespace osu.Game.Tests.Visual
}
}
+ private void testSelectingFilteredRuleset()
+ {
+ var testMixed = createTestBeatmapSet(set_count + 1);
+ AddStep("add mixed ruleset beatmapset", () =>
+ {
+ for (int i = 0; i <= 2; i++)
+ {
+ testMixed.Beatmaps[i].Ruleset = rulesets.AvailableRulesets.ElementAt(i);
+ testMixed.Beatmaps[i].RulesetID = i;
+ }
+
+ carousel.UpdateBeatmapSet(testMixed);
+ });
+ AddStep("filter to ruleset 0", () =>
+ carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
+ AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
+ AddAssert("unfiltered beatmap selected", () => carousel.SelectedBeatmap.Equals(testMixed.Beatmaps[0]));
+
+ AddStep("remove mixed set", () =>
+ {
+ carousel.RemoveBeatmapSet(testMixed);
+ testMixed = null;
+ });
+ var testSingle = createTestBeatmapSet(set_count + 2);
+ testSingle.Beatmaps.ForEach(b =>
+ {
+ b.Ruleset = rulesets.AvailableRulesets.ElementAt(1);
+ b.RulesetID = b.Ruleset.ID ?? 1;
+ });
+ AddStep("add single ruleset beatmapset", () => carousel.UpdateBeatmapSet(testSingle));
+ AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testSingle.Beatmaps[0], false));
+ checkNoSelection();
+ AddStep("remove single ruleset set", () => carousel.RemoveBeatmapSet(testSingle));
+ }
+
private BeatmapSetInfo createTestBeatmapSet(int id)
{
return new BeatmapSetInfo
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
index bde071c4a3..3ccdaa90d9 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs
@@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
@@ -18,6 +19,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBeatmapInfoWedge : OsuTestCase
{
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs
index ad85b3ed52..d9aedb7a5f 100644
--- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
@@ -12,6 +13,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBeatmapSetOverlay : OsuTestCase
{
private readonly BeatmapSetOverlay overlay;
diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs
index 34abef7d76..20bdd6736c 100644
--- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs
+++ b/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs
@@ -1,11 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBreadcrumbs : OsuTestCase
{
public TestCaseBreadcrumbs()
diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
index f9ed606080..51b8c61963 100644
--- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs
@@ -3,11 +3,13 @@
using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing;
-using osu.Game.Screens.Play.BreaksOverlay;
using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseBreakOverlay : OsuTestCase
{
private readonly BreakOverlay breakOverlay;
diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
index 61da76970e..93740593cb 100644
--- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
+++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// 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.Shapes;
@@ -9,6 +10,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseButtonSystem : OsuTestCase
{
public TestCaseButtonSystem()
diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs
index 3a7be686e1..786fcb64ab 100644
--- a/osu.Game.Tests/Visual/TestCaseChatLink.cs
+++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs
@@ -12,12 +12,14 @@ using osu.Game.Users;
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseChatLink : OsuTestCase
{
private readonly TestChatLineContainer textContainer;
diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/TestCaseContextMenu.cs
index 6098187dd6..45c12cf4af 100644
--- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs
+++ b/osu.Game.Tests/Visual/TestCaseContextMenu.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -13,6 +14,7 @@ using osu.Game.Graphics.Cursor;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseContextMenu : OsuTestCase
{
private const int start_time = 0;
diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs
index 363f6b53f0..72e699c54b 100644
--- a/osu.Game.Tests/Visual/TestCaseCursors.cs
+++ b/osu.Game.Tests/Visual/TestCaseCursors.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -16,6 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseCursors : OsuTestCase
{
private readonly ManualInputManager inputManager;
diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs
index d7fbf64664..e9512b29f7 100644
--- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseDialogOverlay : OsuTestCase
{
public TestCaseDialogOverlay()
diff --git a/osu.Game.Tests/Visual/TestCaseDirect.cs b/osu.Game.Tests/Visual/TestCaseDirect.cs
index 8fa576135e..3f3dbb0bca 100644
--- a/osu.Game.Tests/Visual/TestCaseDirect.cs
+++ b/osu.Game.Tests/Visual/TestCaseDirect.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Overlays;
@@ -9,6 +10,7 @@ using osu.Game.Rulesets;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseDirect : OsuTestCase
{
private DirectOverlay direct;
diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
index ec70253118..4268fd305e 100644
--- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
+++ b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,6 +13,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseDrawableRoom : OsuTestCase
{
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/TestCaseEditor.cs b/osu.Game.Tests/Visual/TestCaseEditor.cs
deleted file mode 100644
index 37da41c228..0000000000
--- a/osu.Game.Tests/Visual/TestCaseEditor.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using osu.Framework.Allocation;
-using osu.Game.Beatmaps;
-using osu.Game.Screens.Edit;
-using osu.Game.Screens.Edit.Screens;
-
-namespace osu.Game.Tests.Visual
-{
- public class TestCaseEditor : OsuTestCase
- {
- public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) };
-
- private readonly Random rng;
-
- private BeatmapManager beatmaps;
- private OsuGameBase osuGame;
-
- public TestCaseEditor()
- {
- rng = new Random(1337);
-
- Add(new Editor());
- AddStep("Next beatmap", nextBeatmap);
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
- {
- this.osuGame = osuGame;
- this.beatmaps = beatmaps;
- }
-
- private void nextBeatmap()
- {
- var sets = beatmaps.GetAllUsableBeatmapSets();
- if (sets.Count == 0)
- return;
-
- BeatmapInfo info = sets[rng.Next(0, sets.Count)].Beatmaps[0];
- osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(info);
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
index 76771ecf82..cd25bc1683 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs
@@ -2,45 +2,39 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Game.Beatmaps;
+using osu.Framework.Timing;
+using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Screens.Compose;
+using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorCompose : OsuTestCase
{
- private readonly Random random;
- private readonly Compose compose;
+ public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) };
- public TestCaseEditorCompose()
- {
- random = new Random(1337);
+ private DependencyContainer dependencies;
- Add(compose = new Compose());
- AddStep("Next beatmap", nextBeatmap);
- }
-
- private OsuGameBase osuGame;
- private BeatmapManager beatmaps;
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
- private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
+ private void load(OsuGameBase osuGame)
{
- this.osuGame = osuGame;
- this.beatmaps = beatmaps;
+ osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
+
+ var compose = new Compose();
compose.Beatmap.BindTo(osuGame.Beatmap);
- }
- private void nextBeatmap()
- {
- var sets = beatmaps.GetAllUsableBeatmapSets();
- if (sets.Count == 0)
- return;
-
- var b = sets[random.Next(0, sets.Count)].Beatmaps[0];
- osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b);
+ Child = compose;
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs
index 8717f15311..d9850139cd 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs
@@ -3,11 +3,13 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorComposeRadioButtons : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) };
diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
index 6a47933a3c..d15ee32d8d 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -12,6 +13,7 @@ using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorComposeTimeline : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) };
diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs
index edfcde22b3..ee98fa087a 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
@@ -11,6 +12,7 @@ using osu.Game.Screens.Edit.Menus;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorMenuBar : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) };
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
new file mode 100644
index 0000000000..e9e966a826
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs
@@ -0,0 +1,468 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Tools;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Objects;
+using osu.Game.Screens.Edit.Screens.Compose;
+using osu.Game.Tests.Beatmaps;
+using OpenTK;
+using OpenTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseEditorSeekSnapping : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectComposer) };
+
+ private Track track;
+ private HitObjectComposer composer;
+
+ private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(4);
+
+ private DecoupleableInterpolatingFramedClock clock;
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGameBase osuGame)
+ {
+ clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
+ dependencies.Cache(beatDivisor);
+
+ var testBeatmap = new Beatmap
+ {
+ ControlPointInfo = new ControlPointInfo
+ {
+ TimingPoints =
+ {
+ new TimingControlPoint { Time = 0, BeatLength = 200},
+ new TimingControlPoint { Time = 100, BeatLength = 400 },
+ new TimingControlPoint { Time = 175, BeatLength = 800 },
+ new TimingControlPoint { Time = 350, BeatLength = 200 },
+ new TimingControlPoint { Time = 450, BeatLength = 100 },
+ new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 }
+ }
+ },
+ HitObjects =
+ {
+ new HitCircle { StartTime = 0 },
+ new HitCircle { StartTime = 5000 }
+ }
+ };
+
+ osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap);
+ track = osuGame.Beatmap.Value.Track;
+
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[] { composer = new TestHitObjectComposer(new OsuRuleset()) },
+ new Drawable[] { new TimingPointVisualiser(testBeatmap, track) { Clock = clock } },
+ },
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Distributed),
+ new Dimension(GridSizeMode.AutoSize),
+ }
+ };
+
+ testSeekNoSnapping();
+ testSeekSnappingOnBeat();
+ testSeekSnappingInBetweenBeat();
+ testSeekForwardNoSnapping();
+ testSeekForwardSnappingOnBeat();
+ testSeekForwardSnappingFromInBetweenBeat();
+ testSeekBackwardSnappingOnBeat();
+ testSeekBackwardSnappingFromInBetweenBeat();
+ testSeekingWithFloatingPointBeatLength();
+ }
+
+ ///
+ /// Tests whether time is correctly seeked without snapping.
+ ///
+ private void testSeekNoSnapping()
+ {
+ reset();
+
+ // Forwards
+ AddStep("Seek(0)", () => composer.SeekTo(0));
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ AddStep("Seek(33)", () => composer.SeekTo(33));
+ AddAssert("Time = 33", () => clock.CurrentTime == 33);
+ AddStep("Seek(89)", () => composer.SeekTo(89));
+ AddAssert("Time = 89", () => clock.CurrentTime == 89);
+
+ // Backwards
+ AddStep("Seek(25)", () => composer.SeekTo(25));
+ AddAssert("Time = 25", () => clock.CurrentTime == 25);
+ AddStep("Seek(0)", () => composer.SeekTo(0));
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ }
+
+ ///
+ /// Tests whether seeking to exact beat times puts us on the beat time.
+ /// These are the white/yellow ticks on the graph.
+ ///
+ private void testSeekSnappingOnBeat()
+ {
+ reset();
+
+ AddStep("Seek(0), Snap", () => composer.SeekTo(0, true));
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ AddStep("Seek(50), Snap", () => composer.SeekTo(50, true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("Seek(100), Snap", () => composer.SeekTo(100, true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("Seek(175), Snap", () => composer.SeekTo(175, true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("Seek(350), Snap", () => composer.SeekTo(350, true));
+ AddAssert("Time = 350", () => clock.CurrentTime == 350);
+ AddStep("Seek(400), Snap", () => composer.SeekTo(400, true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("Seek(450), Snap", () => composer.SeekTo(450, true));
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ }
+
+ ///
+ /// Tests whether seeking to somewhere in the middle between beats puts us on the expected beats.
+ /// For example, snapping between a white/yellow beat should put us on either the yellow or white, depending on which one we're closer too.
+ /// If
+ ///
+ private void testSeekSnappingInBetweenBeat()
+ {
+ reset();
+
+ AddStep("Seek(24), Snap", () => composer.SeekTo(24, true));
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ AddStep("Seek(26), Snap", () => composer.SeekTo(26, true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("Seek(150), Snap", () => composer.SeekTo(150, true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("Seek(170), Snap", () => composer.SeekTo(170, true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("Seek(274), Snap", () => composer.SeekTo(274, true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("Seek(276), Snap", () => composer.SeekTo(276, true));
+ AddAssert("Time = 350", () => clock.CurrentTime == 350);
+ }
+
+ ///
+ /// Tests that when seeking forward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
+ ///
+ private void testSeekForwardNoSnapping()
+ {
+ reset();
+
+ AddStep("SeekForward", () => composer.SeekForward());
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("SeekForward", () => composer.SeekForward());
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("SeekForward", () => composer.SeekForward());
+ AddAssert("Time = 200", () => clock.CurrentTime == 200);
+ AddStep("SeekForward", () => composer.SeekForward());
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("SeekForward", () => composer.SeekForward());
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ }
+
+ ///
+ /// Tests that when seeking forward with beat snapping, all beats are snapped to and timing points are never skipped.
+ ///
+ private void testSeekForwardSnappingOnBeat()
+ {
+ reset();
+
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 350", () => clock.CurrentTime == 350);
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ }
+
+ ///
+ /// Tests that when seeking forward from in-between two beats, the next beat or timing point is snapped to, and no beats are skipped.
+ /// This will also test being extremely close to the next beat/timing point, to ensure rounding is not an issue.
+ ///
+ private void testSeekForwardSnappingFromInBetweenBeat()
+ {
+ reset();
+
+ AddStep("Seek(49)", () => composer.SeekTo(49));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("Seek(49.999)", () => composer.SeekTo(49.999));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("Seek(99)", () => composer.SeekTo(99));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("Seek(99.999)", () => composer.SeekTo(99.999));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("Seek(174)", () => composer.SeekTo(174));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("Seek(349)", () => composer.SeekTo(349));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 350", () => clock.CurrentTime == 350);
+ AddStep("Seek(399)", () => composer.SeekTo(399));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("Seek(449)", () => composer.SeekTo(449));
+ AddStep("SeekForward, Snap", () => composer.SeekForward(true));
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ }
+
+ ///
+ /// Tests that when seeking backward with no beat snapping, beats are never explicitly snapped to, nor the next timing point (if we've skipped it).
+ ///
+ private void testSeekBackwardNoSnapping()
+ {
+ reset();
+
+ AddStep("Seek(450)", () => composer.SeekTo(450));
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 425", () => clock.CurrentTime == 425);
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 375", () => clock.CurrentTime == 375);
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 325", () => clock.CurrentTime == 325);
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 125", () => clock.CurrentTime == 125);
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 25", () => clock.CurrentTime == 25);
+ AddStep("SeekBackward", () => composer.SeekBackward());
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ }
+
+ ///
+ /// Tests that when seeking backward with beat snapping, all beats are snapped to and timing points are never skipped.
+ ///
+ private void testSeekBackwardSnappingOnBeat()
+ {
+ reset();
+
+ AddStep("Seek(450)", () => composer.SeekTo(450));
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 350", () => clock.CurrentTime == 350);
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 175", () => clock.CurrentTime == 175);
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 100", () => clock.CurrentTime == 100);
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 50", () => clock.CurrentTime == 50);
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ }
+
+ ///
+ /// Tests that when seeking backward from in-between two beats, the previous beat or timing point is snapped to, and no beats are skipped.
+ /// This will also test being extremely close to the previous beat/timing point, to ensure rounding is not an issue.
+ ///
+ private void testSeekBackwardSnappingFromInBetweenBeat()
+ {
+ reset();
+
+ AddStep("Seek(451)", () => composer.SeekTo(451));
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ AddStep("Seek(450.999)", () => composer.SeekTo(450.999));
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 450", () => clock.CurrentTime == 450);
+ AddStep("Seek(401)", () => composer.SeekTo(401));
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ AddStep("Seek(401.999)", () => composer.SeekTo(401.999));
+ AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
+ AddAssert("Time = 400", () => clock.CurrentTime == 400);
+ }
+
+ ///
+ /// Tests that there are no rounding issues when snapping to beats within a timing point with a floating-point beatlength.
+ ///
+ private void testSeekingWithFloatingPointBeatLength()
+ {
+ reset();
+
+ double lastTime = 0;
+
+ AddStep("Seek(0)", () => composer.SeekTo(0));
+
+ for (int i = 0; i < 20; i++)
+ {
+ AddStep("SeekForward, Snap", () =>
+ {
+ lastTime = clock.CurrentTime;
+ composer.SeekForward(true);
+ });
+ AddAssert("Time > lastTime", () => clock.CurrentTime > lastTime);
+ }
+
+ for (int i = 0; i < 20; i++)
+ {
+ AddStep("SeekBackward, Snap", () =>
+ {
+ lastTime = clock.CurrentTime;
+ composer.SeekBackward(true);
+ });
+ AddAssert("Time < lastTime", () => clock.CurrentTime < lastTime);
+ }
+
+ AddAssert("Time = 0", () => clock.CurrentTime == 0);
+ }
+
+ private void reset()
+ {
+ AddStep("Reset", () => composer.SeekTo(0));
+ }
+
+ private class TestHitObjectComposer : HitObjectComposer
+ {
+ public TestHitObjectComposer(Ruleset ruleset)
+ : base(ruleset)
+ {
+ }
+
+ protected override IReadOnlyList CompositionTools => new ICompositionTool[0];
+ }
+
+ private class TimingPointVisualiser : CompositeDrawable
+ {
+ private readonly Track track;
+
+ private readonly Drawable tracker;
+
+ public TimingPointVisualiser(Beatmap beatmap, Track track)
+ {
+ this.track = track;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Width = 0.75f;
+
+ FillFlowContainer timelineContainer;
+
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Name = "Background",
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black.Opacity(85f)
+ },
+ new Container
+ {
+ Name = "Tracks",
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding(15),
+ Children = new[]
+ {
+ tracker = new Box
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ RelativePositionAxes = Axes.X,
+ Width = 2,
+ Colour = Color4.Red,
+ },
+ timelineContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(0, 5)
+ },
+ }
+ }
+ };
+
+ var timingPoints = beatmap.ControlPointInfo.TimingPoints;
+
+ for (int i = 0; i < timingPoints.Count; i++)
+ {
+ TimingControlPoint next = i == timingPoints.Count - 1 ? null : timingPoints[i + 1];
+ timelineContainer.Add(new TimingPointTimeline(timingPoints[i], next?.Time ?? beatmap.HitObjects.Last().StartTime, track.Length));
+ }
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ tracker.X = (float)(Time.Current / track.Length);
+ }
+
+ private class TimingPointTimeline : CompositeDrawable
+ {
+ public TimingPointTimeline(TimingControlPoint timingPoint, double endTime, double fullDuration)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+
+ Box createMainTick(double time) => new Box
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomCentre,
+ RelativePositionAxes = Axes.X,
+ X = (float)(time / fullDuration),
+ Height = 10,
+ Width = 2
+ };
+
+ Box createBeatTick(double time) => new Box
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomCentre,
+ RelativePositionAxes = Axes.X,
+ X = (float)(time / fullDuration),
+ Height = 5,
+ Width = 2,
+ Colour = time > endTime ? Color4.Gray : Color4.Yellow
+ };
+
+ AddInternal(createMainTick(timingPoint.Time));
+ AddInternal(createMainTick(endTime));
+
+ for (double t = timingPoint.Time + timingPoint.BeatLength / 4; t < fullDuration; t += timingPoint.BeatLength / 4)
+ AddInternal(createBeatTick(t));
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs
index 755800c4e1..bbbfef477a 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs
@@ -3,63 +3,73 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Allocation;
-using OpenTK;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
+using OpenTK;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Edit.Layers.Selection;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
+using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Screens.Edit.Screens.Compose.Layers;
+using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorSelectionLayer : OsuTestCase
{
- public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) };
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(SelectionLayer),
+ typeof(SelectionBox),
+ typeof(HitObjectComposer),
+ typeof(OsuHitObjectComposer),
+ typeof(HitObjectMaskLayer),
+ typeof(HitObjectMask),
+ typeof(HitCircleMask),
+ typeof(SliderMask),
+ typeof(SliderCircleMask)
+ };
+
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuGameBase osuGame)
{
- var playfield = new OsuEditPlayfield();
-
- Children = new Drawable[]
+ osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap
{
- new Container
+ HitObjects = new List
{
- RelativeSizeAxes = Axes.Both,
- Clock = new FramedClock(new StopwatchClock()),
- Child = playfield
+ new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f },
+ new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f },
+ new Slider
+ {
+ Position = new Vector2(128, 256),
+ ControlPoints = new List
+ {
+ Vector2.Zero,
+ new Vector2(216, 0),
+ },
+ Distance = 400,
+ Velocity = 1,
+ TickDistance = 100,
+ Scale = 0.5f,
+ }
},
- new SelectionLayer(playfield)
- };
+ });
- var hitCircle1 = new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f };
- var hitCircle2 = new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f };
- var slider = new Slider
- {
- ControlPoints = new List
- {
- new Vector2(128, 256),
- new Vector2(344, 256),
- },
- Distance = 400,
- Position = new Vector2(128, 256),
- Velocity = 1,
- TickDistance = 100,
- Scale = 0.5f,
- };
+ var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
- hitCircle1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- hitCircle2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
- slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
-
- playfield.Add(new DrawableHitCircle(hitCircle1));
- playfield.Add(new DrawableHitCircle(hitCircle2));
- playfield.Add(new DrawableSlider(slider));
+ Child = new OsuHitObjectComposer(new OsuRuleset());
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
index 8c8699fffa..bbe2956c5d 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
+++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs
@@ -3,29 +3,39 @@
using System;
using System.Collections.Generic;
-using osu.Framework.Audio.Track;
-using osu.Framework.Graphics.Textures;
+using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
using OpenTK;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Framework.Configuration;
+using osu.Framework.Timing;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseEditorSummaryTimeline : OsuTestCase
{
- private const int length = 60000;
- private readonly Random random;
-
public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) };
private readonly Bindable beatmap = new Bindable();
- public TestCaseEditorSummaryTimeline()
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
+
+ [BackgroundDependencyLoader]
+ private void load()
{
- random = new Random(1337);
+ beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
+
+ var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
SummaryTimeline summaryTimeline;
Add(summaryTimeline = new SummaryTimeline
@@ -36,58 +46,6 @@ namespace osu.Game.Tests.Visual
});
summaryTimeline.Beatmap.BindTo(beatmap);
-
- AddStep("New beatmap", newBeatmap);
-
- newBeatmap();
- }
-
- private void newBeatmap()
- {
- var b = new Beatmap();
-
- for (int i = 0; i < random.Next(1, 10); i++)
- b.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { Time = random.Next(0, length) });
-
- for (int i = 0; i < random.Next(1, 5); i++)
- b.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = random.Next(0, length) });
-
- for (int i = 0; i < random.Next(1, 5); i++)
- b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) });
-
- for (int i = 0; i < random.Next(1, 5); i++)
- b.ControlPointInfo.SamplePoints.Add(new SampleControlPoint { Time = random.Next(0, length) });
-
- b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)];
- for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++)
- b.BeatmapInfo.Bookmarks[i] = random.Next(0, length);
-
- beatmap.Value = new TestWorkingBeatmap(b);
- }
-
- private class TestWorkingBeatmap : WorkingBeatmap
- {
- private readonly Beatmap beatmap;
-
- public TestWorkingBeatmap(Beatmap beatmap)
- : base(beatmap.BeatmapInfo)
- {
- this.beatmap = beatmap;
- }
-
- protected override Texture GetBackground() => null;
-
- protected override Beatmap GetBeatmap() => beatmap;
-
- protected override Track GetTrack() => new TestTrack();
-
- private class TestTrack : TrackVirtual
- {
- public TestTrack()
- {
- Length = length;
- }
- }
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseGamefield.cs b/osu.Game.Tests/Visual/TestCaseGamefield.cs
index 44f46dea18..80b3f9eb40 100644
--- a/osu.Game.Tests/Visual/TestCaseGamefield.cs
+++ b/osu.Game.Tests/Visual/TestCaseGamefield.cs
@@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseGamefield : OsuTestCase
{
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/TestCaseGraph.cs
index 99184d4689..285a43707a 100644
--- a/osu.Game.Tests/Visual/TestCaseGraph.cs
+++ b/osu.Game.Tests/Visual/TestCaseGraph.cs
@@ -2,12 +2,14 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using OpenTK;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseGraph : OsuTestCase
{
public TestCaseGraph()
diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs
index a7fc58f2b5..2e94baa9fc 100644
--- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs
+++ b/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -13,6 +14,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseHistoricalSection : OsuTestCase
{
public override IReadOnlyList RequiredTypes =>
diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/TestCaseIconButton.cs
index 525e867c56..fae79e25bd 100644
--- a/osu.Game.Tests/Visual/TestCaseIconButton.cs
+++ b/osu.Game.Tests/Visual/TestCaseIconButton.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
@@ -12,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseIconButton : OsuTestCase
{
public TestCaseIconButton()
diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs
index 97116e7746..4af6255b48 100644
--- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs
+++ b/osu.Game.Tests/Visual/TestCaseIntroSequence.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,6 +13,7 @@ using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseIntroSequence : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs
index 57bb36d144..e39b9f6683 100644
--- a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs
+++ b/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs
@@ -1,10 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseKeyConfiguration : OsuTestCase
{
private readonly KeyBindingOverlay overlay;
diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
index ff1b320b5a..bf73c6899b 100644
--- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
+++ b/osu.Game.Tests/Visual/TestCaseKeyCounter.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Screens.Play;
@@ -8,6 +9,7 @@ using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseKeyCounter : OsuTestCase
{
public TestCaseKeyCounter()
diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs
index f11c37f5b2..8d91a0f0dd 100644
--- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs
@@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Game.Overlays;
using osu.Game.Overlays.MedalSplash;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseMedalOverlay : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs
index 9424a3fee7..2ddc57d7b4 100644
--- a/osu.Game.Tests/Visual/TestCaseMusicController.cs
+++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
@@ -11,6 +12,7 @@ using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseMusicController : OsuTestCase
{
private readonly Bindable beatmapBacking = new Bindable();
diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
index b2d3ac8c4d..2ba57f2bd2 100644
--- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -13,6 +14,7 @@ using osu.Game.Overlays.Notifications;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseNotificationOverlay : OsuTestCase
{
private readonly NotificationOverlay manager;
diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
index 9c6c50858f..6fe8bc5a8a 100644
--- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseOnScreenDisplay : OsuTestCase
{
private FrameworkConfigManager config;
diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
index 9e6776800e..a802db6a10 100644
--- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs
+++ b/osu.Game.Tests/Visual/TestCaseOsuGame.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
@@ -12,6 +13,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseOsuGame : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
index 809de2b8db..5fd8fcc9c3 100644
--- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.MathUtils;
@@ -19,6 +20,7 @@ using osu.Game.Tests.Platform;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCasePlaySongSelect : OsuTestCase
{
private BeatmapManager manager;
@@ -63,12 +65,10 @@ namespace osu.Game.Tests.Visual
var storage = new TestStorage(@"TestCasePlaySongSelect");
// this is by no means clean. should be replacing inside of OsuGameBase somehow.
- var context = new OsuDbContext();
+ IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext());
- OsuDbContext contextFactory() => context;
-
- dependencies.Cache(rulesets = new RulesetStore(contextFactory));
- dependencies.Cache(manager = new BeatmapManager(storage, contextFactory, rulesets, null)
+ dependencies.Cache(rulesets = new RulesetStore(factory));
+ dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null)
{
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
});
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual
{
if (deleteMaps)
{
- manager.DeleteAll();
+ manager.Delete(manager.GetAllUsableBeatmapSets());
game.Beatmap.SetDefault();
}
diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
index 82c0b8f4fd..9cdb3e36e3 100644
--- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
+++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs
@@ -1,7 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Components;
using osu.Game.Tests.Beatmaps;
@@ -9,19 +12,31 @@ using OpenTK;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCasePlaybackControl : OsuTestCase
{
- public TestCasePlaybackControl()
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
+
+ [BackgroundDependencyLoader]
+ private void load()
{
+ var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
+
var playback = new PlaybackControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200,100)
};
+
playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap());
- Add(playback);
+ Child = playback;
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/TestCasePopupDialog.cs
index e3bae3955a..8d830672b7 100644
--- a/osu.Game.Tests/Visual/TestCasePopupDialog.cs
+++ b/osu.Game.Tests/Visual/TestCasePopupDialog.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCasePopupDialog : OsuTestCase
{
public TestCasePopupDialog()
diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/TestCaseRankGraph.cs
index 88631aa982..ad53238e76 100644
--- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs
+++ b/osu.Game.Tests/Visual/TestCaseRankGraph.cs
@@ -9,11 +9,13 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using System.Collections.Generic;
using System;
+using NUnit.Framework;
using osu.Game.Graphics.UserInterface;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseRankGraph : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs
index 237687458d..115ac11919 100644
--- a/osu.Game.Tests/Visual/TestCaseReplay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplay.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual
// We create a dummy RulesetContainer just to get the replay - we don't want to use mods here
// to simulate setting a replay rather than having the replay already set for us
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
- var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, false);
+ var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap, beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo));
// We have the replay
var replay = dummyRulesetContainer.Replay;
diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
index 595a93b194..a1b683b64c 100644
--- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Play.HUD;
@@ -8,6 +9,7 @@ using osu.Game.Screens.Play.PlayerSettings;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseReplaySettingsOverlay : OsuTestCase
{
public TestCaseReplaySettingsOverlay()
diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs
index 012d31e75a..06bdfdb7e1 100644
--- a/osu.Game.Tests/Visual/TestCaseResults.cs
+++ b/osu.Game.Tests/Visual/TestCaseResults.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Scoring;
@@ -11,6 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseResults : OsuTestCase
{
private BeatmapManager beatmaps;
diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
index 8c4aa02a68..c45312392f 100644
--- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
+++ b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
@@ -11,6 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseRoomInspector : OsuTestCase
{
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
index a8dc96ad72..e657035355 100644
--- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
+++ b/osu.Game.Tests/Visual/TestCaseScoreCounter.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
@@ -10,6 +11,7 @@ using OpenTK;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseScoreCounter : OsuTestCase
{
public TestCaseScoreCounter()
diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
index 21d967c3e3..745ae9ad9d 100644
--- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using OpenTK;
using osu.Framework.Graphics;
@@ -16,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseScrollingHitObjects : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) };
@@ -138,12 +140,12 @@ namespace osu.Game.Tests.Visual
{
Origin = Anchor.Centre;
- Add(new Box
+ InternalChild = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both
- });
+ };
switch (direction)
{
@@ -173,7 +175,7 @@ namespace osu.Game.Tests.Visual
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
- Add(new Box { Size = new Vector2(75) });
+ InternalChild = new Box { Size = new Vector2(75) };
}
protected override void UpdateState(ArmedState state)
diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs
index 923ae540db..3f42f2e863 100644
--- a/osu.Game.Tests/Visual/TestCaseSettings.cs
+++ b/osu.Game.Tests/Visual/TestCaseSettings.cs
@@ -1,12 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseSettings : OsuTestCase
{
private readonly SettingsOverlay settings;
diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs
index 3fd66f8be3..df94d5147f 100644
--- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs
+++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs
@@ -1,17 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseSkipButton : OsuTestCase
{
protected override void LoadComplete()
{
base.LoadComplete();
- Add(new SkipButton(Clock.CurrentTime + 5000));
+ Add(new SkipOverlay(Clock.CurrentTime + 5000));
}
}
}
diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/TestCaseSocial.cs
index d3ff18b37f..4003d834d5 100644
--- a/osu.Game.Tests/Visual/TestCaseSocial.cs
+++ b/osu.Game.Tests/Visual/TestCaseSocial.cs
@@ -3,12 +3,14 @@
using System;
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Game.Overlays;
using osu.Game.Overlays.Social;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseSocial : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs
index 2320e8d8db..857fd6c902 100644
--- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs
+++ b/osu.Game.Tests/Visual/TestCaseSongProgress.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
@@ -10,6 +11,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseSongProgress : OsuTestCase
{
private readonly SongProgress progress;
diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs
index 089733c57e..d34a0e0e5f 100644
--- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs
+++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
@@ -14,6 +15,7 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseStoryboard : OsuTestCase
{
private readonly Bindable beatmapBacking = new Bindable();
diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs
index 830dea406a..bf7609ff8d 100644
--- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs
+++ b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -10,6 +11,7 @@ using OpenTK;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseTextAwesome : OsuTestCase
{
public TestCaseTextAwesome()
diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/TestCaseToolbar.cs
index b596c4d5e0..94e45fe0c2 100644
--- a/osu.Game.Tests/Visual/TestCaseToolbar.cs
+++ b/osu.Game.Tests/Visual/TestCaseToolbar.cs
@@ -4,11 +4,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Toolbar;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseToolbar : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs
index b18edf0ccb..ed377dc160 100644
--- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs
+++ b/osu.Game.Tests/Visual/TestCaseUserPanel.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Users;
@@ -8,6 +9,7 @@ using OpenTK;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseUserPanel : OsuTestCase
{
public TestCaseUserPanel()
diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
index 8acc8d1b5b..1fc6c6f224 100644
--- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs
+++ b/osu.Game.Tests/Visual/TestCaseUserProfile.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using NUnit.Framework;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
@@ -11,6 +12,7 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseUserProfile : OsuTestCase
{
private readonly TestUserProfileOverlay profile;
@@ -56,6 +58,12 @@ namespace osu.Game.Tests.Visual
checkSupporterTag(false);
+ AddStep("Show null dummy", () => profile.ShowUser(new User
+ {
+ Username = @"Null",
+ Id = 1,
+ }, false));
+
AddStep("Show ppy", () => profile.ShowUser(new User
{
Username = @"peppy",
diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs
new file mode 100644
index 0000000000..1f7a7e7165
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs
@@ -0,0 +1,161 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Profile.Sections;
+using osu.Game.Overlays.Profile.Sections.Recent;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace osu.Game.Tests.Visual
+{
+ [TestFixture]
+ public class TestCaseUserProfileRecentSection : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(RecentSection),
+ typeof(DrawableRecentActivity),
+ typeof(PaginatedRecentActivityContainer),
+ typeof(MedalIcon)
+ };
+
+ public TestCaseUserProfileRecentSection()
+ {
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = OsuColour.Gray(0.2f)
+ },
+ new ScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ ChildrenEnumerable = createDummyActivities().Select(a => new DrawableRecentActivity(a))
+ },
+ }
+ };
+ }
+
+ private IEnumerable createDummyActivities()
+ {
+ var dummyBeatmap = new RecentActivity.RecentActivityBeatmap
+ {
+ Title = @"Dummy beatmap",
+ Url = "/b/1337",
+ };
+
+ var dummyUser = new RecentActivity.RecentActivityUser
+ {
+ Username = "DummyReborn",
+ Url = "/u/666",
+ PreviousUsername = "Dummy",
+ };
+
+ return new[]
+ {
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.Achievement,
+ Achievement = new RecentActivity.RecentActivityAchievement
+ {
+ Name = @"Feelin' It",
+ Slug = @"all-secret-feelinit",
+ },
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapPlaycount,
+ Count = 1337,
+ Beatmap = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetApprove,
+ Approval = BeatmapApproval.Qualified,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetDelete,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetRevive,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetRevive,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetUpdate,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.BeatmapsetUpload,
+ Beatmapset = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.Rank,
+ Rank = 1,
+ Mode = "osu!",
+ Beatmap = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.RankLost,
+ Mode = "osu!",
+ Beatmap = dummyBeatmap,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.UsernameChange,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.UserSupportAgain,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.UserSupportFirst,
+ },
+ new RecentActivity
+ {
+ User = dummyUser,
+ Type = RecentActivityType.UserSupportGift,
+ },
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/TestCaseUserRanks.cs
index 1926585f07..effc98c381 100644
--- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs
+++ b/osu.Game.Tests/Visual/TestCaseUserRanks.cs
@@ -10,9 +10,11 @@ using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Users;
using System;
using System.Collections.Generic;
+using NUnit.Framework;
namespace osu.Game.Tests.Visual
{
+ [TestFixture]
public class TestCaseUserRanks : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) };
diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs
new file mode 100644
index 0000000000..cfbf7fdb4d
--- /dev/null
+++ b/osu.Game.Tests/Visual/TestCaseVolumePieces.cs
@@ -0,0 +1,30 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// 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.Overlays.Volume;
+using OpenTK.Graphics;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestCaseVolumePieces : OsuTestCase
+ {
+ public override IReadOnlyList RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) };
+
+ protected override void LoadComplete()
+ {
+ VolumeMeter meter;
+ MuteButton mute;
+ Add(meter = new VolumeMeter("MASTER", 125, Color4.Blue));
+ Add(mute = new MuteButton
+ {
+ 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);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs
index dd5420400f..7d4a9d663b 100644
--- a/osu.Game.Tests/Visual/TestCaseWaveform.cs
+++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs
@@ -16,7 +16,7 @@ using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
- [Ignore("CI regularly hangs on this TestCase...")]
+ [TestFixture]
public class TestCaseWaveform : OsuTestCase
{
private readonly Bindable beatmapBacking = new Bindable();
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index e6f4a0b8d1..2014db6c61 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using osu.Framework.Audio.Sample;
namespace osu.Game.Audio
{
@@ -14,22 +13,10 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap";
- public SampleChannel GetChannel(SampleManager manager, string resourceNamespace = null)
- {
- SampleChannel channel = null;
-
- if (resourceNamespace != null)
- channel = manager.Get($"Gameplay/{resourceNamespace}/{Bank}-{Name}");
-
- // try without namespace as a fallback.
- if (channel == null)
- channel = manager.Get($"Gameplay/{Bank}-{Name}");
-
- if (channel != null)
- channel.Volume.Value = Volume / 100.0;
-
- return channel;
- }
+ ///
+ /// An optional ruleset namespace.
+ ///
+ public string Namespace;
///
/// The bank to load the sample from.
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index 4fd54e4364..60cf93fd91 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
@@ -22,13 +21,6 @@ namespace osu.Game.Beatmaps
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List Breaks = new List();
- public List ComboColors = new List
- {
- new Color4(17, 136, 170, 255),
- new Color4(102, 136, 0, 255),
- new Color4(204, 102, 0, 255),
- new Color4(121, 9, 13, 255)
- };
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
@@ -54,7 +46,6 @@ namespace osu.Game.Beatmaps
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
- ComboColors = original?.ComboColors ?? ComboColors;
HitObjects = original?.HitObjects ?? HitObjects;
if (original == null && Metadata == null)
@@ -85,9 +76,13 @@ namespace osu.Game.Beatmaps
/// Constructs a new beatmap.
///
/// The original beatmap to use the parameters of.
- public Beatmap(Beatmap original = null)
+ public Beatmap(Beatmap original)
: base(original)
{
}
+
+ public Beatmap()
+ {
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs
index 20de4e9680..2003b845d9 100644
--- a/osu.Game/Beatmaps/BeatmapConverter.cs
+++ b/osu.Game/Beatmaps/BeatmapConverter.cs
@@ -12,8 +12,16 @@ namespace osu.Game.Beatmaps
/// Converts a Beatmap for another mode.
///
/// The type of HitObject stored in the Beatmap.
- public abstract class BeatmapConverter where T : HitObject
+ public abstract class BeatmapConverter : IBeatmapConverter
+ where T : HitObject
{
+ private event Action> ObjectConverted;
+ event Action> IBeatmapConverter.ObjectConverted
+ {
+ add => ObjectConverted += value;
+ remove => ObjectConverted -= value;
+ }
+
///
/// Checks if a Beatmap can be converted using this Beatmap Converter.
///
@@ -32,6 +40,8 @@ namespace osu.Game.Beatmaps
return ConvertBeatmap(new Beatmap(original));
}
+ void IBeatmapConverter.Convert(Beatmap original) => Convert(original);
+
///
/// Performs the conversion of a Beatmap using this Beatmap Converter.
///
@@ -40,9 +50,13 @@ namespace osu.Game.Beatmaps
protected virtual Beatmap ConvertBeatmap(Beatmap original)
{
var beatmap = CreateBeatmap();
+
+ // todo: this *must* share logic (or directly use) Beatmap's constructor.
+ // right now this isn't easily possible due to generic entanglement.
beatmap.BeatmapInfo = original.BeatmapInfo;
beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
+ beatmap.Breaks = original.Breaks;
return beatmap;
}
@@ -63,8 +77,11 @@ namespace osu.Game.Beatmaps
yield break;
}
+ var converted = ConvertHitObject(original, beatmap).ToList();
+ ObjectConverted?.Invoke(original, converted);
+
// Convert the hit object
- foreach (var obj in ConvertHitObject(original, beatmap))
+ foreach (var obj in converted)
{
if (obj == null)
continue;
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 3bfa70711b..38b84b4b03 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -20,9 +20,17 @@ namespace osu.Game.Beatmaps
public float DrainRate { get; set; } = DEFAULT_DIFFICULTY;
public float CircleSize { get; set; } = DEFAULT_DIFFICULTY;
public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY;
- public float ApproachRate { get; set; } = DEFAULT_DIFFICULTY;
- public float SliderMultiplier { get; set; } = 1;
- public float SliderTickRate { get; set; } = 1;
+
+ private float? approachRate;
+
+ public float ApproachRate
+ {
+ get => approachRate ?? OverallDifficulty;
+ set => approachRate = value;
+ }
+
+ public double SliderMultiplier { get; set; } = 1;
+ public double SliderTickRate { get; set; } = 1;
///
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index a27c3a2a88..46fef78c4e 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -8,19 +8,14 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
-using osu.Framework.Audio.Track;
+using osu.Framework.Audio;
using osu.Framework.Extensions;
-using osu.Framework.Graphics.Textures;
-using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps.Formats;
-using osu.Game.Beatmaps.IO;
using osu.Game.Database;
using osu.Game.Graphics;
-using osu.Game.Graphics.Textures;
-using osu.Game.IO;
-using osu.Game.IPC;
+using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Notifications;
@@ -33,23 +28,13 @@ namespace osu.Game.Beatmaps
///
/// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps.
///
- public class BeatmapManager
+ public partial class BeatmapManager : ArchiveModelManager
{
- ///
- /// Fired when a new becomes available in the database.
- ///
- public event Action BeatmapSetAdded;
-
///
/// Fired when a single difficulty has been hidden.
///
public event Action BeatmapHidden;
- ///
- /// Fired when a is removed from the database.
- ///
- public event Action BeatmapSetRemoved;
-
///
/// Fired when a single difficulty has been restored.
///
@@ -65,21 +50,7 @@ namespace osu.Game.Beatmaps
///
public WorkingBeatmap DefaultBeatmap { private get; set; }
- private readonly Storage storage;
-
- private BeatmapStore createBeatmapStore(Func context)
- {
- var store = new BeatmapStore(context);
- store.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
- store.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
- store.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
- store.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
- return store;
- }
-
- private readonly Func createContext;
-
- private readonly FileStore files;
+ public override string[] HandledExtensions => new[] { ".osz" };
private readonly RulesetStore rulesets;
@@ -87,161 +58,64 @@ namespace osu.Game.Beatmaps
private readonly APIAccess api;
+ private readonly AudioManager audioManager;
+
private readonly List currentDownloads = new List();
- // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
- private BeatmapIPCChannel ipc;
-
- ///
- /// Set an endpoint for notifications to be posted to.
- ///
- public Action PostNotification { private get; set; }
-
///
/// Set a storage with access to an osu-stable install for import purposes.
///
public Func GetStableStorage { private get; set; }
- private void refreshImportContext()
+ public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
+ : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
{
- lock (importContextLock)
- {
- importContext?.Value?.Dispose();
+ beatmaps = (BeatmapStore)ModelStore;
+ beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
+ beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
- importContext = new Lazy(() =>
- {
- var c = createContext();
- c.Database.AutoTransactionsEnabled = false;
- return c;
- });
- }
- }
-
- public BeatmapManager(Storage storage, Func context, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
- {
- createContext = context;
-
- refreshImportContext();
-
- beatmaps = createBeatmapStore(context);
- files = new FileStore(context, storage);
-
- this.storage = files.Storage;
this.rulesets = rulesets;
this.api = api;
-
- if (importHost != null)
- ipc = new BeatmapIPCChannel(importHost, this);
-
- beatmaps.Cleanup();
+ this.audioManager = audioManager;
}
- ///
- /// Import one or more from filesystem .
- /// This will post a notification tracking import progress.
- ///
- /// One or more beatmap locations on disk.
- public void Import(params string[] paths)
+ protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
{
- var notification = new ProgressNotification
+ model.Beatmaps = createBeatmapDifficulties(archive);
+
+ // remove metadata from difficulties where it matches the set
+ foreach (BeatmapInfo b in model.Beatmaps)
+ if (model.Metadata.Equals(b.Metadata))
+ b.Metadata = null;
+ }
+
+ protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model)
+ {
+ // check if this beatmap has already been imported and exit early if so
+ var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash);
+ if (existingHashMatch != null)
{
- Text = "Beatmap import is initialising...",
- CompletionText = "Import successful!",
- Progress = 0,
- State = ProgressNotificationState.Active,
- };
+ Undelete(existingHashMatch);
+ return existingHashMatch;
+ }
- PostNotification?.Invoke(notification);
-
- int i = 0;
- foreach (string path in paths)
+ // check if a set already exists with the same online id
+ if (model.OnlineBeatmapSetID != null)
{
- if (notification.State == ProgressNotificationState.Cancelled)
- // user requested abort
- return;
-
- try
+ var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID);
+ if (existingOnlineId != null)
{
- notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}";
- using (ArchiveReader reader = getReaderFrom(path))
- Import(reader);
-
- notification.Progress = (float)++i / paths.Length;
-
- // We may or may not want to delete the file depending on where it is stored.
- // e.g. reconstructing/repairing database with beatmaps from default storage.
- // Also, not always a single file, i.e. for LegacyFilesystemReader
- // TODO: Add a check to prevent files from storage to be deleted.
- try
- {
- if (File.Exists(path))
- File.Delete(path);
- }
- catch (Exception e)
- {
- Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})");
- }
- }
- catch (Exception e)
- {
- e = e.InnerException ?? e;
- Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})");
- refreshImportContext();
+ Delete(existingOnlineId);
+ beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
}
}
- notification.State = ProgressNotificationState.Completed;
- }
-
- private readonly object importContextLock = new object();
-
- private Lazy importContext;
-
- ///
- /// Import a beatmap from an .
- ///
- /// The beatmap to be imported.
- public BeatmapSetInfo Import(ArchiveReader archiveReader)
- {
- // let's only allow one concurrent import at a time for now.
- lock (importContextLock)
- {
- var context = importContext.Value;
-
- using (var transaction = context.BeginTransaction())
- {
- // create local stores so we can isolate and thread safely, and share a context/transaction.
- var iFiles = new FileStore(() => context, storage);
- var iBeatmaps = createBeatmapStore(() => context);
-
- BeatmapSetInfo set = importToStorage(iFiles, iBeatmaps, archiveReader);
-
- if (set.ID == 0)
- {
- iBeatmaps.Add(set);
- context.SaveChanges();
- }
-
- context.SaveChanges(transaction);
- return set;
- }
- }
- }
-
- ///
- /// Import a beatmap from a .
- ///
- /// The beatmap to be imported.
- public void Import(BeatmapSetInfo beatmapSetInfo)
- {
- // If we have an ID then we already exist in the database.
- if (beatmapSetInfo.ID != 0) return;
-
- createBeatmapStore(createContext).Add(beatmapSetInfo);
+ return null;
}
///
/// Downloads a beatmap.
+ /// This will post notifications tracking progress.
///
/// The to be downloaded.
/// Whether the beatmap should be downloaded without video. Defaults to false.
@@ -283,7 +157,7 @@ namespace osu.Game.Beatmaps
{
// This gets scheduled back to the update thread, but we want the import to run in the background.
using (var stream = new MemoryStream(data))
- using (var archive = new OszArchiveReader(stream))
+ using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString()))
Import(archive);
downloadNotification.State = ProgressNotificationState.Completed;
@@ -425,21 +299,6 @@ namespace osu.Game.Beatmaps
/// The beatmap difficulty to restore.
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
- ///
- /// Returns a to a usable state if it has previously been deleted but not yet purged.
- /// Is a no-op for already usable beatmaps.
- ///
- /// The store to restore beatmaps from.
- /// The store to restore beatmap files from.
- /// The beatmap to restore.
- private void undelete(BeatmapStore beatmaps, FileStore files, BeatmapSetInfo beatmapSet)
- {
- if (!beatmaps.Undelete(beatmapSet)) return;
-
- if (!beatmapSet.Protected)
- files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
- }
-
///
/// Retrieve a instance for the provided
///
@@ -454,7 +313,7 @@ namespace osu.Game.Beatmaps
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
- WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(files.Store, beatmapInfo);
+ WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager);
previous?.TransferTo(working);
@@ -466,21 +325,20 @@ namespace osu.Game.Beatmaps
///
/// The query.
/// The first result for the provided query, or null if no results were found.
- public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.BeatmapSets.AsNoTracking().FirstOrDefault(query);
+ public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query);
///
- /// Refresh an existing instance of a from the store.
+ /// Returns a list of all usable s.
///
- /// A stale instance.
- /// A fresh instance.
- public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
+ /// A list of available .
+ public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList();
///
/// Perform a lookup query on available s.
///
/// The query.
/// Results from the provided query.
- public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.BeatmapSets.AsNoTracking().Where(query);
+ public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query);
///
/// Perform a lookup query on available s.
@@ -497,220 +355,8 @@ namespace osu.Game.Beatmaps
public IEnumerable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query);
///
- /// Creates an from a valid storage path.
+ /// Denotes whether an osu-stable installation is present to perform automated imports from.
///
- /// A file or folder path resolving the beatmap content.
- /// A reader giving access to the beatmap's content.
- private ArchiveReader getReaderFrom(string path)
- {
- if (ZipUtils.IsZipArchive(path))
- // ReSharper disable once InconsistentlySynchronizedField
- return new OszArchiveReader(storage.GetStream(path));
- return new LegacyFilesystemReader(path);
- }
-
- ///
- /// Import a beamap into our local storage.
- /// If the beatmap is already imported, the existing instance will be returned.
- ///
- /// The store to import beatmap files to.
- /// The store to import beatmaps to.
- /// The beatmap archive to be read.
- /// The imported beatmap, or an existing instance if it is already present.
- private BeatmapSetInfo importToStorage(FileStore files, BeatmapStore beatmaps, ArchiveReader reader)
- {
- // let's make sure there are actually .osu files to import.
- string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
- if (string.IsNullOrEmpty(mapName))
- throw new InvalidOperationException("No beatmap files found in the map folder.");
-
- // for now, concatenate all .osu files in the set to create a unique hash.
- MemoryStream hashable = new MemoryStream();
- foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu")))
- using (Stream s = reader.GetStream(file))
- s.CopyTo(hashable);
-
- var hash = hashable.ComputeSHA2Hash();
-
- // check if this beatmap has already been imported and exit early if so.
- var beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == hash);
-
- if (beatmapSet != null)
- {
- undelete(beatmaps, files, beatmapSet);
-
- // ensure all files are present and accessible
- foreach (var f in beatmapSet.Files)
- {
- if (!storage.Exists(f.FileInfo.StoragePath))
- using (Stream s = reader.GetStream(f.Filename))
- files.Add(s, false);
- }
-
- // todo: delete any files which shouldn't exist any more.
-
- return beatmapSet;
- }
-
- List fileInfos = new List();
-
- // import files to manager
- foreach (string file in reader.Filenames)
- using (Stream s = reader.GetStream(file))
- fileInfos.Add(new BeatmapSetFileInfo
- {
- Filename = file,
- FileInfo = files.Add(s)
- });
-
- BeatmapMetadata metadata;
-
- using (var stream = new StreamReader(reader.GetStream(mapName)))
- metadata = Decoder.GetDecoder(stream).DecodeBeatmap(stream).Metadata;
-
- // check if a set already exists with the same online id.
- if (metadata.OnlineBeatmapSetID != null)
- beatmapSet = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == metadata.OnlineBeatmapSetID);
-
- if (beatmapSet == null)
- beatmapSet = new BeatmapSetInfo
- {
- OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
- Beatmaps = new List(),
- Hash = hash,
- Files = fileInfos,
- Metadata = metadata
- };
-
- var mapNames = reader.Filenames.Where(f => f.EndsWith(".osu"));
-
- foreach (var name in mapNames)
- {
- using (var raw = reader.GetStream(name))
- using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
- using (var sr = new StreamReader(ms))
- {
- raw.CopyTo(ms);
- ms.Position = 0;
-
- var decoder = Decoder.GetDecoder(sr);
- Beatmap beatmap = decoder.DecodeBeatmap(sr);
-
- beatmap.BeatmapInfo.Path = name;
- beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
- beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
-
- var existing = beatmaps.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.BeatmapInfo.Hash || beatmap.BeatmapInfo.OnlineBeatmapID != null && b.OnlineBeatmapID == beatmap.BeatmapInfo.OnlineBeatmapID);
-
- if (existing == null)
- {
- // Exclude beatmap-metadata if it's equal to beatmapset-metadata
- if (metadata.Equals(beatmap.Metadata))
- beatmap.BeatmapInfo.Metadata = null;
-
- RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
-
- // TODO: this should be done in a better place once we actually need to dynamically update it.
- beatmap.BeatmapInfo.Ruleset = ruleset;
- beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
-
- beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
- }
- }
- }
-
- return beatmapSet;
- }
-
- ///
- /// Returns a list of all usable s.
- ///
- /// A list of available .
- public List GetAllUsableBeatmapSets()
- {
- return beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList();
- }
-
- protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
- {
- private readonly IResourceStore store;
-
- public BeatmapManagerWorkingBeatmap(IResourceStore store, BeatmapInfo beatmapInfo)
- : base(beatmapInfo)
- {
- this.store = store;
- }
-
- protected override Beatmap GetBeatmap()
- {
- try
- {
- using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
- {
- Decoder decoder = Decoder.GetDecoder(stream);
- return decoder.DecodeBeatmap(stream);
- }
- }
- catch
- {
- return null;
- }
- }
-
- private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
-
- protected override Texture GetBackground()
- {
- if (Metadata?.BackgroundFile == null)
- return null;
-
- try
- {
- return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile));
- }
- catch
- {
- return null;
- }
- }
-
- protected override Track GetTrack()
- {
- try
- {
- var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
- return trackData == null ? null : new TrackBass(trackData);
- }
- catch
- {
- return new TrackVirtual();
- }
- }
-
- protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
-
- protected override Storyboard GetStoryboard()
- {
- try
- {
- using (var beatmap = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
- {
- Decoder decoder = Decoder.GetDecoder(beatmap);
-
- if (BeatmapSetInfo?.StoryboardFile == null)
- return decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap);
-
- using (var storyboard = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
- return decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap, storyboard);
- }
- }
- catch
- {
- return new Storyboard();
- }
- }
- }
-
public bool StableInstallationAvailable => GetStableStorage?.Invoke() != null;
///
@@ -729,35 +375,73 @@ namespace osu.Game.Beatmaps
await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning);
}
- public void DeleteAll()
+ ///
+ /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content.
+ ///
+ private string computeBeatmapSetHash(ArchiveReader reader)
{
- var maps = GetAllUsableBeatmapSets();
+ // for now, concatenate all .osu files in the set to create a unique hash.
+ MemoryStream hashable = new MemoryStream();
+ foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu")))
+ using (Stream s = reader.GetStream(file))
+ s.CopyTo(hashable);
- if (maps.Count == 0) return;
+ return hashable.ComputeSHA2Hash();
+ }
- var notification = new ProgressNotification
+ protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
+ {
+ // let's make sure there are actually .osu files to import.
+ string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
+ if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder.");
+
+ BeatmapMetadata metadata;
+ using (var stream = new StreamReader(reader.GetStream(mapName)))
+ metadata = Decoder.GetDecoder(stream).Decode(stream).Metadata;
+
+ return new BeatmapSetInfo
{
- Progress = 0,
- CompletionText = "Deleted all beatmaps!",
- State = ProgressNotificationState.Active,
+ OnlineBeatmapSetID = metadata.OnlineBeatmapSetID,
+ Beatmaps = new List(),
+ Hash = computeBeatmapSetHash(reader),
+ Metadata = metadata
};
+ }
- PostNotification?.Invoke(notification);
+ ///
+ /// Create all required s for the provided archive.
+ ///
+ private List createBeatmapDifficulties(ArchiveReader reader)
+ {
+ var beatmapInfos = new List();
- int i = 0;
-
- foreach (var b in maps)
+ foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu")))
{
- if (notification.State == ProgressNotificationState.Cancelled)
- // user requested abort
- return;
+ using (var raw = reader.GetStream(name))
+ using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
+ using (var sr = new StreamReader(ms))
+ {
+ raw.CopyTo(ms);
+ ms.Position = 0;
- notification.Text = $"Deleting ({i} of {maps.Count})";
- notification.Progress = (float)++i / maps.Count;
- Delete(b);
+ var decoder = Decoder.GetDecoder(sr);
+ Beatmap beatmap = decoder.Decode(sr);
+
+ beatmap.BeatmapInfo.Path = name;
+ beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
+ beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
+
+ RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
+
+ // TODO: this should be done in a better place once we actually need to dynamically update it.
+ beatmap.BeatmapInfo.Ruleset = ruleset;
+ beatmap.BeatmapInfo.StarDifficulty = ruleset?.CreateInstance()?.CreateDifficultyCalculator(beatmap).Calculate() ?? 0;
+
+ beatmapInfos.Add(beatmap.BeatmapInfo);
+ }
}
- notification.State = ProgressNotificationState.Completed;
+ return beatmapInfos;
}
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
new file mode 100644
index 0000000000..5874314f75
--- /dev/null
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -0,0 +1,125 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.IO;
+using System.Linq;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Framework.Logging;
+using osu.Game.Beatmaps.Formats;
+using osu.Game.Graphics.Textures;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+
+namespace osu.Game.Beatmaps
+{
+ public partial class BeatmapManager
+ {
+ protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
+ {
+ private readonly IResourceStore store;
+ private readonly AudioManager audioManager;
+
+ public BeatmapManagerWorkingBeatmap(IResourceStore store, BeatmapInfo beatmapInfo, AudioManager audioManager)
+ : base(beatmapInfo)
+ {
+ this.store = store;
+ this.audioManager = audioManager;
+ }
+
+ protected override Beatmap GetBeatmap()
+ {
+ try
+ {
+ using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
+ return Decoder.GetDecoder(stream).Decode(stream);
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
+
+ protected override Texture GetBackground()
+ {
+ if (Metadata?.BackgroundFile == null)
+ return null;
+
+ try
+ {
+ return new LargeTextureStore(new RawTextureLoaderStore(store)).Get(getPathForFile(Metadata.BackgroundFile));
+ }
+ catch
+ {
+ return null;
+ }
+ }
+
+ protected override Track GetTrack()
+ {
+ try
+ {
+ var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
+ return trackData == null ? null : new TrackBass(trackData);
+ }
+ catch
+ {
+ return new TrackVirtual();
+ }
+ }
+
+ protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
+
+ protected override Storyboard GetStoryboard()
+ {
+ Storyboard storyboard;
+ try
+ {
+ using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
+ {
+ var decoder = Decoder.GetDecoder(stream);
+
+ // todo: support loading from both set-wide storyboard *and* beatmap specific.
+ if (BeatmapSetInfo?.StoryboardFile == null)
+ storyboard = decoder.Decode(stream);
+ else
+ {
+ using (var secondaryStream = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile))))
+ storyboard = decoder.Decode(stream, secondaryStream);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Storyboard failed to load");
+ storyboard = new Storyboard();
+ }
+
+ storyboard.BeatmapInfo = BeatmapInfo;
+
+ return storyboard;
+ }
+
+ protected override Skin GetSkin()
+ {
+ Skin skin;
+ try
+ {
+ skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Skin failed to load");
+ skin = new DefaultSkin();
+ }
+
+ return skin;
+ }
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs
index 9b528699ef..f2cc419043 100644
--- a/osu.Game/Beatmaps/BeatmapProcessor.cs
+++ b/osu.Game/Beatmaps/BeatmapProcessor.cs
@@ -1,7 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System.Linq;
using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
@@ -19,6 +21,29 @@ namespace osu.Game.Beatmaps
///
///
/// The Beatmap to process.
- public virtual void PostProcess(Beatmap beatmap) { }
+ public virtual void PostProcess(Beatmap beatmap)
+ {
+ IHasComboInformation lastObj = null;
+
+ foreach (var obj in beatmap.HitObjects.OfType())
+ {
+ if (obj.NewCombo)
+ {
+ obj.IndexInCurrentCombo = 0;
+ if (lastObj != null)
+ {
+ lastObj.LastInCombo = true;
+ obj.ComboIndex = lastObj.ComboIndex + 1;
+ }
+ }
+ else if (lastObj != null)
+ {
+ obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1;
+ obj.ComboIndex = lastObj.ComboIndex;
+ }
+
+ lastObj = obj;
+ }
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
index ae4a6772a2..e88af6ed30 100644
--- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
@@ -3,11 +3,12 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using osu.Game.Database;
using osu.Game.IO;
namespace osu.Game.Beatmaps
{
- public class BeatmapSetFileInfo
+ public class BeatmapSetFileInfo : INamedFileInfo
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index 982e41c92c..1736e3fa90 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -8,7 +8,7 @@ using osu.Game.Database;
namespace osu.Game.Beatmaps
{
- public class BeatmapSetInfo : IHasPrimaryKey
+ public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index df71c5c0d0..93ad1badd2 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
+using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using osu.Game.Database;
@@ -11,83 +12,16 @@ namespace osu.Game.Beatmaps
///
/// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing
///
- public class BeatmapStore : DatabaseBackedStore
+ public class BeatmapStore : MutableDatabaseBackedStore
{
- public event Action BeatmapSetAdded;
- public event Action BeatmapSetRemoved;
-
public event Action BeatmapHidden;
public event Action BeatmapRestored;
- public BeatmapStore(Func factory)
+ public BeatmapStore(IDatabaseContextFactory factory)
: base(factory)
{
}
- ///
- /// Add a to the database.
- ///
- /// The beatmap to add.
- public void Add(BeatmapSetInfo beatmapSet)
- {
- var context = GetContext();
-
- foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null))
- {
- // If we detect a new metadata object it'll be attached to the current context so it can be reused
- // to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local)
- // of the corresponding table (.Set()) for matching entries to our criteria.
- var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata));
- if (contextMetadata != null)
- beatmap.Metadata = contextMetadata;
- else
- context.BeatmapMetadata.Attach(beatmap.Metadata);
- }
-
- context.BeatmapSetInfo.Attach(beatmapSet);
- context.SaveChanges();
-
- BeatmapSetAdded?.Invoke(beatmapSet);
- }
-
- ///
- /// Delete a from the database.
- ///
- /// The beatmap to delete.
- /// Whether the beatmap's was changed.
- public bool Delete(BeatmapSetInfo beatmapSet)
- {
- var context = GetContext();
-
- Refresh(ref beatmapSet, BeatmapSets);
-
- if (beatmapSet.DeletePending) return false;
- beatmapSet.DeletePending = true;
- context.SaveChanges();
-
- BeatmapSetRemoved?.Invoke(beatmapSet);
- return true;
- }
-
- ///
- /// Restore a previously deleted .
- ///
- /// The beatmap to restore.
- /// Whether the beatmap's was changed.
- public bool Undelete(BeatmapSetInfo beatmapSet)
- {
- var context = GetContext();
-
- Refresh(ref beatmapSet, BeatmapSets);
-
- if (!beatmapSet.DeletePending) return false;
- beatmapSet.DeletePending = false;
- context.SaveChanges();
-
- BeatmapSetAdded?.Invoke(beatmapSet);
- return true;
- }
-
///
/// Hide a in the database.
///
@@ -95,13 +29,13 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Hide(BeatmapInfo beatmap)
{
- var context = GetContext();
+ using (ContextFactory.GetForWrite())
+ {
+ Refresh(ref beatmap, Beatmaps);
- Refresh(ref beatmap, Beatmaps);
-
- if (beatmap.Hidden) return false;
- beatmap.Hidden = true;
- context.SaveChanges();
+ if (beatmap.Hidden) return false;
+ beatmap.Hidden = true;
+ }
BeatmapHidden?.Invoke(beatmap);
return true;
@@ -114,51 +48,50 @@ namespace osu.Game.Beatmaps
/// Whether the beatmap's was changed.
public bool Restore(BeatmapInfo beatmap)
{
- var context = GetContext();
+ using (ContextFactory.GetForWrite())
+ {
+ Refresh(ref beatmap, Beatmaps);
- Refresh(ref beatmap, Beatmaps);
-
- if (!beatmap.Hidden) return false;
- beatmap.Hidden = false;
- context.SaveChanges();
+ if (!beatmap.Hidden) return false;
+ beatmap.Hidden = false;
+ }
BeatmapRestored?.Invoke(beatmap);
return true;
}
- public override void Cleanup()
+ protected override IQueryable AddIncludesForDeletion(IQueryable query) =>
+ base.AddIncludesForDeletion(query)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Metadata);
+
+ protected override IQueryable AddIncludesForConsumption(IQueryable query) =>
+ base.AddIncludesForConsumption(query)
+ .Include(s => s.Metadata)
+ .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
+ .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
+ .Include(s => s.Files).ThenInclude(f => f.FileInfo);
+
+ protected override void Purge(List items, OsuDbContext context)
{
- var context = GetContext();
-
- var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
- .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
- .Include(s => s.Metadata);
-
// metadata is M-N so we can't rely on cascades
- context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata));
- context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
+ context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata));
+ context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null)));
// todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly.
- context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
+ context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty)));
- // cascades down to beatmaps.
- context.BeatmapSetInfo.RemoveRange(purgeable);
- context.SaveChanges();
+ base.Purge(items, context);
}
- public IQueryable BeatmapSets => GetContext().BeatmapSetInfo
- .Include(s => s.Metadata)
- .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
- .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
- .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
- .Include(s => s.Files).ThenInclude(f => f.FileInfo);
-
- public IQueryable Beatmaps => GetContext().BeatmapInfo
- .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
- .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo)
- .Include(b => b.Metadata)
- .Include(b => b.Ruleset)
- .Include(b => b.BaseDifficulty);
+ public IQueryable Beatmaps =>
+ ContextFactory.Get().BeatmapInfo
+ .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
+ .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo)
+ .Include(b => b.Metadata)
+ .Include(b => b.Ruleset)
+ .Include(b => b.BaseDifficulty);
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
index 69027ffd73..2b42553891 100644
--- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs
@@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using OpenTK;
+
namespace osu.Game.Beatmaps.ControlPoints
{
public class DifficultyControlPoint : ControlPoint
@@ -8,6 +10,12 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The speed multiplier at this control point.
///
- public double SpeedMultiplier = 1;
+ public double SpeedMultiplier
+ {
+ get => speedMultiplier;
+ set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10);
+ }
+
+ private double speedMultiplier = 1;
}
}
diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
index 0592ef38c5..0db1f08a90 100644
--- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs
@@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using OpenTK;
using osu.Game.Beatmaps.Timing;
namespace osu.Game.Beatmaps.ControlPoints
@@ -15,6 +16,12 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The beat length at this control point.
///
- public double BeatLength = 1000;
+ public double BeatLength
+ {
+ get => beatLength;
+ set => beatLength = MathHelper.Clamp(value, 6, 60000);
+ }
+
+ private double beatLength = 1000;
}
}
diff --git a/osu.Game/Beatmaps/DifficultyCalculator.cs b/osu.Game/Beatmaps/DifficultyCalculator.cs
index 798268d05f..2bea31c0d3 100644
--- a/osu.Game/Beatmaps/DifficultyCalculator.cs
+++ b/osu.Game/Beatmaps/DifficultyCalculator.cs
@@ -24,9 +24,15 @@ namespace osu.Game.Beatmaps
protected DifficultyCalculator(Beatmap beatmap, Mod[] mods = null)
{
- Beatmap = CreateBeatmapConverter(beatmap).Convert(beatmap);
Mods = mods ?? new Mod[0];
+ var converter = CreateBeatmapConverter(beatmap);
+
+ foreach (var mod in Mods.OfType>())
+ mod.ApplyToBeatmapConverter(converter);
+
+ Beatmap = converter.Convert(beatmap);
+
ApplyMods(Mods);
PreprocessHitObjects();
@@ -41,12 +47,12 @@ namespace osu.Game.Beatmaps
foreach (var mod in Mods.OfType())
mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty);
+ foreach (var h in Beatmap.HitObjects)
+ h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
+
foreach (var mod in mods.OfType>())
foreach (var obj in Beatmap.HitObjects)
mod.ApplyToHitObject(obj);
-
- foreach (var h in Beatmap.HitObjects)
- h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty);
}
protected virtual void PreprocessHitObjects()
diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs
index 1aae52208a..9f10485c5f 100644
--- a/osu.Game/Beatmaps/Formats/Decoder.cs
+++ b/osu.Game/Beatmaps/Formats/Decoder.cs
@@ -4,38 +4,64 @@
using System;
using System.Collections.Generic;
using System.IO;
-using osu.Game.Storyboards;
+using System.Linq;
namespace osu.Game.Beatmaps.Formats
{
+ public abstract class Decoder : Decoder
+ where TOutput : new()
+ {
+ protected virtual TOutput CreateTemplateObject() => new TOutput();
+
+ public TOutput Decode(StreamReader primaryStream, params StreamReader[] otherStreams)
+ {
+ var output = CreateTemplateObject();
+ foreach (StreamReader stream in new[] { primaryStream }.Concat(otherStreams))
+ ParseStreamInto(stream, output);
+ return output;
+ }
+
+ protected abstract void ParseStreamInto(StreamReader stream, TOutput beatmap);
+ }
+
public abstract class Decoder
{
- private static readonly Dictionary> decoders = new Dictionary>();
+ private static readonly Dictionary>> decoders = new Dictionary>>();
static Decoder()
{
- LegacyDecoder.Register();
+ LegacyBeatmapDecoder.Register();
JsonBeatmapDecoder.Register();
+ LegacyStoryboardDecoder.Register();
}
///
/// Retrieves a to parse a .
///
/// A stream pointing to the .
- public static Decoder GetDecoder(StreamReader stream)
+ public static Decoder GetDecoder(StreamReader stream)
+ where T : new()
{
if (stream == null)
throw new ArgumentNullException(nameof(stream));
+ if (!decoders.TryGetValue(typeof(T), out var typedDecoders))
+ throw new IOException(@"Unknown decoder type");
+
string line;
do
- { line = stream.ReadLine()?.Trim(); }
- while (line != null && line.Length == 0);
+ {
+ line = stream.ReadLine()?.Trim();
+ } while (line != null && line.Length == 0);
- if (line == null || !decoders.ContainsKey(line))
+ if (line == null)
throw new IOException(@"Unknown file format");
- return decoders[line](line);
+ var decoder = typedDecoders.Select(d => line.StartsWith(d.Key) ? d.Value : null).FirstOrDefault();
+ if (decoder == null)
+ throw new IOException(@"Unknown file format");
+
+ return (Decoder)decoder.Invoke(line);
}
///
@@ -43,41 +69,12 @@ namespace osu.Game.Beatmaps.Formats
///
/// A string in the file which triggers this decoder to be used.
/// A function which constructs the given .
- protected static void AddDecoder(string magic, Func constructor)
+ protected static void AddDecoder(string magic, Func constructor)
{
- decoders[magic] = constructor;
+ if (!decoders.TryGetValue(typeof(T), out var typedDecoders))
+ decoders.Add(typeof(T), typedDecoders = new Dictionary>());
+
+ typedDecoders[magic] = constructor;
}
-
- ///
- /// Retrieves a to parse a
- ///
- public abstract Decoder GetStoryboardDecoder();
-
- public virtual Beatmap DecodeBeatmap(StreamReader stream)
- {
- var beatmap = new Beatmap
- {
- BeatmapInfo = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata(),
- BaseDifficulty = new BeatmapDifficulty(),
- },
- };
-
- ParseBeatmap(stream, beatmap);
- return beatmap;
- }
-
- protected abstract void ParseBeatmap(StreamReader stream, Beatmap beatmap);
-
- public virtual Storyboard DecodeStoryboard(params StreamReader[] streams)
- {
- var storyboard = new Storyboard();
- foreach (StreamReader stream in streams)
- ParseStoryboard(stream, storyboard);
- return storyboard;
- }
-
- protected abstract void ParseStoryboard(StreamReader stream, Storyboard storyboard);
}
}
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
new file mode 100644
index 0000000000..93c6c18eec
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using OpenTK.Graphics;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public interface IHasComboColours
+ {
+ List ComboColours { get; set; }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs
new file mode 100644
index 0000000000..14614a6728
--- /dev/null
+++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Collections.Generic;
+using OpenTK.Graphics;
+
+namespace osu.Game.Beatmaps.Formats
+{
+ public interface IHasCustomColours
+ {
+ Dictionary CustomColours { get; set; }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
index b0798e5a87..add0f39280 100644
--- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
@@ -3,20 +3,17 @@
using System.IO;
using osu.Game.IO.Serialization;
-using osu.Game.Storyboards;
namespace osu.Game.Beatmaps.Formats
{
- public class JsonBeatmapDecoder : Decoder
+ public class JsonBeatmapDecoder : Decoder
{
public static void Register()
{
- AddDecoder("{", m => new JsonBeatmapDecoder());
+ AddDecoder("{", m => new JsonBeatmapDecoder());
}
- public override Decoder GetStoryboardDecoder() => this;
-
- protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
+ protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap)
{
stream.BaseStream.Position = 0;
stream.DiscardBufferedData();
@@ -26,10 +23,5 @@ namespace osu.Game.Beatmaps.Formats
foreach (var hitObject in beatmap.HitObjects)
hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
}
-
- protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
- {
- // throw new System.NotImplementedException();
- }
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 3847787a4c..74b7d0272e 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -4,89 +4,99 @@
using System;
using System.Globalization;
using System.IO;
-using OpenTK.Graphics;
+using System.Linq;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Framework;
namespace osu.Game.Beatmaps.Formats
{
- public class LegacyBeatmapDecoder : LegacyDecoder
+ public class LegacyBeatmapDecoder : LegacyDecoder
{
+ public const int LATEST_VERSION = 14;
+
private Beatmap beatmap;
- private bool hasCustomColours;
private ConvertHitObjectParser parser;
private LegacySampleBank defaultSampleBank;
private int defaultSampleVolume = 100;
- public LegacyBeatmapDecoder()
+ public static void Register()
{
+ AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last())));
}
- public LegacyBeatmapDecoder(string header)
+ ///
+ /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited.
+ /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck.
+ ///
+ public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0;
+
+ ///
+ /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes.
+ ///
+ public bool ApplyOffsets = true;
+
+ private readonly int offset = UniversalOffset;
+
+ public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version)
{
- BeatmapVersion = int.Parse(header.Substring(17));
+ // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off)
+ offset += FormatVersion < 5 ? 24 : 0;
}
- protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
+ protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap)
{
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
- if (beatmap == null)
- throw new ArgumentNullException(nameof(beatmap));
-
this.beatmap = beatmap;
- this.beatmap.BeatmapInfo.BeatmapVersion = BeatmapVersion;
+ this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion;
- ParseContent(stream);
+ base.ParseStreamInto(stream, beatmap);
+
+ // objects may be out of order *only* if a user has manually edited an .osu file.
+ // unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828).
+ this.beatmap.HitObjects.Sort((x, y) => x.StartTime.CompareTo(y.StartTime));
foreach (var hitObject in this.beatmap.HitObjects)
hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
}
- protected override bool ShouldSkipLine(string line)
- {
- if (base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_"))
- return true;
- return false;
- }
+ protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(" ") || line.StartsWith("_");
- protected override void ProcessSection(Section section, string line)
+ protected override void ParseLine(Beatmap beatmap, Section section, string line)
{
switch (section)
{
case Section.General:
handleGeneral(line);
- break;
+ return;
case Section.Editor:
handleEditor(line);
- break;
+ return;
case Section.Metadata:
handleMetadata(line);
- break;
+ return;
case Section.Difficulty:
handleDifficulty(line);
- break;
+ return;
case Section.Events:
handleEvents(line);
- break;
+ return;
case Section.TimingPoints:
handleTimingPoints(line);
- break;
- case Section.Colours:
- handleColours(line);
- break;
+ return;
case Section.HitObjects:
handleHitObjects(line);
- break;
+ return;
}
+
+ base.ParseLine(beatmap, section, line);
}
private void handleGeneral(string line)
{
- var pair = SplitKeyVal(line, ':');
+ var pair = SplitKeyVal(line);
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
@@ -98,7 +108,7 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value);
break;
case @"PreviewTime":
- metadata.PreviewTime = int.Parse(pair.Value);
+ metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value));
break;
case @"Countdown":
beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1;
@@ -145,7 +155,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleEditor(string line)
{
- var pair = SplitKeyVal(line, ':');
+ var pair = SplitKeyVal(line);
switch (pair.Key)
{
@@ -169,7 +179,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleMetadata(string line)
{
- var pair = SplitKeyVal(line, ':');
+ var pair = SplitKeyVal(line);
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
@@ -210,7 +220,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleDifficulty(string line)
{
- var pair = SplitKeyVal(line, ':');
+ var pair = SplitKeyVal(line);
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
@@ -228,10 +238,10 @@ namespace osu.Game.Beatmaps.Formats
difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderMultiplier":
- difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
case @"SliderTickRate":
- difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
+ difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo);
break;
}
}
@@ -253,8 +263,8 @@ namespace osu.Game.Beatmaps.Formats
case EventType.Break:
var breakEvent = new BreakPeriod
{
- StartTime = double.Parse(split[1], NumberFormatInfo.InvariantInfo),
- EndTime = double.Parse(split[2], NumberFormatInfo.InvariantInfo)
+ StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)),
+ EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo))
};
if (!breakEvent.HasEffect)
@@ -269,7 +279,7 @@ namespace osu.Game.Beatmaps.Formats
{
string[] split = line.Split(',');
- double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
+ double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo));
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
@@ -351,38 +361,6 @@ namespace osu.Game.Beatmaps.Formats
}
}
- private void handleColours(string line)
- {
- var pair = SplitKeyVal(line, ':');
-
- string[] split = pair.Value.Split(',');
-
- if (split.Length != 3)
- throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
-
- byte r, g, b;
- if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
- throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
-
- if (!hasCustomColours)
- {
- beatmap.ComboColors.Clear();
- hasCustomColours = true;
- }
-
- // Note: the combo index specified in the beatmap is discarded
- if (pair.Key.StartsWith(@"Combo"))
- {
- beatmap.ComboColors.Add(new Color4
- {
- R = r / 255f,
- G = g / 255f,
- B = b / 255f,
- A = 1f,
- });
- }
- }
-
private void handleHitObjects(string line)
{
// If the ruleset wasn't specified, assume the osu!standard ruleset.
@@ -392,7 +370,14 @@ namespace osu.Game.Beatmaps.Formats
var obj = parser.Parse(line);
if (obj != null)
+ {
+ obj.StartTime = getOffsetTime(obj.StartTime);
beatmap.HitObjects.Add(obj);
+ }
}
+
+ private int getOffsetTime(int time) => time + (ApplyOffsets ? offset : 0);
+
+ private double getOffsetTime(double time) => time + (ApplyOffsets ? offset : 0);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index e0fc439924..131c010c5c 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -4,47 +4,22 @@
using System;
using System.Collections.Generic;
using System.IO;
-using osu.Game.Beatmaps.Legacy;
-using osu.Game.Storyboards;
+using osu.Framework.Logging;
+using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats
{
- public abstract class LegacyDecoder : Decoder
+ public abstract class LegacyDecoder : Decoder
+ where T : new()
{
- public static void Register()
+ protected readonly int FormatVersion;
+
+ protected LegacyDecoder(int version)
{
- AddDecoder(@"osu file format v14", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v13", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v12", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v11", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v10", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v9", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v8", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v7", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v6", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v5", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v4", m => new LegacyBeatmapDecoder(m));
- AddDecoder(@"osu file format v3", m => new LegacyBeatmapDecoder(m));
- // TODO: differences between versions
+ FormatVersion = version;
}
- protected int BeatmapVersion;
-
- public override Decoder GetStoryboardDecoder() => new LegacyStoryboardDecoder(BeatmapVersion);
-
- public override Beatmap DecodeBeatmap(StreamReader stream) => new LegacyBeatmap(base.DecodeBeatmap(stream));
-
- protected override void ParseBeatmap(StreamReader stream, Beatmap beatmap)
- {
- throw new NotImplementedException();
- }
-
- protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
- {
- throw new NotImplementedException();
- }
-
- protected void ParseContent(StreamReader stream)
+ protected override void ParseStreamInto(StreamReader stream, T beatmap)
{
Section section = Section.None;
@@ -54,34 +29,72 @@ namespace osu.Game.Beatmaps.Formats
if (ShouldSkipLine(line))
continue;
- // It's already set in ParseBeatmap... why do it again?
- //if (line.StartsWith(@"osu file format v"))
- //{
- // Beatmap.BeatmapInfo.BeatmapVersion = int.Parse(line.Substring(17));
- // continue;
- //}
-
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
{
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
- throw new InvalidDataException($@"Unknown osu section {line}");
+ {
+ Logger.Log($"Unknown section \"{line}\" in {beatmap}");
+ section = Section.None;
+ }
+
continue;
}
- ProcessSection(section, line);
+ ParseLine(beatmap, section, line);
}
}
- protected virtual bool ShouldSkipLine(string line)
+ protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//");
+
+ protected virtual void ParseLine(T output, Section section, string line)
{
- if (string.IsNullOrWhiteSpace(line) || line.StartsWith("//"))
- return true;
- return false;
+ switch (section)
+ {
+ case Section.Colours:
+ handleColours(output, line);
+ return;
+ }
}
- protected abstract void ProcessSection(Section section, string line);
+ private bool hasComboColours;
- protected KeyValuePair SplitKeyVal(string line, char separator)
+ private void handleColours(T output, string line)
+ {
+ var pair = SplitKeyVal(line);
+
+ bool isCombo = pair.Key.StartsWith(@"Combo");
+
+ string[] split = pair.Value.Split(',');
+
+ if (split.Length != 3)
+ throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}");
+
+ if (!byte.TryParse(split[0], out var r) || !byte.TryParse(split[1], out var g) || !byte.TryParse(split[2], out var b))
+ throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
+
+ Color4 colour = new Color4(r, g, b, 255);
+
+ if (isCombo)
+ {
+ if (!(output is IHasComboColours tHasComboColours)) return;
+
+ if (!hasComboColours)
+ {
+ // remove default colours.
+ tHasComboColours.ComboColours.Clear();
+ hasComboColours = true;
+ }
+
+ tHasComboColours.ComboColours.Add(colour);
+ }
+ else
+ {
+ if (!(output is IHasCustomColours tHasCustomColours)) return;
+ tHasCustomColours.CustomColours[pair.Key] = colour;
+ }
+ }
+
+ protected KeyValuePair SplitKeyVal(string line, char separator = ':')
{
var split = line.Trim().Split(new[] { separator }, 2);
@@ -104,6 +117,7 @@ namespace osu.Game.Beatmaps.Formats
Colours,
HitObjects,
Variables,
+ Fonts
}
internal enum LegacySampleBank
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index a4ff060c83..85b0f8d42e 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Globalization;
using System.IO;
using OpenTK;
@@ -13,47 +13,46 @@ using osu.Game.Storyboards;
namespace osu.Game.Beatmaps.Formats
{
- public class LegacyStoryboardDecoder : LegacyDecoder
+ public class LegacyStoryboardDecoder : LegacyDecoder
{
- private Storyboard storyboard;
-
private StoryboardSprite storyboardSprite;
private CommandTimelineGroup timelineGroup;
+ private Storyboard storyboard;
+
private readonly Dictionary variables = new Dictionary();
public LegacyStoryboardDecoder()
+ : base(0)
{
}
- public LegacyStoryboardDecoder(int beatmapVersion)
+ public static void Register()
{
- BeatmapVersion = beatmapVersion;
+ // note that this isn't completely correct
+ AddDecoder(@"osu file format v", m => new LegacyStoryboardDecoder());
+ AddDecoder(@"[Events]", m => new LegacyStoryboardDecoder());
}
- protected override void ParseStoryboard(StreamReader stream, Storyboard storyboard)
+ protected override void ParseStreamInto(StreamReader stream, Storyboard storyboard)
{
- if (stream == null)
- throw new ArgumentNullException(nameof(stream));
- if (storyboard == null)
- throw new ArgumentNullException(nameof(storyboard));
-
this.storyboard = storyboard;
-
- ParseContent(stream);
+ base.ParseStreamInto(stream, storyboard);
}
- protected override void ProcessSection(Section section, string line)
+ protected override void ParseLine(Storyboard storyboard, Section section, string line)
{
switch (section)
{
case Section.Events:
handleEvents(line);
- break;
+ return;
case Section.Variables:
handleVariables(line);
- break;
+ return;
}
+
+ base.ParseLine(storyboard, section, line);
}
private void handleEvents(string line)
@@ -80,38 +79,38 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case EventType.Sprite:
- {
- var layer = parseLayer(split[1]);
- var origin = parseOrigin(split[2]);
- var path = cleanFilename(split[3]);
- var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
- var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
- storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
- storyboard.GetLayer(layer).Add(storyboardSprite);
- }
+ {
+ var layer = parseLayer(split[1]);
+ var origin = parseOrigin(split[2]);
+ var path = cleanFilename(split[3]);
+ var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
+ var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
+ storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
+ storyboard.GetLayer(layer).Add(storyboardSprite);
+ }
break;
case EventType.Animation:
- {
- var layer = parseLayer(split[1]);
- var origin = parseOrigin(split[2]);
- var path = cleanFilename(split[3]);
- var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
- var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
- var frameCount = int.Parse(split[6]);
- var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
- var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
- storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
- storyboard.GetLayer(layer).Add(storyboardSprite);
- }
+ {
+ var layer = parseLayer(split[1]);
+ var origin = parseOrigin(split[2]);
+ var path = cleanFilename(split[3]);
+ var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
+ var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
+ var frameCount = int.Parse(split[6]);
+ var frameDelay = double.Parse(split[7], NumberFormatInfo.InvariantInfo);
+ var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever;
+ storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType);
+ storyboard.GetLayer(layer).Add(storyboardSprite);
+ }
break;
case EventType.Sample:
- {
- var time = double.Parse(split[1], CultureInfo.InvariantCulture);
- var layer = parseLayer(split[2]);
- var path = cleanFilename(split[3]);
- var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
- storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
- }
+ {
+ var time = double.Parse(split[1], CultureInfo.InvariantCulture);
+ var layer = parseLayer(split[2]);
+ var path = cleanFilename(split[3]);
+ var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
+ storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume));
+ }
break;
}
}
@@ -124,120 +123,120 @@ namespace osu.Game.Beatmaps.Formats
switch (commandType)
{
case "T":
- {
- var triggerName = split[1];
- var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
- var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
- var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
- timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
- }
+ {
+ var triggerName = split[1];
+ var startTime = split.Length > 2 ? double.Parse(split[2], CultureInfo.InvariantCulture) : double.MinValue;
+ var endTime = split.Length > 3 ? double.Parse(split[3], CultureInfo.InvariantCulture) : double.MaxValue;
+ var groupNumber = split.Length > 4 ? int.Parse(split[4]) : 0;
+ timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber);
+ }
break;
case "L":
- {
- var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
- var loopCount = int.Parse(split[2]);
- timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
- }
+ {
+ var startTime = double.Parse(split[1], CultureInfo.InvariantCulture);
+ var loopCount = int.Parse(split[2]);
+ timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
+ }
break;
default:
+ {
+ if (string.IsNullOrEmpty(split[3]))
+ split[3] = split[2];
+
+ var easing = (Easing)int.Parse(split[1]);
+ var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
+ var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
+
+ switch (commandType)
{
- if (string.IsNullOrEmpty(split[3]))
- split[3] = split[2];
-
- var easing = (Easing)int.Parse(split[1]);
- var startTime = double.Parse(split[2], CultureInfo.InvariantCulture);
- var endTime = double.Parse(split[3], CultureInfo.InvariantCulture);
-
- switch (commandType)
+ case "F":
{
- case "F":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "S":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
- }
- break;
- case "V":
- {
- var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
- var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
- var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
- timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
- }
- break;
- case "R":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
- }
- break;
- case "M":
- {
- var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
- var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
- var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
- timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
- timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
- }
- break;
- case "MX":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "MY":
- {
- var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
- var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
- timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
- }
- break;
- case "C":
- {
- var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
- var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
- var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
- var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
- var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
- var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
- timelineGroup?.Colour.Add(easing, startTime, endTime,
- new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
- new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
- }
- break;
- case "P":
- {
- var type = split[4];
- switch (type)
- {
- case "A":
- timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit);
- break;
- case "H":
- timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
- break;
- case "V":
- timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
- break;
- }
- }
- break;
- default:
- throw new InvalidDataException($@"Unknown command type: {commandType}");
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue);
}
+ break;
+ case "S":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue));
+ }
+ break;
+ case "V":
+ {
+ var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
+ var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
+ timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY));
+ }
+ break;
+ case "R":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue));
+ }
+ break;
+ case "M":
+ {
+ var startX = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startY = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var endX = split.Length > 6 ? float.Parse(split[6], CultureInfo.InvariantCulture) : startX;
+ var endY = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startY;
+ timelineGroup?.X.Add(easing, startTime, endTime, startX, endX);
+ timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY);
+ }
+ break;
+ case "MX":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue);
+ }
+ break;
+ case "MY":
+ {
+ var startValue = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var endValue = split.Length > 5 ? float.Parse(split[5], CultureInfo.InvariantCulture) : startValue;
+ timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue);
+ }
+ break;
+ case "C":
+ {
+ var startRed = float.Parse(split[4], CultureInfo.InvariantCulture);
+ var startGreen = float.Parse(split[5], CultureInfo.InvariantCulture);
+ var startBlue = float.Parse(split[6], CultureInfo.InvariantCulture);
+ var endRed = split.Length > 7 ? float.Parse(split[7], CultureInfo.InvariantCulture) : startRed;
+ var endGreen = split.Length > 8 ? float.Parse(split[8], CultureInfo.InvariantCulture) : startGreen;
+ var endBlue = split.Length > 9 ? float.Parse(split[9], CultureInfo.InvariantCulture) : startBlue;
+ timelineGroup?.Colour.Add(easing, startTime, endTime,
+ new Color4(startRed / 255f, startGreen / 255f, startBlue / 255f, 1),
+ new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1));
+ }
+ break;
+ case "P":
+ {
+ var type = split[4];
+ switch (type)
+ {
+ case "A":
+ timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit);
+ break;
+ case "H":
+ timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime);
+ break;
+ case "V":
+ timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime);
+ break;
+ }
+ }
+ break;
+ default:
+ throw new InvalidDataException($@"Unknown command type: {commandType}");
}
+ }
break;
}
}
@@ -269,6 +268,7 @@ namespace osu.Game.Beatmaps.Formats
case LegacyOrigins.BottomRight:
return Anchor.BottomRight;
}
+
throw new InvalidDataException($@"Unknown origin: {value}");
}
diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs
new file mode 100644
index 0000000000..ebd900b97e
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapConverter.cs
@@ -0,0 +1,25 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Beatmaps
+{
+ public interface IBeatmapConverter
+ {
+ ///
+ /// Invoked when a has been converted.
+ /// The first argument contains the that was converted.
+ /// The second argument contains the s that were output from the conversion process.
+ ///
+ event Action> ObjectConverted;
+
+ ///
+ /// Converts a Beatmap using this Beatmap Converter.
+ ///
+ /// The un-converted Beatmap.
+ void Convert(Beatmap beatmap);
+ }
+}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index c633b94951..5c0ad7685b 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -14,6 +14,7 @@ using osu.Framework.IO.File;
using System.IO;
using osu.Game.IO.Serialization;
using System.Diagnostics;
+using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
@@ -40,6 +41,7 @@ namespace osu.Game.Beatmaps
track = new AsyncLazy