From 83cb70db17e10ee9c42ba5c20e5547efff6a9118 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 22:54:07 +0200 Subject: [PATCH 01/21] Added initial AimAssist mod --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 97 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 1 + 2 files changed, 98 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs new file mode 100644 index 0000000000..325c5d0c13 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -0,0 +1,97 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + { + public override string Name => "AimAssist"; + public override string Acronym => "AA"; + public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override ModType Type => ModType.Fun; + public override string Description => "No need to chase the circle, the circle chases you"; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + + private HashSet movingObjects = new HashSet(); + private int updateCounter = 0; + + public void Update(Playfield playfield) + { + // Avoid crowded judgment displays + playfield.DisplayJudgements.Value = false; + + // Object destination updated when cursor updates + playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + { + // ... every 500th cursor update iteration + // (lower -> potential lags ; higher -> easier to miss if cursor too fast) + if (updateCounter++ < 500) return; + updateCounter = 0; + + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => + { + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; + d.ClearTransforms(); + switch (d) + { + case DrawableHitCircle circle: + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > h.StartTime; + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + return currentTime > (h as IHasEndTime).EndTime - 50; + case DrawableSpinner spinner: + // TODO + return true; + } + return true; // never happens(?) + }); + }; + } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables) + drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; + } + + private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) + { + if (!(drawable is DrawableOsuHitObject d)) + return; + var h = d.HitObject; + using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + movingObjects.Add(d); + } + } + + /* + * TODOs + * - remove object timing glitches / artifacts + * - remove FollowPoints + * - automate sliders + * - combine with OsuModRelax (?) + * - must be some way to make this more effictient + * + */ +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d50d4f401c..9dbc144501 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Osu new OsuModSpinIn(), new MultiMod(new OsuModGrow(), new OsuModDeflate()), new MultiMod(new ModWindUp(), new ModWindDown()), + new OsuModAimAssist(), }; case ModType.System: From 3434458e0a2119e1ee3171b96186f1b2cc4a0a53 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Mon, 19 Aug 2019 23:51:47 +0200 Subject: [PATCH 02/21] Minor formatting changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 325c5d0c13..26f0d64a5d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private HashSet movingObjects = new HashSet(); - private int updateCounter = 0; + private readonly HashSet movingObjects = new HashSet(); + private int updateCounter; public void Update(Playfield playfield) { @@ -36,7 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods { // ... every 500th cursor update iteration // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) return; + if (updateCounter++ < 500) + return; + updateCounter = 0; // First move objects to new destination, then remove them from movingObjects set if they're too old @@ -45,11 +47,13 @@ namespace osu.Game.Rulesets.Osu.Mods var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; d.ClearTransforms(); + switch (d) { case DrawableHitCircle circle: - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); return currentTime > h.StartTime; + case DrawableSlider slider: // Move slider to cursor @@ -59,11 +63,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime).EndTime - 50; - case DrawableSpinner spinner: + return currentTime > (h as IHasEndTime)?.EndTime - 50; + + case DrawableSpinner _: // TODO return true; } + return true; // never happens(?) }); }; @@ -79,6 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (!(drawable is DrawableOsuHitObject d)) return; + var h = d.HitObject; using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) movingObjects.Add(d); From 28f78f67b237a952bb403fb802640b7c4d8c47b3 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 16:33:56 +0200 Subject: [PATCH 03/21] No longer subscribing to OnUpdate --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 70 ++++++++----------- 1 file changed, 28 insertions(+), 42 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 26f0d64a5d..8d0c1fdcfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield { - public override string Name => "AimAssist"; + public override string Name => "Aim Assist"; public override string Acronym => "AA"; public override IconUsage Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; @@ -24,55 +24,45 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; private readonly HashSet movingObjects = new HashSet(); - private int updateCounter; public void Update(Playfield playfield) { + var drawableCursor = playfield.Cursor.ActiveCursor; + // Avoid crowded judgment displays playfield.DisplayJudgements.Value = false; - // Object destination updated when cursor updates - playfield.Cursor.ActiveCursor.OnUpdate += drawableCursor => + // First move objects to new destination, then remove them from movingObjects set if they're too old + movingObjects.RemoveWhere(d => { - // ... every 500th cursor update iteration - // (lower -> potential lags ; higher -> easier to miss if cursor too fast) - if (updateCounter++ < 500) - return; + var currentTime = playfield.Clock.CurrentTime; + var h = d.HitObject; - updateCounter = 0; - - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + switch (d) { - var currentTime = playfield.Clock.CurrentTime; - var h = d.HitObject; - d.ClearTransforms(); + case DrawableHitCircle circle: + circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); + return currentTime > h.StartTime; - switch (d) - { - case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > h.StartTime; + case DrawableSlider slider: - case DrawableSlider slider: + // Move slider to cursor + if (currentTime < h.StartTime) + d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - // Move slider to cursor - if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime)); + // Move slider so that sliderball stays on the cursor + else + d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); + return currentTime > (h as IHasEndTime)?.EndTime; - // Move slider so that sliderball stays on the cursor - else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition, Math.Max(0, h.StartTime - currentTime)); - return currentTime > (h as IHasEndTime)?.EndTime - 50; + case DrawableSpinner _: + // TODO + return true; - case DrawableSpinner _: - // TODO - return true; - } - - return true; // never happens(?) - }); - }; + default: + return true; + } + }); } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -83,11 +73,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (!(drawable is DrawableOsuHitObject d)) - return; - - var h = d.HitObject; - using (d.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) + if (drawable is DrawableOsuHitObject d) movingObjects.Add(d); } } @@ -96,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Mods * TODOs * - remove object timing glitches / artifacts * - remove FollowPoints - * - automate sliders + * - automate spinners * - combine with OsuModRelax (?) * - must be some way to make this more effictient * From 30f923edde9987bfcfbe866f9bf5258dfdb5b8bf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 17:23:50 +0200 Subject: [PATCH 04/21] Hiding follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 6 +++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 8d0c1fdcfe..54c80525bf 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Osu.UI; namespace osu.Game.Rulesets.Osu.Mods { @@ -29,8 +30,9 @@ namespace osu.Game.Rulesets.Osu.Mods { var drawableCursor = playfield.Cursor.ActiveCursor; - // Avoid crowded judgment displays + // Avoid crowded judgment displays and hide follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // First move objects to new destination, then remove them from movingObjects set if they're too old movingObjects.RemoveWhere(d => @@ -81,10 +83,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - remove object timing glitches / artifacts - * - remove FollowPoints * - automate spinners * - combine with OsuModRelax (?) - * - must be some way to make this more effictient * */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 9037faf606..64620d3629 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + public readonly ConnectionRenderer ConnectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + ConnectionLayer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.UI public override void PostProcess() { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + ConnectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From d4d348390a6ce2dfac7638d61b8800e5439dceaf Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 20 Aug 2019 23:18:57 +0200 Subject: [PATCH 05/21] Change set to list + minor changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 54c80525bf..1101b93ed1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -24,38 +24,46 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; - private readonly HashSet movingObjects = new HashSet(); + private readonly List movingObjects = new List(); public void Update(Playfield playfield) { - var drawableCursor = playfield.Cursor.ActiveCursor; + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - // Avoid crowded judgment displays and hide follow points + // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects set if they're too old - movingObjects.RemoveWhere(d => + // First move objects to new destination, then remove them from movingObjects list if they're too old + movingObjects.RemoveAll(d => { - var currentTime = playfield.Clock.CurrentTime; var h = d.HitObject; + var currentTime = playfield.Clock.CurrentTime; + var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + d.ClearTransforms(); switch (d) { case DrawableHitCircle circle: - circle.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - return currentTime > h.StartTime; + + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + return currentTime > endTime; case DrawableSlider slider: // Move slider to cursor if (currentTime < h.StartTime) - d.MoveTo(drawableCursor.DrawPosition, Math.Max(0, h.StartTime - currentTime - 10)); - + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } // Move slider so that sliderball stays on the cursor else - d.MoveTo(drawableCursor.DrawPosition - slider.Ball.DrawPosition); - return currentTime > (h as IHasEndTime)?.EndTime; + { + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + return currentTime > endTime; + } case DrawableSpinner _: // TODO @@ -75,14 +83,15 @@ namespace osu.Game.Rulesets.Osu.Mods private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) { - if (drawable is DrawableOsuHitObject d) - movingObjects.Add(d); + if (drawable is DrawableOsuHitObject hitobject) + movingObjects.Add(hitobject); } } /* * TODOs - * - remove object timing glitches / artifacts + * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) + * - relocate / hide slider headcircle's explosion, flash, ... * - automate spinners * - combine with OsuModRelax (?) * From 4cd5eb783a017f53682291aaa61eb32e91b72bd9 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 21 Aug 2019 21:37:56 +0200 Subject: [PATCH 06/21] Add spinner automation --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 64 +++++++++++++++---- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1101b93ed1..f70ad2ac7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -22,25 +23,30 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModAutoplay), typeof(OsuModWiggle), typeof(OsuModTransform) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private readonly List movingObjects = new List(); + private DrawableSpinner activeSpinner; + private double spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + var currentTime = playfield.Clock.CurrentTime; // Avoid relocating judgment displays and hide follow points playfield.DisplayJudgements.Value = false; (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - // First move objects to new destination, then remove them from movingObjects list if they're too old + // If object too old, remove from movingObjects list, otherwise move to new destination movingObjects.RemoveAll(d => { var h = d.HitObject; - var currentTime = playfield.Clock.CurrentTime; var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; - d.ClearTransforms(); + + // Object no longer required to be moved -> remove from list + if (currentTime > endTime) + return true; switch (d) { @@ -48,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.Mods // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return currentTime > endTime; + return false; case DrawableSlider slider: @@ -56,23 +62,56 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } // Move slider so that sliderball stays on the cursor else { + slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - return currentTime > endTime; } - case DrawableSpinner _: - // TODO - return true; + return false; + + case DrawableSpinner spinner: + + // Move spinner to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + return false; + } + else + { + spinnerAngle = 0; + activeSpinner = spinner; + return true; + } default: return true; } }); + + if (activeSpinner != null) + { + if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + { + activeSpinner = null; + spinnerAngle = 0; + } + else + { + const float additional_degrees = 4; + const int dist_from_cursor = 30; + spinnerAngle += additional_degrees * Math.PI / 180; + + // Visual progress + activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + + // Logical progress + activeSpinner.Disc.RotationAbsolute += additional_degrees; + } + } } public void ApplyToDrawableHitObjects(IEnumerable drawables) @@ -91,9 +130,8 @@ namespace osu.Game.Rulesets.Osu.Mods /* * TODOs * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - relocate / hide slider headcircle's explosion, flash, ... - * - automate spinners - * - combine with OsuModRelax (?) + * - find nicer way to handle slider headcircle explosion, flash, ... + * - add Aim Assist as incompatible mod for Autoplay (?) * */ } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index ca72f18e9c..0757547d1c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) }; public bool AllowFail => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 9b079895fa..7799cdac32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 17fcd03dd5..a14fd88ff5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From 612f69782b14e6d0397c9fd248b9f77ffd48e01e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 14:29:44 +0100 Subject: [PATCH 07/21] use Playfield.HitObjectContainer.AliveObjects --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 93 ++++++++++--------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f70ad2ac7c..1849b48073 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -7,54 +7,55 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; +using System.Linq; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IApplicableToDrawableHitObjects, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield { public override string Name => "Aim Assist"; public override string Acronym => "AA"; - public override IconUsage Icon => FontAwesome.Solid.MousePointer; + public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circle, the circle chases you"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private readonly List movingObjects = new List(); + public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS + private DrawableSpinner activeSpinner; - private double spinnerAngle; // in radians + private float spinnerAngle; // in radians public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - var currentTime = playfield.Clock.CurrentTime; + double currentTime = playfield.Clock.CurrentTime; - // Avoid relocating judgment displays and hide follow points + // Judgment displays would all be cramped onto the cursor playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.ConnectionLayer.Hide(); + + // FIXME: Hide follow points + //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); // If object too old, remove from movingObjects list, otherwise move to new destination - movingObjects.RemoveAll(d => + foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { - var h = d.HitObject; - var endTime = (h as IHasEndTime)?.EndTime ?? h.StartTime; + var h = drawable.HitObject; + double endTime = h.GetEndTime(); - // Object no longer required to be moved -> remove from list - if (currentTime > endTime) - return true; - - switch (d) + switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); - return false; + + break; case DrawableSlider slider: @@ -66,11 +67,12 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - slider.HeadCircle.Hide(); // temporary solution to supress HeadCircle's explosion, flash, ... at wrong location + // FIXME: Hide flashes + //slider.HeadCircle.Hide(); slider.MoveTo(cursorPos - slider.Ball.DrawPosition); } - return false; + break; case DrawableSpinner spinner: @@ -78,23 +80,32 @@ namespace osu.Game.Rulesets.Osu.Mods if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - return false; } else { - spinnerAngle = 0; - activeSpinner = spinner; - return true; + // TODO: + // - get current angle to cursor + // - move clockwise(?) + // - call spinner.RotationTracker.AddRotation + + // TODO: Remove + //spinnerAngle = 0; + //activeSpinner = spinner; } - default: - return true; - } - }); + break; + default: + continue; + } + } + + // Move active spinner around the cursor if (activeSpinner != null) { - if (currentTime > (activeSpinner.HitObject as IHasEndTime)?.EndTime) + double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); + + if (currentTime > spinnerEndTime) { activeSpinner = null; spinnerAngle = 0; @@ -102,29 +113,23 @@ namespace osu.Game.Rulesets.Osu.Mods else { const float additional_degrees = 4; - const int dist_from_cursor = 30; - spinnerAngle += additional_degrees * Math.PI / 180; + float added_degrees = additional_degrees * (float)Math.PI / 180; + spinnerAngle += added_degrees; + + //int spinsRequired = activeSpinner.HitObject.SpinsRequired; + //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; + //double timeLeft = spinnerEndTime - currentTime; // Visual progress - activeSpinner.MoveTo(new Vector2((float)(dist_from_cursor * Math.Cos(spinnerAngle) + cursorPos.X), (float)(dist_from_cursor * Math.Sin(spinnerAngle) + cursorPos.Y))); + activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); // Logical progress - activeSpinner.Disc.RotationAbsolute += additional_degrees; + activeSpinner.RotationTracker.AddRotation(added_degrees); + Console.WriteLine($"added_degrees={added_degrees}"); + //activeSpinner.Disc.RotationAbsolute += additional_degrees; } } } - - public void ApplyToDrawableHitObjects(IEnumerable drawables) - { - foreach (var drawable in drawables) - drawable.ApplyCustomUpdateState += drawableOnApplyCustomUpdateState; - } - - private void drawableOnApplyCustomUpdateState(DrawableHitObject drawable, ArmedState state) - { - if (drawable is DrawableOsuHitObject hitobject) - movingObjects.Add(hitobject); - } } /* From 27a8bfa4968672c44c64e7beb1e842894755ecf5 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Tue, 4 Jan 2022 22:17:50 +0100 Subject: [PATCH 08/21] handle spinners and follow points --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++-------------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 9 ++- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 1849b48073..306e7a8b24 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,11 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -26,23 +24,19 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public const float SPIN_RADIUS = 50; // same as OsuAutoGeneratorBase.SPIN_RADIUS - - private DrawableSpinner activeSpinner; - private float spinnerAngle; // in radians + private const float spin_radius = 30; + private Vector2? prevCursorPos; public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Judgment displays would all be cramped onto the cursor + // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; + (playfield as OsuPlayfield)?.FollowPoints.Clear(); - // FIXME: Hide follow points - //(playfield as OsuPlayfield)?.ConnectionLayer.Hide(); - - // If object too old, remove from movingObjects list, otherwise move to new destination + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; @@ -79,64 +73,32 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); } else { - // TODO: - // - get current angle to cursor - // - move clockwise(?) - // - call spinner.RotationTracker.AddRotation + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - // TODO: Remove - //spinnerAngle = 0; - //activeSpinner = spinner; + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Logically finish spinner immediatly, no need for the user to click. + // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. + spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; } break; - - default: - continue; } } - // Move active spinner around the cursor - if (activeSpinner != null) - { - double spinnerEndTime = activeSpinner.HitObject.GetEndTime(); - - if (currentTime > spinnerEndTime) - { - activeSpinner = null; - spinnerAngle = 0; - } - else - { - const float additional_degrees = 4; - float added_degrees = additional_degrees * (float)Math.PI / 180; - spinnerAngle += added_degrees; - - //int spinsRequired = activeSpinner.HitObject.SpinsRequired; - //float spunDegrees = activeSpinner.Result.RateAdjustedRotation; - //double timeLeft = spinnerEndTime - currentTime; - - // Visual progress - activeSpinner.MoveTo(new Vector2((float)(SPIN_RADIUS * Math.Cos(spinnerAngle) + cursorPos.X), (float)(SPIN_RADIUS * Math.Sin(spinnerAngle) + cursorPos.Y))); - - // Logical progress - activeSpinner.RotationTracker.AddRotation(added_degrees); - Console.WriteLine($"added_degrees={added_degrees}"); - //activeSpinner.Disc.RotationAbsolute += additional_degrees; - } - } + prevCursorPos = cursorPos; } } - - /* - * TODOs - * - fix sliders reappearing at original position after their EndTime (see https://puu.sh/E7zT4/111cf9cdc8.gif) - * - find nicer way to handle slider headcircle explosion, flash, ... - * - add Aim Assist as incompatible mod for Autoplay (?) - * - */ } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2233a547b9..bc1e80cd12 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI private readonly ProxyContainer approachCircles; private readonly ProxyContainer spinnerProxies; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer followPoints; + + public FollowPointRenderer FollowPoints { get; } public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI { playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both }, spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both }, - followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, + FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }, judgementLayer = new JudgementContainer { RelativeSizeAxes = Axes.Both }, HitObjectContainer, judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both }, @@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override void OnHitObjectAdded(HitObject hitObject) { base.OnHitObjectAdded(hitObject); - followPoints.AddFollowPoints((OsuHitObject)hitObject); + FollowPoints.AddFollowPoints((OsuHitObject)hitObject); } protected override void OnHitObjectRemoved(HitObject hitObject) { base.OnHitObjectRemoved(hitObject); - followPoints.RemoveFollowPoints((OsuHitObject)hitObject); + FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject); } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From 5a62760fe4f301d7f6a736f35feee14c847f5c96 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Wed, 5 Jan 2022 13:05:22 +0100 Subject: [PATCH 09/21] hold spinners & minor adjustments --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 306e7a8b24..cde86c8868 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -5,16 +5,16 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModAimAssist : Mod, IUpdatableByPlayfield + internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Aim Assist"; public override string Acronym => "AA"; @@ -25,7 +25,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private const float spin_radius = 30; + private Vector2? prevCursorPos; + private OsuInputManager inputManager; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + } public void Update(Playfield playfield) { @@ -34,20 +42,20 @@ namespace osu.Game.Rulesets.Osu.Mods // Hide judgment displays and follow points playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Clear(); + (playfield as OsuPlayfield)?.FollowPoints.Hide(); // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - double endTime = h.GetEndTime(); switch (drawable) { case DrawableHitCircle circle: // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, endTime - currentTime - 10)); + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early break; @@ -61,16 +69,16 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider so that sliderball stays on the cursor else { - // FIXME: Hide flashes - //slider.HeadCircle.Hide(); + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done } break; case DrawableSpinner spinner: - // Move spinner to cursor + // Move spinner _next_ to cursor if (currentTime < h.StartTime) { spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); @@ -89,9 +97,12 @@ namespace osu.Game.Rulesets.Osu.Mods spinner.MoveTo(targetPos); - // Logically finish spinner immediatly, no need for the user to click. - // Temporary workaround until spinner rotations are easier to handle, similar as Autopilot mod. - spinner.Result.RateAdjustedRotation = spinner.HitObject.SpinsRequired * 360; + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } } break; From 04d060aba3f95cb75899d642a964c168679524a4 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 10:38:30 +0100 Subject: [PATCH 10/21] update general playfield only once --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cde86c8868..f983ece71b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -33,6 +33,10 @@ namespace osu.Game.Rulesets.Osu.Mods { // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + + // Hide judgment displays and follow points + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } public void Update(Playfield playfield) @@ -40,10 +44,6 @@ namespace osu.Game.Rulesets.Osu.Mods var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Hide judgment displays and follow points - playfield.DisplayJudgements.Value = false; - (playfield as OsuPlayfield)?.FollowPoints.Hide(); - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { From b9d2a10530695421bf5eee2ddad792718624493d Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 14:47:58 +0100 Subject: [PATCH 11/21] adjustable assist strength + dont update spinner & running slider --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 82 ++++--------------- 1 file changed, 16 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index f983ece71b..c7cabd7ab4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,6 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Rulesets.Osu.Mods { @@ -24,16 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - private const float spin_radius = 30; - - private Vector2? prevCursorPos; - private OsuInputManager inputManager; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + { + Precision = 0.05f, + MinValue = 0.0f, + MaxValue = 1.0f, + }; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -41,75 +43,23 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - switch (drawable) + if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) { - case DrawableHitCircle circle: + double timeMoving = currentTime - (h.StartTime - h.TimePreempt); + float percentDoneMoving = (float)(timeMoving / h.TimePreempt); + float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early - - break; - - case DrawableSlider slider: - - // Move slider to cursor - if (currentTime < h.StartTime) - { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - } - // Move slider so that sliderball stays on the cursor - else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); - } - else - { - // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); - } - } - - break; + Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); + drawable.MoveTo(targetPos, h.StartTime - currentTime); } } - - prevCursorPos = cursorPos; } } } From 197ada1a8cf43c7fcb9db2fffa072e310be22b9e Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:04:38 +0100 Subject: [PATCH 12/21] naive 10hz update --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index c7cabd7ab4..89a385dbdc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; + private DateTime? lastUpdate; + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // Hide judgment displays and follow points @@ -43,6 +45,9 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + return; + Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; @@ -60,6 +65,8 @@ namespace osu.Game.Rulesets.Osu.Mods drawable.MoveTo(targetPos, h.StartTime - currentTime); } } + + lastUpdate = DateTime.Now; } } } From b3230868cc88ca7ada83ea6c4c8f0542c333c569 Mon Sep 17 00:00:00 2001 From: MaxOhn Date: Thu, 6 Jan 2022 16:31:30 +0100 Subject: [PATCH 13/21] use playfield clock --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index 89a385dbdc..a383b533fd 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private DateTime? lastUpdate; + private double? lastUpdate; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,11 +45,12 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - if (DateTime.Now - (lastUpdate ?? DateTime.MinValue) < TimeSpan.FromMilliseconds(100)) + double currentTime = playfield.Clock.CurrentTime; + + if (currentTime - (lastUpdate ?? double.MinValue) < 100) return; Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - lastUpdate = DateTime.Now; + lastUpdate = currentTime; } } } From e7d72f1823a5a97db761b0712851128942668366 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:10:56 +0900 Subject: [PATCH 14/21] Revert recent changes --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 80 +++++++++++++++---- 1 file changed, 66 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a383b533fd..cfe3312415 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -11,8 +11,8 @@ using osu.Game.Rulesets.Osu.UI; using osuTK; using System.Linq; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Configuration; using osu.Framework.Bindables; +using osu.Game.Configuration; namespace osu.Game.Rulesets.Osu.Mods { @@ -34,10 +34,16 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private double? lastUpdate; + private const float spin_radius = 30; + + private Vector2? prevCursorPos; + private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { + // Grab the input manager for future use + inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -45,29 +51,75 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; double currentTime = playfield.Clock.CurrentTime; - if (currentTime - (lastUpdate ?? double.MinValue) < 100) - return; - - Vector2 cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - + // Move all currently alive object to new destination foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) { var h = drawable.HitObject; - if (currentTime < h.StartTime && (drawable is DrawableHitCircle || drawable is DrawableSlider)) + switch (drawable) { - double timeMoving = currentTime - (h.StartTime - h.TimePreempt); - float percentDoneMoving = (float)(timeMoving / h.TimePreempt); - float percentDistLeft = Math.Clamp(AssistStrength.Value - percentDoneMoving + 0.1f, 0, 1); + case DrawableHitCircle circle: - Vector2 targetPos = drawable.Position + percentDistLeft * (cursorPos - drawable.Position); - drawable.MoveTo(targetPos, h.StartTime - currentTime); + // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast + circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + // FIXME: some circles cause flash at original(?) position when clicked too early + + break; + + case DrawableSlider slider: + + // Move slider to cursor + if (currentTime < h.StartTime) + { + slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + } + // Move slider so that sliderball stays on the cursor + else + { + slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider + slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + // FIXME: some sliders re-appearing at their original position for a single frame when they're done + } + + break; + + case DrawableSpinner spinner: + + // Move spinner _next_ to cursor + if (currentTime < h.StartTime) + { + spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + } + else + { + // Move spinner visually + Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); + const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value + + // Rotation matrix + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + spinner.MoveTo(targetPos); + + // Move spinner logically + if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) + { + // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... + spinner.RotationTracker.AddRotation(2 * MathF.PI); + } + } + + break; } } - lastUpdate = currentTime; + prevCursorPos = cursorPos; } } } From 104256a054150963b6ffd5a4b68c14fedcfff834 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:06:04 +0900 Subject: [PATCH 15/21] Add test coverage --- .../Mods/TestSceneOsuModAimAssist.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs new file mode 100644 index 0000000000..8fa7e0cd09 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModAimAssist : OsuModTestScene + { + [Test] + public void TestAimAssist() + { + var mod = new OsuModAimAssist(); + + CreateModTest(new ModTestData + { + Autoplay = false, + Mod = mod, + }); + } + } +} From 334ed2c9c488ec63e786d9197f756aa5bdaeb3b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 15:36:09 +0900 Subject: [PATCH 16/21] Fix sliders moving before they are actually hit --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index cfe3312415..a3c5638ea7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods case DrawableSlider slider: // Move slider to cursor - if (currentTime < h.StartTime) + if (!slider.HeadCircle.Result.HasResult) { slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); } From f07502ac5fadab49871be06c51bb29d95a7cb32a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:06 +0900 Subject: [PATCH 17/21] Use simple damp easing rather than transforms --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 69 ++++++++++--------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index a3c5638ea7..db83ee09af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,17 +2,18 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Osu.UI; -using osuTK; using System.Linq; -using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Rulesets.Osu.Mods { @@ -26,17 +27,18 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + private IFrameStableClock gameplayClock; + [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] - public BindableFloat AssistStrength { get; } = new BindableFloat(0.3f) + public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.0f, + MinValue = 0.1f, MaxValue = 1.0f, }; - private const float spin_radius = 30; + private const float spin_radius = 50; - private Vector2? prevCursorPos; private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -44,6 +46,8 @@ namespace osu.Game.Rulesets.Osu.Mods // Grab the input manager for future use inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; + gameplayClock = drawableRuleset.FrameStableClock; + // Hide judgment displays and follow points drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); @@ -62,10 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - - // 10ms earlier on the note to reduce chance of missing when clicking early / cursor moves fast - circle.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); - // FIXME: some circles cause flash at original(?) position when clicked too early + easeTo(circle, cursorPos); break; @@ -74,13 +75,13 @@ namespace osu.Game.Rulesets.Osu.Mods // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) { - slider.MoveTo(cursorPos, Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(slider, cursorPos); } // Move slider so that sliderball stays on the cursor else { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider - slider.MoveTo(cursorPos - slider.Ball.DrawPosition); + easeTo(slider, cursorPos - slider.Ball.DrawPosition); // FIXME: some sliders re-appearing at their original position for a single frame when they're done } @@ -91,35 +92,39 @@ namespace osu.Game.Rulesets.Osu.Mods // Move spinner _next_ to cursor if (currentTime < h.StartTime) { - spinner.MoveTo(cursorPos + new Vector2(0, -spin_radius), Math.Max(0, h.StartTime - currentTime - 10)); + easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); } else { // Move spinner visually - Vector2 delta = spin_radius * (spinner.Position - prevCursorPos ?? cursorPos).Normalized(); - const float angle = 3 * MathF.PI / 180; // radians per update, arbitrary value - - // Rotation matrix - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - spinner.MoveTo(targetPos); + Vector2 delta = new Vector2(spin_radius); + float angle = (float)gameplayClock.CurrentTime * 10; // Move spinner logically if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) { - // Arbitrary value, might lead to some inconsistencies depending on clock rate, replay, ... - spinner.RotationTracker.AddRotation(2 * MathF.PI); + var targetPos = new Vector2( + delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, + delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y + ); + + easeTo(spinner, targetPos); } } break; } } + } - prevCursorPos = cursorPos; + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); } } } From 987aa5a21c043360d71f0155b52d70e77ddd3a5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:14 +0900 Subject: [PATCH 18/21] Add testing of different strengths --- .../Mods/TestSceneOsuModAimAssist.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs index 8fa7e0cd09..b8310bc4e7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs @@ -8,15 +8,19 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModAimAssist : OsuModTestScene { - [Test] - public void TestAimAssist() + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestAimAssist(float strength) { - var mod = new OsuModAimAssist(); - CreateModTest(new ModTestData { + Mod = new OsuModAimAssist + { + AssistStrength = { Value = strength }, + }, + PassCondition = () => true, Autoplay = false, - Mod = mod, }); } } From 2e46404fe5e10f5279730ad61f90be5a805830b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:15:49 +0900 Subject: [PATCH 19/21] Remove spinner support for now --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index db83ee09af..e3a68bcbcc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - private const float spin_radius = 50; - private OsuInputManager inputManager; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -82,34 +80,6 @@ namespace osu.Game.Rulesets.Osu.Mods { slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - // FIXME: some sliders re-appearing at their original position for a single frame when they're done - } - - break; - - case DrawableSpinner spinner: - - // Move spinner _next_ to cursor - if (currentTime < h.StartTime) - { - easeTo(spinner, cursorPos + new Vector2(0, -spin_radius)); - } - else - { - // Move spinner visually - Vector2 delta = new Vector2(spin_radius); - float angle = (float)gameplayClock.CurrentTime * 10; - - // Move spinner logically - if (inputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false) - { - var targetPos = new Vector2( - delta.X * MathF.Cos(angle) - delta.Y * MathF.Sin(angle) + cursorPos.X, - delta.X * MathF.Sin(angle) + delta.Y * MathF.Cos(angle) + cursorPos.Y - ); - - easeTo(spinner, targetPos); - } } break; From 6e41a6e704512ea2a355ae23d5793435ce464ef7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 2 Feb 2022 16:26:10 +0900 Subject: [PATCH 20/21] Tidy up code into a presentable state --- osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs | 31 +++++-------------- 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs index e3a68bcbcc..ed4b139e00 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -23,30 +22,26 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => "AA"; public override IconUsage? Icon => FontAwesome.Solid.MousePointer; public override ModType Type => ModType.Fun; - public override string Description => "No need to chase the circle, the circle chases you"; + public override string Description => "No need to chase the circle – the circle chases you!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; private IFrameStableClock gameplayClock; - [SettingSource("Assist strength", "Change the distance notes should travel towards you.", 0)] + [SettingSource("Assist strength", "How much this mod will assist you.", 0)] public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, - MinValue = 0.1f, + MinValue = 0.05f, MaxValue = 1.0f, }; - private OsuInputManager inputManager; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - // Grab the input manager for future use - inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; - gameplayClock = drawableRuleset.FrameStableClock; - // Hide judgment displays and follow points + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. drawableRuleset.Playfield.DisplayJudgements.Value = false; (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } @@ -54,33 +49,21 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - double currentTime = playfield.Clock.CurrentTime; - // Move all currently alive object to new destination - foreach (var drawable in playfield.HitObjectContainer.AliveObjects.OfType()) + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - var h = drawable.HitObject; - switch (drawable) { case DrawableHitCircle circle: easeTo(circle, cursorPos); - break; case DrawableSlider slider: - // Move slider to cursor if (!slider.HeadCircle.Result.HasResult) - { easeTo(slider, cursorPos); - } - // Move slider so that sliderball stays on the cursor else - { - slider.HeadCircle.Hide(); // hide flash, triangles, ... so they don't move with slider easeTo(slider, cursorPos - slider.Ball.DrawPosition); - } break; } @@ -89,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(500, 50, AssistStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 74637444070838cc55bd0d7e04f9580f13552737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 2 Feb 2022 19:17:33 +0100 Subject: [PATCH 21/21] Fix osu! autoplay-like mods not declaring incompatibility with `AimAssist` --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 106edfb623..2668013321 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index f478790134..ff31cfcd18 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray(); public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score {