Merge branch 'master' of github.com:ppy/osu into #7146

This commit is contained in:
Willy Tu
2020-01-03 11:32:06 -08:00
163 changed files with 3017 additions and 1511 deletions

View File

@ -53,7 +53,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1215.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1219.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2019.1227.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Text;
using DiscordRPC; using DiscordRPC;
using DiscordRPC.Message; using DiscordRPC.Message;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -43,6 +45,10 @@ namespace osu.Desktop
}; };
client.OnReady += onReady; client.OnReady += onReady;
// safety measure for now, until we performance test / improve backoff for failed connections.
client.OnConnectionFailed += (_, __) => client.Deinitialize();
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network); client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u => (user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
@ -77,8 +83,8 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null) if (status.Value is UserStatusOnline && activity.Value != null)
{ {
presence.State = activity.Value.Status; presence.State = truncate(activity.Value.Status);
presence.Details = getDetails(activity.Value); presence.Details = truncate(getDetails(activity.Value));
} }
else else
{ {
@ -96,6 +102,27 @@ namespace osu.Desktop
client.SetPresence(presence); client.SetPresence(presence);
} }
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
private string truncate(string str)
{
if (Encoding.UTF8.GetByteCount(str) <= 128)
return str;
ReadOnlyMemory<char> strMem = str.AsMemory();
do
{
strMem = strMem[..^1];
} while (Encoding.UTF8.GetByteCount(strMem.Span) + ellipsis_length > 128);
return string.Create(strMem.Length + 1, strMem, (span, mem) =>
{
mem.Span.CopyTo(span);
span[^1] = '…';
});
}
private string getDetails(UserActivity activity) private string getDetails(UserActivity activity)
{ {
switch (activity) switch (activity)

View File

@ -4,7 +4,7 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System.Linq;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -14,12 +14,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
public class CatchBeatmapConverter : BeatmapConverter<CatchHitObject> public class CatchBeatmapConverter : BeatmapConverter<CatchHitObject>
{ {
public CatchBeatmapConverter(IBeatmap beatmap) public CatchBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap) : base(beatmap, ruleset)
{ {
} }
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap) protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
{ {

View File

@ -24,13 +24,14 @@ using System;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
public class CatchRuleset : Ruleset public class CatchRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new CatchScoreProcessor(beatmap); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap, this);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap);
public const string SHORT_NAME = "fruits"; public const string SHORT_NAME = "fruits";
@ -106,6 +107,12 @@ namespace osu.Game.Rulesets.Catch
new CatchModFlashlight(), new CatchModFlashlight(),
}; };
case ModType.Conversion:
return new Mod[]
{
new CatchModDifficultyAdjust(),
};
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
@ -128,13 +135,15 @@ namespace osu.Game.Rulesets.Catch
public override string ShortName => SHORT_NAME; public override string ShortName => SHORT_NAME;
public override string PlayingVerb => "Catching fruit";
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 2; public int LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
} }

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDifficultyAdjust : ModDifficultyAdjust
{
[SettingSource("Fruit Size", "Override a beatmap's set CS.")]
public BindableNumber<float> CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
MinValue = 1,
MaxValue = 10,
Default = 5,
Value = 5,
};
[SettingSource("Approach Rate", "Override a beatmap's set AR.")]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,
MinValue = 1,
MaxValue = 10,
Default = 5,
Value = 5,
};
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
TransferSetting(CircleSize, difficulty.CircleSize);
TransferSetting(ApproachRate, difficulty.ApproachRate);
}
protected override void ApplySettings(BeatmapDifficulty difficulty)
{
base.ApplySettings(difficulty);
difficulty.CircleSize = CircleSize.Value;
difficulty.ApproachRate = ApproachRate.Value;
}
}
}

View File

@ -1,40 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring namespace osu.Game.Rulesets.Catch.Scoring
{ {
public class CatchScoreProcessor : ScoreProcessor public class CatchScoreProcessor : ScoreProcessor
{ {
public CatchScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
private float hpDrainRate;
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
}
protected override double HealthAdjustmentFactorFor(JudgementResult result)
{
switch (result.Type)
{
case HitResult.Miss:
return hpDrainRate;
default:
return 10.2 - hpDrainRate; // Award less HP as drain rate is increased
}
}
public override HitWindows CreateHitWindows() => new CatchHitWindows(); public override HitWindows CreateHitWindows() => new CatchHitWindows();
} }
} }

View File

@ -0,0 +1,46 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public abstract class SkinnableTestScene : OsuGridTestScene
{
private Skin defaultSkin;
protected SkinnableTestScene()
: base(1, 2)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager)
{
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);
}
public void SetContents(Func<Drawable> creationFunction)
{
Cell(0).Child = createProvider(null, creationFunction);
Cell(1).Child = createProvider(defaultSkin, creationFunction);
}
private Drawable createProvider(Skin skin, Func<Drawable> creationFunction)
{
var mainProvider = new SkinProvidingContainer(skin);
return mainProvider
.WithChild(new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider))
{
Child = creationFunction()
});
}
}
}

View File

@ -0,0 +1,37 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneDrawableJudgement : SkinnableTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(DrawableJudgement),
typeof(DrawableManiaJudgement)
};
public TestSceneDrawableJudgement()
{
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
{
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}));
}
}
}
}

View File

@ -0,0 +1,314 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
{
private const double time_before_head = 250;
private const double time_head = 1500;
private const double time_during_hold_1 = 2500;
private const double time_tail = 4000;
private const double time_after_tail = 5250;
private List<JudgementResult> judgementResults;
private bool allJudgedFired;
/// <summary>
/// -----[ ]-----
/// o o
/// </summary>
[Test]
public void TestNoInput()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_before_head),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss);
assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.Perfect);
}
/// <summary>
/// -----[ ]-----
/// x o
/// </summary>
[Test]
public void TestPressTooEarlyAndReleaseAfterTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
new ManiaReplayFrame(time_after_tail, ManiaAction.Key1),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// x o
/// </summary>
[Test]
public void TestPressTooEarlyAndReleaseAtTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
new ManiaReplayFrame(time_tail),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// xo x o
/// </summary>
[Test]
public void TestPressTooEarlyThenPressAtStartAndReleaseAfterTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
new ManiaReplayFrame(time_before_head + 10),
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// xo x o
/// </summary>
[Test]
public void TestPressTooEarlyThenPressAtStartAndReleaseAtTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_before_head, ManiaAction.Key1),
new ManiaReplayFrame(time_before_head + 10),
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Perfect);
}
/// <summary>
/// -----[ ]-----
/// xo o
/// </summary>
[Test]
public void TestPressAtStartAndBreak()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_head + 10),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Miss);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// xo x o
/// </summary>
[Test]
public void TestPressAtStartThenBreakThenRepressAndReleaseAfterTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_head + 10),
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// xo x o o
/// </summary>
[Test]
public void TestPressAtStartThenBreakThenRepressAndReleaseAtTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_head + 10),
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
new ManiaReplayFrame(time_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Meh);
}
/// <summary>
/// -----[ ]-----
/// x o
/// </summary>
[Test]
public void TestPressDuringNoteAndReleaseAfterTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Miss);
}
/// <summary>
/// -----[ ]-----
/// x o o
/// </summary>
[Test]
public void TestPressDuringNoteAndReleaseAtTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_during_hold_1, ManiaAction.Key1),
new ManiaReplayFrame(time_tail),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Perfect);
assertTailJudgement(HitResult.Meh);
}
/// <summary>
/// -----[ ]-----
/// xo o
/// </summary>
[Test]
public void TestPressAndReleaseAtTail()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_tail, ManiaAction.Key1),
new ManiaReplayFrame(time_tail + 10),
});
assertHeadJudgement(HitResult.Miss);
assertTickJudgement(HitResult.Miss);
assertTailJudgement(HitResult.Meh);
}
private void assertHeadJudgement(HitResult result)
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
private void assertTailJudgement(HitResult result)
=> AddAssert($"tail judged as {result}", () => judgementResults[^2].Type == result);
private void assertNoteJudgement(HitResult result)
=> AddAssert($"hold note judged as {result}", () => judgementResults[^1].Type == result);
private void assertTickJudgement(HitResult result)
=> AddAssert($"tick judged as {result}", () => judgementResults[6].Type == result); // arbitrary tick
private ScoreAccessibleReplayPlayer currentPlayer;
private void performTest(List<ReplayFrame> frames)
{
AddStep("load player", () =>
{
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<ManiaHitObject>
{
HitObjects =
{
new HoldNote
{
StartTime = time_head,
Duration = time_tail - time_head,
Column = 0,
}
},
BeatmapInfo =
{
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
Ruleset = new ManiaRuleset().RulesetInfo
},
});
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
p.OnLoadComplete += _ =>
{
p.ScoreProcessor.NewJudgement += result =>
{
if (currentPlayer == p) judgementResults.Add(result);
};
p.ScoreProcessor.AllJudged += () =>
{
if (currentPlayer == p) allJudgedFired = true;
};
};
LoadScreen(currentPlayer = p);
allJudgedFired = false;
judgementResults = new List<JudgementResult>();
});
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
AddUntilStep("Wait for all judged", () => allJudgedFired);
}
private class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)
: base(score, false, false)
{
}
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestScenePlayer : PlayerTestScene
{
public TestScenePlayer()
: base(new ManiaRuleset())
{
}
}
}

View File

@ -24,8 +24,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
private const int max_notes_for_density = 7; private const int max_notes_for_density = 7;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
public int TargetColumns; public int TargetColumns;
public bool Dual; public bool Dual;
public readonly bool IsForCurrentRuleset; public readonly bool IsForCurrentRuleset;
@ -37,10 +35,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private ManiaBeatmap beatmap; private ManiaBeatmap beatmap;
public ManiaBeatmapConverter(IBeatmap beatmap) public ManiaBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap) : base(beatmap, ruleset)
{ {
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo); IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize); var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
@ -69,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
} }
} }
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject);
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original) protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
{ {
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly. // Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
if (DrawableObject.IsLoaded) if (DrawableObject.IsLoaded)
{ {
DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail; DrawableNote note = position == HoldNotePosition.Start ? (DrawableNote)DrawableObject.Head : DrawableObject.Tail;
Anchor = note.Anchor; Anchor = note.Anchor;
Origin = note.Origin; Origin = note.Origin;

View File

@ -26,18 +26,20 @@ using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Edit;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osu.Game.Scoring; using osu.Game.Scoring;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
public class ManiaRuleset : Ruleset public class ManiaRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableManiaRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new ManiaScoreProcessor(beatmap); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score);
@ -45,6 +47,8 @@ namespace osu.Game.Rulesets.Mania
public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))
@ -151,6 +155,7 @@ namespace osu.Game.Rulesets.Mania
new ManiaModRandom(), new ManiaModRandom(),
new ManiaModDualStages(), new ManiaModDualStages(),
new ManiaModMirror(), new ManiaModMirror(),
new ManiaModDifficultyAdjust(),
}; };
case ModType.Automation: case ModType.Automation:
@ -174,11 +179,13 @@ namespace osu.Game.Rulesets.Mania
public override string ShortName => SHORT_NAME; public override string ShortName => SHORT_NAME;
public override string PlayingVerb => "Smashing keys";
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
public override int? LegacyID => 3; public int LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();

