public DifficultyPointPiece(DifficultyControlPoint point) : base(point) { speedMultiplier = point.SpeedMultiplierBindable.GetBoundCopy(); Y = Height; }
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { convertType = PatternType.None; if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) { convertType = PatternType.LowProbability; } var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; spanCount = repeatsData?.SpanCount() ?? 1; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); // The true distance, accounting for any repeats double distance = (distanceData?.Distance ?? 0) * spanCount; // The velocity of the osu! hit object - calculated as the velocity of a slider double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; // The duration of the osu! hit object double osuDuration = distance / osuVelocity; EndTime = hitObject.StartTime + osuDuration; SegmentDuration = (EndTime - HitObject.StartTime) / spanCount; }
private void load() { // Calculate default multiplier control points var lastTimingPoint = new TimingControlPoint(); var lastDifficultyPoint = new DifficultyControlPoint(); // Merge timing + difficulty points var allPoints = new SortedList <ControlPoint>(Comparer <ControlPoint> .Default); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); // Generate the timing points, making non-timing changes use the previous timing change var timingChanges = allPoints.Select(c => { var timingPoint = c as TimingControlPoint; var difficultyPoint = c as DifficultyControlPoint; if (timingPoint != null) { lastTimingPoint = timingPoint; } if (difficultyPoint != null) { lastDifficultyPoint = difficultyPoint; } return(new MultiplierControlPoint(c.Time) { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }); }); double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; // Perform some post processing of the timing changes timingChanges = timingChanges // Collapse sections after the last hit object .Where(s => s.StartTime <= lastObjectTime) // Collapse sections with the same start time .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); DefaultControlPoints.AddRange(timingChanges); // If we have no control points, add a default one if (DefaultControlPoints.Count == 0) { DefaultControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield)); }
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; return((float)(scoringDistance / timingPoint.BeatLength)); }
protected override void ApplyMapPropertiesSelf(ControlPointGroup controlPoints, MapDifficulty difficulty) { base.ApplyMapPropertiesSelf(controlPoints, difficulty); TimingControlPoint timingPoint = controlPoints.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPoints.DifficultyPointAt(StartTime); float distance = BaseDistance * difficultyPoint.SpeedMultiplier * difficulty.SliderMultiplier; Speed = distance / timingPoint.BeatLength; }
protected override void ApplyBeatmap() { // Calculate default multiplier control points var lastTimingPoint = new TimingControlPoint(); var lastDifficultyPoint = new DifficultyControlPoint(); // Merge timing + difficulty points var allPoints = new SortedList <ControlPoint>(Comparer <ControlPoint> .Default); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); // Generate the timing points, making non-timing changes use the previous timing change var timingChanges = allPoints.Select(c => { var timingPoint = c as TimingControlPoint; var difficultyPoint = c as DifficultyControlPoint; if (timingPoint != null) { lastTimingPoint = timingPoint; } if (difficultyPoint != null) { lastDifficultyPoint = difficultyPoint; } return(new MultiplierControlPoint(c.Time) { TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }); }); double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; // Perform some post processing of the timing changes timingChanges = timingChanges // Collapse sections after the last hit object .Where(s => s.StartTime <= lastObjectTime) // Collapse sections with the same start time .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime) // Collapse sections with the same beat length .GroupBy(s => s.TimingPoint.BeatLength * s.DifficultyPoint.SpeedMultiplier).Select(g => g.First()); DefaultControlPoints.AddRange(timingChanges); // If we have no control points, add a default one if (DefaultControlPoints.Count == 0) { DefaultControlPoints.Add(new MultiplierControlPoint()); } }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; }
private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) { var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); if (newPoint.EquivalentTo(existing)) { return; } beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == newPoint.Time); beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); }
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaults(controlPointInfo, difficulty); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier; Kiai |= effectPoint.KiaiMode; }
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 TestSlider() { DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f }; DefaultsApplied += _ => { HeadCircle.HitWindows = new TestHitWindows(); TailCircle.HitWindows = new TestHitWindows(); HeadCircle.HitWindows.SetDifficulty(0); TailCircle.HitWindows.SetDifficulty(0); }; }
/// <summary> /// Adds specified difficulty point to the beatmap. /// </summary> private void AddDifficultyPoint(DifficultyControlPoint point) { // Replace existing point if equal time. var existingPoint = map.ControlPoints.DifficultyPointAt(point.Time); if (point.IsEquivalentTo(existingPoint)) { return; } if (existingPoint.Time == point.Time) { map.ControlPoints.DifficultyPoints.Remove(existingPoint); } map.ControlPoints.DifficultyPoints.Add(point); }
public void TestRemoveControlPointFromGroup() { var cpi = new ControlPointInfo(); var group = cpi.GroupAt(1000, true); Assert.That(cpi.Groups.Count, Is.EqualTo(1)); var difficultyPoint = new DifficultyControlPoint(); group.Add(difficultyPoint); group.Remove(difficultyPoint); Assert.That(group.ControlPoints.Count, Is.EqualTo(0)); Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); }
private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) { var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); if (existing.Time == newPoint.Time) { // autogenerated points should not replace non-autogenerated. // this allows for incorrectly ordered timing points to still be correctly handled. if (newPoint.AutoGenerated && !existing.AutoGenerated) { return; } beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); } beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); }
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; } } }
private void load() { Children = new Drawable[] { new FillFlowContainer { Width = 200, Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, Children = new Drawable[] { sliderVelocitySlider = new SliderWithTextBoxInput <double>("Velocity") { Current = new DifficultyControlPoint().SliderVelocityBindable, KeyboardStep = 0.1f }, new OsuTextFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Text = "Hold shift while dragging the end of an object to adjust velocity while snapping." } } } }; var selectedPointBindable = point.SliderVelocityBindable; // there may be legacy control points, which contain infinite precision for compatibility reasons (see LegacyDifficultyControlPoint). // generally that level of precision could only be set by externally editing the .osu file, so at the point // a user is looking to update this within the editor it should be safe to obliterate this additional precision. double expectedPrecision = new DifficultyControlPoint().SliderVelocityBindable.Precision; if (selectedPointBindable.Precision < expectedPrecision) { selectedPointBindable.Precision = expectedPrecision; } sliderVelocitySlider.Current = selectedPointBindable; sliderVelocitySlider.Current.BindValueChanged(_ => beatmap?.Update(hitObject)); }
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 DistanceObjectPatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap) { convertType = PatternType.None; if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) { convertType = PatternType.LowProbability; } var distanceData = hitObject as IHasDistance; var repeatsData = hitObject as IHasRepeats; Debug.Assert(distanceData != null); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint; double beatLength; #pragma warning disable 618 if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) #pragma warning restore 618 { beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; } else { beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity; } SpanCount = repeatsData?.SpanCount() ?? 1; StartTime = (int)Math.Round(hitObject.StartTime); // This matches stable's calculation. EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; }
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 IEnumerable <TaikoHitObject> ConvertHitObject(HitObject obj, IBeatmap beatmap) { var distanceData = obj as IHasDistance; var repeatsData = obj as IHasRepeats; var endTimeData = obj as IHasEndTime; var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information List <SampleInfo> samples = obj.Samples; bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); if (distanceData != null) { // Number of spans of the object - one for the initial length and for each repeat int spans = repeatsData?.SpanCount() ?? 1; TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); double speedAdjustment = difficultyPoint.SpeedMultiplier; double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; // The true distance, accounting for any repeats. This ends up being the drum roll distance later double distance = distanceData.Distance * spans * legacy_velocity_multiplier; // 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; // 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; // 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; } // 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); if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { List <List <SampleInfo> > allSamples = curveData != null ? curveData.NodeSamples : new List <List <SampleInfo> >(new[] { samples }); int i = 0; for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { List <SampleInfo> currentSamples = allSamples[i]; bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); if (isRim) { yield return(new RimHit { StartTime = j, Samples = currentSamples, IsStrong = strong }); } else { yield return(new CentreHit { StartTime = j, Samples = currentSamples, IsStrong = strong }); } i = (i + 1) % allSamples.Count; } } else { yield return(new DrumRoll { StartTime = obj.StartTime, Samples = obj.Samples, IsStrong = strong, Duration = taikoDuration, TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 }); } } else if (endTimeData != null) { double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return(new Swell { StartTime = obj.StartTime, Samples = obj.Samples, Duration = endTimeData.Duration, RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier) }); } else { bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); if (isRim) { yield return(new RimHit { StartTime = obj.StartTime, Samples = obj.Samples, IsStrong = strong }); } else { yield return(new CentreHit { StartTime = obj.StartTime, Samples = obj.Samples, IsStrong = strong }); } } }
private void handleTimingPoints(Beatmap beatmap, string line) { string[] split = line.Split(','); double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) { timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); } LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) { sampleSet = (LegacySampleBank)int.Parse(split[3]); } //SampleBank sampleBank = SampleBank.Default; //if (split.Length >= 5) // sampleBank = (SampleBank)int.Parse(split[4]); int sampleVolume = defaultSampleVolume; if (split.Length >= 6) { sampleVolume = int.Parse(split[5]); } bool timingChange = true; if (split.Length >= 7) { timingChange = split[6][0] == '1'; } bool kiaiMode = false; bool omitFirstBarSignature = false; if (split.Length >= 8) { int effectFlags = int.Parse(split[7]); kiaiMode = (effectFlags & 1) > 0; omitFirstBarSignature = (effectFlags & 8) > 0; } string stringSampleSet = sampleSet.ToString().ToLower(); if (stringSampleSet == @"none") { stringSampleSet = @"normal"; } DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(time); SoundControlPoint soundPoint = beatmap.ControlPointInfo.SoundPointAt(time); EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(time); if (timingChange) { beatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { Time = time, BeatLength = beatLength, TimeSignature = timeSignature }); } if (speedMultiplier != difficultyPoint.SpeedMultiplier) { beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time); beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = time, SpeedMultiplier = speedMultiplier }); } if (stringSampleSet != soundPoint.SampleBank || sampleVolume != soundPoint.SampleVolume) { beatmap.ControlPointInfo.SoundPoints.Add(new SoundControlPoint { Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume }); } if (kiaiMode != effectPoint.KiaiMode || omitFirstBarSignature != effectPoint.OmitFirstBarLine) { beatmap.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = time, KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature }); } }
public override float GetBeatSnapDistanceAt(double referenceTime) { DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); return((float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor)); }
private void handleControlPoints(TextWriter writer) { if (beatmap.ControlPointInfo.Groups.Count == 0) { return; } var legacyControlPoints = new LegacyControlPointInfo(); foreach (var point in beatmap.ControlPointInfo.AllControlPoints) { legacyControlPoints.Add(point.Time, point.DeepClone()); } writer.WriteLine("[TimingPoints]"); SampleControlPoint lastRelevantSamplePoint = null; DifficultyControlPoint lastRelevantDifficultyPoint = null; bool isOsuRuleset = beatmap.BeatmapInfo.RulesetID == 0; // iterate over hitobjects and pull out all required sample and difficulty changes extractDifficultyControlPoints(beatmap.HitObjects); extractSampleControlPoints(beatmap.HitObjects); // handle scroll speed, which is stored as "slider velocity" in legacy formats. // this is relevant for scrolling ruleset beatmaps. if (!isOsuRuleset) { foreach (var point in legacyControlPoints.EffectPoints) { legacyControlPoints.Add(point.Time, new DifficultyControlPoint { SliderVelocity = point.ScrollSpeed }); } } foreach (var group in legacyControlPoints.Groups) { var groupTimingPoint = group.ControlPoints.OfType <TimingControlPoint>().FirstOrDefault(); // If the group contains a timing control point, it needs to be output separately. if (groupTimingPoint != null) { writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},")); writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},")); outputControlPointAt(groupTimingPoint.Time, true); } // Output any remaining effects as secondary non-timing control point. var difficultyPoint = legacyControlPoints.DifficultyPointAt(group.Time); writer.Write(FormattableString.Invariant($"{group.Time},")); writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SliderVelocity},")); outputControlPointAt(group.Time, false); } void outputControlPointAt(double time, bool isTimingPoint) { var samplePoint = legacyControlPoints.SamplePointAt(time); var effectPoint = legacyControlPoints.EffectPointAt(time); // Apply the control point to a hit sample to uncover legacy properties (e.g. suffix) HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty)); // Convert effect flags to the legacy format LegacyEffectFlags effectFlags = LegacyEffectFlags.None; if (effectPoint.KiaiMode) { effectFlags |= LegacyEffectFlags.Kiai; } if (effectPoint.OmitFirstBarLine) { effectFlags |= LegacyEffectFlags.OmitFirstBarLine; } writer.Write(FormattableString.Invariant($"{(int)legacyControlPoints.TimingPointAt(time).TimeSignature},")); writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},")); writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},")); writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},")); writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},")); writer.Write(FormattableString.Invariant($"{(int)effectFlags}")); writer.WriteLine(); } IEnumerable <DifficultyControlPoint> collectDifficultyControlPoints(IEnumerable <HitObject> hitObjects) { if (!isOsuRuleset) { yield break; } foreach (var hitObject in hitObjects) { yield return(hitObject.DifficultyControlPoint); foreach (var nested in collectDifficultyControlPoints(hitObject.NestedHitObjects)) { yield return(nested); } } } void extractDifficultyControlPoints(IEnumerable <HitObject> hitObjects) { foreach (var hDifficultyPoint in collectDifficultyControlPoints(hitObjects).OrderBy(dp => dp.Time)) { if (!hDifficultyPoint.IsRedundant(lastRelevantDifficultyPoint)) { legacyControlPoints.Add(hDifficultyPoint.Time, hDifficultyPoint); lastRelevantDifficultyPoint = hDifficultyPoint; } } } IEnumerable <SampleControlPoint> collectSampleControlPoints(IEnumerable <HitObject> hitObjects) { foreach (var hitObject in hitObjects) { yield return(hitObject.SampleControlPoint); foreach (var nested in collectSampleControlPoints(hitObject.NestedHitObjects)) { yield return(nested); } } } void extractSampleControlPoints(IEnumerable <HitObject> hitObject) { foreach (var hSamplePoint in collectSampleControlPoints(hitObject).OrderBy(sp => sp.Time)) { if (!hSamplePoint.IsRedundant(lastRelevantSamplePoint)) { legacyControlPoints.Add(hSamplePoint.Time, hSamplePoint); lastRelevantSamplePoint = hSamplePoint; } } } }
/// <summary> /// Creates a <see cref="MultiplierControlPoint"/> by copying another <see cref="MultiplierControlPoint"/>. /// </summary> /// <param name="startTime">The start time of this <see cref="MultiplierControlPoint"/>.</param> /// <param name="other">The <see cref="MultiplierControlPoint"/> to copy.</param> public MultiplierControlPoint(double startTime, MultiplierControlPoint other) : this(startTime) { TimingPoint = other.TimingPoint; DifficultyPoint = other.DifficultyPoint; }
private void load() { double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) { IReadOnlyList <TimingControlPoint> timingPoints = Beatmap.ControlPointInfo.TimingPoints; double maxDuration = 0; for (int i = 0; i < timingPoints.Count; i++) { if (timingPoints[i].Time > lastObjectTime) { break; } double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time : lastObjectTime; double duration = endTime - timingPoints[i].Time; if (duration > maxDuration) { maxDuration = duration; // The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths // the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; } } } // Merge sequences of timing and difficulty control points to create the aggregate "multiplier" control point var lastTimingPoint = new TimingControlPoint(); var lastDifficultyPoint = new DifficultyControlPoint(); var allPoints = new SortedList <ControlPoint>(Comparer <ControlPoint> .Default); allPoints.AddRange(Beatmap.ControlPointInfo.TimingPoints); allPoints.AddRange(Beatmap.ControlPointInfo.DifficultyPoints); // Generate the timing points, making non-timing changes use the previous timing change and vice-versa var timingChanges = allPoints.Select(c => { var timingPoint = c as TimingControlPoint; var difficultyPoint = c as DifficultyControlPoint; if (timingPoint != null) { lastTimingPoint = timingPoint; } if (difficultyPoint != null) { lastDifficultyPoint = difficultyPoint; } return(new MultiplierControlPoint(c.Time) { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, BaseBeatLength = baseBeatLength, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint }); }); // Trim unwanted sequences of timing changes timingChanges = timingChanges // Collapse sections after the last hit object .Where(s => s.StartTime <= lastObjectTime) // Collapse sections with the same start time .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime); controlPoints.AddRange(timingChanges); if (controlPoints.Count == 0) { controlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); } }
public DifficultyPointPiece(DifficultyControlPoint difficultyPoint) { this.difficultyPoint = difficultyPoint; speedMultiplier = difficultyPoint.SpeedMultiplierBindable.GetBoundCopy(); }
public DifficultyRowAttribute(DifficultyControlPoint difficulty) : base(difficulty, "difficulty") { speedMultiplier = difficulty.SpeedMultiplierBindable.GetBoundCopy(); }
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); }
public DifficultyEditPopover(HitObject hitObject) { this.hitObject = hitObject; point = hitObject.DifficultyControlPoint; }