1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 02:02:53 +08:00

Merge branch 'master' into multiplayer-beatmap-checksum

This commit is contained in:
smoogipoo 2020-06-05 19:16:05 +09:00
commit caf94df0d0
52 changed files with 1003 additions and 146 deletions

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.528.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.602.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.601.0" />
</ItemGroup>
</Project>

View File

@ -33,13 +33,11 @@ namespace osu.Desktop
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
{
var importer = new ArchiveImportIPCChannel(host);
// Restore the cwd so relative paths given at the command line work correctly
Directory.SetCurrentDirectory(cwd);
foreach (var file in args)
{
Console.WriteLine(@"Importing {0}", file);
if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000))
throw new TimeoutException(@"IPC took too long to send");
}

View File

@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomLeft.Linear,
Time = part.Time
});
@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2),
TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
Time = part.Time
});
@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopRight.Linear,
Time = part.Time
});
@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2),
TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.TopLeft.Linear,
Time = part.Time
});
@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
[VertexMember(4, VertexAttribPointerType.Float)]
public Vector4 TextureRect;
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;

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 NUnit.Framework;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
public class TestSceneEditor : EditorTestScene
{
public TestSceneEditor()
: base(new TaikoRuleset())
{
}
}
}

View File

@ -0,0 +1,55 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Taiko.Tests
{
public class TestSceneTaikoHitObjectComposer : EditorClockTestScene
{
[SetUp]
public void Setup() => Schedule(() =>
{
BeatDivisor.Value = 8;
Clock.Seek(0);
Child = new TestComposer { RelativeSizeAxes = Axes.Both };
});
[Test]
public void BasicTest()
{
}
private class TestComposer : CompositeDrawable
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
public readonly EditorBeatmap EditorBeatmap;
public TestComposer()
{
InternalChildren = new Drawable[]
{
EditorBeatmap = new EditorBeatmap(new TaikoBeatmap())
{
BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo }
},
new TaikoHitObjectComposer(new TaikoRuleset())
};
for (int i = 0; i < 10; i++)
EditorBeatmap.Add(new Hit { StartTime = 125 * i });
}
}
}
}

View File

@ -0,0 +1,15 @@
// 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.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint
{
public DrumRollPlacementBlueprint()
: base(new DrumRoll())
{
}
}
}

View File

@ -0,0 +1,35 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class HitPiece : CompositeDrawable
{
public HitPiece()
{
Origin = Anchor.Centre;
InternalChild = new CircularContainer
{
Masking = true,
BorderThickness = 10,
BorderColour = Color4.Yellow,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
AlwaysPresent = true,
Alpha = 0,
RelativeSizeAxes = Axes.Both
}
}
};
}
}
}

View File

@ -0,0 +1,52 @@
// 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.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class HitPlacementBlueprint : PlacementBlueprint
{
private readonly HitPiece piece;
private static Hit hit;
public HitPlacementBlueprint()
: base(hit = new Hit())
{
InternalChild = piece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
};
}
protected override bool OnMouseDown(MouseDownEvent e)
{
switch (e.Button)
{
case MouseButton.Left:
hit.Type = HitType.Centre;
EndPlacement(true);
return true;
case MouseButton.Right:
hit.Type = HitType.Rim;
EndPlacement(true);
return true;
}
return false;
}
public override void UpdatePosition(SnapResult result)
{
piece.Position = ToLocalSpace(result.ScreenSpacePosition);
base.UpdatePosition(result);
}
}
}

View File

@ -0,0 +1,40 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class LengthPiece : CompositeDrawable
{
public LengthPiece()
{
Origin = Anchor.CentreLeft;
InternalChild = new Container
{
Masking = true,
Colour = Color4.Yellow,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.X,
Height = 8,
},
new Box
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 8,
}
}
};
}
}
}

View File

@ -0,0 +1,15 @@
// 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.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint
{
public SwellPlacementBlueprint()
: base(new Swell())
{
}
}
}

View File

@ -0,0 +1,40 @@
// 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.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class TaikoSelectionBlueprint : OverlaySelectionBlueprint
{
public TaikoSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.None;
AddInternal(new HitPiece
{
RelativeSizeAxes = Axes.Both,
Origin = Anchor.TopLeft
});
}
protected override void Update()
{
base.Update();
// Move the rectangle to cover the hitobjects
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
var bottomRight = new Vector2(float.MinValue, float.MinValue);
topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft));
bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight));
Size = bottomRight - topLeft;
Position = topLeft;
}
}
}

