// Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Replays { internal class ManiaAutoGenerator : AutoGenerator { private const double release_delay = 20; private readonly int availableColumns; public ManiaAutoGenerator(Beatmap beatmap, int availableColumns) : base(beatmap) { this.availableColumns = availableColumns; Replay = new Replay { User = new User { Username = @"Autoplay" } }; } protected Replay Replay; public override Replay Generate() { // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled Replay.Frames.Add(new ManiaReplayFrame(-100000, null, null, ReplayButtonState.None)); double[] holdEndTimes = new double[availableColumns]; for (int i = 0; i < availableColumns; i++) holdEndTimes[i] = double.NegativeInfinity; // Notes are handled row-by-row foreach (var objGroup in Beatmap.HitObjects.GroupBy(h => h.StartTime)) { double groupTime = objGroup.Key; int activeColumns = 0; // Get the previously held-down active columns for (int i = 0; i < availableColumns; i++) { if (holdEndTimes[i] > groupTime) activeColumns |= 1 << i; } // Add on the group columns, keeping track of the held notes for the next rows foreach (var obj in objGroup) { var holdNote = obj as HoldNote; if (holdNote != null) holdEndTimes[obj.Column] = Math.Max(holdEndTimes[obj.Column], holdNote.EndTime); activeColumns |= 1 << obj.Column; } Replay.Frames.Add(new ManiaReplayFrame(groupTime, activeColumns, null, ReplayButtonState.None)); // Add the release frames. We can't do this with the loop above because we need activeColumns to be fully populated foreach (var obj in objGroup.GroupBy(h => (h as IHasEndTime)?.EndTime ?? h.StartTime + release_delay).OrderBy(h => h.Key)) { var groupEndTime = obj.Key; int activeColumnsAtEnd = 0; for (int i = 0; i < availableColumns; i++) { if (holdEndTimes[i] > groupEndTime) activeColumnsAtEnd |= 1 << i; } Replay.Frames.Add(new ManiaReplayFrame(groupEndTime, activeColumnsAtEnd, 0, ReplayButtonState.None)); } } Replay.Frames = Replay.Frames // Pick the maximum activeColumns for all frames at the same time .GroupBy(f => f.Time) .Select(g => new ManiaReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None)) .Cast() // The addition of release frames above maybe result in unordered frames, but we need them ordered .OrderBy(f => f.Time) .ToList(); return Replay; } /// /// Finds the maximum by count of bits from a grouping of s. /// /// The grouping to search. /// The maximum by count of bits. private float maxMouseX(IGrouping group) { int currentCount = -1; int currentMax = 0; foreach (var val in group) { int newCount = countBits((int)(val.MouseX ?? 0)); if (newCount > currentCount) { currentCount = newCount; currentMax = (int)(val.MouseX ?? 0); } } return currentMax; } /// /// Counts the number of bits set in a value. /// /// The value to count. /// The number of set bits. private int countBits(int value) { int count = 0; while (value > 0) { if ((value & 1) > 0) count++; value >>= 1; } return count; } } }