1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:22:55 +08:00

Merge branch 'master' into update-statistics-async

This commit is contained in:
Dean Herbert 2017-11-29 19:27:22 +09:00 committed by GitHub
commit d4c1359a49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
103 changed files with 1942 additions and 698 deletions

View File

@ -9,17 +9,17 @@ cache:
- inspectcode -> appveyor.yml - inspectcode -> appveyor.yml
- packages -> **\packages.config - packages -> **\packages.config
install: install:
- cmd: git submodule update --init --recursive - cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y - cmd: choco install resharper-clt -y
- cmd: choco install nvika -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.3/CodeFileSanity.exe
before_build: before_build:
- cmd: CodeFileSanity.exe - cmd: CodeFileSanity.exe
- cmd: nuget restore - cmd: nuget restore -verbosity quiet
build: build:
project: osu.sln project: osu.sln
parallel: true parallel: true
verbosity: minimal verbosity: minimal
after_build: after_build:
- cmd: inspectcode /o="inspectcodereport.xml" /caches-home="inspectcode" osu.sln - cmd: inspectcode --o="inspectcodereport.xml" --projects:osu.Game* --caches-home="inspectcode" osu.sln > NUL
- cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors - cmd: NVika parsereport "inspectcodereport.xml" --treatwarningsaserrors

@ -1 +1 @@
Subproject commit d87dab204b3df50f62e6070b1970c135ea647d78 Subproject commit 4fc866eee3803f88b155150e32e021b9c21e647f

@ -1 +1 @@
Subproject commit 1750ab8f6761ab35592fd46da71fbe0c141bfd93 Subproject commit 4287ee8043fb1419017359bc3a5db5dc06bc643f

View File

@ -13,7 +13,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
<add key="ProjectName" value="osu.Desktop" /> <add key="ProjectName" value="osu.Desktop" />
<add key="NuSpecName" value="osu.Desktop\osu.nuspec" /> <add key="NuSpecName" value="osu.Desktop\osu.nuspec" />
<add key="SolutionName" value="osu" /> <add key="SolutionName" value="osu" />
<add key="TargetName" value="Client\osu.Desktop" /> <add key="TargetName" value="osu.Desktop" />
<add key="PackageName" value="osulazer" /> <add key="PackageName" value="osulazer" />
<add key="IconName" value="lazer.ico" /> <add key="IconName" value="lazer.ico" />
<add key="CodeSigningCertificate" value="" /> <add key="CodeSigningCertificate" value="" />

View File

@ -145,6 +145,8 @@ namespace osu.Desktop.Deploy
/// </summary> /// </summary>
private static void checkReleaseFiles() private static void checkReleaseFiles()
{ {
if (!canGitHub) return;
var releaseLines = getReleaseLines(); var releaseLines = getReleaseLines();
//ensure we have all files necessary //ensure we have all files necessary
@ -157,6 +159,8 @@ namespace osu.Desktop.Deploy
private static void pruneReleases() private static void pruneReleases()
{ {
if (!canGitHub) return;
write("Pruning RELEASES..."); write("Pruning RELEASES...");
var releaseLines = getReleaseLines().ToList(); var releaseLines = getReleaseLines().ToList();
@ -190,7 +194,7 @@ namespace osu.Desktop.Deploy
private static void uploadBuild(string version) private static void uploadBuild(string version)
{ {
if (string.IsNullOrEmpty(GitHubAccessToken) || string.IsNullOrEmpty(codeSigningCertPath)) if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
return; return;
write("Publishing to GitHub..."); write("Publishing to GitHub...");
@ -228,8 +232,12 @@ namespace osu.Desktop.Deploy
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage); private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
private static void checkGitHubReleases() private static void checkGitHubReleases()
{ {
if (!canGitHub) return;
write("Checking GitHub releases..."); write("Checking GitHub releases...");
var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}"); var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}");
req.AuthenticatedBlockingPerform(); req.AuthenticatedBlockingPerform();

View File

@ -11,14 +11,14 @@ using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
internal class CatchBeatmapConverter : BeatmapConverter<CatchBaseHit> internal class CatchBeatmapConverter : BeatmapConverter<CatchHitObject>
{ {
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
protected override IEnumerable<CatchBaseHit> ConvertHitObject(HitObject obj, Beatmap beatmap) protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap)
{ {
var curveData = obj as IHasCurve; var curveData = obj as IHasCurve;
var positionData = obj as IHasPosition; var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo; var comboData = obj as IHasCombo;
if (positionData == null) if (positionData == null)

View File

@ -6,9 +6,9 @@ using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
internal class CatchBeatmapProcessor : BeatmapProcessor<CatchBaseHit> internal class CatchBeatmapProcessor : BeatmapProcessor<CatchHitObject>
{ {
public override void PostProcess(Beatmap<CatchBaseHit> beatmap) public override void PostProcess(Beatmap<CatchHitObject> beatmap)
{ {
if (beatmap.ComboColors.Count == 0) if (beatmap.ComboColors.Count == 0)
return; return;
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
int comboIndex = 0; int comboIndex = 0;
int colourIndex = 0; int colourIndex = 0;
CatchBaseHit lastObj = null; CatchHitObject lastObj = null;
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {

View File

@ -8,14 +8,14 @@ using System.Collections.Generic;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
public class CatchDifficultyCalculator : DifficultyCalculator<CatchBaseHit> public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject>
{ {
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap) public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{ {
} }
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) => 0; public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
} }
} }

View File

@ -1,14 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public abstract class CatchBaseHit : HitObject, IHasXPosition, IHasCombo public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
{ {
public const double OBJECT_RADIUS = 44;
public float X { get; set; } public float X { get; set; }
public Color4 ComboColour { get; set; } = Color4.Gray; public Color4 ComboColour { get; set; } = Color4.Gray;
@ -20,5 +24,14 @@ namespace osu.Game.Rulesets.Catch.Objects
/// The next fruit starts a new combo. Used for explodey. /// The next fruit starts a new combo. Used for explodey.
/// </summary> /// </summary>
public virtual bool LastInCombo { get; set; } public virtual bool LastInCombo { get; set; }
public float Scale { get; set; } = 1;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
} }
} }

View File

@ -5,11 +5,12 @@ using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
where TObject : CatchBaseHit where TObject : CatchHitObject
{ {
public new TObject HitObject; public new TObject HitObject;
@ -17,12 +18,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
: base(hitObject) : base(hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;
Scale = new Vector2(HitObject.Scale);
} }
} }
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchBaseHit> public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchHitObject>
{ {
protected DrawableCatchHitObject(CatchBaseHit hitObject) protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
@ -30,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Y = (float)HitObject.StartTime; Y = (float)HitObject.StartTime;
} }
public Func<CatchBaseHit, bool> CheckPosition; public Func<CatchHitObject, bool> CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
RelativeChildSize = new Vector2(1, (float)HitObject.Duration) RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
}; };
foreach (CatchBaseHit tick in s.Ticks) foreach (CatchHitObject tick in s.Ticks)
{ {
TinyDroplet tiny = tick as TinyDroplet; TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null) if (tiny != null)
@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
} }
} }
protected override void AddNested(DrawableHitObject<CatchBaseHit> h) protected override void AddNested(DrawableHitObject<CatchHitObject> h)
{ {
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h); dropletContainer.Add(h);

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{ {
public class Pulp : Circle, IHasAccentColour public class Pulp : Circle, IHasAccentColour
{ {
public const float PULP_SIZE = 20; public const float PULP_SIZE = (float)CatchHitObject.OBJECT_RADIUS / 2.2f;
public Pulp() public Pulp()
{ {

View File

@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Droplet : CatchBaseHit public class Droplet : CatchHitObject
{ {
} }
} }

View File

@ -3,7 +3,7 @@
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Fruit : CatchBaseHit public class Fruit : CatchHitObject
{ {
} }
} }

View File

@ -15,7 +15,7 @@ using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class JuiceStream : CatchBaseHit, IHasCurve public class JuiceStream : CatchHitObject, IHasCurve
{ {
/// <summary> /// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments. /// Positional distance that results in a duration of one second, before any speed adjustments.
@ -42,11 +42,11 @@ namespace osu.Game.Rulesets.Catch.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;
} }
public IEnumerable<CatchBaseHit> Ticks public IEnumerable<CatchHitObject> Ticks
{ {
get get
{ {
SortedList<CatchBaseHit> ticks = new SortedList<CatchBaseHit>((a, b) => a.StartTime.CompareTo(b.StartTime)); SortedList<CatchHitObject> ticks = new SortedList<CatchHitObject>((a, b) => a.StartTime.CompareTo(b.StartTime));
if (TickDistance == 0) if (TickDistance == 0)
return ticks; return ticks;

View File

@ -10,14 +10,14 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring namespace osu.Game.Rulesets.Catch.Scoring
{ {
internal class CatchScoreProcessor : ScoreProcessor<CatchBaseHit> internal class CatchScoreProcessor : ScoreProcessor<CatchHitObject>
{ {
public CatchScoreProcessor(RulesetContainer<CatchBaseHit> rulesetContainer) public CatchScoreProcessor(RulesetContainer<CatchHitObject> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {
} }
protected override void SimulateAutoplay(Beatmap<CatchBaseHit> beatmap) protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
{ {
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {

View File

@ -11,16 +11,26 @@ namespace osu.Game.Rulesets.Catch.Tests
[Ignore("getting CI working")] [Ignore("getting CI working")]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseCatchStacker() : base(typeof(CatchRuleset)) public TestCaseCatchStacker()
: base(typeof(CatchRuleset))
{ {
} }
protected override Beatmap CreateBeatmap() protected override Beatmap CreateBeatmap()
{ {
var beatmap = new Beatmap(); var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 6,
}
}
};
for (int i = 0; i < 256; i++) for (int i = 0; i < 512; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 }); beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap; return beatmap;
} }

View File

@ -1,44 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseCatcher : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Catcher),
};
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Children = new Drawable[]
{
new CatchInputManager(rulesets.GetRuleset(2))
{
RelativeSizeAxes = Axes.Both,
Child = new Catcher
{
RelativePositionAxes = Axes.Both,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Size = new Vector2(1, 0.2f),
}
},
};
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
[Ignore("getting CI working")]
internal class TestCaseCatcherArea : OsuTestCase
{
private RulesetInfo catchRuleset;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CatcherArea),
};
public TestCaseCatcherArea()
{
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
}
private void createCatcher(float size)
{
Child = new CatchInputManager(catchRuleset)
{
RelativeSizeAxes = Axes.Both,
Child = new CatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft
},
};
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
catchRuleset = rulesets.GetRuleset(2);
}
}
}

View File

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

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Catch.UI
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content; private readonly Container<Drawable> content;
private readonly Container catcherContainer; private readonly CatcherArea catcherArea;
private readonly Catcher catcher;
public CatchPlayfield() public CatchPlayfield(BeatmapDifficulty difficulty)
: base(Axes.Y) : base(Axes.Y)
{ {
Container explodingFruitContainer; Container explodingFruitContainer;
@ -43,30 +42,16 @@ namespace osu.Game.Rulesets.Catch.UI
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
catcherContainer = new Container catcherArea = new CatcherArea(difficulty)
{ {
RelativeSizeAxes = Axes.X, ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
Height = 180,
Child = catcher = new Catcher
{
ExplodingFruitTarget = explodingFruitContainer,
RelativePositionAxes = Axes.Both,
Origin = Anchor.TopCentre,
X = 0.5f,
}
} }
}; };
} }
protected override void Update() public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.CanCatch(obj);
{
base.Update();
catcher.Size = new Vector2(catcherContainer.DrawSize.Y);
}
public bool CheckIfWeCanCatch(CatchBaseHit obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X / DrawSize.X / 2;
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
@ -88,7 +73,7 @@ namespace osu.Game.Rulesets.Catch.UI
(judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject); (judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject);
(judgedObject.Parent as Container)?.Remove(judgedObject); (judgedObject.Parent as Container)?.Remove(judgedObject);
catcher.Add(judgedObject, screenPosition); catcherArea.Add(judgedObject, screenPosition);
} }
} }
} }

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchBaseHit> public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject>
{ {
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset)
@ -22,15 +22,15 @@ namespace osu.Game.Rulesets.Catch.UI
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
protected override BeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor(); protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override BeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter() => new CatchBeatmapConverter();
protected override Playfield CreatePlayfield() => new CatchPlayfield(); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h) protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{ {
var fruit = h as Fruit; var fruit = h as Fruit;
if (fruit != null) if (fruit != null)

View File

@ -1,193 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
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.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
}
private int currentDirection;
private bool dashing;
protected bool Dashing
{
get { return dashing; }
set
{
if (value == dashing) return;
dashing = value;
if (dashing)
Schedule(addAdditiveSprite);
}
}
private void addAdditiveSprite()
{
if (!dashing) return;
var additive = createCatcherSprite();
additive.RelativePositionAxes = Axes.Both;
additive.Blending = BlendingMode.Additive;
additive.Position = Position;
additive.Scale = Scale;
((Container)Parent).Add(additive);
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
Scheduler.AddDelayed(addAdditiveSprite, 50);
}
private Sprite createCatcherSprite() => new Sprite
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Texture = texture,
OriginPosition = new Vector2(DrawWidth / 2, 10) //temporary until the sprite is aligned correctly.
};
public bool OnPressed(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection--;
return true;
case CatchAction.MoveRight:
currentDirection++;
return true;
case CatchAction.Dash:
Dashing = true;
return true;
}
return false;
}
public bool OnReleased(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection++;
return true;
case CatchAction.MoveRight:
currentDirection--;
return true;
case CatchAction.Dash:
Dashing = false;
return true;
}
return false;
}
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
private const double base_speed = 1.0 / 512;
protected override void Update()
{
base.Update();
if (currentDirection == 0) return;
double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Sign(currentDirection), 1);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * base_speed * dashModifier, 0, 1);
}
public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
{
fruit.RelativePositionAxes = Axes.None;
fruit.Position = new Vector2(ToLocalSpace(absolutePosition).X - DrawSize.X / 2, 0);
fruit.Anchor = Anchor.TopCentre;
fruit.Origin = Anchor.BottomCentre;
fruit.Scale *= 0.7f;
fruit.LifetimeEnd = double.MaxValue;
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{
fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5);
}
caughtFruit.Add(fruit);
if (((CatchBaseHit)fruit.HitObject).LastInCombo)
explode();
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
}
}
}