View File

@ -0,0 +1,110 @@
// 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.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.UI;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
{
public class TaikoSpanPlacementBlueprint : PlacementBlueprint
{
private readonly HitPiece headPiece;
private readonly HitPiece tailPiece;
private readonly LengthPiece lengthPiece;
private readonly IHasDuration spanPlacementObject;
public TaikoSpanPlacementBlueprint(HitObject hitObject)
: base(hitObject)
{
spanPlacementObject = hitObject as IHasDuration;
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
headPiece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
},
lengthPiece = new LengthPiece
{
Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT
},
tailPiece = new HitPiece
{
Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT)
}
};
}
private double originalStartTime;
private Vector2 originalPosition;
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button != MouseButton.Left)
return false;
BeginPlacement(true);
return true;
}
protected override void OnMouseUp(MouseUpEvent e)
{
if (e.Button != MouseButton.Left)
return;
base.OnMouseUp(e);
EndPlacement(true);
}
public override void UpdatePosition(SnapResult result)
{
base.UpdatePosition(result);
if (PlacementActive)
{
if (result.Time is double dragTime)
{
if (dragTime < originalStartTime)
{
HitObject.StartTime = dragTime;
spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
headPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
tailPiece.Position = originalPosition;
}
else
{
HitObject.StartTime = originalStartTime;
spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime);
tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
headPiece.Position = originalPosition;
}
lengthPiece.X = headPiece.X;
lengthPiece.Width = tailPiece.X - headPiece.X;
}
}
else
{
lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition);
if (result.Time is double startTime)
{
originalStartTime = HitObject.StartTime = startTime;
originalPosition = ToLocalSpace(result.ScreenSpacePosition);
}
}
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class DrumRollCompositionTool : HitObjectCompositionTool
{
public DrumRollCompositionTool()
: base(nameof(DrumRoll))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint();
}
}

View File

@ -0,0 +1,20 @@
// 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.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class HitCompositionTool : HitObjectCompositionTool
{
public HitCompositionTool()
: base(nameof(Hit))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint();
}
}

View File

@ -0,0 +1,20 @@
// 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.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
using osu.Game.Rulesets.Taiko.Objects;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class SwellCompositionTool : HitObjectCompositionTool
{
public SwellCompositionTool()
: base(nameof(Swell))
{
}
public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint();
}
}

View File

@ -0,0 +1,24 @@
// 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.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Edit.Blueprints;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class TaikoBlueprintContainer : ComposeBlueprintContainer
{
public TaikoBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
: base(hitObjects)
{
}
protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler();
public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) =>
new TaikoSelectionBlueprint(hitObject);
}
}

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 osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class TaikoHitObjectComposer : HitObjectComposer<TaikoHitObject>
{
public TaikoHitObjectComposer(TaikoRuleset ruleset)
: base(ruleset)
{
}
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => new HitObjectCompositionTool[]
{
new HitCompositionTool(),
new DrumRollCompositionTool(),
new SwellCompositionTool()
};
protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable<DrawableHitObject> hitObjects)
=> new TaikoBlueprintContainer(hitObjects);
}
}

View File

@ -0,0 +1,80 @@
// 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.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Taiko.Edit
{
public class TaikoSelectionHandler : SelectionHandler
{
protected override IEnumerable<MenuItem> GetContextMenuItemsForSelection(IEnumerable<SelectionBlueprint> selection)
{
if (selection.All(s => s.HitObject is Hit))
{
var hits = selection.Select(s => s.HitObject).OfType<Hit>();
yield return new TernaryStateMenuItem("Rim", action: state =>
{
foreach (var h in hits)
{
switch (state)
{
case TernaryState.True:
h.Type = HitType.Rim;
break;
case TernaryState.False:
h.Type = HitType.Centre;
break;
}
}
})
{
State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) }
};
}
if (selection.All(s => s.HitObject is TaikoHitObject))
{
var hits = selection.Select(s => s.HitObject).OfType<TaikoHitObject>();
yield return new TernaryStateMenuItem("Strong", action: state =>
{
foreach (var h in hits)
{
switch (state)
{
case TernaryState.True:
h.IsStrong = true;
break;
case TernaryState.False:
h.IsStrong = false;
break;
}
EditorBeatmap?.UpdateHitObject(h);
}
})
{
State = { Value = getTernaryState(hits, h => h.IsStrong) }
};
}
}
private TernaryState getTernaryState<T>(IEnumerable<T> selection, Func<T, bool> func)
{
if (selection.Any(func))
return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate;
return TernaryState.False;
}
}
}

