1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-27 18:23:01 +08:00
osu-lazer/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs

188 lines
8.3 KiB
C#
Raw Normal View History

// 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.
2018-04-13 17:19:50 +08:00
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Taiko.Beatmaps
{
internal class TaikoBeatmapConverter : BeatmapConverter<TaikoHitObject>
{
/// <summary>
/// osu! is generally slower than taiko, so a factor is added to increase
/// speed. This must be used everywhere slider length or beat length is used.
/// </summary>
private const float legacy_velocity_multiplier = 1.4f;
/// <summary>
/// Because swells are easier in taiko than spinners are in osu!,
/// legacy taiko multiplies a factor when converting the number of required hits.
/// </summary>
private const float swell_hit_multiplier = 1.65f;
/// <summary>
/// Base osu! slider scoring distance.
/// </summary>
private const float osu_base_scoring_distance = 100;
/// <summary>
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
/// </summary>
private const float taiko_base_distance = 100;
private readonly bool isForCurrentRuleset;
public TaikoBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
: base(beatmap, ruleset)
2018-04-13 17:19:50 +08:00
{
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
2018-04-13 17:19:50 +08:00
}
public override bool CanConvert() => true;
2018-04-19 19:44:38 +08:00
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
2018-04-13 17:19:50 +08:00
{
// Rewrite the beatmap info to add the slider velocity multiplier
original.BeatmapInfo = original.BeatmapInfo.Clone();
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
2018-04-13 17:19:50 +08:00
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
if (original.BeatmapInfo.RulesetID == 3)
{
// Post processing step to transform mania hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
{
TaikoHitObject first = x.First();
if (x.Skip(1).Any() && !(first is Swell))
2018-04-13 17:19:50 +08:00
first.IsStrong = true;
return first;
}).ToList();
}
return converted;
}
2018-04-19 19:44:38 +08:00
protected override IEnumerable<TaikoHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap)
2018-04-13 17:19:50 +08:00
{
// Old osu! used hit sounding to determine various hit type information
2019-11-08 13:04:57 +08:00
IList<HitSampleInfo> samples = obj.Samples;
2018-04-13 17:19:50 +08:00
2019-06-30 20:58:30 +08:00
bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
switch (obj)
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
case IHasDistance distanceData:
{
// Number of spans of the object - one for the initial length and for each repeat
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
double speedAdjustment = difficultyPoint.SpeedMultiplier;
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the taiko hit object
double taikoDuration = distance / taikoVelocity;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
// osu-stable always uses the speed-adjusted beatlength to determine the velocities, but
// only uses it for tick rate if beatmap version < 8
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
speedAdjustedBeatLength *= speedAdjustment;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
2019-04-01 11:16:05 +08:00
2019-11-12 18:16:51 +08:00
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
List<IList<HitSampleInfo>> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List<IList<HitSampleInfo>>(new[] { samples });
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
int i = 0;
for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing)
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
IList<HitSampleInfo> currentSamples = allSamples[i];
bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH);
yield return new Hit
2018-04-13 17:19:50 +08:00
{
StartTime = j,
Type = isRim ? HitType.Rim : HitType.Centre,
Samples = currentSamples,
IsStrong = strong
};
2019-11-12 18:16:51 +08:00
i = (i + 1) % allSamples.Count;
2018-04-13 17:19:50 +08:00
}
}
2019-11-12 18:16:51 +08:00
else
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
yield return new DrumRoll
{
StartTime = obj.StartTime,
Samples = obj.Samples,
IsStrong = strong,
Duration = taikoDuration,
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
};
}
break;
2018-04-13 17:19:50 +08:00
}
2019-11-12 18:16:51 +08:00
case IHasEndTime endTimeData:
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
2018-04-13 17:19:50 +08:00
2019-11-12 18:16:51 +08:00
yield return new Swell
2018-04-13 17:19:50 +08:00
{
StartTime = obj.StartTime,
Samples = obj.Samples,
2019-11-12 18:16:51 +08:00
Duration = endTimeData.Duration,
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
2018-04-13 17:19:50 +08:00
};
2019-11-12 18:16:51 +08:00
break;
2018-04-13 17:19:50 +08:00
}
2019-11-12 18:16:51 +08:00
default:
2018-04-13 17:19:50 +08:00
{
2019-11-12 18:16:51 +08:00
bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE);
yield return new Hit
2019-11-12 18:16:51 +08:00
{
StartTime = obj.StartTime,
Type = isRim ? HitType.Rim : HitType.Centre,
Samples = obj.Samples,
IsStrong = strong
};
2019-11-12 18:16:51 +08:00
break;
2018-04-13 17:19:50 +08:00
}
}
}
2018-05-07 09:51:30 +08:00
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
2018-04-13 17:19:50 +08:00
}
}