mirror of
https://github.com/ppy/osu.git
synced 2024-11-12 01:47:24 +08:00
Merge pull request #436 from peppy/replay
Replay loading via drag-drop huzzah!
This commit is contained in:
commit
2fc72b370d
@ -20,23 +20,28 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
internal class TestCasePlayer : TestCase
|
||||
{
|
||||
private WorkingBeatmap beatmap;
|
||||
protected Player Player;
|
||||
private BeatmapDatabase db;
|
||||
|
||||
|
||||
public override string Description => @"Showing everything to play the game.";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapDatabase db)
|
||||
{
|
||||
// ReSharper disable once ReplaceWithSingleCallToFirstOrDefault (TableQuery doesn't have correct LINQ implementation for First/FirstOrDefault).
|
||||
var beatmapInfo = db.Query<BeatmapInfo>().Where(b => b.Mode == PlayMode.Osu).FirstOrDefault();
|
||||
if (beatmapInfo != null)
|
||||
beatmap = db.GetWorkingBeatmap(beatmapInfo);
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
WorkingBeatmap beatmap = null;
|
||||
|
||||
var beatmapInfo = db.Query<BeatmapInfo>().Where(b => b.Mode == PlayMode.Osu).FirstOrDefault();
|
||||
if (beatmapInfo != null)
|
||||
beatmap = db.GetWorkingBeatmap(beatmapInfo);
|
||||
|
||||
if (beatmap?.Track == null)
|
||||
{
|
||||
var objects = new List<HitObject>();
|
||||
@ -82,16 +87,20 @@ namespace osu.Desktop.VisualTests.Tests
|
||||
Colour = Color4.Black,
|
||||
});
|
||||
|
||||
Add(new PlayerLoader(new Player
|
||||
{
|
||||
PreferredPlayMode = PlayMode.Osu,
|
||||
Beatmap = beatmap
|
||||
})
|
||||
Add(new PlayerLoader(Player = CreatePlayer(beatmap))
|
||||
{
|
||||
Beatmap = beatmap
|
||||
});
|
||||
}
|
||||
|
||||
protected virtual Player CreatePlayer(WorkingBeatmap beatmap)
|
||||
{
|
||||
return new Player
|
||||
{
|
||||
Beatmap = beatmap
|
||||
};
|
||||
}
|
||||
|
||||
private class TestWorkingBeatmap : WorkingBeatmap
|
||||
{
|
||||
public TestWorkingBeatmap(Beatmap beatmap)
|
||||
|
40
osu.Desktop.VisualTests/Tests/TestCaseReplay.cs
Normal file
40
osu.Desktop.VisualTests/Tests/TestCaseReplay.cs
Normal file
@ -0,0 +1,40 @@
|
||||
// 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.IO;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Modes;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Desktop.VisualTests.Tests
|
||||
{
|
||||
class TestCaseReplay : TestCasePlayer
|
||||
{
|
||||
private WorkingBeatmap beatmap;
|
||||
|
||||
private InputHandler replay;
|
||||
|
||||
private Func<Stream> getReplayStream;
|
||||
private ScoreDatabase scoreDatabase;
|
||||
|
||||
public override string Description => @"Testing replay playback.";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(Storage storage)
|
||||
{
|
||||
scoreDatabase = new ScoreDatabase(storage);
|
||||
}
|
||||
|
||||
protected override Player CreatePlayer(WorkingBeatmap beatmap)
|
||||
{
|
||||
var player = base.CreatePlayer(beatmap);
|
||||
player.ReplayInputHandler = Ruleset.GetRuleset(beatmap.PlayMode).CreateAutoplayScore(beatmap.Beatmap)?.Replay?.GetInputHandler();
|
||||
return player;
|
||||
}
|
||||
}
|
||||
}
|
@ -86,6 +86,10 @@
|
||||
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress, Version=0.15.1.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
|
||||
@ -187,6 +191,7 @@
|
||||
<Compile Include="Tests\TestCaseHitObjects.cs" />
|
||||
<Compile Include="Tests\TestCaseKeyCounter.cs" />
|
||||
<Compile Include="Tests\TestCaseMenuButtonSystem.cs" />
|
||||
<Compile Include="Tests\TestCaseReplay.cs" />
|
||||
<Compile Include="Tests\TestCaseScoreCounter.cs" />
|
||||
<Compile Include="Tests\TestCaseTextAwesome.cs" />
|
||||
<Compile Include="Tests\TestCasePlaySongSelect.cs" />
|
||||
|
@ -6,6 +6,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
|
||||
<package id="ppy.OpenTK" version="2.0.50727.1340" targetFramework="net45" />
|
||||
<package id="SharpCompress" version="0.15.1" targetFramework="net45" />
|
||||
<package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
|
||||
<package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
|
||||
<package id="SQLiteNetExtensions" version="1.3.0" targetFramework="net45" />
|
||||
|
@ -9,6 +9,8 @@ using osu.Framework.Desktop.Platform;
|
||||
using osu.Desktop.Overlays;
|
||||
using System.Reflection;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Game.Screens.Menu;
|
||||
|
||||
namespace osu.Desktop
|
||||
@ -52,19 +54,32 @@ namespace osu.Desktop
|
||||
private void dragDrop(DragEventArgs e)
|
||||
{
|
||||
// this method will only be executed if e.Effect in dragEnter gets set to something other that None.
|
||||
var dropData = e.Data.GetData(DataFormats.FileDrop) as object[];
|
||||
var dropData = (object[])e.Data.GetData(DataFormats.FileDrop);
|
||||
var filePaths = dropData.Select(f => f.ToString()).ToArray();
|
||||
ImportBeatmapsAsync(filePaths);
|
||||
|
||||
if (filePaths.All(f => Path.GetExtension(f) == @".osz"))
|
||||
Task.Run(() => BeatmapDatabase.Import(filePaths));
|
||||
else if (filePaths.All(f => Path.GetExtension(f) == @".osr"))
|
||||
Task.Run(() =>
|
||||
{
|
||||
var score = ScoreDatabase.ReadReplayFile(filePaths.First());
|
||||
Schedule(() => LoadScore(score));
|
||||
});
|
||||
}
|
||||
|
||||
static readonly string[] allowed_extensions = { @".osz", @".osr" };
|
||||
|
||||
private void dragEnter(DragEventArgs e)
|
||||
{
|
||||
// dragDrop will only be executed if e.Effect gets set to something other that None in this method.
|
||||
bool isFile = e.Data.GetDataPresent(DataFormats.FileDrop);
|
||||
if (isFile)
|
||||
{
|
||||
var paths = (e.Data.GetData(DataFormats.FileDrop) as object[]).Select(f => f.ToString()).ToArray();
|
||||
e.Effect = paths.Any(p => !p.EndsWith(".osz")) ? DragDropEffects.None : DragDropEffects.Copy;
|
||||
var paths = ((object[])e.Data.GetData(DataFormats.FileDrop)).Select(f => f.ToString()).ToArray();
|
||||
if (allowed_extensions.Any(ext => paths.All(p => p.EndsWith(ext))))
|
||||
e.Effect = DragDropEffects.Copy;
|
||||
else
|
||||
e.Effect = DragDropEffects.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Desktop
|
||||
{
|
||||
if (!host.IsPrimaryInstance)
|
||||
{
|
||||
var importer = new BeatmapImporter(host);
|
||||
var importer = new BeatmapIPCChannel(host);
|
||||
// Restore the cwd so relative paths given at the command line work correctly
|
||||
Directory.SetCurrentDirectory(cwd);
|
||||
foreach (var file in args)
|
||||
|
@ -101,8 +101,8 @@
|
||||
<HintPath>$(SolutionDir)\packages\DeltaCompressionDotNet.1.1.0\lib\net20\DeltaCompressionDotNet.PatchApi.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\squirrel.windows.1.5.2\lib\Net45\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Cecil, Version=0.9.6.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||
|
@ -7,6 +7,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
|
||||
<package id="DeltaCompressionDotNet" version="1.1.0" targetFramework="net45" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
|
||||
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net45" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
|
||||
<package id="Splat" version="2.0.0" targetFramework="net45" />
|
||||
<package id="squirrel.windows" version="1.5.2" targetFramework="net45" />
|
||||
</packages>
|
@ -8,6 +8,7 @@ using osu.Game.Modes.Objects;
|
||||
using osu.Game.Modes.Osu.UI;
|
||||
using osu.Game.Modes.UI;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.Catch
|
||||
{
|
||||
@ -15,7 +16,11 @@ namespace osu.Game.Modes.Catch
|
||||
{
|
||||
public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay();
|
||||
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap) => new CatchHitRenderer { Beatmap = beatmap };
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap, PlayerInputManager input = null) => new CatchHitRenderer
|
||||
{
|
||||
Beatmap = beatmap,
|
||||
InputManager = input,
|
||||
};
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
@ -76,7 +81,7 @@ namespace osu.Game.Modes.Catch
|
||||
|
||||
public override FontAwesome Icon => FontAwesome.fa_osu_fruits_o;
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
|
||||
|
||||
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
|
||||
|
||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Modes.Catch.UI
|
||||
{
|
||||
protected override HitObjectConverter<CatchBaseHit> Converter => new CatchConverter();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new CatchPlayfield();
|
||||
protected override Playfield<CatchBaseHit> CreatePlayfield() => new CatchPlayfield();
|
||||
|
||||
protected override DrawableHitObject GetVisualRepresentation(CatchBaseHit h) => null;// new DrawableFruit(h);
|
||||
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h) => null;// new DrawableFruit(h);
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,13 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Modes.Catch.Objects;
|
||||
using osu.Game.Modes.UI;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Modes.Catch.UI
|
||||
{
|
||||
public class CatchPlayfield : Playfield
|
||||
public class CatchPlayfield : Playfield<CatchBaseHit>
|
||||
{
|
||||
public CatchPlayfield()
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using osu.Game.Modes.Objects;
|
||||
using osu.Game.Modes.Osu.UI;
|
||||
using osu.Game.Modes.UI;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.Mania
|
||||
{
|
||||
@ -15,7 +16,11 @@ namespace osu.Game.Modes.Mania
|
||||
{
|
||||
public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay();
|
||||
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap) => new ManiaHitRenderer { Beatmap = beatmap };
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap, PlayerInputManager input = null) => new ManiaHitRenderer
|
||||
{
|
||||
Beatmap = beatmap,
|
||||
InputManager = input,
|
||||
};
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
@ -92,7 +97,7 @@ namespace osu.Game.Modes.Mania
|
||||
|
||||
public override FontAwesome Icon => FontAwesome.fa_osu_mania_o;
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
|
||||
|
||||
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
|
||||
|
||||
|
@ -19,9 +19,9 @@ namespace osu.Game.Modes.Mania.UI
|
||||
|
||||
protected override HitObjectConverter<ManiaBaseHit> Converter => new ManiaConverter(columns);
|
||||
|
||||
protected override Playfield CreatePlayfield() => new ManiaPlayfield(columns);
|
||||
protected override Playfield<ManiaBaseHit> CreatePlayfield() => new ManiaPlayfield(columns);
|
||||
|
||||
protected override DrawableHitObject GetVisualRepresentation(ManiaBaseHit h)
|
||||
protected override DrawableHitObject<ManiaBaseHit> GetVisualRepresentation(ManiaBaseHit h)
|
||||
{
|
||||
return null;
|
||||
//return new DrawableNote(h)
|
||||
|
@ -3,13 +3,14 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Modes.Mania.Objects;
|
||||
using osu.Game.Modes.UI;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Modes.Mania.UI
|
||||
{
|
||||
public class ManiaPlayfield : Playfield
|
||||
public class ManiaPlayfield : Playfield<ManiaBaseHit>
|
||||
{
|
||||
public ManiaPlayfield(int columns)
|
||||
{
|
||||
|
@ -69,15 +69,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
Size = circle.DrawSize;
|
||||
}
|
||||
|
||||
private double hit50 = 150;
|
||||
private double hit100 = 80;
|
||||
private double hit300 = 30;
|
||||
|
||||
protected override void CheckJudgement(bool userTriggered)
|
||||
{
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (Judgement.TimeOffset > hit50)
|
||||
if (Judgement.TimeOffset > HitObject.HitWindowFor(OsuScoreResult.Hit50))
|
||||
Judgement.Result = HitResult.Miss;
|
||||
return;
|
||||
}
|
||||
@ -86,16 +82,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
|
||||
OsuJudgementInfo osuJudgement = (OsuJudgementInfo)Judgement;
|
||||
|
||||
if (hitOffset < hit50)
|
||||
if (hitOffset < HitObject.HitWindowFor(OsuScoreResult.Hit50))
|
||||
{
|
||||
Judgement.Result = HitResult.Hit;
|
||||
|
||||
if (hitOffset < hit300)
|
||||
osuJudgement.Score = OsuScoreResult.Hit300;
|
||||
else if (hitOffset < hit100)
|
||||
osuJudgement.Score = OsuScoreResult.Hit100;
|
||||
else if (hitOffset < hit50)
|
||||
osuJudgement.Score = OsuScoreResult.Hit50;
|
||||
osuJudgement.Score = HitObject.ScoreResultForOffset(hitOffset);
|
||||
}
|
||||
else
|
||||
Judgement.Result = HitResult.Miss;
|
||||
|
@ -6,7 +6,7 @@ using osu.Game.Modes.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
{
|
||||
public class DrawableOsuHitObject : DrawableHitObject
|
||||
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
|
||||
{
|
||||
public const float TIME_PREEMPT = 600;
|
||||
public const float TIME_FADEIN = 400;
|
||||
|
@ -102,8 +102,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
|
||||
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||
|
||||
int repeat = (int)(progress * slider.RepeatCount);
|
||||
progress = progress * slider.RepeatCount % 1;
|
||||
int repeat = slider.RepeatAt(progress);
|
||||
progress = slider.CurveProgressAt(progress);
|
||||
|
||||
if (repeat > currentRepeat)
|
||||
{
|
||||
@ -112,9 +112,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
|
||||
currentRepeat = repeat;
|
||||
}
|
||||
|
||||
if (repeat % 2 == 1)
|
||||
progress = 1 - progress;
|
||||
|
||||
bouncer2.Position = slider.Curve.PositionAt(body.SnakedEnd ?? 0);
|
||||
|
||||
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
|
||||
|
||||
public CirclePiece()
|
||||
{
|
||||
Size = new Vector2(128);
|
||||
Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2);
|
||||
Masking = true;
|
||||
CornerRadius = Size.X / 2;
|
||||
|
||||
|
@ -5,11 +5,19 @@ using System;
|
||||
using osu.Game.Modes.Objects;
|
||||
using OpenTK;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Modes.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Modes.Osu.Objects
|
||||
{
|
||||
public abstract class OsuHitObject : HitObject
|
||||
{
|
||||
public const double OBJECT_RADIUS = 64;
|
||||
|
||||
private const double hittable_range = 300;
|
||||
private const double hit_window_50 = 150;
|
||||
private const double hit_window_100 = 80;
|
||||
private const double hit_window_300 = 30;
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
|
||||
public Vector2 StackedPosition => Position + StackOffset;
|
||||
@ -22,10 +30,38 @@ namespace osu.Game.Modes.Osu.Objects
|
||||
|
||||
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
||||
|
||||
public double Radius => OBJECT_RADIUS * Scale;
|
||||
|
||||
public float Scale { get; set; } = 1;
|
||||
|
||||
public abstract HitObjectType Type { get; }
|
||||
|
||||
public double HitWindowFor(OsuScoreResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
default:
|
||||
return 300;
|
||||
case OsuScoreResult.Hit50:
|
||||
return 150;
|
||||
case OsuScoreResult.Hit100:
|
||||
return 80;
|
||||
case OsuScoreResult.Hit300:
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
|
||||
public OsuScoreResult ScoreResultForOffset(double offset)
|
||||
{
|
||||
if (offset < HitWindowFor(OsuScoreResult.Hit300))
|
||||
return OsuScoreResult.Hit300;
|
||||
if (offset < HitWindowFor(OsuScoreResult.Hit100))
|
||||
return OsuScoreResult.Hit100;
|
||||
if (offset < HitWindowFor(OsuScoreResult.Hit50))
|
||||
return OsuScoreResult.Hit50;
|
||||
return OsuScoreResult.Miss;
|
||||
}
|
||||
|
||||
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
|
||||
{
|
||||
base.SetDefaultsFromBeatmap(beatmap);
|
||||
|
@ -14,7 +14,32 @@ namespace osu.Game.Modes.Osu.Objects
|
||||
{
|
||||
public override double EndTime => StartTime + RepeatCount * Curve.Length / Velocity;
|
||||
|
||||
public override Vector2 EndPosition => RepeatCount % 2 == 0 ? Position : Curve.PositionAt(1);
|
||||
public override Vector2 EndPosition => PositionAt(1);
|
||||
|
||||
/// <summary>
|
||||
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the slider)
|
||||
/// to 1 (end of the slider). This includes repeat logic.
|
||||
/// </summary>
|
||||
/// <param name="progress">Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
||||
/// <returns></returns>
|
||||
public Vector2 PositionAt(double progress) => Curve.PositionAt(CurveProgressAt(progress));
|
||||
|
||||
/// <summary>
|
||||
/// Find the current progress along the curve, accounting for repeat logic.
|
||||
/// </summary>
|
||||
public double CurveProgressAt(double progress)
|
||||
{
|
||||
var p = progress * RepeatCount % 1;
|
||||
if (RepeatAt(progress) % 2 == 1)
|
||||
p = 1 - p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine which repeat of the slider we are on at a given progress.
|
||||
/// Range is 0..RepeatCount where 0 is the first run.
|
||||
/// </summary>
|
||||
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
|
||||
|
||||
private int stackHeight;
|
||||
public override int StackHeight
|
||||
|
@ -185,10 +185,10 @@ namespace osu.Game.Modes.Osu.Objects
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the slider)
|
||||
/// to 1 (end of the slider).
|
||||
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve)
|
||||
/// to 1 (end of the curve).
|
||||
/// </summary>
|
||||
/// <param name="progress">Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
||||
/// <param name="progress">Ranges from 0 (beginning of the curve) to 1 (end of the curve).</param>
|
||||
/// <returns></returns>
|
||||
public Vector2 PositionAt(double progress)
|
||||
{
|
||||
|
298
osu.Game.Modes.Osu/OsuAutoReplay.cs
Normal file
298
osu.Game.Modes.Osu/OsuAutoReplay.cs
Normal file
@ -0,0 +1,298 @@
|
||||
// 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.Game.Beatmaps;
|
||||
using osu.Game.Modes.Osu.Objects;
|
||||
using OpenTK;
|
||||
using System;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Game.Modes.Osu.Objects.Drawables;
|
||||
using osu.Framework.MathUtils;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace osu.Game.Modes.Osu
|
||||
{
|
||||
public class OsuAutoReplay : LegacyReplay
|
||||
{
|
||||
static readonly Vector2 spinner_centre = new Vector2(256, 192);
|
||||
|
||||
const float spin_radius = 50;
|
||||
|
||||
private Beatmap beatmap;
|
||||
|
||||
public OsuAutoReplay(Beatmap beatmap)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
|
||||
createAutoReplay();
|
||||
}
|
||||
|
||||
internal class LegacyReplayFrameComparer : IComparer<LegacyReplayFrame>
|
||||
{
|
||||
public int Compare(LegacyReplayFrame f1, LegacyReplayFrame f2)
|
||||
{
|
||||
return f1.Time.CompareTo(f2.Time);
|
||||
}
|
||||
}
|
||||
|
||||
private static IComparer<LegacyReplayFrame> replayFrameComparer = new LegacyReplayFrameComparer();
|
||||
|
||||
private int findInsertionIndex(LegacyReplayFrame frame)
|
||||
{
|
||||
int index = Frames.BinarySearch(frame, replayFrameComparer);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Go to the first index which is actually bigger
|
||||
while (index < Frames.Count && frame.Time == Frames[index].Time)
|
||||
{
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private void addFrameToReplay(LegacyReplayFrame frame) => Frames.Insert(findInsertionIndex(frame), frame);
|
||||
|
||||
private static Vector2 circlePosition(double t, double radius) => new Vector2((float)(Math.Cos(t) * radius), (float)(Math.Sin(t) * radius));
|
||||
|
||||
private double applyModsToTime(double v) => v;
|
||||
private double applyModsToRate(double v) => v;
|
||||
|
||||
private void createAutoReplay()
|
||||
{
|
||||
int buttonIndex = 0;
|
||||
|
||||
bool delayedMovements = false;// ModManager.CheckActive(Mods.Relax2);
|
||||
EasingTypes preferredEasing = delayedMovements ? EasingTypes.InOutCubic : EasingTypes.Out;
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(-100000, 256, 500, LegacyButtonState.None));
|
||||
addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1500, 256, 500, LegacyButtonState.None));
|
||||
addFrameToReplay(new LegacyReplayFrame(beatmap.HitObjects[0].StartTime - 1000, 256, 192, LegacyButtonState.None));
|
||||
|
||||
// We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps.
|
||||
float frameDelay = (float)applyModsToRate(1000.0 / 60.0);
|
||||
|
||||
// Already superhuman, but still somewhat realistic
|
||||
int reactionTime = (int)applyModsToRate(100);
|
||||
|
||||
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
OsuHitObject h = beatmap.HitObjects[i] as OsuHitObject;
|
||||
|
||||
//if (h.EndTime < InputManager.ReplayStartTime)
|
||||
//{
|
||||
// h.IsHit = true;
|
||||
// continue;
|
||||
//}
|
||||
|
||||
int endDelay = h is Spinner ? 1 : 0;
|
||||
|
||||
if (delayedMovements && i > 0)
|
||||
{
|
||||
OsuHitObject last = beatmap.HitObjects[i - 1] as OsuHitObject;
|
||||
|
||||
//Make the cursor stay at a hitObject as long as possible (mainly for autopilot).
|
||||
if (h.StartTime - h.HitWindowFor(OsuScoreResult.Miss) > last.EndTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - last.EndTime < 1000) addFrameToReplay(new LegacyReplayFrame(last.EndTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Miss), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50) > last.EndTime + h.HitWindowFor(OsuScoreResult.Hit50) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - last.EndTime < 1000) addFrameToReplay(new LegacyReplayFrame(last.EndTime + h.HitWindowFor(OsuScoreResult.Hit50), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
}
|
||||
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100) > last.EndTime + h.HitWindowFor(OsuScoreResult.Hit100) + 50)
|
||||
{
|
||||
if (!(last is Spinner) && h.StartTime - last.EndTime < 1000) addFrameToReplay(new LegacyReplayFrame(last.EndTime + h.HitWindowFor(OsuScoreResult.Hit100), last.EndPosition.X, last.EndPosition.Y, LegacyButtonState.None));
|
||||
if (!(h is Spinner)) addFrameToReplay(new LegacyReplayFrame(h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100), h.Position.X, h.Position.Y, LegacyButtonState.None));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vector2 targetPosition = h.Position;
|
||||
EasingTypes easing = preferredEasing;
|
||||
float spinnerDirection = -1;
|
||||
|
||||
if (h is Spinner)
|
||||
{
|
||||
targetPosition.X = Frames[Frames.Count - 1].MouseX;
|
||||
targetPosition.Y = Frames[Frames.Count - 1].MouseY;
|
||||
|
||||
Vector2 difference = spinner_centre - targetPosition;
|
||||
|
||||
float differenceLength = difference.Length;
|
||||
float newLength = (float)Math.Sqrt(differenceLength * differenceLength - spin_radius * spin_radius);
|
||||
|
||||
if (differenceLength > spin_radius)
|
||||
{
|
||||
float angle = (float)Math.Asin(spin_radius / differenceLength);
|
||||
|
||||
if (angle > 0)
|
||||
{
|
||||
spinnerDirection = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
spinnerDirection = 1;
|
||||
}
|
||||
|
||||
difference.X = difference.X * (float)Math.Cos(angle) - difference.Y * (float)Math.Sin(angle);
|
||||
difference.Y = difference.X * (float)Math.Sin(angle) + difference.Y * (float)Math.Cos(angle);
|
||||
|
||||
difference.Normalize();
|
||||
difference *= newLength;
|
||||
|
||||
targetPosition += difference;
|
||||
|
||||
easing = EasingTypes.In;
|
||||
}
|
||||
else if (difference.Length > 0)
|
||||
{
|
||||
targetPosition = spinner_centre - difference * (spin_radius / difference.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetPosition = spinner_centre + new Vector2(0, -spin_radius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Do some nice easing for cursor movements
|
||||
if (Frames.Count > 0)
|
||||
{
|
||||
LegacyReplayFrame lastFrame = Frames[Frames.Count - 1];
|
||||
|
||||
// Wait until Auto could "see and react" to the next note.
|
||||
double waitTime = h.StartTime - Math.Max(0.0, DrawableOsuHitObject.TIME_PREEMPT - reactionTime);
|
||||
if (waitTime > lastFrame.Time)
|
||||
{
|
||||
lastFrame = new LegacyReplayFrame(waitTime, lastFrame.MouseX, lastFrame.MouseY, lastFrame.ButtonState);
|
||||
addFrameToReplay(lastFrame);
|
||||
}
|
||||
|
||||
Vector2 lastPosition = new Vector2(lastFrame.MouseX, lastFrame.MouseY);
|
||||
|
||||
double timeDifference = applyModsToTime(h.StartTime - lastFrame.Time);
|
||||
|
||||
// Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up.
|
||||
if (timeDifference > 0 && // Sanity checks
|
||||
((lastPosition - targetPosition).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough
|
||||
timeDifference >= 266)) // ... or the beats are slow enough to tap anyway.
|
||||
{
|
||||
// Perform eased movement
|
||||
for (double time = lastFrame.Time + frameDelay; time < h.StartTime; time += frameDelay)
|
||||
{
|
||||
Vector2 currentPosition = Interpolation.ValueAt(time, lastPosition, targetPosition, lastFrame.Time, h.StartTime, easing);
|
||||
addFrameToReplay(new LegacyReplayFrame((int)time, currentPosition.X, currentPosition.Y, lastFrame.ButtonState));
|
||||
}
|
||||
|
||||
buttonIndex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
LegacyButtonState button = buttonIndex % 2 == 0 ? LegacyButtonState.Left1 : LegacyButtonState.Right1;
|
||||
|
||||
LegacyReplayFrame newFrame = new LegacyReplayFrame(h.StartTime, targetPosition.X, targetPosition.Y, button);
|
||||
LegacyReplayFrame endFrame = new LegacyReplayFrame(h.EndTime + endDelay, h.EndPosition.X, h.EndPosition.Y, LegacyButtonState.None);
|
||||
|
||||
// Decrement because we want the previous frame, not the next one
|
||||
int index = findInsertionIndex(newFrame) - 1;
|
||||
|
||||
// Do we have a previous frame? No need to check for < replay.Count since we decremented!
|
||||
if (index >= 0)
|
||||
{
|
||||
LegacyReplayFrame previousFrame = Frames[index];
|
||||
var previousButton = previousFrame.ButtonState;
|
||||
|
||||
// If a button is already held, then we simply alternate
|
||||
if (previousButton != LegacyButtonState.None)
|
||||
{
|
||||
Debug.Assert(previousButton != (LegacyButtonState.Left1 | LegacyButtonState.Right1));
|
||||
|
||||
// Force alternation if we have the same button. Otherwise we can just keep the naturally to us assigned button.
|
||||
if (previousButton == button)
|
||||
{
|
||||
button = (LegacyButtonState.Left1 | LegacyButtonState.Right1) & ~button;
|
||||
newFrame.SetButtonStates(button);
|
||||
}
|
||||
|
||||
// We always follow the most recent slider / spinner, so remove any other frames that occur while it exists.
|
||||
int endIndex = findInsertionIndex(endFrame);
|
||||
|
||||
if (index < Frames.Count - 1)
|
||||
Frames.RemoveRange(index + 1, Math.Max(0, endIndex - (index + 1)));
|
||||
|
||||
// After alternating we need to keep holding the other button in the future rather than the previous one.
|
||||
for (int j = index + 1; j < Frames.Count; ++j)
|
||||
{
|
||||
// Don't affect frames which stop pressing a button!
|
||||
if (j < Frames.Count - 1 || Frames[j].ButtonState == previousButton)
|
||||
Frames[j].SetButtonStates(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addFrameToReplay(newFrame);
|
||||
|
||||
// We add intermediate frames for spinning / following a slider here.
|
||||
if (h is Spinner)
|
||||
{
|
||||
Vector2 difference = targetPosition - spinner_centre;
|
||||
|
||||
float radius = difference.Length;
|
||||
float angle = radius == 0 ? 0 : (float)Math.Atan2(difference.Y, difference.X);
|
||||
|
||||
double t;
|
||||
|
||||
for (double j = h.StartTime + frameDelay; j < h.EndTime; j += frameDelay)
|
||||
{
|
||||
t = applyModsToTime(j - h.StartTime) * spinnerDirection;
|
||||
|
||||
Vector2 pos = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
|
||||
addFrameToReplay(new LegacyReplayFrame((int)j, pos.X, pos.Y, button));
|
||||
}
|
||||
|
||||
t = applyModsToTime(h.EndTime - h.StartTime) * spinnerDirection;
|
||||
Vector2 endPosition = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(h.EndTime, endPosition.X, endPosition.Y, button));
|
||||
|
||||
endFrame.MouseX = endPosition.X;
|
||||
endFrame.MouseY = endPosition.Y;
|
||||
}
|
||||
else if (h is Slider)
|
||||
{
|
||||
Slider s = h as Slider;
|
||||
|
||||
for (double j = frameDelay; j < s.Duration; j += frameDelay)
|
||||
{
|
||||
Vector2 pos = s.PositionAt(j / s.Duration);
|
||||
addFrameToReplay(new LegacyReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
|
||||
}
|
||||
|
||||
addFrameToReplay(new LegacyReplayFrame(h.EndTime, s.EndPosition.X, s.EndPosition.Y, button));
|
||||
}
|
||||
|
||||
// We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed!
|
||||
if (Frames[Frames.Count - 1].Time <= endFrame.Time)
|
||||
addFrameToReplay(endFrame);
|
||||
}
|
||||
|
||||
//Player.currentScore.Replay = InputManager.ReplayScore.Replay;
|
||||
//Player.currentScore.PlayerName = "osu!";
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using osu.Game.Modes.Objects;
|
||||
using osu.Game.Modes.Osu.Objects;
|
||||
using osu.Game.Modes.Osu.UI;
|
||||
using osu.Game.Modes.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.Osu
|
||||
{
|
||||
@ -16,7 +17,11 @@ namespace osu.Game.Modes.Osu
|
||||
{
|
||||
public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay();
|
||||
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap) => new OsuHitRenderer { Beatmap = beatmap };
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap, PlayerInputManager input = null) => new OsuHitRenderer
|
||||
{
|
||||
Beatmap = beatmap,
|
||||
InputManager = input
|
||||
};
|
||||
|
||||
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[]
|
||||
{
|
||||
@ -96,10 +101,17 @@ namespace osu.Game.Modes.Osu
|
||||
|
||||
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount);
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => new OsuScoreProcessor(hitObjectCount);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap);
|
||||
|
||||
public override Score CreateAutoplayScore(Beatmap beatmap)
|
||||
{
|
||||
var score = CreateScoreProcessor().GetScore();
|
||||
score.Replay = new OsuAutoReplay(beatmap);
|
||||
return score;
|
||||
}
|
||||
|
||||
protected override PlayMode PlayMode => PlayMode.Osu;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace osu.Game.Modes.Osu
|
||||
{
|
||||
internal class OsuScoreProcessor : ScoreProcessor
|
||||
{
|
||||
public OsuScoreProcessor(int hitObjectCount)
|
||||
public OsuScoreProcessor(int hitObjectCount = 0)
|
||||
: base(hitObjectCount)
|
||||
{
|
||||
Health.Value = 1;
|
||||
|
@ -13,9 +13,9 @@ namespace osu.Game.Modes.Osu.UI
|
||||
{
|
||||
protected override HitObjectConverter<OsuHitObject> Converter => new OsuHitObjectConverter();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new OsuPlayfield();
|
||||
protected override Playfield<OsuHitObject> CreatePlayfield() => new OsuPlayfield();
|
||||
|
||||
protected override DrawableHitObject GetVisualRepresentation(OsuHitObject h)
|
||||
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
|
||||
{
|
||||
var circle = h as HitCircle;
|
||||
if (circle != null)
|
||||
|
@ -10,10 +10,12 @@ using osu.Game.Modes.Osu.Objects.Drawables;
|
||||
using osu.Game.Modes.Osu.Objects.Drawables.Connections;
|
||||
using osu.Game.Modes.UI;
|
||||
using System.Linq;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Modes.Osu.UI
|
||||
{
|
||||
public class OsuPlayfield : Playfield
|
||||
public class OsuPlayfield : Playfield<OsuHitObject>
|
||||
{
|
||||
private Container approachCircles;
|
||||
private Container judgementLayer;
|
||||
@ -53,11 +55,18 @@ namespace osu.Game.Modes.Osu.UI
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = -1,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
if (InputManager.ReplayInputHandler != null)
|
||||
Add(new OsuCursorContainer { Colour = Color4.LightYellow });
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject<OsuHitObject> h)
|
||||
{
|
||||
h.Depth = (float)h.HitObject.StartTime;
|
||||
IDrawableHitObjectWithProxiedApproach c = h as IDrawableHitObjectWithProxiedApproach;
|
||||
@ -78,9 +87,9 @@ namespace osu.Game.Modes.Osu.UI
|
||||
.OrderBy(h => h.StartTime);
|
||||
}
|
||||
|
||||
private void judgement(DrawableHitObject h, JudgementInfo j)
|
||||
private void judgement(DrawableHitObject<OsuHitObject> h, JudgementInfo j)
|
||||
{
|
||||
HitExplosion explosion = new HitExplosion((OsuJudgementInfo)j, (OsuHitObject)h.HitObject);
|
||||
HitExplosion explosion = new HitExplosion((OsuJudgementInfo)j, h.HitObject);
|
||||
|
||||
judgementLayer.Add(explosion);
|
||||
}
|
||||
|
@ -70,6 +70,7 @@
|
||||
<Compile Include="Objects\OsuHitObjectParser.cs" />
|
||||
<Compile Include="Objects\SliderCurve.cs" />
|
||||
<Compile Include="Objects\SliderTick.cs" />
|
||||
<Compile Include="OsuAutoReplay.cs" />
|
||||
<Compile Include="OsuDifficultyCalculator.cs" />
|
||||
<Compile Include="OsuScore.cs" />
|
||||
<Compile Include="OsuScoreProcessor.cs" />
|
||||
|
@ -8,6 +8,7 @@ using osu.Game.Modes.Osu.UI;
|
||||
using osu.Game.Modes.Taiko.UI;
|
||||
using osu.Game.Modes.UI;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.Taiko
|
||||
{
|
||||
@ -15,7 +16,11 @@ namespace osu.Game.Modes.Taiko
|
||||
{
|
||||
public override ScoreOverlay CreateScoreOverlay() => new OsuScoreOverlay();
|
||||
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap) => new TaikoHitRenderer { Beatmap = beatmap };
|
||||
public override HitRenderer CreateHitRendererWith(Beatmap beatmap, PlayerInputManager input = null) => new TaikoHitRenderer
|
||||
{
|
||||
Beatmap = beatmap,
|
||||
InputManager = input,
|
||||
};
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
@ -76,7 +81,7 @@ namespace osu.Game.Modes.Taiko
|
||||
|
||||
public override FontAwesome Icon => FontAwesome.fa_osu_taiko_o;
|
||||
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
|
||||
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
|
||||
|
||||
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
|
||||
|
||||
|
@ -12,8 +12,8 @@ namespace osu.Game.Modes.Taiko.UI
|
||||
{
|
||||
protected override HitObjectConverter<TaikoBaseHit> Converter => new TaikoConverter();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new TaikoPlayfield();
|
||||
protected override Playfield<TaikoBaseHit> CreatePlayfield() => new TaikoPlayfield();
|
||||
|
||||
protected override DrawableHitObject GetVisualRepresentation(TaikoBaseHit h) => null;// new DrawableTaikoHit(h);
|
||||
protected override DrawableHitObject<TaikoBaseHit> GetVisualRepresentation(TaikoBaseHit h) => null;// new DrawableTaikoHit(h);
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,14 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Modes.Taiko.Objects;
|
||||
using osu.Game.Modes.UI;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Modes.Taiko.UI
|
||||
{
|
||||
public class TaikoPlayfield : Playfield
|
||||
public class TaikoPlayfield : Playfield<TaikoBaseHit>
|
||||
{
|
||||
public TaikoPlayfield()
|
||||
{
|
||||
|
@ -69,7 +69,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
Assert.IsTrue(File.Exists(temp));
|
||||
|
||||
var importer = new BeatmapImporter(client);
|
||||
var importer = new BeatmapIPCChannel(client);
|
||||
if (!importer.ImportAsync(temp).Wait(1000))
|
||||
Assert.Fail(@"IPC took too long to send");
|
||||
|
||||
|
@ -2,12 +2,15 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.IO;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Modes;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
@ -17,6 +20,16 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public readonly BeatmapSetInfo BeatmapSetInfo;
|
||||
|
||||
/// <summary>
|
||||
/// A play mode that is preferred for this beatmap. PlayMode will become this mode where conversion is feasible,
|
||||
/// or otherwise to the beatmap's default.
|
||||
/// </summary>
|
||||
public PlayMode? PreferredPlayMode;
|
||||
|
||||
public PlayMode PlayMode => beatmap?.BeatmapInfo?.Mode > PlayMode.Osu ? beatmap.BeatmapInfo.Mode : PreferredPlayMode ?? PlayMode.Osu;
|
||||
|
||||
public readonly Bindable<IEnumerable<Mod>> Mods = new Bindable<IEnumerable<Mod>>();
|
||||
|
||||
public readonly bool WithStoryboard;
|
||||
|
||||
protected abstract ArchiveReader GetReader();
|
||||
|
@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Security.Cryptography;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -26,14 +26,14 @@ namespace osu.Game.Database
|
||||
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised)
|
||||
private BeatmapImporter ipc;
|
||||
private BeatmapIPCChannel ipc;
|
||||
|
||||
public BeatmapDatabase(Storage storage, GameHost importHost = null)
|
||||
public BeatmapDatabase(Storage storage, IIpcHost importHost = null)
|
||||
{
|
||||
this.storage = storage;
|
||||
|
||||
if (importHost != null)
|
||||
ipc = new BeatmapImporter(importHost, this);
|
||||
ipc = new BeatmapIPCChannel(importHost, this);
|
||||
|
||||
if (connection == null)
|
||||
{
|
||||
@ -163,7 +163,7 @@ namespace osu.Game.Database
|
||||
/// <param name="path">Location on disk</param>
|
||||
public void Import(string path)
|
||||
{
|
||||
Import(new [] { path });
|
||||
Import(new[] { path });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -182,10 +182,9 @@ namespace osu.Game.Database
|
||||
|
||||
if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader
|
||||
{
|
||||
using (var md5 = MD5.Create())
|
||||
using (var input = storage.GetStream(path))
|
||||
{
|
||||
hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant();
|
||||
hash = input.GetMd5Hash();
|
||||
input.Seek(0, SeekOrigin.Begin);
|
||||
path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash);
|
||||
if (!storage.Exists(path))
|
||||
@ -217,22 +216,29 @@ namespace osu.Game.Database
|
||||
Metadata = metadata
|
||||
};
|
||||
|
||||
using (var reader = ArchiveReader.GetReader(storage, path))
|
||||
using (var archive = ArchiveReader.GetReader(storage, path))
|
||||
{
|
||||
string[] mapNames = reader.BeatmapFilenames;
|
||||
string[] mapNames = archive.BeatmapFilenames;
|
||||
foreach (var name in mapNames)
|
||||
using (var stream = new StreamReader(reader.GetStream(name)))
|
||||
using (var raw = archive.GetStream(name))
|
||||
using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit
|
||||
using (var sr = new StreamReader(ms))
|
||||
{
|
||||
var decoder = BeatmapDecoder.GetDecoder(stream);
|
||||
Beatmap beatmap = decoder.Decode(stream);
|
||||
raw.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
var decoder = BeatmapDecoder.GetDecoder(sr);
|
||||
Beatmap beatmap = decoder.Decode(sr);
|
||||
|
||||
beatmap.BeatmapInfo.Path = name;
|
||||
beatmap.BeatmapInfo.Hash = ms.GetMd5Hash();
|
||||
|
||||
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
|
||||
beatmap.BeatmapInfo.Metadata = null;
|
||||
|
||||
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
|
||||
}
|
||||
beatmapSet.StoryboardFile = reader.StoryboardFilename;
|
||||
beatmapSet.StoryboardFile = archive.StoryboardFilename;
|
||||
}
|
||||
|
||||
return beatmapSet;
|
||||
|
@ -39,6 +39,8 @@ namespace osu.Game.Database
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public string Hash { get; set; }
|
||||
|
||||
// General
|
||||
public int AudioLeadIn { get; set; }
|
||||
public bool Countdown { get; set; }
|
||||
@ -64,6 +66,7 @@ namespace osu.Game.Database
|
||||
StoredBookmarks = string.Join(",", value);
|
||||
}
|
||||
}
|
||||
|
||||
public double DistanceSpacing { get; set; }
|
||||
public int BeatDivisor { get; set; }
|
||||
public int GridSize { get; set; }
|
||||
|
110
osu.Game/Database/ScoreDatabase.cs
Normal file
110
osu.Game/Database/ScoreDatabase.cs
Normal file
@ -0,0 +1,110 @@
|
||||
// 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.IO;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Modes;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
|
||||
namespace osu.Game.Database
|
||||
{
|
||||
public class ScoreDatabase
|
||||
{
|
||||
private readonly Storage storage;
|
||||
private readonly BeatmapDatabase beatmaps;
|
||||
|
||||
private const string replay_folder = @"replays";
|
||||
|
||||
private ScoreIPCChannel ipc;
|
||||
|
||||
public ScoreDatabase(Storage storage, IIpcHost importHost = null, BeatmapDatabase beatmaps = null)
|
||||
{
|
||||
this.storage = storage;
|
||||
this.beatmaps = beatmaps;
|
||||
|
||||
if (importHost != null)
|
||||
ipc = new ScoreIPCChannel(importHost, this);
|
||||
}
|
||||
|
||||
public Score ReadReplayFile(string replayFilename)
|
||||
{
|
||||
Score score;
|
||||
|
||||
using (Stream s = storage.GetStream(Path.Combine(replay_folder, replayFilename)))
|
||||
using (SerializationReader sr = new SerializationReader(s))
|
||||
{
|
||||
var ruleset = Ruleset.GetRuleset((PlayMode)sr.ReadByte());
|
||||
var processor = ruleset.CreateScoreProcessor();
|
||||
|
||||
score = processor.GetScore();
|
||||
|
||||
/* score.Pass = true;*/
|
||||
var version = sr.ReadInt32();
|
||||
/* score.FileChecksum = */
|
||||
var beatmapHash = sr.ReadString();
|
||||
score.Beatmap = beatmaps.Query<BeatmapInfo>().Where(b => b.Hash == beatmapHash).FirstOrDefault();
|
||||
/* score.PlayerName = */
|
||||
sr.ReadString();
|
||||
/* var localScoreChecksum = */
|
||||
sr.ReadString();
|
||||
/* score.Count300 = */
|
||||
sr.ReadUInt16();
|
||||
/* score.Count100 = */
|
||||
sr.ReadUInt16();
|
||||
/* score.Count50 = */
|
||||
sr.ReadUInt16();
|
||||
/* score.CountGeki = */
|
||||
sr.ReadUInt16();
|
||||
/* score.CountKatu = */
|
||||
sr.ReadUInt16();
|
||||
/* score.CountMiss = */
|
||||
sr.ReadUInt16();
|
||||
score.TotalScore = sr.ReadInt32();
|
||||
score.MaxCombo = sr.ReadUInt16();
|
||||
/* score.Perfect = */
|
||||
sr.ReadBoolean();
|
||||
/* score.EnabledMods = (Mods)*/
|
||||
sr.ReadInt32();
|
||||
/* score.HpGraphString = */
|
||||
sr.ReadString();
|
||||
/* score.Date = */
|
||||
sr.ReadDateTime();
|
||||
|
||||
var compressedReplay = sr.ReadByteArray();
|
||||
|
||||
if (version >= 20140721)
|
||||
/*OnlineId =*/
|
||||
sr.ReadInt64();
|
||||
else if (version >= 20121008)
|
||||
/*OnlineId =*/
|
||||
sr.ReadInt32();
|
||||
|
||||
using (var replayInStream = new MemoryStream(compressedReplay))
|
||||
{
|
||||
byte[] properties = new byte[5];
|
||||
if (replayInStream.Read(properties, 0, 5) != 5)
|
||||
throw (new Exception("input .lzma is too short"));
|
||||
long outSize = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int v = replayInStream.ReadByte();
|
||||
if (v < 0)
|
||||
throw (new Exception("Can't Read 1"));
|
||||
outSize |= ((long)(byte)v) << (8 * i);
|
||||
}
|
||||
|
||||
long compressedSize = replayInStream.Length - replayInStream.Position;
|
||||
|
||||
using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize))
|
||||
using (var reader = new StreamReader(lzma))
|
||||
score.Replay = new LegacyReplay(reader);
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
}
|
||||
}
|
@ -76,6 +76,12 @@ namespace osu.Game.Graphics.Cursor
|
||||
Scale = new Vector2(1 / texture.ScaleAdjust);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
resetTime();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
@ -17,7 +17,7 @@ using System;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
internal class OsuCursorContainer : CursorContainer
|
||||
public class OsuCursorContainer : CursorContainer
|
||||
{
|
||||
protected override Drawable CreateCursor() => new OsuCursor();
|
||||
|
||||
@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Cursor
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
private class OsuCursor : Container
|
||||
public class OsuCursor : Container
|
||||
{
|
||||
private Container cursorContainer;
|
||||
private Bindable<double> cursorScale;
|
||||
|
11
osu.Game/IO/Legacy/ILegacySerializable.cs
Normal file
11
osu.Game/IO/Legacy/ILegacySerializable.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.IO.Legacy
|
||||
{
|
||||
public interface ILegacySerializable
|
||||
{
|
||||
void ReadFromStream(SerializationReader sr);
|
||||
void WriteToStream(SerializationWriter sw);
|
||||
}
|
||||
}
|
279
osu.Game/IO/Legacy/SerializationReader.cs
Normal file
279
osu.Game/IO/Legacy/SerializationReader.cs
Normal file
@ -0,0 +1,279 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace osu.Game.IO.Legacy
|
||||
{
|
||||
/// <summary> SerializationReader. Extends BinaryReader to add additional data types,
|
||||
/// handle null strings and simplify use with ISerializable. </summary>
|
||||
public class SerializationReader : BinaryReader
|
||||
{
|
||||
Stream stream;
|
||||
|
||||
public SerializationReader(Stream s)
|
||||
: base(s, Encoding.UTF8)
|
||||
{
|
||||
stream = s;
|
||||
}
|
||||
|
||||
public int RemainingBytes => (int)(stream.Length - stream.Position);
|
||||
|
||||
/// <summary> Static method to take a SerializationInfo object (an input to an ISerializable constructor)
|
||||
/// and produce a SerializationReader from which serialized objects can be read </summary>.
|
||||
public static SerializationReader GetReader(SerializationInfo info)
|
||||
{
|
||||
byte[] byteArray = (byte[])info.GetValue("X", typeof(byte[]));
|
||||
MemoryStream ms = new MemoryStream(byteArray);
|
||||
return new SerializationReader(ms);
|
||||
}
|
||||
|
||||
/// <summary> Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. </summary>
|
||||
public override string ReadString()
|
||||
{
|
||||
if (0 == ReadByte()) return null;
|
||||
return base.ReadString();
|
||||
}
|
||||
|
||||
/// <summary> Reads a byte array from the buffer, handling nulls and the array length. </summary>
|
||||
public byte[] ReadByteArray()
|
||||
{
|
||||
int len = ReadInt32();
|
||||
if (len > 0) return ReadBytes(len);
|
||||
if (len < 0) return null;
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
/// <summary> Reads a char array from the buffer, handling nulls and the array length. </summary>
|
||||
public char[] ReadCharArray()
|
||||
{
|
||||
int len = ReadInt32();
|
||||
if (len > 0) return ReadChars(len);
|
||||
if (len < 0) return null;
|
||||
return new char[0];
|
||||
}
|
||||
|
||||
/// <summary> Reads a DateTime from the buffer. </summary>
|
||||
public DateTime ReadDateTime()
|
||||
{
|
||||
long ticks = ReadInt64();
|
||||
if (ticks < 0) throw new AbandonedMutexException("oops");
|
||||
return new DateTime(ticks, DateTimeKind.Utc);
|
||||
}
|
||||
|
||||
/// <summary> Reads a generic list from the buffer. </summary>
|
||||
public IList<T> ReadBList<T>(bool skipErrors = false) where T : ILegacySerializable, new()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
if (count < 0) return null;
|
||||
IList<T> d = new List<T>(count);
|
||||
|
||||
SerializationReader sr = new SerializationReader(BaseStream);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T obj = new T();
|
||||
try
|
||||
{
|
||||
obj.ReadFromStream(sr);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (skipErrors)
|
||||
continue;
|
||||
throw;
|
||||
}
|
||||
|
||||
d.Add(obj);
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary> Reads a generic list from the buffer. </summary>
|
||||
public IList<T> ReadList<T>()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
if (count < 0) return null;
|
||||
IList<T> d = new List<T>(count);
|
||||
for (int i = 0; i < count; i++) d.Add((T)ReadObject());
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary> Reads a generic Dictionary from the buffer. </summary>
|
||||
public IDictionary<T, U> ReadDictionary<T, U>()
|
||||
{
|
||||
int count = ReadInt32();
|
||||
if (count < 0) return null;
|
||||
IDictionary<T, U> d = new Dictionary<T, U>();
|
||||
for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject();
|
||||
return d;
|
||||
}
|
||||
|
||||
/// <summary> Reads an object which was added to the buffer by WriteObject. </summary>
|
||||
public object ReadObject()
|
||||
{
|
||||
ObjType t = (ObjType)ReadByte();
|
||||
switch (t)
|
||||
{
|
||||
case ObjType.boolType:
|
||||
return ReadBoolean();
|
||||
case ObjType.byteType:
|
||||
return ReadByte();
|
||||
case ObjType.uint16Type:
|
||||
return ReadUInt16();
|
||||
case ObjType.uint32Type:
|
||||
return ReadUInt32();
|
||||
case ObjType.uint64Type:
|
||||
return ReadUInt64();
|
||||
case ObjType.sbyteType:
|
||||
return ReadSByte();
|
||||
case ObjType.int16Type:
|
||||
return ReadInt16();
|
||||
case ObjType.int32Type:
|
||||
return ReadInt32();
|
||||
case ObjType.int64Type:
|
||||
return ReadInt64();
|
||||
case ObjType.charType:
|
||||
return ReadChar();
|
||||
case ObjType.stringType:
|
||||
return base.ReadString();
|
||||
case ObjType.singleType:
|
||||
return ReadSingle();
|
||||
case ObjType.doubleType:
|
||||
return ReadDouble();
|
||||
case ObjType.decimalType:
|
||||
return ReadDecimal();
|
||||
case ObjType.dateTimeType:
|
||||
return ReadDateTime();
|
||||
case ObjType.byteArrayType:
|
||||
return ReadByteArray();
|
||||
case ObjType.charArrayType:
|
||||
return ReadCharArray();
|
||||
case ObjType.otherType:
|
||||
return DynamicDeserializer.Deserialize(BaseStream);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicDeserializer
|
||||
{
|
||||
private static VersionConfigToNamespaceAssemblyObjectBinder versionBinder;
|
||||
private static BinaryFormatter formatter;
|
||||
|
||||
private static void initialize()
|
||||
{
|
||||
versionBinder = new VersionConfigToNamespaceAssemblyObjectBinder();
|
||||
formatter = new BinaryFormatter();
|
||||
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
|
||||
formatter.Binder = versionBinder;
|
||||
}
|
||||
|
||||
public static object Deserialize(Stream stream)
|
||||
{
|
||||
if (formatter == null)
|
||||
initialize();
|
||||
return formatter.Deserialize(stream);
|
||||
}
|
||||
|
||||
#region Nested type: VersionConfigToNamespaceAssemblyObjectBinder
|
||||
|
||||
public sealed class VersionConfigToNamespaceAssemblyObjectBinder : SerializationBinder
|
||||
{
|
||||
private readonly Dictionary<string, Type> cache = new Dictionary<string, Type>();
|
||||
|
||||
public override Type BindToType(string assemblyName, string typeName)
|
||||
{
|
||||
Type typeToDeserialize;
|
||||
|
||||
if (cache.TryGetValue(assemblyName + typeName, out typeToDeserialize))
|
||||
return typeToDeserialize;
|
||||
|
||||
List<Type> tmpTypes = new List<Type>();
|
||||
Type genType = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (typeName.Contains("System.Collections.Generic") && typeName.Contains("[["))
|
||||
{
|
||||
string[] splitTyps = typeName.Split('[');
|
||||
|
||||
foreach (string typ in splitTyps)
|
||||
{
|
||||
if (typ.Contains("Version"))
|
||||
{
|
||||
string asmTmp = typ.Substring(typ.IndexOf(',') + 1);
|
||||
string asmName = asmTmp.Remove(asmTmp.IndexOf(']')).Trim();
|
||||
string typName = typ.Remove(typ.IndexOf(','));
|
||||
tmpTypes.Add(BindToType(asmName, typName));
|
||||
}
|
||||
else if (typ.Contains("Generic"))
|
||||
{
|
||||
genType = BindToType(assemblyName, typ);
|
||||
}
|
||||
}
|
||||
if (genType != null && tmpTypes.Count > 0)
|
||||
{
|
||||
return genType.MakeGenericType(tmpTypes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
string toAssemblyName = assemblyName.Split(',')[0];
|
||||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
foreach (Assembly a in assemblies)
|
||||
{
|
||||
if (a.FullName.Split(',')[0] == toAssemblyName)
|
||||
{
|
||||
typeToDeserialize = a.GetType(typeName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
|
||||
cache.Add(assemblyName + typeName, typeToDeserialize);
|
||||
|
||||
return typeToDeserialize;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
public enum ObjType : byte
|
||||
{
|
||||
nullType,
|
||||
boolType,
|
||||
byteType,
|
||||
uint16Type,
|
||||
uint32Type,
|
||||
uint64Type,
|
||||
sbyteType,
|
||||
int16Type,
|
||||
int32Type,
|
||||
int64Type,
|
||||
charType,
|
||||
stringType,
|
||||
singleType,
|
||||
doubleType,
|
||||
decimalType,
|
||||
dateTimeType,
|
||||
byteArrayType,
|
||||
charArrayType,
|
||||
otherType,
|
||||
ILegacySerializableType
|
||||
}
|
||||
}
|
255
osu.Game/IO/Legacy/SerializationWriter.cs
Normal file
255
osu.Game/IO/Legacy/SerializationWriter.cs
Normal file
@ -0,0 +1,255 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Runtime.Serialization.Formatters;
|
||||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace osu.Game.IO.Legacy
|
||||
{
|
||||
/// <summary> SerializationWriter. Extends BinaryWriter to add additional data types,
|
||||
/// handle null strings and simplify use with ISerializable. </summary>
|
||||
public class SerializationWriter : BinaryWriter
|
||||
{
|
||||
public SerializationWriter(Stream s)
|
||||
: base(s, Encoding.UTF8)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary> Static method to initialise the writer with a suitable MemoryStream. </summary>
|
||||
public static SerializationWriter GetWriter()
|
||||
{
|
||||
MemoryStream ms = new MemoryStream(1024);
|
||||
return new SerializationWriter(ms);
|
||||
}
|
||||
|
||||
/// <summary> Writes a string to the buffer. Overrides the base implementation so it can cope with nulls </summary>
|
||||
public override void Write(string str)
|
||||
{
|
||||
if (str == null)
|
||||
{
|
||||
Write((byte)ObjType.nullType);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write((byte)ObjType.stringType);
|
||||
base.Write(str);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Writes a byte array to the buffer. Overrides the base implementation to
|
||||
/// send the length of the array which is needed when it is retrieved </summary>
|
||||
public override void Write(byte[] b)
|
||||
{
|
||||
if (b == null)
|
||||
{
|
||||
Write(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = b.Length;
|
||||
Write(len);
|
||||
if (len > 0) base.Write(b);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Writes a char array to the buffer. Overrides the base implementation to
|
||||
/// sends the length of the array which is needed when it is read. </summary>
|
||||
public override void Write(char[] c)
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
Write(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = c.Length;
|
||||
Write(len);
|
||||
if (len > 0) base.Write(c);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Writes a DateTime to the buffer. <summary>
|
||||
public void Write(DateTime dt)
|
||||
{
|
||||
Write(dt.ToUniversalTime().Ticks);
|
||||
}
|
||||
|
||||
/// <summary> Writes a generic ICollection (such as an IList<T>) to the buffer. </summary>
|
||||
public void Write<T>(List<T> c) where T : ILegacySerializable
|
||||
{
|
||||
if (c == null)
|
||||
{
|
||||
Write(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int count = c.Count;
|
||||
Write(count);
|
||||
for (int i = 0; i < count; i++)
|
||||
c[i].WriteToStream(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Writes a generic IDictionary to the buffer. </summary>
|
||||
public void Write<T, U>(IDictionary<T, U> d)
|
||||
{
|
||||
if (d == null)
|
||||
{
|
||||
Write(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(d.Count);
|
||||
foreach (KeyValuePair<T, U> kvp in d)
|
||||
{
|
||||
WriteObject(kvp.Key);
|
||||
WriteObject(kvp.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Writes an arbitrary object to the buffer. Useful where we have something of type "object"
|
||||
/// and don't know how to treat it. This works out the best method to use to write to the buffer. </summary>
|
||||
public void WriteObject(object obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
Write((byte)ObjType.nullType);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (obj.GetType().Name)
|
||||
{
|
||||
case "Boolean":
|
||||
Write((byte)ObjType.boolType);
|
||||
Write((bool)obj);
|
||||
break;
|
||||
|
||||
case "Byte":
|
||||
Write((byte)ObjType.byteType);
|
||||
Write((byte)obj);
|
||||
break;
|
||||
|
||||
case "UInt16":
|
||||
Write((byte)ObjType.uint16Type);
|
||||
Write((ushort)obj);
|
||||
break;
|
||||
|
||||
case "UInt32":
|
||||
Write((byte)ObjType.uint32Type);
|
||||
Write((uint)obj);
|
||||
break;
|
||||
|
||||
case "UInt64":
|
||||
Write((byte)ObjType.uint64Type);
|
||||
Write((ulong)obj);
|
||||
break;
|
||||
|
||||
case "SByte":
|
||||
Write((byte)ObjType.sbyteType);
|
||||
Write((sbyte)obj);
|
||||
break;
|
||||
|
||||
case "Int16":
|
||||
Write((byte)ObjType.int16Type);
|
||||
Write((short)obj);
|
||||
break;
|
||||
|
||||
case "Int32":
|
||||
Write((byte)ObjType.int32Type);
|
||||
Write((int)obj);
|
||||
break;
|
||||
|
||||
case "Int64":
|
||||
Write((byte)ObjType.int64Type);
|
||||
Write((long)obj);
|
||||
break;
|
||||
|
||||
case "Char":
|
||||
Write((byte)ObjType.charType);
|
||||
base.Write((char)obj);
|
||||
break;
|
||||
|
||||
case "String":
|
||||
Write((byte)ObjType.stringType);
|
||||
base.Write((string)obj);
|
||||
break;
|
||||
|
||||
case "Single":
|
||||
Write((byte)ObjType.singleType);
|
||||
Write((float)obj);
|
||||
break;
|
||||
|
||||
case "Double":
|
||||
Write((byte)ObjType.doubleType);
|
||||
Write((double)obj);
|
||||
break;
|
||||
|
||||
case "Decimal":
|
||||
Write((byte)ObjType.decimalType);
|
||||
Write((decimal)obj);
|
||||
break;
|
||||
|
||||
case "DateTime":
|
||||
Write((byte)ObjType.dateTimeType);
|
||||
Write((DateTime)obj);
|
||||
break;
|
||||
|
||||
case "Byte[]":
|
||||
Write((byte)ObjType.byteArrayType);
|
||||
base.Write((byte[])obj);
|
||||
break;
|
||||
|
||||
case "Char[]":
|
||||
Write((byte)ObjType.charArrayType);
|
||||
base.Write((char[])obj);
|
||||
break;
|
||||
|
||||
default:
|
||||
Write((byte)ObjType.otherType);
|
||||
BinaryFormatter b = new BinaryFormatter();
|
||||
b.AssemblyFormat = FormatterAssemblyStyle.Simple;
|
||||
b.TypeFormat = FormatterTypeStyle.TypesWhenNeeded;
|
||||
b.Serialize(BaseStream, obj);
|
||||
break;
|
||||
} // switch
|
||||
} // if obj==null
|
||||
} // WriteObject
|
||||
|
||||
/// <summary> Adds the SerializationWriter buffer to the SerializationInfo at the end of GetObjectData(). </summary>
|
||||
public void AddToInfo(SerializationInfo info)
|
||||
{
|
||||
byte[] b = ((MemoryStream)BaseStream).ToArray();
|
||||
info.AddValue("X", b, typeof(byte[]));
|
||||
}
|
||||
|
||||
public void WriteRawBytes(byte[] b)
|
||||
{
|
||||
base.Write(b);
|
||||
}
|
||||
|
||||
public void WriteByteArray(byte[] b)
|
||||
{
|
||||
if (b == null)
|
||||
{
|
||||
Write(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int len = b.Length;
|
||||
Write(len);
|
||||
if (len > 0) base.Write(b);
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteUtf8(string str)
|
||||
{
|
||||
WriteRawBytes(Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
}
|
||||
}
|
43
osu.Game/IPC/BeatmapIPCChannel.cs
Normal file
43
osu.Game/IPC/BeatmapIPCChannel.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.IPC
|
||||
{
|
||||
public class BeatmapIPCChannel : IpcChannel<BeatmapImportMessage>
|
||||
{
|
||||
private BeatmapDatabase beatmaps;
|
||||
|
||||
public BeatmapIPCChannel(IIpcHost host, BeatmapDatabase beatmaps = null)
|
||||
: base(host)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
MessageReceived += (msg) =>
|
||||
{
|
||||
Debug.Assert(beatmaps != null);
|
||||
ImportAsync(msg.Path);
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ImportAsync(string path)
|
||||
{
|
||||
if (beatmaps == null)
|
||||
{
|
||||
//we want to contact a remote osu! to handle the import.
|
||||
await SendMessageAsync(new BeatmapImportMessage { Path = path });
|
||||
return;
|
||||
}
|
||||
|
||||
beatmaps.Import(path);
|
||||
}
|
||||
}
|
||||
|
||||
public class BeatmapImportMessage
|
||||
{
|
||||
public string Path;
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.IPC
|
||||
{
|
||||
public class BeatmapImporter : IDisposable
|
||||
{
|
||||
private IpcChannel<BeatmapImportMessage> channel;
|
||||
private BeatmapDatabase beatmaps;
|
||||
|
||||
public BeatmapImporter(GameHost host, BeatmapDatabase beatmaps = null)
|
||||
{
|
||||
this.beatmaps = beatmaps;
|
||||
|
||||
channel = new IpcChannel<BeatmapImportMessage>(host);
|
||||
channel.MessageReceived += messageReceived;
|
||||
}
|
||||
|
||||
public async Task ImportAsync(string path)
|
||||
{
|
||||
if (beatmaps != null)
|
||||
beatmaps.Import(path);
|
||||
else
|
||||
{
|
||||
await channel.SendMessageAsync(new BeatmapImportMessage { Path = path });
|
||||
}
|
||||
}
|
||||
|
||||
private void messageReceived(BeatmapImportMessage msg)
|
||||
{
|
||||
Debug.Assert(beatmaps != null);
|
||||
|
||||
ImportAsync(msg.Path).ContinueWith(t => Logger.Error(t.Exception, @"error during async import"), TaskContinuationOptions.OnlyOnFaulted);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class BeatmapImportMessage
|
||||
{
|
||||
public string Path;
|
||||
}
|
||||
}
|
43
osu.Game/IPC/ScoreIPCChannel.cs
Normal file
43
osu.Game/IPC/ScoreIPCChannel.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.IPC
|
||||
{
|
||||
public class ScoreIPCChannel : IpcChannel<ScoreImportMessage>
|
||||
{
|
||||
private ScoreDatabase scores;
|
||||
|
||||
public ScoreIPCChannel(IIpcHost host, ScoreDatabase scores = null)
|
||||
: base(host)
|
||||
{
|
||||
this.scores = scores;
|
||||
MessageReceived += (msg) =>
|
||||
{
|
||||
Debug.Assert(scores != null);
|
||||
ImportAsync(msg.Path);
|
||||
};
|
||||
}
|
||||
|
||||
public async Task ImportAsync(string path)
|
||||
{
|
||||
if (scores == null)
|
||||
{
|
||||
//we want to contact a remote osu! to handle the import.
|
||||
await SendMessageAsync(new ScoreImportMessage { Path = path });
|
||||
return;
|
||||
}
|
||||
|
||||
scores.ReadReplayFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
public class ScoreImportMessage
|
||||
{
|
||||
public string Path;
|
||||
}
|
||||
}
|
33
osu.Game/Input/Handlers/ReplayInputHandler.cs
Normal file
33
osu.Game/Input/Handlers/ReplayInputHandler.cs
Normal file
@ -0,0 +1,33 @@
|
||||
// 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 osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Platform;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Input.Handlers
|
||||
{
|
||||
public abstract class ReplayInputHandler : InputHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// A function provided to convert replay coordinates from gamefield to screen space.
|
||||
/// </summary>
|
||||
public Func<Vector2, Vector2> ToScreenSpace { protected get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Update the current frame based on an incoming time value.
|
||||
/// There are cases where we return a "must-use" time value that is different from the input.
|
||||
/// This is to ensure accurate playback of replay data.
|
||||
/// </summary>
|
||||
/// <param name="time">The time which we should use for finding the current frame.</param>
|
||||
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
||||
public abstract double? SetFrameFromTime(double time);
|
||||
|
||||
public override bool Initialize(GameHost host) => true;
|
||||
|
||||
public override bool IsActive => true;
|
||||
|
||||
public override int Priority => 0;
|
||||
}
|
||||
}
|
274
osu.Game/Modes/LegacyReplay.cs
Normal file
274
osu.Game/Modes/LegacyReplay.cs
Normal file
@ -0,0 +1,274 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Input.Handlers;
|
||||
using osu.Game.IO.Legacy;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
using KeyboardState = osu.Framework.Input.KeyboardState;
|
||||
using MouseState = osu.Framework.Input.MouseState;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
public class LegacyReplay : Replay
|
||||
{
|
||||
protected List<LegacyReplayFrame> Frames = new List<LegacyReplayFrame>();
|
||||
|
||||
protected LegacyReplay()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LegacyReplay(StreamReader reader)
|
||||
{
|
||||
float lastTime = 0;
|
||||
|
||||
foreach (var l in reader.ReadToEnd().Split(','))
|
||||
{
|
||||
var split = l.Split('|');
|
||||
|
||||
if (split.Length < 4 || float.Parse(split[0]) < 0) continue;
|
||||
|
||||
lastTime += float.Parse(split[0]);
|
||||
|
||||
Frames.Add(new LegacyReplayFrame(
|
||||
lastTime,
|
||||
float.Parse(split[1]),
|
||||
384 - float.Parse(split[2]),
|
||||
(LegacyButtonState)int.Parse(split[3])
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
public override ReplayInputHandler GetInputHandler() => new LegacyReplayInputHandler(Frames);
|
||||
|
||||
/// <summary>
|
||||
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
|
||||
/// It handles logic of any frames which *must* be executed.
|
||||
/// </summary>
|
||||
public class LegacyReplayInputHandler : ReplayInputHandler
|
||||
{
|
||||
private readonly List<LegacyReplayFrame> replayContent;
|
||||
|
||||
public LegacyReplayFrame CurrentFrame => !hasFrames ? null : replayContent[currentFrameIndex];
|
||||
public LegacyReplayFrame NextFrame => !hasFrames ? null : replayContent[nextFrameIndex];
|
||||
|
||||
int currentFrameIndex;
|
||||
|
||||
private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, replayContent.Count - 1);
|
||||
|
||||
public LegacyReplayInputHandler(List<LegacyReplayFrame> replayContent)
|
||||
{
|
||||
this.replayContent = replayContent;
|
||||
}
|
||||
|
||||
private bool advanceFrame()
|
||||
{
|
||||
int newFrame = nextFrameIndex;
|
||||
|
||||
//ensure we aren't at an extent.
|
||||
if (newFrame == currentFrameIndex) return false;
|
||||
|
||||
currentFrameIndex = newFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetPosition(Vector2 pos)
|
||||
{
|
||||
}
|
||||
|
||||
private Vector2? position
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!hasFrames)
|
||||
return null;
|
||||
|
||||
return Interpolation.ValueAt(currentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
|
||||
}
|
||||
}
|
||||
|
||||
public override List<InputState> GetPendingStates()
|
||||
{
|
||||
return new List<InputState>
|
||||
{
|
||||
new InputState
|
||||
{
|
||||
Mouse = new ReplayMouseState(
|
||||
ToScreenSpace(position ?? Vector2.Zero),
|
||||
new List<MouseState.ButtonState>
|
||||
{
|
||||
new MouseState.ButtonState(MouseButton.Left)
|
||||
{
|
||||
State = CurrentFrame?.MouseLeft ?? false
|
||||
},
|
||||
new MouseState.ButtonState(MouseButton.Right)
|
||||
{
|
||||
State = CurrentFrame?.MouseRight ?? false
|
||||
},
|
||||
}
|
||||
),
|
||||
Keyboard = new ReplayKeyboardState(new List<Key>())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AtLastFrame => currentFrameIndex == replayContent.Count - 1;
|
||||
public bool AtFirstFrame => currentFrameIndex == 0;
|
||||
|
||||
public Vector2 Size => new Vector2(512, 384);
|
||||
|
||||
private const double sixty_frame_time = 1000.0 / 60;
|
||||
|
||||
double currentTime;
|
||||
int currentDirection;
|
||||
|
||||
/// <summary>
|
||||
/// When set, we will ensure frames executed by nested drawables are frame-accurate to replay data.
|
||||
/// Disabling this can make replay playback smoother (useful for autoplay, currently).
|
||||
/// </summary>
|
||||
public bool FrameAccuratePlayback = true;
|
||||
|
||||
private bool hasFrames => replayContent.Count > 0;
|
||||
|
||||
bool inImportantSection =>
|
||||
FrameAccuratePlayback &&
|
||||
//a button is in a pressed state
|
||||
(currentDirection > 0 ? CurrentFrame : NextFrame)?.ButtonState > LegacyButtonState.None &&
|
||||
//the next frame is within an allowable time span
|
||||
Math.Abs(currentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2;
|
||||
|
||||
/// <summary>
|
||||
/// Update the current frame based on an incoming time value.
|
||||
/// There are cases where we return a "must-use" time value that is different from the input.
|
||||
/// This is to ensure accurate playback of replay data.
|
||||
/// </summary>
|
||||
/// <param name="time">The time which we should use for finding the current frame.</param>
|
||||
/// <returns>The usable time value. If null, we should not advance time as we do not have enough data.</returns>
|
||||
public override double? SetFrameFromTime(double time)
|
||||
{
|
||||
currentDirection = time.CompareTo(currentTime);
|
||||
if (currentDirection == 0) currentDirection = 1;
|
||||
|
||||
if (hasFrames)
|
||||
{
|
||||
//if we changed frames, we want to execute once *exactly* on the frame's time.
|
||||
if (currentDirection == time.CompareTo(NextFrame.Time) && advanceFrame())
|
||||
return currentTime = CurrentFrame.Time;
|
||||
|
||||
//if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null.
|
||||
if (inImportantSection)
|
||||
return null;
|
||||
}
|
||||
|
||||
return currentTime = time;
|
||||
}
|
||||
|
||||
private class ReplayMouseState : MouseState
|
||||
{
|
||||
public ReplayMouseState(Vector2 position, List<ButtonState> list)
|
||||
{
|
||||
Position = position;
|
||||
ButtonStates = list;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReplayKeyboardState : KeyboardState
|
||||
{
|
||||
public ReplayKeyboardState(List<Key> keys)
|
||||
{
|
||||
Keys = keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum LegacyButtonState
|
||||
{
|
||||
None = 0,
|
||||
Left1 = 1,
|
||||
Right1 = 2,
|
||||
Left2 = 4,
|
||||
Right2 = 8,
|
||||
Smoke = 16
|
||||
}
|
||||
|
||||
public class LegacyReplayFrame
|
||||
{
|
||||
public Vector2 Position => new Vector2(MouseX, MouseY);
|
||||
|
||||
public float MouseX;
|
||||
public float MouseY;
|
||||
public bool MouseLeft;
|
||||
public bool MouseRight;
|
||||
public bool MouseLeft1;
|
||||
public bool MouseRight1;
|
||||
public bool MouseLeft2;
|
||||
public bool MouseRight2;
|
||||
public LegacyButtonState ButtonState;
|
||||
public double Time;
|
||||
|
||||
public LegacyReplayFrame(double time, float posX, float posY, LegacyButtonState buttonState)
|
||||
{
|
||||
MouseX = posX;
|
||||
MouseY = posY;
|
||||
ButtonState = buttonState;
|
||||
SetButtonStates(buttonState);
|
||||
Time = time;
|
||||
}
|
||||
|
||||
public void SetButtonStates(LegacyButtonState buttonState)
|
||||
{
|
||||
ButtonState = buttonState;
|
||||
MouseLeft = (buttonState & (LegacyButtonState.Left1 | LegacyButtonState.Left2)) > 0;
|
||||
MouseLeft1 = (buttonState & LegacyButtonState.Left1) > 0;
|
||||
MouseLeft2 = (buttonState & LegacyButtonState.Left2) > 0;
|
||||
MouseRight = (buttonState & (LegacyButtonState.Right1 | LegacyButtonState.Right2)) > 0;
|
||||
MouseRight1 = (buttonState & LegacyButtonState.Right1) > 0;
|
||||
MouseRight2 = (buttonState & LegacyButtonState.Right2) > 0;
|
||||
}
|
||||
|
||||
public LegacyReplayFrame(Stream s) : this(new SerializationReader(s))
|
||||
{
|
||||
}
|
||||
|
||||
public LegacyReplayFrame(SerializationReader sr)
|
||||
{
|
||||
ButtonState = (LegacyButtonState)sr.ReadByte();
|
||||
SetButtonStates(ButtonState);
|
||||
|
||||
byte bt = sr.ReadByte();
|
||||
if (bt > 0)//Handle Pre-Taiko compatible replays.
|
||||
SetButtonStates(LegacyButtonState.Right1);
|
||||
|
||||
MouseX = sr.ReadSingle();
|
||||
MouseY = sr.ReadSingle();
|
||||
Time = sr.ReadInt32();
|
||||
}
|
||||
|
||||
public void ReadFromStream(SerializationReader sr)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void WriteToStream(SerializationWriter sw)
|
||||
{
|
||||
sw.Write((byte)ButtonState);
|
||||
sw.Write((byte)0);
|
||||
sw.Write(MouseX);
|
||||
sw.Write(MouseY);
|
||||
sw.Write(Time);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Time}\t({MouseX},{MouseY})\t{MouseLeft}\t{MouseRight}\t{MouseLeft1}\t{MouseRight1}\t{MouseLeft2}\t{MouseRight2}\t{ButtonState}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
@ -40,6 +41,12 @@ namespace osu.Game.Modes
|
||||
/// The mods this mod cannot be enabled with.
|
||||
/// </summary>
|
||||
public virtual Type[] IncompatibleMods => new Type[] { };
|
||||
|
||||
/// <summary>
|
||||
/// Direct access to the Player before load has run.
|
||||
/// </summary>
|
||||
/// <param name="player"></param>
|
||||
public virtual void PlayerLoading(Player player) { }
|
||||
}
|
||||
|
||||
public class MultiMod : Mod
|
||||
@ -144,6 +151,12 @@ namespace osu.Game.Modes
|
||||
public override string Description => "Watch a perfect automated play through the song";
|
||||
public override double ScoreMultiplier => 0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModPerfect) };
|
||||
|
||||
public override void PlayerLoading(Player player)
|
||||
{
|
||||
base.PlayerLoading(player);
|
||||
player.ReplayInputHandler = Ruleset.GetRuleset(player.Beatmap.PlayMode).CreateAutoplayScore(player.Beatmap.Beatmap)?.Replay?.GetInputHandler();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ModPerfect : ModSuddenDeath
|
||||
|
@ -16,8 +16,6 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawableHitObject : Container, IStateful<ArmedState>
|
||||
{
|
||||
public event Action<DrawableHitObject, JudgementInfo> OnJudgement;
|
||||
|
||||
public override bool HandleInput => Interactive;
|
||||
|
||||
public bool Interactive = true;
|
||||
@ -26,12 +24,7 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
|
||||
protected abstract JudgementInfo CreateJudgementInfo();
|
||||
|
||||
public HitObject HitObject;
|
||||
|
||||
protected DrawableHitObject(HitObject hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
}
|
||||
protected abstract void UpdateState(ArmedState state);
|
||||
|
||||
private ArmedState state;
|
||||
public ArmedState State
|
||||
@ -52,23 +45,11 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
private SampleChannel sample;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
SampleType type = HitObject.Sample?.Type ?? SampleType.None;
|
||||
if (type == SampleType.None)
|
||||
type = SampleType.Normal;
|
||||
string hitType = type.ToString().ToLower();
|
||||
string sampleSet = (HitObject.Sample?.Set ?? SampleSet.Normal).ToString().ToLower();
|
||||
|
||||
sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}");
|
||||
}
|
||||
protected SampleChannel Sample;
|
||||
|
||||
protected virtual void PlaySample()
|
||||
{
|
||||
sample?.Play();
|
||||
Sample?.Play();
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -84,18 +65,18 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
|
||||
Expire(true);
|
||||
}
|
||||
}
|
||||
|
||||
private List<DrawableHitObject> nestedHitObjects;
|
||||
|
||||
protected IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects;
|
||||
|
||||
protected void AddNested(DrawableHitObject h)
|
||||
public abstract class DrawableHitObject<HitObjectType> : DrawableHitObject
|
||||
where HitObjectType : HitObject
|
||||
{
|
||||
if (nestedHitObjects == null)
|
||||
nestedHitObjects = new List<DrawableHitObject>();
|
||||
public event Action<DrawableHitObject<HitObjectType>, JudgementInfo> OnJudgement;
|
||||
|
||||
h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ;
|
||||
nestedHitObjects.Add(h);
|
||||
public HitObjectType HitObject;
|
||||
|
||||
public DrawableHitObject(HitObjectType hitObject)
|
||||
{
|
||||
HitObject = hitObject;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -145,7 +126,27 @@ namespace osu.Game.Modes.Objects.Drawables
|
||||
UpdateJudgement(false);
|
||||
}
|
||||
|
||||
protected abstract void UpdateState(ArmedState state);
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
string hitType = ((HitObject.Sample?.Type ?? SampleType.None) == SampleType.None ? SampleType.Normal : HitObject.Sample.Type).ToString().ToLower();
|
||||
string sampleSet = (HitObject.Sample?.Set ?? SampleSet.Normal).ToString().ToLower();
|
||||
|
||||
Sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}");
|
||||
}
|
||||
|
||||
private List<DrawableHitObject<HitObjectType>> nestedHitObjects;
|
||||
|
||||
protected IEnumerable<DrawableHitObject<HitObjectType>> NestedHitObjects => nestedHitObjects;
|
||||
|
||||
protected void AddNested(DrawableHitObject<HitObjectType> h)
|
||||
{
|
||||
if (nestedHitObjects == null)
|
||||
nestedHitObjects = new List<DrawableHitObject<HitObjectType>>();
|
||||
|
||||
h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ;
|
||||
nestedHitObjects.Add(h);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ArmedState
|
||||
|
12
osu.Game/Modes/Replay.cs
Normal file
12
osu.Game/Modes/Replay.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Input.Handlers;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
public abstract class Replay
|
||||
{
|
||||
public virtual ReplayInputHandler GetInputHandler() => null;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
@ -28,9 +29,9 @@ namespace osu.Game.Modes
|
||||
|
||||
public abstract IEnumerable<Mod> GetModsFor(ModType type);
|
||||
|
||||
public abstract ScoreProcessor CreateScoreProcessor(int hitObjectCount);
|
||||
public abstract ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0);
|
||||
|
||||
public abstract HitRenderer CreateHitRendererWith(Beatmap beatmap);
|
||||
public abstract HitRenderer CreateHitRendererWith(Beatmap beatmap, PlayerInputManager input = null);
|
||||
|
||||
public abstract HitObjectParser CreateHitObjectParser();
|
||||
|
||||
@ -42,6 +43,8 @@ namespace osu.Game.Modes
|
||||
|
||||
public virtual FontAwesome Icon => FontAwesome.fa_question_circle;
|
||||
|
||||
public virtual Score CreateAutoplayScore(Beatmap beatmap) => null;
|
||||
|
||||
public static Ruleset GetRuleset(PlayMode mode)
|
||||
{
|
||||
Type type;
|
||||
@ -51,5 +54,6 @@ namespace osu.Game.Modes
|
||||
|
||||
return Activator.CreateInstance(type) as Ruleset;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Modes
|
||||
{
|
||||
public class Score
|
||||
@ -10,5 +12,8 @@ namespace osu.Game.Modes
|
||||
public double Combo { get; set; }
|
||||
public double MaxCombo { get; set; }
|
||||
public double Health { get; set; }
|
||||
|
||||
public Replay Replay;
|
||||
public BeatmapInfo Beatmap;
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Modes.Objects;
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.UI
|
||||
{
|
||||
@ -19,24 +20,28 @@ namespace osu.Game.Modes.UI
|
||||
|
||||
public event Action OnAllJudged;
|
||||
|
||||
public abstract bool AllObjectsJudged { get; }
|
||||
|
||||
protected void TriggerOnJudgement(JudgementInfo j)
|
||||
{
|
||||
OnJudgement?.Invoke(j);
|
||||
if (AllObjectsJudged)
|
||||
OnAllJudged?.Invoke();
|
||||
}
|
||||
|
||||
protected Playfield Playfield;
|
||||
|
||||
public bool AllObjectsJudged => Playfield.HitObjects.Children.First()?.Judgement.Result != null; //reverse depth sort means First() instead of Last().
|
||||
|
||||
public IEnumerable<DrawableHitObject> DrawableObjects => Playfield.HitObjects.Children;
|
||||
}
|
||||
|
||||
public abstract class HitRenderer<T> : HitRenderer
|
||||
where T : HitObject
|
||||
public abstract class HitRenderer<TObject> : HitRenderer
|
||||
where TObject : HitObject
|
||||
{
|
||||
private List<T> objects;
|
||||
private List<TObject> objects;
|
||||
|
||||
public PlayerInputManager InputManager;
|
||||
|
||||
protected Playfield<TObject> Playfield;
|
||||
|
||||
public override bool AllObjectsJudged => Playfield.HitObjects.Children.First()?.Judgement.Result != null; //reverse depth sort means First() instead of Last().
|
||||
|
||||
public IEnumerable<DrawableHitObject> DrawableObjects => Playfield.HitObjects.Children;
|
||||
|
||||
public Beatmap Beatmap
|
||||
{
|
||||
@ -48,11 +53,11 @@ namespace osu.Game.Modes.UI
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Playfield CreatePlayfield();
|
||||
protected abstract Playfield<TObject> CreatePlayfield();
|
||||
|
||||
protected abstract HitObjectConverter<T> Converter { get; }
|
||||
protected abstract HitObjectConverter<TObject> Converter { get; }
|
||||
|
||||
protected virtual List<T> Convert(Beatmap beatmap) => Converter.Convert(beatmap);
|
||||
protected virtual List<TObject> Convert(Beatmap beatmap) => Converter.Convert(beatmap);
|
||||
|
||||
protected HitRenderer()
|
||||
{
|
||||
@ -62,10 +67,10 @@ namespace osu.Game.Modes.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Playfield = CreatePlayfield()
|
||||
};
|
||||
Playfield = CreatePlayfield();
|
||||
Playfield.InputManager = InputManager;
|
||||
|
||||
Add(Playfield);
|
||||
|
||||
loadObjects();
|
||||
}
|
||||
@ -73,9 +78,9 @@ namespace osu.Game.Modes.UI
|
||||
private void loadObjects()
|
||||
{
|
||||
if (objects == null) return;
|
||||
foreach (T h in objects)
|
||||
foreach (TObject h in objects)
|
||||
{
|
||||
var drawableObject = GetVisualRepresentation(h);
|
||||
DrawableHitObject<TObject> drawableObject = GetVisualRepresentation(h);
|
||||
|
||||
if (drawableObject == null) continue;
|
||||
|
||||
@ -86,8 +91,8 @@ namespace osu.Game.Modes.UI
|
||||
Playfield.PostProcess();
|
||||
}
|
||||
|
||||
private void onJudgement(DrawableHitObject o, JudgementInfo j) => TriggerOnJudgement(j);
|
||||
private void onJudgement(DrawableHitObject<TObject> o, JudgementInfo j) => TriggerOnJudgement(j);
|
||||
|
||||
protected abstract DrawableHitObject GetVisualRepresentation(T h);
|
||||
protected abstract DrawableHitObject<TObject> GetVisualRepresentation(TObject h);
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,72 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Modes.Objects;
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Modes.UI
|
||||
{
|
||||
public abstract class Playfield : Container
|
||||
public abstract class Playfield<T> : Container
|
||||
where T : HitObject
|
||||
{
|
||||
public HitObjectContainer HitObjects;
|
||||
public HitObjectContainer<DrawableHitObject<T>> HitObjects;
|
||||
|
||||
public virtual void Add(DrawableHitObject h) => HitObjects.Add(h);
|
||||
public virtual void Add(DrawableHitObject<T> h) => HitObjects.Add(h);
|
||||
|
||||
public class HitObjectContainer<U> : Container<U>
|
||||
where U : Drawable
|
||||
{
|
||||
public override bool Contains(Vector2 screenSpacePos) => true;
|
||||
}
|
||||
|
||||
private Container<Drawable> scaledContent;
|
||||
|
||||
public override bool Contains(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
protected Playfield()
|
||||
public Playfield()
|
||||
{
|
||||
AddInternal(Content = new ScaledContainer
|
||||
AddInternal(scaledContent = new ScaledContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
Content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Add(HitObjects = new HitObjectContainer
|
||||
Add(HitObjects = new HitObjectContainer<DrawableHitObject<T>>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An optional inputManager to provide interactivity etc.
|
||||
/// </summary>
|
||||
public PlayerInputManager InputManager;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
if (InputManager != null)
|
||||
{
|
||||
//if we've been provided an InputManager, we want it to sit inside the scaledcontainer
|
||||
scaledContent.Remove(Content);
|
||||
scaledContent.Add(InputManager);
|
||||
InputManager.Add(Content);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void PostProcess()
|
||||
{
|
||||
}
|
||||
|
@ -23,9 +23,11 @@ using osu.Game.Screens.Menu;
|
||||
using OpenTK;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game
|
||||
{
|
||||
@ -81,7 +83,7 @@ namespace osu.Game
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
var paths = args.Where(a => !a.StartsWith(@"-"));
|
||||
ImportBeatmapsAsync(paths);
|
||||
Task.Run(() => BeatmapDatabase.Import(paths));
|
||||
}
|
||||
|
||||
Dependencies.Cache(this);
|
||||
@ -89,9 +91,41 @@ namespace osu.Game
|
||||
PlayMode = LocalConfig.GetBindable<PlayMode>(OsuConfig.PlayMode);
|
||||
}
|
||||
|
||||
protected async void ImportBeatmapsAsync(IEnumerable<string> paths)
|
||||
private ScheduledDelegate scoreLoad;
|
||||
|
||||
protected void LoadScore(Score s)
|
||||
{
|
||||
await Task.Run(() => BeatmapDatabase.Import(paths));
|
||||
scoreLoad?.Cancel();
|
||||
|
||||
var menu = intro.ChildScreen;
|
||||
|
||||
if (menu == null)
|
||||
{
|
||||
scoreLoad = Schedule(() => LoadScore(s));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!menu.IsCurrentScreen)
|
||||
{
|
||||
menu.MakeCurrent();
|
||||
Delay(500);
|
||||
scoreLoad = Schedule(() => LoadScore(s));
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.Beatmap == null)
|
||||
{
|
||||
notificationManager.Post(new SimpleNotification
|
||||
{
|
||||
Text = @"Tried to load a score for a beatmap we don't have!",
|
||||
Icon = FontAwesome.fa_life_saver,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Beatmap.Value = BeatmapDatabase.GetWorkingBeatmap(s.Beatmap);
|
||||
|
||||
menu.Push(new PlayerLoader(new Player { ReplayInputHandler = s.Replay.GetInputHandler() }));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game
|
||||
|
||||
protected BeatmapDatabase BeatmapDatabase;
|
||||
|
||||
protected ScoreDatabase ScoreDatabase;
|
||||
|
||||
protected override string MainResourceFile => @"osu.Game.Resources.dll";
|
||||
|
||||
public APIAccess API;
|
||||
@ -79,6 +81,7 @@ namespace osu.Game
|
||||
Dependencies.Cache(this);
|
||||
Dependencies.Cache(LocalConfig);
|
||||
Dependencies.Cache(BeatmapDatabase = new BeatmapDatabase(Host.Storage, Host));
|
||||
Dependencies.Cache(ScoreDatabase = new ScoreDatabase(Host.Storage, Host, BeatmapDatabase));
|
||||
Dependencies.Cache(new OsuColour());
|
||||
|
||||
//this completely overrides the framework default. will need to change once we make a proper FontStore.
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private FillFlowContainer<ModSection> modSectionsContainer;
|
||||
|
||||
public readonly Bindable<Mod[]> SelectedMods = new Bindable<Mod[]>();
|
||||
public readonly Bindable<IEnumerable<Mod>> SelectedMods = new Bindable<IEnumerable<Mod>>();
|
||||
|
||||
public readonly Bindable<PlayMode> PlayMode = new Bindable<PlayMode>();
|
||||
|
||||
|
@ -136,6 +136,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
FadeIn(100, EasingTypes.OutQuint);
|
||||
contentContainer.MoveToY(0, APPEAR_DURATION, EasingTypes.OutQuint);
|
||||
|
||||
FadeIn(100, EasingTypes.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
@ -147,6 +149,8 @@ namespace osu.Game.Overlays
|
||||
|
||||
foreach (var w in wavesContainer.Children)
|
||||
w.State = Visibility.Hidden;
|
||||
|
||||
FadeOut(DISAPPEAR_DURATION, EasingTypes.InQuint);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -4,12 +4,10 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Modes;
|
||||
using osu.Game.Modes.Objects.Drawables;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using OpenTK;
|
||||
using osu.Framework.Screens;
|
||||
@ -19,26 +17,24 @@ using osu.Game.Configuration;
|
||||
using osu.Framework.Configuration;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Input.Handlers;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class Player : OsuScreen
|
||||
{
|
||||
public bool Autoplay;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
|
||||
|
||||
internal override bool ShowOverlays => false;
|
||||
|
||||
public BeatmapInfo BeatmapInfo;
|
||||
|
||||
public PlayMode PreferredPlayMode;
|
||||
|
||||
public bool IsPaused { get; private set; }
|
||||
|
||||
public int RestartCount;
|
||||
@ -49,6 +45,7 @@ namespace osu.Game.Screens.Play
|
||||
private bool canPause => Time.Current >= lastPauseActionTime + pauseCooldown;
|
||||
|
||||
private IAdjustableClock sourceClock;
|
||||
private IFrameBasedClock interpolatedSourceClock;
|
||||
|
||||
private Ruleset ruleset;
|
||||
|
||||
@ -59,11 +56,21 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private ScoreOverlay scoreOverlay;
|
||||
private PauseOverlay pauseOverlay;
|
||||
private PlayerInputManager playerInputManager;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuGameBase game, OsuConfigManager config)
|
||||
{
|
||||
var beatmap = Beatmap.Beatmap;
|
||||
|
||||
if (beatmap.BeatmapInfo?.Mode > PlayMode.Osu)
|
||||
{
|
||||
//we only support osu! mode for now because the hitobject parsing is crappy and needs a refactor.
|
||||
Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
Beatmap.Mods.Value.ForEach(m => m.PlayerLoading(this));
|
||||
|
||||
dimLevel = config.GetBindable<int>(OsuConfig.DimLevel);
|
||||
mouseWheelDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableWheel);
|
||||
|
||||
@ -93,24 +100,14 @@ namespace osu.Game.Screens.Play
|
||||
}
|
||||
|
||||
sourceClock = (IAdjustableClock)track ?? new StopwatchClock();
|
||||
interpolatedSourceClock = new InterpolatingFramedClock(sourceClock);
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
sourceClock.Reset();
|
||||
});
|
||||
|
||||
var beatmap = Beatmap.Beatmap;
|
||||
|
||||
if (beatmap.BeatmapInfo?.Mode > PlayMode.Osu)
|
||||
{
|
||||
//we only support osu! mode for now because the hitobject parsing is crappy and needs a refactor.
|
||||
Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
PlayMode usablePlayMode = beatmap.BeatmapInfo?.Mode > PlayMode.Osu ? beatmap.BeatmapInfo.Mode : PreferredPlayMode;
|
||||
|
||||
ruleset = Ruleset.GetRuleset(usablePlayMode);
|
||||
ruleset = Ruleset.GetRuleset(Beatmap.PlayMode);
|
||||
|
||||
scoreOverlay = ruleset.CreateScoreOverlay();
|
||||
scoreOverlay.BindProcessor(scoreProcessor = ruleset.CreateScoreProcessor(beatmap.HitObjects.Count));
|
||||
@ -127,7 +124,10 @@ namespace osu.Game.Screens.Play
|
||||
OnQuit = Exit
|
||||
};
|
||||
|
||||
hitRenderer = ruleset.CreateHitRendererWith(beatmap);
|
||||
hitRenderer = ruleset.CreateHitRendererWith(beatmap, new PlayerInputManager
|
||||
{
|
||||
ReplayInputHandler = ReplayInputHandler
|
||||
});
|
||||
|
||||
//bind HitRenderer to ScoreProcessor and ourselves (for a pass situation)
|
||||
hitRenderer.OnJudgement += scoreProcessor.AddJudgement;
|
||||
@ -136,18 +136,19 @@ namespace osu.Game.Screens.Play
|
||||
//bind ScoreProcessor to ourselves (for a fail situation)
|
||||
scoreProcessor.Failed += onFail;
|
||||
|
||||
if (Autoplay)
|
||||
hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Hit));
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
playerInputManager = new PlayerInputManager
|
||||
new Container
|
||||
{
|
||||
Clock = new InterpolatingFramedClock(sourceClock),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Clock = interpolatedSourceClock,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
hitRenderer,
|
||||
skipButton = new SkipButton { Alpha = 0 },
|
||||
skipButton = new SkipButton
|
||||
{
|
||||
Alpha = 0
|
||||
},
|
||||
}
|
||||
},
|
||||
scoreOverlay,
|
||||
@ -295,6 +296,8 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (pauseOverlay == null) return false;
|
||||
|
||||
if (ReplayInputHandler != null) return false;
|
||||
|
||||
if (pauseOverlay.State != Visibility.Visible && !canPause) return true;
|
||||
|
||||
if (!IsPaused && sourceClock.IsRunning) // For if the user presses escape quickly when entering the map
|
||||
@ -320,6 +323,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private Bindable<bool> mouseWheelDisabled;
|
||||
|
||||
public ReplayInputHandler ReplayInputHandler;
|
||||
|
||||
protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value && !IsPaused;
|
||||
}
|
||||
}
|
@ -1,40 +1,75 @@
|
||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using OpenTK.Input;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Configuration;
|
||||
using System.Linq;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Input.Handlers;
|
||||
using OpenTK.Input;
|
||||
using KeyboardState = osu.Framework.Input.KeyboardState;
|
||||
using MouseState = osu.Framework.Input.MouseState;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
internal class PlayerInputManager : UserInputManager
|
||||
public class PlayerInputManager : PassThroughInputManager
|
||||
{
|
||||
private bool leftViaKeyboard;
|
||||
private bool rightViaKeyboard;
|
||||
private Bindable<bool> mouseDisabled;
|
||||
|
||||
private ManualClock clock = new ManualClock();
|
||||
private IFrameBasedClock parentClock;
|
||||
|
||||
private ReplayInputHandler replayInputHandler;
|
||||
public ReplayInputHandler ReplayInputHandler
|
||||
{
|
||||
get { return replayInputHandler; }
|
||||
set
|
||||
{
|
||||
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
|
||||
|
||||
replayInputHandler = value;
|
||||
UseParentState = replayInputHandler == null;
|
||||
|
||||
if (replayInputHandler != null)
|
||||
{
|
||||
replayInputHandler.ToScreenSpace = ToScreenSpace;
|
||||
AddHandler(replayInputHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
parentClock = Clock;
|
||||
Clock = new FramedClock(clock);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
mouseDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableButtons)
|
||||
?? new Bindable<bool>(false);
|
||||
mouseDisabled = config.GetBindable<bool>(OsuConfig.MouseDisableButtons);
|
||||
}
|
||||
|
||||
protected override void TransformState(InputState state)
|
||||
{
|
||||
base.TransformState(state);
|
||||
|
||||
if (state.Keyboard != null)
|
||||
var mouse = state.Mouse as MouseState;
|
||||
var keyboard = state.Keyboard as KeyboardState;
|
||||
|
||||
if (keyboard != null)
|
||||
{
|
||||
leftViaKeyboard = state.Keyboard.Keys.Contains(Key.Z);
|
||||
rightViaKeyboard = state.Keyboard.Keys.Contains(Key.X);
|
||||
leftViaKeyboard = keyboard.Keys.Contains(Key.Z);
|
||||
rightViaKeyboard = keyboard.Keys.Contains(Key.X);
|
||||
}
|
||||
|
||||
var mouse = (Framework.Input.MouseState)state.Mouse;
|
||||
if (state.Mouse != null)
|
||||
if (mouse != null)
|
||||
{
|
||||
if (mouseDisabled.Value)
|
||||
{
|
||||
@ -48,5 +83,38 @@ namespace osu.Game.Screens.Play
|
||||
mouse.ButtonStates.Find(s => s.Button == MouseButton.Right).State = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (parentClock == null) return;
|
||||
|
||||
clock.Rate = parentClock.Rate;
|
||||
clock.IsRunning = parentClock.IsRunning;
|
||||
|
||||
//if a replayHandler is not attached, we should just pass-through.
|
||||
if (UseParentState || replayInputHandler == null)
|
||||
{
|
||||
clock.CurrentTime = parentClock.CurrentTime;
|
||||
base.Update();
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime);
|
||||
|
||||
if (newTime == null)
|
||||
//we shouldn't execute for this time value
|
||||
break;
|
||||
|
||||
if (clock.CurrentTime == parentClock.CurrentTime)
|
||||
break;
|
||||
|
||||
clock.CurrentTime = newTime.Value;
|
||||
base.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,10 +159,11 @@ namespace osu.Game.Screens.Select
|
||||
if (player != null || Beatmap == null)
|
||||
return;
|
||||
|
||||
Beatmap.PreferredPlayMode = playMode.Value;
|
||||
|
||||
(player = new PlayerLoader(new Player
|
||||
{
|
||||
BeatmapInfo = carousel.SelectedGroup.SelectedPanel.Beatmap,
|
||||
PreferredPlayMode = playMode.Value
|
||||
Beatmap = Beatmap, //eagerly set this so it's present before push.
|
||||
})).LoadAsync(Game, l => Push(player));
|
||||
}
|
||||
},
|
||||
@ -336,6 +337,8 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
base.OnBeatmapChanged(beatmap);
|
||||
|
||||
beatmap.Mods.BindTo(modSelect.SelectedMods);
|
||||
|
||||
//todo: change background in selectionChanged instead; support per-difficulty backgrounds.
|
||||
changeBackground(beatmap);
|
||||
carousel.SelectBeatmap(beatmap?.BeatmapInfo);
|
||||
|
15
osu.Game/app.config
Normal file
15
osu.Game/app.config
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
-->
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
@ -43,6 +43,10 @@
|
||||
<HintPath>$(SolutionDir)\packages\ppy.OpenTK.2.0.50727.1340\lib\net45\OpenTK.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SharpCompress, Version=0.15.1.0, Culture=neutral, PublicKeyToken=afb0a02973931d96, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SharpCompress.0.15.1\lib\net45\SharpCompress.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLite.Net, Version=3.1.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)\packages\SQLite.Net.Core-PCL.3.1.1\lib\portable-win8+net45+wp8+wpa81+MonoAndroid1+MonoTouch1\SQLite.Net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
@ -69,6 +73,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
|
||||
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
|
||||
<Compile Include="Database\ScoreDatabase.cs" />
|
||||
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
|
||||
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
|
||||
<Compile Include="Graphics\Sprites\OsuSpriteText.cs" />
|
||||
@ -79,9 +84,16 @@
|
||||
<Compile Include="Graphics\UserInterface\OsuSliderBar.cs" />
|
||||
<Compile Include="Graphics\UserInterface\OsuTextBox.cs" />
|
||||
<Compile Include="Graphics\UserInterface\TwoLayerButton.cs" />
|
||||
<Compile Include="Input\Handlers\ReplayInputHandler.cs" />
|
||||
<Compile Include="IO\Legacy\ILegacySerializable.cs" />
|
||||
<Compile Include="IO\Legacy\SerializationReader.cs" />
|
||||
<Compile Include="IO\Legacy\SerializationWriter.cs" />
|
||||
<Compile Include="IPC\ScoreIPCChannel.cs" />
|
||||
<Compile Include="Modes\LegacyReplay.cs" />
|
||||
<Compile Include="Modes\Objects\Drawables\IDrawableHitObjectWithProxiedApproach.cs" />
|
||||
<Compile Include="Modes\Objects\HitObjectParser.cs" />
|
||||
<Compile Include="Modes\Objects\NullHitObjectParser.cs" />
|
||||
<Compile Include="Modes\Replay.cs" />
|
||||
<Compile Include="Modes\Score.cs" />
|
||||
<Compile Include="Modes\ScoreProcesssor.cs" />
|
||||
<Compile Include="Modes\UI\HealthDisplay.cs" />
|
||||
@ -188,7 +200,7 @@
|
||||
<Compile Include="Graphics\UserInterface\PercentageCounter.cs" />
|
||||
<Compile Include="Graphics\UserInterface\ScoreCounter.cs" />
|
||||
<Compile Include="Graphics\UserInterface\StarCounter.cs" />
|
||||
<Compile Include="IPC\BeatmapImporter.cs" />
|
||||
<Compile Include="IPC\BeatmapIPCChannel.cs" />
|
||||
<Compile Include="Online\API\APIAccess.cs" />
|
||||
<Compile Include="Online\API\APIRequest.cs" />
|
||||
<Compile Include="Online\API\OAuth.cs" />
|
||||
@ -326,6 +338,7 @@
|
||||
<None Include="..\osu.licenseheader">
|
||||
<Link>osu.licenseheader</Link>
|
||||
</None>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
|
@ -7,6 +7,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
|
||||
<package id="DotNetZip" version="1.10.1" targetFramework="net45" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net45" />
|
||||
<package id="ppy.OpenTK" version="2.0.50727.1340" targetFramework="net45" />
|
||||
<package id="SharpCompress" version="0.15.1" targetFramework="net45" />
|
||||
<package id="SQLite.Net.Core-PCL" version="3.1.1" targetFramework="net45" />
|
||||
<package id="SQLite.Net-PCL" version="3.1.1" targetFramework="net45" />
|
||||
<package id="SQLiteNetExtensions" version="1.3.0" targetFramework="net45" />
|
||||
|
Loading…
Reference in New Issue
Block a user