View File

@ -0,0 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDifficultyAdjust : ModDifficultyAdjust
{
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
@ -21,11 +20,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
public override bool DisplayResult => false; public override bool DisplayResult => false;
public DrawableNote Head => headContainer.Child; public DrawableHoldNoteHead Head => headContainer.Child;
public DrawableNote Tail => tailContainer.Child; public DrawableHoldNoteTail Tail => tailContainer.Child;
private readonly Container<DrawableHeadNote> headContainer; private readonly Container<DrawableHoldNoteHead> headContainer;
private readonly Container<DrawableTailNote> tailContainer; private readonly Container<DrawableHoldNoteTail> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer; private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly BodyPiece bodyPiece; private readonly BodyPiece bodyPiece;
@ -33,12 +32,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary> /// <summary>
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note. /// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
/// </summary> /// </summary>
private double? holdStartTime; public double? HoldStartTime { get; private set; }
/// <summary> /// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release. /// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary> /// </summary>
private bool hasBroken; public bool HasBroken { get; private set; }
public DrawableHoldNote(HoldNote hitObject) public DrawableHoldNote(HoldNote hitObject)
: base(hitObject) : base(hitObject)
@ -49,8 +48,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X }, bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both }, tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHeadNote> { RelativeSizeAxes = Axes.Both }, headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableTailNote> { RelativeSizeAxes = Axes.Both }, tailContainer = new Container<DrawableHoldNoteTail> { RelativeSizeAxes = Axes.Both },
}); });
AccentColour.BindValueChanged(colour => AccentColour.BindValueChanged(colour =>
@ -65,11 +64,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject) switch (hitObject)
{ {
case DrawableHeadNote head: case DrawableHoldNoteHead head:
headContainer.Child = head; headContainer.Child = head;
break; break;
case DrawableTailNote tail: case DrawableHoldNoteTail tail:
tailContainer.Child = tail; tailContainer.Child = tail;
break; break;
@ -92,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
switch (hitObject) switch (hitObject)
{ {
case TailNote _: case TailNote _:
return new DrawableTailNote(this) return new DrawableHoldNoteTail(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
@ -100,7 +99,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}; };
case Note _: case Note _:
return new DrawableHeadNote(this) return new DrawableHoldNoteHead(this)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
@ -110,7 +109,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
case HoldNoteTick tick: case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick) return new DrawableHoldNoteTick(tick)
{ {
HoldStartTime = () => holdStartTime, HoldStartTime = () => HoldStartTime,
AccentColour = { BindTarget = AccentColour } AccentColour = { BindTarget = AccentColour }
}; };
} }
@ -125,12 +124,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
} }
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (Tail.AllJudged)
ApplyResult(r => r.Type = HitResult.Perfect);
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
@ -146,146 +139,64 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.UpdateStateTransforms(state); base.UpdateStateTransforms(state);
} }
protected void BeginHold() protected override void CheckForResult(bool userTriggered, double timeOffset)
{ {
holdStartTime = Time.Current; if (Tail.AllJudged)
bodyPiece.Hitting = true; ApplyResult(r => r.Type = HitResult.Perfect);
}
protected void EndHold() if (Tail.Result.Type == HitResult.Miss)
{ HasBroken = true;
holdStartTime = null;
bodyPiece.Hitting = false;
} }
public bool OnPressed(ManiaAction action) public bool OnPressed(ManiaAction action)
{ {
// Make sure the action happened within the body of the hold note if (AllJudged)
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false; return false;
if (action != Action.Value) if (action != Action.Value)
return false; return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed beginHoldAt(Time.Current - Head.HitObject.StartTime);
// and within the limited range of the above if-statement. This state will be managed by the head note if the Head.UpdateResult();
// user has pressed during the hit windows of the head note.
BeginHold();
return true; return true;
} }
private void beginHoldAt(double timeOffset)
{
if (timeOffset < -Head.HitObject.HitWindows.WindowFor(HitResult.Miss))
return;
HoldStartTime = Time.Current;
bodyPiece.Hitting = true;
}
public bool OnReleased(ManiaAction action) public bool OnReleased(ManiaAction action)
{ {
// Make sure that the user started holding the key during the hold note if (AllJudged)
if (!holdStartTime.HasValue)
return false; return false;
if (action != Action.Value) if (action != Action.Value)
return false; return false;
EndHold(); // Make sure a hold was started
if (HoldStartTime == null)
return false;
Tail.UpdateResult();
endHold();
// If the key has been released too early, the user should not receive full score for the release // If the key has been released too early, the user should not receive full score for the release
if (!Tail.IsHit) if (!Tail.IsHit)
hasBroken = true; HasBroken = true;
return true; return true;
} }
/// <summary> private void endHold()
/// The head note of a hold.
/// </summary>
private class DrawableHeadNote : DrawableNote
{ {
private readonly DrawableHoldNote holdNote; HoldStartTime = null;
bodyPiece.Hitting = false;
public DrawableHeadNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
{
this.holdNote = holdNote;
}
public override bool OnPressed(ManiaAction action)
{
if (!base.OnPressed(action))
return false;
// If the key has been released too early, the user should not receive full score for the release
if (Result.Type == HitResult.Miss)
holdNote.hasBroken = true;
// The head note also handles early hits before the body, but we want accurate early hits to count as the body being held
// The body doesn't handle these early early hits, so we have to explicitly set the holding state here
holdNote.BeginHold();
return true;
}
}
/// <summary>
/// The tail note of a hold.
/// </summary>
private class DrawableTailNote : DrawableNote
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Tail)
{
this.holdNote = holdNote;
}
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
timeOffset /= release_window_lenience;
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
return;
ApplyResult(r =>
{
if (holdNote.hasBroken && (result == HitResult.Perfect || result == HitResult.Perfect))
result = HitResult.Good;
r.Type = result;
});
}
public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)
{
// Make sure that the user started holding the key during the hold note
if (!holdNote.holdStartTime.HasValue)
return false;
if (action != Action.Value)
return false;
UpdateResult(true);
// Handled by the hold note, which will set holding = false
return false;
}
} }
} }
} }

View File

@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// The head of a <see cref="DrawableHoldNote"/>.
/// </summary>
public class DrawableHoldNoteHead : DrawableNote
{
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Head)
{
}
public void UpdateResult() => base.UpdateResult(true);
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// The tail of a <see cref="DrawableHoldNote"/>.
/// </summary>
public class DrawableHoldNoteTail : DrawableNote
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
private readonly DrawableHoldNote holdNote;
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
: base(holdNote.HitObject.Tail)
{
this.holdNote = holdNote;
}
public void UpdateResult() => base.UpdateResult(true);
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
timeOffset /= release_window_lenience;
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
ApplyResult(r => r.Type = HitResult.Miss);
return;
}
var result = HitObject.HitWindows.ResultFor(timeOffset);
if (result == HitResult.None)
return;
ApplyResult(r =>
{
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HasBroken))
result = HitResult.Meh;
r.Type = result;
});
}
public override bool OnPressed(ManiaAction action) => false; // Handled by the hold note
public override bool OnReleased(ManiaAction action) => false; // Handled by the hold note
}
}

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Replays
public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{ {
// We don't need to fully convert, just create the converter // We don't need to fully convert, just create the converter
var converter = new ManiaBeatmapConverter(beatmap); var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling // NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage. // elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.

View File

@ -1,87 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Scoring namespace osu.Game.Rulesets.Mania.Scoring
{ {
internal class ManiaScoreProcessor : ScoreProcessor internal class ManiaScoreProcessor : ScoreProcessor
{ {
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_min = 0.75;
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_mid = 0.85;
/// <summary>
/// The hit HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_max = 1;
/// <summary>
/// The MISS HP multiplier at OD = 0.
/// </summary>
private const double hp_multiplier_miss_min = 0.5;
/// <summary>
/// The MISS HP multiplier at OD = 5.
/// </summary>
private const double hp_multiplier_miss_mid = 0.75;
/// <summary>
/// The MISS HP multiplier at OD = 10.
/// </summary>
private const double hp_multiplier_miss_max = 1;
/// <summary>
/// The MISS HP multiplier. This is multiplied to the miss hp increase.
/// </summary>
private double hpMissMultiplier = 1;
/// <summary>
/// The HIT HP multiplier. This is multiplied to hit hp increases.
/// </summary>
private double hpMultiplier = 1;
public ManiaScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
BeatmapDifficulty difficulty = beatmap.BeatmapInfo.BaseDifficulty;
hpMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_min, hp_multiplier_mid, hp_multiplier_max);
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(difficulty.DrainRate, hp_multiplier_miss_min, hp_multiplier_miss_mid, hp_multiplier_miss_max);
}
protected override void SimulateAutoplay(IBeatmap beatmap)
{
while (true)
{
base.SimulateAutoplay(beatmap);
if (!HasFailed)
break;
hpMultiplier *= 1.01;
hpMissMultiplier *= 0.98;
Reset(false);
}
}
protected override double HealthAdjustmentFactorFor(JudgementResult result)
=> result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
public override HitWindows CreateHitWindows() => new ManiaHitWindows(); public override HitWindows CreateHitWindows() => new ManiaHitWindows();
} }
} }

View File

@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Audio;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Mania.Skinning
{
public class ManiaLegacySkinTransformer : ISkin
{
private readonly ISkin source;
public ManiaLegacySkinTransformer(ISkin source)
{
this.source = source;
}
public Drawable GetDrawableComponent(ISkinComponent component)
{
switch (component)
{
case GameplaySkinComponent<HitResult> resultComponent:
return getResult(resultComponent);
}
return null;
}
private Drawable getResult(GameplaySkinComponent<HitResult> resultComponent)
{
switch (resultComponent.Component)
{
case HitResult.Miss:
return this.GetAnimation("mania-hit0", true, true);
case HitResult.Meh:
return this.GetAnimation("mania-hit50", true, true);
case HitResult.Ok:
return this.GetAnimation("mania-hit100", true, true);
case HitResult.Good:
return this.GetAnimation("mania-hit200", true, true);
case HitResult.Great:
return this.GetAnimation("mania-hit300", true, true);
case HitResult.Perfect:
return this.GetAnimation("mania-hit300g", true, true);
}
return null;
}
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) =>
source.GetConfig<TLookup, TValue>(lookup);
}
}