View File

@ -48,12 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
colourIdle = colours.YellowDark;
colourEngaged = colours.YellowDarker;
updateColour();
Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both });
if (MainPiece.Drawable is IHasAccentColour accentMain)
accentMain.AccentColour = colourIdle;
Content.Add(tickContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue
});
}
protected override void LoadComplete()
@ -63,6 +62,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
OnNewResult += onNewResult;
}
protected override void RecreatePieces()
{
base.RecreatePieces();
updateColour();
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);

View File

@ -5,6 +5,8 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Drawables;
@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// <summary>
/// A list of keys which can result in hits for this HitObject.
/// </summary>
public TaikoAction[] HitActions { get; }
public TaikoAction[] HitActions { get; private set; }
/// <summary>
/// The action that caused this <see cref="DrawableHit"/> to be hit.
@ -34,15 +36,35 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private bool pressHandledThisFrame;
private Bindable<HitType> type;
public DrawableHit(Hit hit)
: base(hit)
{
FillMode = FillMode.Fit;
}
[BackgroundDependencyLoader]
private void load()
{
type = HitObject.TypeBindable.GetBoundCopy();
type.BindValueChanged(_ =>
{
updateType();
RecreatePieces();
});
updateType();
}
private void updateType()
{
HitActions =
HitObject.Type == HitType.Centre
? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre }
: new[] { TaikoAction.LeftRim, TaikoAction.RightRim };
RecreatePieces();
}
protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre

View File

@ -8,6 +8,8 @@ using osuTK;
using System.Linq;
using osu.Game.Audio;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects;
@ -115,8 +117,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public new TObject HitObject;
protected readonly Vector2 BaseSize;
protected readonly SkinnableDrawable MainPiece;
protected Vector2 BaseSize;
protected SkinnableDrawable MainPiece;
private Bindable<bool> isStrong;
private readonly Container<DrawableStrongNestedHit> strongHitContainer;
@ -129,13 +133,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Custom;
RelativeSizeAxes = Axes.Both;
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
Content.Add(MainPiece = CreateMainPiece());
AddInternal(strongHitContainer = new Container<DrawableStrongNestedHit>());
}
[BackgroundDependencyLoader]
private void load()
{
isStrong = HitObject.IsStrongBindable.GetBoundCopy();
isStrong.BindValueChanged(_ => RecreatePieces(), true);
}
protected virtual void RecreatePieces()
{
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
MainPiece?.Expire();
Content.Add(MainPiece = CreateMainPiece());
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);

View File

@ -1,13 +1,21 @@
// 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.Bindables;
namespace osu.Game.Rulesets.Taiko.Objects
{
public class Hit : TaikoHitObject
{
public readonly Bindable<HitType> TypeBindable = new Bindable<HitType>();
/// <summary>
/// The <see cref="HitType"/> that actuates this <see cref="Hit"/>.
/// </summary>
public HitType Type { get; set; }
public HitType Type
{
get => TypeBindable.Value;
set => TypeBindable.Value = value;
}
}
}

View File

