Esempio n. 1
0
        internal override IncreaseScoreType GetScorePoints(Vector2 currentMousePos)
        {
            if (!InputManager.ScorableFrame)
            {
                return(0);
            }

            //First update the spinner velocity...
            Vector2 mouseVector    = currentMousePos - SpriteCircleTop.drawPosition;
            double  mouseAngle     = Math.Atan2(mouseVector.Y, mouseVector.X);
            double  mouseAngleDiff = mouseAngle - lastMouseAngle;

            if (mouseAngle - lastMouseAngle < -Math.PI)
            {
                mouseAngleDiff = (2 * Math.PI) + mouseAngle - lastMouseAngle;
            }
            else if (lastMouseAngle - mouseAngle < -Math.PI)
            {
                mouseAngleDiff = (-2 * Math.PI) - lastMouseAngle + mouseAngle;
            }

            double timeDiff = InputManager.LastScorableFrameTime > 0 ? AudioEngine.Time - InputManager.LastScorableFrameTime : GameBase.SIXTY_FRAME_TIME;

            double decay = Math.Pow(0.999, timeDiff);

            totalScoreFrameVariance = decay * totalScoreFrameVariance + (1 - decay) * timeDiff;

            if (mouseAngleDiff == 0)
            {
                velocityTheoretical = zeroCount++ < 1 ? velocityTheoretical / 3 : 0;
            }
            else
            {
                zeroCount = 0;

                if (!Player.Relaxing && ((InputManager.leftButton == ButtonState.Released && InputManager.rightButton == ButtonState.Released) || AudioEngine.Time < StartTime || AudioEngine.Time > EndTime))
                {
                    mouseAngleDiff = 0;
                }

                if (Math.Abs(mouseAngleDiff) < Math.PI)
                {
                    if (HitObjectManager.ApplyModsToTime(totalScoreFrameVariance, hitObjectManager.ActiveMods) > GameBase.SIXTY_FRAME_TIME * 1.04f)
                    {
                        //after a certain lenience we need to stop allowing for SIXTY_FRAMEs and take frames for their actual elapsed time.
                        //this is to handle the case where users are running at sub-60fps.
                        //in a simple world, we could always use this timeDiff calculation, but due to historical reasons,
                        //we were always slightly in the user's favour when calculating velocity here.
                        velocityTheoretical = mouseAngleDiff / HitObjectManager.ApplyModsToTime(timeDiff, hitObjectManager.ActiveMods);
                    }
                    else
                    {
                        velocityTheoretical = mouseAngleDiff / GameBase.SIXTY_FRAME_TIME;
                    }
                }
                else
                {
                    velocityTheoretical = 0;
                }
            }

            lastMouseAngle = mouseAngle;

            //If we have actually progressed, let's return some score...
            if (rotationCount == lastRotationCount)
            {
                return(IncreaseScoreType.Ignore);
            }

            scoringRotationCount++;

            IncreaseScoreType score = IncreaseScoreType.Ignore;

            if (SkinManager.Current.SpinnerFrequencyModulate)
            {
                AudioEngine.UpdateSpinSample((float)scoringRotationCount / rotationRequirement);
            }

            if (scoringRotationCount > rotationRequirement + 3 && (scoringRotationCount - (rotationRequirement + 3)) % 2 == 0)
            {
                if (spriteGlow != null)
                {
                    spriteGlow.FlashColour(Color.White, 200);
                }

                score = IncreaseScoreType.SpinnerBonus;
                if (!ModManager.CheckActive(Mods.Cinema))
                {
                    AudioEngine.PlaySample(@"spinnerbonus", AudioEngine.VolumeSample, SkinSource.All);
                }
                SpriteBonusCounter.Text = (1000 * (scoringRotationCount - (rotationRequirement + 3)) / 2).ToString();
                SpriteBonusCounter.Transformations.Clear();
                SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Fade, 1, 0, AudioEngine.Time, AudioEngine.Time + 800));
                SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Scale, 2f, 1.28f, AudioEngine.Time, AudioEngine.Time + 800));
                SpriteBonusCounter.Transformations[0].Easing = EasingTypes.Out;
                SpriteBonusCounter.Transformations[1].Easing = EasingTypes.Out;
                //Ensure we don't recycle this too early.
                SpriteBonusCounter.Transformations.Add(new Transformation(TransformationType.Fade, 0, 0, EndTime + 800, EndTime + 800));
            }
            else if (scoringRotationCount > 1 && scoringRotationCount % 2 == 0)
            {
                score = IncreaseScoreType.SpinnerSpinPoints;
            }
            else if (scoringRotationCount > 1)
            {
                score = IncreaseScoreType.SpinnerSpin;
            }

            lastRotationCount = rotationCount;
            return(score);
        }