View File

@ -22,10 +22,57 @@ namespace osu.Game.Rulesets.Mania.UI.Components
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>(); private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Drawable hitTarget;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
{
InternalChildren = new[]
{
hitTarget = new DefaultHitTarget
{
RelativeSizeAxes = Axes.X,
},
hitObjectContainer
};
}
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(dir =>
{
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
hitTarget.Anchor = hitTarget.Origin = anchor;
}, true);
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
if (hitTarget is IHasAccentColour colouredHitTarget)
colouredHitTarget.AccentColour = accentColour;
}
}
private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly Container hitTargetLine; private readonly Container hitTargetLine;
private readonly Drawable hitTargetBar; private readonly Drawable hitTargetBar;
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer) public DefaultHitTarget()
{ {
InternalChildren = new[] InternalChildren = new[]
{ {
@ -43,7 +90,6 @@ namespace osu.Game.Rulesets.Mania.UI.Components
Masking = true, Masking = true,
Child = new Box { RelativeSizeAxes = Axes.Both } Child = new Box { RelativeSizeAxes = Axes.Both }
}, },
hitObjectContainer
}; };
} }
@ -95,4 +141,5 @@ namespace osu.Game.Rulesets.Mania.UI.Components
}; };
} }
} }
}
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, SkinManager skinManager) private void load(AudioManager audio, SkinManager skinManager)
{ {
var dllStore = new DllResourceStore("osu.Game.Rulesets.Osu.Tests.dll"); var dllStore = new DllResourceStore(typeof(SkinnableTestScene).Assembly);
metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true); metricsSkin = new TestLegacySkin(new SkinInfo(), new NamespacedResourceStore<byte[]>(dllStore, "Resources/metrics_skin"), audio, true);
defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info); defaultSkin = skinManager.GetSkin(DefaultLegacySkin.Info);

View File

@ -0,0 +1,155 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneLegacyBeatmapSkin : OsuTestScene
{
[Resolved]
private AudioManager audio { get; set; }
[TestCase(true)]
[TestCase(false)]
public void TestBeatmapComboColours(bool customSkinColoursPresent)
{
ExposedPlayer player = null;
AddStep("load coloured beatmap", () => player = loadBeatmap(customSkinColoursPresent, true));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is beatmap skin colours", () => player.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
}
[Test]
public void TestBeatmapNoComboColours()
{
ExposedPlayer player = null;
AddStep("load no-colour beatmap", () => player = loadBeatmap(false, false));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is default user skin colours", () => player.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
}
[Test]
public void TestBeatmapNoComboColoursSkinOverride()
{
ExposedPlayer player = null;
AddStep("load custom-skin colour", () => player = loadBeatmap(true, false));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("is custom user skin colours", () => player.UsableComboColours.SequenceEqual(TestSkin.Colours));
}
private ExposedPlayer loadBeatmap(bool userHasCustomColours, bool beatmapHasColours)
{
ExposedPlayer player;
Beatmap.Value = new CustomSkinWorkingBeatmap(audio, beatmapHasColours);
Child = new OsuScreenStack(player = new ExposedPlayer(userHasCustomColours)) { RelativeSizeAxes = Axes.Both };
return player;
}
private class ExposedPlayer : Player
{
private readonly bool userHasCustomColours;
public ExposedPlayer(bool userHasCustomColours)
: base(false, false)
{
this.userHasCustomColours = userHasCustomColours;
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<ISkinSource>(new TestSkin(userHasCustomColours));
return dependencies;
}
public IReadOnlyList<Color4> UsableComboColours =>
GameplayClockContainer.ChildrenOfType<BeatmapSkinProvidingContainer>()
.First()
.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value;
}
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
{
private readonly bool hasColours;
public CustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
: base(new Beatmap
{
BeatmapInfo =
{
BeatmapSet = new BeatmapSetInfo(),
Ruleset = new OsuRuleset().RulesetInfo,
},
HitObjects = { new HitCircle { Position = new Vector2(256, 192) } }
}, null, null, audio)
{
this.hasColours = hasColours;
}
protected override ISkin GetSkin() => new TestBeatmapSkin(BeatmapInfo, hasColours);
}
private class TestBeatmapSkin : LegacyBeatmapSkin
{
public static Color4[] Colours { get; } =
{
new Color4(50, 100, 150, 255),
new Color4(40, 80, 120, 255),
};
public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
: base(beatmap, new ResourceStore<byte[]>(), null)
{
if (hasColours)
Configuration.AddComboColours(Colours);
}
}
private class TestSkin : LegacySkin, ISkinSource
{
public static Color4[] Colours { get; } =
{
new Color4(150, 100, 50, 255),
new Color4(20, 20, 20, 255),
};
public TestSkin(bool hasCustomColours)
: base(new SkinInfo(), null, null, string.Empty)
{
if (hasCustomColours)
Configuration.AddComboColours(Colours);
}
public event Action SourceChanged
{
add { }
remove { }
}
}
}
}

View File

@ -33,8 +33,8 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(CircularDistanceSnapGrid) typeof(CircularDistanceSnapGrid)
}; };
[Cached(typeof(IEditorBeatmap))] [Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached] [Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneOsuDistanceSnapGrid() public TestSceneOsuDistanceSnapGrid()
{ {
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); editorBeatmap = new EditorBeatmap(new OsuBeatmap());
} }
[SetUp] [SetUp]

View File

@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Tests
EndTime = 6000, EndTime = 6000,
}, },
// placeholder object to avoid hitting the results screen // placeholder object to avoid hitting the results screen
new HitObject new HitCircle
{ {
StartTime = 99999, StartTime = 99999,
} }

View File

@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System; using System.Linq;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{ {
public class OsuBeatmapConverter : BeatmapConverter<OsuHitObject> public class OsuBeatmapConverter : BeatmapConverter<OsuHitObject>
{ {
public OsuBeatmapConverter(IBeatmap beatmap) public OsuBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap) : base(beatmap, ruleset)
{ {
} }
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasPosition) }; public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap) protected override IEnumerable<OsuHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
{ {

View File

@ -91,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Edit
if (sourceIndex == -1) if (sourceIndex == -1)
return null; return null;
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; HitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
int targetIndex = sourceIndex + targetOffset; int targetIndex = sourceIndex + targetOffset;
OsuHitObject targetObject = null; HitObject targetObject = null;
// Keep advancing the target object while its start time falls before the end time of the source object // Keep advancing the target object while its start time falls before the end time of the source object
while (true) while (true)
@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit
targetIndex++; targetIndex++;
} }
return new OsuDistanceSnapGrid(sourceObject, targetObject); return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject);
} }
} }
} }

View File

@ -27,22 +27,5 @@ namespace osu.Game.Rulesets.Osu.Judgements
return 300; return 300;
} }
} }
protected override double HealthIncreaseFor(HitResult result)
{
switch (result)
{
case HitResult.Miss:
return -0.02;
case HitResult.Meh:
case HitResult.Good:
case HitResult.Great:
return 0.01;
default:
return 0;
}
}
} }
} }

View File

@ -18,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModBlinds : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToScoreProcessor public class OsuModBlinds : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToHealthProcessor
{ {
public override string Name => "Blinds"; public override string Name => "Blinds";
public override string Description => "Play with blinds on your screen."; public override string Description => "Play with blinds on your screen.";
@ -37,9 +37,9 @@ namespace osu.Game.Rulesets.Osu.Mods
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap)); drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
} }
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{ {
scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; healthProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); };
} }
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;

View File