@ -1,7 +1,6 @@
// 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.Threading;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Judgements;
@ -25,11 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public int RequiredHits = 10;
public override bool IsStrong
{
set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject.");
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{
base.CreateNestedHitObjects(cancellationToken);

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Threading;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@ -27,11 +28,17 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
public readonly Bindable<bool> IsStrongBindable = new BindableBool();
/// <summary>
/// Whether this HitObject is a "strong" type.
/// Strong hit objects give more points for hitting the hit object with both keys.
/// </summary>
public virtual bool IsStrong { get; set; }
public bool IsStrong
{
get => IsStrongBindable.Value;
set => IsStrongBindable.Value = value;
}
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
{

View File

@ -21,6 +21,8 @@ using osu.Game.Rulesets.Taiko.Difficulty;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
using System;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Skinning;
@ -144,6 +146,8 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko };
public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score);

View File

@ -2,12 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
@ -18,7 +18,6 @@ namespace osu.Game.Tests.Gameplay
[HeadlessTest]
public class TestSceneDrainingHealthProcessor : OsuTestScene
{
private Bindable<bool> breakTime;
private HealthProcessor processor;
private ManualClock clock;
@ -41,6 +40,64 @@ namespace osu.Game.Tests.Gameplay
assertHealthEqualTo(1);
}
[Test]
public void TestHealthDrainBetweenBreakAndObjects()
{
createProcessor(createBeatmap(0, 2000, new BreakPeriod(325, 375)));
// 275 300 325 350 375 400 425
// hitobjects o o
// break [-------------]
// no drain [---------------------------]
setTime(285);
setHealth(1);
setTime(295);
assertHealthNotEqualTo(1);
setTime(305);
setHealth(1);
setTime(315);
assertHealthEqualTo(1);
setTime(365);
assertHealthEqualTo(1);
setTime(395);
assertHealthEqualTo(1);
setTime(425);
assertHealthNotEqualTo(1);
}
[Test]
public void TestHealthDrainDuringMaximalBreak()
{
createProcessor(createBeatmap(0, 2000, new BreakPeriod(300, 400)));
// 275 300 325 350 375 400 425
// hitobjects o o
// break [---------------------------]
// no drain [---------------------------]
setTime(285);
setHealth(1);
setTime(295);
assertHealthNotEqualTo(1);
setTime(305);
setHealth(1);
setTime(395);
assertHealthEqualTo(1);
setTime(425);
assertHealthNotEqualTo(1);
}
[Test]
public void TestHealthNotDrainedAfterGameplayEnd()
{
@ -54,18 +111,6 @@ namespace osu.Game.Tests.Gameplay
assertHealthEqualTo(1);
}
[Test]
public void TestHealthNotDrainedDuringBreak()
{
createProcessor(createBeatmap(0, 2000));
setBreak(true);
setTime(700);
assertHealthEqualTo(1);
setTime(900);
assertHealthEqualTo(1);
}
[Test]
public void TestHealthDrainedDuringGameplay()
{
@ -112,30 +157,31 @@ namespace osu.Game.Tests.Gameplay
assertHealthNotEqualTo(1);
}
private Beatmap createBeatmap(double startTime, double endTime)
private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks)
{
var beatmap = new Beatmap
{
BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } },
BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } },
};
for (double time = startTime; time <= endTime; time += 100)
{
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time });
}
beatmap.Breaks.AddRange(breaks);
return beatmap;
}
private void createProcessor(Beatmap beatmap) => AddStep("create processor", () =>
{
breakTime = new Bindable<bool>();
Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d =>
{
d.RelativeSizeAxes = Axes.Both;
d.Clock = new FramedClock(clock = new ManualClock());
});
processor.IsBreakTime.BindTo(breakTime);
processor.ApplyBeatmap(beatmap);
});
@ -143,8 +189,6 @@ namespace osu.Game.Tests.Gameplay
private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health);
private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled);
private void assertHealthEqualTo(double value)
=> AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f));

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Platform;
@ -35,8 +36,7 @@ namespace osu.Game.Tests.NonVisual
var osu = loadOsu(host);
var storage = osu.Dependencies.Get<Storage>();
string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory));
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory));
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));
}
finally
@ -46,17 +46,17 @@ namespace osu.Game.Tests.NonVisual
}
}
private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path");
private string customPath => Path.Combine(RuntimeInfo.StartupDirectory, "custom-path");
[Test]
public void TestCustomDirectory()
{
using (var host = new HeadlessGameHost(nameof(TestCustomDirectory)))
{
string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory));
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory));
// need access before the game has constructed its own storage yet.
Storage storage = new DesktopStorage(headlessPrefix, host);
Storage storage = new DesktopStorage(defaultStorageLocation, host);
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
@ -84,10 +84,10 @@ namespace osu.Game.Tests.NonVisual
{
using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup)))
{
string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup));
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestSubDirectoryLookup));
// need access before the game has constructed its own storage yet.
Storage storage = new DesktopStorage(headlessPrefix, host);
Storage storage = new DesktopStorage(defaultStorageLocation, host);
// manual cleaning so we can prepare a config file.
storage.DeleteDirectory(string.Empty);
@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual
// for testing nested files are not ignored (only top level)
host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache");
string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestMigration));
string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration));
Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation));

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
using osu.Game.Skinning;
@ -103,7 +104,7 @@ namespace osu.Game.Tests.NonVisual.Skinning
Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1));
}
public override Texture Get(string name) => Textures.GetValueOrDefault(name);
public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name);
}
}
}

