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(); } }