Example #1
0
        private void CalculateSpecificStrain(DifficultyHitObjectOsu PreviousHitObject, BeatmapDifficultyCalculatorOsu.DifficultyType Type, double TimeRate)
        {
            double Addition    = 0;
            double TimeElapsed = (double)(BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime) / TimeRate;
            double Decay       = Math.Pow(DECAY_BASE[(int)Type], TimeElapsed / 1000);

            if (BaseHitObject.IsType(HitObjectType.Spinner))
            {
                // Do nothing for spinners
            }
            else if (BaseHitObject.IsType(HitObjectType.Slider))
            {
                switch (Type)
                {
                case BeatmapDifficultyCalculatorOsu.DifficultyType.Speed:

                    // For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
                    // The spacing weight exists to differentiate between being able to easily alternate or having to single.
                    Addition =
                        SpacingWeight(PreviousHitObject.LazySliderLengthFirst +
                                      PreviousHitObject.LazySliderLengthSubsequent * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                                      DistanceTo(PreviousHitObject), Type) *
                        SPACING_WEIGHT_SCALING[(int)Type];
                    break;


                case BeatmapDifficultyCalculatorOsu.DifficultyType.Aim:

                    // For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
                    // to multiple jumps.
                    Addition =
                        (
                            SpacingWeight(PreviousHitObject.LazySliderLengthFirst, Type) +
                            SpacingWeight(PreviousHitObject.LazySliderLengthSubsequent, Type) * (PreviousHitObject.BaseHitObject.SegmentCount - 1) +
                            SpacingWeight(DistanceTo(PreviousHitObject), Type)
                        ) *
                        SPACING_WEIGHT_SCALING[(int)Type];
                    break;
                }
            }
            else if (BaseHitObject.IsType(HitObjectType.Normal))
            {
                Addition = SpacingWeight(DistanceTo(PreviousHitObject), Type) * SPACING_WEIGHT_SCALING[(int)Type];
            }

            // Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
            // You will never find maps that require this amongst ranked maps.
            Addition /= Math.Max(TimeElapsed, 50);

            Strains[(int)Type] = PreviousHitObject.Strains[(int)Type] * Decay + Addition;
        }
