Skip to content
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

Add osu!taiko mod Quarterize #30731

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
136 changes: 136 additions & 0 deletions osu.Game.Rulesets.Taiko/Mods/TaikoModSimplified.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
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 TaikoModSimplified : Mod, IApplicableToBeatmap
{
public override string Name => "Simplified";
Hiviexd marked this conversation as resolved.
Show resolved Hide resolved
public override string Acronym => "SF";
public override double ScoreMultiplier => 0.6;
public override LocalisableString Description => "Simplify tricky rhythms!";
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(false);

[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(false);
Hiviexd marked this conversation as resolved.
Show resolved Hide resolved

public void ApplyToBeatmap(IBeatmap beatmap)
{
var taikoBeatmap = (TaikoBeatmap)beatmap;
var controlPointInfo = taikoBeatmap.ControlPointInfo;
List<Hit> toRemove = new List<Hit>();

// Snap conversions for rhythms
var snapConversions = new Dictionary<int, int>
{
{ 8, 4 }, // 1/8 snap to 1/4 snap
{ 6, 4 }, // 1/6 snap to 1/4 snap
{ 3, 2 }, // 1/3 snap to 1/2 snap
};

bool inPattern = false;

List<Hit> hits = taikoBeatmap.HitObjects.Where(obj => obj is Hit).Cast<Hit>().ToList();
Hiviexd marked this conversation as resolved.
Show resolved Hide resolved

foreach (var snapConversion in snapConversions)
{
int patternStartIndex = 0;

// Skip processing if the corresponding conversion is disabled
if (!shouldProcessRhythm(snapConversion.Key))
continue;

for (int i = 0; i < hits.Count; i++)
{
double snapValue = i < hits.Count - 1
? getSnapBetweenNotes(controlPointInfo, hits[i], hits[i + 1])
: 1; // No next note, default to a safe 1/1 snap

if (snapValue == snapConversion.Key)
{
if (!inPattern)
{
patternStartIndex = i;
}

inPattern = true;
}

// check if end of pattern
if (inPattern && snapValue != snapConversion.Key)
{
// End of the pattern
inPattern = false;

// Iterate through the pattern
for (int j = patternStartIndex; j <= i; j++)
{
int currentHitPosition = j - patternStartIndex;

if (snapConversion.Key == 8)
{
// 1/8: Remove the second note
if (currentHitPosition % 2 == 1)
{
toRemove.Add(hits[j]);
}
}
else
{
// 1/6 and 1/3: Adjust the second note and remove the third
if (currentHitPosition % 3 == 1)
{
hits[j].StartTime = hits[j - 1].StartTime + (controlPointInfo.TimingPointAt(hits[j].StartTime).BeatLength / Convert.ToDouble(snapConversion.Value));
}
else if (currentHitPosition % 3 == 2)
{
toRemove.Add(hits[j]);
}
}
}
}
}

// Remove queued notes
taikoBeatmap.HitObjects.RemoveAll(obj => toRemove.Contains(obj));
}
}

private int getSnapBetweenNotes(ControlPointInfo controlPointInfo, Hit currentNote, Hit nextNote)
{
double gapMs = Math.Max(currentNote.StartTime, nextNote.StartTime) - Math.Min(currentNote.StartTime, nextNote.StartTime);
Hiviexd marked this conversation as resolved.
Show resolved Hide resolved
var currentTimingPoint = controlPointInfo.TimingPointAt(currentNote.StartTime);

return controlPointInfo.GetClosestBeatDivisor(gapMs + currentTimingPoint.Time);
}

private bool shouldProcessRhythm(int snap)
{
return snap switch
{
3 => OneThirdConversion.Value,
6 => OneSixthConversion.Value,
8 => OneEighthConversion.Value,
_ => false,
};
}
}
}
1 change: 1 addition & 0 deletions osu.Game.Rulesets.Taiko/TaikoRuleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
new TaikoModEasy(),
new TaikoModNoFail(),
new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()),
new TaikoModSimplified(),
};

case ModType.DifficultyIncrease:
Expand Down
Loading