View File

@ -17,14 +17,14 @@ namespace osu.Game.Tests.Resources
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
var temp = Path.GetTempFileName() + ".osz";
var tempPath = Path.GetTempFileName() + ".osz";
using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(temp))
using (var newFile = File.Create(tempPath))
stream.CopyTo(newFile);
Assert.IsTrue(File.Exists(temp));
return temp;
Assert.IsTrue(File.Exists(tempPath));
return tempPath;
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty<Mod>());
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap));
return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap));
}
protected override void AddCheckSteps()

View File

@ -246,7 +246,12 @@ namespace osu.Game.Tests.Visual.Online
{
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, };
InternalChildren = new Drawable[]
{
ChannelManager,
ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, },
};
ChatOverlay.Show();
}
}

View File

@ -36,12 +36,14 @@ namespace osu.Game.Tests.Visual.Ranking
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
}
private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo));
private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo));
[Test]
public void ResultsWithoutPlayer()
{
TestSoloResults screen = null;
TestResultsScreen screen = null;
OsuScreenStack stack;
AddStep("load results", () =>
@ -60,13 +62,23 @@ namespace osu.Game.Tests.Visual.Ranking
[Test]
public void ResultsWithPlayer()
{
TestSoloResults screen = null;
TestResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
}
[Test]
public void ResultsForUnranked()
{
UnrankedSoloResultsScreen screen = null;
AddStep("load results", () => Child = new TestResultsContainer(screen = createUnrankedSoloResultsScreen()));
AddUntilStep("wait for loaded", () => screen.IsLoaded);
AddAssert("retry overlay present", () => screen.RetryOverlay != null);
}
private class TestResultsContainer : Container
{
[Cached(typeof(Player))]
@ -86,11 +98,11 @@ namespace osu.Game.Tests.Visual.Ranking
}
}
private class TestSoloResults : ResultsScreen
private class TestResultsScreen : ResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;
public TestSoloResults(ScoreInfo score)
public TestResultsScreen(ScoreInfo score)
: base(score)
{
}
@ -102,5 +114,24 @@ namespace osu.Game.Tests.Visual.Ranking
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
}
private class UnrankedSoloResultsScreen : SoloResultsScreen
{
public HotkeyRetryOverlay RetryOverlay;
public UnrankedSoloResultsScreen(ScoreInfo score)
: base(score)
{
Score.Beatmap.OnlineBeatmapID = 0;
Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending;
}
protected override void LoadComplete()
{
base.LoadComplete();
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
}
}
}

View File

