private static double GetBpmScale(this Beatmap beatmap, CatchHitObject hitObject)
        {
            var timingLine = beatmap.GetTimingLine(hitObject.ActualTime);
            var bpm        = GetBeatsPerMinute(timingLine);

            return(180 / bpm);
        }
        /// <summary>
        ///     Check if the current object is the same snap as the other object.
        ///     There is a snap margin of 4 ms since objects can be at most 2 ms off before they are detected by MV.
        /// </summary>
        /// <param name="currentObject">The current object which is being used for the range</param>
        /// <param name="otherObject">The other object which is checked if it is in the range of the current object</param>
        /// <returns>True if the current object and the other object are the same snap</returns>
        public static bool IsSameSnap(this CatchHitObject currentObject, CatchHitObject otherObject)
        {
            const int snapMargin = 4;
            var       range      = Enumerable.Range(
                currentObject.TimeToTarget - snapMargin,
                currentObject.TimeToTarget + snapMargin);

            return(range.Contains(otherObject.TimeToTarget));
        }
        private static ObjectMetadata GenerateObjectMetadata(
            Beatmap beatmap,
            CatchHitObject current,
            CatchHitObject next,
            NoteDirection lastDirection,
            double dashRange,
            double walkRange,
            double halfCatcherWidth,
            double baseWalkRange,
            double hyperdashLeniency,
            bool lastWasHyper
            )
        {
            var metadata = new ObjectMetadata {
                Direction = current.X == next.X ? NoteDirection.NONE : current.X > next.X ? NoteDirection.LEFT : NoteDirection.RIGHT,
                // 1/4th of a frame of grace time, taken from osu-stable
                TimeToNext         = next.ActualTime - current.ActualTime - 1000f / 60f / 4,
                DistanceInOsuCords = Math.Abs(next.X - current.X)
            };

            var bpmScale = beatmap.GetBpmScale(next);

            double actualWalkRange;

            if (lastWasHyper)
            {
                actualWalkRange = (lastDirection == metadata.Direction ? walkRange : baseWalkRange) *
                                  hyperdashLeniency;
            }
            else
            {
                actualWalkRange = lastDirection == metadata.Direction ? walkRange : baseWalkRange;
            }

            metadata.HyperDistanceToNext = metadata.DistanceInOsuCords - (lastDirection != NoteDirection.NONE || lastDirection == metadata.Direction ? dashRange : halfCatcherWidth);
            metadata.DashDistanceToNext  = metadata.DistanceInOsuCords - (lastDirection != NoteDirection.NONE ? actualWalkRange : baseWalkRange);
            metadata.DistanceToHyper     = (int)Math.Floor((float)(metadata.TimeToNext - metadata.HyperDistanceToNext));
            metadata.DistanceToDash      = (int)Math.Floor((float)(metadata.TimeToNext - metadata.DashDistanceToNext - (metadata.TimeToNext * (bpmScale * 0.3))));

            // Label the type of movement based on if the distance is dashable or walkable
            if (metadata.DistanceToHyper < 0)
            {
                metadata.MovementType = MovementType.HYPERDASH;
            }
            else if (metadata.DistanceToDash < 0)
            {
                metadata.MovementType = MovementType.DASH;
            }
            else
            {
                metadata.MovementType = MovementType.WALK;
            }

            return(metadata);
        }
        /// <summary>
        ///     Check if the snapping between two objects is higher-snapped or basic-snapped
        ///     Cup: No dashes or hyperdashes are allowed
        ///     Salad: 125-249 ms dashes are higher-snapped, hyperdashes are not allowed
        ///     Platter: 62-124 ms dashes are higher-snapped, 125-249 ms hyperdashes are higher-snapped
        ///     Rain: 62-124 ms dashes/hyperdashes are higher-snapped
        ///     Overdose: No allowed distance are specified so no basic-snapped and higher-snapped exist
        /// </summary>
        /// <param name="currentObject">The current object which is getting checked</param>
        /// <param name="difficulty">The difficulty of the mapset</param>
        /// <returns>True if the origin object is higher-snapped</returns>
        public static bool IsHigherSnapped(this CatchHitObject currentObject, Beatmap.Difficulty difficulty)
        {
            var ms = currentObject.TimeToTarget;

            return(difficulty switch
            {
                Beatmap.Difficulty.Normal => ms < 250,
                Beatmap.Difficulty.Hard => ms < (currentObject.MovementType == MovementType.HYPERDASH ? 250 : 125),
                Beatmap.Difficulty.Insane => ms < 125,
                _ => false
            });
        private static List <CatchHitObject> GenerateCatchHitObjects(Beatmap beatmap)
        {
            var mapObjects = beatmap.hitObjects;
            var objects    = new List <CatchHitObject>();

            // Get all the catch objects from the spinners
            foreach (var mapSliderObject in mapObjects.OfType <Slider>())
            {
                var objectExtras = new List <CatchHitObject>();
                var objectCode   = mapSliderObject.code.Split(',');

                // The first object of a slider is always its head
                var sliderObject = new CatchHitObject(objectCode, beatmap, NoteType.HEAD, mapSliderObject, mapSliderObject.time);

                var edgeTimes = GetEdgeTimes(mapSliderObject).ToList();

                for (var i = 0; i < edgeTimes.Count; i++)
                {
                    objectExtras.Add(i + 1 == edgeTimes.Count
                        ? CreateObjectExtra(beatmap, sliderObject, mapSliderObject, edgeTimes[i], objectCode, NoteType.TAIL)
                        : CreateObjectExtra(beatmap, sliderObject, mapSliderObject, edgeTimes[i], objectCode, NoteType.REPEAT));
                }

                // TODO doesn't seem to work?
                var actualSliderTicks = mapSliderObject.sliderTickTimes.Where(tickTime =>
                                                                              objectExtras.All(extra => !IsSimilarTime(tickTime, extra.ActualTime)));

                objectExtras.AddRange(mapSliderObject.sliderTickTimes.Select(sliderTick =>
                                                                             CreateObjectExtra(beatmap, sliderObject, mapSliderObject, sliderTick, objectCode, NoteType.DROPLET)));

                objects.Add(sliderObject);
                objects.AddRange(objectExtras);
            }

            // Add all circles
            objects.AddRange(
                from mapObject in mapObjects
                where mapObject is Circle
                select new CatchHitObject(mapObject.code.Split(','), beatmap, NoteType.CIRCLE, mapObject, mapObject.time)
                );

            // Add all spinners so we can ignore then when calculating dashes or hyperdashes
            objects.AddRange(
                from mapObject in mapObjects
                where mapObject is Spinner
                select new CatchHitObject(mapObject.code.Split(','), beatmap, NoteType.SPINNER, mapObject, mapObject.time)
                );
            objects.Sort((h1, h2) => h1.time.CompareTo(h2.time));
            return(objects);
        }
        private static CatchHitObject CreateObjectExtra(Beatmap beatmap, CatchHitObject catchHitObject,
                                                        Slider slider, double time, string[] objectCode, NoteType type)
        {
            var objectCodeCopy = (string[])objectCode.Clone();

            objectCodeCopy[0] = slider.GetPathPosition(time).X.ToString(CultureInfo.InvariantCulture);
            objectCodeCopy[2] = time.ToString(CultureInfo.InvariantCulture);
            var line = string.Join(",", objectCodeCopy);
            var catchSliderHitObject = new CatchHitObject(line.Split(','), beatmap, type, slider, time)
            {
                SliderHead = catchHitObject
            };

            return(catchSliderHitObject);
        }
        private static bool IsEdgeMovement(Beatmap beatmap, CatchHitObject hitObject)
        {
            var pixelsScale = (int)beatmap.GetBpmScale(hitObject) * 10;

            switch (hitObject.MovementType)
            {
            case MovementType.WALK:
                return(hitObject.DistanceToDash <= pixelsScale);

            case MovementType.DASH:
                return(hitObject.DistanceToHyper <= pixelsScale);

            default:
                return(false);
            }
        }
        public static float GetTriggerDistance(this CatchHitObject currentObject)
        {
            if (currentObject.MovementType != MovementType.HYPERDASH)
            {
                return(0f);
            }

            var xDistance = currentObject.NoteDirection switch
            {
                NoteDirection.LEFT => currentObject.X - currentObject.Target.X,
                NoteDirection.RIGHT => currentObject.Target.X - currentObject.X,
                _ => 0f
            };

            if (xDistance > 0f)
            {
                return(xDistance - Math.Abs(currentObject.DistanceToHyper));
            }

            return(0f);
        }
Ejemplo n.º 9
0
        private static ObjectMetadata GenerateObjectMetadata(
            CatchHitObject current,
            CatchHitObject next,
            NoteDirection lastDirection,
            double dashRange,
            double walkRange,
            double halfCatcherWidth
            )
        {
            var metadata = new ObjectMetadata {
                Direction = next.X > current.X ? NoteDirection.LEFT : NoteDirection.RIGHT,
                // 1/4th of a frame of grace time, taken from osu-stable
                TimeToNext         = next.time - current.time - 1000f / 60f / 4,
                DistanceInOsuCords = Math.Abs(next.X - current.X)
            };

            metadata.HyperDistanceToNext = metadata.DistanceInOsuCords - (lastDirection == metadata.Direction ? dashRange : halfCatcherWidth);
            metadata.DashDistanceToNext  = metadata.DistanceInOsuCords - (lastDirection == metadata.Direction ? walkRange : halfCatcherWidth / 2);
            metadata.DistanceToHyper     = (int)(metadata.TimeToNext - metadata.HyperDistanceToNext);
            // Greaper's : metadata.DistanceToDash = (int) (metadata.TimeToNext - metadata.DashDistanceToNext - (metadata.TimeToNext * 0.3));
            // Basically full walk - 30 counts as a dash according to data from salad diffs
            metadata.DistanceToDash = (int)(metadata.TimeToNext - metadata.DashDistanceToNext - (metadata.TimeToNext * 0.50) + 30);

            // Label the type of movement based on if the distance is dashable or walkable
            if (metadata.DistanceToHyper < 0)
            {
                metadata.MovementType = MovementType.HYPERDASH;
            }
            else if (metadata.DistanceToDash < 0)
            {
                metadata.MovementType = MovementType.DASH;
            }
            else
            {
                metadata.MovementType = MovementType.WALK;
            }

            return(metadata);
        }
 public static float GetCurrentTriggerDistance(this CatchHitObject currentObject)
 {
     return(GetTriggerDistance(currentObject) / currentObject.DistanceToHyper);
 }
Ejemplo n.º 11
0
        private static List <CatchHitObject> GenerateCatchHitObjects(Beatmap beatmap)
        {
            var mapObjects = beatmap.hitObjects;
            var objects    = new List <CatchHitObject>();

            // Get all the catch objects from the spinners
            foreach (var mapSliderObject in mapObjects.OfType <Slider>())
            {
                var objectExtras = new List <CatchHitObject>();
                var objectCode   = mapSliderObject.code.Split(',');

                // The first object of a slider is always its head
                var sliderObject = new CatchHitObject(objectCode, beatmap, NoteType.HEAD);

                var edgeTimes = GetEdgeTimes(mapSliderObject).ToList();

                if (edgeTimes.Count == 1)
                {
                    // We only have a slider end so can specify this as a "Tail"
                    objectExtras.AddRange(CreateObjectExtra(beatmap, mapSliderObject, edgeTimes, objectCode, NoteType.TAIL));
                }
                else if (edgeTimes.Count >= 2)
                {
                    // We have a repeat so the slider end is the last object
                    var lastObjectArray = new[] { edgeTimes.Last() };

                    // Remove the last object from the edgeTimes so we only have repeats
                    edgeTimes.RemoveAt(edgeTimes.Count - 1);

                    objectExtras.AddRange(CreateObjectExtra(beatmap, mapSliderObject, edgeTimes, objectCode, NoteType.REPEAT));
                    objectExtras.AddRange(CreateObjectExtra(beatmap, mapSliderObject, lastObjectArray, objectCode, NoteType.TAIL));
                }

                foreach (var sliderTick in mapSliderObject.sliderTickTimes)
                {
                    // Only add a slider tick to the objects extra if not present yet
                    if (objectExtras.Any(x => x.time.Equals(sliderTick)))
                    {
                        continue;
                    }

                    objectExtras.AddRange(CreateObjectExtra(beatmap, mapSliderObject, new [] { sliderTick }, objectCode, NoteType.DROPLET));
                }

                objectExtras.Sort((h1, h2) => h1.time.CompareTo(h2.time));

                sliderObject.Extras = objectExtras;
                objects.Add(sliderObject);
            }

            // Add all circles
            objects.AddRange(
                from mapObject in mapObjects
                where mapObject is Circle
                select new CatchHitObject(mapObject.code.Split(','), beatmap, NoteType.CIRCLE)
                );



            objects.Sort((h1, h2) => h1.time.CompareTo(h2.time));

            return(new HashSet <CatchHitObject>(objects, new TimeComparer()).ToList());
        }