View File

@ -0,0 +1,240 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
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.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
public const float CATCHER_SIZE = 172;
private readonly Catcher catcher;
public Container ExplodingFruitTarget
{
set { catcher.ExplodingFruitTarget = value; }
}
public CatcherArea(BeatmapDifficulty difficulty = null)
{
RelativeSizeAxes = Axes.X;
Height = CATCHER_SIZE;
Child = catcher = new Catcher(difficulty)
{
AdditiveTarget = this,
};
}
public void Add(DrawableHitObject fruit, Vector2 absolutePosition)
{
fruit.RelativePositionAxes = Axes.None;
fruit.Position = new Vector2(catcher.ToLocalSpace(absolutePosition).X - catcher.DrawSize.X / 2, 0);
fruit.Anchor = Anchor.TopCentre;
fruit.Origin = Anchor.BottomCentre;
fruit.Scale *= 0.7f;
fruit.LifetimeEnd = double.MaxValue;
catcher.Add(fruit);
}
public bool CanCatch(CatchHitObject obj) => Math.Abs(catcher.Position.X - obj.X) < catcher.DrawSize.X * Math.Abs(catcher.Scale.X) / DrawSize.X / 2;
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget;
public Container AdditiveTarget;
public Catcher(BeatmapDifficulty difficulty = null)
{
RelativePositionAxes = Axes.X;
X = 0.5f;
Origin = Anchor.TopCentre;
Anchor = Anchor.TopLeft;
Size = new Vector2(CATCHER_SIZE);
if (difficulty != null)
Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[]
{
createCatcherSprite(),
caughtFruit = new Container<DrawableHitObject>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
}
};
}
private int currentDirection;
private bool dashing;
protected bool Dashing
{
get { return dashing; }
set
{
if (value == dashing) return;
dashing = value;
if (dashing)
Schedule(addAdditiveSprite);
}
}
private void addAdditiveSprite()
{
if (!dashing || AdditiveTarget == null) return;
var additive = createCatcherSprite();
additive.Anchor = Anchor;
additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
additive.Position = Position;
additive.Scale = Scale;
additive.RelativePositionAxes = RelativePositionAxes;
additive.Blending = BlendingMode.Additive;
AdditiveTarget.Add(additive);
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
Scheduler.AddDelayed(addAdditiveSprite, 50);
}
private Sprite createCatcherSprite() => new Sprite
{
Size = new Vector2(CATCHER_SIZE),
FillMode = FillMode.Fill,
Texture = texture,
OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
};
public void Add(DrawableHitObject fruit)
{
float distance = fruit.DrawSize.X / 2 * fruit.Scale.X;
while (caughtFruit.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.DistanceSquared(f.Position, fruit.Position) < distance * distance))
{
fruit.X += RNG.Next(-5, 5);
fruit.Y -= RNG.Next(0, 5);
}
caughtFruit.Add(fruit);
if (((CatchHitObject)fruit.HitObject).LastInCombo)
explode();
}
public bool OnPressed(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection--;
return true;
case CatchAction.MoveRight:
currentDirection++;
return true;
case CatchAction.Dash:
Dashing = true;
return true;
}
return false;
}
public bool OnReleased(CatchAction action)
{
switch (action)
{
case CatchAction.MoveLeft:
currentDirection++;
return true;
case CatchAction.MoveRight:
currentDirection--;
return true;
case CatchAction.Dash:
Dashing = false;
return true;
}
return false;
}
/// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary>
public const double BASE_SPEED = 1.0 / 512;
protected override void Update()
{
base.Update();
if (currentDirection == 0) return;
double dashModifier = Dashing ? 1 : 0.5;
Scale = new Vector2(Math.Abs(Scale.X) * Math.Sign(currentDirection), Scale.Y);
X = (float)MathHelper.Clamp(X + Math.Sign(currentDirection) * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
}
private void explode()
{
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
{
var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f);
}
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
f.Expire();
}
}
}
}
}

View File

@ -57,16 +57,17 @@
<Compile Include="Objects\JuiceStream.cs" /> <Compile Include="Objects\JuiceStream.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" /> <Compile Include="Scoring\CatchScoreProcessor.cs" />
<Compile Include="Judgements\CatchJudgement.cs" /> <Compile Include="Judgements\CatchJudgement.cs" />
<Compile Include="Objects\CatchBaseHit.cs" /> <Compile Include="Objects\CatchHitObject.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" /> <Compile Include="Objects\Drawable\DrawableFruit.cs" />
<Compile Include="Objects\Droplet.cs" /> <Compile Include="Objects\Droplet.cs" />
<Compile Include="Objects\Fruit.cs" /> <Compile Include="Objects\Fruit.cs" />
<Compile Include="Objects\TinyDroplet.cs" /> <Compile Include="Objects\TinyDroplet.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" /> <Compile Include="Tests\TestCaseCatcherArea.cs" />
<Compile Include="Tests\TestCaseCatchStacker.cs" /> <Compile Include="Tests\TestCaseCatchStacker.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" /> <Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\Catcher.cs" /> <Compile Include="UI\CatcherArea.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" /> <Compile Include="UI\CatchRulesetContainer.cs" />
<Compile Include="UI\CatchPlayfield.cs" /> <Compile Include="UI\CatchPlayfield.cs" />
<Compile Include="CatchRuleset.cs" /> <Compile Include="CatchRuleset.cs" />

View File

@ -138,8 +138,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Pattern newPattern = conversion.Generate(); Pattern newPattern = conversion.Generate();
lastPattern = newPattern; lastPattern = newPattern;
var stairPatternGenerator = (HitObjectPatternGenerator)conversion; var stairPatternGenerator = conversion as HitObjectPatternGenerator;
lastStair = stairPatternGenerator.StairType; lastStair = stairPatternGenerator?.StairType ?? lastStair;
return newPattern.HitObjects; return newPattern.HitObjects;
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania
{ {
} }
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) => 0; public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize))); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize)));
} }

View File

