1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 06:52:56 +08:00

Merge branch 'master' into leaderboards

This commit is contained in:
Dean Herbert 2017-03-16 11:12:53 +09:00 committed by GitHub
commit a22b34817c
96 changed files with 1190 additions and 565 deletions

@ -1 +1 @@
Subproject commit 8c79c94aa71711cf9c8bb169f684df2e35596f9d
Subproject commit 036b124eb2387dde29af56e06391c42063ec85e2

View File

@ -48,6 +48,7 @@ namespace osu.Desktop.VisualTests.Tests
HitObjects = objects,
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BaseDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",

View File

@ -1,19 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using OpenTK.Graphics;
using osu.Framework.Screens.Testing;
using osu.Framework.Timing;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Judgements;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.Objects.Drawables;
using System.Collections.Generic;
namespace osu.Desktop.VisualTests.Tests
{
@ -60,12 +62,15 @@ namespace osu.Desktop.VisualTests.Tests
add(new DrawableSlider(new Slider
{
StartTime = framedClock.CurrentTime + 600,
ControlPoints = new List<Vector2>
CurveObject = new CurvedHitObject
{
new Vector2(-200, 0),
new Vector2(400, 0),
ControlPoints = new List<Vector2>
{
new Vector2(-200, 0),
new Vector2(400, 0),
},
Distance = 400
},
Length = 400,
Position = new Vector2(-200, 0),
Velocity = 1,
TickDistance = 100,
@ -75,7 +80,7 @@ namespace osu.Desktop.VisualTests.Tests
add(new DrawableSpinner(new Spinner
{
StartTime = framedClock.CurrentTime + 600,
Length = 1000,
EndTime = framedClock.CurrentTime + 1600,
Position = new Vector2(0, 0),
}));
break;
@ -123,7 +128,7 @@ namespace osu.Desktop.VisualTests.Tests
private int depth;
private void add(DrawableHitObject h)
private void add(DrawableOsuHitObject h)
{
h.Anchor = Anchor.Centre;
h.Depth = depth++;

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Screens.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using OpenTK;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps.IO;
@ -61,13 +60,12 @@ namespace osu.Desktop.VisualTests.Tests
time += 500;
}
var decoder = new ConstructableBeatmapDecoder();
Beatmap b = new Beatmap
{
HitObjects = objects,
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BaseDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
@ -77,8 +75,6 @@ namespace osu.Desktop.VisualTests.Tests
}
};
decoder.Process(b);
beatmap = new TestWorkingBeatmap(b);
}

View File

@ -1,5 +1,4 @@
<?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

View File

@ -23,7 +23,7 @@
<SignAssembly>false</SignAssembly>
<TargetZone>LocalIntranet</TargetZone>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
@ -102,10 +102,6 @@
<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, 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">
<HintPath>$(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll</HintPath>
<Private>True</Private>

View File

@ -1,5 +1,4 @@
<?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
@ -8,7 +7,6 @@ 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

@ -1,7 +1,6 @@
// 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.Beatmaps;
using osu.Game.Modes.Catch.Objects;
using System.Collections.Generic;

View File

@ -0,0 +1,19 @@
// 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.Beatmaps;
using osu.Game.Modes.Catch.Objects;
namespace osu.Game.Modes.Catch.Beatmaps
{
internal class CatchBeatmapProcessor : IBeatmapProcessor<CatchBaseHit>
{
public void SetDefaults(CatchBaseHit hitObject, Beatmap<CatchBaseHit> beatmap)
{
}
public void PostProcess(Beatmap<CatchBaseHit> beatmap)
{
}
}
}

View File

@ -7,7 +7,6 @@ using osu.Game.Graphics;
using osu.Game.Modes.Catch.Mods;
using osu.Game.Modes.Catch.UI;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
@ -90,8 +89,6 @@ namespace osu.Game.Modes.Catch
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
}
}

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
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes.Catch.Judgements
{
public class CatchJudgementInfo : JudgementInfo
{
}
}

View File

@ -7,6 +7,6 @@ namespace osu.Game.Modes.Catch.Objects
{
public abstract class CatchBaseHit : HitObject
{
public float Position;
public float Position { get; set; }
}
}

View File

@ -29,8 +29,10 @@ namespace osu.Game.Modes.Catch.Objects.Drawable
{
Texture = textures.Get(@"Menu/logo");
double duration = 0;
Transforms.Add(new TransformPosition { StartTime = h.StartTime - 200, EndTime = h.StartTime, StartValue = new Vector2(h.Position, -0.1f), EndValue = new Vector2(h.Position, 0.9f) });
Transforms.Add(new TransformAlpha { StartTime = h.StartTime + h.Duration + 200, EndTime = h.StartTime + h.Duration + 400, StartValue = 1, EndValue = 0 });
Transforms.Add(new TransformAlpha { StartTime = h.StartTime + duration + 200, EndTime = h.StartTime + duration + 400, StartValue = 1, EndValue = 0 });
Expire(true);
}
}

View File

@ -3,13 +3,14 @@
using osu.Game.Beatmaps;
using osu.Game.Modes.Catch.Beatmaps;
using osu.Game.Modes.Catch.Judgements;
using osu.Game.Modes.Catch.Objects;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.UI;
namespace osu.Game.Modes.Catch.UI
{
public class CatchHitRenderer : HitRenderer<CatchBaseHit>
public class CatchHitRenderer : HitRenderer<CatchBaseHit, CatchJudgementInfo>
{
public CatchHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
@ -18,8 +19,10 @@ namespace osu.Game.Modes.Catch.UI
protected override IBeatmapConverter<CatchBaseHit> CreateBeatmapConverter() => new CatchBeatmapConverter();
protected override Playfield<CatchBaseHit> CreatePlayfield() => new CatchPlayfield();
protected override IBeatmapProcessor<CatchBaseHit> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override Playfield<CatchBaseHit, CatchJudgementInfo> CreatePlayfield() => new CatchPlayfield();
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h) => null;// new DrawableFruit(h);
protected override DrawableHitObject<CatchBaseHit, CatchJudgementInfo> GetVisualRepresentation(CatchBaseHit h) => null;
}
}

View File

@ -6,10 +6,11 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Modes.Catch.Objects;
using osu.Game.Modes.UI;
using OpenTK;
using osu.Game.Modes.Catch.Judgements;
namespace osu.Game.Modes.Catch.UI
{
public class CatchPlayfield : Playfield<CatchBaseHit>
public class CatchPlayfield : Playfield<CatchBaseHit, CatchJudgementInfo>
{
public CatchPlayfield()
{

View File

@ -48,7 +48,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\CatchBeatmapConverter.cs" />
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="Judgements\CatchJudgementInfo.cs" />
<Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" />
<Compile Include="Objects\Droplet.cs" />

View File

@ -1,5 +1,4 @@
<?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

View File

@ -0,0 +1,19 @@
// 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.Beatmaps;
using osu.Game.Modes.Mania.Objects;
namespace osu.Game.Modes.Mania.Beatmaps
{
internal class ManiaBeatmapProcessor : IBeatmapProcessor<ManiaBaseHit>
{
public void SetDefaults(ManiaBaseHit hitObject, Beatmap<ManiaBaseHit> beatmap)
{
}
public void PostProcess(Beatmap<ManiaBaseHit> beatmap)
{
}
}
}

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
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes.Mania.Judgements
{
public class ManiaJudgementInfo : JudgementInfo
{
}
}

View File

@ -6,7 +6,6 @@ using osu.Game.Graphics;
using osu.Game.Modes.Mania.Mods;
using osu.Game.Modes.Mania.UI;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
@ -105,8 +104,6 @@ namespace osu.Game.Modes.Mania
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
}
}

View File

@ -26,8 +26,10 @@ namespace osu.Game.Modes.Mania.Objects.Drawable
{
Texture = textures.Get(@"Menu/logo");
double duration = 0;
Transforms.Add(new TransformPositionY { StartTime = note.StartTime - 200, EndTime = note.StartTime, StartValue = -0.1f, EndValue = 0.9f });
Transforms.Add(new TransformAlpha { StartTime = note.StartTime + note.Duration + 200, EndTime = note.StartTime + note.Duration + 400, StartValue = 1, EndValue = 0 });
Transforms.Add(new TransformAlpha { StartTime = note.StartTime + duration + 200, EndTime = note.StartTime + duration + 400, StartValue = 1, EndValue = 0 });
Expire(true);
}
}

View File

@ -3,13 +3,14 @@
using osu.Game.Beatmaps;
using osu.Game.Modes.Mania.Beatmaps;
using osu.Game.Modes.Mania.Judgements;
using osu.Game.Modes.Mania.Objects;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.UI;
namespace osu.Game.Modes.Mania.UI
{
public class ManiaHitRenderer : HitRenderer<ManiaBaseHit>
public class ManiaHitRenderer : HitRenderer<ManiaBaseHit, ManiaJudgementInfo>
{
private readonly int columns;
@ -21,16 +22,10 @@ namespace osu.Game.Modes.Mania.UI
protected override IBeatmapConverter<ManiaBaseHit> CreateBeatmapConverter() => new ManiaBeatmapConverter();
protected override Playfield<ManiaBaseHit> CreatePlayfield() => new ManiaPlayfield(columns);
protected override IBeatmapProcessor<ManiaBaseHit> CreateBeatmapProcessor() => new ManiaBeatmapProcessor();
protected override Playfield<ManiaBaseHit, ManiaJudgementInfo> CreatePlayfield() => new ManiaPlayfield(columns);
protected override DrawableHitObject<ManiaBaseHit> GetVisualRepresentation(ManiaBaseHit h)
{
return null;
//return new DrawableNote(h)
//{
// Position = new Vector2((float)(h.Column + 0.5) / columns, -0.1f),
// RelativePositionAxes = Axes.Both
//};
}
protected override DrawableHitObject<ManiaBaseHit, ManiaJudgementInfo> GetVisualRepresentation(ManiaBaseHit h) => null;
}
}

