mirror of
https://github.com/ppy/osu.git
synced 2024-12-16 16:22:57 +08:00
Merge pull request #1248 from smoogipooo/mania-autoplay
Implement auto play for osu!mania
This commit is contained in:
commit
e44b6e1ced
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
Mods = new Mod[]
|
Mods = new Mod[]
|
||||||
{
|
{
|
||||||
new ModAutoplay(),
|
new ManiaModAutoplay(),
|
||||||
new ModCinema(),
|
new ModCinema(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4,6 +4,13 @@
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System;
|
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
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
@ -154,4 +161,24 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override bool Ranked => true;
|
public override bool Ranked => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
|
||||||
|
{
|
||||||
|
private int availableColumns;
|
||||||
|
|
||||||
|
public override void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> 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<ManiaHitObject> beatmap) => new Score
|
||||||
|
{
|
||||||
|
User = new User { Username = "osu!topus!" },
|
||||||
|
Replay = new ManiaAutoGenerator(beatmap, availableColumns).Generate(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
133
osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
Normal file
133
osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// 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<ManiaHitObject>
|
||||||
|
{
|
||||||
|
private const double release_delay = 20;
|
||||||
|
|
||||||
|
private readonly int availableColumns;
|
||||||
|
|
||||||
|
public ManiaAutoGenerator(Beatmap<ManiaHitObject> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the maximum <see cref="ReplayFrame.MouseX"/> by count of bits from a grouping of <see cref="ReplayFrame"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="group">The <see cref="ReplayFrame"/> grouping to search.</param>
|
||||||
|
/// <returns>The maximum <see cref="ReplayFrame.MouseX"/> by count of bits.</returns>
|
||||||
|
private float maxMouseX(IGrouping<double, ReplayFrame> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the number of bits set in a value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to count.</param>
|
||||||
|
/// <returns>The number of set bits.</returns>
|
||||||
|
private int countBits(int value)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
while (value > 0)
|
||||||
|
{
|
||||||
|
if ((value & 1) > 0)
|
||||||
|
count++;
|
||||||
|
value >>= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// 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<InputState> GetPendingStates()
|
||||||
|
{
|
||||||
|
var actions = new List<ManiaAction>();
|
||||||
|
|
||||||
|
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<InputState> { new ReplayState<ManiaAction> { PressedActions = actions } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -285,6 +285,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
base.Reset();
|
base.Reset();
|
||||||
|
|
||||||
Health.Value = 1;
|
Health.Value = 1;
|
||||||
|
Accuracy.Value = 1;
|
||||||
|
|
||||||
bonusScore = 0;
|
bonusScore = 0;
|
||||||
comboPortion = 0;
|
comboPortion = 0;
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500);
|
this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500).Expire();
|
||||||
inner.FadeOut(250);
|
inner.FadeOut(250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,12 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Judgements;
|
using osu.Game.Rulesets.Mania.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mania.Timing;
|
using osu.Game.Rulesets.Mania.Timing;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Timing;
|
using osu.Game.Rulesets.Timing;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -34,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
/// The number of columns which the <see cref="ManiaPlayfield"/> should display, and which
|
/// The number of columns which the <see cref="ManiaPlayfield"/> should display, and which
|
||||||
/// the beatmap converter will attempt to convert beatmaps to use.
|
/// the beatmap converter will attempt to convert beatmaps to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private int availableColumns;
|
public int AvailableColumns { get; private set; }
|
||||||
|
|
||||||
public IEnumerable<DrawableBarLine> BarLines;
|
public IEnumerable<DrawableBarLine> BarLines;
|
||||||
|
|
||||||
@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
BarLines.ForEach(Playfield.Add);
|
BarLines.ForEach(Playfield.Add);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(availableColumns)
|
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(AvailableColumns)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -83,26 +85,26 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
|
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<ManiaHitObject> CreateBeatmapConverter()
|
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter()
|
||||||
{
|
{
|
||||||
if (IsForCurrentRuleset)
|
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
|
else
|
||||||
{
|
{
|
||||||
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
|
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
|
||||||
if (percentSliderOrSpinner < 0.2)
|
if (percentSliderOrSpinner < 0.2)
|
||||||
availableColumns = 7;
|
AvailableColumns = 7;
|
||||||
else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
|
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)
|
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
|
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<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
|
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
|
||||||
@ -123,5 +125,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
|
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
|
||||||
|
|
||||||
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
|
protected override SpeedAdjustmentContainer CreateSpeedAdjustmentContainer(MultiplierControlPoint controlPoint) => new ManiaSpeedAdjustmentContainer(controlPoint, ScrollingAlgorithm.Basic);
|
||||||
|
|
||||||
|
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,8 @@
|
|||||||
<Compile Include="Objects\Drawables\Pieces\LaneGlowPiece.cs" />
|
<Compile Include="Objects\Drawables\Pieces\LaneGlowPiece.cs" />
|
||||||
<Compile Include="Objects\Drawables\Pieces\NotePiece.cs" />
|
<Compile Include="Objects\Drawables\Pieces\NotePiece.cs" />
|
||||||
<Compile Include="Objects\Types\IHasColumn.cs" />
|
<Compile Include="Objects\Types\IHasColumn.cs" />
|
||||||
|
<Compile Include="Replays\ManiaAutoGenerator.cs" />
|
||||||
|
<Compile Include="Replays\ManiaFramedReplayInputHandler.cs" />
|
||||||
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
<Compile Include="Scoring\ManiaScoreProcessor.cs" />
|
||||||
<Compile Include="Objects\BarLine.cs" />
|
<Compile Include="Objects\BarLine.cs" />
|
||||||
<Compile Include="Objects\HoldNote.cs" />
|
<Compile Include="Objects\HoldNote.cs" />
|
||||||
@ -100,14 +102,6 @@
|
|||||||
<Project>{C76BF5B3-985E-4D39-95FE-97C9C879B83A}</Project>
|
<Project>{C76BF5B3-985E-4D39-95FE-97C9C879B83A}</Project>
|
||||||
<Name>osu.Framework</Name>
|
<Name>osu.Framework</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj">
|
|
||||||
<Project>{C92A607B-1FDD-4954-9F92-03FF547D9080}</Project>
|
|
||||||
<Name>osu.Game.Rulesets.Osu</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj">
|
|
||||||
<Project>{F167E17A-7DE6-4AF5-B920-A5112296C695}</Project>
|
|
||||||
<Name>osu.Game.Rulesets.Taiko</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj">
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj">
|
||||||
<Project>{0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D}</Project>
|
<Project>{0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D}</Project>
|
||||||
<Name>osu.Game</Name>
|
<Name>osu.Game</Name>
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
{
|
{
|
||||||
protected abstract Score CreateReplayScore(Beatmap<T> beatmap);
|
protected abstract Score CreateReplayScore(Beatmap<T> beatmap);
|
||||||
|
|
||||||
public void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer)
|
public virtual void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer)
|
||||||
{
|
{
|
||||||
rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay);
|
rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user