@ -1,13 +1,19 @@
// 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.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Overlays;
using osu.Game.Overlays.KeyBinding;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
public class TestSceneKeyBindingPanel : OsuTestScene
public class TestSceneKeyBindingPanel : OsuManualInputManagerTestScene
{
private readonly KeyBindingPanel panel;
@ -21,5 +27,42 @@ namespace osu.Game.Tests.Visual.Settings
base.LoadComplete();
panel.Show();
}
[Test]
public void TestClickTwiceOnClearButton()
{
KeyBindingRow firstRow = null;
AddStep("click first row", () =>
{
firstRow = panel.ChildrenOfType<KeyBindingRow>().First();
InputManager.MoveMouseTo(firstRow);
InputManager.Click(MouseButton.Left);
});
AddStep("schedule button clicks", () =>
{
var clearButton = firstRow.ChildrenOfType<KeyBindingRow.ClearButton>().Single();
InputManager.MoveMouseTo(clearButton);
int buttonClicks = 0;
ScheduledDelegate clickDelegate = null;
clickDelegate = Scheduler.AddDelayed(() =>
{
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
if (++buttonClicks == 2)
{
// ReSharper disable once AccessToModifiedClosure
Debug.Assert(clickDelegate != null);
// ReSharper disable once AccessToModifiedClosure
clickDelegate.Cancel();
}
}, 0, true);
});
}
}
}

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tournament.Screens
private FillFlowContainer fillFlow;
private LoginOverlay loginOverlay;
private ActionableInfo resolution;
private ResolutionSelector resolution;
[Resolved]
private MatchIPCInfo ipc { get; set; }
@ -108,18 +108,20 @@ namespace osu.Game.Tournament.Screens
Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset,
},
resolution = new ActionableInfo
resolution = new ResolutionSelector
{
Label = "Stream area resolution",
ButtonText = "Set to 1080p",
Action = () =>
ButtonText = "Set height",
Action = height =>
{
windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080);
windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height);
}
},
};
}
private const float aspect_ratio = 16f / 9f;
protected override void Update()
{
base.Update();
@ -174,6 +176,7 @@ namespace osu.Game.Tournament.Screens
public Action Action;
private TournamentSpriteText valueText;
protected FillFlowContainer FlowContainer;
protected override Drawable CreateComponent() => new Container
{
@ -186,15 +189,67 @@ namespace osu.Game.Tournament.Screens
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
button = new TriangleButton
FlowContainer = new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Size = new Vector2(100, 30),
Action = () => Action?.Invoke()
},
AutoSizeAxes = Axes.Both,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
button = new TriangleButton
{
Size = new Vector2(100, 40),
Action = () => Action?.Invoke()
}
}
}
}
};
}
private class ResolutionSelector : ActionableInfo
{
private const int minimum_window_height = 480;
private const int maximum_window_height = 2160;
public new Action<int> Action;
private OsuNumberBox numberBox;
protected override Drawable CreateComponent()
{
var drawable = base.CreateComponent();
FlowContainer.Insert(-1, numberBox = new OsuNumberBox
{
Text = "1080",
Width = 100
});
base.Action = () =>
{
if (string.IsNullOrEmpty(numberBox.Text))
return;
// box contains text
if (!int.TryParse(numberBox.Text, out var number))
{
// at this point, the only reason we can arrive here is if the input number was too big to parse into an int
// so clamp to max allowed value
number = maximum_window_height;
}
else
{
number = Math.Clamp(number, minimum_window_height, maximum_window_height);
}
// in case number got clamped, reset number in numberBox
numberBox.Text = number.ToString();
Action?.Invoke(number);
};
return drawable;
}
}
}
}

View File

@ -11,23 +11,13 @@ namespace osu.Game.Graphics.UserInterface
/// </summary>
public class TernaryStateMenuItem : StatefulMenuItem<TernaryState>
{
/// <summary>
/// Creates a new <see cref="TernaryStateMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: this(text, type, null)
{
}
/// <summary>
/// Creates a new <see cref="TernaryStateMenuItem"/>.
/// </summary>
/// <param name="text">The text to display.</param>
/// <param name="type">The type of action which this <see cref="TernaryStateMenuItem"/> performs.</param>
/// <param name="action">A delegate to be invoked when this <see cref="TernaryStateMenuItem"/> is pressed.</param>
public TernaryStateMenuItem(string text, MenuItemType type, Action<TernaryState> action)
public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action<TernaryState> action = null)
: this(text, getNextState, type, action)
{
}

View File

