1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:42:57 +08:00

Merge branch 'master' into spun-out

This commit is contained in:
Dean Herbert 2020-03-29 13:24:48 +09:00 committed by GitHub
commit b259708915
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
97 changed files with 1673 additions and 441 deletions

View File

@ -52,6 +52,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.319.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2020.327.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch
new KeyBinding(InputKey.Shift, CatchAction.Dash), new KeyBinding(InputKey.Shift, CatchAction.Dash),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))
yield return new CatchModNightcore(); yield return new CatchModNightcore();

View File

@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Catch.Replays
} }
} }
public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{ {
Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
@ -56,5 +56,14 @@ namespace osu.Game.Rulesets.Catch.Replays
Actions.Add(CatchAction.MoveLeft); Actions.Add(CatchAction.MoveLeft);
} }
} }
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
ReplayButtonState state = ReplayButtonState.None;
if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1;
return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state);
}
} }
} }

View File

@ -0,0 +1,26 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Catch.UI
{
public class CatchReplayRecorder : ReplayRecorder<CatchAction>
{
private readonly CatchPlayfield playfield;
public CatchReplayRecorder(Replay target, CatchPlayfield playfield)
: base(target)
{
this.playfield = playfield;
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<CatchAction> actions, ReplayFrame previousFrame)
=> new CatchReplayFrame(Time.Current, playfield.CatcherArea.MovableCatcher.X, actions.Contains(CatchAction.Dash), previousFrame as CatchReplayFrame);
}
}

View File

@ -141,14 +141,14 @@ namespace osu.Game.Rulesets.Catch.UI
var ourRadius = fruit.DisplayRadius; var ourRadius = fruit.DisplayRadius;
float theirRadius = 0; float theirRadius = 0;
const float allowance = 6; const float allowance = 10;
while (caughtFruit.Any(f => while (caughtFruit.Any(f =>
f.LifetimeEnd == double.MaxValue && f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{ {
var diff = (ourRadius + theirRadius) / allowance; var diff = (ourRadius + theirRadius) / allowance;
fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; fruit.X += (RNG.NextSingle() - 0.5f) * diff * 2;
fruit.Y -= RNG.NextSingle() * diff; fruit.Y -= RNG.NextSingle() * diff;
} }

View File

@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new CatchReplayRecorder(replay, (CatchPlayfield)Playfield);
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{ {
} }
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) protected override KeyBindingContainer<ManiaAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new LocalKeyBindingContainer(ruleset, variant, unique); => new LocalKeyBindingContainer(ruleset, variant, unique);
private class LocalKeyBindingContainer : RulesetKeyBindingContainer private class LocalKeyBindingContainer : RulesetKeyBindingContainer

View File

@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source); public override ISkin CreateLegacySkinProvider(ISkinSource source) => new ManiaLegacySkinTransformer(source);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))
yield return new ManiaModNightcore(); yield return new ManiaModNightcore();
@ -118,6 +118,59 @@ namespace osu.Game.Rulesets.Mania
yield return new ManiaModRandom(); yield return new ManiaModRandom();
} }
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
{
var value = base.ConvertToLegacyMods(mods);
foreach (var mod in mods)
{
switch (mod)
{
case ManiaModKey1 _:
value |= LegacyMods.Key1;
break;
case ManiaModKey2 _:
value |= LegacyMods.Key2;
break;
case ManiaModKey3 _:
value |= LegacyMods.Key3;
break;
case ManiaModKey4 _:
value |= LegacyMods.Key4;
break;
case ManiaModKey5 _:
value |= LegacyMods.Key5;
break;
case ManiaModKey6 _:
value |= LegacyMods.Key6;
break;
case ManiaModKey7 _:
value |= LegacyMods.Key7;
break;
case ManiaModKey8 _:
value |= LegacyMods.Key8;
break;
case ManiaModKey9 _:
value |= LegacyMods.Key9;
break;
case ManiaModFadeIn _:
value |= LegacyMods.FadeIn;
break;
}
}
return value;
}
public override IEnumerable<Mod> GetModsFor(ModType type) public override IEnumerable<Mod> GetModsFor(ModType type)
{ {
switch (type) switch (type)

View File

@ -3,24 +3,17 @@
using System.Linq; using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModRandom : Mod, IApplicableToBeatmap public class ManiaModRandom : ModRandom, IApplicableToBeatmap
{ {
public override string Name => "Random";
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.Dice;
public override string Description => @"Shuffle around the keys!"; public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 1;
public void ApplyToBeatmap(IBeatmap beatmap) public void ApplyToBeatmap(IBeatmap beatmap)
{ {

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Replays.Legacy; using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
@ -24,15 +25,9 @@ namespace osu.Game.Rulesets.Mania.Replays
Actions.AddRange(actions); Actions.AddRange(actions);
} }
public void ConvertFrom(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) public void FromLegacy(LegacyReplayFrame legacyFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{ {
// We don't need to fully convert, just create the converter var maniaBeatmap = (ManiaBeatmap)beatmap;
var converter = new ManiaBeatmapConverter(beatmap, new ManiaRuleset());
// NB: Via co-op mod, osu-stable can have two stages with floor(col/2) and ceil(col/2) columns. This will need special handling
// elsewhere in the game if we do choose to support the old co-op mod anyway. For now, assume that there is only one stage.
var stage = new StageDefinition { Columns = converter.TargetColumns };
var normalAction = ManiaAction.Key1; var normalAction = ManiaAction.Key1;
var specialAction = ManiaAction.Special1; var specialAction = ManiaAction.Special1;
@ -42,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Replays
while (activeColumns > 0) while (activeColumns > 0)
{ {
var isSpecial = stage.IsSpecialColumn(counter); var isSpecial = maniaBeatmap.Stages.First().IsSpecialColumn(counter);
if ((activeColumns & 1) > 0) if ((activeColumns & 1) > 0)
Actions.Add(isSpecial ? specialAction : normalAction); Actions.Add(isSpecial ? specialAction : normalAction);
@ -56,5 +51,40 @@ namespace osu.Game.Rulesets.Mania.Replays
activeColumns >>= 1; activeColumns >>= 1;
} }
} }
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
var maniaBeatmap = (ManiaBeatmap)beatmap;
int keys = 0;
var specialColumns = new List<int>();
for (int i = 0; i < maniaBeatmap.TotalColumns; i++)
{
if (maniaBeatmap.Stages.First().IsSpecialColumn(i))
specialColumns.Add(i);
}
foreach (var action in Actions)
{
switch (action)
{
case ManiaAction.Special1:
keys |= 1 << specialColumns[0];
break;
case ManiaAction.Special2:
keys |= 1 << specialColumns[1];
break;
default:
keys |= 1 << (action - ManiaAction.Key1);
break;
}
}
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
}
} }
} }

View File

@ -85,5 +85,7 @@ namespace osu.Game.Rulesets.Mania.UI
} }
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new ManiaReplayRecorder(replay);
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaReplayRecorder : ReplayRecorder<ManiaAction>
{
public ManiaReplayRecorder(Replay replay)
: base(replay)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<ManiaAction> actions, ReplayFrame previousFrame)
=> new ManiaReplayFrame(Time.Current, actions.ToArray());
}
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Scoring;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Game.Skinning; using osu.Game.Skinning;
@ -196,6 +197,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r => r.Type = r.Judgement.MaxResult); ApplyResult(r => r.Type = r.Judgement.MaxResult);
} }
public override void PlaySamples()
{
// rather than doing it this way, we should probably attach the sample to the tail circle.
// this can only be done after we stop using LegacyLastTick.
if (TailCircle.Result.Type != HitResult.Miss)
base.PlaySamples();
}
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)
{ {
base.UpdateStateTransforms(state); base.UpdateStateTransforms(state);

View File

@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderRepeatJudgement : OsuJudgement public class SliderRepeatJudgement : OsuJudgement
{ {
public override bool IsBonus => true;
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0;
} }
} }

View File

@ -36,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public class SliderTickJudgement : OsuJudgement public class SliderTickJudgement : OsuJudgement
{ {
public override bool IsBonus => true;
protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0;
} }
} }

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu
/// </summary> /// </summary>
public bool AllowUserCursorMovement { get; set; } = true; public bool AllowUserCursorMovement { get; set; } = true;
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) protected override KeyBindingContainer<OsuAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique); => new OsuKeyBindingContainer(ruleset, variant, unique);
public OsuInputManager(RulesetInfo ruleset) public OsuInputManager(RulesetInfo ruleset)

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))
yield return new OsuModNightcore(); yield return new OsuModNightcore();

View File

@ -26,11 +26,23 @@ namespace osu.Game.Rulesets.Osu.Replays
Actions.AddRange(actions); Actions.AddRange(actions);
} }
public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{ {
Position = currentFrame.Position; Position = currentFrame.Position;
if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton); if (currentFrame.MouseLeft) Actions.Add(OsuAction.LeftButton);
if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton); if (currentFrame.MouseRight) Actions.Add(OsuAction.RightButton);
} }
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
ReplayButtonState state = ReplayButtonState.None;
if (Actions.Contains(OsuAction.LeftButton))
state |= ReplayButtonState.Left1;
if (Actions.Contains(OsuAction.RightButton))
state |= ReplayButtonState.Right1;
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
}
} }
} }

View File

@ -58,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.UI
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new OsuReplayRecorder(replay);
public override double GameplayStartTime public override double GameplayStartTime
{ {
get get

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuReplayRecorder : ReplayRecorder<OsuAction>
{
public OsuReplayRecorder(Replay replay)
: base(replay)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<OsuAction> actions, ReplayFrame previousFrame)
=> new OsuReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Mods
{
public class TaikoModRandom : ModRandom, IApplicableToBeatmap
{
public override string Description => @"Shuffle around the colours!";
public void ApplyToBeatmap(IBeatmap beatmap)
{
var taikoBeatmap = (TaikoBeatmap)beatmap;
foreach (var obj in taikoBeatmap.HitObjects)
{
if (obj is Hit hit)
hit.Type = RNG.Next(2) == 0 ? HitType.Centre : HitType.Rim;
}
}
}
}

View File

@ -23,12 +23,24 @@ namespace osu.Game.Rulesets.Taiko.Replays
Actions.AddRange(actions); Actions.AddRange(actions);
} }
public void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
{ {
if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim); if (currentFrame.MouseRight1) Actions.Add(TaikoAction.LeftRim);
if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim); if (currentFrame.MouseRight2) Actions.Add(TaikoAction.RightRim);
if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre); if (currentFrame.MouseLeft1) Actions.Add(TaikoAction.LeftCentre);
if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre); if (currentFrame.MouseLeft2) Actions.Add(TaikoAction.RightCentre);
} }
public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
{
ReplayButtonState state = ReplayButtonState.None;
if (Actions.Contains(TaikoAction.LeftRim)) state |= ReplayButtonState.Right1;
if (Actions.Contains(TaikoAction.RightRim)) state |= ReplayButtonState.Right2;
if (Actions.Contains(TaikoAction.LeftCentre)) state |= ReplayButtonState.Left1;
if (Actions.Contains(TaikoAction.RightCentre)) state |= ReplayButtonState.Left2;
return new LegacyReplayFrame(Time, null, null, state);
}
} }
} }