@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
public class OsuModDeflate : OsuModeObjectScaleTween public class OsuModDeflate : OsuModObjectScaleTween
{ {
public override string Name => "Deflate"; public override string Name => "Deflate";

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModDifficultyAdjust : ModDifficultyAdjust
{
[SettingSource("Circle Size", "Override a beatmap's set CS.")]
public BindableNumber<float> CircleSize { get; } = new BindableFloat
{
Precision = 0.1f,
MinValue = 1,
MaxValue = 10,
Default = 5,
Value = 5,
};
[SettingSource("Approach Rate", "Override a beatmap's set AR.")]
public BindableNumber<float> ApproachRate { get; } = new BindableFloat
{
Precision = 0.1f,
MinValue = 1,
MaxValue = 10,
Default = 5,
Value = 5,
};
protected override void TransferSettings(BeatmapDifficulty difficulty)
{
base.TransferSettings(difficulty);
TransferSetting(CircleSize, difficulty.CircleSize);
TransferSetting(ApproachRate, difficulty.ApproachRate);
}
protected override void ApplySettings(BeatmapDifficulty difficulty)
{
base.ApplySettings(difficulty);
difficulty.CircleSize = CircleSize.Value;
difficulty.ApproachRate = ApproachRate.Value;
}
}
}

View File

@ -5,7 +5,7 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
internal class OsuModGrow : OsuModeObjectScaleTween internal class OsuModGrow : OsuModObjectScaleTween
{ {
public override string Name => "Grow"; public override string Name => "Grow";

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Mods
/// <summary> /// <summary>
/// Adjusts the size of hit objects during their fade in animation. /// Adjusts the size of hit objects during their fade in animation.
/// </summary> /// </summary>
public abstract class OsuModeObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects public abstract class OsuModObjectScaleTween : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
{ {
public override ModType Type => ModType.Fun; public override ModType Type => ModType.Fun;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation. // todo: this mod should be able to be compatible with hidden with a bit of further implementation.
public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) }; public override Type[] IncompatibleMods => new[] { typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360; private const int rotate_offset = 360;
private const float rotate_starting_width = 2; private const float rotate_starting_width = 2;

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Put your faith in the approach circles..."; public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) }; public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) };
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>(); private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public void ReadFromConfig(OsuConfigManager config) public void ReadFromConfig(OsuConfigManager config)

View File

@ -32,13 +32,13 @@ using System;
namespace osu.Game.Rulesets.Osu namespace osu.Game.Rulesets.Osu
{ {
public class OsuRuleset : Ruleset public class OsuRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new OsuScoreProcessor(beatmap); public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this);
public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap);
@ -130,6 +130,7 @@ namespace osu.Game.Rulesets.Osu
return new Mod[] return new Mod[]
{ {
new OsuModTarget(), new OsuModTarget(),
new OsuModDifficultyAdjust(),
}; };
case ModType.Automation: case ModType.Automation:
@ -174,11 +175,13 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => SHORT_NAME; public override string ShortName => SHORT_NAME;
public override string PlayingVerb => "Clicking circles";
public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source); public override ISkin CreateLegacySkinProvider(ISkinSource source) => new OsuLegacySkinTransformer(source);
public override int? LegacyID => 0; public int LegacyID => 0;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame();

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
@ -11,44 +10,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
{ {
internal class OsuScoreProcessor : ScoreProcessor internal class OsuScoreProcessor : ScoreProcessor
{ {
public OsuScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
private float hpDrainRate;
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate;
}
protected override double HealthAdjustmentFactorFor(JudgementResult result)
{
switch (result.Type)
{
case HitResult.Great:
return 10.2 - hpDrainRate;
case HitResult.Good:
return 8 - hpDrainRate;
case HitResult.Meh:
return 4 - hpDrainRate;
// case HitResult.SliderTick:
// return Math.Max(7 - hpDrainRate, 0) * 0.01;
case HitResult.Miss:
return hpDrainRate;
default:
return 0;
}
}
protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement);
public override HitWindows CreateHitWindows() => new OsuHitWindows(); public override HitWindows CreateHitWindows() => new OsuHitWindows();

View File

@ -26,10 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Audio
var centre = s.GetSampleInfo(); var centre = s.GetSampleInfo();
var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP); var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP);
// todo: this is ugly
centre.Namespace = "taiko";
rim.Namespace = "taiko";
mappings[s.Time] = new DrumSample mappings[s.Time] = new DrumSample
{ {
Centre = addSound(centre), Centre = addSound(centre),

View File

@ -39,14 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private readonly bool isForCurrentRuleset; private readonly bool isForCurrentRuleset;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) }; public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap, ruleset)
public TaikoBeatmapConverter(IBeatmap beatmap)
: base(beatmap)
{ {
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(new TaikoRuleset().RulesetInfo); isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
} }
public override bool CanConvert() => true;
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original) protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
{ {
// Rewrite the beatmap info to add the slider velocity multiplier // Rewrite the beatmap info to add the slider velocity multiplier

View File

@ -0,0 +1,11 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModDifficultyAdjust : ModDifficultyAdjust
{
}
}

View File

@ -166,8 +166,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// Normal and clap samples are handled by the drum // Normal and clap samples are handled by the drum
protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); protected override IEnumerable<HitSampleInfo> GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
protected override string SampleNamespace => "taiko";
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece(); protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
/// <summary> /// <summary>

View File

@ -121,41 +121,13 @@ namespace osu.Game.Rulesets.Taiko.Replays
var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button var nextHitObject = GetNextObject(i); // Get the next object that requires pressing the same button
bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY; bool canDelayKeyUp = nextHitObject == null || nextHitObject.StartTime > endTime + KEY_UP_DELAY;
double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9; double calculatedDelay = canDelayKeyUp ? KEY_UP_DELAY : (nextHitObject.StartTime - endTime) * 0.9;
Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay)); Frames.Add(new TaikoReplayFrame(endTime + calculatedDelay));
if (i < Beatmap.HitObjects.Count - 1)
{
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime)
Frames.Add(new TaikoReplayFrame(waitTime));
}
hitButton = !hitButton; hitButton = !hitButton;
} }
return Replay; return Replay;
} }
protected override HitObject GetNextObject(int currentIndex)
{
Type desiredType = Beatmap.HitObjects[currentIndex].GetType();
for (int i = currentIndex + 1; i < Beatmap.HitObjects.Count; i++)
{
var currentObj = Beatmap.HitObjects[i];
if (currentObj.GetType() == desiredType ||
// Un-press all keys before a DrumRoll or Swell
currentObj is DrumRoll || currentObj is Swell)
{
return Beatmap.HitObjects[i];
}
}
return null;
}
} }
} }

View File

@ -0,0 +1,49 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Scoring
{
/// <summary>
/// A <see cref="HealthProcessor"/> for the taiko ruleset.
/// Taiko fails if the player has not half-filled their health by the end of the map.
/// </summary>
public class TaikoHealthProcessor : AccumulatingHealthProcessor
{
/// <summary>
/// A value used for calculating <see cref="hpMultiplier"/>.
/// </summary>
private const double object_count_factor = 3;
/// <summary>
/// HP multiplier for a successful <see cref="HitResult"/>.
/// </summary>
private double hpMultiplier;
/// <summary>
/// HP multiplier for a <see cref="HitResult.Miss"/>.
/// </summary>
private double hpMissMultiplier;
public TaikoHealthProcessor()
: base(0.5)
{
}
public override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType<Hit>().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}
protected override double GetHealthIncreaseFor(JudgementResult result)
=> base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier);
}
}

View File

@ -1,60 +1,12 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Scoring namespace osu.Game.Rulesets.Taiko.Scoring
{ {
internal class TaikoScoreProcessor : ScoreProcessor internal class TaikoScoreProcessor : ScoreProcessor
{ {
/// <summary>
/// A value used for calculating <see cref="hpMultiplier"/>.
/// </summary>
private const double object_count_factor = 3;
/// <summary>
/// Taiko fails at the end of the map if the player has not half-filled their HP bar.
/// </summary>
protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5;
/// <summary>
/// HP multiplier for a successful <see cref="HitResult"/>.
/// </summary>
private double hpMultiplier;
/// <summary>
/// HP multiplier for a <see cref="HitResult.Miss"/>.
/// </summary>
private double hpMissMultiplier;
public TaikoScoreProcessor(IBeatmap beatmap)
: base(beatmap)
{
}
protected override void ApplyBeatmap(IBeatmap beatmap)
{
base.ApplyBeatmap(beatmap);
hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.OfType<Hit>().Count() * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}
protected override double HealthAdjustmentFactorFor(JudgementResult result)
=> result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier;
protected override void Reset(bool storeResults)
{
base.Reset(storeResults);
Health.Value = 0;
}
public override HitWindows CreateHitWindows() => new TaikoHitWindows(); public override HitWindows CreateHitWindows() => new TaikoHitWindows();
} }
} }

View File

@ -0,0 +1,55 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.Skinning
{
public class TaikoLegacySkinTransformer : ISkin
{
private readonly ISkinSource source;
public TaikoLegacySkinTransformer(ISkinSource source)
{
this.source = source;
}
public Drawable GetDrawableComponent(ISkinComponent component) => source.GetDrawableComponent(component);
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo));
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source.GetConfig<TLookup, TValue>(lookup);
private class LegacyTaikoSampleInfo : ISampleInfo
{
private readonly ISampleInfo source;
public LegacyTaikoSampleInfo(ISampleInfo source)
{
this.source = source;
}
public IEnumerable<string> LookupNames
{
get
{
foreach (var name in source.LookupNames)
yield return $"taiko-{name}";
foreach (var name in source.LookupNames)
yield return name;
}
}
public int Volume => source.Volume;
}
}
}

View File

@ -21,16 +21,22 @@ using osu.Game.Rulesets.Taiko.Difficulty;
using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using System; using System;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko namespace osu.Game.Rulesets.Taiko
{ {
public class TaikoRuleset : Ruleset public class TaikoRuleset : Ruleset, ILegacyRuleset
{ {
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
public override ScoreProcessor CreateScoreProcessor(IBeatmap beatmap) => new TaikoScoreProcessor(beatmap); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TaikoHealthProcessor();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap, this);
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new TaikoLegacySkinTransformer(source);
public const string SHORT_NAME = "taiko"; public const string SHORT_NAME = "taiko";
@ -105,6 +111,12 @@ namespace osu.Game.Rulesets.Taiko
new TaikoModFlashlight(), new TaikoModFlashlight(),
}; };
case ModType.Conversion:
return new Mod[]
{
new TaikoModDifficultyAdjust(),
};
case ModType.Automation: case ModType.Automation:
return new Mod[] return new Mod[]
{ {
@ -127,13 +139,15 @@ namespace osu.Game.Rulesets.Taiko
public override string ShortName => SHORT_NAME; public override string ShortName => SHORT_NAME;
public override string PlayingVerb => "Bashing drums";
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);
public override int? LegacyID => 1; public int LegacyID => 1;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
} }

View File