View File

@ -7,10 +7,11 @@ using osu.Game.Modes.Mania.Objects;
using osu.Game.Modes.UI;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Modes.Mania.Judgements;
namespace osu.Game.Modes.Mania.UI
{
public class ManiaPlayfield : Playfield<ManiaBaseHit>
public class ManiaPlayfield : Playfield<ManiaBaseHit, ManiaJudgementInfo>
{
public ManiaPlayfield(int columns)
{

View File

@ -48,6 +48,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
<Compile Include="Beatmaps\ManiaBeatmapProcessor.cs" />
<Compile Include="Judgements\ManiaJudgementInfo.cs" />
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableNote.cs" />
<Compile Include="Objects\HoldNote.cs" />

View File

@ -1,5 +1,4 @@
<?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

View File

@ -1,13 +1,14 @@
// 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;
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.Objects.Drawables;
using System.Collections.Generic;
using osu.Game.Modes.Objects.Types;
using System.Linq;
namespace osu.Game.Modes.Osu.Beatmaps
{
@ -17,28 +18,64 @@ namespace osu.Game.Modes.Osu.Beatmaps
{
return new Beatmap<OsuHitObject>(original)
{
HitObjects = convertHitObject(original.HitObjects, original.BeatmapInfo?.StackLeniency ?? 0.7f)
HitObjects = convertHitObjects(original.HitObjects, original.BeatmapInfo?.StackLeniency ?? 0.7f)
};
}
private List<OsuHitObject> convertHitObject(List<HitObject> hitObjects, float stackLeniency)
private List<OsuHitObject> convertHitObjects(List<HitObject> hitObjects, float stackLeniency)
{
List<OsuHitObject> converted = new List<OsuHitObject>();
int combo = 0;
foreach (HitObject h in hitObjects)
{
if (h.NewCombo) combo = 0;
h.ComboIndex = combo++;
converted.Add(h as OsuHitObject);
}
List<OsuHitObject> converted = hitObjects.Select(convertHitObject).ToList();
updateStacking(converted, stackLeniency);
return converted;
}
private OsuHitObject convertHitObject(HitObject original)
{
IHasCurve curveData = original as IHasCurve;
IHasEndTime endTimeData = original as IHasEndTime;
IHasPosition positionData = original as IHasPosition;
IHasCombo comboData = original as IHasCombo;
if (curveData != null)
{
return new Slider
{
StartTime = original.StartTime,
Sample = original.Sample,
CurveObject = curveData,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false
};
}
if (endTimeData != null)
{
return new Spinner
{
StartTime = original.StartTime,
Sample = original.Sample,
Position = new Vector2(512, 384) / 2,
EndTime = endTimeData.EndTime
};
}
return new HitCircle
{
StartTime = original.StartTime,
Sample = original.Sample,
Position = positionData?.Position ?? Vector2.Zero,
NewCombo = comboData?.NewCombo ?? false
};
}
private void updateStacking(List<OsuHitObject> hitObjects, float stackLeniency, int startIndex = 0, int endIndex = -1)
{
if (endIndex == -1)
@ -62,9 +99,12 @@ namespace osu.Game.Modes.Osu.Beatmaps
if (stackBaseObject is Spinner) break;
OsuHitObject objectN = hitObjects[n];
if (objectN is Spinner) continue;
if (objectN is Spinner)
continue;
if (objectN.StartTime - stackBaseObject.EndTime > stackThreshold)
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object.
break;
@ -116,7 +156,9 @@ namespace osu.Game.Modes.Osu.Beatmaps
OsuHitObject objectN = hitObjects[n];
if (objectN is Spinner) continue;
if (objectI.StartTime - objectN.EndTime > stackThreshold)
double endTime = (objectN as IHasEndTime)?.EndTime ?? objectN.StartTime;
if (objectI.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the previous object.
break;

View File

@ -0,0 +1,37 @@
// 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.Beatmaps;
using osu.Game.Modes.Osu.Objects;
namespace osu.Game.Modes.Osu.Beatmaps
{
internal class OsuBeatmapProcessor : IBeatmapProcessor<OsuHitObject>
{
public void SetDefaults(OsuHitObject hitObject, Beatmap<OsuHitObject> beatmap)
{
hitObject.SetDefaultsFromBeatmap(beatmap);
}
public void PostProcess(Beatmap<OsuHitObject> beatmap)
{
if (beatmap.ComboColors.Count == 0)
return;
int comboIndex = 0;
int colourIndex = 0;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
comboIndex = 0;
colourIndex = (colourIndex + 1) % beatmap.ComboColors.Count;
}
obj.ComboIndex = comboIndex++;
obj.ComboColour = beatmap.ComboColors[colourIndex];
}
}
}
}

View File

@ -0,0 +1,50 @@
// 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;
using osu.Game.Modes.Judgements;
using osu.Game.Modes.Osu.Objects.Drawables;
namespace osu.Game.Modes.Osu.Judgements
{
public class OsuJudgementInfo : JudgementInfo
{
/// <summary>
/// The positional hit offset.
/// </summary>
public Vector2 PositionOffset;
/// <summary>
/// The score the user achieved.
/// </summary>
public OsuScoreResult Score;
/// <summary>
/// The score which would be achievable on a perfect hit.
/// </summary>
public OsuScoreResult MaxScore = OsuScoreResult.Hit300;
public int ScoreValue => scoreToInt(Score);
public int MaxScoreValue => scoreToInt(MaxScore);
private int scoreToInt(OsuScoreResult result)
{
switch (result)
{
default:
return 0;
case OsuScoreResult.Hit50:
return 50;
case OsuScoreResult.Hit100:
return 100;
case OsuScoreResult.Hit300:
return 300;
case OsuScoreResult.SliderTick:
return 10;
}
}
public ComboResult Combo;
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using OpenTK;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Osu.Objects.Drawables.Connections
{
@ -63,7 +64,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Connections
{
Vector2 startPosition = prevHitObject.EndPosition;
Vector2 endPosition = currHitObject.Position;
double startTime = prevHitObject.EndTime;
double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime;
double endTime = currHitObject.StartTime;
Vector2 distanceVector = endPosition - startPosition;

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics.Transforms;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
@ -35,16 +36,16 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{
glow = new GlowPiece
{
Colour = osuObject.Colour
Colour = osuObject.ComboColour
},
circle = new CirclePiece
{
Colour = osuObject.Colour,
Colour = osuObject.ComboColour,
Hit = () =>
{
if (Judgement.Result.HasValue) return false;
((PositionalJudgementInfo)Judgement).PositionOffset = Vector2.Zero; //todo: set to correct value
Judgement.PositionOffset = Vector2.Zero; //todo: set to correct value
UpdateJudgement(true);
return true;
},
@ -57,11 +58,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
flash = new FlashPiece(),
explode = new ExplodePiece
{
Colour = osuObject.Colour,
Colour = osuObject.ComboColour,
},
ApproachCircle = new ApproachCircle
{
Colour = osuObject.Colour,
Colour = osuObject.ComboColour,
}
};
@ -80,12 +81,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
double hitOffset = Math.Abs(Judgement.TimeOffset);
OsuJudgementInfo osuJudgement = (OsuJudgementInfo)Judgement;
if (hitOffset < HitObject.HitWindowFor(OsuScoreResult.Hit50))
{
Judgement.Result = HitResult.Hit;
osuJudgement.Score = HitObject.ScoreResultForOffset(hitOffset);
Judgement.Score = HitObject.ScoreResultForOffset(hitOffset);
}
else
Judgement.Result = HitResult.Miss;
@ -118,13 +117,16 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
ApproachCircle.FadeOut();
glow.Delay(osuObject.Duration);
double endTime = (osuObject as IHasEndTime)?.EndTime ?? osuObject.StartTime;
double duration = endTime - osuObject.StartTime;
glow.Delay(duration);
glow.FadeOut(400);
switch (state)
{
case ArmedState.Idle:
Delay(osuObject.Duration + TIME_PREEMPT);
Delay(duration + TIME_PREEMPT);
FadeOut(TIME_FADEOUT);
break;
case ArmedState.Miss:

View File

@ -3,10 +3,11 @@
using System.ComponentModel;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Judgements;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject>
public class DrawableOsuHitObject : DrawableHitObject<OsuHitObject, OsuJudgementInfo>
{
public const float TIME_PREEMPT = 600;
public const float TIME_FADEIN = 400;
@ -17,7 +18,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{
}
protected override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.Hit300 };
protected override OsuJudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.Hit300 };
protected override void UpdateState(ArmedState state)
{
@ -45,42 +46,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
}
}
public class OsuJudgementInfo : PositionalJudgementInfo
{
/// <summary>
/// The score the user achieved.
/// </summary>
public OsuScoreResult Score;
/// <summary>
/// The score which would be achievable on a perfect hit.
/// </summary>
public OsuScoreResult MaxScore = OsuScoreResult.Hit300;
public int ScoreValue => scoreToInt(Score);
public int MaxScoreValue => scoreToInt(MaxScore);
private int scoreToInt(OsuScoreResult result)
{
switch (result)
{
default:
return 0;
case OsuScoreResult.Hit50:
return 50;
case OsuScoreResult.Hit100:
return 100;
case OsuScoreResult.Hit300:
return 300;
case OsuScoreResult.SliderTick:
return 10;
}
}
public ComboResult Combo;
}
public enum ComboResult
{
[Description(@"")]

View File

@ -60,7 +60,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Position = s.StackedPosition,
ComboIndex = s.ComboIndex,
Scale = s.Scale,
Colour = s.Colour,
ComboColour = s.ComboColour,
Sample = s.Sample,
}),
};
@ -72,7 +72,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
AddNested(initialCircle);
var repeatDuration = s.Curve.Length / s.Velocity;
var repeatDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.Ticks)
{
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
@ -104,7 +104,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
int repeat = slider.RepeatAt(progress);
progress = slider.CurveProgressAt(progress);
progress = slider.ProgressAt(progress);
if (repeat > currentRepeat)
{
@ -125,27 +125,24 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
protected override void CheckJudgement(bool userTriggered)
{
var j = (OsuJudgementInfo)Judgement;
var sc = (OsuJudgementInfo)initialCircle.Judgement;
if (!userTriggered && Time.Current >= HitObject.EndTime)
if (!userTriggered && Time.Current >= slider.EndTime)
{
var ticksCount = ticks.Children.Count() + 1;
var ticksHit = ticks.Children.Count(t => t.Judgement.Result == HitResult.Hit);
if (sc.Result == HitResult.Hit)
if (initialCircle.Judgement.Result == HitResult.Hit)
ticksHit++;
var hitFraction = (double)ticksHit / ticksCount;
if (hitFraction == 1 && sc.Score == OsuScoreResult.Hit300)
j.Score = OsuScoreResult.Hit300;
else if (hitFraction >= 0.5 && sc.Score >= OsuScoreResult.Hit100)
j.Score = OsuScoreResult.Hit100;
if (hitFraction == 1 && initialCircle.Judgement.Score == OsuScoreResult.Hit300)
Judgement.Score = OsuScoreResult.Hit300;
else if (hitFraction >= 0.5 && initialCircle.Judgement.Score >= OsuScoreResult.Hit100)
Judgement.Score = OsuScoreResult.Hit100;
else if (hitFraction > 0)
j.Score = OsuScoreResult.Hit50;
Judgement.Score = OsuScoreResult.Hit50;
else
j.Score = OsuScoreResult.Miss;
Judgement.Score = OsuScoreResult.Miss;
j.Result = j.Score != OsuScoreResult.Miss ? HitResult.Hit : HitResult.Miss;
Judgement.Result = Judgement.Score != OsuScoreResult.Miss ? HitResult.Hit : HitResult.Miss;
}
}
@ -165,7 +162,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
ball.FadeIn();
Delay(HitObject.Duration, true);
Delay(slider.Duration, true);
body.FadeOut(160);
ball.FadeOut(160);

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Judgements;
using OpenTK;
using OpenTK.Graphics;
@ -26,7 +27,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
public override bool RemoveWhenNotAlive => false;
protected override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.SliderTick };
protected override OsuJudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.SliderTick };
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
{
@ -47,7 +48,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = sliderTick.Colour,
Colour = sliderTick.ComboColour,
Alpha = 0.3f,
}
};
@ -71,12 +72,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
protected override void CheckJudgement(bool userTriggered)
{
var j = (OsuJudgementInfo)Judgement;
if (Judgement.TimeOffset >= 0)
{
j.Result = Tracking ? HitResult.Hit : HitResult.Miss;
j.Score = Tracking ? OsuScoreResult.SliderTick : OsuScoreResult.Miss;
Judgement.Result = Tracking ? HitResult.Hit : HitResult.Miss;
Judgement.Score = Tracking ? OsuScoreResult.SliderTick : OsuScoreResult.Miss;
}
}

View File

@ -46,7 +46,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Alpha = 0,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DiscColour = s.Colour
DiscColour = s.ComboColour
},
circleContainer = new Container
{
@ -75,35 +75,33 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{
if (Time.Current < HitObject.StartTime) return;
var j = (OsuJudgementInfo)Judgement;
disc.ScaleTo(Interpolation.ValueAt(Math.Sqrt(Progress), scaleToCircle, Vector2.One, 0, 1), 100);
if (Progress >= 1)
disc.Complete = true;
if (!userTriggered && Time.Current >= HitObject.EndTime)
if (!userTriggered && Time.Current >= spinner.EndTime)
{
if (Progress >= 1)
{
j.Score = OsuScoreResult.Hit300;
j.Result = HitResult.Hit;
Judgement.Score = OsuScoreResult.Hit300;
Judgement.Result = HitResult.Hit;
}
else if (Progress > .9)
{
j.Score = OsuScoreResult.Hit100;
j.Result = HitResult.Hit;
Judgement.Score = OsuScoreResult.Hit100;
Judgement.Result = HitResult.Hit;
}
else if (Progress > .75)
{
j.Score = OsuScoreResult.Hit50;
j.Result = HitResult.Hit;
Judgement.Score = OsuScoreResult.Hit50;
Judgement.Result = HitResult.Hit;
}
else
{
j.Score = OsuScoreResult.Miss;
if (Time.Current >= HitObject.EndTime)
j.Result = HitResult.Miss;
Judgement.Score = OsuScoreResult.Miss;
if (Time.Current >= spinner.EndTime)
Judgement.Result = HitResult.Miss;
}
}
}
@ -140,7 +138,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
base.UpdateState(state);
Delay(HitObject.Duration, true);
Delay(spinner.Duration, true);
FadeOut(160);

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Game.Graphics.Sprites;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Judgements;
using OpenTK;
using OpenTK.Graphics;

View File

@ -51,7 +51,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
{
new Box
{
Colour = slider.Colour,
Colour = slider.ComboColour,
Alpha = 0.4f,
Width = width,
Height = width,

View File

@ -110,10 +110,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
{
progress -= border_portion;
bytes[i * 4] = (byte)(slider.Colour.R * 255);
bytes[i * 4 + 1] = (byte)(slider.Colour.G * 255);
bytes[i * 4 + 2] = (byte)(slider.Colour.B * 255);
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (slider.Colour.A * 255));
bytes[i * 4] = (byte)(slider.ComboColour.R * 255);
bytes[i * 4 + 1] = (byte)(slider.ComboColour.G * 255);
bytes[i * 4 + 2] = (byte)(slider.ComboColour.B * 255);
bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (slider.ComboColour.A * 255));
}
}

View File

@ -0,0 +1,13 @@
// 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.Modes.Osu.Objects
{
public enum HitObjectType
{
Circle,
Slider,
Spinner,
SliderTick
}
}

View File

@ -1,15 +1,16 @@
// 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.Game.Modes.Objects;
using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Modes.Osu.Objects.Drawables;
using osu.Game.Modes.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Modes.Osu.Objects
{
public abstract class OsuHitObject : HitObject
public abstract class OsuHitObject : HitObject, IHasCombo, IHasPosition
{
public const double OBJECT_RADIUS = 64;
@ -36,6 +37,10 @@ namespace osu.Game.Modes.Osu.Objects
public abstract HitObjectType Type { get; }
public Color4 ComboColour { get; set; }
public virtual bool NewCombo { get; set; }
public int ComboIndex { get; set; }
public double HitWindowFor(OsuScoreResult result)
{
switch (result)
@ -62,23 +67,9 @@ namespace osu.Game.Modes.Osu.Objects
return OsuScoreResult.Miss;
}
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
public virtual void SetDefaultsFromBeatmap(Beatmap<OsuHitObject> beatmap)
{
base.SetDefaultsFromBeatmap(beatmap);
Scale = (1.0f - 0.7f * (beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2;
}
}
[Flags]
public enum HitObjectType
{
Circle = 1 << 0,
Slider = 1 << 1,
NewCombo = 1 << 2,
Spinner = 1 << 3,
ColourHax = 122,
Hold = 1 << 7,
SliderTick = 1 << 8,
}
}

View File

@ -1,105 +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.Collections.Generic;
using System.Globalization;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes.Objects;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class OsuHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text)
{
string[] split = text.Split(',');
var type = (HitObjectType)int.Parse(split[3]);
bool combo = type.HasFlag(HitObjectType.NewCombo);
type &= (HitObjectType)0xF;
type &= ~HitObjectType.NewCombo;
OsuHitObject result;
switch (type)
{
case HitObjectType.Circle:
result = new HitCircle
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1]))
};
break;
case HitObjectType.Slider:
CurveTypes curveType = CurveTypes.Catmull;
double length = 0;
List<Vector2> points = new List<Vector2> { new Vector2(int.Parse(split[0]), int.Parse(split[1])) };
string[] pointsplit = split[5].Split('|');
foreach (string t in pointsplit)
{
if (t.Length == 1)
{
switch (t)
{
case @"C":
curveType = CurveTypes.Catmull;
break;
case @"B":
curveType = CurveTypes.Bezier;
break;
case @"L":
curveType = CurveTypes.Linear;
break;
case @"P":
curveType = CurveTypes.PerfectCurve;
break;
}
continue;
}
string[] temp = t.Split(':');
Vector2 v = new Vector2(
(int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture),
(int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)
);
points.Add(v);
}
int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);
if (repeatCount > 9000)
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
result = new Slider
{
ControlPoints = points,
Length = length,
CurveType = curveType,
RepeatCount = repeatCount,
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1]))
};
break;
case HitObjectType.Spinner:
result = new Spinner
{
Length = Convert.ToDouble(split[5], CultureInfo.InvariantCulture) - Convert.ToDouble(split[2], CultureInfo.InvariantCulture),
Position = new Vector2(512, 384) / 2,
};
break;
default:
throw new InvalidOperationException($@"Unknown hit object type {type}");
}
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
result.Sample = new HitSampleInfo
{
Type = (SampleType)int.Parse(split[4]),
Set = SampleSet.Soft,
};
result.NewCombo = combo;
// TODO: "addition" field
return result;
}
}
}

