1
0
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:
Dean Herbert 2017-03-07 14:41:23 +09:00 committed by GitHub
commit 2fc72b370d
62 changed files with 1944 additions and 242 deletions

View File

@ -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)

View 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;
}
}
}

View File

@ -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" />

View File

@ -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" />

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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">

View File

@ -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>

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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()
{

View File

@ -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();

View File

@ -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)

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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)
{

View 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!";
}
}
}

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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)

View File

@ -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);
}

View File

@ -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" />

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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()
{

View File

@ -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");

View File

@ -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();

View File

@ -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;

View File

@ -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; }

View 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;
}
}
}

View File

@ -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();

View File

@ -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;

View 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);
}
}

View 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
}
}

View 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));
}
}
}

View 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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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}";
}
}
}
}

View File

@ -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

View File

@ -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
View 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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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()
{
}

View File

@ -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()

View File

@ -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.

View File

@ -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>();

View File

@ -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()

View File

@ -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;
}
}

View File

@ -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();
}
}
}
}

View File

@ -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
View 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>

View File

@ -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 />

View File

@ -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" />