Example #2
0
        internal void CalculateStrains(DifficultyHitObjectTaiko PreviousHitObject, double TimeRate)
        {
            // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make.
            // See Taiko feedback thread.
            TimeElapsed = (double)(BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime) / TimeRate;
            double Decay = Math.Pow(DECAY_BASE, TimeElapsed / 1000);

            double Addition = 1;

            // Only if we are no slider or spinner we get an extra addition
            if (PreviousHitObject.BaseHitObject.IsType(HitObjectType.Normal) && BaseHitObject.IsType(HitObjectType.Normal) &&
                BaseHitObject.StartTime - PreviousHitObject.BaseHitObject.StartTime < 1000)    // And we only want to check out hitobjects which aren't so far in the past
            {
                Addition += ColorChangeAddition(PreviousHitObject);
                Addition += RhythmChangeAddition(PreviousHitObject);
            }

            double AdditionFactor = 1.0;

            // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50
            if (TimeElapsed < 50.0)
            {
                AdditionFactor = 0.4 + 0.6 * TimeElapsed / 50.0;
            }

            Strain = PreviousHitObject.Strain * Decay + Addition * AdditionFactor;
        }
        protected override void UpdateHitObject(HitObject currHitObject)
        {
            SliderOsu slider = currHitObject as SliderOsu;

            if (slider != null)
            {
                if (AudioEngine.Time >= slider.StartTime && AudioEngine.Time <= slider.EndTime)
                {
                    slider.InitSlide();
                    sliderActive = true;
                }
            }

            if (AudioEngine.AudioState == AudioStates.Playing && AudioEngine.FrameAverage < 300)
            {
                if ((currHitObject.StartTime <= AudioEngine.Time &&
                     currHitObject.StartTime >= AudioEngine.TimeLastFrame &&
                     !currHitObject.IsType(HitObjectType.Spinner))
                    ||
                    (currHitObject.EndTime <= AudioEngine.Time &&
                     currHitObject.EndTime >= AudioEngine.TimeLastFrame))
                {
                    if (!currHitObject.Sounded)
                    {
                        currHitObject.PlaySound();
                        currHitObject.Sounded = true;
                    }
                }
                else if (currHitObject.Sounded &&
                         Math.Abs(currHitObject.StartTime - AudioEngine.Time) > AudioEngine.FrameAverage * 4)
                {
                    currHitObject.Sounded = false;
                }
            }

            base.UpdateHitObject(currHitObject);
        }
        /// <summary>
        /// Calculates the hp drop rate via some insane looping through the map.
        /// Oh god.
        /// </summary>
        /// <returns></returns>
        internal double CalculateHpDropRate()
        {
            double testDrop = 0.05;

            double lowestHpEver =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 195, 160, 60);
            double lowestHpComboEnd =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 170, 80);
            double lowestHpEnd =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 198, 180, 80);
            double HpRecoveryAvailable =
                hitObjectManager.MapDifficultyRange(BeatmapManager.Current.DifficultyHpDrainRate, 8, 4, 0);

            do
            {
                TotalHitsPossible = 0;

                HpBar.Reset();

                ComboCounter.HitCombo          = 0;
                Player.currentScore.TotalScore = 0;

                double lowestHp = HpBar.CurrentHp;
                int    lastTime = hitObjectManager.hitObjects[0].StartTime - hitObjectManager.PreEmpt;
                bool   fail     = false;

                int comboTooLowCount = 0;


                int breakCount  = player.eventManager.eventBreaks.Count;
                int breakNumber = 0;

                for (int i = 0; i < hitObjectManager.hitObjectsCount; i++)
                {
                    HitObject h = hitObjectManager.hitObjects[i];

                    //Find active break (between current and lastTime)
                    int localLastTime = lastTime;

                    int breakTime = 0;
                    if (breakCount > 0 && breakNumber < breakCount)
                    {
                        Event e = player.eventManager.eventBreaks[breakNumber];
                        if (e.StartTime >= localLastTime && e.EndTime <= h.StartTime)
                        {
                            breakTime = e.Length;
                            breakNumber++;
                        }
                    }

                    HpBar.ReduceCurrentHp(testDrop * (h.StartTime - lastTime - breakTime));

                    lastTime = h.EndTime;


                    if (HpBar.CurrentHp < lowestHp)
                    {
                        lowestHp = HpBar.CurrentHp;
                    }

                    if (HpBar.CurrentHp <= lowestHpEver)
                    {
                        //Debug.Print("Overall score drops below " + lowestHpEver + " at " + lastTime + " (" + testDrop + ", lowest " + lowestHp + ")");
                        fail      = true;
                        testDrop *= 0.96;
                        break;
                    }

                    if (h is HitCircleFruitsTick)
                    {
                        IncreaseScoreHit(IncreaseScoreType.FruitTick, h);
                    }
                    else if (h is HitCircleFruitsTickTiny)
                    {
                        IncreaseScoreHit(IncreaseScoreType.FruitTickTiny, h);
                    }
                    else if (h is HitCircleFruitsSpin)
                    {
                        HpBar.IncreaseCurrentHp(HpMultiplierNormal / 2);
                    }
                    else
                    {
                        if (i == hitObjectManager.hitObjectsCount - 1 ||
                            hitObjectManager.hitObjects[i + 1].NewCombo)
                        {
                            IncreaseScoreHit(IncreaseScoreType.Hit300g, h);
                            if (HpBar.CurrentHp < lowestHpComboEnd)
                            {
                                if (++comboTooLowCount > 2)
                                {
                                    HpMultiplierComboEnd *= 1.07;
                                    HpMultiplierNormal   *= 1.03;
                                    fail = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            IncreaseScoreHit(IncreaseScoreType.Hit300, h);
                        }
                    }

                    if (!(h is HitCircleFruitsTickTiny) && !(h is HitCircleFruitsSpin))
                    {
                        TotalHitsPossible++;
                    }

                    h.MaxHp = HpBar.CurrentHp;
                }

                if (!fail && HpBar.CurrentHp < lowestHpEnd)
                {
                    //Debug.Print("Song ends on " + currentHp + " - being more lenient");
                    fail                  = true;
                    testDrop             *= 0.94;
                    HpMultiplierComboEnd *= 1.01;
                    HpMultiplierNormal   *= 1.01;
                }

                double recovery = (HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / hitObjectManager.hitObjectsCount;
                if (!fail && recovery < HpRecoveryAvailable)
                {
                    //Debug.Print("Song has average " + recovery + " recovery - being more lenient");
                    fail                  = true;
                    testDrop             *= 0.96;
                    HpMultiplierComboEnd *= 1.02;
                    HpMultiplierNormal   *= 1.01;
                }

                if (fail)
                {
                    continue;
                }

                if (GameBase.TestMode)
                {
                    PlayerTest pt = player as PlayerTest;
                    if (pt != null)
                    {
                        pt.testMaxScore = Player.currentScore.TotalScore;
                        pt.testMaxCombo = ComboCounter.HitCombo;
                    }
                }

                Debug.Print("Loaded Beatmap " + BeatmapManager.Current.Filename);
                Debug.Print(string.Empty);
                Debug.Print("           stars: " + BeatmapManager.Current.DifficultyEyupStars);
                Debug.Print("           stars: " + BeatmapManager.Current.DifficultyEchoStars);
                Debug.Print("     slider rate: " + BeatmapManager.Current.DifficultySliderMultiplier);
                Debug.Print(" playable length: " + BeatmapManager.Current.DrainLength);
                Debug.Print("      hitobjects: " + BeatmapManager.Current.ObjectCount);
                Debug.Print("      hitcircles: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Normal)).Count);
                Debug.Print("         sliders: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Slider)).Count);
                Debug.Print("        spinners: " +
                            hitObjectManager.hitObjects.FindAll(h => h.IsType(HitObjectType.Spinner)).Count);
                Debug.Print("      drain rate: {0:00.00}%/s", (testDrop * 60) / 2);
                Debug.Print("       lowest hp: {0:00.00}%", lowestHp / 2);
                Debug.Print("normal multiplier: {0:00.00}x", HpMultiplierNormal);
                Debug.Print("combo multiplier: {0:00.00}x", HpMultiplierComboEnd);
                Debug.Print(" excess hp recov: {0:00.00}%/hitobject",
                            (float)(HpBar.CurrentHpUncapped - HP_BAR_MAXIMUM) / 2 / hitObjectManager.hitObjects.Count);
                Debug.Print("    max final hp: {0:00.00}%", HpBar.CurrentHp / 2);
                Debug.Print("       max combo: " + TotalHitsPossible);
                Debug.Print(string.Empty);

                return(testDrop);
            } while (true);
        }
        /// <summary>
        /// Handle splitting up and labelling the hitObjects into different player's scopes.
        /// </summary>
        internal override void InitializeHitObjectPostProcessing()
        {
            bMatch match = PlayerVs.Match;

            usedPlayerSlots = new List <int>();

            int playerCount = 0;

            for (int i = 0; i < bMatch.MAX_PLAYERS; i++)
            {
                if ((match.slotStatus[i] & SlotStatus.Playing) > 0 &&
                    match.slotTeam[i] == match.slotTeam[player.localPlayerMatchId])
                {
                    usedPlayerSlots.Add(i);
                    playerCount++;
                }
            }

            HitObjectManager hitObjectManager = player.hitObjectManager;

            int currentPlayer = -1;

            localUserActiveTime = new List <EventBreak>();

            EventBreak currentBreak = null;

            bool firstCombo = true;
            bool customTagColor;

            if (customTagColor = MatchSetup.TagComboColour != Color.TransparentWhite)
            {
                GameBase.Scheduler.Add(delegate
                {
                    SkinManager.SliderRenderer.Tag = MatchSetup.TagComboColour;
                });
            }

            for (int i = 0; i < hitObjectManager.hitObjectsCount; i++)
            {
                HitObject h = hitObjectManager.hitObjects[i];
                //HitObject hLast = i > 0 ? hitObjectManager.hitObjects[i-1] : hitObjectManager.hitObjects[i];

                bool firstInCombo = h.NewCombo || firstCombo;

                bool spinner = h.IsType(HitObjectType.Spinner) || h is HitCircleFruitsSpin;

                if (firstInCombo)
                {
                    if (!spinner)
                    {
                        currentPlayer = (currentPlayer + 1) % playerCount;
                        firstCombo    = false;
                    }

                    if (spinner || usedPlayerSlots[currentPlayer] == player.localPlayerMatchId)
                    {
                        //The local player starts playing at this point.
                        int st = (i > 0
                                      ? Math.Max(h.StartTime - hitObjectManager.HitWindow50,
                                                 hitObjectManager.hitObjects[i - 1].EndTime + 1)
                                      : h.StartTime - hitObjectManager.HitWindow50);
                        int ed = h.EndTime;
                        currentBreak = new EventBreak(st, ed);
                        localUserActiveTime.Add(currentBreak);
                    }
                    else
                    {
                        //Another play has taken over.
                        currentBreak = null;
                    }
                }


                if (spinner || usedPlayerSlots[currentPlayer] == player.localPlayerMatchId)
                {
                    if (currentBreak != null)
                    {
                        //The local player finishes playing at this point (or further).
                        currentBreak.SetEndTime(h.EndTime + hitObjectManager.HitWindow50);
                    }

                    if (customTagColor)
                    {
                        h.SetColour(MatchSetup.TagComboColour);
                    }
                }
                else
                {
                    h.IsScorable = false;
                    h.SetColour(Color.Gray);
                }

                h.TagNumeric = spinner ? -2 : usedPlayerSlots[currentPlayer];
            }

            InitializeWarningArrows();
        }
Example #6
0
        internal DifficultyHitObjectOsu(HitObject BaseHitObject, float CircleRadius)
        {
            this.BaseHitObject = BaseHitObject;

            if (BaseHitObject.IsType(HitObjectType.Slider))
            {
                SliderOsu Slider = (SliderOsu)BaseHitObject;
                MaxCombo = 1 + Slider.sliderScoreTimingPoints.Count;
            }
            else
            {
                MaxCombo = 1;
            }

            // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
            float ScalingFactor = (52.0f / CircleRadius);

            if (CircleRadius < 30)
            {
                float smallCircleBonus = Math.Min(30.0f - CircleRadius, 5.0f) / 50.0f;
                ScalingFactor *= 1.0f + smallCircleBonus;
            }

            NormalizedStartPosition = BaseHitObject.Position * ScalingFactor;

            // Calculate approximation of lazy movement on the slider
            if (BaseHitObject.IsType(HitObjectType.Slider))
            {
                float SliderFollowCircleRadius = CircleRadius * 3;                                       // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests.

                int SegmentLength  = Math.Min(BaseHitObject.Length / BaseHitObject.SegmentCount, 60000); // We don't want infinite loops if someone decides to make a too long slider. (MillhioreF, I am talking about you! https://osu.ppy.sh/b/326585)
                int SegmentEndTime = BaseHitObject.StartTime + SegmentLength;

                // For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later
                Vector2 CursorPos = BaseHitObject.Position; //

                //Debug.Print("" + (BaseHitObject.StartTime + LAZY_SLIDER_STEP_LENGTH) + " " + SegmentEndTime + " " + BaseHitObject.EndTime + " " + BaseHitObject.SegmentCount);

                // Actual computation of the first lazy curve
                for (int Time = BaseHitObject.StartTime + LAZY_SLIDER_STEP_LENGTH; Time < SegmentEndTime; Time += LAZY_SLIDER_STEP_LENGTH)
                {
                    Vector2 Difference = BaseHitObject.PositionAtTime(Time) - CursorPos;
                    float   Distance   = Difference.Length();

                    // Did we move away too far?
                    if (Distance > SliderFollowCircleRadius)
                    {
                        // Yep, we need to move the cursor
                        Difference.Normalize();                         // Obtain the direction of difference. We do no longer need the actual difference
                        Distance              -= SliderFollowCircleRadius;
                        CursorPos             += Difference * Distance; // We move the cursor just as far as needed to stay in the follow circle
                        LazySliderLengthFirst += Distance;
                    }
                }

                LazySliderLengthFirst *= ScalingFactor;
                // If we have an odd amount of repetitions the current position will be the end of the slider. Note that this will -always- be triggered if
                // BaseHitObject.SegmentCount <= 1, because BaseHitObject.SegmentCount can not be smaller than 1. Therefore NormalizedEndPosition will always be initialized
                if (BaseHitObject.SegmentCount % 2 == 1)
                {
                    NormalizedEndPosition = CursorPos * ScalingFactor;
                }

                // If we have more than one segment, then we also need to compute the length ob subsequent lazy curves. They are different from the first one, since the first
                // one starts right at the beginning of the slider.
                if (BaseHitObject.SegmentCount > 1)
                {
                    // Use the next segment
                    SegmentEndTime += SegmentLength;

                    for (int Time = SegmentEndTime - SegmentLength + LAZY_SLIDER_STEP_LENGTH; Time < SegmentEndTime; Time += LAZY_SLIDER_STEP_LENGTH)
                    {
                        Vector2 Difference = BaseHitObject.PositionAtTime(Time) - CursorPos;
                        float   Distance   = Difference.Length();

                        // Did we move away too far?
                        if (Distance > SliderFollowCircleRadius)
                        {
                            // Yep, we need to move the cursor
                            Difference.Normalize();             // Obtain the direction of difference. We do no longer need the actual difference
                            Distance  -= SliderFollowCircleRadius;
                            CursorPos += Difference * Distance; // We move the cursor just as far as needed to stay in the follow circle
                            LazySliderLengthSubsequent += Distance;
                        }
                    }

                    LazySliderLengthSubsequent *= ScalingFactor;
                    // If we have an even amount of repetitions the current position will be the end of the slider
                    if (BaseHitObject.SegmentCount % 2 == 0)
                    {
                        NormalizedEndPosition = CursorPos * ScalingFactor;
                    }
                }
            }
            // We have a normal HitCircle or a spinner
            else
            {
                NormalizedEndPosition = NormalizedStartPosition;
            }
        }