@ -176,22 +176,10 @@ namespace osu.Game.Rulesets.Mania.Mods
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject> public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
{ {
private int availableColumns;
public override void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer)
{
// Todo: This shouldn't be done, we should be getting a ManiaBeatmap which should store AvailableColumns
// But this is dependent on a _lot_ of refactoring
var maniaRulesetContainer = (ManiaRulesetContainer)rulesetContainer;
availableColumns = maniaRulesetContainer.AvailableColumns;
base.ApplyToRulesetContainer(rulesetContainer);
}
protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) => new Score protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) => new Score
{ {
User = new User { Username = "osu!topus!" }, User = new User { Username = "osu!topus!" },
Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(), Replay = new ManiaAutoGenerator(beatmap).Generate(),
}; };
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
@ -13,15 +13,11 @@ namespace osu.Game.Rulesets.Mania.Replays
{ {
internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject> internal class ManiaAutoGenerator : AutoGenerator<ManiaHitObject>
{ {
private const double release_delay = 20; public const double RELEASE_DELAY = 20;
private readonly int availableColumns; public ManiaAutoGenerator(Beatmap<ManiaHitObject> beatmap)
public ManiaAutoGenerator(Beatmap<ManiaHitObject> beatmap, int availableColumns)
: base(beatmap) : base(beatmap)
{ {
this.availableColumns = availableColumns;
Replay = new Replay { User = new User { Username = @"Autoplay" } }; Replay = new Replay { User = new User { Username = @"Autoplay" } };
} }
@ -30,104 +26,52 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate() public override Replay Generate()
{ {
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
Replay.Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
double[] holdEndTimes = new double[availableColumns]; var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
for (int i = 0; i < availableColumns; i++)
holdEndTimes[i] = double.NegativeInfinity;
// Notes are handled row-by-row int activeColumns = 0;
foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime)) foreach (var group in pointGroups)
{ {
double groupTime = objGroup.Key; foreach (var point in group)
int activeColumns = 0;
// Get the previously held-down active columns
for (int i = 0; i < availableColumns; i++)
{ {
if (holdEndTimes[i] > groupTime) if (point is HitPoint)
activeColumns |= 1 << i; activeColumns |= 1 << point.Column;
if (point is ReleasePoint)
activeColumns ^= 1 << point.Column;
} }
// Add on the group columns, keeping track of the held notes for the next rows Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, activeColumns));
foreach (var obj in objGroup)
{
var holdNote = obj as HoldNote;
if (holdNote != null)
holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime);
activeColumns |= 1 << obj.Column;
}
Replay.Frames.Add(new ReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None));
// Add the release frames. We can't do this with the loop above because we need activeColumns to be fully populated
foreach (var obj in objGroup.GroupBy(h => (h as IHasEndTime)?.EndTime ?? h.StartTime + release_delay).OrderBy(h => h.Key))
{
var groupEndTime = obj.Key;
int activeColumnsAtEnd = 0;
for (int i = 0; i < availableColumns; i++)
{
if (holdEndTimes[i] > groupEndTime)
activeColumnsAtEnd |= 1 << i;
}
Replay.Frames.Add(new ReplayFrame(groupEndTime, activeColumnsAtEnd, 0, ReplayButtonState.None));
}
} }
Replay.Frames = Replay.Frames
// Pick the maximum activeColumns for all frames at the same time
.GroupBy(f => f.Time)
.Select(g => new ReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None))
// The addition of release frames above maybe result in unordered frames, but we need them ordered
.OrderBy(f => f.Time)
.ToList();
return Replay; return Replay;
} }
/// <summary> private IEnumerable<IActionPoint> generateActionPoints()
/// Finds the maximum <see cref="ReplayFrame.MouseX"/> by count of bits from a grouping of <see cref="ReplayFrame"/>s.
/// </summary>
/// <param name="group">The <see cref="ReplayFrame"/> grouping to search.</param>
/// <returns>The maximum <see cref="ReplayFrame.MouseX"/> by count of bits.</returns>
private float maxMouseX(IGrouping<double, ReplayFrame> group)
{ {
int currentCount = -1; foreach (var obj in Beatmap.HitObjects)
int currentMax = 0;
foreach (var val in group)
{ {
int newCount = countBits((int)(val.MouseX ?? 0)); yield return new HitPoint { Time = obj.StartTime, Column = obj.Column };
if (newCount > currentCount) yield return new ReleasePoint { Time = ((obj as IHasEndTime)?.EndTime ?? obj.StartTime) + RELEASE_DELAY, Column = obj.Column };
{
currentCount = newCount;
currentMax = (int)(val.MouseX ?? 0);
}
} }
return currentMax;
} }
/// <summary> private interface IActionPoint
/// Counts the number of bits set in a value.
/// </summary>
/// <param name="value">The value to count.</param>
/// <returns>The number of set bits.</returns>
private int countBits(int value)
{ {
int count = 0; double Time { get; set; }
while (value > 0) int Column { get; set; }
{ }
if ((value & 1) > 0)
count++;
value >>= 1;
}
return count; private struct HitPoint : IActionPoint
{
public double Time { get; set; }
public int Column { get; set; }
}
private struct ReleasePoint : IActionPoint
{
public double Time { get; set; }
public int Column { get; set; }
} }
} }
} }

View File

@ -2,29 +2,37 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays namespace osu.Game.Rulesets.Mania.Replays
{ {
internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler
{ {
public ManiaFramedReplayInputHandler(Replay replay) private readonly ManiaRulesetContainer container;
public ManiaFramedReplayInputHandler(Replay replay, ManiaRulesetContainer container)
: base(replay) : base(replay)
{ {
this.container = container;
} }
private ManiaPlayfield playfield;
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates()
{ {
var actions = new List<ManiaAction>(); var actions = new List<ManiaAction>();
int activeColumns = (int)(CurrentFrame.MouseX ?? 0); if (playfield == null)
playfield = (ManiaPlayfield)container.Playfield;
int activeColumns = (int)(CurrentFrame.MouseX ?? 0);
int counter = 0; int counter = 0;
while (activeColumns > 0) while (activeColumns > 0)
{ {
if ((activeColumns & 1) > 0) if ((activeColumns & 1) > 0)
actions.Add(ManiaAction.Key1 + counter); actions.Add(playfield.Columns.ElementAt(counter).Action);
counter++; counter++;
activeColumns >>= 1; activeColumns >>= 1;
} }

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Mania.Replays
{
public class ManiaReplayFrame : ReplayFrame
{
public override bool IsImportant => MouseX > 0;
public ManiaReplayFrame(double time, int activeColumns)
: base(time, activeColumns, null, ReplayButtonState.None)
{
}
}
}

View File

@ -0,0 +1,173 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
[Ignore("getting CI working")]
public class TestCaseAutoGeneration : OsuTestCase
{
[Test]
public void TestSingleNote()
{
// | |
// | - |
// | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
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");
}
[Test]
public void TestSingleHoldNote()
{
// | |
// | * |
// | * |
// | * |
// | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
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");
}
[Test]
public void TestSingleNoteChord()
{
// | | |
// | - | - |
// | | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
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");
}
[Test]
public void TestHoldNoteChord()
{
// | | |
// | * | * |
// | * | * |
// | * | * |
// | | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
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");
}
[Test]
public void TestSingleNoteStair()
{
// | | |
// | | - |
// | - | |
// | | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
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");
}
[Test]
public void TestHoldNoteStair()
{
// | | |
// | | * |
// | * | * |
// | * | * |
// | * | |
// | | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
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");
}
[Test]
public void TestHoldNoteWithReleasePress()
{
// | | |
// | * | - |
// | * | |
// | * | |
// | | |
var beatmap = new Beatmap<ManiaHitObject>();
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
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");
}
}
}

View File

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

View File

@ -124,6 +124,6 @@ namespace osu.Game.Rulesets.Mania.UI
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic); protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
} }
} }

View File

@ -72,6 +72,7 @@
<Compile Include="Objects\Types\IHasColumn.cs" /> <Compile Include="Objects\Types\IHasColumn.cs" />
<Compile Include="Replays\ManiaAutoGenerator.cs" /> <Compile Include="Replays\ManiaAutoGenerator.cs" />
<Compile Include="Replays\ManiaFramedReplayInputHandler.cs" /> <Compile Include="Replays\ManiaFramedReplayInputHandler.cs" />
<Compile Include="Replays\ManiaReplayFrame.cs" />
<Compile Include="Scoring\ManiaScoreProcessor.cs" /> <Compile Include="Scoring\ManiaScoreProcessor.cs" />
<Compile Include="Objects\BarLine.cs" /> <Compile Include="Objects\BarLine.cs" />
<Compile Include="Objects\HoldNote.cs" /> <Compile Include="Objects\HoldNote.cs" />
@ -80,8 +81,10 @@
<Compile Include="Objects\Note.cs" /> <Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ManiaInputManager.cs" /> <Compile Include="ManiaInputManager.cs" />
<Compile Include="Tests\TestCaseAutoGeneration.cs" />
<Compile Include="Tests\TestCaseManiaHitObjects.cs" /> <Compile Include="Tests\TestCaseManiaHitObjects.cs" />
<Compile Include="Tests\TestCaseManiaPlayfield.cs" /> <Compile Include="Tests\TestCaseManiaPlayfield.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Timing\GravityScrollingContainer.cs" /> <Compile Include="Timing\GravityScrollingContainer.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" /> <Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" /> <Compile Include="UI\Column.cs" />

View File

@ -45,6 +45,18 @@ namespace osu.Game.Rulesets.Osu.Objects
set { Curve.Distance = value; } set { Curve.Distance = value; }
} }
/// <summary>
/// The position of the cursor at the point of completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation.
/// </summary>
internal Vector2? LazyEndPosition;
/// <summary>
/// The distance travelled by the cursor upon completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation.
/// </summary>
internal float LazyTravelDistance;
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>(); public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public int RepeatCount { get; set; } = 1; public int RepeatCount { get; set; } = 1;

View File

@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
(h as Slider)?.Curve?.Calculate(); (h as Slider)?.Curve?.Calculate();
} }
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{ {
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects); OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap(Beatmap.HitObjects, TimeRate);
Skill[] skills = Skill[] skills =
{ {
new Aim(), new Aim(),
@ -67,8 +67,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
if (categoryDifficulty != null) if (categoryDifficulty != null)
{ {
categoryDifficulty.Add("Aim", aimRating.ToString("0.00")); categoryDifficulty.Add("Aim", aimRating);
categoryDifficulty.Add("Speed", speedRating.ToString("0.00")); categoryDifficulty.Add("Speed", speedRating);
} }
return starRating; return starRating;

View File

@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as /// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation. /// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
/// </summary> /// </summary>
public OsuDifficultyBeatmap(List<OsuHitObject> objects) public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
{ {
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases. // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator. // This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime)); objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
difficultyObjects = createDifficultyObjectEnumerator(objects); difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
} }
/// <summary> /// <summary>
@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects) private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
{ {
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object. // We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
OsuHitObject[] triangle = new OsuHitObject[3]; OsuHitObject[] triangle = new OsuHitObject[3];
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
triangle[1] = triangle[0]; triangle[1] = triangle[0];
triangle[0] = objects[i]; triangle[0] = objects[i];
yield return new OsuDifficultyHitObject(triangle); yield return new OsuDifficultyHitObject(triangle, timeRate);
} }
} }
} }

