mirror of
https://github.com/ppy/osu
synced 2025-03-31 23:58:37 +00:00
Merge pull request #7221 from LeNitrous/nightcore-beats
Implement backing beats for Nightcore mods
This commit is contained in:
commit
06287f72f3
@ -1,11 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModNightcore : ModNightcore
|
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.06;
|
public override double ScoreMultiplier => 1.06;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModNightcore : ModNightcore
|
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
{
|
{
|
||||||
public class OsuModNightcore : ModNightcore
|
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Mods
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
{
|
{
|
||||||
public class TaikoModNightcore : ModNightcore
|
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1.12;
|
public override double ScoreMultiplier => 1.12;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
// 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.Extensions.IEnumerableExtensions;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Tests.Visual.UserInterface;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneNightcoreBeatContainer : TestSceneBeatSyncedContainer
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(ModNightcore<>)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
Beatmap.Value.Track.Seek(Beatmap.Value.Beatmap.HitObjects.First().StartTime - 1000);
|
||||||
|
|
||||||
|
Add(new ModNightcore<HitObject>.NightcoreBeatContainer());
|
||||||
|
|
||||||
|
AddStep("change signature to quadruple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleQuadruple));
|
||||||
|
AddStep("change signature to triple", () => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints.ForEach(p => p.TimeSignature = TimeSignatures.SimpleTriple));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,11 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeSinceLastBeat { get; private set; }
|
public double TimeSinceLastBeat { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many beats per beatlength to trigger. Defaults to 1.
|
||||||
|
/// </summary>
|
||||||
|
public int Divisor { get; set; } = 1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -42,6 +47,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
private EffectControlPoint defaultEffect;
|
private EffectControlPoint defaultEffect;
|
||||||
private TrackAmplitudes defaultAmplitudes;
|
private TrackAmplitudes defaultAmplitudes;
|
||||||
|
|
||||||
|
protected bool IsBeatSyncedWithTrack { get; private set; }
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
Track track = null;
|
Track track = null;
|
||||||
@ -65,26 +72,34 @@ namespace osu.Game.Graphics.Containers
|
|||||||
effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
|
effectPoint = beatmap.ControlPointInfo.EffectPointAt(currentTrackTime);
|
||||||
|
|
||||||
if (timingPoint.BeatLength == 0)
|
if (timingPoint.BeatLength == 0)
|
||||||
|
{
|
||||||
|
IsBeatSyncedWithTrack = false;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsBeatSyncedWithTrack = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
IsBeatSyncedWithTrack = false;
|
||||||
currentTrackTime = Clock.CurrentTime;
|
currentTrackTime = Clock.CurrentTime;
|
||||||
timingPoint = defaultTiming;
|
timingPoint = defaultTiming;
|
||||||
effectPoint = defaultEffect;
|
effectPoint = defaultEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / timingPoint.BeatLength);
|
double beatLength = timingPoint.BeatLength / Divisor;
|
||||||
|
|
||||||
|
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (effectPoint.OmitFirstBarLine ? 1 : 0);
|
||||||
|
|
||||||
// The beats before the start of the first control point are off by 1, this should do the trick
|
// The beats before the start of the first control point are off by 1, this should do the trick
|
||||||
if (currentTrackTime < timingPoint.Time)
|
if (currentTrackTime < timingPoint.Time)
|
||||||
beatIndex--;
|
beatIndex--;
|
||||||
|
|
||||||
TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % timingPoint.BeatLength;
|
TimeUntilNextBeat = (timingPoint.Time - currentTrackTime) % beatLength;
|
||||||
if (TimeUntilNextBeat < 0)
|
if (TimeUntilNextBeat < 0)
|
||||||
TimeUntilNextBeat += timingPoint.BeatLength;
|
TimeUntilNextBeat += beatLength;
|
||||||
|
|
||||||
TimeSinceLastBeat = timingPoint.BeatLength - TimeUntilNextBeat;
|
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||||
|
|
||||||
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
|
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
|
||||||
return;
|
return;
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mods
|
namespace osu.Game.Rulesets.Mods
|
||||||
{
|
{
|
||||||
public abstract class ModNightcore : ModDoubleTime
|
public abstract class ModNightcore<TObject> : ModDoubleTime, IApplicableToDrawableRuleset<TObject>
|
||||||
|
where TObject : HitObject
|
||||||
{
|
{
|
||||||
public override string Name => "Nightcore";
|
public override string Name => "Nightcore";
|
||||||
public override string Acronym => "NC";
|
public override string Acronym => "NC";
|
||||||
@ -34,5 +44,105 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
|
||||||
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
|
track.AddAdjustment(AdjustableProperty.Tempo, tempoAdjust);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<TObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
drawableRuleset.Overlays.Add(new NightcoreBeatContainer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NightcoreBeatContainer : BeatSyncedContainer
|
||||||
|
{
|
||||||
|
private SkinnableSound hatSample;
|
||||||
|
private SkinnableSound clapSample;
|
||||||
|
private SkinnableSound kickSample;
|
||||||
|
private SkinnableSound finishSample;
|
||||||
|
|
||||||
|
private int? firstBeat;
|
||||||
|
|
||||||
|
public NightcoreBeatContainer()
|
||||||
|
{
|
||||||
|
Divisor = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")),
|
||||||
|
clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")),
|
||||||
|
kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")),
|
||||||
|
finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int bars_per_segment = 4;
|
||||||
|
|
||||||
|
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes)
|
||||||
|
{
|
||||||
|
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
||||||
|
|
||||||
|
int beatsPerBar = (int)timingPoint.TimeSignature;
|
||||||
|
int segmentLength = beatsPerBar * Divisor * bars_per_segment;
|
||||||
|
|
||||||
|
if (!IsBeatSyncedWithTrack)
|
||||||
|
{
|
||||||
|
firstBeat = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!firstBeat.HasValue || beatIndex < firstBeat)
|
||||||
|
// decide on a good starting beat index if once has not yet been decided.
|
||||||
|
firstBeat = beatIndex < 0 ? 0 : (beatIndex / segmentLength + 1) * segmentLength;
|
||||||
|
|
||||||
|
if (beatIndex >= firstBeat)
|
||||||
|
playBeatFor(beatIndex % segmentLength, timingPoint.TimeSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void playBeatFor(int beatIndex, TimeSignatures signature)
|
||||||
|
{
|
||||||
|
if (beatIndex == 0)
|
||||||
|
finishSample?.Play();
|
||||||
|
|
||||||
|
switch (signature)
|
||||||
|
{
|
||||||
|
case TimeSignatures.SimpleTriple:
|
||||||
|
switch (beatIndex % 6)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
kickSample?.Play();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
clapSample?.Play();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
hatSample?.Play();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TimeSignatures.SimpleQuadruple:
|
||||||
|
switch (beatIndex % 4)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
kickSample?.Play();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
clapSample?.Play();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
hatSample?.Play();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user