View File

@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Taiko
new KeyBinding(InputKey.K, TaikoAction.RightRim), new KeyBinding(InputKey.K, TaikoAction.RightRim),
}; };
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) public override IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods)
{ {
if (mods.HasFlag(LegacyMods.Nightcore)) if (mods.HasFlag(LegacyMods.Nightcore))
yield return new TaikoModNightcore(); yield return new TaikoModNightcore();
@ -114,6 +114,7 @@ namespace osu.Game.Rulesets.Taiko
case ModType.Conversion: case ModType.Conversion:
return new Mod[] return new Mod[]
{ {
new TaikoModRandom(),
new TaikoModDifficultyAdjust(), new TaikoModDifficultyAdjust(),
}; };

View File

@ -65,5 +65,7 @@ namespace osu.Game.Rulesets.Taiko.UI
} }
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new TaikoFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Replay replay) => new TaikoReplayRecorder(replay);
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Taiko.Replays;
using osu.Game.Rulesets.UI;
using osuTK;
namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoReplayRecorder : ReplayRecorder<TaikoAction>
{
public TaikoReplayRecorder(Replay replay)
: base(replay)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TaikoAction> actions, ReplayFrame previousFrame) =>
new TaikoReplayFrame(Time.Current, actions.ToArray());
}
}

View File

@ -26,34 +26,34 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream); var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable); Assert.IsTrue(storyboard.HasDrawable);
Assert.AreEqual(4, storyboard.Layers.Count()); Assert.AreEqual(5, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background); Assert.IsNotNull(background);
Assert.AreEqual(16, background.Elements.Count); Assert.AreEqual(16, background.Elements.Count);
Assert.IsTrue(background.EnabledWhenFailing); Assert.IsTrue(background.VisibleWhenFailing);
Assert.IsTrue(background.EnabledWhenPassing); Assert.IsTrue(background.VisibleWhenPassing);
Assert.AreEqual("Background", background.Name); Assert.AreEqual("Background", background.Name);
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
Assert.IsNotNull(fail); Assert.IsNotNull(fail);
Assert.AreEqual(0, fail.Elements.Count); Assert.AreEqual(0, fail.Elements.Count);
Assert.IsTrue(fail.EnabledWhenFailing); Assert.IsTrue(fail.VisibleWhenFailing);
Assert.IsFalse(fail.EnabledWhenPassing); Assert.IsFalse(fail.VisibleWhenPassing);
Assert.AreEqual("Fail", fail.Name); Assert.AreEqual("Fail", fail.Name);
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
Assert.IsNotNull(pass); Assert.IsNotNull(pass);
Assert.AreEqual(0, pass.Elements.Count); Assert.AreEqual(0, pass.Elements.Count);
Assert.IsFalse(pass.EnabledWhenFailing); Assert.IsFalse(pass.VisibleWhenFailing);
Assert.IsTrue(pass.EnabledWhenPassing); Assert.IsTrue(pass.VisibleWhenPassing);
Assert.AreEqual("Pass", pass.Name); Assert.AreEqual("Pass", pass.Name);
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
Assert.IsNotNull(foreground); Assert.IsNotNull(foreground);
Assert.AreEqual(151, foreground.Elements.Count); Assert.AreEqual(151, foreground.Elements.Count);
Assert.IsTrue(foreground.EnabledWhenFailing); Assert.IsTrue(foreground.VisibleWhenFailing);
Assert.IsTrue(foreground.EnabledWhenPassing); Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name); Assert.AreEqual("Foreground", foreground.Name);
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite)); int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));

View File

@ -0,0 +1,282 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Gameplay
{
public class TestSceneReplayRecorder : OsuManualInputManagerTestScene
{
private TestRulesetInputManager playbackManager;
private TestRulesetInputManager recordingManager;
private Replay replay;
private TestReplayRecorder recorder;
[SetUp]
public void SetUp() => Schedule(() =>
{
replay = new Replay();
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = recorder = new TestReplayRecorder(replay)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestInputConsumer()
}
},
}
}
}
});
});
[Test]
public void TestBasic()
{
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddUntilStep("one frame recorded", () => replay.Frames.Count == 1);
AddAssert("position matches", () => playbackManager.ChildrenOfType<Box>().First().Position == recordingManager.ChildrenOfType<Box>().First().Position);
}
[Test]
public void TestHighFrameRate()
{
ScheduledDelegate moveFunction = null;
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
}
[Test]
public void TestLimitedFrameRate()
{
ScheduledDelegate moveFunction = null;
AddStep("lower rate", () => recorder.RecordFrameRate = 2);
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move", () => moveFunction = Scheduler.AddDelayed(() =>
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)), 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("less than 10 frames recorded", () => replay.Frames.Count < 10);
}
[Test]
public void TestLimitedFrameRateWithImportantFrames()
{
ScheduledDelegate moveFunction = null;
AddStep("lower rate", () => recorder.RecordFrameRate = 2);
AddStep("move to center", () => InputManager.MoveMouseTo(recordingManager.ScreenSpaceDrawQuad.Centre));
AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() =>
{
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0));
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}, 10, true));
AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel());
AddAssert("at least 60 frames recorded", () => replay.Frames.Count > 60);
}
protected override void Update()
{
base.Update();
playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100);
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
public override List<IInput> GetPendingInputs()
{
return new List<IInput>
{
new MousePositionAbsoluteInput
{
Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
},
new ReplayState<TestAction>
{
PressedActions = CurrentFrame?.Actions ?? new List<TestAction>()
}
};
}
}
public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
private readonly Box box;
public TestInputConsumer()
{
Size = new Vector2(30);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
box = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
Position = e.MousePosition;
return base.OnMouseMove(e);
}
public bool OnPressed(TestAction action)
{
box.Colour = Color4.White;
return true;
}
public void OnReleased(TestAction action)
{
box.Colour = Color4.Black;
}
}
public class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new TestKeyBindingContainer();
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
}
}
public class TestReplayFrame : ReplayFrame
{
public Vector2 Position;
public List<TestAction> Actions = new List<TestAction>();
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
}
public enum TestAction
{
Down,
}
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
: base(target)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame)
=> new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}
}

View File

@ -0,0 +1,221 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Tests.Visual;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Gameplay
{
public class TestSceneReplayRecording : OsuTestScene
{
private readonly TestRulesetInputManager playbackManager;
private readonly TestRulesetInputManager recordingManager;
public TestSceneReplayRecording()
{
Replay replay = new Replay();
Add(new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
Recorder = new TestReplayRecorder(replay)
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.Brown,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Recording",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestConsumer()
}
},
}
},
new Drawable[]
{
playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
},
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Colour = Color4.DarkBlue,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Text = "Playback",
Scale = new Vector2(3),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new TestConsumer()
}
},
}
}
}
});
}
protected override void Update()
{
base.Update();
playbackManager.ReplayInputHandler.SetFrameFromTime(Time.Current - 500);
}
}
public class TestFramedReplayInputHandler : FramedReplayInputHandler<TestReplayFrame>
{
public TestFramedReplayInputHandler(Replay replay)
: base(replay)
{
}
public override List<IInput> GetPendingInputs()
{
return new List<IInput>
{
new MousePositionAbsoluteInput
{
Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero)
},
new ReplayState<TestAction>
{
PressedActions = CurrentFrame?.Actions ?? new List<TestAction>()
}
};
}
}
public class TestConsumer : CompositeDrawable, IKeyBindingHandler<TestAction>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
private readonly Box box;
public TestConsumer()
{
Size = new Vector2(30);
Origin = Anchor.Centre;
InternalChildren = new Drawable[]
{
box = new Box
{
Colour = Color4.Black,
RelativeSizeAxes = Axes.Both,
},
};
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
Position = e.MousePosition;
return base.OnMouseMove(e);
}
public bool OnPressed(TestAction action)
{
box.Colour = Color4.White;
return true;
}
public void OnReleased(TestAction action)
{
box.Colour = Color4.Black;
}
}
public class TestRulesetInputManager : RulesetInputManager<TestAction>
{
public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique)
{
}
protected override KeyBindingContainer<TestAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new TestKeyBindingContainer();
internal class TestKeyBindingContainer : KeyBindingContainer<TestAction>
{
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(InputKey.MouseLeft, TestAction.Down),
};
}
}
public class TestReplayFrame : ReplayFrame
{
public Vector2 Position;
public List<TestAction> Actions = new List<TestAction>();
public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
: base(time)
{
Position = position;
Actions.AddRange(actions);
}
}
public enum TestAction
{
Down,
}
internal class TestReplayRecorder : ReplayRecorder<TestAction>
{
public TestReplayRecorder(Replay target)
: base(target)
{
}
protected override ReplayFrame HandleFrame(Vector2 mousePosition, List<TestAction> actions, ReplayFrame previousFrame) =>
new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
}
}

View File

@ -0,0 +1,31 @@
osu file format v14
[Events]
//Background and Video events
0,0,"BG.jpg",0,0
Video,0,"video.avi"
//Break Periods
//Storyboard Layer 0 (Background)
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Layer 4 (Overlay)
//Storyboard Sound Samples
[TimingPoints]
1674,333.333333333333,4,2,1,70,1,0
1674,-100,4,2,1,70,0,0
3340,-100,4,2,1,70,0,0
3507,-100,4,2,1,70,0,0
3673,-100,4,2,1,70,0,0
[Colours]
Combo1 : 240,80,80
Combo2 : 171,252,203
Combo3 : 128,128,255
Combo4 : 249,254,186
[HitObjects]
148,303,1674,5,6,3:2:0:0:
378,252,1840,1,0,0:0:0:0:
389,270,2340,5,2,0:1:0:0:

View File

@ -236,8 +236,6 @@ namespace osu.Game.Tests.Scores.IO
} }
public override IEnumerable<string> Filenames => new[] { "test_file.osr" }; public override IEnumerable<string> Filenames => new[] { "test_file.osr" };
public override Stream GetUnderlyingStream() => new MemoryStream();
} }
} }
} }

View File

