diff --git a/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs b/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs index 4fdfbc789..6026466ef 100644 --- a/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs +++ b/osu.Game.Rulesets.Karaoke/Configuration/KaraokeSessionStatics.cs @@ -52,7 +52,7 @@ public KaraokeSessionStatics(KaraokeRulesetConfigManager config, IBeatmap beatma SetDefault(KaraokeRulesetSession.PlaybackSpeed, overridePlaybackSpeed ? playbackSpeedValue : 0, -10, 10); // Practice - SetDefault(KaraokeRulesetSession.NowLyric, null); + SetDefault(KaraokeRulesetSession.SingingLyrics, null); // Saiten status SetDefault(KaraokeRulesetSession.SaitenStatus, SaitenStatusMode.NotInitialized); @@ -80,7 +80,7 @@ public enum KaraokeRulesetSession PlaybackSpeed, // Practice - NowLyric, + SingingLyrics, // Saiten status SaitenStatus, diff --git a/osu.Game.Rulesets.Karaoke/Difficulty/KaraokeDifficultyCalculator.cs b/osu.Game.Rulesets.Karaoke/Difficulty/KaraokeDifficultyCalculator.cs index 2afc1b16f..5908fedc8 100644 --- a/osu.Game.Rulesets.Karaoke/Difficulty/KaraokeDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Karaoke/Difficulty/KaraokeDifficultyCalculator.cs @@ -39,9 +39,6 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat if (beatmap.HitObjects.Count == 0) return new KaraokeDifficultyAttributes { Mods = mods }; - HitWindows hitWindows = new KaraokeHitWindows(); - hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); - return new KaraokeDifficultyAttributes { StarRating = skills[0].DifficultyValue() * star_scaling_factor, diff --git a/osu.Game.Rulesets.Karaoke/Judgements/KaraokeLyricJudgement.cs b/osu.Game.Rulesets.Karaoke/Judgements/KaraokeLyricJudgement.cs index 38a597f15..ed22f8b09 100644 --- a/osu.Game.Rulesets.Karaoke/Judgements/KaraokeLyricJudgement.cs +++ b/osu.Game.Rulesets.Karaoke/Judgements/KaraokeLyricJudgement.cs @@ -7,19 +7,8 @@ namespace osu.Game.Rulesets.Karaoke.Judgements { public class KaraokeLyricJudgement : KaraokeJudgement { - public LyricTime Time { get; set; } - public override HitResult MaxResult => HitResult.Perfect; protected override double HealthIncreaseFor(HitResult result) => 0; } - - public enum LyricTime - { - NotYet, - - Available, - - Exceed - } } diff --git a/osu.Game.Rulesets.Karaoke/Objects/Drawables/DrawableLyric.cs b/osu.Game.Rulesets.Karaoke/Objects/Drawables/DrawableLyric.cs index 220effcc3..31cea3c8f 100644 --- a/osu.Game.Rulesets.Karaoke/Objects/Drawables/DrawableLyric.cs +++ b/osu.Game.Rulesets.Karaoke/Objects/Drawables/DrawableLyric.cs @@ -11,13 +11,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Karaoke.Configuration; -using osu.Game.Rulesets.Karaoke.Judgements; +using osu.Game.Rulesets.Karaoke.Scoring; using osu.Game.Rulesets.Karaoke.Skinning.Default; using osu.Game.Rulesets.Karaoke.Skinning.Elements; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK; @@ -46,10 +43,8 @@ public class DrawableLyric : DrawableKaraokeHitObject private readonly IBindableList singersBindable = new BindableList(); private readonly BindableDictionary translateTextBindable = new(); - /// - /// Invoked when a has been applied by this or a nested . - /// - public event Action OnLyricStart; + public event Action OnLyricStart; + public event Action OnLyricEnd; public new Lyric HitObject => (Lyric)base.HitObject; @@ -208,26 +203,20 @@ private void applyTranslate() protected override void CheckForResult(bool userTriggered, double timeOffset) { - if (Result.Judgement is not KaraokeLyricJudgement judgement) - return; - - double lyricStartOffset = timeOffset + HitObject.LyricDuration; - - if (lyricStartOffset < 0) - { - judgement.Time = LyricTime.NotYet; - } - else if (!HitObject.HitWindows.CanBeHit(lyricStartOffset) && judgement.Time != LyricTime.Available) + if (timeOffset + HitObject.LyricDuration >= 0 && HitObject.HitWindows.CanBeHit(timeOffset + HitObject.LyricDuration)) { - // Apply start hit result - judgement.Time = LyricTime.Available; - OnLyricStart?.Invoke(this, Result); + // note: CheckForResult will not being triggered when roll-back the time. + // so there's no need to consider the case while roll-back. + OnLyricStart?.Invoke(this); + return; } - else if (!HitObject.HitWindows.CanBeHit(timeOffset)) + + if (timeOffset >= 0 && HitObject.HitWindows.CanBeHit(timeOffset)) { - judgement.Time = LyricTime.Exceed; + OnLyricEnd?.Invoke(this); + // Apply end hit result - ApplyResult(r => { r.Type = HitResult.Meh; }); + ApplyResult(r => { r.Type = KaraokeLyricHitWindows.DEFAULT_HIT_RESULT; }); } } diff --git a/osu.Game.Rulesets.Karaoke/Objects/KaraokeHitObject.cs b/osu.Game.Rulesets.Karaoke/Objects/KaraokeHitObject.cs index 53d6c8376..8c41ee276 100644 --- a/osu.Game.Rulesets.Karaoke/Objects/KaraokeHitObject.cs +++ b/osu.Game.Rulesets.Karaoke/Objects/KaraokeHitObject.cs @@ -1,9 +1,7 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Karaoke.Scoring; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Karaoke.Objects { @@ -11,7 +9,5 @@ public class KaraokeHitObject : HitObject { public double TimePreempt = 600; public double TimeFadeIn = 400; - - protected override HitWindows CreateHitWindows() => new KaraokeHitWindows(); } } diff --git a/osu.Game.Rulesets.Karaoke/Objects/Lyric.cs b/osu.Game.Rulesets.Karaoke/Objects/Lyric.cs index 02a7d3bd7..0b9281303 100644 --- a/osu.Game.Rulesets.Karaoke/Objects/Lyric.cs +++ b/osu.Game.Rulesets.Karaoke/Objects/Lyric.cs @@ -12,8 +12,10 @@ using osu.Game.Rulesets.Karaoke.Beatmaps; using osu.Game.Rulesets.Karaoke.Judgements; using osu.Game.Rulesets.Karaoke.Objects.Types; +using osu.Game.Rulesets.Karaoke.Scoring; using osu.Game.Rulesets.Karaoke.Utils; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Karaoke.Objects { @@ -202,5 +204,7 @@ public void InitialWorkingTime() StartTime = LyricStartTime; Duration = LyricDuration; } + + protected override HitWindows CreateHitWindows() => new KaraokeLyricHitWindows(); } } diff --git a/osu.Game.Rulesets.Karaoke/Objects/Note.cs b/osu.Game.Rulesets.Karaoke/Objects/Note.cs index 6eafd0c54..8ade51dc2 100644 --- a/osu.Game.Rulesets.Karaoke/Objects/Note.cs +++ b/osu.Game.Rulesets.Karaoke/Objects/Note.cs @@ -7,7 +7,9 @@ using osu.Game.Rulesets.Karaoke.Configuration; using osu.Game.Rulesets.Karaoke.Judgements; using osu.Game.Rulesets.Karaoke.Objects.Types; +using osu.Game.Rulesets.Karaoke.Scoring; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Karaoke.Objects { @@ -100,5 +102,7 @@ public Lyric ParentLyric } public override Judgement CreateJudgement() => new KaraokeNoteJudgement(); + + protected override HitWindows CreateHitWindows() => new KaraokeNoteHitWindows(); } } diff --git a/osu.Game.Rulesets.Karaoke/Scoring/KaraokeHitWindows.cs b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeHitWindows.cs index 6d3d66d7d..3e981c921 100644 --- a/osu.Game.Rulesets.Karaoke/Scoring/KaraokeHitWindows.cs +++ b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeHitWindows.cs @@ -5,28 +5,7 @@ namespace osu.Game.Rulesets.Karaoke.Scoring { - public class KaraokeHitWindows : HitWindows + public abstract class KaraokeHitWindows : HitWindows { - private static readonly DifficultyRange[] karaoke_ranges = - { - new(HitResult.Perfect, 80, 50, 20), - new(HitResult.Meh, 80, 50, 20), - new(HitResult.Miss, 2000, 1500, 1000), - }; - - public override bool IsHitResultAllowed(HitResult result) - { - // In karaoke ruleset, time range is not the first thing. - return result switch - { - // Karaoke note hit result - HitResult.Perfect => true, - // Lyric hit result - HitResult.Meh => true, - _ => false - }; - } - - protected override DifficultyRange[] GetRanges() => karaoke_ranges; } } diff --git a/osu.Game.Rulesets.Karaoke/Scoring/KaraokeLyricHitWindows.cs b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeLyricHitWindows.cs new file mode 100644 index 000000000..c8c063359 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeLyricHitWindows.cs @@ -0,0 +1,26 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Karaoke.Scoring +{ + public class KaraokeLyricHitWindows : KaraokeHitWindows + { + public const HitResult DEFAULT_HIT_RESULT = HitResult.Perfect; + + private static readonly DifficultyRange[] lyric_ranges = + { + new(DEFAULT_HIT_RESULT, 40, 20, 10), + }; + + public override bool IsHitResultAllowed(HitResult result) => + result switch + { + DEFAULT_HIT_RESULT => true, + _ => false + }; + + protected override DifficultyRange[] GetRanges() => lyric_ranges; + } +} diff --git a/osu.Game.Rulesets.Karaoke/Scoring/KaraokeNoteHitWindows.cs b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeNoteHitWindows.cs new file mode 100644 index 000000000..4640d8966 --- /dev/null +++ b/osu.Game.Rulesets.Karaoke/Scoring/KaraokeNoteHitWindows.cs @@ -0,0 +1,27 @@ +// Copyright (c) andy840119 . Licensed under the GPL Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Karaoke.Scoring +{ + public class KaraokeNoteHitWindows : KaraokeHitWindows + { + private static readonly DifficultyRange[] karaoke_ranges = + { + new(HitResult.Perfect, 80, 50, 20), + new(HitResult.Meh, 80, 50, 20), + new(HitResult.Miss, 2000, 1500, 1000), + }; + + public override bool IsHitResultAllowed(HitResult result) => + result switch + { + HitResult.Perfect => true, + HitResult.Meh => true, + _ => false + }; + + protected override DifficultyRange[] GetRanges() => karaoke_ranges; + } +} diff --git a/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/LyricPreview.cs b/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/LyricPreview.cs index 63e1c773b..f9c17cd5d 100644 --- a/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/LyricPreview.cs +++ b/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/LyricPreview.cs @@ -121,7 +121,7 @@ private Lyric createPreviewLyric() Text = "karaoke" }, }, - HitWindows = new KaraokeHitWindows(), + HitWindows = new KaraokeLyricHitWindows(), }; private IDictionary createPreviewTranslate(CultureInfo cultureInfo) diff --git a/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/NotePlayfieldPreview.cs b/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/NotePlayfieldPreview.cs index 985a36ddb..84c2dae90 100644 --- a/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/NotePlayfieldPreview.cs +++ b/osu.Game.Rulesets.Karaoke/Screens/Settings/Previews/Gameplay/NotePlayfieldPreview.cs @@ -66,7 +66,7 @@ protected override void Update() StartTime = startTime, Duration = 1000, Text = "Note", - HitWindows = new KaraokeHitWindows(), + HitWindows = new KaraokeNoteHitWindows(), }); notePlayfield.Add(new BarLine diff --git a/osu.Game.Rulesets.Karaoke/UI/LyricPlayfield.cs b/osu.Game.Rulesets.Karaoke/UI/LyricPlayfield.cs index 2dc8aaafe..fd2acf801 100644 --- a/osu.Game.Rulesets.Karaoke/UI/LyricPlayfield.cs +++ b/osu.Game.Rulesets.Karaoke/UI/LyricPlayfield.cs @@ -1,15 +1,11 @@ // Copyright (c) andy840119 . Licensed under the GPL Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Caching; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Karaoke.Configuration; -using osu.Game.Rulesets.Karaoke.Judgements; using osu.Game.Rulesets.Karaoke.Objects; using osu.Game.Rulesets.Karaoke.Objects.Drawables; using osu.Game.Rulesets.Objects; @@ -20,69 +16,46 @@ namespace osu.Game.Rulesets.Karaoke.UI { public class LyricPlayfield : Playfield { - [Resolved] - private IBindable beatmap { get; set; } + private readonly Bindable singingLyrics = new(); - public new IEnumerable AllHitObjects => base.AllHitObjects.OfType(); - - protected WorkingBeatmap WorkingBeatmap => beatmap.Value; - - private readonly BindableDouble preemptTime = new(); - private readonly Bindable nowLyric = new(); - private readonly Cached seekCache = new(); - - public LyricPlayfield() + protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) { - // Switch to target time - nowLyric.BindValueChanged(value => + if (drawableHitObject is DrawableLyric drawableLyric) { - if (!seekCache.IsValid || value.NewValue == null) - return; - - double lyricStartTime = value.NewValue.LyricStartTime - preemptTime.Value; - - WorkingBeatmap.Track.Seek(lyricStartTime); - }); + drawableLyric.OnLyricStart += onLyricStart; + drawableLyric.OnLyricEnd += onLyricEnd; + } - seekCache.Validate(); + base.OnNewDrawableHitObject(drawableHitObject); } - protected override void LoadComplete() + private void onLyricStart(DrawableLyric drawableLyric) { - base.LoadComplete(); + var lyrics = singingLyrics.Value ?? Array.Empty(); + var lyric = drawableLyric.HitObject; - NewResult += OnNewResult; - } - - protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject) - { - if (drawableHitObject is DrawableLyric drawableLyric) - { - // todo : not really sure should cancel binding action in here? - drawableLyric.OnLyricStart += OnNewResult; - } + if (lyrics.Contains(lyric)) + return; - base.OnNewDrawableHitObject(drawableHitObject); + singingLyrics.Value = lyrics.Concat(new[] { lyric }).ToArray(); } - internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) + private void onLyricEnd(DrawableLyric drawableLyric) { - if (result.Judgement is not KaraokeLyricJudgement karaokeLyricJudgement) + var lyrics = singingLyrics.Value ?? Array.Empty(); + var lyric = drawableLyric.HitObject; + + if (!lyrics.Contains(lyric)) return; - // Update now lyric - var targetLyric = karaokeLyricJudgement.Time == LyricTime.Available ? judgedObject.HitObject as Lyric : null; - seekCache.Invalidate(); - nowLyric.Value = targetLyric; - seekCache.Validate(); + singingLyrics.Value = lyrics.Where(x => x != lyric).ToArray(); } [BackgroundDependencyLoader] - private void load(KaraokeRulesetConfigManager rulesetConfig, KaraokeSessionStatics session) + private void load(KaraokeSessionStatics session) { // Practice - rulesetConfig.BindWith(KaraokeRulesetSetting.PracticePreemptTime, preemptTime); - session.BindWith(KaraokeRulesetSession.NowLyric, nowLyric); + session.BindWith(KaraokeRulesetSession.SingingLyrics, singingLyrics); RegisterPool(50); } diff --git a/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/LyricsPreview.cs b/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/LyricsPreview.cs index 7da684e5a..54ee8a69b 100644 --- a/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/LyricsPreview.cs +++ b/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/LyricsPreview.cs @@ -10,8 +10,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets.Karaoke.Configuration; using osu.Game.Rulesets.Karaoke.Graphics.Sprites; using osu.Game.Rulesets.Karaoke.Objects; using osuTK; @@ -21,10 +23,14 @@ namespace osu.Game.Rulesets.Karaoke.UI.PlayerSettings { public class LyricsPreview : CompositeDrawable { - public Bindable SelectedLyric { get; } = new(); + private readonly Bindable bindablePreemptTime = new(); + private readonly Bindable singingLyrics = new(); private readonly FillFlowContainer lyricTable; + [Resolved] + private IBindable beatmap { get; set; } + public LyricsPreview(IEnumerable lyrics) { InternalChild = new OsuScrollContainer @@ -35,7 +41,7 @@ public LyricsPreview(IEnumerable lyrics) AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Direction = FillDirection.Vertical, - Children = lyrics.Select(x => CreateLyricContainer(x).With(c => + Children = lyrics.Select(x => createLyricContainer(x).With(c => { c.Selected = false; c.Action = () => triggerLyric(x); @@ -43,24 +49,28 @@ public LyricsPreview(IEnumerable lyrics) } }; - SelectedLyric.BindValueChanged(value => + singingLyrics.BindValueChanged(value => { var oldValue = value.OldValue; if (oldValue != null) - lyricTable.Where(x => x.HitObject == oldValue).ForEach(x => { x.Selected = false; }); + lyricTable.Where(x => oldValue.Contains(x.HitObject)).ForEach(x => { x.Selected = false; }); var newValue = value.NewValue; if (newValue != null) - lyricTable.Where(x => x.HitObject == newValue).ForEach(x => { x.Selected = true; }); + lyricTable.Where(x => newValue.Contains(x.HitObject)).ForEach(x => { x.Selected = true; }); }); } + private ClickableLyric createLyricContainer(Lyric lyric) => new(lyric); + private void triggerLyric(Lyric lyric) { - if (SelectedLyric.Value == lyric) - SelectedLyric.TriggerChange(); - else - SelectedLyric.Value = lyric; + double time = lyric.LyricStartTime - bindablePreemptTime.Value; + beatmap.Value.Track.Seek(time); + + // because playback might not clear singing lyrics, so we should re-assign the lyric here. + // todo: find a better place. + singingLyrics.Value = new[] { lyric }; } public Vector2 Spacing @@ -69,9 +79,14 @@ public Vector2 Spacing set => lyricTable.Spacing = value; } - protected virtual ClickableLyric CreateLyricContainer(Lyric lyric) => new(lyric); + [BackgroundDependencyLoader] + private void load(KaraokeRulesetConfigManager config, KaraokeSessionStatics session) + { + config.BindWith(KaraokeRulesetSetting.PracticePreemptTime, bindablePreemptTime); + session.BindWith(KaraokeRulesetSession.SingingLyrics, singingLyrics); + } - public class ClickableLyric : ClickableContainer + private class ClickableLyric : ClickableContainer { private const float fade_duration = 100; @@ -94,12 +109,12 @@ public ClickableLyric(Lyric lyric) { RelativeSizeAxes = Axes.Both }, - icon = CreateIcon(), - previewLyric = CreateLyric(lyric), + icon = createIcon(), + previewLyric = createLyric(lyric), }; } - protected virtual PreviewLyricSpriteText CreateLyric(Lyric lyric) => new(lyric) + private PreviewLyricSpriteText createLyric(Lyric lyric) => new(lyric) { Font = new FontUsage(size: 25), RubyFont = new FontUsage(size: 10), @@ -107,7 +122,7 @@ public ClickableLyric(Lyric lyric) Margin = new MarginPadding { Left = 25 } }; - protected virtual Drawable CreateIcon() => new SpriteIcon + private Drawable createIcon() => new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/PracticeSettings.cs b/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/PracticeSettings.cs index 96f2af8ca..9ac90c53f 100644 --- a/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/PracticeSettings.cs +++ b/osu.Game.Rulesets.Karaoke/UI/PlayerSettings/PracticeSettings.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Karaoke.UI.PlayerSettings public class PracticeSettings : PlayerSettingsGroup, IKeyBindingHandler { private readonly PlayerSliderBar preemptTimeSliderBar; - private readonly LyricsPreview lyricsPreview; public PracticeSettings(IBeatmap beatmap) : base("Practice") @@ -36,7 +35,7 @@ public PracticeSettings(IBeatmap beatmap) { Text = "Lyric:" }, - lyricsPreview = new LyricsPreview(lyrics) + new LyricsPreview(lyrics) { Height = 580, RelativeSizeAxes = Axes.X, @@ -77,10 +76,9 @@ public void OnReleased(KeyBindingReleaseEvent e) } [BackgroundDependencyLoader] - private void load(KaraokeRulesetConfigManager config, KaraokeSessionStatics session) + private void load(KaraokeRulesetConfigManager config) { preemptTimeSliderBar.Current = config.GetBindable(KaraokeRulesetSetting.PracticePreemptTime); - session.BindWith(KaraokeRulesetSession.NowLyric, lyricsPreview.SelectedLyric); } } }