@ -93,12 +93,6 @@ namespace osu.Game.Online.Chat
{
if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel))
JoinChannel(e.NewValue);
if (e.NewValue?.MessagesLoaded == false)
{
// let's fetch a small number of messages to bring us up-to-date with the backlog.
fetchInitalMessages(e.NewValue);
}
}
/// <summary>
@ -240,7 +234,6 @@ namespace osu.Game.Online.Chat
}
JoinChannel(channel);
CurrentChannel.Value = channel;
break;
case "help":
@ -275,7 +268,7 @@ namespace osu.Game.Online.Chat
// join any channels classified as "defaults"
if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
JoinChannel(ch);
joinChannel(ch);
}
};
req.Failure += error =>
@ -296,7 +289,7 @@ namespace osu.Game.Online.Chat
/// <param name="channel">The channel </param>
private void fetchInitalMessages(Channel channel)
{
if (channel.Id <= 0) return;
if (channel.Id <= 0 || channel.MessagesLoaded) return;
var fetchInitialMsgReq = new GetMessagesRequest(channel);
fetchInitialMsgReq.Success += messages =>
@ -351,9 +344,10 @@ namespace osu.Game.Online.Chat
/// Joins a channel if it has not already been joined.
/// </summary>
/// <param name="channel">The channel to join.</param>
/// <param name="alreadyJoined">Whether the channel has already been joined server-side. Will skip a join request.</param>
/// <returns>The joined channel. Note that this may not match the parameter channel as it is a backed object.</returns>
public Channel JoinChannel(Channel channel, bool alreadyJoined = false)
public Channel JoinChannel(Channel channel) => joinChannel(channel, true);
private Channel joinChannel(Channel channel, bool fetchInitialMessages = false)
{
if (channel == null) return null;
@ -362,21 +356,36 @@ namespace osu.Game.Online.Chat
// ensure we are joined to the channel
if (!channel.Joined.Value)
{
if (alreadyJoined)
channel.Joined.Value = true;
else
switch (channel.Type)
{
switch (channel.Type)
{
case ChannelType.Public:
var req = new JoinChannelRequest(channel, api.LocalUser.Value);
req.Success += () => JoinChannel(channel, true);
req.Failure += ex => LeaveChannel(channel);
api.Queue(req);
return channel;
}
case ChannelType.Multiplayer:
// join is implicit. happens when you join a multiplayer game.
// this will probably change in the future.
channel.Joined.Value = true;
joinChannel(channel, fetchInitialMessages);
return channel;
case ChannelType.Private:
// can't do this yet.
break;
default:
var req = new JoinChannelRequest(channel, api.LocalUser.Value);
req.Success += () =>
{
channel.Joined.Value = true;
joinChannel(channel, fetchInitialMessages);
};
req.Failure += ex => LeaveChannel(channel);
api.Queue(req);
return channel;
}
}
else
{
if (fetchInitialMessages)
fetchInitalMessages(channel);
}
if (CurrentChannel.Value == null)
CurrentChannel.Value = channel;
@ -420,7 +429,8 @@ namespace osu.Game.Online.Chat
foreach (var channel in updates.Presence)
{
// we received this from the server so should mark the channel already joined.
JoinChannel(channel, true);
channel.Joined.Value = true;
joinChannel(channel);
}
//todo: handle left channels

View File

@ -274,6 +274,9 @@ namespace osu.Game.Overlays.KeyBinding
private void clear()
{
if (bindTarget == null)
return;
bindTarget.UpdateKeyCombination(InputKey.None);
finalise();
}
@ -333,7 +336,7 @@ namespace osu.Game.Overlays.KeyBinding
}
}
private class ClearButton : TriangleButton
public class ClearButton : TriangleButton
{
public ClearButton()
{

View File

@ -87,11 +87,11 @@ namespace osu.Game.Rulesets.Edit
/// <summary>
/// Updates the position of this <see cref="PlacementBlueprint"/> to a new screen-space position.
/// </summary>
/// <param name="snapResult">The snap result information.</param>
public virtual void UpdatePosition(SnapResult snapResult)
/// <param name="result">The snap result information.</param>
public virtual void UpdatePosition(SnapResult result)
{
if (!PlacementActive)
HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current;
HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current;
}
/// <summary>

View File

@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Objects
public static class SliderEventGenerator
{
[Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115
// ReSharper disable once RedundantOverload.Global
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
double? legacyLastTickOffset)
{

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Users;
using JetBrains.Annotations;
namespace osu.Game.Rulesets
{
@ -100,7 +101,8 @@ namespace osu.Game.Rulesets
return value;
}
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().First();
[CanBeNull]
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using osu.Framework;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Database;
@ -153,14 +154,14 @@ namespace osu.Game.Rulesets
{
try
{
string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll");
var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll");
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
loadRulesetFromFile(file);
}
catch (Exception e)
{
Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}");
Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}");
}
}

View File

@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Scoring
{
@ -47,6 +49,8 @@ namespace osu.Game.Rulesets.Scoring
private double targetMinimumHealth;
private double drainRate = 1;
private PeriodTracker noDrainPeriodTracker;
/// <summary>
/// Creates a new <see cref="DrainingHealthProcessor"/>.
/// </summary>
@ -60,14 +64,14 @@ namespace osu.Game.Rulesets.Scoring
{
base.Update();
if (!IsBreakTime.Value)
{
// When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
if (noDrainPeriodTracker?.IsInAny(Time.Current) == true)
return;
Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
}
// When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time
double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime);
double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime);
Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime);
}
public override void ApplyBeatmap(IBeatmap beatmap)
@ -77,6 +81,19 @@ namespace osu.Game.Rulesets.Scoring
if (beatmap.HitObjects.Count > 0)
gameplayEndTime = beatmap.HitObjects[^1].GetEndTime();
noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period(
beatmap.HitObjects
.Select(hitObject => hitObject.GetEndTime())
.Where(endTime => endTime <= breakPeriod.StartTime)
.DefaultIfEmpty(double.MinValue)
.Last(),
beatmap.HitObjects
.Select(hitObject => hitObject.StartTime)
.Where(startTime => startTime >= breakPeriod.EndTime)
.DefaultIfEmpty(double.MaxValue)
.First()
)));
targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
base.ApplyBeatmap(beatmap);