@ -3,8 +3,10 @@
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.Break;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -23,10 +25,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.BreakOverlay.Breaks.First().StartTime)); AddStep("seek to break time", () => Player.GameplayClockContainer.Seek(Player.ChildrenOfType<BreakTracker>().First().Breaks.First().StartTime));
AddUntilStep("wait for seek to complete", () => AddUntilStep("wait for seek to complete", () =>
Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime); Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= Player.BreakOverlay.Breaks.First().StartTime);
AddAssert("test keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -12,14 +13,16 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
[TestFixture] [TestFixture]
public class TestSceneBreakOverlay : OsuTestScene public class TestSceneBreakTracker : OsuTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(BreakOverlay), typeof(BreakOverlay),
}; };
private readonly TestBreakOverlay breakOverlay; private readonly BreakOverlay breakOverlay;
private readonly TestBreakTracker breakTracker;
private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod> private readonly IReadOnlyList<BreakPeriod> testBreaks = new List<BreakPeriod>
{ {
@ -35,9 +38,23 @@ namespace osu.Game.Tests.Visual.Gameplay
}, },
}; };
public TestSceneBreakOverlay() public TestSceneBreakTracker()
{ {
Add(breakOverlay = new TestBreakOverlay(true)); AddRange(new Drawable[]
{
breakTracker = new TestBreakTracker(),
breakOverlay = new BreakOverlay(true, null)
{
ProcessCustomClock = false,
}
});
}
protected override void Update()
{
base.Update();
breakOverlay.Clock = breakTracker.Clock;
} }
[Test] [Test]
@ -88,7 +105,7 @@ namespace osu.Game.Tests.Visual.Gameplay
loadBreaksStep("multiple breaks", testBreaks); loadBreaksStep("multiple breaks", testBreaks);
seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true); seekAndAssertBreak("seek to break start", testBreaks[1].StartTime, true);
AddAssert("is skipped to break #2", () => breakOverlay.CurrentBreakIndex == 1); AddAssert("is skipped to break #2", () => breakTracker.CurrentBreakIndex == 1);
seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true); seekAndAssertBreak("seek to break middle", testBreaks[1].StartTime + testBreaks[1].Duration / 2, true);
seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false); seekAndAssertBreak("seek to break end", testBreaks[1].EndTime, false);
@ -110,7 +127,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addShowBreakStep(double seconds) private void addShowBreakStep(double seconds)
{ {
AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = new List<BreakPeriod> AddStep($"show '{seconds}s' break", () => breakOverlay.Breaks = breakTracker.Breaks = new List<BreakPeriod>
{ {
new BreakPeriod new BreakPeriod
{ {
@ -122,12 +139,12 @@ namespace osu.Game.Tests.Visual.Gameplay
private void setClock(bool useManual) private void setClock(bool useManual)
{ {
AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakOverlay.SwitchClock(useManual)); AddStep($"set {(useManual ? "manual" : "realtime")} clock", () => breakTracker.SwitchClock(useManual));
} }
private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks) private void loadBreaksStep(string breakDescription, IReadOnlyList<BreakPeriod> breaks)
{ {
AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breaks); AddStep($"load {breakDescription}", () => breakOverlay.Breaks = breakTracker.Breaks = breaks);
seekAndAssertBreak("seek back to 0", 0, false); seekAndAssertBreak("seek back to 0", 0, false);
} }
@ -151,17 +168,18 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak) private void seekAndAssertBreak(string seekStepDescription, double time, bool shouldBeBreak)
{ {
AddStep(seekStepDescription, () => breakOverlay.ManualClockTime = time); AddStep(seekStepDescription, () => breakTracker.ManualClockTime = time);
AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () => AddAssert($"is{(!shouldBeBreak ? " not" : string.Empty)} break time", () =>
{ {
breakOverlay.ProgressTime(); breakTracker.ProgressTime();
return breakOverlay.IsBreakTime.Value == shouldBeBreak; return breakTracker.IsBreakTime.Value == shouldBeBreak;
}); });
} }
private class TestBreakOverlay : BreakOverlay private class TestBreakTracker : BreakTracker
{ {
private readonly FramedClock framedManualClock; public readonly FramedClock FramedManualClock;
private readonly ManualClock manualClock; private readonly ManualClock manualClock;
private IFrameBasedClock originalClock; private IFrameBasedClock originalClock;
@ -173,20 +191,19 @@ namespace osu.Game.Tests.Visual.Gameplay
set => manualClock.CurrentTime = value; set => manualClock.CurrentTime = value;
} }
public TestBreakOverlay(bool letterboxing) public TestBreakTracker()
: base(letterboxing)
{ {
framedManualClock = new FramedClock(manualClock = new ManualClock()); FramedManualClock = new FramedClock(manualClock = new ManualClock());
ProcessCustomClock = false; ProcessCustomClock = false;
} }
public void ProgressTime() public void ProgressTime()
{ {
framedManualClock.ProcessFrame(); FramedManualClock.ProcessFrame();
Update(); Update();
} }
public void SwitchClock(bool setManual) => Clock = setManual ? framedManualClock : originalClock; public void SwitchClock(bool setManual) => Clock = setManual ? FramedManualClock : originalClock;
protected override void LoadComplete() protected override void LoadComplete()
{ {

View File

@ -9,8 +9,12 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Resources;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -54,7 +58,11 @@ namespace osu.Game.Tests.Visual.Gameplay
State = { Value = Visibility.Visible }, State = { Value = Visibility.Visible },
} }
}); });
}
[Test]
public void TestStoryboard()
{
AddStep("Restart", restart); AddStep("Restart", restart);
AddToggleStep("Passing", passing => AddToggleStep("Passing", passing =>
{ {
@ -62,6 +70,12 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
} }
[Test]
public void TestStoryboardMissingVideo()
{
AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -94,5 +108,28 @@ namespace osu.Game.Tests.Visual.Gameplay
storyboardContainer.Add(storyboard); storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(working.Track); decoupledClock.ChangeSource(working.Track);
} }
private void loadStoryboardNoVideo()
{
if (storyboard != null)
storyboardContainer.Remove(storyboard);
var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true };
storyboardContainer.Clock = decoupledClock;
Storyboard sb;
using (var str = TestResources.OpenResource("storyboard_no_video.osu"))
using (var bfr = new LineBufferedReader(str))
{
var decoder = new LegacyStoryboardDecoder();
sb = decoder.Decode(bfr);
}
storyboard = sb.CreateDrawable(Beatmap.Value);
storyboardContainer.Add(storyboard);
decoupledClock.ChangeSource(Beatmap.Value.Track);
}
} }
} }

View File

@ -64,6 +64,8 @@ namespace osu.Game.Tests.Visual.Menus
introStack.Push(CreateScreen()); introStack.Push(CreateScreen());
}); });
AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu);
} }
protected abstract IScreen CreateScreen(); protected abstract IScreen CreateScreen();

View File

@ -36,8 +36,6 @@ namespace osu.Game.Tests.Visual.Menus
[Test] [Test]
public void TestInstantLoad() public void TestInstantLoad()
{ {
// visual only, very impossible to test this using asserts.
AddStep("load immediately", () => AddStep("load immediately", () =>
{ {
loader = new TestLoader(); loader = new TestLoader();
@ -46,12 +44,17 @@ namespace osu.Game.Tests.Visual.Menus
LoadScreen(loader); LoadScreen(loader);
}); });
AddAssert("spinner did not display", () => loader.LoadingSpinner?.Alpha == 0); spinnerNotPresentOrHidden();
AddUntilStep("loaded", () => loader.ScreenLoaded); AddUntilStep("loaded", () => loader.ScreenLoaded);
AddUntilStep("not current", () => !loader.IsCurrentScreen()); AddUntilStep("not current", () => !loader.IsCurrentScreen());
spinnerNotPresentOrHidden();
} }
private void spinnerNotPresentOrHidden() =>
AddAssert("spinner did not display", () => loader.LoadingSpinner == null || loader.LoadingSpinner.Alpha == 0);
[Test] [Test]
public void TestDelayedLoad() public void TestDelayedLoad()
{ {

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online
private readonly Bindable<UserStatus> status = new Bindable<UserStatus>(); private readonly Bindable<UserStatus> status = new Bindable<UserStatus>();
private UserGridPanel peppy; private UserGridPanel peppy;
private UserListPanel evast; private TestUserListPanel evast;
[Resolved] [Resolved]
private RulesetStore rulesetStore { get; set; } private RulesetStore rulesetStore { get; set; }
@ -38,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online
{ {
UserGridPanel flyte; UserGridPanel flyte;
activity.Value = null;
status.Value = null;
Child = new FillFlowContainer Child = new FillFlowContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Online
IsSupporter = true, IsSupporter = true,
SupportLevel = 3, SupportLevel = 3,
}) { Width = 300 }, }) { Width = 300 },
evast = new UserListPanel(new User evast = new TestUserListPanel(new User
{ {
Username = @"Evast", Username = @"Evast",
Id = 8195163, Id = 8195163,
@ -96,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestUserActivity() public void TestUserActivity()
{ {
AddStep("set online status", () => peppy.Status.Value = evast.Status.Value = new UserStatusOnline()); AddStep("set online status", () => status.Value = new UserStatusOnline());
AddStep("idle", () => activity.Value = null); AddStep("idle", () => activity.Value = null);
AddStep("spectating", () => activity.Value = new UserActivity.Spectating()); AddStep("spectating", () => activity.Value = new UserActivity.Spectating());
@ -109,6 +112,29 @@ namespace osu.Game.Tests.Visual.Online
AddStep("modding", () => activity.Value = new UserActivity.Modding()); AddStep("modding", () => activity.Value = new UserActivity.Modding());
} }
[Test]
public void TestUserActivityChange()
{
AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent);
AddStep("set online status", () => status.Value = new UserStatusOnline());
AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent);
AddStep("set choosing activity", () => activity.Value = new UserActivity.ChoosingBeatmap());
AddStep("set offline status", () => status.Value = new UserStatusOffline());
AddAssert("visit message is visible", () => evast.LastVisitMessage.IsPresent);
AddStep("set online status", () => status.Value = new UserStatusOnline());
AddAssert("visit message is not visible", () => !evast.LastVisitMessage.IsPresent);
}
private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId)); private UserActivity soloGameStatusForRuleset(int rulesetId) => new UserActivity.SoloGame(null, rulesetStore.GetRuleset(rulesetId));
private class TestUserListPanel : UserListPanel
{
public TestUserListPanel(User user)
: base(user)
{
}
public new TextFlowContainer LastVisitMessage => base.LastVisitMessage;
}
} }
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.IO; using osu.Game.IO;
@ -51,8 +50,6 @@ namespace osu.Game.Tests
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override VideoSprite GetVideo() => null;
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
protected override Track GetTrack() => trackStore.Get(firstAudioFile); protected override Track GetTrack() => trackStore.Get(firstAudioFile);

View File

@ -14,7 +14,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -403,7 +402,6 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap; protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override VideoSprite GetVideo() => null;
protected override Track GetTrack() => null; protected override Track GetTrack() => null;
} }

View File

@ -6,7 +6,6 @@ using System.Linq;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
@ -67,24 +66,6 @@ namespace osu.Game.Beatmaps
} }
} }
protected override VideoSprite GetVideo()
{
if (Metadata?.VideoFile == null)
return null;
try
{
var stream = textureStore.GetStream(getPathForFile(Metadata.VideoFile));
return stream == null ? null : new VideoSprite(stream);
}
catch (Exception e)
{
Logger.Error(e, "Video failed to load");
return null;
}
}
protected override Track GetTrack() protected override Track GetTrack()
{ {
try try

View File

@ -52,7 +52,6 @@ namespace osu.Game.Beatmaps
public int PreviewTime { get; set; } public int PreviewTime { get; set; }
public string AudioFile { get; set; } public string AudioFile { get; set; }
public string BackgroundFile { get; set; } public string BackgroundFile { get; set; }
public string VideoFile { get; set; }
public override string ToString() => $"{Artist} - {Title} ({Author})"; public override string ToString() => $"{Artist} - {Title} ({Author})";
@ -82,8 +81,7 @@ namespace osu.Game.Beatmaps
&& Tags == other.Tags && Tags == other.Tags
&& PreviewTime == other.PreviewTime && PreviewTime == other.PreviewTime
&& AudioFile == other.AudioFile && AudioFile == other.AudioFile
&& BackgroundFile == other.BackgroundFile && BackgroundFile == other.BackgroundFile;
&& VideoFile == other.VideoFile;
} }
} }
} }

