Skip to content

Rewrite osu!taiko's time range computation logic to match 1:1 with stable #26781

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 3 additions & 17 deletions osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Rulesets.Timing;
Expand All @@ -36,6 +35,8 @@ public partial class DrawableTaikoRuleset : DrawableScrollingRuleset<TaikoHitObj

public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;

protected new TaikoPlayfieldAdjustmentContainer PlayfieldAdjustmentContainer => (TaikoPlayfieldAdjustmentContainer)base.PlayfieldAdjustmentContainer;

protected override bool UserScrollSpeedAdjustment => false;

private SkinnableDrawable scroller;
Expand Down Expand Up @@ -68,22 +69,7 @@ protected override void Update()
TimeRange.Value = ComputeTimeRange();
}

protected virtual double ComputeTimeRange()
{
// Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
const float scroll_rate = 10;

// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
// Width is used because it defines how many notes fit on the playfield.
// We clamp the ratio to the maximum aspect ratio to keep scroll speed consistent on widths lower than the default.
float ratio = Math.Max(DrawSize.X / 768f, TaikoPlayfieldAdjustmentContainer.MAXIMUM_ASPECT);

// Stable internally increased the slider velocity of objects by a factor of `VELOCITY_MULTIPLIER`.
// To simulate this, we shrink the time range by that factor here.
// This, when combined with the rest of the scrolling ruleset machinery (see `MultiplierControlPoint` et al.),
// has the effect of increasing each multiplier control point's multiplier by `VELOCITY_MULTIPLIER`, ensuring parity with stable.
return (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate / TaikoBeatmapConverter.VELOCITY_MULTIPLIER;
}
protected virtual double ComputeTimeRange() => PlayfieldAdjustmentContainer.ComputeTimeRange();

protected override void UpdateAfterChildren()
{
Expand Down
40 changes: 36 additions & 4 deletions osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.UI;
using osuTK;

Expand All @@ -14,22 +15,25 @@ public partial class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentCont
public const float MAXIMUM_ASPECT = 16f / 9f;
public const float MINIMUM_ASPECT = 5f / 4f;

private const float stable_gamefield_height = 480f;

public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);

public TaikoPlayfieldAdjustmentContainer()
{
RelativeSizeAxes = Axes.X;
RelativePositionAxes = Axes.Y;
Height = TaikoPlayfield.BASE_HEIGHT;

// Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514
Y = 135f / stable_gamefield_height;
}

protected override void Update()
{
base.Update();

const float base_relative_height = TaikoPlayfield.BASE_HEIGHT / 768;
// Matches stable, see https://github.com/peppy/osu-stable-reference/blob/7519cafd1823f1879c0d9c991ba0e5c7fd3bfa02/osu!/GameModes/Play/Rulesets/Taiko/RulesetTaiko.cs#L514
const float base_position = 135f / 480f;

float relativeHeight = base_relative_height;

Expand All @@ -51,10 +55,38 @@ protected override void Update()
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
relativeHeight = Math.Min(relativeHeight, 1f / 3f);

Y = base_position;

Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
Width = 1 / Scale.X;
}

public double ComputeTimeRange()
{
float currentAspect = Parent!.ChildSize.X / Parent!.ChildSize.Y;

if (LockPlayfieldAspectRange.Value)
currentAspect = Math.Clamp(currentAspect, MINIMUM_ASPECT, MAXIMUM_ASPECT);

// in a game resolution of 1024x768, stable's scrolling system consists of objects being placed 600px (widthScaled - 40) away from their hit location.
Copy link
Collaborator

@bdach bdach Jan 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stable's scrolling system consists of objects being placed 600px (widthScaled - 40) away from their hit location

no reference for this (where's the 600px figure in stable?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is connected with the rest of the comment block about the "in length" value, in that it is all sourced from this line in stable code.

In a game resolution of 1024x768, the "scaled" resolution is 640x480 because of the aspect ratio of the game resolution. So "width scaled" is 640px, subtracting 40 from it leads to the final value of 600px, which is the scrolling distance of the object in a 640x480 space (since it's assigned to the inLength variable, see scroll code.

// however, the point at which the object renders at the end of the screen is exactly x=640, but stable makes the object start moving from beyond the screen instead of the boundary point.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the point at which the object renders at the end of the screen is exactly x=640, but stable makes the object start moving from beyond the screen instead of the boundary point

no reference for this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment needs to be reworded but what I was trying to convey is that stable starts scrolling the object from X = hit target + inLength, instead of X = end of screen. See scroll code and notice the X values of the transformations.

// therefore, in lazer we have to adjust the "in length" so that it's in a 640px->160px fashion before passing it down as a "time range".
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean? what is "a 640px->160px fashion"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, the inMsLength property in stable currently gives the time spent by the object moving from 760px to 160px (760px is determined by stable_hit_location + inLength, when game resolution is 1024x768).

This is bad for lazer, because 640px in stable is considered the right edge of the playfield, i.e. stable begins scrolling the object from outside of the playfield, while lazer begins scrolling the object from the right edge of the playfield instead (technically scrolls from the right edge of the HitObjectContainer).

So to compromise this, the inLength is changed to be calculated by widthScaled - stable_hit_location (distance between right edge of playfield and hit location) instead of widthScaled - 40 (stable's distance, 600px)

// see stable's "in length": https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L168
const float stable_hit_location = 160f;
float widthScaled = currentAspect * stable_gamefield_height;
float inLength = widthScaled - stable_hit_location;

// also in a game resolution of 1024x768, stable makes hit objects scroll from 760px->160px at a duration of 6000ms, divided by slider velocity (i.e. at a rate of 0.1px/ms)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does 760px come from here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a game resolution of 1024x768, the value 760px comes from 160px + 600px (where 160px is hit location, and 600px is stable's "in length").

In stable code, 760px is used specifically in this line (s.Position is set to hit location here)

// compare: https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L218
// note: the variable "sv", in the linked reference, is equivalent to MultiplierControlPoint.Multiplier * 100, but since time range is agnostic of velocity, we replace "sv" with 100 below.
float inMsLength = inLength / 100 * 1000;

// stable multiplies the slider velocity by 1.4x for certain reasons, divide the time range by that factor to achieve similar result.
// for references on how the factor is applied to the time range, see:
// 1. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManagerTaiko.cs#L79 (DifficultySliderMultiplier multiplied by 1.4x)
// 2. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManager.cs#L468-L470 (DifficultySliderMultiplier used to calculate SliderScoringPointDistance)
// 3. https://github.com/peppy/osu-stable-reference/blob/013c3010a9d495e3471a9c59518de17006f9ad89/osu!/GameplayElements/HitObjectManager.cs#L248-L250 (SliderScoringPointDistance used to calculate slider velocity, i.e. the "sv" variable from above)
inMsLength /= TaikoBeatmapConverter.VELOCITY_MULTIPLIER;

return inMsLength;
}
}
}