View File

@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq;
using OpenTK;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
@ -33,13 +35,17 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
private const int normalized_radius = 52; private const int normalized_radius = 52;
private readonly double timeRate;
private readonly OsuHitObject[] t; private readonly OsuHitObject[] t;
/// <summary> /// <summary>
/// Initializes the object calculating extra data required for difficulty calculation. /// Initializes the object calculating extra data required for difficulty calculation.
/// </summary> /// </summary>
public OsuDifficultyHitObject(OsuHitObject[] triangle) public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
{ {
this.timeRate = timeRate;
t = triangle; t = triangle;
BaseObject = t[0]; BaseObject = t[0];
setDistances(); setDistances();
@ -57,14 +63,53 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus; scalingFactor *= 1 + smallCircleBonus;
} }
Distance = (t[0].StackedPosition - t[1].StackedPosition).Length * scalingFactor; Vector2 lastCursorPosition = t[1].StackedPosition;
float lastTravelDistance = 0;
var lastSlider = t[1] as Slider;
if (lastSlider != null)
{
computeSliderCursorPosition(lastSlider);
lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition;
lastTravelDistance = lastSlider.LazyTravelDistance;
}
Distance = (lastTravelDistance + (BaseObject.StackedPosition - lastCursorPosition).Length) * scalingFactor;
} }
private void setTimingValues() private void setTimingValues()
{ {
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure. // Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
DeltaTime = Math.Max(40, t[0].StartTime - t[1].StartTime); DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
TimeUntilHit = 450; // BaseObject.PreEmpt; TimeUntilHit = 450; // BaseObject.PreEmpt;
} }
private void computeSliderCursorPosition(Slider slider)
{
if (slider.LazyEndPosition != null)
return;
slider.LazyEndPosition = slider.StackedPosition;
float approxFollowCircleRadius = (float)(slider.Radius * 3);
var computeVertex = new Action<double>(t =>
{
var diff = slider.PositionAt(t) - slider.LazyEndPosition.Value;
float dist = diff.Length;
if (dist > approxFollowCircleRadius)
{
// The cursor would be outside the follow circle, we need to move it
diff.Normalize(); // Obtain direction of diff
dist -= approxFollowCircleRadius;
slider.LazyEndPosition += diff * dist;
slider.LazyTravelDistance += dist;
}
});
var scoringTimes = slider.Ticks.Select(t => t.StartTime).Concat(slider.RepeatPoints.Select(r => r.StartTime)).OrderBy(t => t);
foreach (var time in scoringTimes)
computeVertex(time);
computeVertex(slider.EndTime);
}
} }
} }

View File

@ -14,6 +14,8 @@ using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
@ -114,6 +116,8 @@ namespace osu.Game.Rulesets.Osu
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods); public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
public override PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override string Description => "osu!"; public override string Description => "osu!";
public override SettingsSubsection CreateSettings() => new OsuSettings(); public override SettingsSubsection CreateSettings() => new OsuSettings();

View File

@ -0,0 +1,199 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
{
public class OsuPerformanceCalculator : PerformanceCalculator<OsuHitObject>
{
private readonly int countHitCircles;
private readonly int beatmapMaxCombo;
private Mod[] mods;
private double realApproachRate;
private double accuracy;
private int scoreMaxCombo;
private int count300;
private int count100;
private int count50;
private int countMiss;
public OsuPerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
beatmapMaxCombo = Beatmap.HitObjects.Count;
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.RepeatCount + s.Ticks.Count());
}
public override double Calculate(Dictionary<string, double> categoryRatings = null)
{
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
count300 = Convert.ToInt32(Score.Statistics["300"]);
count100 = Convert.ToInt32(Score.Statistics["100"]);
count50 = Convert.ToInt32(Score.Statistics["50"]);
countMiss = Convert.ToInt32(Score.Statistics["x"]);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
return 0;
// Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
// locally for now as doing so would modify animations and other things unexpectedly
// DO NOT MODIFY THIS
double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
if (mods.Any(m => m is OsuModHardRock))
ar = Math.Min(10, ar * 1.4);
if (mods.Any(m => m is OsuModEasy))
ar = Math.Max(0, ar / 2);
double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
if (mods.Any(m => m is OsuModNoFail))
multiplier *= 0.90f;
if (mods.Any(m => m is OsuModSpunOut))
multiplier *= 0.95f;
double aimValue = computeAimValue();
double speedValue = computeSpeedValue();
double accuracyValue = computeAccuracyValue();
double totalValue =
Math.Pow(
Math.Pow(aimValue, 1.1f) +
Math.Pow(speedValue, 1.1f) +
Math.Pow(accuracyValue, 1.1f), 1.0f / 1.1f
) * multiplier;
if (categoryRatings != null)
{
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
}
return totalValue;
}
private double computeAimValue()
{
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
aimValue *= lengthBonus;
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
aimValue *= Math.Pow(0.97f, countMiss);
// Combo scaling
if (beatmapMaxCombo > 0)
aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
double approachRateFactor = 1.0f;
if (realApproachRate > 10.33f)
approachRateFactor += 0.45f * (realApproachRate - 10.33f);
else if (realApproachRate < 8.0f)
{
// HD is worth more with lower ar!
if (mods.Any(h => h is OsuModHidden))
approachRateFactor += 0.02f * (8.0f - realApproachRate);
else
approachRateFactor += 0.01f * (8.0f - realApproachRate);
}
aimValue *= approachRateFactor;
if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.18f;
if (mods.Any(h => h is OsuModFlashlight))
{
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
aimValue *= 1.45f * lengthBonus;
}
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
return aimValue;
}
private double computeSpeedValue()
{
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f);
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
speedValue *= Math.Pow(0.97f, countMiss);
// Combo scaling
if (beatmapMaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
return speedValue;
}
private double computeAccuracyValue()
{
// This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window
double betterAccuracyPercentage;
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
// It is possible to reach a negative accuracy with this formula. Cap it at zero - zero points
if (betterAccuracyPercentage < 0)
betterAccuracyPercentage = 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
if (mods.Any(m => m is OsuModHidden))
accuracyValue *= 1.02f;
if (mods.Any(m => m is OsuModFlashlight))
accuracyValue *= 1.02f;
return accuracyValue;
}
private double totalHits => count300 + count100 + count50 + countMiss;
private double totalSuccessfulHits => count300 + count100 + count50;
protected override BeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
}
}

View File

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

View File

@ -85,9 +85,11 @@
<Compile Include="OsuInputManager.cs" /> <Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" /> <Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\TestCaseHitObjects.cs" /> <Compile Include="Tests\TestCaseHitObjects.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" /> <Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" /> <Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" /> <Compile Include="UI\OsuSettings.cs" />
<Compile Include="Scoring\OsuPerformanceCalculator.cs" />
<Compile Include="Scoring\OsuScoreProcessor.cs" /> <Compile Include="Scoring\OsuScoreProcessor.cs" />
<Compile Include="UI\OsuRulesetContainer.cs" /> <Compile Include="UI\OsuRulesetContainer.cs" />
<Compile Include="UI\OsuPlayfield.cs" /> <Compile Include="UI\OsuPlayfield.cs" />

View File