View File

@ -5,41 +5,33 @@ using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Samples;
using osu.Game.Beatmaps.Timing;
using osu.Game.Modes.Objects.Types;
using System;
using System.Collections.Generic;
using osu.Game.Modes.Objects;
namespace osu.Game.Modes.Osu.Objects
{
public class Slider : OsuHitObject
public class Slider : OsuHitObject, IHasCurve
{
public override double EndTime => StartTime + RepeatCount * Curve.Length / Velocity;
public IHasCurve CurveObject { get; set; }
public SliderCurve Curve => CurveObject.Curve;
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime;
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));
public Vector2 PositionAt(double progress) => CurveObject.PositionAt(progress);
public double ProgressAt(double progress) => CurveObject.ProgressAt(progress);
public int RepeatAt(double progress) => CurveObject.RepeatAt(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;
}
public List<Vector2> ControlPoints => CurveObject.ControlPoints;
public CurveType CurveType => CurveObject.CurveType;
public double Distance => CurveObject.Distance;
/// <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);
public int RepeatCount => CurveObject.RepeatCount;
private int stackHeight;
public override int StackHeight
@ -52,28 +44,10 @@ namespace osu.Game.Modes.Osu.Objects
}
}
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public double Length
{
get { return Curve.Length; }
set { Curve.Length = value; }
}
public CurveTypes CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public double Velocity;
public double TickDistance;
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
public override void SetDefaultsFromBeatmap(Beatmap<OsuHitObject> beatmap)
{
base.SetDefaultsFromBeatmap(beatmap);
@ -88,17 +62,13 @@ namespace osu.Game.Modes.Osu.Objects
TickDistance = baseVelocity / baseDifficulty.SliderTickRate;
}
public int RepeatCount = 1;
internal readonly SliderCurve Curve = new SliderCurve();
public IEnumerable<SliderTick> Ticks
{
get
{
if (TickDistance == 0) yield break;
var length = Curve.Length;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
@ -124,7 +94,7 @@ namespace osu.Game.Modes.Osu.Objects
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
Colour = Colour,
ComboColour = ComboColour,
Sample = new HitSampleInfo
{
Type = SampleType.None,
@ -138,12 +108,4 @@ namespace osu.Game.Modes.Osu.Objects
public override HitObjectType Type => HitObjectType.Slider;
}
public enum CurveTypes
{
Catmull,
Bezier,
Linear,
PerfectCurve
}
}

View File

@ -1,14 +1,17 @@
// 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.Modes.Objects.Types;
namespace osu.Game.Modes.Osu.Objects
{
public class Spinner : OsuHitObject
public class Spinner : OsuHitObject, IHasEndTime
{
public double Length;
public override double EndTime => StartTime + Length;
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
public override HitObjectType Type => HitObjectType.Spinner;
public override bool NewCombo => true;
}
}

View File

@ -10,6 +10,7 @@ using osu.Game.Modes.Osu.Objects.Drawables;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Osu
{
@ -100,20 +101,22 @@ namespace osu.Game.Modes.Osu
{
OsuHitObject last = beatmap.HitObjects[i - 1];
double endTime = (last as IHasEndTime)?.EndTime ?? last.StartTime;
//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 (h.StartTime - h.HitWindowFor(OsuScoreResult.Miss) > 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 (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(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)
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit50) > 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 (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(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)
else if (h.StartTime - h.HitWindowFor(OsuScoreResult.Hit100) > 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 (!(last is Spinner) && h.StartTime - endTime < 1000) addFrameToReplay(new LegacyReplayFrame(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));
}
}
@ -206,8 +209,10 @@ namespace osu.Game.Modes.Osu
LegacyButtonState button = buttonIndex % 2 == 0 ? LegacyButtonState.Left1 : LegacyButtonState.Right1;
double hEndTime = (h as IHasEndTime)?.EndTime ?? h.StartTime;
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);
LegacyReplayFrame endFrame = new LegacyReplayFrame(hEndTime + 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;
@ -251,6 +256,8 @@ namespace osu.Game.Modes.Osu
// We add intermediate frames for spinning / following a slider here.
if (h is Spinner)
{
Spinner s = h as Spinner;
Vector2 difference = targetPosition - spinner_centre;
float radius = difference.Length;
@ -258,7 +265,7 @@ namespace osu.Game.Modes.Osu
double t;
for (double j = h.StartTime + frameDelay; j < h.EndTime; j += frameDelay)
for (double j = h.StartTime + frameDelay; j < s.EndTime; j += frameDelay)
{
t = applyModsToTime(j - h.StartTime) * spinnerDirection;
@ -266,10 +273,10 @@ namespace osu.Game.Modes.Osu
addFrameToReplay(new LegacyReplayFrame((int)j, pos.X, pos.Y, button));
}
t = applyModsToTime(h.EndTime - h.StartTime) * spinnerDirection;
t = applyModsToTime(s.EndTime - h.StartTime) * spinnerDirection;
Vector2 endPosition = spinner_centre + circlePosition(t / 20 + angle, spin_radius);
addFrameToReplay(new LegacyReplayFrame(h.EndTime, endPosition.X, endPosition.Y, button));
addFrameToReplay(new LegacyReplayFrame(s.EndTime, endPosition.X, endPosition.Y, button));
endFrame.MouseX = endPosition.X;
endFrame.MouseY = endPosition.Y;
@ -284,7 +291,7 @@ namespace osu.Game.Modes.Osu
addFrameToReplay(new LegacyReplayFrame(h.StartTime + j, pos.X, pos.Y, button));
}
addFrameToReplay(new LegacyReplayFrame(h.EndTime, s.EndPosition.X, s.EndPosition.Y, button));
addFrameToReplay(new LegacyReplayFrame(s.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!

View File

@ -5,7 +5,6 @@ using OpenTK.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Mods;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI;
@ -96,8 +95,6 @@ namespace osu.Game.Modes.Osu
public override FontAwesome Icon => FontAwesome.fa_osu_osu_o;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => new OsuScoreProcessor(hitObjectCount);
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap);

View File

@ -1,8 +1,9 @@
// 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.Modes.Judgements;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables;
using osu.Game.Modes.Osu.Judgements;
namespace osu.Game.Modes.Osu
{

View File

@ -4,6 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Beatmaps;
using osu.Game.Modes.Osu.Judgements;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.Objects.Drawables;
using osu.Game.Modes.UI;
@ -11,7 +12,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Modes.Osu.UI
{
public class OsuHitRenderer : HitRenderer<OsuHitObject>
public class OsuHitRenderer : HitRenderer<OsuHitObject, OsuJudgementInfo>
{
public OsuHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
@ -20,11 +21,13 @@ namespace osu.Game.Modes.Osu.UI
protected override IBeatmapConverter<OsuHitObject> CreateBeatmapConverter() => new OsuBeatmapConverter();
protected override Playfield<OsuHitObject> CreatePlayfield() => new OsuPlayfield();
protected override IBeatmapProcessor<OsuHitObject> CreateBeatmapProcessor() => new OsuBeatmapProcessor();
protected override Playfield<OsuHitObject, OsuJudgementInfo> CreatePlayfield() => new OsuPlayfield();
protected override KeyConversionInputManager CreateKeyConversionInputManager() => new OsuKeyConversionInputManager();
protected override DrawableHitObject<OsuHitObject> GetVisualRepresentation(OsuHitObject h)
protected override DrawableHitObject<OsuHitObject, OsuJudgementInfo> GetVisualRepresentation(OsuHitObject h)
{
var circle = h as HitCircle;
if (circle != null)

View File

@ -11,11 +11,12 @@ using osu.Game.Modes.Osu.Objects.Drawables.Connections;
using osu.Game.Modes.UI;
using System.Linq;
using osu.Game.Graphics.Cursor;
using osu.Game.Modes.Osu.Judgements;
using OpenTK.Graphics;
namespace osu.Game.Modes.Osu.UI
{
public class OsuPlayfield : Playfield<OsuHitObject>
public class OsuPlayfield : Playfield<OsuHitObject, OsuJudgementInfo>
{
private Container approachCircles;
private Container judgementLayer;
@ -65,16 +66,13 @@ namespace osu.Game.Modes.Osu.UI
AddInternal(new OsuCursorContainer { Colour = Color4.LightYellow });
}
public override void Add(DrawableHitObject<OsuHitObject> h)
public override void Add(DrawableHitObject<OsuHitObject, OsuJudgementInfo> h)
{
h.Depth = (float)h.HitObject.StartTime;
IDrawableHitObjectWithProxiedApproach c = h as IDrawableHitObjectWithProxiedApproach;
if (c != null)
{
approachCircles.Add(c.ProxiedLayer.CreateProxy());
}
h.OnJudgement += judgement;
base.Add(h);
}
@ -86,9 +84,9 @@ namespace osu.Game.Modes.Osu.UI
.OrderBy(h => h.StartTime);
}
private void judgement(DrawableHitObject<OsuHitObject> h, JudgementInfo j)
public override void OnJudgement(DrawableHitObject<OsuHitObject, OsuJudgementInfo> judgedObject)
{
HitExplosion explosion = new HitExplosion((OsuJudgementInfo)j, h.HitObject);
HitExplosion explosion = new HitExplosion(judgedObject.Judgement, judgedObject.HitObject);
judgementLayer.Add(explosion);
}

View File

@ -44,11 +44,11 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\OsuBeatmapConverter.cs" />
<Compile Include="Objects\BezierApproximator.cs" />
<Compile Include="Objects\CircularArcApproximator.cs" />
<Compile Include="Beatmaps\OsuBeatmapProcessor.cs" />
<Compile Include="Objects\Drawables\DrawableOsuHitObject.cs" />
<Compile Include="Objects\Drawables\Connections\ConnectionRenderer.cs" />
<Compile Include="Objects\Drawables\Connections\FollowPointRenderer.cs" />
<Compile Include="Judgements\OsuJudgementInfo.cs" />
<Compile Include="Objects\Drawables\Pieces\ApproachCircle.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerBackground.cs" />
<Compile Include="Objects\Drawables\Pieces\CirclePiece.cs" />
@ -67,9 +67,8 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\HitObjectType.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" />
<Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuAutoReplay.cs" />
<Compile Include="OsuDifficultyCalculator.cs" />

View File

@ -1,5 +1,4 @@
<?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

View File

@ -0,0 +1,19 @@
// 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.Beatmaps;
using osu.Game.Modes.Taiko.Objects;
namespace osu.Game.Modes.Taiko.Beatmaps
{
internal class TaikoBeatmapProcessor : IBeatmapProcessor<TaikoBaseHit>
{
public void SetDefaults(TaikoBaseHit hitObject, Beatmap<TaikoBaseHit> beatmap)
{
}
public void PostProcess(Beatmap<TaikoBaseHit> beatmap)
{
}
}
}

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
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes.Taiko.Judgements
{
public class TaikoJudgementInfo : JudgementInfo
{
}
}

View File

@ -29,8 +29,10 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
{
Texture = textures.Get(@"Menu/logo");
double duration = 0;
Transforms.Add(new TransformPositionX { StartTime = h.StartTime - 200, EndTime = h.StartTime, StartValue = 1.1f, EndValue = 0.1f });
Transforms.Add(new TransformAlpha { StartTime = h.StartTime + h.Duration + 200, EndTime = h.StartTime + h.Duration + 400, StartValue = 1, EndValue = 0 });
Transforms.Add(new TransformAlpha { StartTime = h.StartTime + duration + 200, EndTime = h.StartTime + duration + 400, StartValue = 1, EndValue = 0 });
Expire(true);
}
}

View File

@ -5,7 +5,6 @@ using OpenTK.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Taiko.Mods;
using osu.Game.Modes.Taiko.UI;
using osu.Game.Modes.UI;
@ -91,8 +90,6 @@ namespace osu.Game.Modes.Taiko
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount = 0) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
}
}

View File

@ -4,12 +4,13 @@
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Taiko.Beatmaps;
using osu.Game.Modes.Taiko.Judgements;
using osu.Game.Modes.Taiko.Objects;
using osu.Game.Modes.UI;
namespace osu.Game.Modes.Taiko.UI
{
public class TaikoHitRenderer : HitRenderer<TaikoBaseHit>
public class TaikoHitRenderer : HitRenderer<TaikoBaseHit, TaikoJudgementInfo>
{
public TaikoHitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
@ -18,8 +19,10 @@ namespace osu.Game.Modes.Taiko.UI
protected override IBeatmapConverter<TaikoBaseHit> CreateBeatmapConverter() => new TaikoBeatmapConverter();
protected override Playfield<TaikoBaseHit> CreatePlayfield() => new TaikoPlayfield();
protected override IBeatmapProcessor<TaikoBaseHit> CreateBeatmapProcessor() => new TaikoBeatmapProcessor();
protected override Playfield<TaikoBaseHit, TaikoJudgementInfo> CreatePlayfield() => new TaikoPlayfield();
protected override DrawableHitObject<TaikoBaseHit> GetVisualRepresentation(TaikoBaseHit h) => null;// new DrawableTaikoHit(h);
protected override DrawableHitObject<TaikoBaseHit, TaikoJudgementInfo> GetVisualRepresentation(TaikoBaseHit h) => null;
}
}

View File

@ -9,10 +9,11 @@ using osu.Game.Modes.Taiko.Objects;
using osu.Game.Modes.UI;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Modes.Taiko.Judgements;
namespace osu.Game.Modes.Taiko.UI
{
public class TaikoPlayfield : Playfield<TaikoBaseHit>
public class TaikoPlayfield : Playfield<TaikoBaseHit, TaikoJudgementInfo>
{
public TaikoPlayfield()
{

View File

@ -48,6 +48,8 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
<Compile Include="Beatmaps\TaikoBeatmapProcessor.cs" />
<Compile Include="Judgements\TaikoJudgementInfo.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHit.cs" />
<Compile Include="Objects\TaikoBaseHit.cs" />

View File

@ -1,5 +1,4 @@
<?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

View File

@ -8,9 +8,9 @@ using OpenTK.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes;
using osu.Game.Modes.Osu;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Tests.Resources;
using osu.Game.Modes.Osu;
using osu.Game.Modes.Objects.Legacy;
namespace osu.Game.Tests.Beatmaps.Formats
{
@ -133,16 +133,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
{
var beatmap = decoder.Decode(new StreamReader(stream));
var slider = beatmap.HitObjects[0] as Slider;
var slider = beatmap.HitObjects[0] as LegacySlider;
Assert.IsNotNull(slider);
Assert.AreEqual(new Vector2(192, 168), slider.Position);
Assert.AreEqual(956, slider.StartTime);
Assert.AreEqual(SampleType.None, slider.Sample.Type);
var circle = beatmap.HitObjects[1] as HitCircle;
Assert.IsNotNull(circle);
Assert.AreEqual(new Vector2(304, 56), circle.Position);
Assert.AreEqual(1285, circle.StartTime);
Assert.AreEqual(SampleType.Clap, circle.Sample.Type);
var hit = beatmap.HitObjects[1] as LegacyHit;
Assert.IsNotNull(hit);
Assert.AreEqual(new Vector2(304, 56), hit.Position);
Assert.AreEqual(1285, hit.StartTime);
Assert.AreEqual(SampleType.Clap, hit.Sample.Type);
}
}
}

View File

@ -19,7 +19,13 @@ namespace osu.Game.Beatmaps
{
public BeatmapInfo BeatmapInfo;
public List<ControlPoint> ControlPoints;
public List<Color4> ComboColors;
public readonly List<Color4> ComboColors = new List<Color4>
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
};
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
@ -34,9 +40,9 @@ namespace osu.Game.Beatmaps
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap original = null)
{
BeatmapInfo = original?.BeatmapInfo;
ControlPoints = original?.ControlPoints;
ComboColors = original?.ComboColors;
BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo;
ControlPoints = original?.ControlPoints ?? ControlPoints;
ComboColors = original?.ComboColors ?? ComboColors;
}
public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength;

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using osu.Game.Modes.Objects;
using OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
@ -31,9 +30,7 @@ namespace osu.Game.Beatmaps.Formats
public virtual Beatmap Decode(TextReader stream)
{
Beatmap b = ParseFile(stream);
Process(b);
return b;
return ParseFile(stream);
}
public virtual void Decode(TextReader stream, Beatmap beatmap)
@ -41,20 +38,12 @@ namespace osu.Game.Beatmaps.Formats
ParseFile(stream, beatmap);
}
public virtual Beatmap Process(Beatmap beatmap)
{
ApplyColours(beatmap);
return beatmap;
}
protected virtual Beatmap ParseFile(TextReader stream)
{
var beatmap = new Beatmap
{
HitObjects = new List<HitObject>(),
ControlPoints = new List<ControlPoint>(),
ComboColors = new List<Color4>(),
BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata(),
@ -65,25 +54,5 @@ namespace osu.Game.Beatmaps.Formats
return beatmap;
}
protected abstract void ParseFile(TextReader stream, Beatmap beatmap);
public virtual void ApplyColours(Beatmap b)
{
List<Color4> colours = b.ComboColors ?? new List<Color4> {
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255),
};
if (colours.Count == 0) return;
int i = -1;
foreach (HitObject h in b.HitObjects)
{
if (h.NewCombo || i == -1) i = (i + 1) % colours.Count;
h.Colour = colours[i];
}
}
}
}

View File

@ -212,14 +212,23 @@ namespace osu.Game.Beatmaps.Formats
beatmap.ControlPoints.Add(cp);
}
private void handleColours(Beatmap beatmap, string key, string val)
private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours)
{
string[] split = val.Split(',');
if (split.Length != 3)
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {val}");
byte r, g, b;
if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b))
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
if (!hasCustomColours)
{
beatmap.ComboColors.Clear();
hasCustomColours = true;
}
// Note: the combo index specified in the beatmap is discarded
if (key.StartsWith(@"Combo"))
{
@ -237,6 +246,8 @@ namespace osu.Game.Beatmaps.Formats
{
HitObjectParser parser = null;
bool hasCustomColours = false;
var section = Section.None;
while (true)
{
@ -265,7 +276,7 @@ namespace osu.Game.Beatmaps.Formats
{
case Section.General:
handleGeneral(beatmap, key, val);
parser = Ruleset.GetRuleset(beatmap.BeatmapInfo.Mode).CreateHitObjectParser();
parser = new LegacyHitObjectParser();
break;
case Section.Editor:
handleEditor(beatmap, key, val);
@ -283,16 +294,14 @@ namespace osu.Game.Beatmaps.Formats
handleTimingPoints(beatmap, val);
break;
case Section.Colours:
handleColours(beatmap, key, val);
handleColours(beatmap, key, val, ref hasCustomColours);
break;
case Section.HitObjects:
var obj = parser?.Parse(val);
if (obj != null)
{
obj.SetDefaultsFromBeatmap(beatmap);
beatmap.HitObjects.Add(obj);
}
break;
}
}

View File

@ -5,8 +5,17 @@ using osu.Game.Modes.Objects;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Converts a Beatmap for another mode.
/// </summary>
/// <typeparam name="T">The type of HitObject stored in the Beatmap.</typeparam>
public interface IBeatmapConverter<T> where T : HitObject
{
/// <summary>
/// Converts a Beatmap to another mode.
/// </summary>
/// <param name="original">The original Beatmap.</param>
/// <returns>The converted Beatmap.</returns>
Beatmap<T> Convert(Beatmap original);
}
}

View File

@ -0,0 +1,31 @@
// 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.Modes.Objects;
namespace osu.Game.Beatmaps
{
/// <summary>
/// Processes a post-converted Beatmap.
/// </summary>
/// <typeparam name="T">The type of HitObject contained in the Beatmap.</typeparam>
public interface IBeatmapProcessor<T>
where T : HitObject
{
/// <summary>
/// Sets default values for a HitObject.
/// </summary>
/// <param name="hitObject">The HitObject to set default values for.</param>
/// <param name="beatmap">The Beatmap to extract the default values from.</param>
void SetDefaults(T hitObject, Beatmap<T> beatmap);
/// <summary>
/// Post-processes a Beatmap to add mode-specific components that aren't added during conversion.
/// <para>
/// An example of such a usage is for combo colours.
/// </para>
/// </summary>
/// <param name="beatmap">The Beatmap to process.</param>
void PostProcess(Beatmap<T> beatmap);
}
}

View File

@ -0,0 +1,14 @@
// 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.Modes.Objects.Drawables;
namespace osu.Game.Modes.Judgements
{
public class JudgementInfo
{
public ulong? ComboAtHit;
public HitResult? Result;
public double TimeOffset;
}
}

View File

@ -1,7 +1,6 @@
// 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.Modes.Objects;
using osu.Game.Modes.UI;

View File

@ -4,7 +4,7 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
namespace osu.Game.Modes.Objects
{
public class BezierApproximator
{

View File

@ -1,12 +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 OpenTK;
using osu.Framework.MathUtils;
using System;
using System.Collections.Generic;
using osu.Framework.MathUtils;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
namespace osu.Game.Modes.Objects
{
public class CircularArcApproximator
{

View File

@ -0,0 +1,49 @@
// 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;
using osu.Game.Modes.Objects.Types;
using System.Collections.Generic;
namespace osu.Game.Modes.Objects
{
public class CurvedHitObject : HitObject, IHasCurve
{
public SliderCurve Curve { get; } = new SliderCurve();
public int RepeatCount { get; set; } = 1;
public double EndTime => 0;
public double Duration => 0;
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public CurveType CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
var p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
}
}

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
namespace osu.Game.Modes.Objects.Drawables
{
public enum ArmedState
{
Idle,
Hit,
Miss
}
}

View File

@ -3,26 +3,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Game.Beatmaps.Samples;
using OpenTK;
using osu.Game.Modes.Judgements;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Objects.Drawables
{
public abstract class DrawableHitObject : Container, IStateful<ArmedState>
public abstract class DrawableHitObject<TJudgement> : Container, IStateful<ArmedState>
where TJudgement : JudgementInfo
{
public override bool HandleInput => Interactive;
public bool Interactive = true;
public JudgementInfo Judgement;
public TJudgement Judgement;
protected abstract JudgementInfo CreateJudgementInfo();
protected abstract TJudgement CreateJudgementInfo();
protected abstract void UpdateState(ArmedState state);
@ -67,14 +68,15 @@ namespace osu.Game.Modes.Objects.Drawables
}
}
public abstract class DrawableHitObject<HitObjectType> : DrawableHitObject
where HitObjectType : HitObject
public abstract class DrawableHitObject<TObject, TJudgement> : DrawableHitObject<TJudgement>
where TObject : HitObject
where TJudgement : JudgementInfo
{
public event Action<DrawableHitObject<HitObjectType>, JudgementInfo> OnJudgement;
public event Action<DrawableHitObject<TObject, TJudgement>> OnJudgement;
public HitObjectType HitObject;
public TObject HitObject;
protected DrawableHitObject(HitObjectType hitObject)
protected DrawableHitObject(TObject hitObject)
{
HitObject = hitObject;
}
@ -88,7 +90,9 @@ namespace osu.Game.Modes.Objects.Drawables
if (Judgement.Result != null)
return false;
Judgement.TimeOffset = Time.Current - HitObject.EndTime;
double endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
Judgement.TimeOffset = Time.Current - endTime;
CheckJudgement(userTriggered);
@ -105,7 +109,7 @@ namespace osu.Game.Modes.Objects.Drawables
break;
}
OnJudgement?.Invoke(this, Judgement);
OnJudgement?.Invoke(this);
return true;
}
@ -138,44 +142,17 @@ namespace osu.Game.Modes.Objects.Drawables
Sample = audio.Sample.Get($@"Gameplay/{sampleSet.ToString().ToLower()}-hit{type.ToString().ToLower()}");
}
private List<DrawableHitObject<HitObjectType>> nestedHitObjects;
private List<DrawableHitObject<TObject, TJudgement>> nestedHitObjects;
protected IEnumerable<DrawableHitObject<HitObjectType>> NestedHitObjects => nestedHitObjects;
protected IEnumerable<DrawableHitObject<TObject, TJudgement>> NestedHitObjects => nestedHitObjects;
protected void AddNested(DrawableHitObject<HitObjectType> h)
protected void AddNested(DrawableHitObject<TObject, TJudgement> h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject<HitObjectType>>();
nestedHitObjects = new List<DrawableHitObject<TObject, TJudgement>>();
h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ;
h.OnJudgement += d => OnJudgement?.Invoke(d);
nestedHitObjects.Add(h);
}
}
public enum ArmedState
{
Idle,
Hit,
Miss
}
public class PositionalJudgementInfo : JudgementInfo
{
public Vector2 PositionOffset;
}
public class JudgementInfo
{
public ulong? ComboAtHit;
public HitResult? Result;
public double TimeOffset;
}
public enum HitResult
{
[Description(@"Miss")]
Miss,
[Description(@"Hit")]
Hit,
}
}

View File

@ -0,0 +1,15 @@
// 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.ComponentModel;
namespace osu.Game.Modes.Objects.Drawables
{
public enum HitResult
{
[Description(@"Miss")]
Miss,
[Description(@"Hit")]
Hit,
}
}

View File

@ -1,30 +1,26 @@
// 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.Beatmaps;
using osu.Game.Beatmaps.Samples;
using OpenTK.Graphics;
namespace osu.Game.Modes.Objects
{
/// <summary>
/// A hitobject describes a point in a beatmap
/// A HitObject describes an object in a Beatmap.
/// <para>
/// HitObjects may contain more properties for which you should be checking through the IHas* types.
/// </para>
/// </summary>
public abstract class HitObject
public class HitObject
{
public double StartTime;
public virtual double EndTime => StartTime;
/// <summary>
/// The time at which the HitObject starts.
/// </summary>
public double StartTime { get; set; }
public bool NewCombo { get; set; }
public Color4 Colour = new Color4(17, 136, 170, 255);
public double Duration => EndTime - StartTime;
public HitSampleInfo Sample;
public int ComboIndex;
public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { }
/// <summary>
/// The sample to be played when this HitObject is hit.
/// </summary>
public HitSampleInfo Sample { get; set; }
}
}

View File

@ -0,0 +1,18 @@
// 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.Modes.Objects.Types;
using OpenTK;
namespace osu.Game.Modes.Objects.Legacy
{
/// <summary>
/// Legacy Hit-type, used for parsing Beatmaps.
/// </summary>
public sealed class LegacyHit : HitObject, IHasPosition, IHasCombo
{
public Vector2 Position { get; set; }
public bool NewCombo { get; set; }
}
}

View File

@ -0,0 +1,18 @@
// 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;
namespace osu.Game.Modes.Objects.Legacy
{
[Flags]
public enum LegacyHitObjectType
{
Circle = 1 << 0,
Slider = 1 << 1,
NewCombo = 1 << 2,
Spinner = 1 << 3,
ColourHax = 112,
Hold = 1 << 7
}
}

View File

@ -0,0 +1,18 @@
// 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;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Modes.Objects.Legacy
{
/// <summary>
/// Legacy Hold-type, used for parsing "specials" in beatmaps.
/// </summary>
public sealed class LegacyHold : HitObject, IHasPosition, IHasCombo, IHasHold
{
public Vector2 Position { get; set; }
public bool NewCombo { get; set; }
}
}

View File

@ -0,0 +1,18 @@
// 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.Modes.Objects.Types;
using OpenTK;
namespace osu.Game.Modes.Objects.Legacy
{
/// <summary>
/// Legacy Slider-type, used for parsing Beatmaps.
/// </summary>
public sealed class LegacySlider : CurvedHitObject, IHasPosition, IHasCombo
{
public Vector2 Position { get; set; }
public bool NewCombo { get; set; }
}
}

View File

@ -0,0 +1,17 @@
// 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.Modes.Objects.Types;
namespace osu.Game.Modes.Objects.Legacy
{
/// <summary>
/// Legacy Spinner-type, used for parsing Beatmaps.
/// </summary>
internal class LegacySpinner : HitObject, IHasEndTime
{
public double EndTime { get; set; }
public double Duration => EndTime - StartTime;
}
}

View File

@ -0,0 +1,119 @@
// 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;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes.Objects.Types;
using System;
using System.Collections.Generic;
using System.Globalization;
using osu.Game.Modes.Objects.Legacy;
namespace osu.Game.Modes.Objects
{
internal class LegacyHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text)
{
string[] split = text.Split(',');
var type = (LegacyHitObjectType)int.Parse(split[3]) & ~LegacyHitObjectType.ColourHax;
bool combo = type.HasFlag(LegacyHitObjectType.NewCombo);
type &= ~LegacyHitObjectType.NewCombo;
HitObject result;
if ((type & LegacyHitObjectType.Circle) > 0)
{
result = new LegacyHit
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
}
else if ((type & LegacyHitObjectType.Slider) > 0)
{
CurveType curveType = CurveType.Catmull;
double length = 0;
List<Vector2> points = new List<Vector2> { new Vector2(int.Parse(split[0]), int.Parse(split[1])) };
string[] pointsplit = split[5].Split('|');
foreach (string t in pointsplit)
{
if (t.Length == 1)
{
switch (t)
{
case @"C":
curveType = CurveType.Catmull;
break;
case @"B":
curveType = CurveType.Bezier;
break;
case @"L":
curveType = CurveType.Linear;
break;
case @"P":
curveType = CurveType.PerfectCurve;
break;
}
continue;
}
string[] temp = t.Split(':');
Vector2 v = new Vector2(
(int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture),
(int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)
);
points.Add(v);
}
int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture);
if (repeatCount > 9000)
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
result = new LegacySlider
{
ControlPoints = points,
Distance = length,
CurveType = curveType,
RepeatCount = repeatCount,
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
}
else if ((type & LegacyHitObjectType.Spinner) > 0)
{
result = new LegacySpinner
{
EndTime = Convert.ToDouble(split[5], CultureInfo.InvariantCulture)
};
}
else if ((type & LegacyHitObjectType.Hold) > 0)
{
// Note: Hold is generated by BMS converts
result = new LegacyHold
{
Position = new Vector2(int.Parse(split[0]), int.Parse(split[1])),
NewCombo = combo
};
}
else
throw new InvalidOperationException($@"Unknown hit object type {type}");
result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture);
result.Sample = new HitSampleInfo
{
Type = (SampleType)int.Parse(split[4]),
Set = SampleSet.Soft,
};
// TODO: "addition" field
return result;
}
}
}

