mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 19:32:55 +08:00
Merge branch 'master' into add-spinner-bonus-score
This commit is contained in:
commit
8a85875cbf
@ -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>
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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[]
|
||||||
{
|
{
|
||||||
@ -134,7 +141,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
49
osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
Normal file
49
osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
46
osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
Normal file
46
osu.Game.Rulesets.Mania.Tests/SkinnableTestScene.cs
Normal 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()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
Normal file
37
osu.Game.Rulesets.Mania.Tests/TestSceneDrawableJudgement.cs
Normal 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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
314
osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
Normal file
314
osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs
Normal 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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
Normal file
15
osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs
Normal 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())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
@ -178,7 +183,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
11
osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
Normal file
11
osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -22,26 +22,15 @@ 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 Container hitTargetLine;
|
private readonly Drawable hitTarget;
|
||||||
private readonly Drawable hitTargetBar;
|
|
||||||
|
|
||||||
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
|
public ColumnHitObjectArea(HitObjectContainer hitObjectContainer)
|
||||||
{
|
{
|
||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
hitTargetBar = new Box
|
hitTarget = new DefaultHitTarget
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = NotePiece.NOTE_HEIGHT,
|
|
||||||
Alpha = 0.6f,
|
|
||||||
Colour = Color4.Black
|
|
||||||
},
|
|
||||||
hitTargetLine = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = hit_target_bar_height,
|
|
||||||
Masking = true,
|
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
|
||||||
},
|
},
|
||||||
hitObjectContainer
|
hitObjectContainer
|
||||||
};
|
};
|
||||||
@ -55,17 +44,10 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
{
|
{
|
||||||
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||||
|
|
||||||
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
|
hitTarget.Anchor = hitTarget.Origin = anchor;
|
||||||
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
updateColours();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
@ -78,21 +60,86 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
|
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
|
|
||||||
updateColours();
|
if (hitTarget is IHasAccentColour colouredHitTarget)
|
||||||
|
colouredHitTarget.AccentColour = accentColour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateColours()
|
private class DefaultHitTarget : CompositeDrawable, IHasAccentColour
|
||||||
{
|
{
|
||||||
if (!IsLoaded)
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
return;
|
|
||||||
|
|
||||||
hitTargetLine.EdgeEffect = new EdgeEffectParameters
|
private readonly Container hitTargetLine;
|
||||||
|
private readonly Drawable hitTargetBar;
|
||||||
|
|
||||||
|
public DefaultHitTarget()
|
||||||
{
|
{
|
||||||
Type = EdgeEffectType.Glow,
|
InternalChildren = new[]
|
||||||
Radius = 5,
|
{
|
||||||
Colour = accentColour.Opacity(0.5f),
|
hitTargetBar = new Box
|
||||||
};
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = NotePiece.NOTE_HEIGHT,
|
||||||
|
Alpha = 0.6f,
|
||||||
|
Colour = Color4.Black
|
||||||
|
},
|
||||||
|
hitTargetLine = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = hit_target_bar_height,
|
||||||
|
Masking = true,
|
||||||
|
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
|
direction.BindValueChanged(dir =>
|
||||||
|
{
|
||||||
|
Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
|
||||||
|
|
||||||
|
hitTargetBar.Anchor = hitTargetBar.Origin = anchor;
|
||||||
|
hitTargetLine.Anchor = hitTargetLine.Origin = anchor;
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
updateColours();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 accentColour;
|
||||||
|
|
||||||
|
public Color4 AccentColour
|
||||||
|
{
|
||||||
|
get => accentColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (accentColour == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
accentColour = value;
|
||||||
|
|
||||||
|
updateColours();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateColours()
|
||||||
|
{
|
||||||
|
if (!IsLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hitTargetLine.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 5,
|
||||||
|
Colour = accentColour.Opacity(0.5f),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
155
osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
Normal file
155
osu.Game.Rulesets.Osu.Tests/TestSceneLegacyBeatmapSkin.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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]
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
49
osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
Normal file
49
osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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";
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
@ -178,7 +179,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
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();
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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),
|
||||||
|
@ -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
|
||||||
|
11
osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
Normal file
11
osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs
Normal 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
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitclap.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitfinish.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitnormal.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/normal-hitwhistle.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitclap.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitfinish.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitnormal.wav
Executable file
Binary file not shown.
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav
Executable file
BIN
osu.Game.Rulesets.Taiko/Resources/Samples/Gameplay/soft-hitwhistle.wav
Executable file
Binary file not shown.
49
osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
Normal file
49
osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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[]
|
||||||
{
|
{
|
||||||
@ -133,7 +145,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
@ -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 =
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
159
osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
Normal file
159
osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
74
osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
Normal file
74
osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
Normal 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()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz
Normal file
BIN
osu.Game.Tests/Resources/Archives/241526 Soleily - Renatus.osz
Normal file
Binary file not shown.
Binary file not shown.
@ -13,7 +13,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
BIN
osu.Game.Tests/Resources/test-sample.mp3
Normal file
BIN
osu.Game.Tests/Resources/test-sample.mp3
Normal file
Binary file not shown.
@ -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(255, 128, 128, 255),
|
||||||
new Color4(142, 199, 255, 255),
|
new Color4(128, 255, 255, 255),
|
||||||
new Color4(255, 128, 128, 255),
|
new Color4(100, 100, 100, 100),
|
||||||
new Color4(128, 255, 255, 255),
|
};
|
||||||
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()
|
||||||
{
|
{
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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[]
|
||||||
{
|
{
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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) });
|
||||||
|
@ -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),
|
||||||
|
@ -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) };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
{
|
{
|
@ -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))
|
||||||
|
@ -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}";
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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"/>.
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
|
@ -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,10 +74,15 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
},
|
},
|
||||||
Nub = new Nub
|
nubContainer = new Container
|
||||||
{
|
{
|
||||||
Origin = Anchor.TopCentre,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Expanded = true,
|
Child = Nub = new Nub
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativePositionAxes = Axes.X,
|
||||||
|
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>
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
CreateIcon(),
|
CreateIcon().With(t =>
|
||||||
new FillFlowContainer
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
t.Anchor = Anchor.Centre;
|
||||||
Direction = FillDirection.Horizontal,
|
t.Origin = Anchor.Centre;
|
||||||
Spacing = new Vector2(6, 0),
|
}),
|
||||||
Children = new[]
|
titleText = new OsuSpriteText
|
||||||
{
|
{
|
||||||
titleText = new OsuSpriteText
|
Anchor = Anchor.Centre,
|
||||||
{
|
Origin = Anchor.Centre,
|
||||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
|
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
|
||||||
},
|
Margin = new MarginPadding { Bottom = text_offset }
|
||||||
pageText = new OsuSpriteText
|
},
|
||||||
{
|
new Circle
|
||||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light),
|
{
|
||||||
}
|
Anchor = Anchor.Centre,
|
||||||
}
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(4),
|
||||||
|
Colour = Color4.Gray,
|
||||||
|
},
|
||||||
|
pageText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(size: 20),
|
||||||
|
Margin = new MarginPadding { Bottom = text_offset }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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
|
RelativeSizeAxes = Axes.Both,
|
||||||
{
|
Texture = textures.Get(textureName),
|
||||||
Masking = true,
|
Anchor = Anchor.Centre,
|
||||||
BorderColour = colours.Violet,
|
Origin = Anchor.Centre,
|
||||||
BorderThickness = 3,
|
FillMode = FillMode.Fit
|
||||||
MaskingSmoothness = 1,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Sprite
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Texture = textures.Get(textureName),
|
|
||||||
Size = new Vector2(circle_allowance),
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
},
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = colours.Violet,
|
|
||||||
Alpha = 0,
|
|
||||||
AlwaysPresent = true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,21 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
public override bool OnPressed(PlatformAction action)
|
public override bool OnPressed(PlatformAction action)
|
||||||
{
|
{
|
||||||
// Shift+delete is handled via PlatformAction on macOS. this is not so useful in the context of a SearchTextBox
|
switch (action.ActionType)
|
||||||
// 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.
|
case PlatformActionType.LineEnd:
|
||||||
if (action.ActionType == PlatformActionType.CharNext && action.ActionMethod == PlatformActionMethod.Delete)
|
case PlatformActionType.LineStart:
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
// Avoid handling it here to allow other components to potentially consume the shortcut.
|
||||||
|
case PlatformActionType.CharNext:
|
||||||
|
if (action.ActionMethod == PlatformActionMethod.Delete)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnPressed(action);
|
return base.OnPressed(action);
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
39
osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs
Normal file
39
osu.Game/Overlays/BreadcrumbControlOverlayHeader.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// 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.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays
|
||||||
|
{
|
||||||
|
public abstract class BreadcrumbControlOverlayHeader : OverlayHeader
|
||||||
|
{
|
||||||
|
protected OverlayHeaderBreadcrumbControl BreadcrumbControl;
|
||||||
|
|
||||||
|
protected override TabControl<string> CreateTabControl() => BreadcrumbControl = new OverlayHeaderBreadcrumbControl();
|
||||||
|
|
||||||
|
public class OverlayHeaderBreadcrumbControl : BreadcrumbControl<string>
|
||||||
|
{
|
||||||
|
public OverlayHeaderBreadcrumbControl()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TabItem<string> CreateTabItem(string value) => new ControlTabItem(value);
|
||||||
|
|
||||||
|
private class ControlTabItem : BreadcrumbTabItem
|
||||||
|
{
|
||||||
|
protected override float ChevronSize => 8;
|
||||||
|
|
||||||
|
public ControlTabItem(string value)
|
||||||
|
: base(value)
|
||||||
|
{
|
||||||
|
Text.Font = Text.Font.With(size: 14);
|
||||||
|
Chevron.Y = 3;
|
||||||
|
Bar.Height = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,7 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Changelog
|
namespace osu.Game.Overlays.Changelog
|
||||||
{
|
{
|
||||||
public class ChangelogHeader : OverlayHeader
|
public class ChangelogHeader : BreadcrumbControlOverlayHeader
|
||||||
{
|
{
|
||||||
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
|
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
|
||||||
|
|
||||||
@ -23,12 +23,12 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
|
|
||||||
public UpdateStreamBadgeArea Streams;
|
public UpdateStreamBadgeArea Streams;
|
||||||
|
|
||||||
private const string listing_string = "Listing";
|
private const string listing_string = "listing";
|
||||||
|
|
||||||
public ChangelogHeader()
|
public ChangelogHeader()
|
||||||
{
|
{
|
||||||
TabControl.AddItem(listing_string);
|
BreadcrumbControl.AddItem(listing_string);
|
||||||
TabControl.Current.ValueChanged += e =>
|
BreadcrumbControl.Current.ValueChanged += e =>
|
||||||
{
|
{
|
||||||
if (e.NewValue == listing_string)
|
if (e.NewValue == listing_string)
|
||||||
ListingSelected?.Invoke();
|
ListingSelected?.Invoke();
|
||||||
@ -46,7 +46,9 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
TabControl.AccentColour = colours.Violet;
|
BreadcrumbControl.AccentColour = colours.Violet;
|
||||||
|
TitleBackgroundColour = colours.GreyVioletDarker;
|
||||||
|
ControlBackgroundColour = colours.GreyVioletDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChangelogHeaderTitle title;
|
private ChangelogHeaderTitle title;
|
||||||
@ -54,12 +56,12 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
private void showBuild(ValueChangedEvent<APIChangelogBuild> e)
|
private void showBuild(ValueChangedEvent<APIChangelogBuild> e)
|
||||||
{
|
{
|
||||||
if (e.OldValue != null)
|
if (e.OldValue != null)
|
||||||
TabControl.RemoveItem(e.OldValue.ToString());
|
BreadcrumbControl.RemoveItem(e.OldValue.ToString());
|
||||||
|
|
||||||
if (e.NewValue != null)
|
if (e.NewValue != null)
|
||||||
{
|
{
|
||||||
TabControl.AddItem(e.NewValue.ToString());
|
BreadcrumbControl.AddItem(e.NewValue.ToString());
|
||||||
TabControl.Current.Value = e.NewValue.ToString();
|
BreadcrumbControl.Current.Value = e.NewValue.ToString();
|
||||||
|
|
||||||
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name);
|
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name);
|
||||||
|
|
||||||
@ -67,7 +69,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TabControl.Current.Value = listing_string;
|
BreadcrumbControl.Current.Value = listing_string;
|
||||||
Streams.Current.Value = null;
|
Streams.Current.Value = null;
|
||||||
title.Version = null;
|
title.Version = null;
|
||||||
}
|
}
|
||||||
@ -111,7 +113,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
|
|
||||||
public ChangelogHeaderTitle()
|
public ChangelogHeaderTitle()
|
||||||
{
|
{
|
||||||
Title = "Changelog";
|
Title = "changelog";
|
||||||
Version = null;
|
Version = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +158,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Task initialFetchTask;
|
private Task initialFetchTask;
|
||||||
|
|
||||||
private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => Schedule(action));
|
private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ =>
|
||||||
|
Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||||
|
|
||||||
private Task fetchListing()
|
private Task fetchListing()
|
||||||
{
|
{
|
||||||
@ -185,10 +186,10 @@ namespace osu.Game.Overlays
|
|||||||
tcs.SetResult(true);
|
tcs.SetResult(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
req.Failure += _ =>
|
req.Failure += e =>
|
||||||
{
|
{
|
||||||
initialFetchTask = null;
|
initialFetchTask = null;
|
||||||
tcs.SetResult(false);
|
tcs.SetException(e);
|
||||||
};
|
};
|
||||||
|
|
||||||
await API.PerformAsync(req);
|
await API.PerformAsync(req);
|
||||||
|
@ -171,6 +171,7 @@ namespace osu.Game.Overlays
|
|||||||
d.Origin = Anchor.BottomLeft;
|
d.Origin = Anchor.BottomLeft;
|
||||||
d.RelativeSizeAxes = Axes.Both;
|
d.RelativeSizeAxes = Axes.Both;
|
||||||
d.OnRequestLeave = channelManager.LeaveChannel;
|
d.OnRequestLeave = channelManager.LeaveChannel;
|
||||||
|
d.IsSwitchable = true;
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user