// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Linq; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays { internal class CatchAutoGenerator : AutoGenerator { public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; public CatchAutoGenerator(IBeatmap beatmap) : base(beatmap) { } protected override void GenerateFrames() { if (Beatmap.HitObjects.Count == 0) return; float lastPosition = CatchPlayfield.CENTER_X; double lastTime = 0; void moveToNext(PalpableCatchHitObject h) { float positionChange = Math.Abs(lastPosition - h.EffectiveX); double timeAvailable = h.StartTime - lastTime; if (timeAvailable < 0) { return; } // So we can either make it there without a dash or not. // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too) // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour. double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable; bool dashRequired = speedRequired > Catcher.BASE_WALK_SPEED; bool impossibleJump = speedRequired > Catcher.BASE_DASH_SPEED; // todo: get correct catcher size, based on difficulty CS. const float catcher_width_half = Catcher.BASE_SIZE * 0.3f * 0.5f; if (lastPosition - catcher_width_half < h.EffectiveX && lastPosition + catcher_width_half > h.EffectiveX) { // we are already in the correct range. lastTime = h.StartTime; addFrame(h.StartTime, lastPosition); return; } if (impossibleJump) { addFrame(h.StartTime, h.EffectiveX); } else if (h.HyperDash) { addFrame(h.StartTime - timeAvailable, lastPosition); addFrame(h.StartTime, h.EffectiveX); } else if (dashRequired) { // we do a movement in two parts - the dash part then the normal part... double timeAtNormalSpeed = positionChange / Catcher.BASE_WALK_SPEED; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeAtDashSpeed = timeWeNeedToSave / 2; float midPosition = (float)Interpolation.Lerp(lastPosition, h.EffectiveX, (float)timeAtDashSpeed / timeAvailable); // dash movement addFrame(h.StartTime - timeAvailable + 1, lastPosition, true); addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition); addFrame(h.StartTime, h.EffectiveX); } else { double timeBefore = positionChange / Catcher.BASE_WALK_SPEED; addFrame(h.StartTime - timeBefore, lastPosition); addFrame(h.StartTime, h.EffectiveX); } lastTime = h.StartTime; lastPosition = h.EffectiveX; } foreach (var obj in Beatmap.HitObjects) { if (obj is PalpableCatchHitObject palpableObject) { moveToNext(palpableObject); } foreach (var nestedObj in obj.NestedHitObjects.Cast()) { if (nestedObj is PalpableCatchHitObject palpableNestedObject) { moveToNext(palpableNestedObject); } } } } private void addFrame(double time, float? position = null, bool dashing = false) { Frames.Add(new CatchReplayFrame(time, position, dashing, LastFrame)); } } }