View File

@ -1,13 +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
namespace osu.Game.Modes.Objects
{
/// <summary>
/// Returns null HitObjects but at least allows us to run.
/// </summary>
public class NullHitObjectParser : HitObjectParser
{
public override HitObject Parse(string text) => null;
}
}

View File

@ -2,19 +2,20 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK;
using System.Linq;
using osu.Framework.MathUtils;
using osu.Game.Modes.Objects.Types;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
namespace osu.Game.Modes.Objects
{
public class SliderCurve
{
public double Length;
public double Distance;
public List<Vector2> ControlPoints;
public CurveTypes CurveType = CurveTypes.PerfectCurve;
public CurveType CurveType = CurveType.PerfectCurve;
public Vector2 Offset;
@ -25,9 +26,9 @@ namespace osu.Game.Modes.Osu.Objects
{
switch (CurveType)
{
case CurveTypes.Linear:
case CurveType.Linear:
return subControlPoints;
case CurveTypes.PerfectCurve:
case CurveType.PerfectCurve:
//we can only use CircularArc iff we have exactly three control points and no dissection.
if (ControlPoints.Count != 3 || subControlPoints.Count != 3)
break;
@ -82,12 +83,12 @@ namespace osu.Game.Modes.Osu.Objects
// Shorten slider curves that are too long compared to what's
// in the .osu file.
if (Length - l < d)
if (Distance - l < d)
{
calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Length - l) / d);
calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Distance - l) / d);
calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i);
l = Length;
l = Distance;
cumulativeLength.Add(l);
break;
}
@ -129,7 +130,7 @@ namespace osu.Game.Modes.Osu.Objects
private double progressToDistance(double progress)
{
return MathHelper.Clamp(progress, 0, 1) * Length;
return MathHelper.Clamp(progress, 0, 1) * Distance;
}
private Vector2 interpolateVertices(int i, double d)

View File

@ -0,0 +1,13 @@
// 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.Modes.Objects.Types
{
public enum CurveType
{
Catmull,
Bezier,
Linear,
PerfectCurve
}
}

View File

@ -0,0 +1,16 @@
// 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.Modes.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo.
/// </summary>
public interface IHasCombo
{
/// <summary>
/// Whether the HitObject starts a new combo.
/// </summary>
bool NewCombo { get; }
}
}

View File

@ -0,0 +1,52 @@
// 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 OpenTK;
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a curve.
/// </summary>
public interface IHasCurve : IHasDistance, IHasRepeats
{
/// <summary>
/// The curve.
/// </summary>
SliderCurve Curve { get; }
/// <summary>
/// The control points that shape the curve.
/// </summary>
List<Vector2> ControlPoints { get; }
/// <summary>
/// The type of curve.
/// </summary>
CurveType CurveType { get; }
/// <summary>
/// Computes the position on the curve at a given progress, accounting for repeat logic.
/// <para>
/// Ranges from [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.
/// </para>
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
Vector2 PositionAt(double progress);
/// <summary>
/// Finds the progress along the curve, accounting for repeat logic.
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
double ProgressAt(double progress);
/// <summary>
/// Determines which repeat of the curve the progress point is on.
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, RepeatCount] where 0 is the first run.</returns>
int RepeatAt(double progress);
}
}

