public void TestLegacyLastTickOffset() { var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); }
public void TestNonEvenTicks() { var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[1].Time, Is.EqualTo(300)); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[2].Time, Is.EqualTo(600)); Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[3].Time, Is.EqualTo(900)); Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat)); Assert.That(events[4].Time, Is.EqualTo(span_duration)); Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[5].Time, Is.EqualTo(1100)); Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[6].Time, Is.EqualTo(1400)); Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[7].Time, Is.EqualTo(1700)); Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail)); Assert.That(events[9].Time, Is.EqualTo(2 * span_duration)); }
protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { var sliderEvents = SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken); foreach (var e in sliderEvents) { switch (e.Type) { case SliderEventType.Tick: AddNested(new SliderTick { SpanIndex = e.SpanIndex, SpanStartTime = e.SpanStartTime, StartTime = e.Time, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, }); break; case SliderEventType.Head: AddNested(HeadCircle = new SliderHeadCircle { StartTime = e.Time, Position = Position, StackHeight = StackHeight, }); break; case SliderEventType.LegacyLastTick: AddNested(TailCircle = new StrictTrackingSliderTailCircle(this) { RepeatIndex = e.SpanIndex, StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight }); break; case SliderEventType.Repeat: AddNested(new SliderRepeat(this) { RepeatIndex = e.SpanIndex, StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, Scale = Scale, }); break; } } UpdateNestedSamples(); }
public static List <SentakkiHitObject> CreateTapFromTicks(HitObject original, int path, IBeatmap beatmap, Random rng) { var curve = original as IHasCurve; double spanDuration = curve.Duration / (curve.RepeatCount + 1); bool isRepeatSpam = spanDuration < 75 && curve.RepeatCount > 0; List <SentakkiHitObject> hitObjects = new List <SentakkiHitObject>(); if (isRepeatSpam) { return(hitObjects); } var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(original.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(original.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(original.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset)) { int newPath = path; while (newPath == path) { newPath = rng.Next(0, 8); } switch (e.Type) { case SliderEventType.Tick: case SliderEventType.Repeat: hitObjects.Add(new Tap { NoteColor = Color4.Orange, Angle = newPath.GetAngleFromPath(), Samples = getTickSamples(original.Samples), StartTime = e.Time, EndPosition = SentakkiExtensions.GetPosition(SentakkiPlayfield.INTERSECTDISTANCE, newPath), Position = SentakkiExtensions.GetPosition(SentakkiPlayfield.NOTESTARTDISTANCE, newPath), }); break; } } return(hitObjects); }
public void TestSingleSpan() { var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[1].Time, Is.EqualTo(span_duration / 2)); Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail)); Assert.That(events[3].Time, Is.EqualTo(span_duration)); }
private IEnumerable <SentakkiHitObject> createTapsFromTicks(HitObject original) { int noteLane = getNewLane(true); var curve = original as IHasPathWithRepeats; double spanDuration = curve.Duration / (curve.RepeatCount + 1); bool isRepeatSpam = spanDuration < 75 && curve.RepeatCount > 0; if (isRepeatSpam) { yield break; } var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(original.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(original.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (original as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(original.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset, CancellationToken.None)) { switch (e.Type) { case SliderEventType.Tick: case SliderEventType.Repeat: yield return(new Tap { Lane = noteLane, Samples = original.Samples.Select(s => new HitSampleInfo(@"slidertick", s.Bank, s.Suffix, s.Volume)).ToList(), StartTime = e.Time }); break; } } }
public void TestRepeat() { var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[1].Time, Is.EqualTo(span_duration / 2)); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat)); Assert.That(events[2].Time, Is.EqualTo(span_duration)); Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick)); Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2)); Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail)); Assert.That(events[5].Time, Is.EqualTo(2 * span_duration)); }
public static IEnumerable <BosuHitObject> ConvertBuzzSlider(HitObject obj, Vector2 originalPosition, IBeatmap beatmap, IHasPathWithRepeats curve, double spanDuration) { List <BosuHitObject> converted = new List <BosuHitObject>(); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset, new CancellationToken())) { var sliderEventPosition = toPlayfieldSpace(originalPosition) * new Vector2(1, 0.4f); switch (e.Type) { case SliderEventType.Head: converted.AddRange(generateExplosion(e.Time, bullets_per_slider_reverse, sliderEventPosition)); break; case SliderEventType.Repeat: converted.AddRange(generateExplosion(e.Time, bullets_per_slider_reverse, sliderEventPosition, slider_angle_per_span * (e.SpanIndex + 1))); break; case SliderEventType.Tail: converted.AddRange(generateExplosion(e.Time, bullets_per_slider_reverse, sliderEventPosition, slider_angle_per_span * (curve.RepeatCount + 1))); break; } } return(converted); }
public void TestMinimumTickDistance() { const double velocity = 5; const double min_distance = velocity * 10; var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); Assert.Multiple(() => { int tickIndex = -1; while (++tickIndex < events.Length) { if (events[tickIndex].Type != SliderEventType.Tick) { continue; } Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance)); } }); }
public static IEnumerable <BosuHitObject> ConvertDefaultSlider(HitObject obj, Vector2 originalPosition, IBeatmap beatmap, IHasPathWithRepeats curve, double spanDuration) { List <BosuHitObject> converted = new List <BosuHitObject>(); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset, new CancellationToken())) { var curvePosition = curve.CurvePositionAt(e.PathProgress / (curve.RepeatCount + 1)) + originalPosition; var sliderEventPosition = toPlayfieldSpace(curvePosition) * new Vector2(1, 0.4f); switch (e.Type) { case SliderEventType.Repeat: converted.AddRange(generateExplosion(e.Time, Math.Clamp((int)curve.Distance / 15, 3, 15), sliderEventPosition, MathExtensions.GetRandomTimedAngleOffset(e.Time))); break; case SliderEventType.Tail: converted.AddRange(generateExplosion(e.Time, Math.Clamp((int)curve.Distance * (curve.RepeatCount + 1) / 15, 5, 20), sliderEventPosition, MathExtensions.GetRandomTimedAngleOffset(e.Time))); break; } } return(converted); }
protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); var tickSamples = Samples.Select(s => new HitSampleInfo { Bank = s.Bank, Name = @"slidertick", Volume = s.Volume }).ToList(); SliderEventDescriptor?lastEvent = null; foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { // generate tiny droplets since the last point if (lastEvent != null) { double sinceLastTick = e.Time - lastEvent.Value.Time; if (sinceLastTick > 80) { double timeBetweenTiny = sinceLastTick; while (timeBetweenTiny > 100) { timeBetweenTiny /= 2; } for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { AddNested(new TinyDroplet { Samples = tickSamples, StartTime = t + lastEvent.Value.Time, X = X + Path.PositionAt( lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, }); } } } // this also includes LegacyLastTick and this is used for TinyDroplet generation above. // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. lastEvent = e; switch (e.Type) { case SliderEventType.Tick: AddNested(new Droplet { Samples = tickSamples, StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; case SliderEventType.Head: case SliderEventType.Tail: case SliderEventType.Repeat: AddNested(new Fruit { Samples = Samples, StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, }); break; } } }
protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); var dropletSamples = Samples.Select(s => s.With(@"slidertick")).ToList(); int nodeIndex = 0; SliderEventDescriptor?lastEvent = null; foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) { // generate tiny droplets since the last point if (lastEvent != null) { double sinceLastTick = e.Time - lastEvent.Value.Time; if (sinceLastTick > 80) { double timeBetweenTiny = sinceLastTick; while (timeBetweenTiny > 100) { timeBetweenTiny /= 2; } for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { cancellationToken.ThrowIfCancellationRequested(); AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, X = X + Path.PositionAt( lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, }); } } } // this also includes LegacyLastTick and this is used for TinyDroplet generation above. // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. lastEvent = e; switch (e.Type) { case SliderEventType.Tick: AddNested(new Droplet { Samples = dropletSamples, StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X, }); break; case SliderEventType.Head: case SliderEventType.Tail: case SliderEventType.Repeat: AddNested(new Fruit { Samples = this.GetNodeSamples(nodeIndex++), StartTime = e.Time, X = X + Path.PositionAt(e.PathProgress).X, }); break; } } }
protected override IEnumerable <BosuHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap) { var objPosition = (obj as IHasPosition)?.Position ?? Vector2.Zero; var comboData = obj as IHasCombo; var difficulty = beatmap.BeatmapInfo.BaseDifficulty; if (comboData?.NewCombo ?? false) { index++; } List <BosuHitObject> hitObjects = new List <BosuHitObject>(); EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(obj.StartTime); bool kiai = effectPoint.KiaiMode; switch (obj) { // Slider case IHasCurve curve: if (!SlidersOnly) { var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double spanDuration = curve.Duration / (curve.RepeatCount + 1); double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset)) { Vector2 sliderEventPosition; // Don't take into account very small sliders. There's a chance that they will contain reverse spam, and offset looks ugly if (spanDuration < 75) { sliderEventPosition = objPosition * new Vector2(1, 0.5f); } else { sliderEventPosition = (curve.CurvePositionAt(e.PathProgress / (curve.RepeatCount + 1)) + objPosition) * new Vector2(1, 0.5f); } switch (e.Type) { case SliderEventType.Head: hitObjects.AddRange(generateExplosion( e.Time, kiai ? bullets_per_slider_head_kiai : bullets_per_slider_head, sliderEventPosition, comboData, index)); if (Symmetry) { hitObjects.AddRange(generateExplosion( e.Time, kiai ? bullets_per_slider_head_kiai : bullets_per_slider_head, getSymmetricalXPosition(sliderEventPosition), comboData, index)); } hitObjects.Add(new SoundHitObject { StartTime = obj.StartTime, Samples = obj.Samples }); break; case SliderEventType.Tick: hitObjects.Add(new TickCherry { StartTime = e.Time, Position = sliderEventPosition, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }); if (Symmetry) { hitObjects.Add(new TickCherry { StartTime = e.Time, Position = getSymmetricalXPosition(sliderEventPosition), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = getTickSamples(obj.Samples) }); break; case SliderEventType.Repeat: hitObjects.AddRange(generateExplosion( obj.StartTime + (e.SpanIndex + 1) * spanDuration, kiai ? bullets_per_slider_reverse_kiai : bullets_per_slider_reverse, sliderEventPosition, comboData, index, slider_angle_per_span * e.SpanIndex)); if (Symmetry) { hitObjects.AddRange(generateExplosion( obj.StartTime + (e.SpanIndex + 1) * spanDuration, kiai ? bullets_per_slider_reverse_kiai : bullets_per_slider_reverse, getSymmetricalXPosition(sliderEventPosition), comboData, index, -slider_angle_per_span * e.SpanIndex)); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = obj.Samples }); break; case SliderEventType.Tail: hitObjects.AddRange(generateExplosion( e.Time, kiai ? bullets_per_slider_tail_kiai : bullets_per_slider_tail, sliderEventPosition, comboData, index)); if (Symmetry) { hitObjects.AddRange(generateExplosion( e.Time, kiai ? bullets_per_slider_tail_kiai : bullets_per_slider_tail, getSymmetricalXPosition(sliderEventPosition), comboData, index)); } hitObjects.Add(new SoundHitObject { StartTime = curve.EndTime, Samples = obj.Samples }); break; } } } //body var bodyCherriesCount = Math.Min(curve.Distance * (curve.RepeatCount + 1) / 10, max_visuals_per_slider_span * (curve.RepeatCount + 1)); for (int i = 0; i < bodyCherriesCount; i++) { var progress = (float)i / bodyCherriesCount; var position = (curve.CurvePositionAt(progress) + objPosition); if (!SlidersOnly) { position *= new Vector2(1, 0.5f); hitObjects.Add(new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = position, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }); if (Symmetry) { hitObjects.Add(new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = getSymmetricalXPosition(position), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }); } } else { hitObjects.AddRange(new[] { new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = position, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = getSymmetricalXPosition(position), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = getSymmetricalYPosition(position), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime + curve.Duration * progress, Position = getSymmetricalYPosition(getSymmetricalXPosition(position)), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index } }); } } break; // Spinner case IHasEndTime endTime: if (SlidersOnly) { break; } var spansPerSpinner = endTime.Duration / spinner_span_delay; for (int i = 0; i < spansPerSpinner; i++) { hitObjects.AddRange(generateExplosion( obj.StartTime + i * spinner_span_delay, kiai ? bullets_per_spinner_span_kiai : bullets_per_spinner_span, objPosition * new Vector2(1, 0.5f), comboData, index, i * spinner_angle_per_span)); } break; // Hitcircle default: if (SlidersOnly) { hitObjects.AddRange(new[] { new SliderPartCherry { StartTime = obj.StartTime, Position = objPosition, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime, Position = getSymmetricalXPosition(objPosition), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime, Position = getSymmetricalYPosition(objPosition), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index }, new SliderPartCherry { StartTime = obj.StartTime, Position = getSymmetricalYPosition(getSymmetricalXPosition(objPosition)), NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index } }); break; } hitObjects.AddRange(generateExplosion( obj.StartTime, kiai ? bullets_per_hitcircle_kiai : bullets_per_hitcircle, objPosition * new Vector2(1, 0.5f), comboData, index, 0, 120)); if (Symmetry) { hitObjects.AddRange(generateExplosion( obj.StartTime, kiai ? bullets_per_hitcircle_kiai : bullets_per_hitcircle, getSymmetricalXPosition(objPosition * new Vector2(1, 0.5f)), comboData, index, 0, 120)); } hitObjects.Add(new SoundHitObject { StartTime = obj.StartTime, Samples = obj.Samples }); break; } return(hitObjects); }
private static List <TouhouHitObject> generateRepeatSpamSlider(HitObject obj, IBeatmap beatmap, IHasCurve curve, double spanDuration, bool isKiai, int index) { List <TouhouHitObject> hitObjects = new List <TouhouHitObject>(); var objPosition = (obj as IHasPosition)?.Position ?? Vector2.Zero; var comboData = obj as IHasCombo; var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset)) { var sliderEventPosition = objPosition * new Vector2(1, 0.5f); switch (e.Type) { case SliderEventType.Head: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index)); } hitObjects.Add(new SoundHitObject { StartTime = obj.StartTime, Samples = obj.Samples }); break; case SliderEventType.Repeat: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index, slider_angle_per_span * (e.SpanIndex + 1))); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = obj.Samples }); break; case SliderEventType.Tail: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, bullets_per_slider_reverse, sliderEventPosition, comboData, isKiai, index, slider_angle_per_span * (curve.RepeatCount + 1))); } hitObjects.Add(new SoundHitObject { StartTime = curve.EndTime, Samples = obj.Samples }); break; } } hitObjects.AddRange(generateSliderBody(obj, curve, isKiai, index)); return(hitObjects); }
private static List <TouhouHitObject> generateDefaultSlider(HitObject obj, IBeatmap beatmap, IHasCurve curve, double spanDuration, bool isKiai, int index) { List <TouhouHitObject> hitObjects = new List <TouhouHitObject>(); var objPosition = (obj as IHasPosition)?.Position ?? Vector2.Zero; var comboData = obj as IHasCombo; var difficulty = beatmap.BeatmapInfo.BaseDifficulty; var controlPointInfo = beatmap.ControlPointInfo; TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(obj.StartTime); double scoringDistance = 100 * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; var velocity = scoringDistance / timingPoint.BeatLength; var tickDistance = scoringDistance / difficulty.SliderTickRate; double legacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0; foreach (var e in SliderEventGenerator.Generate(obj.StartTime, spanDuration, velocity, tickDistance, curve.Path.Distance, curve.RepeatCount + 1, legacyLastTickOffset)) { var sliderEventPosition = (curve.CurvePositionAt(e.PathProgress / (curve.RepeatCount + 1)) + objPosition) * new Vector2(1, 0.5f); switch (e.Type) { case SliderEventType.Head: hitObjects.Add(new SoundHitObject { StartTime = obj.StartTime, Samples = obj.Samples }); break; case SliderEventType.Tick: if (positionIsValid(sliderEventPosition)) { hitObjects.Add(new TickCherry { Angle = 180, StartTime = e.Time, Position = sliderEventPosition, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, IndexInBeatmap = index, IsKiai = isKiai }); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = getTickSamples(obj.Samples) }); break; case SliderEventType.Repeat: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateTriangularExplosion( e.Time, 20, sliderEventPosition, comboData, isKiai, index, MathExtensions.GetRandomTimedAngleOffset(e.Time))); } hitObjects.Add(new SoundHitObject { StartTime = e.Time, Samples = obj.Samples }); break; case SliderEventType.Tail: if (positionIsValid(sliderEventPosition)) { hitObjects.AddRange(generateExplosion( e.Time, Math.Clamp((int)curve.Distance / 15, 5, 20), sliderEventPosition, comboData, isKiai, index)); } hitObjects.Add(new SoundHitObject { StartTime = curve.EndTime, Samples = obj.Samples }); break; } } hitObjects.AddRange(generateSliderBody(obj, curve, isKiai, index)); return(hitObjects); }