diff --git a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapProcessor.cs b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapProcessor.cs index 39499a371..5710c5936 100644 --- a/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Sentakki/Beatmaps/SentakkiBeatmapProcessor.cs @@ -32,7 +32,7 @@ public override void PostProcess() { Color4 noteColor = hitObject.DefaultNoteColour; - if (hitObject is SentakkiLanedHitObject laned && laned.Break) + if (hitObject is IBreakNote breakNote && breakNote.Break) noteColor = breakColor; else if (isTwin) noteColor = twinColor; diff --git a/osu.Game.Rulesets.Sentakki/Mods/SentakkiModRelax.cs b/osu.Game.Rulesets.Sentakki/Mods/SentakkiModRelax.cs index 0ade0b6e1..4a50394f1 100644 --- a/osu.Game.Rulesets.Sentakki/Mods/SentakkiModRelax.cs +++ b/osu.Game.Rulesets.Sentakki/Mods/SentakkiModRelax.cs @@ -25,7 +25,8 @@ public class SentakkiModRelax : Mod, IApplicableAfterBeatmapConversion public void ApplyToBeatmap(IBeatmap beatmap) { foreach (SentakkiHitObject ho in beatmap.HitObjects) - ho.Ex = true; + if (ho is IExNote exNote) + exNote.Ex = true; } } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs index fae189dfd..2fb9fb813 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiHitObject.cs @@ -1,12 +1,14 @@ -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Sentakki.Judgements; using osu.Game.Rulesets.Sentakki.UI; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Sentakki.Objects.Drawables { @@ -25,7 +27,13 @@ public bool Auto // Used for the animation update protected readonly Bindable AnimationDuration = new Bindable(1000); - protected override float SamplePlaybackPosition => (Position.X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + 0.5f; + protected override float SamplePlaybackPosition => Position.X / (SentakkiPlayfield.INTERSECTDISTANCE * 2); + + private PausableSkinnableSound breakSample = null!; + + private Container scorePaddingObjects = null!; + + private Container scoreBonusObjects = null!; public DrawableSentakkiHitObject() : this(null) @@ -37,6 +45,32 @@ public DrawableSentakkiHitObject(SentakkiHitObject? hitObject = null) { } + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + scorePaddingObjects = new Container(), + scoreBonusObjects = new Container(), + breakSample = new PausableSkinnableSound(), + }); + } + + protected override void LoadSamples() + { + base.LoadSamples(); + + breakSample.Samples = HitObject.CreateBreakSample(); + } + + public override void PlaySamples() + { + base.PlaySamples(); + + breakSample.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); + breakSample.Play(); + } + [Resolved] protected DrawableSentakkiRuleset? DrawableSentakkiRuleset { get; private set; } @@ -52,7 +86,46 @@ protected override void OnApply() { base.OnApply(); AccentColour.BindTo(HitObject.ColourBindable); - ExBindable.BindTo(HitObject.ExBindable); + ExBindable.Value = (HitObject as IExNote)?.Ex ?? false; + } + + protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) + { + switch (hitObject) + { + case ScorePaddingObject p: + return new DrawableScorePaddingObject(p); + + case ScoreBonusObject b: + return new DrawableScoreBonusObject(b); + } + + return base.CreateNestedHitObject(hitObject); + } + + protected override void AddNestedHitObject(DrawableHitObject hitObject) + { + switch (hitObject) + { + case DrawableScorePaddingObject p: + scorePaddingObjects.Add(p); + break; + + case DrawableScoreBonusObject b: + scoreBonusObjects.Add(b); + break; + + default: + base.AddNestedHitObject(hitObject); + break; + } + } + + protected override void ClearNestedHitObjects() + { + base.ClearNestedHitObjects(); + scorePaddingObjects.Clear(false); + scoreBonusObjects.Clear(false); } protected override JudgementResult CreateResult(Judgement judgement) => new SentakkiJudgementResult(HitObject, judgement); @@ -70,6 +143,14 @@ protected override void OnApply() SentakkiJudgementResult.Critical = false; } + // Judge the scoreBonus + foreach (var bonusObject in scoreBonusObjects) + bonusObject.TriggerResult(); + + // Also give Break note score padding a judgement + for (int i = 0; i < scorePaddingObjects.Count; ++i) + scorePaddingObjects[^(i + 1)].ApplyResult(result); + base.ApplyResult(result); } @@ -77,7 +158,7 @@ protected override void OnFree() { base.OnFree(); AccentColour.UnbindFrom(HitObject.ColourBindable); - ExBindable.UnbindFrom(HitObject.ExBindable); + breakSample.ClearSamples(); } protected override void Update() diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiLanedHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiLanedHitObject.cs index 49700cee7..e83f4f9bc 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiLanedHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableSentakkiLanedHitObject.cs @@ -5,7 +5,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Sentakki.UI; -using osu.Game.Skinning; namespace osu.Game.Rulesets.Sentakki.Objects.Drawables { @@ -16,12 +15,6 @@ public partial class DrawableSentakkiLanedHitObject : DrawableSentakkiHitObject protected override float SamplePlaybackPosition => (SentakkiExtensions.GetPositionAlongLane(SentakkiPlayfield.INTERSECTDISTANCE, HitObject.Lane).X / (SentakkiPlayfield.INTERSECTDISTANCE * 2)) + .5f; - private PausableSkinnableSound breakSample = null!; - - private Container scorePaddingObjects = null!; - - private Container scoreBonusObjects = null!; - public DrawableSentakkiLanedHitObject(SentakkiLanedHitObject? hitObject) : base(hitObject) { @@ -30,88 +23,8 @@ public DrawableSentakkiLanedHitObject(SentakkiLanedHitObject? hitObject) [BackgroundDependencyLoader] private void load() { - AddRangeInternal(new Drawable[] - { - scorePaddingObjects = new Container(), - scoreBonusObjects = new Container(), - breakSample = new PausableSkinnableSound(), - }); - if (DrawableSentakkiRuleset is not null) AnimationDuration.BindTo(DrawableSentakkiRuleset?.AdjustedAnimDuration); } - - protected override void LoadSamples() - { - base.LoadSamples(); - - breakSample.Samples = HitObject.CreateBreakSample(); - } - - public override void PlaySamples() - { - base.PlaySamples(); - - breakSample.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); - breakSample.Play(); - } - - protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject) - { - switch (hitObject) - { - case ScorePaddingObject p: - return new DrawableScorePaddingObject(p); - - case ScoreBonusObject b: - return new DrawableScoreBonusObject(b); - } - - return base.CreateNestedHitObject(hitObject); - } - - protected override void AddNestedHitObject(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableScorePaddingObject p: - scorePaddingObjects.Add(p); - break; - - case DrawableScoreBonusObject b: - scoreBonusObjects.Add(b); - break; - - default: - base.AddNestedHitObject(hitObject); - break; - } - } - - protected override void ClearNestedHitObjects() - { - base.ClearNestedHitObjects(); - scorePaddingObjects.Clear(false); - scoreBonusObjects.Clear(false); - } - - protected override void OnFree() - { - base.OnFree(); - breakSample.ClearSamples(); - } - - protected new void ApplyResult(HitResult hitResult) - { - // Judge the scoreBonus - foreach (var bonusObject in scoreBonusObjects) - bonusObject.TriggerResult(); - - // Also give Break note score padding a judgement - for (int i = 0; i < scorePaddingObjects.Count; ++i) - scorePaddingObjects[^(i + 1)].ApplyResult(hitResult); - - base.ApplyResult(hitResult); - } } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs index ad0d6e004..324976d73 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Drawables/DrawableTouch.cs @@ -115,7 +115,7 @@ protected override void CheckForResult(bool userTriggered, double timeOffset) if (timeOffset < 0 && result is not HitResult.Perfect) return; - if (result < HitResult.Perfect && ExBindable.Value && result.IsHit()) + if (result < HitResult.Perfect && HitObject.Ex && result.IsHit()) result = Result.Judgement.MaxResult; ApplyResult(result); diff --git a/osu.Game.Rulesets.Sentakki/Objects/IBreakNote.cs b/osu.Game.Rulesets.Sentakki/Objects/IBreakNote.cs new file mode 100644 index 000000000..98e06995c --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Objects/IBreakNote.cs @@ -0,0 +1,14 @@ +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Sentakki.Objects; + +public interface IBreakNote +{ + virtual bool NeedBreakSample => true; + + virtual int BreakScoreWeighting => 5; + + BindableBool BreakBindable { get; } + + bool Break { get; set; } +} diff --git a/osu.Game.Rulesets.Sentakki/Objects/IExNote.cs b/osu.Game.Rulesets.Sentakki/Objects/IExNote.cs new file mode 100644 index 000000000..c0ffb0a14 --- /dev/null +++ b/osu.Game.Rulesets.Sentakki/Objects/IExNote.cs @@ -0,0 +1,6 @@ +namespace osu.Game.Rulesets.Sentakki.Objects; + +public interface IExNote +{ + bool Ex { get; set; } +} diff --git a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs index 1c3efbd97..14ad95dd3 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/SentakkiHitObject.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -23,6 +24,10 @@ protected SentakkiHitObject() ColourBindable.Value = DefaultNoteColour; } + public int ScoreWeighting => (this is IBreakNote breakNote && breakNote.Break) ? breakNote.BreakScoreWeighting : BaseScoreWeighting; + + protected virtual int BaseScoreWeighting => 1; + public override Judgement CreateJudgement() => new SentakkiJudgement(); [JsonIgnore] @@ -35,18 +40,58 @@ public Color4 NoteColour set => ColourBindable.Value = value; } - public Bindable ExBindable = new Bindable(); + [JsonIgnore] + public virtual Color4 DefaultNoteColour => Color4Extensions.FromHex("FF0064"); + + protected override HitWindows CreateHitWindows() => new SentakkiTapHitWindows(); - public bool Ex + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - get => ExBindable.Value; - set => ExBindable.Value = value; + base.CreateNestedHitObjects(cancellationToken); + + for (int i = 1; i < ScoreWeighting; ++i) + AddNested(new ScorePaddingObject { StartTime = this.GetEndTime() }); + + if (this is IBreakNote breakNote && breakNote.Break) + AddNested(new ScoreBonusObject + { + StartTime = this.GetEndTime(), + HitWindows = HitWindows + }); } - [JsonIgnore] - public virtual Color4 DefaultNoteColour => Color4Extensions.FromHex("FF0064"); + public override IList AuxiliarySamples => CreateBreakSample(); - protected override HitWindows CreateHitWindows() => new SentakkiTapHitWindows(); + public HitSampleInfo[] CreateBreakSample() + { + if (this is not IBreakNote breakNote || !breakNote.NeedBreakSample || !breakNote.Break) + return Array.Empty(); + + return new[] + { + new BreakSample( CreateHitSampleInfo()) + }; + } + + public class BreakSample : HitSampleInfo + { + public override IEnumerable LookupNames + { + get + { + foreach (string name in base.LookupNames) + yield return name; + + foreach (string name in base.LookupNames) + yield return name.Replace("-max", string.Empty); + } + } + + public BreakSample(HitSampleInfo sampleInfo) + : base("spinnerbonus-max", sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume) + { + } + } // This special hitsample is used for Sentakki specific samples, with doesn't have bank specific variants public class SentakkiHitSampleInfo : HitSampleInfo, IEquatable @@ -64,19 +109,17 @@ public override IEnumerable LookupNames } } -#nullable enable public override HitSampleInfo With(Optional newName = default, Optional newBank = default, Optional newSuffix = default, Optional newVolume = default) { return new SentakkiHitSampleInfo(newName.GetOr(Name), newVolume.GetOr(Volume)); } -#nullable disable - public bool Equals(SentakkiHitSampleInfo other) + public bool Equals(SentakkiHitSampleInfo? other) { return other != null && Name == other.Name; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is SentakkiHitSampleInfo s && Equals(s); } diff --git a/osu.Game.Rulesets.Sentakki/Objects/SentakkiLanedHitObject.cs b/osu.Game.Rulesets.Sentakki/Objects/SentakkiLanedHitObject.cs index 63b22e1b9..fcaba0c52 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/SentakkiLanedHitObject.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/SentakkiLanedHitObject.cs @@ -1,26 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Threading; using osu.Framework.Bindables; -using osu.Game.Audio; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Sentakki.Objects { - public abstract class SentakkiLanedHitObject : SentakkiHitObject + public abstract class SentakkiLanedHitObject : SentakkiHitObject, IBreakNote, IExNote { - protected virtual bool NeedBreakSample => true; - - public virtual int ScoreWeighting => Break ? 5 : 1; - - public readonly BindableBool BreakBindable = new BindableBool(); - - public bool Break - { - get => BreakBindable.Value; - set => BreakBindable.Value = value; - } - public readonly BindableInt LaneBindable = new BindableInt(); public int Lane @@ -29,53 +12,20 @@ public int Lane set => LaneBindable.Value = value; } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) - { - base.CreateNestedHitObjects(cancellationToken); - - for (int i = 1; i < ScoreWeighting; ++i) - AddNested(new ScorePaddingObject { StartTime = this.GetEndTime() }); - - if (Break) - AddNested(new ScoreBonusObject - { - StartTime = this.GetEndTime(), - HitWindows = HitWindows - }); - } + public virtual int BreakScoreWeighting => 5; - public override IList AuxiliarySamples => new HitSampleInfo[] { new BreakSample(CreateHitSampleInfo()) }; + public BindableBool BreakBindable { get; } = new BindableBool(); - public HitSampleInfo[] CreateBreakSample() + public bool Break { - if (!NeedBreakSample || !Break) - return Array.Empty(); - - return new[] - { - new BreakSample( CreateHitSampleInfo()) - }; + get => BreakBindable.Value; + set => BreakBindable.Value = value; } - public class BreakSample : HitSampleInfo - { - public override IEnumerable LookupNames - { - get - { - foreach (string name in base.LookupNames) - yield return name; - - foreach (string name in base.LookupNames) - yield return name.Replace("-max", string.Empty); - } - } + public bool Ex { get; set; } - public BreakSample(HitSampleInfo sampleInfo) - : base("spinnerbonus-max", sampleInfo.Bank, sampleInfo.Suffix, sampleInfo.Volume) + protected virtual bool NeedBreakSample => true; - { - } - } + bool IBreakNote.NeedBreakSample => NeedBreakSample; } } diff --git a/osu.Game.Rulesets.Sentakki/Objects/SlideBody.cs b/osu.Game.Rulesets.Sentakki/Objects/SlideBody.cs index 5ec41df42..7cffe3d62 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/SlideBody.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/SlideBody.cs @@ -19,7 +19,7 @@ public class SlideBody : SentakkiLanedHitObject, IHasDuration { public override Color4 DefaultNoteColour => Color4.Aqua; - public override int ScoreWeighting => Break ? 5 : 3; + protected override int BaseScoreWeighting => 3; public double EndTime { diff --git a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs index a0851f2e2..259ef03af 100644 --- a/osu.Game.Rulesets.Sentakki/Objects/Touch.cs +++ b/osu.Game.Rulesets.Sentakki/Objects/Touch.cs @@ -1,3 +1,4 @@ +using osu.Framework.Bindables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Sentakki.Scoring; using osuTK; @@ -5,12 +6,22 @@ namespace osu.Game.Rulesets.Sentakki.Objects { - public class Touch : SentakkiHitObject + public class Touch : SentakkiHitObject, IBreakNote, IExNote { public override Color4 DefaultNoteColour => Color4.Aqua; public Vector2 Position { get; set; } + public BindableBool BreakBindable { get; } = new BindableBool(); + + public bool Break + { + get => BreakBindable.Value; + set => BreakBindable.Value = value; + } + + public bool Ex { get; set; } + protected override HitWindows CreateHitWindows() => new SentakkiTouchHitWindows(); } } diff --git a/osu.Game.Rulesets.Sentakki/UI/TouchPlayfield.cs b/osu.Game.Rulesets.Sentakki/UI/TouchPlayfield.cs index 8349b4b60..784436843 100644 --- a/osu.Game.Rulesets.Sentakki/UI/TouchPlayfield.cs +++ b/osu.Game.Rulesets.Sentakki/UI/TouchPlayfield.cs @@ -3,7 +3,7 @@ using osu.Framework.Input; using osu.Framework.Lists; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Sentakki.Configuration; +using osu.Game.Rulesets.Sentakki.Objects; using osu.Game.Rulesets.Sentakki.Objects.Drawables; using osu.Game.Rulesets.UI; using osuTK; @@ -32,6 +32,9 @@ public TouchPlayfield() private void load() { RegisterPool(8); + + RegisterPool(20); + RegisterPool(5); } protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new SentakkiHitObjectLifetimeEntry(hitObject, drawableSentakkiRuleset);