View File

@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.Scoring
/// </summary>
public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
/// <summary>
/// Whether gameplay is currently in a break.
/// </summary>
public readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
/// <summary>
/// Whether this ScoreProcessor has already triggered the failed state.
/// </summary>

View File

@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private Drawable outline;
[Resolved(CanBeNull = true)]
private EditorBeatmap editorBeatmap { get; set; }
protected EditorBeatmap EditorBeatmap { get; private set; }
[Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; }
@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
internal void HandleSelected(SelectionBlueprint blueprint)
{
selectedBlueprints.Add(blueprint);
editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject);
EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject);
UpdateVisibility();
}
@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
internal void HandleDeselected(SelectionBlueprint blueprint)
{
selectedBlueprints.Remove(blueprint);
editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
// We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection
if (selectedBlueprints.Count == 0)
@ -165,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
changeHandler?.BeginChange();
foreach (var h in selectedBlueprints.ToList())
editorBeatmap?.Remove(h.HitObject);
EditorBeatmap?.Remove(h.HitObject);
changeHandler?.EndChange();
}

View File

@ -259,8 +259,6 @@ namespace osu.Game.Screens.Play
Breaks = working.Beatmap.Breaks
}
});
HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime);
}
private void addOverlayComponents(Container target, WorkingBeatmap working)

View File

@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Expanded
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
Text = $"Played on {score.Date.ToLocalTime():g}"
Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}"
}
}
};

View File

@ -243,5 +243,10 @@ namespace osu.Game.Screens.Ranking
return true;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
=> base.ReceivePositionalInputAt(screenSpacePos)
|| topLayerContainer.ReceivePositionalInputAt(screenSpacePos)
|| middleLayerContainer.ReceivePositionalInputAt(screenSpacePos);
}
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
@ -24,6 +25,9 @@ namespace osu.Game.Screens.Ranking
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
{
if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending)
return null;
var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return req;

View File

@ -7,6 +7,8 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
@ -20,6 +22,9 @@ namespace osu.Game.Screens.Select
private bool removeAutoModOnResume;
private OsuScreen player;
[Resolved(CanBeNull = true)]
private NotificationOverlay notifications { get; set; }
public override bool AllowExternalScreenChange => true;
protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap();
@ -49,8 +54,11 @@ namespace osu.Game.Screens.Select
if (removeAutoModOnResume)
{
var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType();
ModSelect.DeselectTypes(new[] { autoType }, true);
var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType();
if (autoType != null)
ModSelect.DeselectTypes(new[] { autoType }, true);
removeAutoModOnResume = false;
}
}
@ -78,10 +86,19 @@ namespace osu.Game.Screens.Select
if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true)
{
var auto = Ruleset.Value.CreateInstance().GetAutoplayMod();
var autoType = auto.GetType();
var autoType = auto?.GetType();
var mods = Mods.Value;
if (autoType == null)
{
notifications?.Post(new SimpleNotification
{
Text = "The current ruleset doesn't have an autoplay mod avalaible!"
});
return false;
}
if (mods.All(m => m.GetType() != autoType))
{
Mods.Value = mods.Append(auto).ToArray();

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@ -118,7 +119,7 @@ namespace osu.Game.Tests.Visual
}
}
localStorage = new Lazy<Storage>(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}"));
localStorage = new Lazy<Storage>(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}")));
}
[Resolved]

View File

@ -24,8 +24,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.528.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.601.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.602.0" />
<PackageReference Include="Sentry" Version="2.1.1" />
<PackageReference Include="SharpCompress" Version="0.25.1" />
<PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -70,8 +70,8 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.528.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.512.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.601.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.602.0" />
</ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies">
@ -80,7 +80,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2020.528.0" />
<PackageReference Include="ppy.osu.Framework" Version="2020.601.0" />
<PackageReference Include="SharpCompress" Version="0.25.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />