internal IncreaseScoreType HitStart(HitCircleManiaLong h)
        {
            lastHitObject = h;
            IncreaseScoreType hitValue = h.HitStart();

            return(hitValue);
        }
        internal IncreaseScoreType Holding(HitCircleManiaLong h)
        {
            lastHitObject = h;
            IncreaseScoreType hitValue = h.Holding();

            if (hitValue > IncreaseScoreType.Ignore)
            {
                OnHit(hitValue, "", h);
            }
            return(hitValue);
        }
        internal void FreezeNote(HitCircleManiaLong note)
        {
            //Remove the head sprites's after-EndTime transformations. These will be recalculated if an Unfreeze happens
            note.s_note.Transformations.RemoveAll(t => t.Type == TransformationType.MovementY && t.TagNumeric == TransformationTag.AfterEndTime);

            //Add a dummy transformation to keep the note alive for as long as it will be needed
            float freezePos  = ScaleFlipPosition(ManiaStage.Skin.HitPosition);
            int   freezeTime = note.EndTime + HitWindow50;

            note.s_note.Transformations.AddInPlace(new Transformation(TransformationType.MovementY, freezePos, freezePos, freezeTime, freezeTime)
            {
                TagNumeric = TransformationTag.Freeze
            });

            //Clip the note's other sprites above the head sprite
            note.s_rear.ClipRectangle = note.s_body.ClipRectangle = note.Column.Host.ScaleFlipRectangleFromTB(0, ManiaStage.Skin.HitPosition - SpriteHeight(note.s_note) / 2);

            note.IsFrozen = true;
        }
        internal void UnfreezeNote(HitCircleManiaLong note)
        {
            if (!note.IsFrozen)
            {
                return;
            }

            //Remove the dummy transformation we added
            note.s_note.Transformations.RemoveAll(t => t.Type == TransformationType.MovementY && t.TagNumeric == TransformationTag.Freeze);

            //Unfreeze the head sprite by adding back its forwards transformations
            int time = note.IsUnfrozen ? note.UnfreezeTime : Math.Max(note.StartTime, AudioEngine.Time);

            addForwardsTransformations(note.s_note, time, findOffsetIndex(time, speedChanges), SpriteHeight(note.s_note));
            note.s_note.Transformations.Sort();

            //Move the clip rectangle of the body and rear sprites down at the same speed of the new forwards transformations
            RectangleF rect = (RectangleF)note.s_body.ClipRectangle;

            foreach (Transformation t in note.s_note.Transformations)
            {
                if (t.Type == TransformationType.MovementY && t.TagNumeric == TransformationTag.AfterEndTime)
                {
                    float          difference         = t.EndFloat - t.StartFloat;
                    RectangleF     endRect            = new RectangleF(rect.X, rect.Y + Math.Min(difference, 0), rect.Width, rect.Height + Math.Abs(difference));
                    Transformation clipTransformation = new Transformation(rect, endRect, t.Time1, t.Time2, EasingTypes.None);
                    note.s_body.Transformations.Add(clipTransformation);
                    note.s_rear.Transformations.Add(clipTransformation);
                    rect = endRect;
                }
            }
            note.s_body.Transformations.Sort();
            note.s_rear.Transformations.Sort();

            note.IsFrozen     = false;
            note.IsUnfrozen   = true;
            note.UnfreezeTime = time;
        }
        internal override void UpdateHitObjects()
        {
            //notes hit here.
            bool[] hitted = new bool[ManiaStage.Columns.Count];//some notes have already hitted in that column
            Player.IsSliding = false;
            foreach (HitObject h in hitObjectsMinimal)
            {
                HitCircleMania curr = (HitCircleMania)h;

                curr.Update();

                if (curr.IsHit)
                {
                    //note been hitted too early, and it's still above judge line.
                    if (curr.StartTime > AudioEngine.Time)
                    {
                        hitted[curr.Column] = true;
                    }
                    continue;
                }

                if (hitted[curr.Column] || AudioEngine.Time < curr.StartTime - hitWindowEarly)
                {
                    continue;
                }

                //give a 0 value hit
                if (AudioEngine.Time - HitWindow100 >= curr.EndTime && !curr.IsHit)
                {
                    Hit(curr);
                    continue;
                }

                if (curr.Column.KeyState && !curr.Column.KeyStateLast)
                {
                    //press
                    if (curr.ManiaType == ManiaNoteType.Normal || curr.ManiaType == ManiaNoteType.Special)
                    {
                        curr.Pressed           = true;
                        curr.TimePress         = AudioEngine.Time;
                        hitted[curr.Column]    = true;
                        nextSound[curr.Column] = null;
                        Player.Instance.LogHitError(curr); //mania here
                        Hit(curr);
                    }
                    else if (curr.ManiaType == ManiaNoteType.Long && !curr.Pressed)
                    {
                        HitCircleManiaLong currLong = (HitCircleManiaLong)curr;

                        Player.IsSliding = true;

                        curr.TimePress         = AudioEngine.Time;
                        hitted[curr.Column]    = true;
                        nextSound[curr.Column] = null;
                        curr.Pressed           = true;
                        currLong.TimesPressed++;

                        //Only the first press should be logged
                        if (currLong.TimesPressed <= 1)
                        {
                            Player.Instance.LogHitError(curr);
                        }
                        HitStart(currLong);
                        curr.Column.HasLongHitLight = true;
                    }
                }
                else if (curr.Column.KeyState && curr.Column.KeyStateLast)
                {
                    //holding
                    if (curr.ManiaType == ManiaNoteType.Long && curr.Pressed)
                    {
                        HitCircleManiaLong currLong = (HitCircleManiaLong)curr;

                        hitted[curr.Column]    = true;
                        nextSound[curr.Column] = null;
                        Holding(currLong);
                        Player.IsSliding            = true;
                        curr.Column.HasLongHitLight = true;
                    }
                }
                else if (!curr.Column.KeyState)
                {
                    //not pressing
                    if (curr.ManiaType == ManiaNoteType.Long)
                    {
                        HitCircleManiaLong currLong = (HitCircleManiaLong)curr;

                        if (curr.Pressed)
                        {
                            curr.Pressed     = false;
                            curr.TimeRelease = AudioEngine.Time;
                            currLong.TimesReleased++;

                            //Only the first release should be logged
                            if (currLong.TimesReleased <= 1)
                            {
                                Player.Instance.LogHitError(curr, AudioEngine.Time - curr.EndTime);
                            }

                            Hit(curr);
                        }
                        else if (!currLong.IsMissing)
                        {
                            Hit(curr);
                        }
                    }
                    curr.Column.HasLongHitLight = false;
                }
            }

            if (Player.Instance.Status != PlayerStatus.Playing)
            {
                return;
            }

            for (int i = 0; i < ManiaStage.Columns.Count; i++)
            {
                ColumnMania column = ManiaStage.Columns[i];

                //release time hax check
                if (column.KeyState && !column.KeyStateLast)
                {
                    releaseTime[i] = AudioEngine.Time;
                }
                else if (!column.KeyState && column.KeyStateLast && releaseTime[i] != 0)
                {
                    //press-release interval of normal player should be 40~70ms
                    int interval = AudioEngine.Time - releaseTime[i];
                    if (interval < 20)
                    {
                        PressHaxCount += 2;
                    }
                    else if (interval < 38)  //based on cheat replay analysis
                    {
                        PressHaxCount++;
                    }
                    if (interval < 100)
                    {
                        pressHaxChecked++; //interval less than 100ms should be count as normal hit.
                    }
                }

                //empty press
                if (hitted[i])
                {
                    continue;
                }

                if (column.KeyState && !column.KeyStateLast)
                {
                    if (nextSound[i] == null)
                    {
                        HitCircleMania h = (HitCircleMania)hitObjects.Find(obj => obj.StartTime > AudioEngine.Time && ((HitCircleMania)obj).Column == i);
                        nextSound[i] = h;
                    }
                    if (nextSound[i] != null)
                    {
                        nextSound[i].PlaySound();
                    }
                }
            }
        }
        internal void ReCalculate(int audioTime)
        {
            bool paused     = false;
            bool upsideDown = ManiaStage.Skin.UpsideDown;

            if (audioTime > LastNoteTime)
            {
                return;
            }

            if (AudioEngine.AudioState == AudioStates.Playing)
            {
                AudioEngine.TogglePause();
                paused = true;
            }

            //Clear anything from a previous ReCalculate
            if (barLines != null)
            {
                barLines.ForEach(b => b.Transformations.Clear());
                barLines.Clear();
            }
            else
            {
                barLines = new List <pSprite>();
            }

            foreach (HitObject h in hitObjects)
            {
                if (((HitCircleMania)h).IsFinished)
                {
                    continue;
                }
                h.SpriteCollection.ForEach(s => s.Transformations.RemoveAll(t => (t.Type & transformationsToClear) > 0));
            }

            //Start a fresh forward play queue
            foreach (StageMania stage in ManiaStage)
            {
                stage.SpriteManagerNotes.Clear(false);
                stage.SpriteManagerNotes.ForwardPlayOptimisedAdd = true;
            }

            //Mania-specific maps use all control points while autoconverts only use BPM changes
            List <ControlPoint> beatmapControlPoints = Beatmap.PlayMode != PlayModes.OsuMania ? Beatmap.TimingPoints : Beatmap.ControlPoints;

            if (beatmapControlPoints.Count == 0 || hitObjects.Count == 0)
            {
                return;
            }

            //For moving things on the play field at the correct speed. It is "collapsed" so you can iterate it in both directions
            speedChanges = new List <ControlPoint>();
            //For placing bar lines
            timingChanges = new List <ControlPoint>();

            double lastBeatLength = beatmapControlPoints[0].BeatLength;

            foreach (ControlPoint p in beatmapControlPoints)
            {
                ControlPoint t = new ControlPoint();
                t.TimingChange  = false; //So that we can guarantee the ordering when searching
                t.BeatLength    = p.BeatLength < 0 ? lastBeatLength * p.BpmMultiplier : p.BeatLength;
                t.TimeSignature = p.TimeSignature;
                t.Offset        = p.Offset;

                //If an SV change right after the initial BPM is before the first note, apply it from the start of time just like the initial BPM
                if (speedChanges.Count == 1 && p.BeatLength < 0 && t.Offset < FirstNoteTime)
                {
                    t.Offset = speedChanges[0].Offset;
                }

                //Collapse changes at the same offset
                if (speedChanges.Count > 0 && t.Offset <= speedChanges[speedChanges.Count - 1].Offset)
                {
                    speedChanges.RemoveAt(speedChanges.Count - 1);
                }

                //Collapse changes that leave us at the same beat length
                if (speedChanges.Count == 0 || t.BeatLength != speedChanges[speedChanges.Count - 1].BeatLength)
                {
                    speedChanges.Add(t);
                }

                //Uncollapsed timing changes
                if (p.BeatLength >= 0)
                {
                    timingChanges.Add(t);
                    lastBeatLength = t.BeatLength;
                }
            }

            //Move the first offset of the control points to before the song starts
            //Doing this after collapsing speed changes makes it so that SV changes to the opening BPM apply from the very start of the song
            double preTimeStart = beatmapControlPoints[0].Offset;

            while (preTimeStart >= 0)
            {
                preTimeStart -= beatmapControlPoints[0].BeatLength * (int)beatmapControlPoints[0].TimeSignature;
            }
            preTimeStart -= beatmapControlPoints[0].BeatLength * (int)beatmapControlPoints[0].TimeSignature;

            speedChanges[0].Offset  = preTimeStart;
            timingChanges[0].Offset = preTimeStart;

            //Add a final control point to speed changes which is useful as an endpoint
            speedChanges.Add(new ControlPoint()
            {
                Offset = Double.PositiveInfinity
            });

            //Add notes
            for (int i = 0; i < hitObjects.Count; i++)
            {
                HitCircleMania     note     = (HitCircleMania)hitObjects[i];
                HitCircleManiaLong noteLong = hitObjects[i] as HitCircleManiaLong;
                if (note.IsFinished)
                {
                    continue;
                }

                float headHeight = SpriteHeight(note.s_note);
                addTransformations(note.s_note, note.StartTime, headHeight);
                if (noteLong != null)
                {
                    float rearHeight = SpriteHeight(noteLong.s_rear);
                    addTransformations(noteLong.s_rear, note.EndTime, rearHeight);
                    addTransformations(noteLong.s_body, note.StartTime, headHeight / 2f, -headHeight / 2f, note.EndTime - note.StartTime);

                    //addTransformations very deliberately breaks up the transformations into three parts
                    //From offscreen to the StartTime -> from the StartTime to the EndTime -> from the end time to offscreen
                    //Using these StartTime and EndTime endpoints, we can find the distance between the head and rear sprites
                    float start = 0, end = 0;
                    if (noteLong.Length > 0)
                    {
                        start = noteLong.s_body.Transformations.Find(t => t.Type == TransformationType.MovementY && t.TagNumeric == TransformationTag.BetweenStartEndTime).StartFloat;
                        end   = noteLong.s_body.Transformations.FindLast(t => t.Type == TransformationType.MovementY && t.TagNumeric == TransformationTag.BetweenStartEndTime).EndFloat;
                    }

                    float length = Math.Abs(end - start) * 1.6f;

                    noteLong.s_body.VectorScale.Y = noteLong.s_body.FlipVertical ? -1f : 1f;

                    if (noteLong.s_body.DrawDimensionsManualOverride)
                    {
                        noteLong.s_body.WrapTexture = false;
                        noteLong.s_body.DrawDimensionsManualOverride = false;
                        noteLong.s_body.UpdateTextureSize();
                    }

                    ManiaNoteBodyStyle style = ManiaStage.Skin.GetNoteBodyStyle(note.Column);
                    if (style == ManiaNoteBodyStyle.Stretch)
                    {
                        noteLong.s_body.VectorScale.Y *= length / noteLong.s_body.DrawHeight;
                    }
                    else
                    {
                        noteLong.s_body.WrapTexture    = true;
                        noteLong.s_body.VectorScale.Y *= ManiaStage.HeightRatio;
                        float height = noteLong.s_body.DrawHeight / ManiaStage.HeightRatio;
                        noteLong.s_body.DrawHeight = noteLong.s_body.Height = (int)(length / ManiaStage.HeightRatio);
                        noteLong.s_body.DrawDimensionsManualOverride = true;

                        switch (style)
                        {
                        case ManiaNoteBodyStyle.RepeatBottom:
                        default:
                            noteLong.s_body.DrawTop = 0;
                            break;

                        case ManiaNoteBodyStyle.RepeatTop:
                            noteLong.s_body.DrawTop = (int)(height - length);
                            break;

                        case ManiaNoteBodyStyle.RepeatTopAndBottom:
                            noteLong.s_body.DrawTop = (int)((height - length) / 2f);
                            break;
                        }
                    }

                    noteLong.s_body.UpdateTextureAlignment();

                    if (noteLong.IsFrozen || noteLong.IsUnfrozen)
                    {
                        FreezeNote(noteLong);
                    }
                    if (noteLong.IsUnfrozen)
                    {
                        UnfreezeNote(noteLong);
                    }
                }

                note.Column.Host.SpriteManagerNotes.Add(note.SpriteCollection);
            }

            //Add barlines
            if (!Configuration.ConfigManager.sIgnoreBarline)
            {
                Color c = ManiaStage.Skin.GetColour(@"Barline");

                for (int i = 0; i < timingChanges.Count; i++)
                {
                    double beatTime = timingChanges[i].Offset;
                    double timeEnd  = LastNoteTime + 1;

                    if (i + 1 < timingChanges.Count)
                    {
                        timeEnd = timingChanges[i + 1].Offset - 1;
                    }

                    if (timeEnd < audioTime - 1000)
                    {
                        continue;
                    }

                    while (beatTime < timeEnd)
                    {
                        foreach (StageMania stage in ManiaStage)
                        {
                            pSprite p = new pSprite(GameBase.WhitePixel, Fields.TopLeft, Origins.CentreLeft, Clocks.Audio, new Vector2(0, -100), 0.62F, false, c);
                            p.VectorScale = new Vector2(stage.Width * 1.6f, ManiaStage.Skin.BarlineHeight);

                            addTransformations(p, beatTime, ManiaStage.Skin.BarlineHeight);

                            barLines.Add(p);
                            stage.SpriteManagerNotes.Add(p);
                        }
                        beatTime += timingChanges[i].BeatLength * (int)timingChanges[i].TimeSignature;
                    }
                }
            }

            //Add arrows
            int arrowTime = FirstNoteTime - 1000;

            for (int arrowCount = 0; arrowTime > speedChanges[0].Offset && arrowCount < 3; arrowCount++)
            {
                foreach (StageMania stage in ManiaStage)
                {
                    pSprite arrow = new pSprite(ManiaStage.Skin.Load(@"WarningArrow", @"mania-warningarrow", ManiaStage.SkinSource), Fields.TopLeft, ManiaStage.FlipOrigin(Origins.TopCentre), Clocks.Audio,
                                                new Vector2(stage.Width / 2, 0), 0.63F, false, Color.White);
                    if (SkinManager.Current.Version >= 2.4 || stage.HeightRatio < 1f)
                    {
                        arrow.Scale = stage.MinimumRatio;
                    }
                    arrow.FlipVertical = upsideDown;

                    //The origin of the arrow is the end of its tail. This is because the tail needs to be 1 second before the first note
                    addTransformations(arrow, arrowTime, -SpriteHeight(arrow));

                    barLines.Add(arrow);
                    stage.SpriteManagerNotes.Add(arrow);
                }
                arrowTime -= 1000;
            }

            //Stop adding to the forward play queue
            foreach (StageMania stage in ManiaStage)
            {
                stage.SpriteManagerNotes.ForwardPlayOptimisedAdd = false;
            }

            if (paused)
            {
                AudioEngine.TogglePause();
            }
        }