View File

@ -7,7 +7,6 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -45,8 +44,6 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
protected override VideoSprite GetVideo() => null;
protected override Track GetTrack() => GetVirtualTrack(); protected override Track GetTrack() => GetVirtualTrack();
private class DummyRulesetInfo : RulesetInfo private class DummyRulesetInfo : RulesetInfo

View File

@ -6,11 +6,11 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO;
using osu.Game.Beatmaps.Legacy; using osu.Game.Beatmaps.Legacy;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
using osu.Game.Rulesets.Objects.Legacy;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -303,10 +303,6 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]); beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break; break;
case LegacyEventType.Video:
beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]);
break;
case LegacyEventType.Break: case LegacyEventType.Break:
double start = getOffsetTime(Parsing.ParseDouble(split[1])); double start = getOffsetTime(Parsing.ParseDouble(split[1]));

View File

@ -133,9 +133,6 @@ namespace osu.Game.Beatmaps.Formats
if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile)) if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0")); writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Background},0,\"{beatmap.BeatmapInfo.Metadata.BackgroundFile}\",0,0"));
if (!string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.VideoFile))
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Video},0,\"{beatmap.BeatmapInfo.Metadata.VideoFile}\",0,0"));
foreach (var b in beatmap.Breaks) foreach (var b in beatmap.Breaks)
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}")); writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
} }

View File

@ -4,13 +4,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Beatmaps.Legacy;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Storyboards; using osu.Game.Storyboards;
using osu.Game.Beatmaps.Legacy; using osuTK;
using osu.Framework.Utils; using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -87,6 +87,15 @@ namespace osu.Game.Beatmaps.Formats
switch (type) switch (type)
{ {
case LegacyEventType.Video:
{
var offset = Parsing.ParseInt(split[1]);
var path = CleanFilename(split[2]);
storyboard.GetLayer("Video").Add(new StoryboardVideo(path, offset));
break;
}
case LegacyEventType.Sprite: case LegacyEventType.Sprite:
{ {
var layer = parseLayer(split[1]); var layer = parseLayer(split[1]);

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
@ -27,11 +26,6 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
Texture Background { get; } Texture Background { get; }
/// <summary>
/// Retrieves the video background file for this <see cref="WorkingBeatmap"/>.
/// </summary>
VideoSprite Video { get; }
/// <summary> /// <summary>
/// Retrieves the audio track for this <see cref="WorkingBeatmap"/>. /// Retrieves the audio track for this <see cref="WorkingBeatmap"/>.
/// </summary> /// </summary>

View File

@ -8,6 +8,7 @@ namespace osu.Game.Beatmaps.Legacy
Background = 0, Background = 0,
Fail = 1, Fail = 1,
Pass = 2, Pass = 2,
Foreground = 3 Foreground = 3,
Video = 4
} }
} }

View File

@ -1,23 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Game.Rulesets.Mods;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Storyboards;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Statistics; using osu.Framework.Statistics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Framework.Graphics.Video; using osu.Game.Storyboards;
using osu.Framework.Logging;
namespace osu.Game.Beatmaps namespace osu.Game.Beatmaps
{ {
@ -234,10 +233,6 @@ namespace osu.Game.Beatmaps
protected abstract Texture GetBackground(); protected abstract Texture GetBackground();
private readonly RecyclableLazy<Texture> background; private readonly RecyclableLazy<Texture> background;
public VideoSprite Video => GetVideo();
protected abstract VideoSprite GetVideo();
public bool TrackLoaded => track.IsResultAvailable; public bool TrackLoaded => track.IsResultAvailable;
public Track Track => track.Value; public Track Track => track.Value;
protected abstract Track GetTrack(); protected abstract Track GetTrack();

View File

@ -70,7 +70,6 @@ namespace osu.Game.Configuration
Set(OsuSetting.ShowFpsDisplay, false); Set(OsuSetting.ShowFpsDisplay, false);
Set(OsuSetting.ShowStoryboard, true); Set(OsuSetting.ShowStoryboard, true);
Set(OsuSetting.ShowVideoBackground, true);
Set(OsuSetting.BeatmapSkins, true); Set(OsuSetting.BeatmapSkins, true);
Set(OsuSetting.BeatmapHitsounds, true); Set(OsuSetting.BeatmapHitsounds, true);
@ -176,7 +175,6 @@ namespace osu.Game.Configuration
BlurLevel, BlurLevel,
LightenDuringBreaks, LightenDuringBreaks,
ShowStoryboard, ShowStoryboard,
ShowVideoBackground,
KeyOverlay, KeyOverlay,
ScoreMeter, ScoreMeter,
FloatingComments, FloatingComments,

View File

@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers
/// <summary> /// <summary>
/// Whether player is in break time. /// Whether player is in break time.
/// Must be bound to <see cref="BreakOverlay.IsBreakTime"/> to allow for dim adjustments in gameplay. /// Must be bound to <see cref="BreakTracker.IsBreakTime"/> to allow for dim adjustments in gameplay.
/// </summary> /// </summary>
public readonly IBindable<bool> IsBreakTime = new Bindable<bool>(); public readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
@ -55,8 +55,6 @@ namespace osu.Game.Graphics.Containers
protected Bindable<bool> ShowStoryboard { get; private set; } protected Bindable<bool> ShowStoryboard { get; private set; }
protected Bindable<bool> ShowVideo { get; private set; }
private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0; private float breakLightening => LightenDuringBreaks.Value && IsBreakTime.Value ? BREAK_LIGHTEN_AMOUNT : 0;
protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0); protected float DimLevel => Math.Max(EnableUserDim.Value && !IgnoreUserSettings.Value ? (float)UserDimLevel.Value - breakLightening : 0, 0);
@ -79,14 +77,12 @@ namespace osu.Game.Graphics.Containers
UserDimLevel = config.GetBindable<double>(OsuSetting.DimLevel); UserDimLevel = config.GetBindable<double>(OsuSetting.DimLevel);
LightenDuringBreaks = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks); LightenDuringBreaks = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks);
ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard); ShowStoryboard = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
ShowVideo = config.GetBindable<bool>(OsuSetting.ShowVideoBackground);
EnableUserDim.ValueChanged += _ => UpdateVisuals(); EnableUserDim.ValueChanged += _ => UpdateVisuals();
UserDimLevel.ValueChanged += _ => UpdateVisuals(); UserDimLevel.ValueChanged += _ => UpdateVisuals();
LightenDuringBreaks.ValueChanged += _ => UpdateVisuals(); LightenDuringBreaks.ValueChanged += _ => UpdateVisuals();
IsBreakTime.ValueChanged += _ => UpdateVisuals(); IsBreakTime.ValueChanged += _ => UpdateVisuals();
ShowStoryboard.ValueChanged += _ => UpdateVisuals(); ShowStoryboard.ValueChanged += _ => UpdateVisuals();
ShowVideo.ValueChanged += _ => UpdateVisuals();
StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals(); StoryboardReplacesBackground.ValueChanged += _ => UpdateVisuals();
IgnoreUserSettings.ValueChanged += _ => UpdateVisuals(); IgnoreUserSettings.ValueChanged += _ => UpdateVisuals();
} }

View File

@ -1,9 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
namespace osu.Game.Graphics.Sprites namespace osu.Game.Graphics.Sprites
{ {
@ -15,23 +13,4 @@ namespace osu.Game.Graphics.Sprites
Font = OsuFont.Default; Font = OsuFont.Default;
} }
} }
public static class OsuSpriteTextTransformExtensions
{
/// <summary>
/// Sets <see cref="SpriteText.Text">Text</see> to a new value after a duration.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> TransformTextTo<T>(this T spriteText, string newText, double duration = 0, Easing easing = Easing.None)
where T : OsuSpriteText
=> spriteText.TransformTo(nameof(OsuSpriteText.Text), newText, duration, easing);
/// <summary>
/// Sets <see cref="SpriteText.Text">Text</see> to a new value after a duration.
/// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> TransformTextTo<T>(this TransformSequence<T> t, string newText, double duration = 0, Easing easing = Easing.None)
where T : OsuSpriteText
=> t.Append(o => o.TransformTextTo(newText, duration, easing));
}
} }

View File

@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives
return buffer; return buffer;
} }
} }
public abstract Stream GetUnderlyingStream();
} }
} }

View File

@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
namespace osu.Game.IO.Archives
{
/// <summary>
/// Allows reading a single file from the provided stream.
/// </summary>
public class LegacyByteArrayReader : ArchiveReader
{
private readonly byte[] content;
public LegacyByteArrayReader(byte[] content, string filename)
: base(filename)
{
this.content = content;
}
public override Stream GetStream(string name) => new MemoryStream(content);
public override void Dispose()
{
}
public override IEnumerable<string> Filenames => new[] { Name };
}
}

View File

@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives
} }
public override IEnumerable<string> Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray(); public override IEnumerable<string> Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray();
public override Stream GetUnderlyingStream() => null;
} }
} }

View File

@ -28,7 +28,5 @@ namespace osu.Game.IO.Archives
} }
public override IEnumerable<string> Filenames => new[] { Name }; public override IEnumerable<string> Filenames => new[] { Name };
public override Stream GetUnderlyingStream() => null;
} }
} }

View File

@ -45,7 +45,5 @@ namespace osu.Game.IO.Archives
} }
public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames();
public override Stream GetUnderlyingStream() => archiveStream;
} }
} }

View File

@ -18,15 +18,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{ {
new SettingsCheckbox new SettingsCheckbox
{ {
LabelText = "Storyboards", LabelText = "Storyboard / Video",
Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard) Bindable = config.GetBindable<bool>(OsuSetting.ShowStoryboard)
}, },
new SettingsCheckbox new SettingsCheckbox
{
LabelText = "Video",
Bindable = config.GetBindable<bool>(OsuSetting.ShowVideoBackground)
},
new SettingsCheckbox
{ {
LabelText = "Hit Lighting", LabelText = "Hit Lighting",
Bindable = config.GetBindable<bool>(OsuSetting.HitLighting) Bindable = config.GetBindable<bool>(OsuSetting.HitLighting)

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -56,24 +57,32 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, },
}; };
rawInputToggle.ValueChanged += enabled => if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
{ {
// this is temporary until we support per-handler settings. rawInputToggle.Disabled = true;
const string raw_mouse_handler = @"OsuTKRawMouseHandler"; sensitivity.Bindable.Disabled = true;
const string standard_mouse_handler = @"OsuTKMouseHandler"; }
else
ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
};
ignoredInputHandler = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
ignoredInputHandler.ValueChanged += handler =>
{ {
bool raw = !handler.NewValue.Contains("Raw"); rawInputToggle.ValueChanged += enabled =>
rawInputToggle.Value = raw; {
sensitivity.Bindable.Disabled = !raw; // this is temporary until we support per-handler settings.
}; const string raw_mouse_handler = @"OsuTKRawMouseHandler";
const string standard_mouse_handler = @"OsuTKMouseHandler";
ignoredInputHandler.TriggerChange(); ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler;
};
ignoredInputHandler = config.GetBindable<string>(FrameworkSetting.IgnoredInputHandlers);
ignoredInputHandler.ValueChanged += handler =>
{
bool raw = !handler.NewValue.Contains("Raw");
rawInputToggle.Value = raw;
sensitivity.Bindable.Disabled = !raw;
};
ignoredInputHandler.TriggerChange();
}
} }
private class SensitivitySetting : SettingsSlider<double, SensitivitySlider> private class SensitivitySetting : SettingsSlider<double, SensitivitySlider>

