diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index d852a54ab6..0011d2837f 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Mania { Mods = new Mod[] { - new ModAutoplay(), + new ManiaModAutoplay(), new ModCinema(), }, }, diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs index bb11a05fc8..60415632a8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaMod.cs @@ -4,6 +4,13 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using System; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { @@ -154,4 +161,24 @@ namespace osu.Game.Rulesets.Mania.Mods public override double ScoreMultiplier => 1; public override bool Ranked => true; } + + public class ManiaModAutoplay : ModAutoplay + { + private int availableColumns; + + public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + { + // Todo: This shouldn't be done, we should be getting a ManiaBeatmap which should store AvailableColumns + // But this is dependent on a _lot_ of refactoring + var maniaRulesetContainer = (ManiaRulesetContainer)rulesetContainer; + availableColumns = maniaRulesetContainer.AvailableColumns; + + base.ApplyToRulesetContainer(rulesetContainer); + } + protected override Score CreateReplayScore(Beatmap beatmap) => new Score + { + User = new User { Username = "osu!topus!" }, + Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(), + }; + } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs new file mode 100644 index 0000000000..64982532a7 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -0,0 +1,133 @@ +// 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 ReplayFrame(-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 ReplayFrame(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 ReplayFrame(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 ReplayFrame(g.First().Time, maxMouseX(g), 0, ReplayButtonState.None)) + // 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; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs new file mode 100644 index 0000000000..e352997f2c --- /dev/null +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using osu.Framework.Input; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Mania.Replays +{ + internal class ManiaFramedReplayInputHandler : FramedReplayInputHandler + { + public ManiaFramedReplayInputHandler(Replay replay) + : base(replay) + { + } + + public override List GetPendingStates() + { + var actions = new List(); + + int activeColumns = (int)(CurrentFrame.MouseX ?? 0); + + int counter = 0; + while (activeColumns > 0) + { + if ((activeColumns & 1) > 0) + actions.Add(ManiaAction.Key1 + counter); + counter++; + activeColumns >>= 1; + } + + return new List { new ReplayState { PressedActions = actions } }; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 63b443319f..596eadd00c 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -285,6 +285,7 @@ namespace osu.Game.Rulesets.Mania.Scoring base.Reset(); Health.Value = 1; + Accuracy.Value = 1; bonusScore = 0; comboPortion = 0; diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 962082c368..6adad771aa 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI { base.LoadComplete(); - this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); + this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500).Expire(); inner.FadeOut(250); } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 5a3da6d074..d69fa8b6ff 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -18,10 +18,12 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; @@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.UI /// The number of columns which the should display, and which /// the beatmap converter will attempt to convert beatmaps to use. /// - private int availableColumns; + public int AvailableColumns { get; private set; } public IEnumerable BarLines; @@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI BarLines.ForEach(Playfield.Add); } - protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(availableColumns) + protected sealed override Playfield CreatePlayfield() => new ManiaPlayfield(AvailableColumns) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -83,26 +85,26 @@ namespace osu.Game.Rulesets.Mania.UI public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, availableColumns); + public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, AvailableColumns); protected override BeatmapConverter CreateBeatmapConverter() { if (IsForCurrentRuleset) - availableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize)); + AvailableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize)); else { float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) - availableColumns = 7; + AvailableColumns = 7; else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5) - availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6; + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6; else if (percentSliderOrSpinner > 0.6) - availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4; + AvailableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4; else - availableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7)); + AvailableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7)); } - return new ManiaBeatmapConverter(IsForCurrentRuleset, availableColumns); + return new ManiaBeatmapConverter(IsForCurrentRuleset, AvailableColumns); } protected override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) @@ -123,5 +125,7 @@ namespace osu.Game.Rulesets.Mania.UI protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic); + + protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); } } diff --git a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj index 8fc10b7cc4..ac759e2ab8 100644 --- a/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj +++ b/osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj @@ -74,6 +74,8 @@ + + @@ -100,14 +102,6 @@ {C76BF5B3-985E-4D39-95FE-97C9C879B83A} osu.Framework - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} osu.Game diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 2b10d098f3..ece0deba84 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods { protected abstract Score CreateReplayScore(Beatmap beatmap); - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) { rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); }