Esempio n. 2
0
        internal override void CreateAutoplayReplay()
        {
            int buttonIndex = 0;

            bool        delayedMovements = ModManager.CheckActive(Mods.Relax2);
            EasingTypes preferredEasing  = delayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;

            InputManager.ReplayScore.Replay = new List <bReplayFrame>();
            List <bReplayFrame> replay = InputManager.ReplayScore.Replay;

            AddFrameToReplay(replay, new bReplayFrame(-100000, 256, 500, pButtonState.None));
            AddFrameToReplay(replay, new bReplayFrame(hitObjectManager.hitObjects[0].StartTime - 1500, 256, 500, pButtonState.None));
            AddFrameToReplay(replay, new bReplayFrame(hitObjectManager.hitObjects[0].StartTime - 1000, 256, 192, pButtonState.None));

            // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps.
            float       frameDelay    = (float)HitObjectManager.ApplyModsToRate(1000.0 / 60.0);
            Vector2     spinnerCentre = new Vector2(256, 192);
            const float spinnerRadius = 50;

            // Already superhuman, but still somewhat realistic
            int reactionTime = (int)HitObjectManager.ApplyModsToRate(100);


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

                if (h.EndTime < InputManager.ReplayStartTime)
                {
                    h.IsHit = true;
                    continue;
                }

                int endDelay = h is SpinnerOsu ? 1 : 0;

                if (delayedMovements && i > 0)
                {
                    HitObject last = hitObjectManager.hitObjects[i - 1];

                    //Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
                    if (h.StartTime - HitObjectManager.HITTABLE_RANGE > last.EndTime + hitObjectManager.HitWindow50 + 50)
                    {
                        if (!(last is Spinner) && h.StartTime - last.EndTime < 1000)
                        {
                            AddFrameToReplay(replay, new bReplayFrame(last.EndTime + hitObjectManager.HitWindow50, last.EndPosition.X, last.EndPosition.Y, pButtonState.None));
                        }
                        if (!(h is Spinner))
                        {
                            AddFrameToReplay(replay, new bReplayFrame(h.StartTime - HitObjectManager.HITTABLE_RANGE, h.Position.X, h.Position.Y, pButtonState.None));
                        }
                    }
                    else if (h.StartTime - hitObjectManager.HitWindow50 > last.EndTime + hitObjectManager.HitWindow50 + 50)
                    {
                        if (!(last is Spinner) && h.StartTime - last.EndTime < 1000)
                        {
                            AddFrameToReplay(replay, new bReplayFrame(last.EndTime + hitObjectManager.HitWindow50, last.EndPosition.X, last.EndPosition.Y, pButtonState.None));
                        }
                        if (!(h is Spinner))
                        {
                            AddFrameToReplay(replay, new bReplayFrame(h.StartTime - hitObjectManager.HitWindow50, h.Position.X, h.Position.Y, pButtonState.None));
                        }
                    }
                    else if (h.StartTime - hitObjectManager.HitWindow100 > last.EndTime + hitObjectManager.HitWindow100 + 50)
                    {
                        if (!(last is Spinner) && h.StartTime - last.EndTime < 1000)
                        {
                            AddFrameToReplay(replay, new bReplayFrame(last.EndTime + hitObjectManager.HitWindow100, last.EndPosition.X, last.EndPosition.Y, pButtonState.None));
                        }
                        if (!(h is Spinner))
                        {
                            AddFrameToReplay(replay, new bReplayFrame(h.StartTime - hitObjectManager.HitWindow100, h.Position.X, h.Position.Y, pButtonState.None));
                        }
                    }
                }


                Vector2     targetPosition   = h.Position;
                EasingTypes easing           = preferredEasing;
                float       spinnerDirection = -1;

                if (h is Spinner)
                {
                    targetPosition.X = replay[replay.Count - 1].mouseX;
                    targetPosition.Y = replay[replay.Count - 1].mouseY;

                    Vector2 difference = spinnerCentre - targetPosition;

                    float differenceLength = difference.Length();
                    float newLength        = (float)Math.Sqrt(differenceLength * differenceLength - spinnerRadius * spinnerRadius);

                    if (differenceLength > spinnerRadius)
                    {
                        float angle = (float)Math.Asin(spinnerRadius / differenceLength);

                        if (angle > 0)
                        {
                            spinnerDirection = -1;
                        }
                        else
                        {
                            spinnerDirection = 1;
                        }

                        difference.X = difference.X * (float)Math.Cos(angle) - difference.Y * (float)Math.Sin(angle);
                        difference.Y = difference.X * (float)Math.Sin(angle) + difference.Y * (float)Math.Cos(angle);

                        difference.Normalize();
                        difference *= newLength;

                        targetPosition += difference;

                        easing = EasingTypes.In;
                    }
                    else if (difference.Length() > 0)
                    {
                        targetPosition = spinnerCentre - difference * (spinnerRadius / difference.Length());
                    }
                    else
                    {
                        targetPosition = spinnerCentre + new Vector2(0, -spinnerRadius);
                    }
                }


                // Do some nice easing for cursor movements
                if (replay.Count > 0)
                {
                    bReplayFrame lastFrame = replay[replay.Count - 1];

                    // Wait until Auto could "see and react" to the next note.
                    int waitTime = h.StartTime - (int)Math.Max(0.0, hitObjectManager.PreEmpt - reactionTime);
                    if (waitTime > lastFrame.time)
                    {
                        lastFrame = new bReplayFrame(waitTime, lastFrame.mouseX, lastFrame.mouseY, lastFrame.buttonState);
                        AddFrameToReplay(replay, lastFrame);
                    }

                    Vector2 lastPosition = new Vector2(lastFrame.mouseX, lastFrame.mouseY);

                    HitObjectManagerOsu hom = hitObjectManager as HitObjectManagerOsu;
                    double timeDifference   = HitObjectManager.ApplyModsToTime(h.StartTime - lastFrame.time, ModManager.ModStatus);

                    // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
                    if (hom != null && timeDifference > 0 &&                                                                // Sanity checks
                        ((lastPosition - targetPosition).Length() > hom.HitObjectRadius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
                         timeDifference >= 266))                                                                            // ... or the beats are slow enough to tap anyway.
                    {
                        // Perform eased movement
                        for (float time = lastFrame.time + frameDelay; time < h.StartTime; time += frameDelay)
                        {
                            Vector2 currentPosition = OsuMathHelper.TweenValues(lastPosition, targetPosition, time, lastFrame.time, h.StartTime, easing);
                            AddFrameToReplay(replay, new bReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.buttonState));
                        }

                        buttonIndex = 0;
                    }
                    else
                    {
                        buttonIndex++;
                    }
                }

                pButtonState button         = buttonIndex % 2 == 0 ? pButtonState.Left1 : pButtonState.Right1;
                pButtonState previousButton = pButtonState.None;

                bReplayFrame newFrame = new bReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button);
                bReplayFrame endFrame = new bReplayFrame(h.EndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, pButtonState.None);

                // Decrement because we want the previous frame, not the next one
                int index = FindInsertionIndex(replay, newFrame) - 1;

                // Do we have a previous frame? No need to check for < replay.Count since we decremented!
                if (index >= 0)
                {
                    bReplayFrame previousFrame = replay[index];
                    previousButton = previousFrame.buttonState;

                    // If a button is already held, then we simply alternate
                    if (previousButton != pButtonState.None)
                    {
                        Debug.Assert(previousButton != (pButtonState.Left1 | pButtonState.Right1));

                        // Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
                        if (previousButton == button)
                        {
                            button = (pButtonState.Left1 | pButtonState.Right1) & ~button;
                            newFrame.SetButtonStates(button);
                        }

                        // We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
                        int endIndex = FindInsertionIndex(replay, endFrame);

                        if (index < replay.Count - 1)
                        {
                            replay.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1)));
                        }

                        // After alternating we need to keep holding the other button in the future rather than the previous one.
                        for (int j = index + 1; j < replay.Count; ++j)
                        {
                            // Don't affect frames which stop pressing a button!
                            if (j < replay.Count - 1 || replay[j].buttonState == previousButton)
                            {
                                replay[j].SetButtonStates(button);
                            }
                        }
                    }
                }

                AddFrameToReplay(replay, newFrame);

                // We add intermediate frames for spinning / following a slider here.
                if (h is SpinnerOsu)
                {
                    Vector2 difference = targetPosition - spinnerCentre;

                    float radius = difference.Length();
                    float angle  = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);

                    float t;

                    for (float j = h.StartTime + frameDelay; j < h.EndTime; j += frameDelay)
                    {
                        t = (float)HitObjectManager.ApplyModsToTime(j - h.StartTime) * spinnerDirection;

                        Vector2 pos = spinnerCentre + CirclePosition(t / 20 + angle, spinnerRadius);
                        AddFrameToReplay(replay, new bReplayFrame((int)j, pos.X, pos.Y, button));
                    }

                    t = (float)HitObjectManager.ApplyModsToTime(h.EndTime - h.StartTime) * spinnerDirection;
                    Vector2 endPosition = spinnerCentre + CirclePosition(t / 20 + angle, spinnerRadius);

                    AddFrameToReplay(replay, new bReplayFrame(h.EndTime, endPosition.X, endPosition.Y, button));

                    endFrame.mouseX = endPosition.X;
                    endFrame.mouseY = endPosition.Y;
                }
                else if (h is SliderOsu)
                {
                    SliderOsu s        = h as SliderOsu;
                    int       lastTime = 0;

                    foreach (
                        Transformation t in
                        s.sliderFollower.Transformations.FindAll(
                            tr => tr.Type == TransformationType.Movement))
                    {
                        if (lastTime != 0 && t.Time1 - lastTime < frameDelay)
                        {
                            continue;
                        }

                        AddFrameToReplay(replay, new bReplayFrame(t.Time1, t.StartVector.X, t.StartVector.Y,
                                                                  button));
                        lastTime = t.Time1;
                    }

                    AddFrameToReplay(replay, new bReplayFrame(h.EndTime, h.EndPosition.X, h.EndPosition.Y, button));
                }

                // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
                if (replay[replay.Count - 1].time <= endFrame.time)
                {
                    AddFrameToReplay(replay, endFrame);
                }
            }

            Player.currentScore.Replay     = InputManager.ReplayScore.Replay;
            Player.currentScore.PlayerName = "osu!";
        }
Esempio n. 3
0
        internal override void Update()
        {
            if (AudioEngine.Time < EndTime && IsHit)
            {
                Disarm();
            }

            if (IsHit || AudioEngine.Time < StartTime)
            {
                return;
            }

            double decay = Math.Pow(0.9, InputManager.ElapsedAudioTime / GameBase.SIXTY_FRAME_TIME);

            rpm = rpm * decay + (1.0 - decay) * (Math.Abs(velocityCurrent) * 1000) / (Math.PI * 2) * 60;

            if (spriteRpmText != null)
            {
                spriteRpmText.Text = string.Format(@"{0:#,0}", rpm);
            }

            Player.Instance?.LogSpinSpeed(rpm);

            if (spriteMiddleTop != null)
            {
                spriteMiddleTop.InitialColour = ColourHelper.ColourLerp(Color.White, Color.Red, (float)(AudioEngine.Time - StartTime) / Length);
            }

            if (GameBase.Mode == OsuModes.Edit)
            {
                editorCircleRotation.EndFloat = (float)(rotationRequirement * Math.PI);
                floatRotationCount            = (float)((AudioEngine.Time - StartTime) / 1000 * hitObjectManager.SpinnerRotationRatio);
            }
            else if (AudioEngine.Time < EndTime && AudioEngine.Time > StartTime && !Player.Recovering)
            {
                // Mod time is applied here to keep discrepancies between DT, HT and nomod to preserve integrity of older scores. :(
                double maxAccelThisFrame = HitObjectManager.ApplyModsToTime(maxAccel * InputManager.ElapsedAudioTime, hitObjectManager.ActiveMods);

                if ((GameBase.Mode == OsuModes.Play && ModManager.CheckActive(hitObjectManager.ActiveMods, Mods.SpunOut)) || Player.Relaxing2)
                {
                    velocityCurrent = 0.03;
                }
                else if (velocityTheoretical > velocityCurrent)
                {
                    velocityCurrent += Math.Min(velocityTheoretical - velocityCurrent, velocityCurrent < 0 && Player.Relaxing ? maxAccelThisFrame / RELAX_BONUS_ACCEL : maxAccelThisFrame);
                }
                else
                {
                    velocityCurrent += Math.Max(velocityTheoretical - velocityCurrent, velocityCurrent > 0 && Player.Relaxing ? -maxAccelThisFrame / RELAX_BONUS_ACCEL : -maxAccelThisFrame);
                }


                velocityCurrent = Math.Max(-0.05, Math.Min(velocityCurrent, 0.05));

                float rotationAddition = (float)(velocityCurrent * InputManager.ElapsedAudioTime);
                float turnRatio        = spriteMiddleBottom != null && spriteMiddleBottom.Texture != null ? 0.5f : 1;

                // We don't want the spinner sprite to spin faster / slower when DT / HT are active. It should always spin proportionally to the cursor spinning rate.
                SpriteCircleTop.Rotation += (float)HitObjectManager.ApplyModsToTime(rotationAddition * turnRatio, hitObjectManager.ActiveMods);
                if (spriteMiddleBottom != null)
                {
                    spriteMiddleBottom.Rotation += (float)HitObjectManager.ApplyModsToTime(rotationAddition, hitObjectManager.ActiveMods);
                    spriteCircleBottom.Rotation  = SpriteCircleTop.Rotation / 3f;
                }

                if (velocityCurrent != 0)
                {
                    StartSound();
                }
                else
                {
                    StopSound();
                }

                floatRotationCount += Math.Abs((float)(rotationAddition / Math.PI));
            }

            updateCompletion(Math.Abs(floatRotationCount) / rotationRequirement * 100);

            switch (state)
            {
            case SpinningState.NotStarted:
                if (scoringRotationCount == 0 || AudioEngine.Time < StartTime + 500)
                {
                    break;
                }

                spriteSpin?.FadeOut(300);
                state = SpinningState.Started;
                break;

            case SpinningState.Started:
                if (scoringRotationCount < rotationRequirement)
                {
                    break;
                }

                if (spriteGlow != null)
                {
                    spriteGlow.InitialColour = new Color(3, 151, 255);
                }

                if (spriteSpin != null)
                {
                    spriteSpin.FadeOut(100);
                    spriteClear.Transformations.Clear();
                    spriteClear.Transformations.Add(new Transformation(TransformationType.Fade, 0, 1, AudioEngine.Time, Math.Min(EndTime, AudioEngine.Time + 400), EasingTypes.Out));
                    spriteClear.Transformations.Add(new Transformation(TransformationType.Scale, 2, 0.8f, AudioEngine.Time, Math.Min(EndTime, AudioEngine.Time + 240), EasingTypes.Out));
                    spriteClear.Transformations.Add(new Transformation(TransformationType.Scale, 0.8f, 1, Math.Min(EndTime, AudioEngine.Time + 240), Math.Min(EndTime, AudioEngine.Time + 400), EasingTypes.None));
                    spriteClear.Transformations.Add(new Transformation(TransformationType.Fade, 1, 0, EndTime - 50, EndTime));
                }

                state = SpinningState.Passed;
                break;
            }
        }