View File

@ -39,7 +39,6 @@ namespace osu.Game.Rulesets.Mods
{ {
player.Background.EnableUserDim.Value = false; player.Background.EnableUserDim.Value = false;
player.DimmableVideo.IgnoreUserSettings.Value = true;
player.DimmableStoryboard.IgnoreUserSettings.Value = true; player.DimmableStoryboard.IgnoreUserSettings.Value = true;
player.BreakOverlay.Hide(); player.BreakOverlay.Hide();

View File

@ -0,0 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods
{
public abstract class ModRandom : Mod
{
public override string Name => "Random";
public override string Acronym => "RD";
public override ModType Type => ModType.Conversion;
public override IconUsage? Icon => OsuIcon.Dice;
public override double ScoreMultiplier => 1;
}
}

View File

@ -344,7 +344,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// Plays all the hit sounds for this <see cref="DrawableHitObject"/>. /// Plays all the hit sounds for this <see cref="DrawableHitObject"/>.
/// This is invoked automatically when this <see cref="DrawableHitObject"/> is hit. /// This is invoked automatically when this <see cref="DrawableHitObject"/> is hit.
/// </summary> /// </summary>
public void PlaySamples() => Samples?.Play(); public virtual void PlaySamples() => Samples?.Play();
protected override void Update() protected override void Update()
{ {

View File

@ -17,6 +17,12 @@ namespace osu.Game.Rulesets.Replays.Types
/// <param name="currentFrame">The <see cref="LegacyReplayFrame"/> to extract values from.</param> /// <param name="currentFrame">The <see cref="LegacyReplayFrame"/> to extract values from.</param>
/// <param name="beatmap">The beatmap.</param> /// <param name="beatmap">The beatmap.</param>
/// <param name="lastFrame">The last post-conversion <see cref="ReplayFrame"/>, used to fill in missing delta information. May be null.</param> /// <param name="lastFrame">The last post-conversion <see cref="ReplayFrame"/>, used to fill in missing delta information. May be null.</param>
void ConvertFrom(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null); void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null);
/// <summary>
/// Populates this <see cref="ReplayFrame"/> using values from a <see cref="LegacyReplayFrame"/>.
/// </summary>
/// <param name="beatmap">The beatmap.</param>
LegacyReplayFrame ToLegacy(IBeatmap beatmap);
} }
} }

View File

@ -42,9 +42,63 @@ namespace osu.Game.Rulesets
/// <summary> /// <summary>
/// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset. /// Converts mods from legacy enum values. Do not override if you're not a legacy ruleset.
/// </summary> /// </summary>
/// <param name="mods">The legacy enum which will be converted</param> /// <param name="mods">The legacy enum which will be converted.</param>
/// <returns>An enumerable of constructed <see cref="Mod"/>s</returns> /// <returns>An enumerable of constructed <see cref="Mod"/>s.</returns>
public virtual IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods) => Array.Empty<Mod>(); public virtual IEnumerable<Mod> ConvertFromLegacyMods(LegacyMods mods) => Array.Empty<Mod>();
/// <summary>
/// Converts mods to legacy enum values. Do not override if you're not a legacy ruleset.
/// </summary>
/// <param name="mods">The mods which will be converted.</param>
/// <returns>A single bitwise enumerable value representing (to the best of our ability) the mods.</returns>
public virtual LegacyMods ConvertToLegacyMods(Mod[] mods)
{
var value = LegacyMods.None;
foreach (var mod in mods)
{
switch (mod)
{
case ModNoFail _:
value |= LegacyMods.NoFail;
break;
case ModEasy _:
value |= LegacyMods.Easy;
break;
case ModHidden _:
value |= LegacyMods.Hidden;
break;
case ModHardRock _:
value |= LegacyMods.HardRock;
break;
case ModSuddenDeath _:
value |= LegacyMods.SuddenDeath;
break;
case ModDoubleTime _:
value |= LegacyMods.DoubleTime;
break;
case ModRelax _:
value |= LegacyMods.Relax;
break;
case ModHalfTime _:
value |= LegacyMods.HalfTime;
break;
case ModFlashlight _:
value |= LegacyMods.Flashlight;
break;
}
}
return value;
}
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().First(); public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().First();

View File

@ -72,9 +72,9 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public override Playfield Playfield => playfield.Value; public override Playfield Playfield => playfield.Value;
private Container overlays; public override Container Overlays { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override Container Overlays => overlays; public override Container FrameStableComponents { get; } = new Container { RelativeSizeAxes = Axes.Both };
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
@ -187,11 +187,12 @@ namespace osu.Game.Rulesets.UI
FrameStablePlayback = FrameStablePlayback, FrameStablePlayback = FrameStablePlayback,
Children = new Drawable[] Children = new Drawable[]
{ {
FrameStableComponents,
KeyBindingInputManager KeyBindingInputManager
.WithChild(CreatePlayfieldAdjustmentContainer() .WithChild(CreatePlayfieldAdjustmentContainer()
.WithChild(Playfield) .WithChild(Playfield)
), ),
overlays = new Container { RelativeSizeAxes = Axes.Both } Overlays,
} }
}, },
}; };
@ -262,6 +263,21 @@ namespace osu.Game.Rulesets.UI
Playfield.Add(drawableObject); Playfield.Add(drawableObject);
} }
public override void SetRecordTarget(Replay recordingReplay)
{
if (!(KeyBindingInputManager is IHasRecordingHandler recordingInputManager))
throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports recording is not available");
var recorder = CreateReplayRecorder(recordingReplay);
if (recorder == null)
return;
recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield;
recordingInputManager.Recorder = recorder;
}
public override void SetReplayScore(Score replayScore) public override void SetReplayScore(Score replayScore)
{ {
if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager))
@ -302,6 +318,8 @@ namespace osu.Game.Rulesets.UI
protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
protected virtual ReplayRecorder CreateReplayRecorder(Replay replay) => null;
/// <summary> /// <summary>
/// Creates a Playfield. /// Creates a Playfield.
/// </summary> /// </summary>
@ -389,10 +407,15 @@ namespace osu.Game.Rulesets.UI
public abstract Playfield Playfield { get; } public abstract Playfield Playfield { get; }
/// <summary> /// <summary>
/// Place to put drawables above hit objects but below UI. /// Content to be placed above hitobjects. Will be affected by frame stability.
/// </summary> /// </summary>
public abstract Container Overlays { get; } public abstract Container Overlays { get; }
/// <summary>
/// Components to be run potentially multiple times in line with frame-stable gameplay.
/// </summary>
public abstract Container FrameStableComponents { get; }
/// <summary> /// <summary>
/// The frame-stable clock which is being used for playfield display. /// The frame-stable clock which is being used for playfield display.
/// </summary> /// </summary>
@ -470,6 +493,12 @@ namespace osu.Game.Rulesets.UI
/// <param name="replayScore">The replay, null for local input.</param> /// <param name="replayScore">The replay, null for local input.</param>
public abstract void SetReplayScore(Score replayScore); public abstract void SetReplayScore(Score replayScore);
/// <summary>
/// Sets a replay to be used to record gameplay.
/// </summary>
/// <param name="recordingReplay">The target to be recorded to.</param>
public abstract void SetRecordTarget(Replay recordingReplay);
/// <summary> /// <summary>
/// Invoked when the interactive user requests resuming from a paused state. /// Invoked when the interactive user requests resuming from a paused state.
/// Allows potentially delaying the resume process until an interaction is performed. /// Allows potentially delaying the resume process until an interaction is performed.

View File