@ -20,7 +20,7 @@ namespace osu.Game.Tests.Beatmaps
[Test] [Test]
public void TestHitObjectAddEvent() public void TestHitObjectAddEvent()
{ {
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject addedObject = null; HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h; editorBeatmap.HitObjectAdded += h => addedObject = h;
@ -38,7 +38,7 @@ namespace osu.Game.Tests.Beatmaps
public void HitObjectRemoveEvent() public void HitObjectRemoveEvent()
{ {
var hitCircle = new HitCircle(); var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } }); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null; HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h; editorBeatmap.HitObjectRemoved += h => removedObject = h;
@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestInitialHitObjectStartTimeChangeEvent() public void TestInitialHitObjectStartTimeChangeEvent()
{ {
var hitCircle = new HitCircle(); var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } }); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null; HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h; editorBeatmap.StartTimeChanged += h => changedObject = h;
@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps
[Test] [Test]
public void TestAddedHitObjectStartTimeChangeEvent() public void TestAddedHitObjectStartTimeChangeEvent()
{ {
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); var editorBeatmap = new EditorBeatmap(new OsuBeatmap());
HitObject changedObject = null; HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h; editorBeatmap.StartTimeChanged += h => changedObject = h;
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestRemovedHitObjectStartTimeChangeEvent() public void TestRemovedHitObjectStartTimeChangeEvent()
{ {
var hitCircle = new HitCircle(); var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } }); var editorBeatmap = new EditorBeatmap(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null; HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h; editorBeatmap.StartTimeChanged += h => changedObject = h;
@ -110,7 +110,7 @@ namespace osu.Game.Tests.Beatmaps
[Test] [Test]
public void TestAddHitObjectInMiddle() public void TestAddHitObjectInMiddle()
{ {
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{ {
HitObjects = HitObjects =
{ {
@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps
public void TestResortWhenStartTimeChanged() public void TestResortWhenStartTimeChanged()
{ {
var hitCircle = new HitCircle { StartTime = 1000 }; var hitCircle = new HitCircle { StartTime = 1000 };
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap var editorBeatmap = new EditorBeatmap(new OsuBeatmap
{ {
HitObjects = HitObjects =
{ {

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -313,7 +314,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{ {
var beatmap = decoder.Decode(stream); var beatmap = decoder.Decode(stream);
var converted = new OsuBeatmapConverter(beatmap).Convert(); var converted = new OsuBeatmapConverter(beatmap, new OsuRuleset()).Convert();
new OsuBeatmapProcessor(converted).PreProcess(); new OsuBeatmapProcessor(converted).PreProcess();
new OsuBeatmapProcessor(converted).PostProcess(); new OsuBeatmapProcessor(converted).PostProcess();
@ -336,7 +337,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{ {
var beatmap = decoder.Decode(stream); var beatmap = decoder.Decode(stream);
var converted = new CatchBeatmapConverter(beatmap).Convert(); var converted = new CatchBeatmapConverter(beatmap, new CatchRuleset()).Convert();
new CatchBeatmapProcessor(converted).PreProcess(); new CatchBeatmapProcessor(converted).PreProcess();
new CatchBeatmapProcessor(converted).PostProcess(); new CatchBeatmapProcessor(converted).PostProcess();

View File

@ -2,11 +2,12 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
@ -17,6 +18,9 @@ namespace osu.Game.Tests.Editor
{ {
private TestHitObjectComposer composer; private TestHitObjectComposer composer;
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
[SetUp] [SetUp]
public void Setup() => Schedule(() => public void Setup() => Schedule(() =>
{ {
@ -183,7 +187,7 @@ namespace osu.Game.Tests.Editor
private class TestHitObjectComposer : OsuHitObjectComposer private class TestHitObjectComposer : OsuHitObjectComposer
{ {
public new EditorBeatmap<OsuHitObject> EditorBeatmap => base.EditorBeatmap; public new EditorBeatmap EditorBeatmap => base.EditorBeatmap;
public TestHitObjectComposer() public TestHitObjectComposer()
: base(new OsuRuleset()) : base(new OsuRuleset())

View File

@ -0,0 +1,159 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneDrainingHealthProcessor : OsuTestScene
{
private Bindable<bool> breakTime;
private HealthProcessor processor;
private ManualClock clock;
[Test]
public void TestInitialHealthStartsAtOne()
{
createProcessor(createBeatmap(1000, 2000));
assertHealthEqualTo(1);
}
[Test]
public void TestHealthNotDrainedBeforeGameplayStart()
{
createProcessor(createBeatmap(1000, 2000));
setTime(100);
assertHealthEqualTo(1);
setTime(900);
assertHealthEqualTo(1);
}
[Test]
public void TestHealthNotDrainedAfterGameplayEnd()
{
createProcessor(createBeatmap(1000, 2000));
setTime(2001); // After the hitobjects
setHealth(1); // Reset the current health for assertions to take place
setTime(2100);
assertHealthEqualTo(1);
setTime(3000);
assertHealthEqualTo(1);
}
[Test]
public void TestHealthNotDrainedDuringBreak()
{
createProcessor(createBeatmap(0, 2000));
setBreak(true);
setTime(700);
assertHealthEqualTo(1);
setTime(900);
assertHealthEqualTo(1);
}
[Test]
public void TestHealthDrainedDuringGameplay()
{
createProcessor(createBeatmap(0, 1000));
setTime(500);
assertHealthNotEqualTo(1);
}
[Test]
public void TestHealthGainedAfterRewind()
{
createProcessor(createBeatmap(0, 1000));
setTime(500);
setTime(0);
assertHealthEqualTo(1);
}
[Test]
public void TestHealthGainedOnHit()
{
Beatmap beatmap = createBeatmap(0, 1000);
createProcessor(beatmap);
setTime(10); // Decrease health slightly
assertHealthNotEqualTo(1);
AddStep("apply hit result", () => processor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
assertHealthEqualTo(1);
}
[Test]
public void TestHealthRemovedOnRevert()
{
var beatmap = createBeatmap(0, 1000);
JudgementResult result = null;
createProcessor(beatmap);
setTime(10); // Decrease health slightly
AddStep("apply hit result", () => processor.ApplyResult(result = new JudgementResult(beatmap.HitObjects[0], new Judgement()) { Type = HitResult.Perfect }));
AddStep("revert hit result", () => processor.RevertResult(result));
assertHealthNotEqualTo(1);
}
private Beatmap createBeatmap(double startTime, double endTime)
{
var beatmap = new Beatmap
{
BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
};
for (double time = startTime; time <= endTime; time += 100)
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
return beatmap;
}
private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
{
breakTime = new Bindable<bool>();
Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
{
d.RelativeSizeAxes = Axes.Both;
d.Clock = new FramedClock(clock = new ManualClock());
});
processor.IsBreakTime.BindTo(breakTime);
processor.ApplyBeatmap(beatmap);
});
private void setTime(double time) => AddStep($"set time = {time}", () => clock.CurrentTime = time);
private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
private void assertHealthEqualTo(double value)
=> AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
private void assertHealthNotEqualTo(double value)
=> AddAssert($"health != {value}", () => !Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));
private class JudgeableHitObject : HitObject
{
public override Judgement CreateJudgement() => new Judgement();
}
}
}

View File

@ -130,7 +130,7 @@ namespace osu.Game.Tests.Gameplay
switch (global) switch (global)
{ {
case GlobalSkinConfiguration.ComboColours: case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(ComboColours)); return SkinUtils.As<TValue>(new Bindable<IReadOnlyList<Color4>>(ComboColours));
} }
break; break;

View File

@ -0,0 +1,74 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.IO.Stores;
using osu.Game.Audio;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
{
public class TestSceneStoryboardSamples : OsuTestScene
{
[Test]
public void TestRetrieveTopLevelSample()
{
ISkin skin = null;
SampleChannel channel = null;
AddStep("create skin", () => skin = new TestSkin("test-sample", Audio));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("test-sample")));
AddAssert("sample is non-null", () => channel != null);
}
[Test]
public void TestRetrieveSampleInSubFolder()
{
ISkin skin = null;
SampleChannel channel = null;
AddStep("create skin", () => skin = new TestSkin("folder/test-sample", Audio));
AddStep("retrieve sample", () => channel = skin.GetSample(new SampleInfo("folder/test-sample")));
AddAssert("sample is non-null", () => channel != null);
}
private class TestSkin : LegacySkin
{
public TestSkin(string resourceName, AudioManager audioManager)
: base(DefaultLegacySkin.Info, new TestResourceStore(resourceName), audioManager, "skin.ini")
{
}
}
private class TestResourceStore : IResourceStore<byte[]>
{
private readonly string resourceName;
public TestResourceStore(string resourceName)
{
this.resourceName = resourceName;
}
public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null;
public Task<byte[]> GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null;
public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null;
public IEnumerable<string> GetAvailableResources() => new[] { resourceName };
public void Dispose()
{
}
}
}
}

View File

@ -9,11 +9,11 @@ namespace osu.Game.Tests.Resources
{ {
public static class TestResources public static class TestResources
{ {
public static DllResourceStore GetStore() => new DllResourceStore("osu.Game.Tests.dll"); public static DllResourceStore GetStore() => new DllResourceStore(typeof(TestResources).Assembly);
public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}"); public static Stream OpenResource(string name) => GetStore().GetStream($"Resources/{name}");
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz"); public static Stream GetTestBeatmapStream(bool virtualTrack = false) => OpenResource($"Archives/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport(bool virtualTrack = false) public static string GetTestBeatmapForImport(bool virtualTrack = false)
{ {

Binary file not shown.

View File

@ -13,31 +13,22 @@ namespace osu.Game.Tests.Skins
[TestFixture] [TestFixture]
public class LegacySkinDecoderTest public class LegacySkinDecoderTest
{ {
[TestCase(true)] [Test]
[TestCase(false)] public void TestDecodeSkinColours()
public void TestDecodeSkinColours(bool hasColours)
{ {
var decoder = new LegacySkinDecoder(); var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new LineBufferedReader(resStream)) using (var stream = new LineBufferedReader(resStream))
{ {
var comboColors = decoder.Decode(stream).ComboColours; var comboColors = decoder.Decode(stream).ComboColours;
var expectedColors = new List<Color4>
List<Color4> expectedColors;
if (hasColours)
{
expectedColors = new List<Color4>
{ {
new Color4(142, 199, 255, 255), new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255), new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255), new Color4(128, 255, 255, 255),
new Color4(100, 100, 100, 100), new Color4(100, 100, 100, 100),
}; };
}
else
expectedColors = new DefaultSkin().Configuration.ComboColours;
Assert.AreEqual(expectedColors.Count, comboColors.Count); Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++) for (int i = 0; i < expectedColors.Count; i++)
@ -45,6 +36,37 @@ namespace osu.Game.Tests.Skins
} }
} }
[Test]
public void TestDecodeEmptySkinColours()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
var expectedColors = SkinConfiguration.DefaultComboColours;
Assert.AreEqual(expectedColors.Count, comboColors.Count);
for (int i = 0; i < expectedColors.Count; i++)
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
[Test]
public void TestDecodeEmptySkinColoursNoFallback()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin-empty.ini"))
using (var stream = new LineBufferedReader(resStream))
{
var skinConfiguration = decoder.Decode(stream);
skinConfiguration.AllowDefaultComboColoursFallback = false;
Assert.IsNull(skinConfiguration.ComboColours);
}
}
[Test] [Test]
public void TestDecodeGeneral() public void TestDecodeGeneral()
{ {

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
@ -21,8 +22,8 @@ namespace osu.Game.Tests.Skins
[HeadlessTest] [HeadlessTest]
public class TestSceneSkinConfigurationLookup : OsuTestScene public class TestSceneSkinConfigurationLookup : OsuTestScene
{ {
private LegacySkin source1; private SkinSource source1;
private LegacySkin source2; private SkinSource source2;
private SkinRequester requester; private SkinRequester requester;
[SetUp] [SetUp]
@ -94,7 +95,7 @@ namespace osu.Game.Tests.Skins
[Test] [Test]
public void TestGlobalLookup() public void TestGlobalLookup()
{ {
AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, List<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0); AddAssert("Check combo colours", () => requester.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.Count > 0);
} }
[Test] [Test]
@ -116,6 +117,28 @@ namespace osu.Game.Tests.Skins
}); });
} }
[Test]
public void TestEmptyComboColours()
{
AddAssert("Check retrieved combo colours is skin default colours", () =>
requester.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(SkinConfiguration.DefaultComboColours) ?? false);
}
[Test]
public void TestEmptyComboColoursNoFallback()
{
AddStep("Add custom combo colours to source1", () => source1.Configuration.AddComboColours(
new Color4(100, 150, 200, 255),
new Color4(55, 110, 166, 255),
new Color4(75, 125, 175, 255)
));
AddStep("Disallow default colours fallback in source2", () => source2.Configuration.AllowDefaultComboColoursFallback = false);
AddAssert("Check retrieved combo colours from source1", () =>
requester.GetConfig<GlobalSkinConfiguration, IReadOnlyList<Color4>>(GlobalSkinConfiguration.ComboColours)?.Value?.SequenceEqual(source1.Configuration.ComboColours) ?? false);
}
[Test] [Test]
public void TestLegacyVersionLookup() public void TestLegacyVersionLookup()
{ {

View File

@ -4,6 +4,8 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor namespace osu.Game.Tests.Visual.Editor
@ -11,10 +13,21 @@ namespace osu.Game.Tests.Visual.Editor
[TestFixture] [TestFixture]
public class TestSceneComposeScreen : EditorClockTestScene public class TestSceneComposeScreen : EditorClockTestScene
{ {
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap =
new EditorBeatmap(new OsuBeatmap
{
BeatmapInfo =
{
Ruleset = new OsuRuleset().RulesetInfo
}
});
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new ComposeScreen(); Child = new ComposeScreen();
} }
} }

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
@ -21,15 +20,15 @@ namespace osu.Game.Tests.Visual.Editor
private const double beat_length = 100; private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384); private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(IEditorBeatmap))] [Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))] [Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider(); private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid() public TestSceneDistanceSnapGrid()
{ {
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap()); editorBeatmap = new EditorBeatmap(new OsuBeatmap());
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
} }

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Editor
{ {
Beatmap.Value = new WaveformTestBeatmap(audio); Beatmap.Value = new WaveformTestBeatmap(audio);
var editorBeatmap = new EditorBeatmap<HitObject>((Beatmap<HitObject>)Beatmap.Value.Beatmap); var editorBeatmap = new EditorBeatmap((Beatmap<HitObject>)Beatmap.Value.Beatmap);
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
@ -59,9 +60,12 @@ namespace osu.Game.Tests.Visual.Editor
}, },
}); });
var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
Dependencies.CacheAs<IAdjustableClock>(clock); Dependencies.CacheAs<IAdjustableClock>(clock);
Dependencies.CacheAs<IFrameBasedClock>(clock); Dependencies.CacheAs<IFrameBasedClock>(clock);
Dependencies.CacheAs(editorBeatmap);
Child = new OsuHitObjectComposer(new OsuRuleset()); Child = new OsuHitObjectComposer(new OsuRuleset());
} }

View File

@ -5,7 +5,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing;
namespace osu.Game.Tests.Visual.Editor namespace osu.Game.Tests.Visual.Editor
@ -25,10 +26,13 @@ namespace osu.Game.Tests.Visual.Editor
typeof(RowAttribute) typeof(RowAttribute)
}; };
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap());
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Child = new TimingScreen(); Child = new TimingScreen();
} }
} }

View File

@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods); public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new TestDrawableScrollingRuleset(this, beatmap, mods);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap, null);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
@ -268,12 +268,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestBeatmapConverter : BeatmapConverter<TestHitObject> private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
{ {
public TestBeatmapConverter(IBeatmap beatmap) public TestBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap) : base(beatmap, ruleset)
{ {
} }
protected override IEnumerable<Type> ValidConversionTypes => new[] { typeof(HitObject) }; public override bool CanConvert() => true;
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap) protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
{ {

View File

@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true; HealthProcessor.FailConditions += (_, __) => true;
} }
} }
} }