View File

@ -0,0 +1,16 @@
// 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.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a positional length.
/// </summary>
public interface IHasDistance : IHasEndTime
{
/// <summary>
/// The positional length of the HitObject.
/// </summary>
double Distance { get; }
}
}

View File

@ -0,0 +1,21 @@
// 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.Modes.Objects.Types
{
/// <summary>
/// A HitObject that ends at a different time than its start time.
/// </summary>
public interface IHasEndTime
{
/// <summary>
/// The time at which the HitObject ends.
/// </summary>
double EndTime { get; }
/// <summary>
/// The duration of the HitObject.
/// </summary>
double Duration { get; }
}
}

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
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A special type of HitObject, mostly used for legacy conversion of "holds".
/// </summary>
public interface IHasHold
{
}
}

View File

@ -0,0 +1,18 @@
// 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;
namespace osu.Game.Modes.Objects.Types
{
/// <summary>
/// A HitObject that has a starting position.
/// </summary>
public interface IHasPosition
{
/// <summary>
/// The starting position of the HitObject.
/// </summary>
Vector2 Position { get; }
}
}

View File

@ -0,0 +1,16 @@
// 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.Modes.Objects.Types
{
/// <summary>
/// A HitObject that spans some length.
/// </summary>
public interface IHasRepeats : IHasEndTime
{
/// <summary>
/// The amount of times the HitObject repeats.
/// </summary>
int RepeatCount { get; }
}
}