@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.UI
{ {
/// <summary> /// <summary>
/// A container which consumes a parent gameplay clock and standardises frame counts for children. /// A container which consumes a parent gameplay clock and standardises frame counts for children.
/// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. /// Will ensure a minimum of 50 frames per clock second is maintained, regardless of any system lag or seeks.
/// </summary> /// </summary>
public class FrameStabilityContainer : Container, IHasReplayHandler public class FrameStabilityContainer : Container, IHasReplayHandler
{ {

View File

@ -30,6 +30,11 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public Func<Vector2, Vector2> GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace; public Func<Vector2, Vector2> GamefieldToScreenSpace => HitObjectContainer.ToScreenSpace;
/// <summary>
/// A function that converts screen space coordinates to gamefield.
/// </summary>
public Func<Vector2, Vector2> ScreenSpaceToGamefield => HitObjectContainer.ToLocalSpace;
/// <summary> /// <summary>
/// All the <see cref="DrawableHitObject"/>s contained in this <see cref="Playfield"/> and all <see cref="NestedPlayfields"/>. /// All the <see cref="DrawableHitObject"/>s contained in this <see cref="Playfield"/> and all <see cref="NestedPlayfields"/>.
/// </summary> /// </summary>

View File

@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Replays;
using osu.Game.Rulesets.Replays;
using osuTK;
namespace osu.Game.Rulesets.UI
{
public abstract class ReplayRecorder<T> : ReplayRecorder, IKeyBindingHandler<T>
where T : struct
{
private readonly Replay target;
private readonly List<T> pressedActions = new List<T>();
private InputManager inputManager;
public int RecordFrameRate = 60;
protected ReplayRecorder(Replay target)
{
this.target = target;
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
}
protected override void LoadComplete()
{
base.LoadComplete();
inputManager = GetContainingInputManager();
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
recordFrame(false);
return base.OnMouseMove(e);
}
public bool OnPressed(T action)
{
pressedActions.Add(action);
recordFrame(true);
return false;
}
public void OnReleased(T action)
{
pressedActions.Remove(action);
recordFrame(true);
}
private void recordFrame(bool important)
{
var last = target.Frames.LastOrDefault();
if (!important && last != null && Time.Current - last.Time < (1000d / RecordFrameRate))
return;
var position = ScreenSpaceToGamefield?.Invoke(inputManager.CurrentState.Mouse.Position) ?? inputManager.CurrentState.Mouse.Position;
var frame = HandleFrame(position, pressedActions, last);
if (frame != null)
target.Frames.Add(frame);
}
protected abstract ReplayFrame HandleFrame(Vector2 mousePosition, List<T> actions, ReplayFrame previousFrame);
}
public abstract class ReplayRecorder : Component
{
public Func<Vector2, Vector2> ScreenSpaceToGamefield;
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -23,9 +24,24 @@ using MouseState = osu.Framework.Input.States.MouseState;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler, IHasRecordingHandler
where T : struct where T : struct
{ {
private ReplayRecorder recorder;
public ReplayRecorder Recorder
{
set
{
if (recorder != null)
throw new InvalidOperationException("Cannot attach more than one recorder");
recorder = value;
KeyBindingContainer.Add(recorder);
}
}
protected override InputState CreateInitialState() protected override InputState CreateInitialState()
{ {
var state = base.CreateInitialState(); var state = base.CreateInitialState();
@ -148,7 +164,7 @@ namespace osu.Game.Rulesets.UI
#endregion #endregion
protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) protected virtual KeyBindingContainer<T> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new RulesetKeyBindingContainer(ruleset, variant, unique); => new RulesetKeyBindingContainer(ruleset, variant, unique);
public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer<T> public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer<T>
@ -168,6 +184,11 @@ namespace osu.Game.Rulesets.UI
ReplayInputHandler ReplayInputHandler { get; set; } ReplayInputHandler ReplayInputHandler { get; set; }
} }
public interface IHasRecordingHandler
{
public ReplayRecorder Recorder { set; }
}
/// <summary> /// <summary>
/// Supports attaching a <see cref="KeyCounterDisplay"/>. /// Supports attaching a <see cref="KeyCounterDisplay"/>.
/// Keys will be populated automatically and a receptor will be injected inside. /// Keys will be populated automatically and a receptor will be injected inside.

View File

@ -7,15 +7,15 @@ using osu.Game.Rulesets;
namespace osu.Game.Scoring.Legacy namespace osu.Game.Scoring.Legacy
{ {
/// <summary> /// <summary>
/// A <see cref="LegacyScoreParser"/> which retrieves the applicable <see cref="Beatmap"/> and <see cref="Ruleset"/> /// A <see cref="LegacyScoreDecoder"/> which retrieves the applicable <see cref="Beatmap"/> and <see cref="Ruleset"/>
/// for the score from the database. /// for the score from the database.
/// </summary> /// </summary>
public class DatabasedLegacyScoreParser : LegacyScoreParser public class DatabasedLegacyScoreDecoder : LegacyScoreDecoder
{ {
private readonly RulesetStore rulesets; private readonly RulesetStore rulesets;
private readonly BeatmapManager beatmaps; private readonly BeatmapManager beatmaps;
public DatabasedLegacyScoreParser(RulesetStore rulesets, BeatmapManager beatmaps) public DatabasedLegacyScoreDecoder(RulesetStore rulesets, BeatmapManager beatmaps)
{ {
this.rulesets = rulesets; this.rulesets = rulesets;
this.beatmaps = beatmaps; this.beatmaps = beatmaps;

View File

@ -19,7 +19,7 @@ using SharpCompress.Compressors.LZMA;
namespace osu.Game.Scoring.Legacy namespace osu.Game.Scoring.Legacy
{ {
public abstract class LegacyScoreParser public abstract class LegacyScoreDecoder
{ {
private IBeatmap currentBeatmap; private IBeatmap currentBeatmap;
private Ruleset currentRuleset; private Ruleset currentRuleset;
@ -45,9 +45,6 @@ namespace osu.Game.Scoring.Legacy
if (workingBeatmap is DummyWorkingBeatmap) if (workingBeatmap is DummyWorkingBeatmap)
throw new BeatmapNotFoundException(); throw new BeatmapNotFoundException();
currentBeatmap = workingBeatmap.Beatmap;
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
scoreInfo.User = new User { Username = sr.ReadString() }; scoreInfo.User = new User { Username = sr.ReadString() };
// MD5Hash // MD5Hash
@ -66,7 +63,10 @@ namespace osu.Game.Scoring.Legacy
/* score.Perfect = */ /* score.Perfect = */
sr.ReadBoolean(); sr.ReadBoolean();
scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); scoreInfo.Mods = currentRuleset.ConvertFromLegacyMods((LegacyMods)sr.ReadInt32()).ToArray();
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
/* score.HpGraphString = */ /* score.HpGraphString = */
sr.ReadString(); sr.ReadString();
@ -264,7 +264,7 @@ namespace osu.Game.Scoring.Legacy
if (convertible == null) if (convertible == null)
throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}");
convertible.ConvertFrom(currentFrame, currentBeatmap, lastFrame); convertible.FromLegacy(currentFrame, currentBeatmap, lastFrame);
var frame = (ReplayFrame)convertible; var frame = (ReplayFrame)convertible;
frame.Time = currentFrame.Time; frame.Time = currentFrame.Time;

View File

@ -0,0 +1,114 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Linq;
using System.Text;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.IO.Legacy;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types;
using SharpCompress.Compressors.LZMA;
namespace osu.Game.Scoring.Legacy
{
public class LegacyScoreEncoder
{
public const int LATEST_VERSION = 128;
private readonly Score score;
private readonly IBeatmap beatmap;
public LegacyScoreEncoder(Score score, IBeatmap beatmap)
{
this.score = score;
this.beatmap = beatmap;
if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3)
throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score));
}
public void Encode(Stream stream)
{
using (SerializationWriter sw = new SerializationWriter(stream))
{
sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0));
sw.Write(LATEST_VERSION);
sw.Write(score.ScoreInfo.Beatmap.MD5Hash);
sw.Write(score.ScoreInfo.UserString);
sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash());
sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCount100() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCount50() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCountGeki() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCountKatu() ?? 0));
sw.Write((ushort)(score.ScoreInfo.GetCountMiss() ?? 0));
sw.Write((int)(score.ScoreInfo.TotalScore));
sw.Write((ushort)score.ScoreInfo.MaxCombo);
sw.Write(score.ScoreInfo.Combo == score.ScoreInfo.MaxCombo);
sw.Write((int)score.ScoreInfo.Ruleset.CreateInstance().ConvertToLegacyMods(score.ScoreInfo.Mods));
sw.Write(getHpGraphFormatted());
sw.Write(score.ScoreInfo.Date.DateTime);
sw.WriteByteArray(createReplayData());
sw.Write((long)0);
writeModSpecificData(score.ScoreInfo, sw);
}
}
private void writeModSpecificData(ScoreInfo score, SerializationWriter sw)
{
}
private byte[] createReplayData()
{
var content = new ASCIIEncoding().GetBytes(replayStringContent);
using (var outStream = new MemoryStream())
{
using (var lzma = new LzmaStream(new LzmaEncoderProperties(false, 1 << 21, 255), false, outStream))
{
outStream.Write(lzma.Properties);
long fileSize = content.Length;
for (int i = 0; i < 8; i++)
outStream.WriteByte((byte)(fileSize >> (8 * i)));
lzma.Write(content);
}
return outStream.ToArray();
}
}
private string replayStringContent
{
get
{
StringBuilder replayData = new StringBuilder();
if (score.Replay != null)
{
LegacyReplayFrame lastF = new LegacyReplayFrame(0, 0, 0, ReplayButtonState.None);
foreach (var f in score.Replay.Frames.OfType<IConvertibleReplayFrame>().Select(f => f.ToLegacy(beatmap)))
{
replayData.Append(FormattableString.Invariant($"{f.Time - lastF.Time}|{f.MouseX ?? 0}|{f.MouseY ?? 0}|{(int)f.ButtonState},"));
lastF = f;
}
}
replayData.AppendFormat(@"{0}|{1}|{2}|{3},", -12345, 0, 0, 0);
return replayData.ToString();
}
}
private string getHpGraphFormatted()
{
// todo: implement, maybe?
return string.Empty;
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Scoring
var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath;
using (var stream = store.GetStream(replayFilename)) using (var stream = store.GetStream(replayFilename))
Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay;
} }
} }
} }

View File

@ -46,9 +46,9 @@ namespace osu.Game.Scoring
{ {
try try
{ {
return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; return new DatabasedLegacyScoreDecoder(rulesets, beatmaps()).Parse(stream).ScoreInfo;
} }
catch (LegacyScoreParser.BeatmapNotFoundException e) catch (LegacyScoreDecoder.BeatmapNotFoundException e)
{ {
Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error);
return null; return null;

View File

@ -166,7 +166,7 @@ namespace osu.Game.Screens.Backgrounds
BlurAmount.ValueChanged += _ => UpdateVisuals(); BlurAmount.ValueChanged += _ => UpdateVisuals();
} }
protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value || !ShowVideo.Value; // The background needs to be hidden in the case of it being replaced by the storyboard protected override bool ShowDimContent => !ShowStoryboard.Value || !StoryboardReplacesBackground.Value; // The background needs to be hidden in the case of it being replaced by the storyboard
protected override void UpdateVisuals() protected override void UpdateVisuals()
{ {

View File

@ -140,7 +140,7 @@ namespace osu.Game.Screens.Menu
preloadSongSelect(); preloadSongSelect();
} }
[Resolved] [Resolved(canBeNull: true)]
private OsuGame game { get; set; } private OsuGame game { get; set; }
private void confirmAndExit() private void confirmAndExit()
@ -148,7 +148,7 @@ namespace osu.Game.Screens.Menu
if (exitConfirmed) return; if (exitConfirmed) return;
exitConfirmed = true; exitConfirmed = true;
game.PerformFromScreen(menu => menu.Exit()); game?.PerformFromScreen(menu => menu.Exit());
} }
private void preloadSongSelect() private void preloadSongSelect()

View File

@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -16,8 +14,6 @@ namespace osu.Game.Screens.Play
{ {
public class BreakOverlay : Container public class BreakOverlay : Container
{ {
private readonly ScoreProcessor scoreProcessor;
/// <summary> /// <summary>
/// The duration of the break overlay fading. /// The duration of the break overlay fading.
/// </summary> /// </summary>
@ -37,10 +33,6 @@ namespace osu.Game.Screens.Play
{ {
breaks = value; breaks = value;
// reset index in case the new breaks list is smaller than last one
isBreakTime.Value = false;
CurrentBreakIndex = 0;
if (IsLoaded) if (IsLoaded)
initializeBreaks(); initializeBreaks();
} }
@ -48,27 +40,17 @@ namespace osu.Game.Screens.Play
public override bool RemoveCompletedTransforms => false; public override bool RemoveCompletedTransforms => false;
/// <summary>
/// Whether the gameplay is currently in a break.
/// </summary>
public IBindable<bool> IsBreakTime => isBreakTime;
protected int CurrentBreakIndex;
private readonly BindableBool isBreakTime = new BindableBool();
private readonly Container remainingTimeAdjustmentBox; private readonly Container remainingTimeAdjustmentBox;
private readonly Container remainingTimeBox; private readonly Container remainingTimeBox;
private readonly RemainingTimeCounter remainingTimeCounter; private readonly RemainingTimeCounter remainingTimeCounter;
private readonly BreakInfo info;
private readonly BreakArrows breakArrows; private readonly BreakArrows breakArrows;
private readonly double gameplayStartTime;
public BreakOverlay(bool letterboxing, double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null) public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
{ {
this.gameplayStartTime = gameplayStartTime;
this.scoreProcessor = scoreProcessor;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
BreakInfo info;
Child = fadeContainer = new Container Child = fadeContainer = new Container
{ {
Alpha = 0, Alpha = 0,
@ -119,13 +101,11 @@ namespace osu.Game.Screens.Play
} }
}; };
if (scoreProcessor != null) bindProcessor(scoreProcessor); if (scoreProcessor != null)
} {
info.AccuracyDisplay.Current.BindTo(scoreProcessor.Accuracy);
[BackgroundDependencyLoader(true)] info.GradeDisplay.Current.BindTo(scoreProcessor.Rank);
private void load(GameplayClock clock) }
{
if (clock != null) Clock = clock;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -134,42 +114,6 @@ namespace osu.Game.Screens.Play
initializeBreaks(); initializeBreaks();
} }
protected override void Update()
{
base.Update();
updateBreakTimeBindable();
}
private void updateBreakTimeBindable() =>
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|| Clock.CurrentTime < gameplayStartTime
|| scoreProcessor?.HasCompleted == true;
private BreakPeriod getCurrentBreak()
{
if (breaks?.Count > 0)
{
var time = Clock.CurrentTime;
if (time > breaks[CurrentBreakIndex].EndTime)
{
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
CurrentBreakIndex++;
}
else
{
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
CurrentBreakIndex--;
}
var closest = breaks[CurrentBreakIndex];
return closest.Contains(time) ? closest : null;
}
return null;
}
private void initializeBreaks() private void initializeBreaks()
{ {
FinishTransforms(true); FinishTransforms(true);
@ -207,11 +151,5 @@ namespace osu.Game.Screens.Play
} }
} }
} }
private void bindProcessor(ScoreProcessor processor)
{
info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
info.GradeDisplay.Current.BindTo(processor.Rank);
}
} }
} }