View File

@ -22,12 +22,12 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddUntilStep("wait for fail", () => Player.HasFailed); AddUntilStep("wait for fail", () => Player.HasFailed);
AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1); AddUntilStep("wait for multiple judged objects", () => ((FailPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.Count(h => h.AllJudged) > 1);
AddAssert("total judgements == 1", () => ((FailPlayer)Player).ScoreProcessor.JudgedHits == 1); AddAssert("total judgements == 1", () => ((FailPlayer)Player).HealthProcessor.JudgedHits >= 1);
} }
private class FailPlayer : TestPlayer private class FailPlayer : TestPlayer
{ {
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HealthProcessor HealthProcessor => base.HealthProcessor;
public FailPlayer() public FailPlayer()
: base(false, false) : base(false, false)
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
ScoreProcessor.FailConditions += (_, __) => true; HealthProcessor.FailConditions += (_, __) => true;
} }
} }
} }

View File

@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create overlay", () => AddStep("create overlay", () =>
{ {
Child = hudOverlay = new HUDOverlay(null, null, Array.Empty<Mod>()); Child = hudOverlay = new HUDOverlay(null, null, null, Array.Empty<Mod>());
action?.Invoke(hudOverlay); action?.Invoke(hudOverlay);
}); });

View File

@ -52,7 +52,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestResumeWithResumeOverlay() public void TestResumeWithResumeOverlay()
{ {
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm(); pauseAndConfirm();
resume(); resume();
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestPauseWithResumeOverlay() public void TestPauseWithResumeOverlay()
{ {
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm(); pauseAndConfirm();
@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("move cursor to button", () => AddStep("move cursor to button", () =>
InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType<HoldToConfirmContainer>().First().ScreenSpaceDrawQuad.Centre)); InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType<HoldToConfirmContainer>().First().ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); AddUntilStep("wait for hitobjects", () => Player.HealthProcessor.Health.Value < 1);
pauseAndConfirm(); pauseAndConfirm();
resumeAndConfirm(); resumeAndConfirm();
@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected class PausePlayer : TestPlayer protected class PausePlayer : TestPlayer
{ {
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; public new HealthProcessor HealthProcessor => base.HealthProcessor;
public new HUDOverlay HUDOverlay => base.HUDOverlay; public new HUDOverlay HUDOverlay => base.HUDOverlay;

View File

@ -3,12 +3,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -28,12 +30,16 @@ namespace osu.Game.Tests.Visual.Gameplay
[Cached(typeof(IReadOnlyList<Mod>))] [Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>(); private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
private const int spawn_interval = 5000;
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4];
private ScheduledDelegate hitObjectSpawnDelegate;
public TestSceneScrollingHitObjects() [SetUp]
public void Setup() => Schedule(() =>
{ {
Add(new GridContainer Child = new GridContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Content = new[] Content = new[]
@ -43,48 +49,66 @@ namespace osu.Game.Tests.Visual.Gameplay
scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up) scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = playfields[0] = new TestPlayfield() Child = playfields[0] = new TestPlayfield(),
TimeRange = spawn_interval
}, },
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up) scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = playfields[1] = new TestPlayfield() Child = playfields[1] = new TestPlayfield(),
TimeRange = spawn_interval
}, },
}, },
new Drawable[] new Drawable[]
{ {
scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up) scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Left)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = playfields[2] = new TestPlayfield() Child = playfields[2] = new TestPlayfield(),
TimeRange = spawn_interval
}, },
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up) scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = playfields[3] = new TestPlayfield() Child = playfields[3] = new TestPlayfield(),
TimeRange = spawn_interval
} }
} }
} }
};
setUpHitObjects();
}); });
private void setUpHitObjects()
{
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
for (int i = 0; i <= spawn_interval; i += 1000)
addHitObject(Time.Current + i);
hitObjectSpawnDelegate?.Cancel();
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true);
}
[Test]
public void TestScrollAlgorithms()
{
AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v)); AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v));
AddStep("Add control point", () => addControlPoint(Time.Current + 5000)); AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval));
} }
protected override void LoadComplete() [Test]
public void TestScrollLifetime()
{ {
base.LoadComplete(); AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0))); // otherwise the hitobjects will spawn already partly visible on screen and look wrong
AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0));
for (int i = 0; i <= 5000; i += 1000)
addHitObject(Time.Current + i);
Scheduler.AddDelayed(() => addHitObject(Time.Current + 5000), 1000, true);
} }
private void addHitObject(double time) private void addHitObject(double time)
@ -207,7 +231,9 @@ namespace osu.Game.Tests.Visual.Gameplay
public TestDrawableHitObject(double time) public TestDrawableHitObject(double time)
: base(new HitObject { StartTime = time }) : base(new HitObject { StartTime = time })
{ {
Origin = Anchor.Centre; Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
AddInternal(new Box { Size = new Vector2(75) }); AddInternal(new Box { Size = new Vector2(75) });

View File

@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online
typeof(ProfileHeader), typeof(ProfileHeader),
typeof(RankGraph), typeof(RankGraph),
typeof(LineGraph), typeof(LineGraph),
typeof(OverlayHeaderTabControl), typeof(TabControlOverlayHeader.OverlayHeaderTabControl),
typeof(CentreHeaderContainer), typeof(CentreHeaderContainer),
typeof(BottomHeaderContainer), typeof(BottomHeaderContainer),
typeof(DetailHeaderContainer), typeof(DetailHeaderContainer),

View File

@ -1,237 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.SongSelect
{
[TestFixture]
[System.ComponentModel.Description("PlaySongSelect leaderboard/details area")]
public class TestSceneBeatmapDetailArea : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(BeatmapDetails) };
private ModDisplay modDisplay;
[BackgroundDependencyLoader]
private void load(OsuGameBase game, RulesetStore rulesets)
{
BeatmapDetailArea detailsArea;
Add(detailsArea = new BeatmapDetailArea
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(550f, 450f),
});
Add(modDisplay = new ModDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Position = new Vector2(0, 25),
});
modDisplay.Current.BindTo(SelectedMods);
AddStep("all metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
BeatmapSet = new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has all the metrics",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
},
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
}));
AddStep("all except source", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
BeatmapSet = new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
{
Tags = "this beatmap has all the metrics",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
},
StarDifficulty = 5.3f,
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
}));
AddStep("ratings", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
BeatmapSet = new BeatmapSetInfo
{
Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }
},
Version = "Only Ratings",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has ratings metrics but not retries or fails",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 6,
DrainRate = 9,
OverallDifficulty = 6,
ApproachRate = 6,
},
StarDifficulty = 4.8f
}
}));
AddStep("fails+retries", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has retries and fails but no ratings",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 3.7f,
DrainRate = 6,
OverallDifficulty = 6,
ApproachRate = 7,
},
StarDifficulty = 2.91f,
Metrics = new BeatmapMetrics
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
}));
AddStep("null metrics", () => detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
Version = "No Metrics",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has no metrics",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 5.5f,
ApproachRate = 6.5f,
},
StarDifficulty = 1.97f,
}
}));
AddStep("null beatmap", () => detailsArea.Beatmap = null);
Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
AddStep("with EZ mod", () =>
{
detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
Version = "Has Easy Mod",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has the easy mod enabled",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 3,
DrainRate = 3,
OverallDifficulty = 3,
ApproachRate = 3,
},
StarDifficulty = 1f,
}
});
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) };
});
AddStep("with HR mod", () =>
{
detailsArea.Beatmap = new TestWorkingBeatmap(new Beatmap
{
BeatmapInfo =
{
Version = "Has Hard Rock Mod",
Metadata = new BeatmapMetadata
{
Source = "osu!lazer",
Tags = "this beatmap has the hard rock mod enabled",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 3,
DrainRate = 3,
OverallDifficulty = 3,
ApproachRate = 3,
},
StarDifficulty = 1f,
}
});
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) };
});
}
}
}

View File

@ -3,8 +3,14 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.SongSelect namespace osu.Game.Tests.Visual.SongSelect
@ -174,5 +180,27 @@ namespace osu.Game.Tests.Visual.SongSelect
OnlineBeatmapID = 162, OnlineBeatmapID = 162,
}); });
} }
[Resolved]
private RulesetStore rulesets { get; set; }
[Resolved]
private OsuColour colours { get; set; }
[Test]
public void TestModAdjustments()
{
TestAllMetrics();
Ruleset ruleset = rulesets.AvailableRulesets.First().CreateInstance();
AddStep("with EZ mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModEasy) });
AddAssert("first bar coloured blue", () => details.ChildrenOfType<Bar>().Skip(1).First().AccentColour == colours.BlueDark);
AddStep("with HR mod", () => SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModHardRock) });
AddAssert("first bar coloured red", () => details.ChildrenOfType<Bar>().Skip(1).First().AccentColour == colours.Red);
}
} }
} }

View File

@ -10,11 +10,11 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
[TestFixture] [TestFixture]
public class TestSceneBreadcrumbs : OsuTestScene public class TestSceneBreadcrumbControl : OsuTestScene
{ {
private readonly BreadcrumbControl<BreadcrumbTab> breadcrumbs; private readonly BreadcrumbControl<BreadcrumbTab> breadcrumbs;
public TestSceneBreadcrumbs() public TestSceneBreadcrumbControl()
{ {
Add(breadcrumbs = new BreadcrumbControl<BreadcrumbTab> Add(breadcrumbs = new BreadcrumbControl<BreadcrumbTab>
{ {

View File

@ -67,9 +67,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRepeatStep(@"add many simple", sendManyNotifications, 3); AddRepeatStep(@"add many simple", sendManyNotifications, 3);
AddWaitStep("wait some", 5); waitForCompletion();
checkProgressingCount(0);
AddStep(@"progress #3", sendUploadProgress); AddStep(@"progress #3", sendUploadProgress);
@ -77,9 +75,7 @@ namespace osu.Game.Tests.Visual.UserInterface
checkDisplayedCount(33); checkDisplayedCount(33);
AddWaitStep("wait some", 10); waitForCompletion();
checkProgressingCount(0);
} }
[Test] [Test]
@ -109,9 +105,9 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep(@"background progress #1", sendBackgroundUploadProgress); AddStep(@"background progress #1", sendBackgroundUploadProgress);
AddWaitStep("wait some", 5); checkProgressingCount(1);
checkProgressingCount(0); waitForCompletion();
checkDisplayedCount(2); checkDisplayedCount(2);
@ -190,6 +186,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected);
private void waitForCompletion() => AddUntilStep("wait for notification progress completion", () => progressingNotifications.Count == 0);
private void sendBarrage() private void sendBarrage()
{ {
switch (RNG.Next(0, 4)) switch (RNG.Next(0, 4))

View File

@ -52,7 +52,7 @@ namespace osu.Game.Tournament
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(Storage storage, FrameworkConfigManager frameworkConfig) private void load(Storage storage, FrameworkConfigManager frameworkConfig)
{ {
Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll")); Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly));
AddFont(Resources, @"Resources/Fonts/Aquatico-Regular"); AddFont(Resources, @"Resources/Fonts/Aquatico-Regular");
AddFont(Resources, @"Resources/Fonts/Aquatico-Light"); AddFont(Resources, @"Resources/Fonts/Aquatico-Light");

View File

@ -17,11 +17,6 @@ namespace osu.Game.Audio
public const string HIT_NORMAL = @"hitnormal"; public const string HIT_NORMAL = @"hitnormal";
public const string HIT_CLAP = @"hitclap"; public const string HIT_CLAP = @"hitclap";
/// <summary>
/// An optional ruleset namespace.
/// </summary>
public string Namespace;
/// <summary> /// <summary>
/// The bank to load the sample from. /// The bank to load the sample from.
/// </summary> /// </summary>
@ -49,15 +44,6 @@ namespace osu.Game.Audio
{ {
get get
{ {
if (!string.IsNullOrEmpty(Namespace))
{
if (!string.IsNullOrEmpty(Suffix))
yield return $"{Namespace}/{Bank}-{Name}{Suffix}";
yield return $"{Namespace}/{Bank}-{Name}";
}
// check non-namespace as a fallback even when we have a namespace
if (!string.IsNullOrEmpty(Suffix)) if (!string.IsNullOrEmpty(Suffix))
yield return $"{Bank}-{Name}{Suffix}"; yield return $"{Bank}-{Name}{Suffix}";

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
@ -25,7 +26,7 @@ namespace osu.Game.Beatmaps
public IBeatmap Beatmap { get; } public IBeatmap Beatmap { get; }
protected BeatmapConverter(IBeatmap beatmap) protected BeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
{ {
Beatmap = beatmap; Beatmap = beatmap;
} }
@ -33,7 +34,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>. /// Whether <see cref="Beatmap"/> can be converted by this <see cref="BeatmapConverter{T}"/>.
/// </summary> /// </summary>
public bool CanConvert => !Beatmap.HitObjects.Any() || ValidConversionTypes.All(t => Beatmap.HitObjects.Any(t.IsInstanceOfType)); public abstract bool CanConvert();
/// <summary> /// <summary>
/// Converts <see cref="Beatmap"/>. /// Converts <see cref="Beatmap"/>.
@ -92,11 +93,6 @@ namespace osu.Game.Beatmaps
return result; return result;
} }
/// <summary>
/// The types of HitObjects that can be converted to be used for this Beatmap.
/// </summary>
protected abstract IEnumerable<Type> ValidConversionTypes { get; }
/// <summary> /// <summary>
/// Creates the <see cref="Beatmap{T}"/> that will be returned by this <see cref="BeatmapProcessor"/>. /// Creates the <see cref="Beatmap{T}"/> that will be returned by this <see cref="BeatmapProcessor"/>.
/// </summary> /// </summary>

View File

@ -76,7 +76,7 @@ namespace osu.Game.Beatmaps
public IBeatmap Beatmap { get; set; } public IBeatmap Beatmap { get; set; }
public bool CanConvert => true; public bool CanConvert() => true;
public IBeatmap Convert() public IBeatmap Convert()
{ {

View File

@ -8,6 +8,14 @@ namespace osu.Game.Beatmaps.Formats
{ {
public interface IHasComboColours public interface IHasComboColours
{ {
List<Color4> ComboColours { get; set; } /// <summary>
/// Retrieves the list of combo colours for presentation only.
/// </summary>
IReadOnlyList<Color4> ComboColours { get; }
/// <summary>
/// Adds combo colours to the list.
/// </summary>
void AddComboColours(params Color4[] colours);
} }
} }

View File

@ -77,8 +77,6 @@ namespace osu.Game.Beatmaps.Formats
return line; return line;
} }
private bool hasComboColours;
private void handleColours(T output, string line) private void handleColours(T output, string line)
{ {
var pair = SplitKeyVal(line); var pair = SplitKeyVal(line);
@ -105,14 +103,7 @@ namespace osu.Game.Beatmaps.Formats
{ {
if (!(output is IHasComboColours tHasComboColours)) return; if (!(output is IHasComboColours tHasComboColours)) return;
if (!hasComboColours) tHasComboColours.AddComboColours(colour);
{
// remove default colours.
tHasComboColours.ComboColours.Clear();
hasComboColours = true;
}
tHasComboColours.ComboColours.Add(colour);
} }
else else
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// Whether <see cref="Beatmap"/> can be converted by this <see cref="IBeatmapConverter"/>. /// Whether <see cref="Beatmap"/> can be converted by this <see cref="IBeatmapConverter"/>.
/// </summary> /// </summary>
bool CanConvert { get; } bool CanConvert();
/// <summary> /// <summary>
/// Converts <see cref="Beatmap"/>. /// Converts <see cref="Beatmap"/>.

View File

@ -108,7 +108,7 @@ namespace osu.Game.Beatmaps
IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance); IBeatmapConverter converter = CreateBeatmapConverter(Beatmap, rulesetInstance);
// Check if the beatmap can be converted // Check if the beatmap can be converted
if (!converter.CanConvert) if (Beatmap.HitObjects.Count > 0 && !converter.CanConvert())
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter}).");
// Apply conversion mods // Apply conversion mods

View File

@ -259,6 +259,9 @@ namespace osu.Game.Database
/// <summary> /// <summary>
/// Create a SHA-2 hash from the provided archive based on file content of all files matching <see cref="HashableFileTypes"/>. /// Create a SHA-2 hash from the provided archive based on file content of all files matching <see cref="HashableFileTypes"/>.
/// </summary> /// </summary>
/// <remarks>
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
/// </remarks>
private string computeHash(ArchiveReader reader) private string computeHash(ArchiveReader reader)
{ {
// for now, concatenate all .osu files in the set to create a unique hash. // for now, concatenate all .osu files in the set to create a unique hash.
@ -270,7 +273,7 @@ namespace osu.Game.Database
s.CopyTo(hashable); s.CopyTo(hashable);
} }
return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : null; return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : reader.Name.ComputeSHA2Hash();
} }
/// <summary> /// <summary>

View File

@ -15,14 +15,13 @@ namespace osu.Game.Graphics.UserInterface
public class BreadcrumbControl<T> : OsuTabControl<T> public class BreadcrumbControl<T> : OsuTabControl<T>
{ {
private const float padding = 10; private const float padding = 10;
private const float item_chevron_size = 10;
protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value) protected override TabItem<T> CreateTabItem(T value) => new BreadcrumbTabItem(value)
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
}; };
protected override float StripWidth() => base.StripWidth() - (padding + item_chevron_size); protected override float StripWidth => base.StripWidth - TabContainer.FirstOrDefault()?.Padding.Right ?? 0;
public BreadcrumbControl() public BreadcrumbControl()
{ {
@ -41,8 +40,10 @@ namespace osu.Game.Graphics.UserInterface
}; };
} }
private class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility> protected class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
{ {
protected virtual float ChevronSize => 10;
public event Action<Visibility> StateChanged; public event Action<Visibility> StateChanged;
public readonly SpriteIcon Chevron; public readonly SpriteIcon Chevron;
@ -52,7 +53,6 @@ namespace osu.Game.Graphics.UserInterface
public override bool HandleNonPositionalInput => State == Visibility.Visible; public override bool HandleNonPositionalInput => State == Visibility.Visible;
public override bool HandlePositionalInput => State == Visibility.Visible; public override bool HandlePositionalInput => State == Visibility.Visible;
public override bool IsRemovable => true;
private Visibility state; private Visibility state;
@ -91,12 +91,12 @@ namespace osu.Game.Graphics.UserInterface
{ {
Text.Font = Text.Font.With(size: 18); Text.Font = Text.Font.With(size: 18);
Text.Margin = new MarginPadding { Vertical = 8 }; Text.Margin = new MarginPadding { Vertical = 8 };
Padding = new MarginPadding { Right = padding + item_chevron_size }; Padding = new MarginPadding { Right = padding + ChevronSize };
Add(Chevron = new SpriteIcon Add(Chevron = new SpriteIcon
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Size = new Vector2(item_chevron_size), Size = new Vector2(ChevronSize),
Icon = FontAwesome.Solid.ChevronRight, Icon = FontAwesome.Solid.ChevronRight,
Margin = new MarginPadding { Left = padding }, Margin = new MarginPadding { Left = padding },
Alpha = 0f, Alpha = 0f,

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -31,6 +32,7 @@ namespace osu.Game.Graphics.UserInterface
protected readonly Nub Nub; protected readonly Nub Nub;
private readonly Box leftBox; private readonly Box leftBox;
private readonly Box rightBox; private readonly Box rightBox;
private readonly Container nubContainer;
public virtual string TooltipText { get; private set; } public virtual string TooltipText { get; private set; }
@ -72,11 +74,16 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Alpha = 0.5f, Alpha = 0.5f,
}, },
Nub = new Nub nubContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = Nub = new Nub
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
RelativePositionAxes = Axes.X,
Expanded = true, Expanded = true,
}, },
},
new HoverClickSounds() new HoverClickSounds()
}; };
@ -90,6 +97,13 @@ namespace osu.Game.Graphics.UserInterface
AccentColour = colours.Pink; AccentColour = colours.Pink;
} }
protected override void Update()
{
base.Update();
nubContainer.Padding = new MarginPadding { Horizontal = RangePadding };
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
@ -176,14 +190,14 @@ namespace osu.Game.Graphics.UserInterface
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
leftBox.Scale = new Vector2(Math.Clamp( leftBox.Scale = new Vector2(Math.Clamp(
Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1);
rightBox.Scale = new Vector2(Math.Clamp( rightBox.Scale = new Vector2(Math.Clamp(
DrawWidth - Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1);
} }
protected override void UpdateValue(float value) protected override void UpdateValue(float value)
{ {
Nub.MoveToX(RangePadding + UsableWidth * value, 250, Easing.OutQuint); Nub.MoveToX(value, 250, Easing.OutQuint);
} }
/// <summary> /// <summary>

View File

@ -28,8 +28,7 @@ namespace osu.Game.Graphics.UserInterface
protected override TabItem<T> CreateTabItem(T value) => new OsuTabItem(value); protected override TabItem<T> CreateTabItem(T value) => new OsuTabItem(value);
protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X; protected virtual float StripWidth => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
protected virtual float StripHeight() => 1;
/// <summary> /// <summary>
/// Whether entries should be automatically populated if <typeparamref name="T"/> is an <see cref="Enum"/> type. /// Whether entries should be automatically populated if <typeparamref name="T"/> is an <see cref="Enum"/> type.
@ -46,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Height = StripHeight(), Height = 1,
Colour = Color4.White.Opacity(0), Colour = Color4.White.Opacity(0),
}); });
@ -99,7 +98,7 @@ namespace osu.Game.Graphics.UserInterface
// dont bother calculating if the strip is invisible // dont bother calculating if the strip is invisible
if (strip.Colour.MaxAlpha > 0) if (strip.Colour.MaxAlpha > 0)
strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth(), 0, 500, Easing.OutQuint); strip.Width = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 1000), strip.Width, StripWidth, 0, 500, Easing.OutQuint);
} }
public class OsuTabItem : TabItem<T>, IHasAccentColour public class OsuTabItem : TabItem<T>, IHasAccentColour

View File

@ -12,10 +12,12 @@ using osu.Framework.Input.Events;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuTextBox : TextBox public class OsuTextBox : BasicTextBox
{ {
protected override float LeftRightPadding => 10; protected override float LeftRightPadding => 10;
protected override float CaretWidth => 3;
protected override SpriteText CreatePlaceholder() => new OsuSpriteText protected override SpriteText CreatePlaceholder() => new OsuSpriteText
{ {
Font = OsuFont.GetFont(italics: true), Font = OsuFont.GetFont(italics: true),
@ -41,6 +43,8 @@ namespace osu.Game.Graphics.UserInterface
BackgroundCommit = BorderColour = colour.Yellow; BackgroundCommit = BorderColour = colour.Yellow;
} }
protected override Color4 SelectionColour => new Color4(249, 90, 255, 255);
protected override void OnFocus(FocusEvent e) protected override void OnFocus(FocusEvent e)
{ {
BorderThickness = 3; BorderThickness = 3;

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osuTK; using osuTK;
@ -13,15 +14,15 @@ namespace osu.Game.Graphics.UserInterface
{ {
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
{ {
public const float ICON_WIDTH = ICON_SIZE + icon_spacing; public const float ICON_WIDTH = ICON_SIZE + spacing;
public const float ICON_SIZE = 25; public const float ICON_SIZE = 25;
private const float spacing = 6;
private const int text_offset = 2;
private SpriteIcon iconSprite; private SpriteIcon iconSprite;
private readonly OsuSpriteText titleText, pageText; private readonly OsuSpriteText titleText, pageText;
private const float icon_spacing = 10;
protected IconUsage Icon protected IconUsage Icon
{ {
set set
@ -63,26 +64,35 @@ namespace osu.Game.Graphics.UserInterface
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(icon_spacing, 0), Spacing = new Vector2(spacing, 0),
Children = new[]
{
CreateIcon(),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(6, 0),
Children = new[] Children = new[]
{ {
CreateIcon().With(t =>
{
t.Anchor = Anchor.Centre;
t.Origin = Anchor.Centre;
}),
titleText = new OsuSpriteText titleText = new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
Margin = new MarginPadding { Bottom = text_offset }
},
new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(4),
Colour = Color4.Gray,
}, },
pageText = new OsuSpriteText pageText = new OsuSpriteText
{ {
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), Anchor = Anchor.Centre,
} Origin = Anchor.Centre,
} Font = OsuFont.GetFont(size: 20),
Margin = new MarginPadding { Bottom = text_offset }
} }
} }
}, },

View File

@ -4,7 +4,6 @@
using osu.Framework.Allocation; 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.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osuTK; using osuTK;
@ -16,8 +15,6 @@ namespace osu.Game.Graphics.UserInterface
/// </summary> /// </summary>
public class ScreenTitleTextureIcon : CompositeDrawable public class ScreenTitleTextureIcon : CompositeDrawable
{ {
private const float circle_allowance = 0.8f;
private readonly string textureName; private readonly string textureName;
public ScreenTitleTextureIcon(string textureName) public ScreenTitleTextureIcon(string textureName)
@ -26,38 +23,17 @@ namespace osu.Game.Graphics.UserInterface
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures, OsuColour colours) private void load(TextureStore textures)
{ {
Size = new Vector2(ScreenTitle.ICON_SIZE / circle_allowance); Size = new Vector2(ScreenTitle.ICON_SIZE);
InternalChildren = new Drawable[] InternalChild = new Sprite
{
new CircularContainer
{
Masking = true,
BorderColour = colours.Violet,
BorderThickness = 3,
MaskingSmoothness = 1,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Sprite
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Texture = textures.Get(textureName), Texture = textures.Get(textureName),
Size = new Vector2(circle_allowance),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, FillMode = FillMode.Fit
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Violet,
Alpha = 0,
AlwaysPresent = true,
},
}
},
}; };
} }
} }

View File

@ -34,12 +34,22 @@ namespace osu.Game.Graphics.UserInterface
public override bool OnPressed(PlatformAction action) public override bool OnPressed(PlatformAction action)
{ {
switch (action.ActionType)
{
case PlatformActionType.LineEnd:
case PlatformActionType.LineStart:
return false;
// Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox // Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
// as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text) // as we do not allow arrow key navigation in the first place (ie. the caret should always be at the end of text)
// Avoid handling it here to allow other components to potentially consume the shortcut. // Avoid handling it here to allow other components to potentially consume the shortcut.
if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete) case PlatformActionType.CharNext:
if (action.ActionMethod == PlatformActionMethod.Delete)
return false; return false;
break;
}
return base.OnPressed(action); return base.OnPressed(action);
} }

View File

@ -39,6 +39,7 @@ using osu.Game.Online.Chat;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Overlays.Volume; using osu.Game.Overlays.Volume;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Utils; using osu.Game.Utils;
@ -204,6 +205,7 @@ namespace osu.Game
Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade);
SelectedMods.BindValueChanged(modsChanged);
Beatmap.BindValueChanged(beatmapChanged, true); Beatmap.BindValueChanged(beatmapChanged, true);
} }
@ -403,9 +405,29 @@ namespace osu.Game
oldBeatmap.Track.Completed -= currentTrackCompleted; oldBeatmap.Track.Completed -= currentTrackCompleted;
} }
updateModDefaults();
nextBeatmap?.LoadBeatmapAsync(); nextBeatmap?.LoadBeatmapAsync();
} }
private void modsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
{
updateModDefaults();
}
private void updateModDefaults()
{
BeatmapDifficulty baseDifficulty = Beatmap.Value.BeatmapInfo.BaseDifficulty;
if (baseDifficulty != null && SelectedMods.Value.Any(m => m is IApplicableToDifficulty))
{
var adjustedDifficulty = baseDifficulty.Clone();
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDifficulty>())
mod.ReadFromDifficulty(adjustedDifficulty);
}
}
private void currentTrackCompleted() => Schedule(() => private void currentTrackCompleted() => Schedule(() =>
{ {
if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled)

View File

@ -29,6 +29,7 @@ using osu.Game.Database;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Resources;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -125,7 +126,7 @@ namespace osu.Game
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll")); Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly));
dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage)); dependencies.Cache(contextFactory = new DatabaseContextFactory(Storage));

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