@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System; using System;
namespace osu.Game.Rulesets.Taiko namespace osu.Game.Rulesets.Taiko
@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko
{ {
} }
public override double Calculate(Dictionary<string, string> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{ {
// Fill our custom DifficultyHitObject class, that carries additional information // Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear(); difficultyHitObjects.Clear();
@ -53,8 +52,8 @@ namespace osu.Game.Rulesets.Taiko
if (categoryDifficulty != null) if (categoryDifficulty != null)
{ {
categoryDifficulty.Add("Strain", starRating.ToString("0.00", CultureInfo.InvariantCulture)); categoryDifficulty.Add("Strain", starRating);
categoryDifficulty.Add("Hit window 300", (35 /*HitObjectManager.HitWindow300*/ / TimeRate).ToString("0.00", CultureInfo.InvariantCulture)); categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
} }
return starRating; return starRating;

View File

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

View File

@ -83,6 +83,7 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" /> <Compile Include="Scoring\TaikoScoreProcessor.cs" />
<Compile Include="TaikoInputManager.cs" /> <Compile Include="TaikoInputManager.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />
<Compile Include="Tests\TestCaseTaikoPlayfield.cs" /> <Compile Include="Tests\TestCaseTaikoPlayfield.cs" />
<Compile Include="UI\HitTarget.cs" /> <Compile Include="UI\HitTarget.cs" />
<Compile Include="UI\InputDrum.cs" /> <Compile Include="UI\InputDrum.cs" />

View File

@ -0,0 +1,33 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Menu;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
internal class TestCaseButtonSystem : OsuTestCase
{
public TestCaseButtonSystem()
{
OsuLogo logo;
ButtonSystem buttons;
Children = new Drawable[]
{
new Box
{
Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke),
RelativeSizeAxes = Axes.Both,
},
buttons = new ButtonSystem(),
logo = new OsuLogo()
};
buttons.SetOsuLogo(logo);
}
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Menu;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
[Description("main menu")]
internal class TestCaseMenuButtonSystem : OsuTestCase
{
public TestCaseMenuButtonSystem()
{
Add(new Box
{
Colour = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke),
RelativeSizeAxes = Framework.Graphics.Axes.Both,
});
Add(new ButtonSystem());
}
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual
Add(container = new ExampleContainer()); Add(container = new ExampleContainer());
AddStep(@"Add button", () => container.Add(new OsuButton AddStep(@"Add button", () => container.Add(new TriangleButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Text = @"Button", Text = @"Button",

View File

@ -116,7 +116,7 @@
<Compile Include="Visual\TestCaseKeyCounter.cs" /> <Compile Include="Visual\TestCaseKeyCounter.cs" />
<Compile Include="Visual\TestCaseLeaderboard.cs" /> <Compile Include="Visual\TestCaseLeaderboard.cs" />
<Compile Include="Visual\TestCaseMedalOverlay.cs" /> <Compile Include="Visual\TestCaseMedalOverlay.cs" />
<Compile Include="Visual\TestCaseMenuButtonSystem.cs" /> <Compile Include="Visual\TestCaseButtonSystem.cs" />
<Compile Include="Visual\TestCaseMenuOverlays.cs" /> <Compile Include="Visual\TestCaseMenuOverlays.cs" />
<Compile Include="Visual\TestCaseMods.cs" /> <Compile Include="Visual\TestCaseMods.cs" />
<Compile Include="Visual\TestCaseMusicController.cs" /> <Compile Include="Visual\TestCaseMusicController.cs" />

View File

@ -80,6 +80,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Performs the conversion of a hit object. /// Performs the conversion of a hit object.
/// This method is generally executed sequentially for all objects in a beatmap.
/// </summary> /// </summary>
/// <param name="original">The hit object to convert.</param> /// <param name="original">The hit object to convert.</param>
/// <param name="beatmap">The un-converted Beatmap.</param> /// <param name="beatmap">The un-converted Beatmap.</param>

View File

@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps
public class BeatmapMetrics public class BeatmapMetrics
{ {
/// <summary> /// <summary>
/// Total vote counts of user ratings on a scale of 0..length. /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?).
/// </summary> /// </summary>
public IEnumerable<int> Ratings { get; set; } public IEnumerable<int> Ratings { get; set; }

View File

@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps
{ {
protected double TimeRate = 1; protected double TimeRate = 1;
public abstract double Calculate(Dictionary<string, string> categoryDifficulty = null); public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
} }
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject

View File

@ -3,12 +3,18 @@
using System; using System;
using osu.Framework; using osu.Framework;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.MathUtils;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
@ -22,6 +28,10 @@ namespace osu.Game.Beatmaps.Drawables
private readonly Container nestedContainer; private readonly Container nestedContainer;
private readonly Container borderContainer;
private readonly Box hoverLayer;
protected override Container<Drawable> Content => nestedContainer; protected override Container<Drawable> Content => nestedContainer;
protected Panel() protected Panel()
@ -29,20 +39,56 @@ namespace osu.Game.Beatmaps.Drawables
Height = MAX_HEIGHT; Height = MAX_HEIGHT;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AddInternal(nestedContainer = new Container AddInternal(borderContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true, Masking = true,
CornerRadius = 10, CornerRadius = 10,
BorderColour = new Color4(221, 255, 255, 255), BorderColour = new Color4(221, 255, 255, 255),
Children = new Drawable[]
{
nestedContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
hoverLayer = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Blending = BlendingMode.Additive,
},
}
}); });
Alpha = 0; Alpha = 0;
} }
private SampleChannel sampleHover;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colours)
{
sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
}
protected override bool OnHover(InputState state)
{
sampleHover?.Play();
hoverLayer.FadeIn(100, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hoverLayer.FadeOut(1000, Easing.OutQuint);
base.OnHoverLost(state);
}
public void SetMultiplicativeAlpha(float alpha) public void SetMultiplicativeAlpha(float alpha)
{ {
nestedContainer.Alpha = alpha; borderContainer.Alpha = alpha;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -94,8 +140,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Selected() protected virtual void Selected()
{ {
nestedContainer.BorderThickness = 2.5f; borderContainer.BorderThickness = 2.5f;
nestedContainer.EdgeEffect = new EdgeEffectParameters borderContainer.EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150), Colour = new Color4(130, 204, 255, 150),
@ -106,8 +152,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void Deselected() protected virtual void Deselected()
{ {
nestedContainer.BorderThickness = 0; borderContainer.BorderThickness = 0;
nestedContainer.EdgeEffect = new EdgeEffectParameters borderContainer.EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Shadow, Type = EdgeEffectType.Shadow,
Offset = new Vector2(1), Offset = new Vector2(1),

View File

@ -16,12 +16,13 @@ namespace osu.Game.Configuration
Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); Set(OsuSetting.Ruleset, 0, 0, int.MaxValue);
Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details);
Set(OsuSetting.ShowConvertedBeatmaps, true);
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1); Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation); Set(OsuSetting.SelectionRandomType, SelectionRandomType.RandomPermutation);
Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1, 0.01); Set(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2, 1);
// Online settings // Online settings
Set(OsuSetting.Username, string.Empty); Set(OsuSetting.Username, string.Empty);
@ -112,6 +113,7 @@ namespace osu.Game.Configuration
SnakingOutSliders, SnakingOutSliders,
ShowFpsDisplay, ShowFpsDisplay,
ChatDisplayHeight, ChatDisplayHeight,
Version Version,
ShowConvertedBeatmaps
} }
} }

View File

@ -35,6 +35,7 @@ namespace osu.Game.Database
var id = obj.ID; var id = obj.ID;
obj = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id); obj = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
context.Entry(obj).Reload();
} }
/// <summary> /// <summary>

View File

@ -2,34 +2,39 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Graphics;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
public class OsuClickableContainer : ClickableContainer public class OsuClickableContainer : ClickableContainer
{ {
protected SampleChannel SampleClick, SampleHover; private readonly HoverSampleSet sampleSet;
private readonly Container content = new Container { RelativeSizeAxes = Axes.Both };
protected override Container<Drawable> Content => content;
public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal)
{
this.sampleSet = sampleSet;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load()
{ {
SampleHover = audio.Sample.Get(@"UI/generic-hover"); if (AutoSizeAxes != Axes.None)
SampleClick = audio.Sample.Get(@"UI/generic-click"); {
} content.RelativeSizeAxes = RelativeSizeAxes;
content.AutoSizeAxes = AutoSizeAxes;
}
protected override bool OnHover(InputState state) InternalChildren = new Drawable[]
{ {
SampleHover?.Play(); content,
return base.OnHover(state); new HoverClickSounds(sampleSet)
} };
protected override bool OnClick(InputState state)
{
SampleClick?.Play();
return base.OnClick(state);
} }
} }
} }

View File

@ -16,8 +16,8 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
samplePopIn = audio.Sample.Get(@"UI/melodic-5"); samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in");
samplePopOut = audio.Sample.Get(@"UI/melodic-4"); samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out");
StateChanged += onStateChanged; StateChanged += onStateChanged;
} }

View File

@ -57,19 +57,31 @@ namespace osu.Game.Graphics
private void load(FontStore store) private void load(FontStore store)
{ {
this.store = store; this.store = store;
updateTexture(); updateTexture();
} }
protected override void LoadComplete()
{
base.LoadComplete();
updateTexture();
}
private FontAwesome loadedIcon;
private void updateTexture() private void updateTexture()
{ {
var texture = store?.Get(((char)icon).ToString()); var loadableIcon = icon;
if (loadableIcon == loadedIcon) return;
var texture = store?.Get(((char)loadableIcon).ToString());
spriteMain.Texture = texture; spriteMain.Texture = texture;
spriteShadow.Texture = texture; spriteShadow.Texture = texture;
if (Size == Vector2.Zero) if (Size == Vector2.Zero)
Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0);
loadedIcon = loadableIcon;
} }
public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true)

View File

@ -20,6 +20,7 @@ namespace osu.Game.Graphics.UserInterface
private const Easing easing = Easing.InOutCubic; private const Easing easing = Easing.InOutCubic;
private float length; private float length;
/// <summary> /// <summary>
/// Length of the bar, ranges from 0 to 1 /// Length of the bar, ranges from 0 to 1
/// </summary> /// </summary>
@ -134,4 +135,4 @@ namespace osu.Game.Graphics.UserInterface
Vertical = TopToBottom | BottomToTop, Vertical = TopToBottom | BottomToTop,
Horizontal = LeftToRight | RightToLeft, Horizontal = LeftToRight | RightToLeft,
} }
} }

View File

@ -0,0 +1,36 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Input;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// Adds hover and click sounds to a drawable.
/// Does not draw anything.
/// </summary>
public class HoverClickSounds : HoverSounds
{
private SampleChannel sampleClick;
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet)
{
}
protected override bool OnClick(InputState state)
{
sampleClick?.Play();
return base.OnClick(state);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}");
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// Adds hover sounds to a drawable.
/// Does not draw anything.
/// </summary>
public class HoverSounds : CompositeDrawable
{
private SampleChannel sampleHover;
protected readonly HoverSampleSet SampleSet;
public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal)
{
SampleSet = sampleSet;
RelativeSizeAxes = Axes.Both;
}
protected override bool OnHover(InputState state)
{
sampleHover?.Play();
return base.OnHover(state);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}");
}
}
public enum HoverSampleSet
{
[Description("")]
Loud,
[Description("-soft")]
Normal,
[Description("-softer")]
Soft
}
}

View File

@ -1,126 +1,18 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuButton : Button, IFilterable /// <summary>
/// A button with added default sound effects.
/// </summary>
public class OsuButton : Button
{ {
private Box hover;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
protected Triangles Triangles;
public OsuButton() public OsuButton()
{ {
Height = 40; Add(new HoverClickSounds(HoverSampleSet.Loud));
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Bold",
};
public override bool HandleInput => Action != null;
[BackgroundDependencyLoader]
private void load(OsuColour colours, AudioManager audio)
{
if (Action == null)
Colour = OsuColour.Gray(0.5f);
BackgroundColour = colours.BlueDark;
Content.Masking = true;
Content.CornerRadius = 5;
AddRange(new Drawable[]
{
Triangles = new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourDark = colours.BlueDarker,
ColourLight = colours.Blue,
},
hover = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingMode.Additive,
Colour = Color4.White.Opacity(0.1f),
Alpha = 0,
},
});
sampleClick = audio.Sample.Get(@"UI/generic-click");
sampleHover = audio.Sample.Get(@"UI/generic-hover");
Enabled.ValueChanged += enabled_ValueChanged;
Enabled.TriggerChange();
}
private void enabled_ValueChanged(bool enabled)
{
this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
}
protected override bool OnClick(InputState state)
{
sampleClick?.Play();
return base.OnClick(state);
}
protected override bool OnHover(InputState state)
{
sampleHover?.Play();
hover.FadeIn(200);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(200);
base.OnHoverLost(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
Content.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
public IEnumerable<string> FilterTerms => new[] { Text };
public bool MatchingFilter
{
set
{
this.FadeTo(value ? 1 : 0);
}
} }
} }
} }

View File

@ -70,7 +70,8 @@ namespace osu.Game.Graphics.UserInterface
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 5 }, Margin = new MarginPadding { Right = 5 },
} },
new HoverClickSounds()
}; };
Nub.Current.BindTo(Current); Nub.Current.BindTo(Current);

View File

@ -33,7 +33,6 @@ namespace osu.Game.Graphics.UserInterface
if (accentColour == default(Color4)) if (accentColour == default(Color4))
accentColour = colours.PinkDarker; accentColour = colours.PinkDarker;
updateAccentColour(); updateAccentColour();
} }
private void updateAccentColour() private void updateAccentColour()
@ -137,6 +136,8 @@ namespace osu.Game.Graphics.UserInterface
nonAccentHoverColour = colours.PinkDarker; nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f); nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours(); updateColours();
AddInternal(new HoverClickSounds(HoverSampleSet.Soft));
} }
protected override void UpdateForegroundColour() protected override void UpdateForegroundColour()
@ -183,7 +184,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
} },
}; };
} }
} }
@ -237,8 +238,10 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 4 }, Margin = new MarginPadding { Right = 4 },
Size = new Vector2(20), Size = new Vector2(20),
} },
}; };
AddInternal(new HoverClickSounds());
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -72,7 +72,7 @@ namespace osu.Game.Graphics.UserInterface
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
sampleHover = audio.Sample.Get(@"UI/generic-hover"); sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click"); sampleClick = audio.Sample.Get(@"UI/generic-select");
BackgroundColour = Color4.Transparent; BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023"); BackgroundColourHover = OsuColour.FromHex(@"172023");

View File

@ -88,7 +88,8 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Expanded = true, Expanded = true,
} },
new HoverClickSounds()
}; };
Current.DisabledChanged += disabled => Current.DisabledChanged += disabled =>

View File

@ -131,7 +131,8 @@ namespace osu.Game.Graphics.UserInterface
Colour = Color4.White, Colour = Color4.White,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
} },
new HoverClickSounds()
}; };
} }

View File

@ -57,6 +57,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
}, },
new HoverClickSounds()
}; };
} }

View File

@ -0,0 +1,108 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
/// <summary>
/// A button with moving triangles in the background.
/// </summary>
public class TriangleButton : OsuButton, IFilterable
{
private Box hover;
protected Triangles Triangles;
public TriangleButton()
{
Height = 40;
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Bold",
};
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.BlueDark;
Content.Masking = true;
Content.CornerRadius = 5;
AddRange(new Drawable[]
{
Triangles = new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourDark = colours.BlueDarker,
ColourLight = colours.Blue,
},
hover = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingMode.Additive,
Colour = Color4.White.Opacity(0.1f),
Alpha = 0,
},
});
Enabled.ValueChanged += enabled_ValueChanged;
Enabled.TriggerChange();
}
private void enabled_ValueChanged(bool enabled)
{
this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint);
}
protected override bool OnHover(InputState state)
{
hover.FadeIn(200);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(200);
base.OnHoverLost(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
Content.ScaleTo(0.9f, 4000, Easing.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
Content.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
public IEnumerable<string> FilterTerms => new[] { Text };
public bool MatchingFilter
{
set
{
this.FadeTo(value ? 1 : 0);
}
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Development; using osu.Framework.Development;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -137,6 +138,10 @@ namespace osu.Game
Beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap); Beatmap = new NonNullableBindable<WorkingBeatmap>(defaultBeatmap);
BeatmapManager.DefaultBeatmap = defaultBeatmap; BeatmapManager.DefaultBeatmap = defaultBeatmap;
// tracks play so loud our samples can't keep up.
// this adds a global reduction of track volume for the time being.
Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8));
Beatmap.ValueChanged += b => Beatmap.ValueChanged += b =>
{ {
var trackLoaded = lastBeatmap?.TrackLoaded ?? false; var trackLoaded = lastBeatmap?.TrackLoaded ?? false;

View File

@ -234,7 +234,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void handleBeatmapAdd(BeatmapSetInfo beatmap) private void handleBeatmapAdd(BeatmapSetInfo beatmap)
{ {
if (beatmap.OnlineBeatmapSetID == BeatmapSet.OnlineBeatmapSetID) if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
downloadButtonsContainer.FadeOut(transition_duration); downloadButtonsContainer.FadeOut(transition_duration);
} }

View File

@ -222,7 +222,7 @@ namespace osu.Game.Overlays.Chat
} }
private class MessageSender : ClickableContainer, IHasContextMenu private class MessageSender : OsuClickableContainer, IHasContextMenu
{ {
private readonly User sender; private readonly User sender;

View File

@ -17,6 +17,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using System; using System;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat namespace osu.Game.Overlays.Chat
{ {
@ -259,7 +260,7 @@ namespace osu.Game.Overlays.Chat
}; };
} }
public class CloseButton : ClickableContainer public class CloseButton : OsuClickableContainer
{ {
private readonly SpriteIcon icon; private readonly SpriteIcon icon;

View File

@ -50,7 +50,7 @@ namespace osu.Game.Overlays
{ {
if (beatmapSets?.Equals(value) ?? false) return; if (beatmapSets?.Equals(value) ?? false) return;
beatmapSets = value; beatmapSets = value?.ToList();
if (beatmapSets == null) return; if (beatmapSets == null) return;
@ -65,8 +65,6 @@ namespace osu.Game.Overlays
} }
ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags)); ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags));
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
} }
} }
@ -222,7 +220,11 @@ namespace osu.Game.Overlays
switch (displayStyle) switch (displayStyle)
{ {
case PanelDisplayStyle.Grid: case PanelDisplayStyle.Grid:
return new DirectGridPanel(b); return new DirectGridPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
};
default: default:
return new DirectListPanel(b); return new DirectListPanel(b);
} }
@ -282,7 +284,11 @@ namespace osu.Game.Overlays
var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList();
// may not need scheduling; loads async internally. // may not need scheduling; loads async internally.
Schedule(() => BeatmapSets = sets); Schedule(() =>
{
BeatmapSets = sets;
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
});
}); });
}; };

View File

@ -55,7 +55,7 @@ namespace osu.Game.Overlays.KeyBinding
} }
} }
public class ResetButton : OsuButton public class ResetButton : TriangleButton
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)

View File

@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI;
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -148,7 +149,7 @@ namespace osu.Game.Overlays.Mods
// the mods from Mod, only multiple if Mod is a MultiMod // the mods from Mod, only multiple if Mod is a MultiMod
public override Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex); public virtual Mod SelectedMod => Mods.ElementAtOrDefault(SelectedIndex);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
@ -253,6 +254,7 @@ namespace osu.Game.Overlays.Mods
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
TextSize = 18, TextSize = 18,
}, },
new HoverClickSounds()
}; };
Mod = mod; Mod = mod;

View File

@ -3,7 +3,6 @@
using OpenTK; using OpenTK;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Overlays.Mods namespace osu.Game.Overlays.Mods
{ {
@ -12,8 +11,6 @@ namespace osu.Game.Overlays.Mods
/// </summary> /// </summary>
public class ModButtonEmpty : Container public class ModButtonEmpty : Container
{ {
public virtual Mod SelectedMod => null;
public ModButtonEmpty() public ModButtonEmpty()
{ {
Size = new Vector2(100f); Size = new Vector2(100f);

View File

@ -17,6 +17,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
new SettingsCheckbox
{
LabelText = "Show converted beatmaps",
Bindable = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps),
},
new SettingsSlider<double, StarSlider> new SettingsSlider<double, StarSlider>
{ {
LabelText = "Display beatmaps from", LabelText = "Display beatmaps from",
@ -33,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{ {
LabelText = "Random beatmap selection", LabelText = "Random beatmap selection",
Bindable = config.GetBindable<SelectionRandomType>(OsuSetting.SelectionRandomType), Bindable = config.GetBindable<SelectionRandomType>(OsuSetting.SelectionRandomType),
}, }
}; };
} }

View File

@ -12,9 +12,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{ {
public class GeneralSettings : SettingsSubsection public class GeneralSettings : SettingsSubsection
{ {
private OsuButton importButton; private TriangleButton importButton;
private OsuButton deleteButton; private TriangleButton deleteButton;
private OsuButton restoreButton; private TriangleButton restoreButton;
protected override string Header => "General"; protected override string Header => "General";

View File

@ -6,7 +6,7 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public class SettingsButton : OsuButton public class SettingsButton : TriangleButton
{ {
public SettingsButton() public SettingsButton()
{ {

View File

@ -12,14 +12,14 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public class SidebarButton : Container public class SidebarButton : OsuButton
{ {
private readonly SpriteIcon drawableIcon; private readonly SpriteIcon drawableIcon;
private readonly SpriteText headerText; private readonly SpriteText headerText;
private readonly Box backgroundBox;
private readonly Box selectionIndicator; private readonly Box selectionIndicator;
private readonly Container text; private readonly Container text;
public Action<SettingsSection> Action; public Action<SettingsSection> Action;
@ -61,17 +61,14 @@ namespace osu.Game.Overlays.Settings
public SidebarButton() public SidebarButton()
{ {
BackgroundColour = OsuColour.Gray(60);
Background.Alpha = 0;
Height = Sidebar.DEFAULT_WIDTH; Height = Sidebar.DEFAULT_WIDTH;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Children = new Drawable[]
AddRange(new Drawable[]
{ {
backgroundBox = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingMode.Additive,
Colour = OsuColour.Gray(60),
Alpha = 0,
},
text = new Container text = new Container
{ {
Width = Sidebar.DEFAULT_WIDTH, Width = Sidebar.DEFAULT_WIDTH,
@ -101,7 +98,7 @@ namespace osu.Game.Overlays.Settings
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
} }
}; });
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -113,20 +110,19 @@ namespace osu.Game.Overlays.Settings
protected override bool OnClick(InputState state) protected override bool OnClick(InputState state)
{ {
Action?.Invoke(section); Action?.Invoke(section);
backgroundBox.FlashColour(Color4.White, 400); return base.OnClick(state);
return true;
} }
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
backgroundBox.FadeTo(0.4f, 200); Background.FadeTo(0.4f, 200);
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
backgroundBox.FadeTo(0, 200); Background.FadeTo(0, 200);
base.OnHoverLost(state); base.OnHoverLost(state);
} }
} }
} }

View File

@ -13,6 +13,7 @@ using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Toolbar
private readonly SpriteText tooltip2; private readonly SpriteText tooltip2;
protected FillFlowContainer Flow; protected FillFlowContainer Flow;
public ToolbarButton() public ToolbarButton() : base(HoverSampleSet.Loud)
{ {
Width = WIDTH; Width = WIDTH;
RelativeSizeAxes = Axes.Y; RelativeSizeAxes = Axes.Y;
@ -195,4 +196,4 @@ namespace osu.Game.Overlays.Toolbar
}; };
} }
} }
} }

View File

@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Replays
{ {
public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0); public Vector2 Position => new Vector2(MouseX ?? 0, MouseY ?? 0);
public bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight); public virtual bool IsImportant => MouseX.HasValue && MouseY.HasValue && (MouseLeft || MouseRight);
public float? MouseX; public float? MouseX;
public float? MouseY; public float? MouseY;
@ -68,4 +68,4 @@ namespace osu.Game.Rulesets.Replays
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}"; return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
} }
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets namespace osu.Game.Rulesets
@ -49,6 +50,8 @@ namespace osu.Game.Rulesets
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null); public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null);
public virtual PerformanceCalculator CreatePerformanceCalculator(Beatmap beatmap, Score score) => null;
public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle };
public abstract string Description { get; } public abstract string Description { get; }

View File

@ -0,0 +1,35 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// 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.Objects;
namespace osu.Game.Rulesets.Scoring
{
public abstract class PerformanceCalculator
{
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);
}
public abstract class PerformanceCalculator<TObject> : PerformanceCalculator
where TObject : HitObject
{
private readonly Dictionary<string, double> attributes = new Dictionary<string, double>();
protected IDictionary<string, double> Attributes => attributes;
protected readonly Beatmap<TObject> Beatmap;
protected readonly Score Score;
protected PerformanceCalculator(Ruleset ruleset, Beatmap beatmap, Score score)
{
Beatmap = CreateBeatmapConverter().Convert(beatmap);
Score = score;
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap, score.Mods);
diffCalc.Calculate(attributes);
}
protected abstract BeatmapConverter<TObject> CreateBeatmapConverter();
}
}

View File

@ -174,7 +174,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
sampleHover = audio.Sample.Get(@"Menu/hover"); sampleHover = audio.Sample.Get(@"Menu/button-hover");
if (!string.IsNullOrEmpty(sampleName)) if (!string.IsNullOrEmpty(sampleName))
sampleClick = audio.Sample.Get($@"Menu/{sampleName}"); sampleClick = audio.Sample.Get($@"Menu/{sampleName}");
} }

View File

@ -117,13 +117,13 @@ namespace osu.Game.Screens.Menu
}, },
}; };
buttonsPlay.Add(new Button(@"solo", @"select-6", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new Button(@"multi", @"select-5", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M)); buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M));
buttonsPlay.Add(new Button(@"chart", @"select-5", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
buttonsTopLevel.Add(new Button(@"play", @"select-1", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P)); buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), onPlay, WEDGE_WIDTH, Key.P));
buttonsTopLevel.Add(new Button(@"osu!editor", @"select-5", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E));
buttonsTopLevel.Add(new Button(@"osu!direct", string.Empty, FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D));
buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), onExit, 0, Key.Q)); buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), onExit, 0, Key.Q));
buttonFlow.AddRange(buttonsPlay); buttonFlow.AddRange(buttonsPlay);
@ -134,7 +134,7 @@ namespace osu.Game.Screens.Menu
private void load(AudioManager audio, OsuGame game = null) private void load(AudioManager audio, OsuGame game = null)
{ {
toolbar = game?.Toolbar; toolbar = game?.Toolbar;
sampleBack = audio.Sample.Get(@"Menu/select-4"); sampleBack = audio.Sample.Get(@"Menu/button-back-select");
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
@ -180,19 +180,21 @@ namespace osu.Game.Screens.Menu
State = MenuState.TopLevel; State = MenuState.TopLevel;
} }
private void onOsuLogo() private bool onOsuLogo()
{ {
switch (state) switch (state)
{ {
default:
return true;
case MenuState.Initial: case MenuState.Initial:
State = MenuState.TopLevel; State = MenuState.TopLevel;
return; return true;
case MenuState.TopLevel: case MenuState.TopLevel:
buttonsTopLevel.First().TriggerOnClick(); buttonsTopLevel.First().TriggerOnClick();
return; return false;
case MenuState.Play: case MenuState.Play:
buttonsPlay.First().TriggerOnClick(); buttonsPlay.First().TriggerOnClick();
return; return false;
} }
} }

View File

@ -48,7 +48,10 @@ namespace osu.Game.Screens.Menu
private readonly Triangles triangles; private readonly Triangles triangles;
public Action Action; /// <summary>
/// Return value decides whether the logo should play its own sample for the click action.
/// </summary>
public Func<bool> Action;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f; public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f;
@ -248,8 +251,8 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures, AudioManager audio) private void load(TextureStore textures, AudioManager audio)
{ {
sampleClick = audio.Sample.Get(@"Menu/select-2"); sampleClick = audio.Sample.Get(@"Menu/osu-logo-select");
sampleBeat = audio.Sample.Get(@"Menu/heartbeat"); sampleBeat = audio.Sample.Get(@"Menu/osu-logo-heartbeat");
logo.Texture = textures.Get(@"Menu/logo"); logo.Texture = textures.Get(@"Menu/logo");
ripple.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo");
@ -354,13 +357,12 @@ namespace osu.Game.Screens.Menu
{ {
if (!interactive) return false; if (!interactive) return false;
sampleClick.Play(); if (Action?.Invoke() ?? true)
sampleClick.Play();
flashLayer.ClearTransforms(); flashLayer.ClearTransforms();
flashLayer.Alpha = 0.4f; flashLayer.Alpha = 0.4f;
flashLayer.FadeOut(1500, Easing.OutExpo); flashLayer.FadeOut(1500, Easing.OutExpo);
Action?.Invoke();
return true; return true;
} }

View File

@ -70,7 +70,7 @@ namespace osu.Game.Screens
if (osuGame != null) if (osuGame != null)
Ruleset.BindTo(osuGame.Ruleset); Ruleset.BindTo(osuGame.Ruleset);
sampleExit = audio.Sample.Get(@"UI/melodic-1"); sampleExit = audio.Sample.Get(@"UI/screen-back");
} }
protected override void OnResuming(Screen last) protected override void OnResuming(Screen last)

View File

@ -22,8 +22,6 @@ namespace osu.Game.Screens.Play
{ {
public bool IsPaused { get; private set; } public bool IsPaused { get; private set; }
public bool AllowExit => IsPaused && pauseOverlay.Alpha == 1;
public Func<bool> CheckCanPause; public Func<bool> CheckCanPause;
private const double pause_cooldown = 1000; private const double pause_cooldown = 1000;

View File

@ -46,6 +46,8 @@ namespace osu.Game.Screens.Play
public bool HasFailed { get; private set; } public bool HasFailed { get; private set; }
public bool AllowPause { get; set; } = true;
public int RestartCount; public int RestartCount;
private IAdjustableClock adjustableSourceClock; private IAdjustableClock adjustableSourceClock;
@ -158,7 +160,7 @@ namespace osu.Game.Screens.Play
FramedClock = offsetClock, FramedClock = offsetClock,
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = Exit,
CheckCanPause = () => ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
Retries = RestartCount, Retries = RestartCount,
OnPause = () => { OnPause = () => {
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
@ -308,7 +310,7 @@ namespace osu.Game.Screens.Play
if (!loadedSuccessfully) if (!loadedSuccessfully)
return; return;
(Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, Easing.OutQuint); (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1000, Easing.OutQuint);
dimLevel.ValueChanged += dimLevel_ValueChanged; dimLevel.ValueChanged += dimLevel_ValueChanged;
showStoryboard.ValueChanged += showStoryboard_ValueChanged; showStoryboard.ValueChanged += showStoryboard_ValueChanged;
@ -355,7 +357,7 @@ namespace osu.Game.Screens.Play
protected override bool OnExiting(Screen next) protected override bool OnExiting(Screen next)
{ {
if (HasFailed || !ValidForResume || pauseContainer?.AllowExit != false || RulesetContainer?.HasReplayLoaded != false) if (!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused != false || RulesetContainer?.HasReplayLoaded != false)
{ {
// In the case of replays, we may have changed the playback rate. // In the case of replays, we may have changed the playback rate.
applyRateFromMods(); applyRateFromMods();

View File

@ -29,11 +29,17 @@ namespace osu.Game.Screens.Select.Details
if (value == metrics) return; if (value == metrics) return;
metrics = value; metrics = value;
var ratings = Metrics.Ratings.ToList(); const int rating_range = 10;
negativeRatings.Text = ratings.GetRange(0, ratings.Count / 2 + 1).Sum().ToString();
positiveRatings.Text = ratings.GetRange(ratings.Count / 2 + 1, ratings.Count / 2).Sum().ToString(); var ratings = Metrics.Ratings.Skip(1).Take(rating_range); // adjust for API returning weird empty data at 0.
ratingsBar.Length = (float)ratings.GetRange(0, ratings.Count / 2 + 1).Sum() / ratings.Sum();
graph.Values = Metrics.Ratings.Select(r => (float)r); var negativeCount = ratings.Take(rating_range / 2).Sum();
var totalCount = ratings.Sum();
negativeRatings.Text = negativeCount.ToString();
positiveRatings.Text = (totalCount - negativeCount).ToString();
ratingsBar.Length = totalCount == 0 ? 0 : (float)negativeCount / totalCount;
graph.Values = ratings.Take(rating_range).Select(r => (float)r);
} }
} }

View File

@ -15,6 +15,7 @@ using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container; using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
@ -60,6 +61,7 @@ namespace osu.Game.Screens.Select
Group = group, Group = group,
Sort = sort, Sort = sort,
SearchText = searchTextBox.Text, SearchText = searchTextBox.Text,
AllowConvertedBeatmaps = showConverted,
Ruleset = ruleset Ruleset = ruleset
}; };
@ -163,17 +165,24 @@ namespace osu.Game.Screens.Select
private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
private Bindable<bool> showConverted;
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, OsuGame osu) private void load(OsuColour colours, OsuGame osu, OsuConfigManager config)
{ {
sortTabs.AccentColour = colours.GreenLight; sortTabs.AccentColour = colours.GreenLight;
showConverted = config.GetBindable<bool>(OsuSetting.ShowConvertedBeatmaps);
showConverted.ValueChanged += val => updateCriteria();
if (osu != null) if (osu != null)
ruleset.BindTo(osu.Ruleset); ruleset.BindTo(osu.Ruleset);
ruleset.ValueChanged += val => FilterChanged?.Invoke(CreateCriteria()); ruleset.ValueChanged += val => updateCriteria();
ruleset.TriggerChange(); ruleset.TriggerChange();
} }
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseMove(InputState state) => true; protected override bool OnMouseMove(InputState state) => true;
@ -182,4 +191,4 @@ namespace osu.Game.Screens.Select
protected override bool OnDragStart(InputState state) => true; protected override bool OnDragStart(InputState state) => true;
} }
} }

View File

@ -16,6 +16,7 @@ namespace osu.Game.Screens.Select
public SortMode Sort; public SortMode Sort;
public string SearchText; public string SearchText;
public RulesetInfo Ruleset; public RulesetInfo Ruleset;
public bool AllowConvertedBeatmaps;
public void Filter(List<BeatmapGroup> groups) public void Filter(List<BeatmapGroup> groups)
{ {
@ -23,7 +24,7 @@ namespace osu.Game.Screens.Select
{ {
var set = g.BeatmapSet; var set = g.BeatmapSet;
bool hasCurrentMode = set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0));
bool match = hasCurrentMode; bool match = hasCurrentMode;

View File

@ -4,6 +4,8 @@
using System.Linq; using System.Linq;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
@ -42,9 +44,13 @@ namespace osu.Game.Screens.Select
beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s)); beatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s));
} }
private SampleChannel sampleConfirm;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours, AudioManager audio)
{ {
sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue); Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
@ -128,6 +134,8 @@ namespace osu.Game.Screens.Select
Beatmap.Value.Track.Looping = false; Beatmap.Value.Track.Looping = false;
Beatmap.Disabled = true; Beatmap.Disabled = true;
sampleConfirm?.Play();
LoadComponentAsync(player = new PlayerLoader(new Player()), l => Push(player)); LoadComponentAsync(player = new PlayerLoader(new Player()), l => Push(player));
} }
} }

View File

@ -203,8 +203,8 @@ namespace osu.Game.Screens.Select
Push(new Editor()); Push(new Editor());
} }
private void onBeatmapRestored(BeatmapInfo b) => carousel.UpdateBeatmap(b); private void onBeatmapRestored(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
private void onBeatmapHidden(BeatmapInfo b) => carousel.UpdateBeatmap(b); private void onBeatmapHidden(BeatmapInfo b) => Schedule(() => carousel.UpdateBeatmap(b));
private void carouselBeatmapsLoaded() private void carouselBeatmapsLoaded()
{ {
@ -332,7 +332,11 @@ namespace osu.Game.Screens.Select
logo.FadeIn(logo_transition, Easing.OutQuint); logo.FadeIn(logo_transition, Easing.OutQuint);
logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint); logo.ScaleTo(0.4f, logo_transition, Easing.OutQuint);
logo.Action = () => carouselRaisedStart(); logo.Action = () =>
{
carouselRaisedStart();
return false;
};
} }
protected override void LogoExiting(OsuLogo logo) protected override void LogoExiting(OsuLogo logo)
@ -413,7 +417,7 @@ namespace osu.Game.Screens.Select
if (backgroundModeBeatmap != null) if (backgroundModeBeatmap != null)
{ {
backgroundModeBeatmap.Beatmap = beatmap; backgroundModeBeatmap.Beatmap = beatmap;
backgroundModeBeatmap.BlurTo(background_blur, 1000); backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint);
backgroundModeBeatmap.FadeTo(1, 250); backgroundModeBeatmap.FadeTo(1, 250);
} }

View File

@ -193,21 +193,21 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuButton new TriangleButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Text = "Begin random", Text = "Begin random",
Action = teamsContainer.StartScrolling, Action = teamsContainer.StartScrolling,
}, },
new OsuButton new TriangleButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Text = "Stop random", Text = "Stop random",
Action = teamsContainer.StopScrolling, Action = teamsContainer.StopScrolling,
}, },
new OsuButton new TriangleButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
@ -232,7 +232,7 @@ namespace osu.Game.Screens.Tournament
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuButton new TriangleButton
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -0,0 +1,48 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Screens;
using osu.Game.Screens;
namespace osu.Game.Tests.Visual
{
/// <summary>
/// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
/// </summary>
public abstract class ScreenTestCase : OsuTestCase
{
private readonly TestOsuScreen baseScreen;
protected ScreenTestCase()
{
Add(baseScreen = new TestOsuScreen());
}
protected void LoadScreen(OsuScreen screen) => baseScreen.LoadScreen(screen);
public class TestOsuScreen : OsuScreen
{
private OsuScreen nextScreen;
public void LoadScreen(OsuScreen screen) => Schedule(() =>
{
nextScreen = screen;
if (IsCurrentScreen)
{
Push(screen);
nextScreen = null;
}
else
MakeCurrent();
});
protected override void OnResuming(Screen last)
{
base.OnResuming(last);
if (nextScreen != null)
LoadScreen(nextScreen);
}
}
}
}

View File

@ -0,0 +1,395 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Caching;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Tests.Visual
{
public abstract class TestCasePerformancePoints : OsuTestCase
{
protected TestCasePerformancePoints(Ruleset ruleset)
{
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new BeatmapList(ruleset)
}
}
},
null,
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new StarRatingGrid()
}
}
},
null,
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.5f,
},
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new PerformanceList()
}
}
},
}
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 20),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 20)
}
};
}
private class BeatmapList : CompositeDrawable
{
private readonly Container<BeatmapDisplay> beatmapDisplays;
private readonly Ruleset ruleset;
public BeatmapList(Ruleset ruleset)
{
this.ruleset = ruleset;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = beatmapDisplays = new FillFlowContainer<BeatmapDisplay>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4)
};
}
[BackgroundDependencyLoader]
private void load(BeatmapManager beatmaps)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID < 0 || b.RulesetID == ruleset.LegacyID);
allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b)));
}
private class BeatmapDisplay : CompositeDrawable, IHasTooltip
{
private readonly OsuSpriteText text;
private readonly BeatmapInfo beatmap;
private BeatmapManager beatmaps;
private OsuGameBase osuGame;
private bool isSelected;
public string TooltipText => text.Text;
public BeatmapDisplay(BeatmapInfo beatmap)
{
this.beatmap = beatmap;
AutoSizeAxes = Axes.Both;
InternalChild = text = new OsuSpriteText();
}
protected override bool OnClick(InputState state)
{
if (osuGame.Beatmap.Value.BeatmapInfo.ID == beatmap.ID)
return false;
osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
isSelected = true;
return true;
}
protected override bool OnHover(InputState state)
{
if (isSelected)
return false;
this.FadeColour(Color4.Yellow, 100);
return true;
}
protected override void OnHoverLost(InputState state)
{
if (isSelected)
return;
this.FadeColour(Color4.White, 100);
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
{
this.osuGame = osuGame;
this.beatmaps = beatmaps;
var working = beatmaps.GetWorkingBeatmap(beatmap);
text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]";
osuGame.Beatmap.ValueChanged += beatmapChanged;
}
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
if (isSelected)
this.FadeColour(Color4.White, 100);
isSelected = false;
}
}
}
private class PerformanceList : CompositeDrawable
{
private readonly FillFlowContainer<PerformanceDisplay> scores;
private APIAccess api;
public PerformanceList()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = scores = new FillFlowContainer<PerformanceDisplay>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4)
};
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame, APIAccess api)
{
this.api = api;
if (!api.IsLoggedIn)
{
InternalChild = new SpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Please login to see online scores",
};
}
osuGame.Beatmap.ValueChanged += beatmapChanged;
}
private GetScoresRequest lastRequest;
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
lastRequest?.Cancel();
scores.Clear();
if (!api.IsLoggedIn)
return;
lastRequest = new GetScoresRequest(newBeatmap.BeatmapInfo);
lastRequest.Success += res => res.Scores.ForEach(s => scores.Add(new PerformanceDisplay(s, newBeatmap.Beatmap)));
api.Queue(lastRequest);
}
private class PerformanceDisplay : CompositeDrawable
{
private readonly OsuSpriteText text;
private readonly Score score;
private readonly Beatmap beatmap;
public PerformanceDisplay(Score score, Beatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = text = new OsuSpriteText();
}
[BackgroundDependencyLoader]
private void load()
{
var ruleset = beatmap.BeatmapInfo.Ruleset.CreateInstance();
var calculator = ruleset.CreatePerformanceCalculator(beatmap, score);
if (calculator == null)
return;
var attributes = new Dictionary<string, double>();
double performance = calculator.Calculate(attributes);
text.Text = $"{score.User.Username} -> online: {score.PP:n2}pp | local: {performance:n2}pp";
}
}
}
private class StarRatingGrid : CompositeDrawable
{
private readonly FillFlowContainer<OsuCheckbox> modFlow;
private readonly OsuSpriteText totalText;
private readonly FillFlowContainer categoryTexts;
public StarRatingGrid()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
modFlow = new FillFlowContainer<OsuCheckbox>
{
Name = "Checkbox flow",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(4, 4)
},
new FillFlowContainer
{
Name = "Information display",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 4),
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
totalText = new OsuSpriteText { TextSize = 24 },
categoryTexts = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical
}
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
{
osuGame.Beatmap.ValueChanged += beatmapChanged;
}
private Cached informationCache = new Cached();
private Ruleset ruleset;
private WorkingBeatmap beatmap;
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
beatmap = newBeatmap;
modFlow.Clear();
ruleset = newBeatmap.BeatmapInfo.Ruleset.CreateInstance();
foreach (var mod in ruleset.GetAllMods())
{
var checkBox = new OsuCheckbox
{
RelativeSizeAxes = Axes.None,
Width = 50,
LabelText = mod.ShortenedName
};
checkBox.Current.ValueChanged += v => informationCache.Invalidate();
modFlow.Add(checkBox);
}
informationCache.Invalidate();
}
protected override void Update()
{
base.Update();
if (ruleset == null)
return;
if (!informationCache.IsValid)
{
totalText.Text = string.Empty;
categoryTexts.Clear();
var allMods = ruleset.GetAllMods().ToList();
Mod[] activeMods = modFlow.Where(c => c.Current.Value).Select(c => allMods.First(m => m.ShortenedName == c.LabelText)).ToArray();
var diffCalc = ruleset.CreateDifficultyCalculator(beatmap.Beatmap, activeMods);
if (diffCalc != null)
{
var categories = new Dictionary<string, double>();
double totalSr = diffCalc.Calculate(categories);
totalText.Text = $"Star rating: {totalSr:n2}";
foreach (var kvp in categories)
categoryTexts.Add(new OsuSpriteText { Text = $"{kvp.Key}: {kvp.Value:n2}" });
}
informationCache.Validate();
}
}
}
}
}

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