View File

@ -0,0 +1,82 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Play
{
public class BreakTracker : Component
{
private readonly ScoreProcessor scoreProcessor;
private readonly double gameplayStartTime;
/// <summary>
/// Whether the gameplay is currently in a break.
/// </summary>
public IBindable<bool> IsBreakTime => isBreakTime;
protected int CurrentBreakIndex;
private readonly BindableBool isBreakTime = new BindableBool();
private IReadOnlyList<BreakPeriod> breaks;
public IReadOnlyList<BreakPeriod> Breaks
{
get => breaks;
set
{
breaks = value;
// reset index in case the new breaks list is smaller than last one
isBreakTime.Value = false;
CurrentBreakIndex = 0;
}
}
public BreakTracker(double gameplayStartTime = 0, ScoreProcessor scoreProcessor = null)
{
this.gameplayStartTime = gameplayStartTime;
this.scoreProcessor = scoreProcessor;
}
protected override void Update()
{
base.Update();
isBreakTime.Value = getCurrentBreak()?.HasEffect == true
|| Clock.CurrentTime < gameplayStartTime
|| scoreProcessor?.HasCompleted == true;
}
private BreakPeriod getCurrentBreak()
{
if (breaks?.Count > 0)
{
var time = Clock.CurrentTime;
if (time > breaks[CurrentBreakIndex].EndTime)
{
while (time > breaks[CurrentBreakIndex].EndTime && CurrentBreakIndex < breaks.Count - 1)
CurrentBreakIndex++;
}
else
{
while (time < breaks[CurrentBreakIndex].StartTime && CurrentBreakIndex > 0)
CurrentBreakIndex--;
}
var closest = breaks[CurrentBreakIndex];
return closest.Contains(time) ? closest : null;
}
return null;
}
}
}

View File

@ -44,7 +44,6 @@ namespace osu.Game.Screens.Play
return; return;
drawableStoryboard = storyboard.CreateDrawable(); drawableStoryboard = storyboard.CreateDrawable();
drawableStoryboard.Masking = true;
if (async) if (async)
LoadComponentAsync(drawableStoryboard, Add); LoadComponentAsync(drawableStoryboard, Add);

View File

@ -1,88 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Video;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
public class DimmableVideo : UserDimContainer
{
private readonly VideoSprite video;
private DrawableVideo drawableVideo;
public DimmableVideo(VideoSprite video)
{
this.video = video;
}
[BackgroundDependencyLoader]
private void load()
{
initializeVideo(false);
}
protected override void LoadComplete()
{
ShowVideo.BindValueChanged(_ => initializeVideo(true), true);
base.LoadComplete();
}
protected override bool ShowDimContent => IgnoreUserSettings.Value || (ShowVideo.Value && DimLevel < 1);
private void initializeVideo(bool async)
{
if (video == null)
return;
if (drawableVideo != null)
return;
if (!ShowVideo.Value && !IgnoreUserSettings.Value)
return;
drawableVideo = new DrawableVideo(video);
if (async)
LoadComponentAsync(drawableVideo, Add);
else
Add(drawableVideo);
}
private class DrawableVideo : Container
{
public DrawableVideo(VideoSprite video)
{
RelativeSizeAxes = Axes.Both;
Masking = true;
video.RelativeSizeAxes = Axes.Both;
video.FillMode = FillMode.Fit;
video.Anchor = Anchor.Centre;
video.Origin = Anchor.Centre;
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
video,
});
}
[BackgroundDependencyLoader]
private void load(GameplayClock clock)
{
if (clock != null)
Clock = clock;
}
}
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
@ -17,13 +18,16 @@ using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.IO.Archives;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Replays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Users; using osu.Game.Users;
@ -72,6 +76,8 @@ namespace osu.Game.Screens.Play
public BreakOverlay BreakOverlay; public BreakOverlay BreakOverlay;
private BreakTracker breakTracker;
protected ScoreProcessor ScoreProcessor { get; private set; } protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; } protected HealthProcessor HealthProcessor { get; private set; }
@ -85,7 +91,6 @@ namespace osu.Game.Screens.Play
protected GameplayClockContainer GameplayClockContainer { get; private set; } protected GameplayClockContainer GameplayClockContainer { get; private set; }
public DimmableStoryboard DimmableStoryboard { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; }
public DimmableVideo DimmableVideo { get; private set; }
[Cached] [Cached]
[Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))] [Cached(Type = typeof(IBindable<IReadOnlyList<Mod>>))]
@ -118,6 +123,23 @@ namespace osu.Game.Screens.Play
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); => dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
protected override void LoadComplete()
{
base.LoadComplete();
PrepareReplay();
}
private Replay recordingReplay;
/// <summary>
/// Run any recording / playback setup for replays.
/// </summary>
protected virtual void PrepareReplay()
{
DrawableRuleset.SetRecordTarget(recordingReplay = new Replay());
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config) private void load(AudioManager audio, OsuConfigManager config)
{ {
@ -184,12 +206,11 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>()) foreach (var mod in Mods.Value.OfType<IApplicableToHealthProcessor>())
mod.ApplyToHealthProcessor(HealthProcessor); mod.ApplyToHealthProcessor(HealthProcessor);
BreakOverlay.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true);
} }
private void addUnderlayComponents(Container target) private void addUnderlayComponents(Container target)
{ {
target.Add(DimmableVideo = new DimmableVideo(Beatmap.Value.Video) { RelativeSizeAxes = Axes.Both });
target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both });
} }
@ -212,12 +233,30 @@ namespace osu.Game.Screens.Play
DrawableRuleset, DrawableRuleset,
new ComboEffects(ScoreProcessor) new ComboEffects(ScoreProcessor)
}); });
DrawableRuleset.FrameStableComponents.AddRange(new Drawable[]
{
ScoreProcessor,
HealthProcessor,
breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Breaks = working.Beatmap.Breaks
}
});
HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
} }
private void addOverlayComponents(Container target, WorkingBeatmap working) private void addOverlayComponents(Container target, WorkingBeatmap working)
{ {
target.AddRange(new[] target.AddRange(new[]
{ {
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Clock = DrawableRuleset.FrameStableClock,
ProcessCustomClock = false,
Breaks = working.Beatmap.Breaks
},
// display the cursor above some HUD elements. // display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
@ -274,20 +313,8 @@ namespace osu.Game.Screens.Play
performImmediateExit(); performImmediateExit();
}, },
}, },
failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, },
}); });
DrawableRuleset.Overlays.Add(BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, DrawableRuleset.GameplayStartTime, ScoreProcessor)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Breaks = working.Beatmap.Breaks
});
DrawableRuleset.Overlays.Add(ScoreProcessor);
DrawableRuleset.Overlays.Add(HealthProcessor);
HealthProcessor.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
} }
private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime) private void onBreakTimeChanged(ValueChangedEvent<bool> isBreakTime)
@ -299,7 +326,7 @@ namespace osu.Game.Screens.Play
private void updatePauseOnFocusLostState() => private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value && !DrawableRuleset.HasReplayLoaded.Value
&& !BreakOverlay.IsBreakTime.Value; && !breakTracker.IsBreakTime.Value;
private IBeatmap loadPlayableBeatmap() private IBeatmap loadPlayableBeatmap()
{ {
@ -521,7 +548,7 @@ namespace osu.Game.Screens.Play
PauseOverlay.Hide(); PauseOverlay.Hide();
// breaks and time-based conditions may allow instant resume. // breaks and time-based conditions may allow instant resume.
if (BreakOverlay.IsBreakTime.Value) if (breakTracker.IsBreakTime.Value)
completeResume(); completeResume();
else else
DrawableRuleset.RequestResume(completeResume); DrawableRuleset.RequestResume(completeResume);
@ -555,9 +582,8 @@ namespace osu.Game.Screens.Play
Background.BlurAmount.Value = 0; Background.BlurAmount.Value = 0;
// bind component bindables. // bind component bindables.
Background.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
DimmableStoryboard.IsBreakTime.BindTo(BreakOverlay.IsBreakTime); DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
DimmableVideo.IsBreakTime.BindTo(BreakOverlay.IsBreakTime);
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); DimmableStoryboard.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
@ -625,19 +651,33 @@ namespace osu.Game.Screens.Play
completionProgressDelegate?.Cancel(); completionProgressDelegate?.Cancel();
completionProgressDelegate = Schedule(delegate completionProgressDelegate = Schedule(delegate
{ {
var score = CreateScore(); if (DrawableRuleset.ReplayScore != null)
this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo));
if (DrawableRuleset.ReplayScore == null)
{
scoreManager.Import(score).ContinueWith(_ => Schedule(() =>
{
// screen may be in the exiting transition phase.
if (this.IsCurrentScreen())
this.Push(CreateResults(score));
}));
}
else else
this.Push(CreateResults(score)); {
var score = new Score { ScoreInfo = CreateScore() };
LegacyByteArrayReader replayReader = null;
if (recordingReplay?.Frames.Count > 0)
{
score.Replay = recordingReplay;
using (var stream = new MemoryStream())
{
new LegacyScoreEncoder(score, gameplayBeatmap.PlayableBeatmap).Encode(stream);
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
}
}
scoreManager.Import(score.ScoreInfo, replayReader)
.ContinueWith(imported => Schedule(() =>
{
// screen may be in the exiting transition phase.
if (this.IsCurrentScreen())
this.Push(CreateResults(imported.Result));
}));
}
}); });
} }

View File