View File

@ -4,7 +4,6 @@
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.UI;
using osu.Game.Screens.Play;
using System;
@ -34,8 +33,6 @@ namespace osu.Game.Modes
public abstract HitRenderer CreateHitRendererWith(WorkingBeatmap beatmap);
public abstract HitObjectParser CreateHitObjectParser();
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);
public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType());

View File

@ -2,9 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Game.Modes.Objects.Drawables;
using System;
using System.Collections.Generic;
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes
{

View File

@ -5,25 +5,51 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Modes.Judgements;
using osu.Game.Modes.Mods;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Screens.Play;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace osu.Game.Modes.UI
{
/// <summary>
/// Base HitRenderer. Doesn't hold objects.
/// <para>
/// Should not be derived - derive <see cref="HitRenderer{TObject, TJudgement}"/> instead.
/// </para>
/// </summary>
public abstract class HitRenderer : Container
{
/// <summary>
/// The event that's fired when a hit object is judged.
/// </summary>
public event Action<JudgementInfo> OnJudgement;
/// <summary>
/// The event that's fired when all hit objects have been judged.
/// </summary>
public event Action OnAllJudged;
/// <summary>
/// The input manager for this HitRenderer.
/// </summary>
internal readonly PlayerInputManager InputManager = new PlayerInputManager();
/// <summary>
/// The key conversion input manager for this HitRenderer.
/// </summary>
protected readonly KeyConversionInputManager KeyConversionInputManager;
/// <summary>
/// Whether all the HitObjects have been judged.
/// </summary>
protected abstract bool AllObjectsJudged { get; }
protected HitRenderer()
{
KeyConversionInputManager = CreateKeyConversionInputManager();
@ -31,10 +57,9 @@ namespace osu.Game.Modes.UI
}
/// <summary>
/// Whether all the HitObjects have been judged.
/// Triggers a judgement for further processing.
/// </summary>
protected abstract bool AllObjectsJudged { get; }
/// <param name="j">The judgement to trigger.</param>
protected void TriggerOnJudgement(JudgementInfo j)
{
OnJudgement?.Invoke(j);
@ -43,29 +68,92 @@ namespace osu.Game.Modes.UI
OnAllJudged?.Invoke();
}
/// <summary>
/// Creates a key conversion input manager.
/// </summary>
/// <returns>The input manager.</returns>
protected virtual KeyConversionInputManager CreateKeyConversionInputManager() => new KeyConversionInputManager();
}
/// <summary>
/// HitRenderer that applies conversion to Beatmaps. Does not contain a Playfield
/// and does not load drawable hit objects.
/// <para>
/// Should not be derived - derive <see cref="HitRenderer{TObject, TJudgement}"/> instead.
/// </para>
/// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this HitRenderer.</typeparam>
public abstract class HitRenderer<TObject> : HitRenderer
where TObject : HitObject
{
/// <summary>
/// The Beatmap
/// </summary>
public Beatmap<TObject> Beatmap;
protected override Container<Drawable> Content => content;
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result.HasValue);
protected Playfield<TObject> Playfield;
private Container content;
protected HitRenderer(WorkingBeatmap beatmap)
{
Debug.Assert(beatmap != null, "HitRenderer initialized with a null beatmap.");
// Convert + process the beatmap
Beatmap = CreateBeatmapConverter().Convert(beatmap.Beatmap);
Beatmap.HitObjects.ForEach(h => CreateBeatmapProcessor().SetDefaults(h, Beatmap));
CreateBeatmapProcessor().PostProcess(Beatmap);
applyMods(beatmap.Mods.Value);
RelativeSizeAxes = Axes.Both;
}
/// <summary>
/// Applies the active mods to this HitRenderer.
/// </summary>
/// <param name="mods"></param>
private void applyMods(IEnumerable<Mod> mods)
{
if (mods == null)
return;
foreach (var mod in mods.OfType<IApplicableMod<TObject>>())
mod.Apply(this);
}
/// <summary>
/// Creates a converter to convert Beatmap to a specific mode.
/// </summary>
/// <returns>The Beatmap converter.</returns>
protected abstract IBeatmapConverter<TObject> CreateBeatmapConverter();
/// <summary>
/// Creates a processor to perform post-processing operations
/// on HitObjects in converted Beatmaps.
/// </summary>
/// <returns>The Beatmap processor.</returns>
protected abstract IBeatmapProcessor<TObject> CreateBeatmapProcessor();
}
/// <summary>
/// A derivable HitRenderer that manages the Playfield and HitObjects.
/// </summary>
/// <typeparam name="TObject">The type of HitObject contained by this HitRenderer.</typeparam>
/// <typeparam name="TJudgement">The type of Judgement of DrawableHitObjects contained by this HitRenderer.</typeparam>
public abstract class HitRenderer<TObject, TJudgement> : HitRenderer<TObject>
where TObject : HitObject
where TJudgement : JudgementInfo
{
protected override Container<Drawable> Content => content;
protected override bool AllObjectsJudged => Playfield.HitObjects.Children.All(h => h.Judgement.Result.HasValue);
/// <summary>
/// The playfield.
/// </summary>
protected Playfield<TObject, TJudgement> Playfield;
private Container content;
protected HitRenderer(WorkingBeatmap beatmap)
: base(beatmap)
{
KeyConversionInputManager.Add(Playfield = CreatePlayfield());
InputManager.Add(content = new Container
@ -90,7 +178,7 @@ namespace osu.Game.Modes.UI
{
foreach (TObject h in Beatmap.HitObjects)
{
DrawableHitObject<TObject> drawableObject = GetVisualRepresentation(h);
var drawableObject = GetVisualRepresentation(h);
if (drawableObject == null)
continue;
@ -103,19 +191,27 @@ namespace osu.Game.Modes.UI
Playfield.PostProcess();
}
private void applyMods(IEnumerable<Mod> mods)
/// <summary>
/// Triggered when an object's Judgement is updated.
/// </summary>
/// <param name="judgedObject">The object that Judgement has been updated for.</param>
private void onJudgement(DrawableHitObject<TObject, TJudgement> judgedObject)
{
if (mods == null)
return;
foreach (var mod in mods.OfType<IApplicableMod<TObject>>())
mod.Apply(this);
TriggerOnJudgement(judgedObject.Judgement);
Playfield.OnJudgement(judgedObject);
}
private void onJudgement(DrawableHitObject<TObject> o, JudgementInfo j) => TriggerOnJudgement(j);
/// <summary>
/// Creates a DrawableHitObject from a HitObject.
/// </summary>
/// <param name="h">The HitObject to make drawable.</param>
/// <returns>The DrawableHitObject.</returns>
protected abstract DrawableHitObject<TObject, TJudgement> GetVisualRepresentation(TObject h);
protected abstract DrawableHitObject<TObject> GetVisualRepresentation(TObject h);
protected abstract Playfield<TObject> CreatePlayfield();
protected abstract IBeatmapConverter<TObject> CreateBeatmapConverter();
/// <summary>
/// Creates a Playfield.
/// </summary>
/// <returns>The Playfield.</returns>
protected abstract Playfield<TObject, TJudgement> CreatePlayfield();
}
}

View File

@ -6,22 +6,23 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables;
using OpenTK;
using osu.Game.Modes.Judgements;
namespace osu.Game.Modes.UI
{
public abstract class Playfield<T> : Container
where T : HitObject
public abstract class Playfield<TObject, TJudgement> : Container
where TObject : HitObject
where TJudgement : JudgementInfo
{
public HitObjectContainer<DrawableHitObject<T>> HitObjects;
public virtual void Add(DrawableHitObject<T> h) => HitObjects.Add(h);
/// <summary>
/// The HitObjects contained in this Playfield.
/// </summary>
public HitObjectContainer<DrawableHitObject<TObject, TJudgement>> HitObjects;
public override bool Contains(Vector2 screenSpacePos) => true;
internal Container<Drawable> ScaledContent;
public override bool Contains(Vector2 screenSpacePos) => true;
protected override Container<Drawable> Content => content;
private Container<Drawable> content;
/// <summary>
@ -43,15 +44,28 @@ namespace osu.Game.Modes.UI
}
});
Add(HitObjects = new HitObjectContainer<DrawableHitObject<T>>
Add(HitObjects = new HitObjectContainer<DrawableHitObject<TObject, TJudgement>>
{
RelativeSizeAxes = Axes.Both,
});
}
public virtual void PostProcess()
{
}
/// <summary>
/// Performs post-processing tasks (if any) after all DrawableHitObjects are loaded into this Playfield.
/// </summary>
public virtual void PostProcess() { }
/// <summary>
/// Adds a DrawableHitObject to this Playfield.
/// </summary>
/// <param name="h">The DrawableHitObject to add.</param>
public virtual void Add(DrawableHitObject<TObject, TJudgement> h) => HitObjects.Add(h);
/// <summary>
/// Triggered when an object's Judgement is updated.
/// </summary>
/// <param name="judgedObject">The object that Judgement has been updated for.</param>
public virtual void OnJudgement(DrawableHitObject<TObject, TJudgement> judgedObject) { }
private class ScaledContainer : Container
{

View File

@ -1,7 +1,6 @@
// 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;
using OpenTK.Graphics;
using osu.Framework.Allocation;

View File

@ -21,6 +21,8 @@ using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Modes;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Types;
namespace osu.Game.Screens.Select
{
@ -90,11 +92,14 @@ namespace osu.Game.Screens.Select
if (beatmap.Beatmap != null)
{
HitObject lastObject = beatmap.Beatmap.HitObjects.LastOrDefault();
double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0;
labels.Add(new InfoLabel(new BeatmapStatistic
{
Name = "Length",
Icon = FontAwesome.fa_clock_o,
Content = beatmap.Beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(beatmap.Beatmap.HitObjects.Last().EndTime - beatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
Content = beatmap.Beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic

View File

@ -74,6 +74,7 @@
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Beatmaps\IBeatmapCoverter.cs" />
<Compile Include="Beatmaps\IBeatmapProcessor.cs" />
<Compile Include="Database\ScoreDatabase.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
@ -93,9 +94,29 @@
<Compile Include="Modes\LegacyReplay.cs" />
<Compile Include="Modes\Mods\IApplicableMod.cs" />
<Compile Include="Modes\Mods\ModType.cs" />
<Compile Include="Modes\Objects\Drawables\ArmedState.cs" />
<Compile Include="Modes\Objects\Drawables\HitResult.cs" />
<Compile Include="Modes\Objects\BezierApproximator.cs" />
<Compile Include="Modes\Objects\CircularArcApproximator.cs" />
<Compile Include="Modes\Objects\CurvedHitObject.cs" />
<Compile Include="Modes\Objects\Legacy\LegacyHit.cs" />
<Compile Include="Modes\Objects\LegacyHitObjectParser.cs" />
<Compile Include="Modes\Objects\Legacy\LegacyHold.cs" />
<Compile Include="Modes\Objects\Legacy\LegacySlider.cs" />
<Compile Include="Modes\Objects\Legacy\LegacySpinner.cs" />
<Compile Include="Modes\Objects\SliderCurve.cs" />
<Compile Include="Modes\Objects\Types\CurveType.cs" />
<Compile Include="Modes\Objects\Drawables\IDrawableHitObjectWithProxiedApproach.cs" />
<Compile Include="Modes\Judgements\JudgementInfo.cs" />
<Compile Include="Modes\Objects\HitObjectParser.cs" />
<Compile Include="Modes\Objects\NullHitObjectParser.cs" />
<Compile Include="Modes\Objects\Types\IHasCombo.cs" />
<Compile Include="Modes\Objects\Types\IHasEndTime.cs" />
<Compile Include="Modes\Objects\Types\IHasDistance.cs" />
<Compile Include="Modes\Objects\Types\IHasCurve.cs" />
<Compile Include="Modes\Objects\Types\IHasRepeats.cs" />
<Compile Include="Modes\Objects\Types\IHasPosition.cs" />
<Compile Include="Modes\Objects\Types\IHasHold.cs" />
<Compile Include="Modes\Objects\Legacy\LegacyHitObjectType.cs" />
<Compile Include="Modes\Replay.cs" />
<Compile Include="Modes\Score.cs" />
<Compile Include="Modes\ScoreProcesssor.cs" />

View File

@ -1,5 +1,4 @@
<?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