mirror of
https://github.com/ppy/osu.git
synced 2026-06-06 11:54:13 +08:00
28fbb83cc6
- closes https://github.com/ppy/osu/issues/36942 - fixes https://osu.ppy.sh/community/forums/topics/2187041?n=1 ## [Ensure Simplified Rhythm mod does not produce beatmaps with objects out of order](https://github.com/ppy/osu/commit/f4807b3a42dbf46ea0e669b0ba658feaef9baca5) This is a last ditch safety. This mod has more apparent issues (see below) but this is a first step of restoring sanity. Aside than the other fix described below I have not attempted to figure out further why this is happening because the conversion logic is in over my head. I just want hard breakage to not be possible. Why this extra sort is necessary can be investigated by @Hiviexd if he's so inclined. ## [Fix `GetClosestBeatDivisor()` not working correctly with negative time instants](https://github.com/ppy/osu/commit/88dc0d8a73c712a040635af262d7519fe6d772a0) This was causing the 1/3 -> 1/2 conversion in Simplified Rhythm to engage on some maps that weren't even mapped in waltz time. Those maps had the common trait of having a timing point with a negative start time. Notably this could feasibly affect other places as well, like mania beat snap colouring, or the Synesthesia osu! mod. <details> <summary>testing</summary> Tested with Simplified Rhythm engaged, with 1/6 -> 1/4 and 1/3 -> 1/2 conversion enabled [BULANOVA - NE PLACH' [oni]](https://osu.ppy.sh/beatmapsets/1740291#taiko/3664995) (1/8 snap): - No mods: 5.18* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 3.48* - Simplified Rhythm @933de7ab: 5.18* [BULANOVA - NE PLACH' [inner oni]](https://osu.ppy.sh/beatmapsets/1740291#taiko/3648625) (1/8 snap): - No mods: 5.84* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 4.13* - Simplified Rhythm @933de7ab: 5.84* [BULANOVA - NE PLACH' [don't cry]](https://osu.ppy.sh/beatmapsets/1740291#taiko/3557681) (1/8 snap): - No mods: 6.91* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 4.84* - Simplified Rhythm @933de7ab: 6.91* [Mili - Peach Pit and Cyanide [nik's Normal]](https://osu.ppy.sh/beatmapsets/2468654#osu/5405909) (1/6 snap): - No mods: 1.63* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 1.10* - Simplified Rhythm @933de7ab: 1.10* [Mili - Peach Pit and Cyanide [Ix's Hard]](https://osu.ppy.sh/beatmapsets/2468654#osu/5405906) (1/3 snap): - No mods: 2.50* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 1.71* - Simplified Rhythm @933de7ab: 1.71* [Mili - Peach Pit and Cyanide [nomi's Hidden Insane]](https://osu.ppy.sh/beatmapsets/2468654#osu/5405910) (1/3 snap): - No mods: 3.93* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 2.59* - Simplified Rhythm @933de7ab: 2.59* [Within Temptation - The Unforgiving [Stairway To The Skies]](https://osu.ppy.sh/beatmapsets/29157#osu/172617) (1/3 snap in editor): - No mods: 1.53* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 1.48* - Simplified Rhythm @933de7ab: 1.48* [Within Temptation - The Unforgiving [Iron]](https://osu.ppy.sh/beatmapsets/29157#osu/172612) (1/6 snap in editor): - No mods: 2.76* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 1.75* - Simplified Rhythm @933de7ab: 1.75* [Within Temptation - The Unforgiving [Marathon]](https://osu.ppy.sh/beatmapsets/29157#osu/156352) (1/4 snap in editor, but has 1/3 / 1/6 sections for sure): - No mods: 2.96* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 2.75* - Simplified Rhythm @933de7ab: 2.75* [Halozy - Genryuu Kaiko [Higan Torrent]](https://osu.ppy.sh/beatmapsets/180138#osu/433005) (1/4 snap): - No mods: 5.37* - Simplified Rhythm @ master: 0.00* - Simplified Rhythm @f4807b3a: 3.95* - Simplified Rhythm @933de7ab: 5.37* </details>
136 lines
5.2 KiB
C#
136 lines
5.2 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using osu.Framework.Bindables;
|
|
using osu.Framework.Graphics.Sprites;
|
|
using osu.Framework.Localisation;
|
|
using osu.Game.Beatmaps;
|
|
using osu.Game.Beatmaps.ControlPoints;
|
|
using osu.Game.Configuration;
|
|
using osu.Game.Graphics;
|
|
using osu.Game.Rulesets.Mods;
|
|
using osu.Game.Rulesets.Taiko.Beatmaps;
|
|
using osu.Game.Rulesets.Taiko.Objects;
|
|
|
|
namespace osu.Game.Rulesets.Taiko.Mods
|
|
{
|
|
public class TaikoModSimplifiedRhythm : Mod, IApplicableToBeatmap
|
|
{
|
|
public override string Name => "Simplified Rhythm";
|
|
public override string Acronym => "SR";
|
|
public override double ScoreMultiplier => 0.6;
|
|
public override LocalisableString Description => "Simplify tricky rhythms!";
|
|
public override IconUsage? Icon => OsuIcon.ModSimplifiedRhythm;
|
|
public override ModType Type => ModType.DifficultyReduction;
|
|
|
|
[SettingSource("1/3 to 1/2 conversion", "Converts 1/3 patterns to 1/2 rhythm.")]
|
|
public Bindable<bool> OneThirdConversion { get; } = new BindableBool();
|
|
|
|
[SettingSource("1/6 to 1/4 conversion", "Converts 1/6 patterns to 1/4 rhythm.")]
|
|
public Bindable<bool> OneSixthConversion { get; } = new BindableBool(true);
|
|
|
|
[SettingSource("1/8 to 1/4 conversion", "Converts 1/8 patterns to 1/4 rhythm.")]
|
|
public Bindable<bool> OneEighthConversion { get; } = new BindableBool();
|
|
|
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
|
{
|
|
var taikoBeatmap = (TaikoBeatmap)beatmap;
|
|
var controlPointInfo = taikoBeatmap.ControlPointInfo;
|
|
|
|
Hit[] hits = taikoBeatmap.HitObjects.OfType<Hit>().ToArray();
|
|
|
|
if (hits.Length == 0)
|
|
return;
|
|
|
|
var conversions = new List<(int, int)>();
|
|
|
|
if (OneEighthConversion.Value) conversions.Add((8, 4));
|
|
if (OneSixthConversion.Value) conversions.Add((6, 4));
|
|
if (OneThirdConversion.Value) conversions.Add((3, 2));
|
|
|
|
bool inPattern = false;
|
|
|
|
foreach ((int baseRhythm, int adjustedRhythm) in conversions)
|
|
{
|
|
int patternStartIndex = 0;
|
|
|
|
for (int i = 1; i < hits.Length; i++)
|
|
{
|
|
double snapValue = getSnapBetweenNotes(controlPointInfo, hits[i - 1], hits[i]);
|
|
|
|
if (inPattern)
|
|
{
|
|
// pattern continues
|
|
if (snapValue == baseRhythm)
|
|
continue;
|
|
|
|
inPattern = false;
|
|
processPattern(i);
|
|
}
|
|
else
|
|
{
|
|
if (snapValue == baseRhythm)
|
|
{
|
|
patternStartIndex = i - 1;
|
|
inPattern = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process the last pattern if we reached the end of the beatmap and are still in a pattern.
|
|
if (inPattern)
|
|
processPattern(hits.Length);
|
|
|
|
void processPattern(int patternEndIndex)
|
|
{
|
|
// Iterate through the pattern
|
|
for (int j = patternStartIndex; j < patternEndIndex; j++)
|
|
{
|
|
int indexInPattern = j - patternStartIndex;
|
|
|
|
switch (baseRhythm)
|
|
{
|
|
// 1/8: Remove every second note
|
|
case 8:
|
|
{
|
|
if (indexInPattern % 2 == 1)
|
|
{
|
|
taikoBeatmap.HitObjects.Remove(hits[j]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// 1/6 and 1/3: Remove every second note and adjust time of every third
|
|
case 6:
|
|
case 3:
|
|
{
|
|
if (indexInPattern % 3 == 1)
|
|
taikoBeatmap.HitObjects.Remove(hits[j]);
|
|
else if (indexInPattern % 3 == 2)
|
|
hits[j].StartTime = hits[j - 2].StartTime + controlPointInfo.TimingPointAt(hits[j].StartTime).BeatLength / adjustedRhythm;
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(baseRhythm));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
taikoBeatmap.HitObjects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
|
|
}
|
|
|
|
private int getSnapBetweenNotes(ControlPointInfo controlPointInfo, Hit currentNote, Hit nextNote)
|
|
{
|
|
var currentTimingPoint = controlPointInfo.TimingPointAt(currentNote.StartTime);
|
|
return controlPointInfo.GetClosestBeatDivisor(currentTimingPoint.Time + (nextNote.StartTime - currentNote.StartTime));
|
|
}
|
|
}
|
|
}
|