@ -15,7 +15,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
private readonly PlayerSliderBar<double> dimSliderBar; private readonly PlayerSliderBar<double> dimSliderBar;
private readonly PlayerSliderBar<double> blurSliderBar; private readonly PlayerSliderBar<double> blurSliderBar;
private readonly PlayerCheckbox showStoryboardToggle; private readonly PlayerCheckbox showStoryboardToggle;
private readonly PlayerCheckbox showVideoToggle;
private readonly PlayerCheckbox beatmapSkinsToggle; private readonly PlayerCheckbox beatmapSkinsToggle;
private readonly PlayerCheckbox beatmapHitsoundsToggle; private readonly PlayerCheckbox beatmapHitsoundsToggle;
@ -43,8 +42,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
{ {
Text = "Toggles:" Text = "Toggles:"
}, },
showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboard / Video" },
showVideoToggle = new PlayerCheckbox { LabelText = "Video" },
beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" },
beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" }
}; };
@ -56,7 +54,6 @@ namespace osu.Game.Screens.Play.PlayerSettings
dimSliderBar.Bindable = config.GetBindable<double>(OsuSetting.DimLevel); dimSliderBar.Bindable = config.GetBindable<double>(OsuSetting.DimLevel);
blurSliderBar.Bindable = config.GetBindable<double>(OsuSetting.BlurLevel); blurSliderBar.Bindable = config.GetBindable<double>(OsuSetting.BlurLevel);
showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard); showStoryboardToggle.Current = config.GetBindable<bool>(OsuSetting.ShowStoryboard);
showVideoToggle.Current = config.GetBindable<bool>(OsuSetting.ShowVideoBackground);
beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins); beatmapSkinsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapSkins);
beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds); beatmapHitsoundsToggle.Current = config.GetBindable<bool>(OsuSetting.BeatmapHitsounds);
} }

View File

@ -18,9 +18,8 @@ namespace osu.Game.Screens.Play
this.score = score; this.score = score;
} }
protected override void LoadComplete() protected override void PrepareReplay()
{ {
base.LoadComplete();
DrawableRuleset?.SetReplayScore(score); DrawableRuleset?.SetReplayScore(score);
} }

View File

@ -16,6 +16,7 @@ using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Framework.Input.Events;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
@ -136,6 +137,8 @@ namespace osu.Game.Screens.Select
public void Deactivate() public void Deactivate()
{ {
searchTextBox.ReadOnly = true;
searchTextBox.HoldFocus = false; searchTextBox.HoldFocus = false;
if (searchTextBox.HasFocus) if (searchTextBox.HasFocus)
GetContainingInputManager().ChangeFocus(searchTextBox); GetContainingInputManager().ChangeFocus(searchTextBox);
@ -143,6 +146,7 @@ namespace osu.Game.Screens.Select
public void Activate() public void Activate()
{ {
searchTextBox.ReadOnly = false;
searchTextBox.HoldFocus = true; searchTextBox.HoldFocus = true;
} }
@ -184,5 +188,9 @@ namespace osu.Game.Screens.Select
} }
private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria());
protected override bool OnClick(ClickEvent e) => true;
protected override bool OnHover(HoverEvent e) => true;
} }
} }

View File

@ -107,5 +107,7 @@ namespace osu.Game.Screens.Select
protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnMouseDown(MouseDownEvent e) => true;
protected override bool OnClick(ClickEvent e) => true; protected override bool OnClick(ClickEvent e) => true;
protected override bool OnHover(HoverEvent e) => true;
} }
} }

View File

@ -56,6 +56,7 @@ namespace osu.Game.Screens.Select
switch (e.Key) switch (e.Key)
{ {
case Key.Enter: case Key.Enter:
case Key.KeypadEnter:
// this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is // this is a special hard-coded case; we can't rely on OnPressed (of SongSelect) as GlobalActionContainer is
// matching with exact modifier consideration (so Ctrl+Enter would be ignored). // matching with exact modifier consideration (so Ctrl+Enter would be ignored).
FinaliseSelection(); FinaliseSelection();
@ -84,8 +85,6 @@ namespace osu.Game.Screens.Select
} }
} }
Beatmap.Value.Track.Looping = false;
SampleConfirm?.Play(); SampleConfirm?.Play();
this.Push(player = new PlayerLoader(() => new Player())); this.Push(player = new PlayerLoader(() => new Player()));

View File

@ -572,6 +572,9 @@ namespace osu.Game.Screens.Select
BeatmapOptions.Hide(); BeatmapOptions.Hide();
if (Beatmap.Value.Track != null)
Beatmap.Value.Track.Looping = false;
this.ScaleTo(1.1f, 250, Easing.InSine); this.ScaleTo(1.1f, 250, Easing.InSine);
this.FadeOut(250); this.FadeOut(250);

View File

@ -75,7 +75,7 @@ namespace osu.Game.Storyboards.Drawables
private void updateLayerVisibility() private void updateLayerVisibility()
{ {
foreach (var layer in Children) foreach (var layer in Children)
layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; layer.Enabled = passing ? layer.Layer.VisibleWhenPassing : layer.Layer.VisibleWhenFailing;
} }
} }
} }

View File

@ -21,7 +21,8 @@ namespace osu.Game.Storyboards.Drawables
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
Enabled = layer.EnabledWhenPassing; Enabled = layer.VisibleWhenPassing;
Masking = layer.Masking;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
namespace osu.Game.Storyboards.Drawables
{
public class DrawableStoryboardVideo : CompositeDrawable
{
public readonly StoryboardVideo Video;
private VideoSprite videoSprite;
public override bool RemoveWhenNotAlive => false;
public DrawableStoryboardVideo(StoryboardVideo video)
{
Video = video;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader(true)]
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
{
var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Video.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
if (path == null)
return;
var stream = textureStore.GetStream(path);
if (stream == null)
return;
InternalChild = videoSprite = new VideoSprite(stream, false)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Clock = new FramedOffsetClock(Clock) { Offset = -Video.StartTime }
};
}
protected override void LoadComplete()
{
base.LoadComplete();
if (videoSprite == null) return;
using (videoSprite.BeginAbsoluteSequence(0))
videoSprite.FadeIn(500);
}
}
}

View File

@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Beatmaps;
using osu.Game.Storyboards.Drawables;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Storyboards.Drawables;
namespace osu.Game.Storyboards namespace osu.Game.Storyboards
{ {
@ -21,9 +21,10 @@ namespace osu.Game.Storyboards
public Storyboard() public Storyboard()
{ {
layers.Add("Video", new StoryboardLayer("Video", 4, false));
layers.Add("Background", new StoryboardLayer("Background", 3)); layers.Add("Background", new StoryboardLayer("Background", 3));
layers.Add("Fail", new StoryboardLayer("Fail", 2) { EnabledWhenPassing = false, }); layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
layers.Add("Pass", new StoryboardLayer("Pass", 1) { EnabledWhenFailing = false, }); layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
layers.Add("Foreground", new StoryboardLayer("Foreground", 0)); layers.Add("Foreground", new StoryboardLayer("Foreground", 0));
} }
@ -46,6 +47,9 @@ namespace osu.Game.Storyboards
if (backgroundPath == null) if (backgroundPath == null)
return false; return false;
if (GetLayer("Video").Elements.Any())
return true;
return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath);
} }
} }

View File

@ -8,17 +8,23 @@ namespace osu.Game.Storyboards
{ {
public class StoryboardLayer public class StoryboardLayer
{ {
public string Name; public readonly string Name;
public int Depth;
public bool EnabledWhenPassing = true; public readonly int Depth;
public bool EnabledWhenFailing = true;
public readonly bool Masking;
public bool VisibleWhenPassing = true;
public bool VisibleWhenFailing = true;
public List<IStoryboardElement> Elements = new List<IStoryboardElement>(); public List<IStoryboardElement> Elements = new List<IStoryboardElement>();
public StoryboardLayer(string name, int depth) public StoryboardLayer(string name, int depth, bool masking = true)
{ {
Name = name; Name = name;
Depth = depth; Depth = depth;
Masking = masking;
} }
public void Add(IStoryboardElement element) public void Add(IStoryboardElement element)

View File

@ -0,0 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Storyboards.Drawables;
namespace osu.Game.Storyboards
{
public class StoryboardVideo : IStoryboardElement
{
public string Path { get; }
public bool IsDrawable => true;
public double StartTime { get; }
public StoryboardVideo(string path, int offset)
{
Path = path;
StartTime = offset;
}
public Drawable CreateDrawable() => new DrawableStoryboardVideo(this);
}
}

View File

@ -10,7 +10,6 @@ using Newtonsoft.Json;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.IO; using osu.Game.IO;
@ -207,8 +206,6 @@ namespace osu.Game.Tests.Beatmaps
protected override Texture GetBackground() => throw new NotImplementedException(); protected override Texture GetBackground() => throw new NotImplementedException();
protected override VideoSprite GetVideo() => throw new NotImplementedException();
protected override Track GetTrack() => throw new NotImplementedException(); protected override Track GetTrack() => throw new NotImplementedException();
protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Beatmaps
protected void Test(LegacyMods legacyMods, Type[] expectedMods) protected void Test(LegacyMods legacyMods, Type[] expectedMods)
{ {
var ruleset = CreateRuleset(); var ruleset = CreateRuleset();
var mods = ruleset.ConvertLegacyMods(legacyMods).ToList(); var mods = ruleset.ConvertFromLegacyMods(legacyMods).ToList();
Assert.AreEqual(expectedMods.Length, mods.Count); Assert.AreEqual(expectedMods.Length, mods.Count);
foreach (var modType in expectedMods) foreach (var modType in expectedMods)

View File

@ -3,7 +3,6 @@
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Storyboards; using osu.Game.Storyboards;
@ -32,8 +31,6 @@ namespace osu.Game.Tests.Beatmaps
protected override Texture GetBackground() => null; protected override Texture GetBackground() => null;
protected override VideoSprite GetVideo() => null;
protected override Track GetTrack() => null; protected override Track GetTrack() => null;
} }
} }

View File

@ -36,9 +36,10 @@ namespace osu.Game.Users
protected DelayedLoadUnloadWrapper Background { get; private set; } protected DelayedLoadUnloadWrapper Background { get; private set; }
protected TextFlowContainer LastVisitMessage { get; private set; }
private SpriteIcon statusIcon; private SpriteIcon statusIcon;
private OsuSpriteText statusMessage; private OsuSpriteText statusMessage;
private TextFlowContainer lastVisitMessage;
protected UserPanel(User user) protected UserPanel(User user)
{ {
@ -153,7 +154,7 @@ namespace osu.Game.Users
var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft; var alignment = rightAlignedChildren ? Anchor.CentreRight : Anchor.CentreLeft;
statusContainer.Add(lastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text => statusContainer.Add(LastVisitMessage = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)).With(text =>
{ {
text.Anchor = alignment; text.Anchor = alignment;
text.Origin = alignment; text.Origin = alignment;
@ -184,6 +185,8 @@ namespace osu.Game.Users
{ {
if (status != null) if (status != null)
{ {
LastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0);
// Set status message based on activity (if we have one) and status is not offline // Set status message based on activity (if we have one) and status is not offline
if (activity != null && !(status is UserStatusOffline)) if (activity != null && !(status is UserStatusOffline))
{ {
@ -193,7 +196,6 @@ namespace osu.Game.Users
} }
// Otherwise use only status // Otherwise use only status
lastVisitMessage.FadeTo(status is UserStatusOffline && User.LastVisit.HasValue ? 1 : 0);
statusMessage.Text = status.Message; statusMessage.Text = status.Message;
statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint); statusIcon.FadeColour(status.GetAppropriateColour(colours), 500, Easing.OutQuint);

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.319.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.327.0" />
<PackageReference Include="Sentry" Version="2.1.1" /> <PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2020.315.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.319.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2020.327.0" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
@ -79,7 +79,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.319.0" /> <PackageReference Include="ppy.osu.Framework" Version="2020.327.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />