mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 01:39:54 +08:00
Compare commits
229 Commits
@@ -28,7 +28,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Annotate CI run with test results
|
||||
uses: dorny/test-reporter@v1.4.2
|
||||
uses: dorny/test-reporter@v1.6.0
|
||||
with:
|
||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||
|
||||
+5
-2
@@ -51,11 +51,14 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1127.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1126.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.1207.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1208.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||
<PackageReference Include="Realm" Version="10.18.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<LinkDescription Include="$(MSBuildThisFileDirectory)\osu.Android\Linker.xml"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<linker>
|
||||
<assembly fullname="mscorlib">
|
||||
<!-- see https://github.com/ppy/osu/issues/21516 -->
|
||||
<type fullname="System.Globalization.*Calendar"/>
|
||||
</assembly>
|
||||
</linker>
|
||||
@@ -4,8 +4,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Rulesets.Catch.Mods;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
@@ -55,6 +57,21 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestGameCursorHidden()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new CatchModRelax(),
|
||||
Autoplay = false,
|
||||
PassCondition = () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableCatchRuleset>().Single());
|
||||
return this.ChildrenOfType<MenuCursorContainer>().Single().State.Value == Visibility.Hidden;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private bool passCondition()
|
||||
{
|
||||
var playfield = this.ChildrenOfType<CatchPlayfield>().Single();
|
||||
|
||||
@@ -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.Framework.Graphics;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public partial class CatchCursorContainer : GameplayCursorContainer
|
||||
{
|
||||
// Just hide the cursor.
|
||||
// The main goal here is to show that we have a cursor so the game never shows the global one.
|
||||
protected override Drawable CreateCursor() => Empty();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
|
||||
@@ -49,6 +50,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
||||
@@ -90,6 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
public override bool CursorInPlacementArea => false;
|
||||
|
||||
public TestHitObjectComposer(Playfield playfield)
|
||||
: base(new ManiaRuleset())
|
||||
{
|
||||
Playfield = playfield;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Osu.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class OsuHitObjectGenerationUtilsTest
|
||||
{
|
||||
private static Slider createTestSlider()
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
Position = new Vector2(128, 128),
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(new Vector2(), PathType.Linear),
|
||||
new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0)
|
||||
new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128)
|
||||
}
|
||||
},
|
||||
RepeatCount = 1
|
||||
};
|
||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
return slider;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReflectSliderHorizontallyAlongPlayfield()
|
||||
{
|
||||
var slider = createTestSlider();
|
||||
|
||||
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider);
|
||||
|
||||
Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128)));
|
||||
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128)));
|
||||
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
|
||||
{
|
||||
new Vector2(),
|
||||
new Vector2(64, -128),
|
||||
new Vector2(128, 0)
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReflectSliderVerticallyAlongPlayfield()
|
||||
{
|
||||
var slider = createTestSlider();
|
||||
|
||||
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider);
|
||||
|
||||
Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128)));
|
||||
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128)));
|
||||
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
|
||||
{
|
||||
new Vector2(),
|
||||
new Vector2(-64, 128),
|
||||
new Vector2(-128, 0)
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFlipSliderInPlaceHorizontally()
|
||||
{
|
||||
var slider = createTestSlider();
|
||||
|
||||
OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
|
||||
|
||||
Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128)));
|
||||
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(256, 128)));
|
||||
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
|
||||
{
|
||||
new Vector2(),
|
||||
new Vector2(64, -128),
|
||||
new Vector2(128, 0)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -22,6 +24,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
|
||||
private bool isPlacingEnd;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[CanBeNull]
|
||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
||||
|
||||
public SpinnerPlacementBlueprint()
|
||||
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
||||
{
|
||||
@@ -33,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
base.Update();
|
||||
|
||||
if (isPlacingEnd)
|
||||
HitObject.EndTime = Math.Max(HitObject.StartTime, EditorClock.CurrentTime);
|
||||
updateEndTimeFromCurrent();
|
||||
|
||||
piece.UpdateFrom(HitObject);
|
||||
}
|
||||
@@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
if (e.Button != MouseButton.Right)
|
||||
return false;
|
||||
|
||||
HitObject.EndTime = EditorClock.CurrentTime;
|
||||
updateEndTimeFromCurrent();
|
||||
EndPlacement(true);
|
||||
}
|
||||
else
|
||||
@@ -61,5 +67,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateEndTimeFromCurrent()
|
||||
{
|
||||
HitObject.EndTime = beatSnapProvider == null
|
||||
? Math.Max(HitObject.StartTime, EditorClock.CurrentTime)
|
||||
: Math.Max(HitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(HitObject.StartTime), beatSnapProvider.SnapTime(EditorClock.CurrentTime));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
var osuObject = (OsuHitObject)hitObject;
|
||||
|
||||
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,16 +27,16 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
switch (Reflection.Value)
|
||||
{
|
||||
case MirrorType.Horizontal:
|
||||
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
|
||||
break;
|
||||
|
||||
case MirrorType.Vertical:
|
||||
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
|
||||
break;
|
||||
|
||||
case MirrorType.Both:
|
||||
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
|
||||
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
flowDirection = !flowDirection;
|
||||
}
|
||||
|
||||
if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5)
|
||||
{
|
||||
OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
||||
|
||||
@@ -17,7 +17,6 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@@ -196,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
private IEnumerable<double> generateBeats(IBeatmap beatmap, IReadOnlyCollection<OsuHitObject> originalHitObjects)
|
||||
{
|
||||
double startTime = originalHitObjects.First().StartTime;
|
||||
double endTime = originalHitObjects.Last().GetEndTime();
|
||||
double startTime = beatmap.HitObjects.First().StartTime;
|
||||
double endTime = beatmap.GetLastObjectTime();
|
||||
|
||||
var beats = beatmap.ControlPointInfo.TimingPoints
|
||||
// Ignore timing points after endTime
|
||||
|
||||
@@ -112,44 +112,46 @@ namespace osu.Game.Rulesets.Osu.Utils
|
||||
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield horizontally.
|
||||
/// </summary>
|
||||
/// <param name="osuObject">The object to reflect.</param>
|
||||
public static void ReflectHorizontally(OsuHitObject osuObject)
|
||||
public static void ReflectHorizontallyAlongPlayfield(OsuHitObject osuObject)
|
||||
{
|
||||
osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y);
|
||||
|
||||
if (!(osuObject is Slider slider))
|
||||
if (osuObject is not Slider slider)
|
||||
return;
|
||||
|
||||
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||
void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y);
|
||||
static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
|
||||
|
||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||
foreach (var point in controlPoints)
|
||||
point.Position = new Vector2(-point.Position.X, point.Position.Y);
|
||||
|
||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||
modifySlider(slider, reflectNestedObject, reflectControlPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield vertically.
|
||||
/// </summary>
|
||||
/// <param name="osuObject">The object to reflect.</param>
|
||||
public static void ReflectVertically(OsuHitObject osuObject)
|
||||
public static void ReflectVerticallyAlongPlayfield(OsuHitObject osuObject)
|
||||
{
|
||||
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
|
||||
|
||||
if (!(osuObject is Slider slider))
|
||||
if (osuObject is not Slider slider)
|
||||
return;
|
||||
|
||||
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||
void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y);
|
||||
static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y);
|
||||
|
||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||
foreach (var point in controlPoints)
|
||||
point.Position = new Vector2(point.Position.X, -point.Position.Y);
|
||||
modifySlider(slider, reflectNestedObject, reflectControlPoint);
|
||||
}
|
||||
|
||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||
/// <summary>
|
||||
/// Flips the position of the <see cref="Slider"/> around its start position horizontally.
|
||||
/// </summary>
|
||||
/// <param name="slider">The slider to be flipped.</param>
|
||||
public static void FlipSliderInPlaceHorizontally(Slider slider)
|
||||
{
|
||||
void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y);
|
||||
static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
|
||||
|
||||
modifySlider(slider, flipNestedObject, flipControlPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,14 +162,20 @@ namespace osu.Game.Rulesets.Osu.Utils
|
||||
public static void RotateSlider(Slider slider, float rotation)
|
||||
{
|
||||
void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position;
|
||||
void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation);
|
||||
|
||||
modifySlider(slider, rotateNestedObject, rotateControlPoint);
|
||||
}
|
||||
|
||||
private static void modifySlider(Slider slider, Action<OsuHitObject> modifyNestedObject, Action<PathControlPoint> modifyControlPoint)
|
||||
{
|
||||
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(rotateNestedObject);
|
||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(rotateNestedObject);
|
||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(modifyNestedObject);
|
||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(modifyNestedObject);
|
||||
|
||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||
foreach (var point in controlPoints)
|
||||
point.Position = rotateVector(point.Position, rotation);
|
||||
modifyControlPoint(point);
|
||||
|
||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||
{
|
||||
public partial class TestSceneTaikoKiaiGlow : TaikoSkinnableTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestKiaiGlow()
|
||||
{
|
||||
AddStep("Create kiai glow", () => SetContents(_ => new LegacyKiaiGlow()));
|
||||
AddToggleStep("Toggle kiai mode", setUpBeatmap);
|
||||
}
|
||||
|
||||
private void setUpBeatmap(bool withKiai)
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
|
||||
if (withKiai)
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPointInfo
|
||||
});
|
||||
|
||||
Beatmap.Value.Track.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// 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.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
internal partial class LegacyKiaiGlow : BeatSyncedContainer
|
||||
{
|
||||
private bool isKiaiActive;
|
||||
|
||||
private Sprite sprite = null!;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(ISkinSource skin, HealthProcessor? healthProcessor)
|
||||
{
|
||||
Child = sprite = new Sprite
|
||||
{
|
||||
Texture = skin.GetTexture("taiko-glow"),
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(0.7f),
|
||||
Colour = new Colour4(255, 228, 0, 255),
|
||||
};
|
||||
|
||||
if (healthProcessor != null)
|
||||
healthProcessor.NewJudgement += onNewJudgement;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (isKiaiActive)
|
||||
sprite.Alpha = (float)Math.Min(1, sprite.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 100f);
|
||||
else
|
||||
sprite.Alpha = (float)Math.Max(0, sprite.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 600f);
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
isKiaiActive = effectPoint.KiaiMode;
|
||||
}
|
||||
|
||||
private void onNewJudgement(JudgementResult result)
|
||||
{
|
||||
if (!result.IsHit || !isKiaiActive)
|
||||
return;
|
||||
|
||||
sprite.ScaleTo(0.85f).Then()
|
||||
.ScaleTo(0.7f, 80, Easing.OutQuad);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// 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.Audio.Track;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
private Sprite kiai = null!;
|
||||
|
||||
private bool kiaiDisplayed;
|
||||
private bool isKiaiActive;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
@@ -41,17 +42,19 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (isKiaiActive)
|
||||
kiai.Alpha = (float)Math.Min(1, kiai.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 200f);
|
||||
else
|
||||
kiai.Alpha = (float)Math.Max(0, kiai.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 200f);
|
||||
}
|
||||
|
||||
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||
{
|
||||
base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes);
|
||||
|
||||
if (effectPoint.KiaiMode != kiaiDisplayed)
|
||||
{
|
||||
kiaiDisplayed = effectPoint.KiaiMode;
|
||||
|
||||
kiai.ClearTransforms();
|
||||
kiai.FadeTo(kiaiDisplayed ? 1 : 0, 200);
|
||||
}
|
||||
isKiaiActive = effectPoint.KiaiMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
case TaikoSkinComponents.Mascot:
|
||||
return new DrawableTaikoMascot();
|
||||
|
||||
case TaikoSkinComponents.KiaiGlow:
|
||||
if (GetTexture("taiko-glow") != null)
|
||||
return new LegacyKiaiGlow();
|
||||
|
||||
return null;
|
||||
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
|
||||
@@ -21,5 +21,6 @@ namespace osu.Game.Rulesets.Taiko
|
||||
TaikoExplosionKiai,
|
||||
Scroller,
|
||||
Mascot,
|
||||
KiaiGlow
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,10 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
FillMode = FillMode.Fit,
|
||||
Children = new[]
|
||||
{
|
||||
new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
hitExplosionContainer = new Container<HitExplosion>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
|
||||
@@ -314,6 +314,24 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGetLastObjectTime()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("mania-last-object-not-latest.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
|
||||
Assert.That(beatmap.HitObjects.Last().StartTime, Is.EqualTo(2494));
|
||||
Assert.That(beatmap.HitObjects.Last().GetEndTime(), Is.EqualTo(2494));
|
||||
|
||||
Assert.That(beatmap.HitObjects.Max(h => h.GetEndTime()), Is.EqualTo(2582));
|
||||
Assert.That(beatmap.GetLastObjectTime(), Is.EqualTo(2582));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapComboOffsetsOsu()
|
||||
{
|
||||
|
||||
@@ -16,7 +16,9 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -179,6 +181,40 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSoloScoreData()
|
||||
{
|
||||
var ruleset = new OsuRuleset().RulesetInfo;
|
||||
|
||||
var scoreInfo = TestResources.CreateTestScoreInfo(ruleset);
|
||||
scoreInfo.Mods = new Mod[]
|
||||
{
|
||||
new OsuModDoubleTime { SpeedChange = { Value = 1.1 } }
|
||||
};
|
||||
|
||||
var beatmap = new TestBeatmap(ruleset);
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = scoreInfo,
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics));
|
||||
Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods));
|
||||
});
|
||||
}
|
||||
|
||||
private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap)
|
||||
{
|
||||
var encodeStream = new MemoryStream();
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -137,6 +138,31 @@ namespace osu.Game.Tests.Gameplay
|
||||
AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResultSetBeforeLoadComplete()
|
||||
{
|
||||
TestDrawableHitObject dho = null;
|
||||
HitObjectLifetimeEntry lifetimeEntry = null;
|
||||
AddStep("Create lifetime entry", () =>
|
||||
{
|
||||
var hitObject = new HitObject { StartTime = Time.Current };
|
||||
lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
|
||||
{
|
||||
Result = new JudgementResult(hitObject, hitObject.CreateJudgement())
|
||||
{
|
||||
Type = HitResult.Great
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("Create DHO and apply entry", () =>
|
||||
{
|
||||
dho = new TestDrawableHitObject();
|
||||
dho.Apply(lifetimeEntry);
|
||||
Child = dho;
|
||||
});
|
||||
AddAssert("DHO state is correct", () => dho.State.Value, () => Is.EqualTo(ArmedState.Hit));
|
||||
}
|
||||
|
||||
private partial class TestDrawableHitObject : DrawableHitObject
|
||||
{
|
||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
osu file format v14
|
||||
|
||||
[General]
|
||||
SampleSet: Normal
|
||||
StackLeniency: 0.7
|
||||
Mode: 3
|
||||
|
||||
[Difficulty]
|
||||
HPDrainRate:3
|
||||
CircleSize:5
|
||||
OverallDifficulty:8
|
||||
ApproachRate:8
|
||||
SliderMultiplier:3.59999990463257
|
||||
SliderTickRate:2
|
||||
|
||||
[TimingPoints]
|
||||
24,352.941176470588,4,1,1,100,1,0
|
||||
6376,-50,4,1,1,100,0,0
|
||||
|
||||
[HitObjects]
|
||||
51,192,24,1,0,0:0:0:0:
|
||||
153,192,200,1,0,0:0:0:0:
|
||||
358,192,376,1,0,0:0:0:0:
|
||||
460,192,553,1,0,0:0:0:0:
|
||||
460,192,729,128,0,1435:0:0:0:0:
|
||||
358,192,906,128,0,1612:0:0:0:0:
|
||||
256,192,1082,128,0,1788:0:0:0:0:
|
||||
153,192,1259,128,0,1965:0:0:0:0:
|
||||
51,192,1435,128,0,2141:0:0:0:0:
|
||||
51,192,2318,1,12,0:0:0:0:
|
||||
153,192,2318,1,4,0:0:0:0:
|
||||
256,192,2318,1,6,0:0:0:0:
|
||||
358,192,2318,1,14,0:0:0:0:
|
||||
460,192,2318,1,0,0:0:0:0:
|
||||
51,192,2494,128,0,2582:0:0:0:0:
|
||||
153,192,2494,128,14,2582:0:0:0:0:
|
||||
256,192,2494,128,6,2582:0:0:0:0:
|
||||
358,192,2494,128,4,2582:0:0:0:0:
|
||||
460,192,2494,1,12,0:0:0:0:0:
|
||||
@@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
@@ -355,6 +356,28 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
[Test]
|
||||
public void TestAccuracyWhenNearPerfect()
|
||||
{
|
||||
const int count_judgements = 1000;
|
||||
const int count_misses = 1;
|
||||
|
||||
double actual = new TestScoreProcessor().ComputeAccuracy(new ScoreInfo
|
||||
{
|
||||
Statistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
{ HitResult.Great, count_judgements - count_misses },
|
||||
{ HitResult.Miss, count_misses }
|
||||
}
|
||||
});
|
||||
|
||||
const double expected = (count_judgements - count_misses) / (double)count_judgements;
|
||||
|
||||
Assert.That(actual, Is.Not.EqualTo(0.0));
|
||||
Assert.That(actual, Is.Not.EqualTo(1.0));
|
||||
Assert.That(actual, Is.EqualTo(expected).Within(Precision.FLOAT_EPSILON));
|
||||
}
|
||||
|
||||
private class TestRuleset : Ruleset
|
||||
{
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace osu.Game.Tests.Utils
|
||||
public class NamingUtilsTest
|
||||
{
|
||||
[Test]
|
||||
public void TestEmptySet()
|
||||
public void TestNextBestNameEmptySet()
|
||||
{
|
||||
string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty<string>(), "New Difficulty");
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotTaken()
|
||||
public void TestNextBestNameNotTaken()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotTakenButClose()
|
||||
public void TestNextBestNameNotTakenButClose()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -49,7 +49,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlreadyTaken()
|
||||
public void TestNextBestNameAlreadyTaken()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -62,7 +62,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlreadyTakenWithDifferentCase()
|
||||
public void TestNextBestNameAlreadyTakenWithDifferentCase()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -75,7 +75,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlreadyTakenWithBrackets()
|
||||
public void TestNextBestNameAlreadyTakenWithBrackets()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -88,7 +88,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleAlreadyTaken()
|
||||
public void TestNextBestNameMultipleAlreadyTaken()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -104,7 +104,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEvenMoreAlreadyTaken()
|
||||
public void TestNextBestNameEvenMoreAlreadyTaken()
|
||||
{
|
||||
string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray();
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace osu.Game.Tests.Utils
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleAlreadyTakenWithGaps()
|
||||
public void TestNextBestNameMultipleAlreadyTakenWithGaps()
|
||||
{
|
||||
string[] existingNames =
|
||||
{
|
||||
@@ -128,5 +128,153 @@ namespace osu.Game.Tests.Utils
|
||||
|
||||
Assert.AreEqual("New Difficulty (2)", nextBestName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameEmptySet()
|
||||
{
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(Enumerable.Empty<string>(), "test_file.osr");
|
||||
|
||||
Assert.AreEqual("test_file.osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameNotTaken()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"this file exists.zip",
|
||||
"that file exists.too",
|
||||
"three.4",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "test_file.osr");
|
||||
|
||||
Assert.AreEqual("test_file.osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameNotTakenButClose()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file(1).osr",
|
||||
"replay_file (not a number).zip",
|
||||
"replay_file (1 <- now THAT is a number right here).lol",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
|
||||
Assert.AreEqual("replay_file.osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameAlreadyTaken()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
|
||||
Assert.AreEqual("replay_file (1).osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameAlreadyTakenDifferentCase()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
"RePlAy_FiLe (1).OsR",
|
||||
"REPLAY_FILE (2).OSR",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
Assert.AreEqual("replay_file (3).osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameAlreadyTakenWithBrackets()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
"replay_file (copy).osr",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
Assert.AreEqual("replay_file (1).osr", nextBestFilename);
|
||||
|
||||
nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file (copy).osr");
|
||||
Assert.AreEqual("replay_file (copy) (1).osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameMultipleAlreadyTaken()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
"replay_file (1).osr",
|
||||
"replay_file (2).osr",
|
||||
"replay_file (3).osr",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
|
||||
Assert.AreEqual("replay_file (4).osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameMultipleAlreadyTakenWithGaps()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
"replay_file (1).osr",
|
||||
"replay_file (2).osr",
|
||||
"replay_file (4).osr",
|
||||
"replay_file (5).osr",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
|
||||
Assert.AreEqual("replay_file (3).osr", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameNoExtensions()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"those",
|
||||
"are definitely",
|
||||
"files",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "surely");
|
||||
Assert.AreEqual("surely", nextBestFilename);
|
||||
|
||||
nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "those");
|
||||
Assert.AreEqual("those (1)", nextBestFilename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextBestFilenameDifferentExtensions()
|
||||
{
|
||||
string[] existingFiles =
|
||||
{
|
||||
"replay_file.osr",
|
||||
"replay_file (1).osr",
|
||||
"replay_file.txt",
|
||||
};
|
||||
|
||||
string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr");
|
||||
Assert.AreEqual("replay_file (2).osr", nextBestFilename);
|
||||
|
||||
nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.txt");
|
||||
Assert.AreEqual("replay_file (1).txt", nextBestFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
public partial class TestSceneTrianglesV2Background : OsuTestScene
|
||||
{
|
||||
private readonly TrianglesV2 triangles;
|
||||
private readonly Box box;
|
||||
|
||||
public TestSceneTrianglesV2Background()
|
||||
{
|
||||
@@ -23,27 +25,44 @@ namespace osu.Game.Tests.Visual.Background
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Gray
|
||||
},
|
||||
new Container
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 100),
|
||||
Masking = true,
|
||||
CornerRadius = 40,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Red
|
||||
Size = new Vector2(500, 100),
|
||||
Masking = true,
|
||||
CornerRadius = 40,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Red
|
||||
},
|
||||
triangles = new TrianglesV2
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
},
|
||||
triangles = new TrianglesV2
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColourTop = Color4.White,
|
||||
ColourBottom = Color4.Red
|
||||
Size = new Vector2(500, 100),
|
||||
Masking = true,
|
||||
CornerRadius = 40,
|
||||
Child = box = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,8 +73,16 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("Spawn ratio", 0f, 2f, 1f, s => triangles.SpawnRatio = s);
|
||||
AddSliderStep("Spawn ratio", 0f, 10f, 1f, s =>
|
||||
{
|
||||
triangles.SpawnRatio = s;
|
||||
triangles.Reset(1234);
|
||||
});
|
||||
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t);
|
||||
|
||||
AddStep("White colour", () => box.Colour = triangles.Colour = Color4.White);
|
||||
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
|
||||
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Screens.Edit;
|
||||
@@ -26,6 +28,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
private TimingScreen timingScreen;
|
||||
private EditorBeatmap editorBeatmap;
|
||||
|
||||
protected override bool ScrollUsingMouseWheel => false;
|
||||
|
||||
@@ -35,8 +38,11 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
Beatmap.Disabled = true;
|
||||
}
|
||||
|
||||
var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
|
||||
private void reloadEditorBeatmap()
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
@@ -58,7 +64,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
AddStep("Stop clock", () => EditorClock.Stop());
|
||||
|
||||
AddUntilStep("wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
|
||||
AddStep("Reload Editor Beatmap", reloadEditorBeatmap);
|
||||
|
||||
AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -95,6 +103,37 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollControlGroupIntoView()
|
||||
{
|
||||
AddStep("Add many control points", () =>
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Clear();
|
||||
|
||||
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint());
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
editorBeatmap.ControlPointInfo.Add((i + 1) * 1000, new EffectControlPoint
|
||||
{
|
||||
KiaiMode = Convert.ToBoolean(i % 2),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("Select first effect point", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Child.ChildrenOfType<EffectRowAttribute>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Seek to beginning", () => EditorClock.Seek(0));
|
||||
|
||||
AddStep("Seek to last point", () => EditorClock.Seek(101 * 1000));
|
||||
|
||||
AddUntilStep("Scrolled to end", () => timingScreen.ChildrenOfType<OsuScrollContainer>().First().IsScrolledToEnd());
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -187,18 +188,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestInputDoesntWorkWhenHUDHidden()
|
||||
{
|
||||
SongProgressBar getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().Single();
|
||||
SongProgressBar? getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().SingleOrDefault();
|
||||
|
||||
bool seeked = false;
|
||||
|
||||
createNew();
|
||||
|
||||
AddUntilStep("wait for song progress", () => getSongProgress() != null);
|
||||
|
||||
AddStep("bind seek", () =>
|
||||
{
|
||||
seeked = false;
|
||||
|
||||
var progress = getSongProgress();
|
||||
|
||||
Debug.Assert(progress != null);
|
||||
|
||||
progress.ShowHandle = true;
|
||||
progress.OnSeek += _ => seeked = true;
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
|
||||
AddAssert("state is available", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||
checkState(DownloadState.NotDownloaded);
|
||||
|
||||
AddStep("click button", () =>
|
||||
{
|
||||
@@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
|
||||
AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||
checkState(DownloadState.NotDownloaded);
|
||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
|
||||
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||
checkState(DownloadState.NotDownloaded);
|
||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
}
|
||||
|
||||
@@ -174,17 +174,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
|
||||
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||
checkState(DownloadState.NotDownloaded);
|
||||
|
||||
AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)));
|
||||
|
||||
AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);
|
||||
checkState(DownloadState.LocallyAvailable);
|
||||
AddAssert("button is enabled", () => downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
|
||||
AddStep("delete score", () => scoreManager.Delete(imported.Value));
|
||||
|
||||
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
|
||||
checkState(DownloadState.NotDownloaded);
|
||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
}
|
||||
|
||||
@@ -202,10 +201,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
|
||||
|
||||
AddAssert("state is unknown", () => downloadButton.State.Value == DownloadState.Unknown);
|
||||
checkState(DownloadState.Unknown);
|
||||
AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType<DownloadButton>().First().Enabled.Value);
|
||||
}
|
||||
|
||||
private void checkState(DownloadState expectedState) =>
|
||||
AddUntilStep($"state is {expectedState}", () => downloadButton.State.Value, () => Is.EqualTo(expectedState));
|
||||
|
||||
private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo
|
||||
{
|
||||
OnlineID = hasOnlineId ? online_score_id : 0,
|
||||
|
||||
@@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestFinalFramesPurgedBeforeEndingPlay()
|
||||
{
|
||||
AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score()));
|
||||
AddStep("begin playing", () => spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), new Score()));
|
||||
|
||||
AddStep("send frames and finish play", () =>
|
||||
{
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
};
|
||||
|
||||
spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore);
|
||||
spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), recordingScore);
|
||||
spectatorClient.OnNewFrames += onNewFrames;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,11 +117,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
BeatmapID = 0,
|
||||
RulesetID = 0,
|
||||
Mods = user.Mods,
|
||||
MaximumScoringValues = new ScoringValues
|
||||
MaximumStatistics = new Dictionary<HitResult, int>
|
||||
{
|
||||
BaseScore = 10000,
|
||||
MaxCombo = 1000,
|
||||
CountBasicHitObjects = 1000
|
||||
{ HitResult.Perfect, 100 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,11 +130,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
|
||||
|
||||
var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
|
||||
var linkCompilers = newLine.DrawableContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
|
||||
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
|
||||
|
||||
return linkSprites.All(d => d.Colour == linkColour)
|
||||
&& newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour);
|
||||
&& newLine.DrawableContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(null));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is listening"));
|
||||
}
|
||||
@@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo()));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is editing"));
|
||||
}
|
||||
@@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo()));
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
AddAssert("Check correct response", () => postTarget.LastMessage.Contains("is playing"));
|
||||
}
|
||||
@@ -69,7 +70,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
BeatmapInfo = { OnlineID = hasOnlineId ? 1234 : -1 }
|
||||
});
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand()));
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
if (hasOnlineId)
|
||||
AddAssert("Check link presence", () => postTarget.LastMessage.Contains("/b/1234"));
|
||||
@@ -77,6 +78,18 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("Check link not present", () => !postTarget.LastMessage.Contains("https://"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModPresence()
|
||||
{
|
||||
AddStep("Set activity", () => api.Activity.Value = new UserActivity.InSoloGame(new BeatmapInfo(), new RulesetInfo()));
|
||||
|
||||
AddStep("Add Hidden mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModHidden>() });
|
||||
|
||||
AddStep("Run command", () => Add(new NowPlayingCommand(new Channel())));
|
||||
|
||||
AddAssert("Check mod is present", () => postTarget.LastMessage.Contains("+HD"));
|
||||
}
|
||||
|
||||
public partial class PostTarget : Component, IChannelPostTarget
|
||||
{
|
||||
public void PostMessage(string text, bool isAction = false, Channel target = null)
|
||||
|
||||
@@ -73,6 +73,11 @@ namespace osu.Game.Tests.Visual.Online
|
||||
messageIdSequence = 0;
|
||||
channelManager.CurrentChannel.Value = testChannel = new Channel();
|
||||
|
||||
reinitialiseDrawableDisplay();
|
||||
});
|
||||
|
||||
private void reinitialiseDrawableDisplay()
|
||||
{
|
||||
Children = new[]
|
||||
{
|
||||
chatDisplay = new TestStandAloneChatDisplay
|
||||
@@ -92,13 +97,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Channel = { Value = testChannel },
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSystemMessageOrdering()
|
||||
{
|
||||
var standardMessage = new Message(messageIdSequence++)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Sender = admin,
|
||||
Content = "I am a wang!"
|
||||
};
|
||||
@@ -106,14 +112,45 @@ namespace osu.Game.Tests.Visual.Online
|
||||
var infoMessage1 = new InfoMessage($"the system is calling {messageIdSequence++}");
|
||||
var infoMessage2 = new InfoMessage($"the system is calling {messageIdSequence++}");
|
||||
|
||||
var standardMessage2 = new Message(messageIdSequence++)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Sender = admin,
|
||||
Content = "I am a wang!"
|
||||
};
|
||||
|
||||
AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage));
|
||||
AddStep("message from system", () => testChannel.AddNewMessages(infoMessage1));
|
||||
AddStep("message from system", () => testChannel.AddNewMessages(infoMessage2));
|
||||
AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage2));
|
||||
|
||||
AddAssert("message order is correct", () => testChannel.Messages.Count == 3
|
||||
&& testChannel.Messages[0] == standardMessage
|
||||
&& testChannel.Messages[1] == infoMessage1
|
||||
&& testChannel.Messages[2] == infoMessage2);
|
||||
AddAssert("count is correct", () => testChannel.Messages.Count, () => Is.EqualTo(4));
|
||||
|
||||
AddAssert("message order is correct", () => testChannel.Messages, () => Is.EqualTo(new[]
|
||||
{
|
||||
standardMessage,
|
||||
infoMessage1,
|
||||
infoMessage2,
|
||||
standardMessage2
|
||||
}));
|
||||
|
||||
AddAssert("displayed order is correct", () => chatDisplay.DrawableChannel.ChildrenOfType<ChatLine>().Select(c => c.Message), () => Is.EqualTo(new[]
|
||||
{
|
||||
standardMessage,
|
||||
infoMessage1,
|
||||
infoMessage2,
|
||||
standardMessage2
|
||||
}));
|
||||
|
||||
AddStep("reinit drawable channel", reinitialiseDrawableDisplay);
|
||||
|
||||
AddAssert("displayed order is still correct", () => chatDisplay.DrawableChannel.ChildrenOfType<ChatLine>().Select(c => c.Message), () => Is.EqualTo(new[]
|
||||
{
|
||||
standardMessage,
|
||||
infoMessage1,
|
||||
infoMessage2,
|
||||
standardMessage2
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -62,7 +62,6 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
section.Children.Where(f => f.IsPresent)
|
||||
.OfType<ISettingsItem>()
|
||||
.OfType<IFilterable>()
|
||||
.Where(f => !(f is IHasFilterableChildren))
|
||||
.All(f => f.FilterTerms.Any(t => t.ToString().Contains("scaling")))
|
||||
));
|
||||
|
||||
|
||||
@@ -1,28 +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 NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Select;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneDifficultyRangeFilterControl : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("create control", () =>
|
||||
{
|
||||
Child = new DifficultyRangeFilterControl
|
||||
{
|
||||
Width = 200,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// 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.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using NUnit.Framework;
|
||||
using osuTK;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneButtonsInput : OsuManualInputManagerTestScene
|
||||
{
|
||||
private const int width = 500;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
private readonly SettingsButton settingsButton;
|
||||
private readonly OsuClickableContainer clickableContainer;
|
||||
private readonly RoundedButton roundedButton;
|
||||
private readonly ShearedButton shearedButton;
|
||||
|
||||
public TestSceneButtonsInput()
|
||||
{
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 500,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
clickableContainer = new OsuClickableContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Enabled = { Value = true },
|
||||
Masking = true,
|
||||
CornerRadius = 20,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Red
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Rounded clickable container"
|
||||
}
|
||||
}
|
||||
},
|
||||
settingsButton = new SettingsButton
|
||||
{
|
||||
Enabled = { Value = true },
|
||||
Text = "Settings button"
|
||||
},
|
||||
roundedButton = new RoundedButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Enabled = { Value = true },
|
||||
Text = "Rounded button"
|
||||
},
|
||||
shearedButton = new ShearedButton(width)
|
||||
{
|
||||
Text = "Sheared button",
|
||||
LighterColour = Colour4.FromHex("#FFFFFF"),
|
||||
DarkerColour = Colour4.FromHex("#FFCC22"),
|
||||
TextColour = Colour4.Black,
|
||||
Height = 40,
|
||||
Enabled = { Value = true },
|
||||
Padding = new MarginPadding(0)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSettingsButtonInput()
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton));
|
||||
AddAssert("Button is hovered", () => settingsButton.IsHovered);
|
||||
AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10)));
|
||||
AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !settingsButton.IsHovered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundedButtonInput()
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(roundedButton));
|
||||
AddAssert("Button is hovered", () => roundedButton.IsHovered);
|
||||
AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(roundedButton.ScreenSpaceDrawQuad.TopLeft + Vector2.One));
|
||||
AddAssert("Cursor within a button", () => roundedButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !roundedButton.IsHovered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShearedButtonInput()
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(shearedButton));
|
||||
AddAssert("Button is hovered", () => shearedButton.IsHovered);
|
||||
AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(shearedButton.ScreenSpaceDrawQuad.TopLeft + Vector2.One));
|
||||
AddAssert("Cursor within a button", () => shearedButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !shearedButton.IsHovered);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoundedClickableContainerInput()
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(clickableContainer));
|
||||
AddAssert("Button is hovered", () => clickableContainer.IsHovered);
|
||||
AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(clickableContainer.ScreenSpaceDrawQuad.TopLeft + Vector2.One));
|
||||
AddAssert("Cursor within a button", () => clickableContainer.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !clickableContainer.IsHovered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,21 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
checkBindableAtValue("Circle Size", 9);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExtendedLimitsRetainedAfterBoundCopyCreation()
|
||||
{
|
||||
setExtendedLimits(true);
|
||||
setSliderValue("Circle Size", 11);
|
||||
|
||||
checkSliderAtValue("Circle Size", 11);
|
||||
checkBindableAtValue("Circle Size", 11);
|
||||
|
||||
AddStep("create bound copy", () => _ = modDifficultyAdjust.CircleSize.GetBoundCopy());
|
||||
|
||||
checkSliderAtValue("Circle Size", 11);
|
||||
checkBindableAtValue("Circle Size", 11);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestResetToDefault()
|
||||
{
|
||||
|
||||
@@ -1,47 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneOsuButton : OsuTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestToggleEnabled()
|
||||
{
|
||||
OsuButton button = null;
|
||||
|
||||
AddStep("add button", () => Child = button = new OsuButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200),
|
||||
Text = "Button"
|
||||
});
|
||||
|
||||
AddToggleStep("toggle enabled", toggle =>
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
button.Action = toggle ? () => { } : null;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitiallyDisabled()
|
||||
{
|
||||
AddStep("add button", () => Child = new OsuButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(200),
|
||||
Text = "Button"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneRangeSlider : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Red);
|
||||
|
||||
private readonly BindableNumber<double> customStart = new BindableNumber<double>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100,
|
||||
Precision = 0.1f
|
||||
};
|
||||
|
||||
private readonly BindableNumber<double> customEnd = new BindableNumber<double>(100)
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100,
|
||||
Precision = 0.1f
|
||||
};
|
||||
|
||||
private RangeSlider rangeSlider = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create control", () => Child = rangeSlider = new RangeSlider
|
||||
{
|
||||
Width = 200,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(3),
|
||||
LowerBound = customStart,
|
||||
UpperBound = customEnd,
|
||||
TooltipSuffix = "suffix",
|
||||
NubWidth = Nub.HEIGHT * 2,
|
||||
DefaultStringLowerBound = "Start",
|
||||
DefaultStringUpperBound = "End",
|
||||
MinRange = 10
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustRange()
|
||||
{
|
||||
AddAssert("Initial lower bound is correct", () => rangeSlider.LowerBound.Value, () => Is.EqualTo(0).Within(0.1f));
|
||||
AddAssert("Initial upper bound is correct", () => rangeSlider.UpperBound.Value, () => Is.EqualTo(100).Within(0.1f));
|
||||
|
||||
AddStep("Adjust range", () =>
|
||||
{
|
||||
customStart.Value = 50;
|
||||
customEnd.Value = 75;
|
||||
});
|
||||
|
||||
AddAssert("Adjusted lower bound is correct", () => rangeSlider.LowerBound.Value, () => Is.EqualTo(50).Within(0.1f));
|
||||
AddAssert("Adjusted upper bound is correct", () => rangeSlider.UpperBound.Value, () => Is.EqualTo(75).Within(0.1f));
|
||||
|
||||
AddStep("Test nub pushing", () =>
|
||||
{
|
||||
customStart.Value = 90;
|
||||
});
|
||||
|
||||
AddAssert("Pushed lower bound is correct", () => rangeSlider.LowerBound.Value, () => Is.EqualTo(90).Within(0.1f));
|
||||
AddAssert("Pushed upper bound is correct", () => rangeSlider.UpperBound.Value, () => Is.EqualTo(100).Within(0.1f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,6 +109,8 @@ namespace osu.Game.Audio
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
Stop();
|
||||
Track?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,9 +81,14 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public double GetMostCommonBeatLength()
|
||||
{
|
||||
double lastTime;
|
||||
|
||||
// The last playable time in the beatmap - the last timing point extends to this time.
|
||||
// Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context.
|
||||
double lastTime = HitObjects.LastOrDefault()?.GetEndTime() ?? ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0;
|
||||
if (!HitObjects.Any())
|
||||
lastTime = ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0;
|
||||
else
|
||||
lastTime = this.GetLastObjectTime();
|
||||
|
||||
var mostCommon =
|
||||
// Construct a set of (beatLength, duration) tuples for each individual timing point.
|
||||
|
||||
@@ -11,7 +11,7 @@ using osu.Game.Beatmaps.Drawables.Cards.Buttons;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Ranking.Expanded.Accuracy;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
private readonly UpdateableOnlineBeatmapSetCover cover;
|
||||
private readonly Container foreground;
|
||||
private readonly PlayButton playButton;
|
||||
private readonly SmoothCircularProgress progress;
|
||||
private readonly CircularProgress progress;
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
progress = new SmoothCircularProgress
|
||||
progress = new CircularProgress
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -102,5 +103,16 @@ namespace osu.Game.Beatmaps
|
||||
addCombo(nested, ref combo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the absolute end time of the latest <see cref="HitObject"/> in a beatmap. Will throw if beatmap contains no objects.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This correctly accounts for rulesets which have concurrent hitobjects which may have durations, causing the .Last() object
|
||||
/// to not necessarily have the latest end time.
|
||||
///
|
||||
/// It's not super efficient so calls should be kept to a minimum.
|
||||
/// </remarks>
|
||||
public static double GetLastObjectTime(this IBeatmap beatmap) => beatmap.HitObjects.Max(h => h.GetEndTime());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Utils;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -37,8 +39,11 @@ namespace osu.Game.Database
|
||||
/// <param name="item">The item to export.</param>
|
||||
public void Export(TModel item)
|
||||
{
|
||||
string filename = $"{item.GetDisplayString().GetValidFilename()}{FileExtension}";
|
||||
string itemFilename = item.GetDisplayString().GetValidFilename();
|
||||
|
||||
IEnumerable<string> existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}");
|
||||
|
||||
string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}");
|
||||
using (var stream = exportStorage.CreateFileSafely(filename))
|
||||
ExportModelTo(item, stream);
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace osu.Game.Database
|
||||
[Resolved]
|
||||
private RealmAccess realmAccess { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)] // canBeNull required while we remain on mono for mobile platforms.
|
||||
private DesktopGameHost? desktopGameHost { get; set; }
|
||||
[Resolved]
|
||||
private GameHost gameHost { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private INotificationOverlay? notifications { get; set; }
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Database
|
||||
|
||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||
|
||||
public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, desktopGameHost);
|
||||
public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost);
|
||||
|
||||
public virtual async Task<int> GetImportCount(StableContent content, CancellationToken cancellationToken)
|
||||
{
|
||||
|
||||
@@ -11,9 +11,7 @@ using osu.Framework.Allocation;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
@@ -23,28 +21,12 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
{
|
||||
private const float triangle_size = 100;
|
||||
private const float base_velocity = 50;
|
||||
private const int texture_height = 128;
|
||||
|
||||
/// <summary>
|
||||
/// sqrt(3) / 2
|
||||
/// </summary>
|
||||
private const float equilateral_triangle_ratio = 0.866f;
|
||||
|
||||
private readonly Bindable<Color4> colourTop = new Bindable<Color4>(Color4.White);
|
||||
private readonly Bindable<Color4> colourBottom = new Bindable<Color4>(Color4.Black);
|
||||
|
||||
public Color4 ColourTop
|
||||
{
|
||||
get => colourTop.Value;
|
||||
set => colourTop.Value = value;
|
||||
}
|
||||
|
||||
public Color4 ColourBottom
|
||||
{
|
||||
get => colourBottom.Value;
|
||||
set => colourBottom.Value = value;
|
||||
}
|
||||
|
||||
public float Thickness { get; set; } = 0.02f; // No need for invalidation since it's happening in Update()
|
||||
|
||||
/// <summary>
|
||||
@@ -70,9 +52,6 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
private readonly List<TriangleParticle> parts = new List<TriangleParticle>();
|
||||
|
||||
[Resolved]
|
||||
private IRenderer renderer { get; set; } = null!;
|
||||
|
||||
private Random? stableRandom;
|
||||
|
||||
private IShader shader = null!;
|
||||
@@ -89,42 +68,19 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders)
|
||||
private void load(ShaderManager shaders, IRenderer renderer)
|
||||
{
|
||||
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "TriangleBorder");
|
||||
texture = renderer.WhitePixel;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
colourTop.BindValueChanged(_ => updateTexture());
|
||||
colourBottom.BindValueChanged(_ => updateTexture(), true);
|
||||
|
||||
spawnRatio.BindValueChanged(_ => Reset(), true);
|
||||
}
|
||||
|
||||
private void updateTexture()
|
||||
{
|
||||
var image = new Image<Rgba32>(texture_height, 1);
|
||||
|
||||
texture = renderer.CreateTexture(1, texture_height, true);
|
||||
|
||||
for (int i = 0; i < texture_height; i++)
|
||||
{
|
||||
float ratio = (float)i / texture_height;
|
||||
|
||||
image[i, 0] = new Rgba32(
|
||||
colourBottom.Value.R * ratio + colourTop.Value.R * (1f - ratio),
|
||||
colourBottom.Value.G * ratio + colourTop.Value.G * (1f - ratio),
|
||||
colourBottom.Value.B * ratio + colourTop.Value.B * (1f - ratio)
|
||||
);
|
||||
}
|
||||
|
||||
texture.SetData(new TextureUpload(image));
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@@ -280,36 +236,42 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
||||
|
||||
float texturePartWidth = triangleSize.X / size.X;
|
||||
float texturePartHeight = triangleSize.Y / size.Y * texture_height;
|
||||
float relativeHeight = triangleSize.Y / size.Y;
|
||||
float relativeWidth = triangleSize.X / size.X;
|
||||
|
||||
foreach (TriangleParticle particle in parts)
|
||||
{
|
||||
Vector2 topLeft = particle.Position * size - new Vector2(triangleSize.X * 0.5f, 0f);
|
||||
Vector2 topRight = topLeft + new Vector2(triangleSize.X, 0f);
|
||||
Vector2 bottomLeft = topLeft + new Vector2(0f, triangleSize.Y);
|
||||
Vector2 bottomRight = topLeft + triangleSize;
|
||||
Vector2 topLeft = particle.Position - new Vector2(relativeWidth * 0.5f, 0f);
|
||||
Vector2 topRight = topLeft + new Vector2(relativeWidth, 0f);
|
||||
Vector2 bottomLeft = topLeft + new Vector2(0f, relativeHeight);
|
||||
Vector2 bottomRight = bottomLeft + new Vector2(relativeWidth, 0f);
|
||||
|
||||
var drawQuad = new Quad(
|
||||
Vector2Extensions.Transform(topLeft, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(topRight, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix)
|
||||
Vector2Extensions.Transform(topLeft * size, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(topRight * size, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomLeft * size, DrawInfo.Matrix),
|
||||
Vector2Extensions.Transform(bottomRight * size, DrawInfo.Matrix)
|
||||
);
|
||||
|
||||
var tRect = new Quad(
|
||||
topLeft.X / size.X,
|
||||
topLeft.Y / size.Y * texture_height,
|
||||
texturePartWidth,
|
||||
texturePartHeight
|
||||
).AABBFloat;
|
||||
ColourInfo colourInfo = triangleColourInfo(DrawColourInfo.Colour, new Quad(topLeft, topRight, bottomLeft, bottomRight));
|
||||
|
||||
renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour, tRect, vertexBatch.AddAction, textureCoords: tRect);
|
||||
renderer.DrawQuad(texture, drawQuad, colourInfo, vertexAction: vertexBatch.AddAction);
|
||||
}
|
||||
|
||||
shader.Unbind();
|
||||
}
|
||||
|
||||
private static ColourInfo triangleColourInfo(ColourInfo source, Quad quad)
|
||||
{
|
||||
return new ColourInfo
|
||||
{
|
||||
TopLeft = source.Interpolate(quad.TopLeft),
|
||||
TopRight = source.Interpolate(quad.TopRight),
|
||||
BottomLeft = source.Interpolate(quad.BottomLeft),
|
||||
BottomRight = source.Interpolate(quad.BottomRight)
|
||||
};
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
@@ -18,6 +17,12 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
private readonly Container content = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
// base call is checked for cases when `OsuClickableContainer` has masking applied to it directly (ie. externally in object initialisation).
|
||||
base.ReceivePositionalInputAt(screenSpacePos)
|
||||
// Implementations often apply masking / edge rounding at a content level, so it's imperative to check that as well.
|
||||
&& Content.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
protected virtual HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet) { Enabled = { BindTarget = Enabled } };
|
||||
@@ -38,11 +43,8 @@ namespace osu.Game.Graphics.Containers
|
||||
content.AutoSizeAxes = AutoSizeAxes;
|
||||
}
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
content,
|
||||
CreateHoverSounds(sampleSet)
|
||||
};
|
||||
AddInternal(content);
|
||||
Add(CreateHoverSounds(sampleSet));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,9 @@ namespace osu.Game.Graphics.Containers
|
||||
headerBackgroundContainer.Height = expandableHeaderSize + fixedHeaderSize;
|
||||
headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0;
|
||||
|
||||
float smallestSectionHeight = Children.Count > 0 ? Children.Min(d => d.Height) : 0;
|
||||
var flowChildren = scrollContentContainer.FlowingChildren.OfType<T>();
|
||||
|
||||
float smallestSectionHeight = flowChildren.Any() ? flowChildren.Min(d => d.Height) : 0;
|
||||
|
||||
// scroll offset is our fixed header height if we have it plus 10% of content height
|
||||
// plus 5% to fix floating point errors and to not have a section instantly unselect when scrolling upwards
|
||||
@@ -249,7 +251,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
float scrollCentre = fixedHeaderSize + scrollContainer.DisplayableContent * scroll_y_centre + selectionLenienceAboveSection;
|
||||
|
||||
var presentChildren = Children.Where(c => c.IsPresent);
|
||||
var presentChildren = flowChildren.Where(c => c.IsPresent);
|
||||
|
||||
if (lastClickedSection != null)
|
||||
SelectedSection.Value = lastClickedSection;
|
||||
|
||||
@@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -13,6 +11,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
@@ -20,16 +19,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// <summary>
|
||||
/// A button with added default sound effects.
|
||||
/// </summary>
|
||||
public partial class OsuButton : Button
|
||||
public abstract partial class OsuButton : Button
|
||||
{
|
||||
public LocalisableString Text
|
||||
{
|
||||
get => SpriteText?.Text ?? default;
|
||||
set
|
||||
{
|
||||
if (SpriteText != null)
|
||||
SpriteText.Text = value;
|
||||
}
|
||||
get => SpriteText.Text;
|
||||
set => SpriteText.Text = value;
|
||||
}
|
||||
|
||||
private Color4? backgroundColour;
|
||||
@@ -66,13 +61,19 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override Container<Drawable> Content { get; }
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
// base call is checked for cases when `OsuClickableContainer` has masking applied to it directly (ie. externally in object initialisation).
|
||||
base.ReceivePositionalInputAt(screenSpacePos)
|
||||
// Implementations often apply masking / edge rounding at a content level, so it's imperative to check that as well.
|
||||
&& Content.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
protected Box Hover;
|
||||
protected Box Background;
|
||||
protected SpriteText SpriteText;
|
||||
|
||||
private readonly Box flashLayer;
|
||||
|
||||
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
|
||||
protected OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
|
||||
{
|
||||
Height = 40;
|
||||
|
||||
@@ -115,7 +116,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
});
|
||||
|
||||
if (hoverSounds.HasValue)
|
||||
AddInternal(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } });
|
||||
Add(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
// 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.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class RangeSlider : CompositeDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The lower limiting value
|
||||
/// </summary>
|
||||
public Bindable<double> LowerBound
|
||||
{
|
||||
get => lowerBound.Current;
|
||||
set => lowerBound.Current = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The upper limiting value
|
||||
/// </summary>
|
||||
public Bindable<double> UpperBound
|
||||
{
|
||||
get => upperBound.Current;
|
||||
set => upperBound.Current = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text that describes this RangeSlider's functionality
|
||||
/// </summary>
|
||||
public string Label
|
||||
{
|
||||
set => label.Text = value;
|
||||
}
|
||||
|
||||
public float NubWidth
|
||||
{
|
||||
set => lowerBound.NubWidth = upperBound.NubWidth = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Minimum difference between the lower bound and higher bound
|
||||
/// </summary>
|
||||
public float MinRange
|
||||
{
|
||||
set => minRange = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// lower bound display for when it is set to its default value
|
||||
/// </summary>
|
||||
public string DefaultStringLowerBound
|
||||
{
|
||||
set => lowerBound.DefaultString = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// upper bound display for when it is set to its default value
|
||||
/// </summary>
|
||||
public string DefaultStringUpperBound
|
||||
{
|
||||
set => upperBound.DefaultString = value;
|
||||
}
|
||||
|
||||
public LocalisableString DefaultTooltipLowerBound
|
||||
{
|
||||
set => lowerBound.DefaultTooltip = value;
|
||||
}
|
||||
|
||||
public LocalisableString DefaultTooltipUpperBound
|
||||
{
|
||||
set => upperBound.DefaultTooltip = value;
|
||||
}
|
||||
|
||||
public string TooltipSuffix
|
||||
{
|
||||
set => upperBound.TooltipSuffix = lowerBound.TooltipSuffix = value;
|
||||
}
|
||||
|
||||
private float minRange = 0.1f;
|
||||
|
||||
private readonly OsuSpriteText label;
|
||||
|
||||
private readonly LowerBoundSlider lowerBound;
|
||||
private readonly UpperBoundSlider upperBound;
|
||||
|
||||
public RangeSlider()
|
||||
{
|
||||
const float vertical_offset = 13;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
label = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
},
|
||||
upperBound = new UpperBoundSlider
|
||||
{
|
||||
KeyboardStep = 0.1f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Y = vertical_offset,
|
||||
},
|
||||
lowerBound = new LowerBoundSlider
|
||||
{
|
||||
KeyboardStep = 0.1f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Y = vertical_offset,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
lowerBound.Current.ValueChanged += min => upperBound.Current.Value = Math.Max(min.NewValue + minRange, upperBound.Current.Value);
|
||||
upperBound.Current.ValueChanged += max => lowerBound.Current.Value = Math.Min(max.NewValue - minRange, lowerBound.Current.Value);
|
||||
}
|
||||
|
||||
private partial class LowerBoundSlider : BoundSlider
|
||||
{
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
LeftBox.Height = 6; // hide any colour bleeding from overlap
|
||||
|
||||
AccentColour = BackgroundColour;
|
||||
BackgroundColour = Color4.Transparent;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
base.ReceivePositionalInputAt(screenSpacePos)
|
||||
&& screenSpacePos.X <= Nub.ScreenSpaceDrawQuad.TopRight.X;
|
||||
}
|
||||
|
||||
private partial class UpperBoundSlider : BoundSlider
|
||||
{
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
RightBox.Height = 6; // just to match the left bar height really
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
base.ReceivePositionalInputAt(screenSpacePos)
|
||||
&& screenSpacePos.X >= Nub.ScreenSpaceDrawQuad.TopLeft.X;
|
||||
}
|
||||
|
||||
protected partial class BoundSlider : OsuSliderBar<double>
|
||||
{
|
||||
public string? DefaultString;
|
||||
public LocalisableString? DefaultTooltip;
|
||||
public string? TooltipSuffix;
|
||||
public float NubWidth { get; set; } = Nub.HEIGHT;
|
||||
|
||||
public override LocalisableString TooltipText =>
|
||||
(Current.IsDefault ? DefaultTooltip : Current.Value.ToString($@"0.## {TooltipSuffix}")) ?? Current.Value.ToString($@"0.## {TooltipSuffix}");
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
base.OnHover(e);
|
||||
return true; // Make sure only one nub shows hover effect at once.
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Nub.Width = NubWidth;
|
||||
RangePadding = Nub.Width / 2;
|
||||
|
||||
OsuSpriteText currentDisplay;
|
||||
|
||||
Nub.Add(currentDisplay = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = -0.5f,
|
||||
Colour = Color4.White,
|
||||
Font = OsuFont.Torus.With(size: 10),
|
||||
});
|
||||
|
||||
Current.BindValueChanged(current =>
|
||||
{
|
||||
currentDisplay.Text = (current.NewValue != Current.Default ? current.NewValue.ToString("N1") : DefaultString) ?? current.NewValue.ToString("N1");
|
||||
}, true);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider? colourProvider)
|
||||
{
|
||||
if (colourProvider == null) return;
|
||||
|
||||
AccentColour = colourProvider.Background2;
|
||||
Nub.AccentColour = colourProvider.Background2;
|
||||
Nub.GlowingAccentColour = colourProvider.Background1;
|
||||
Nub.GlowColour = colourProvider.Background2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
@@ -79,8 +80,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
Debug.Assert(triangleGradientSecondColour != null);
|
||||
|
||||
Triangles.ColourTop = triangleGradientSecondColour.Value;
|
||||
Triangles.ColourBottom = BackgroundColour;
|
||||
Triangles.Colour = ColourInfo.GradientVertical(triangleGradientSecondColour.Value, BackgroundColour);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace osu.Game.Online.API.Requests
|
||||
var req = base.CreateWebRequest();
|
||||
if (channel != null) req.AddParameter(@"channel", channel.Id.ToString());
|
||||
req.AddParameter(@"since", since.ToString());
|
||||
req.AddParameter(@"includes[]", "presence");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
@@ -16,5 +16,7 @@ namespace osu.Game.Online.API.Requests
|
||||
|
||||
[JsonProperty]
|
||||
public List<Message> Messages;
|
||||
|
||||
// TODO: Handle Silences here (will need to add to includes[] in the request).
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace osu.Game.Online.API.Requests
|
||||
Loved,
|
||||
Pending,
|
||||
Guest,
|
||||
Graveyard
|
||||
Graveyard,
|
||||
Nominated,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
||||
[JsonProperty(@"guest_beatmapset_count")]
|
||||
public int GuestBeatmapsetCount;
|
||||
|
||||
[JsonProperty(@"nominated_beatmapset_count")]
|
||||
public int NominatedBeatmapsetCount;
|
||||
|
||||
[JsonProperty(@"scores_best_count")]
|
||||
public int ScoresBestCount;
|
||||
|
||||
|
||||
@@ -529,6 +529,10 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
Logger.Log($"Joined public channel {channel}");
|
||||
joinChannel(channel, fetchInitialMessages);
|
||||
|
||||
// Required after joining public channels to mark the user as online in them.
|
||||
// Todo: Temporary workaround for https://github.com/ppy/osu-web/issues/9602
|
||||
SendAck();
|
||||
};
|
||||
req.Failure += e =>
|
||||
{
|
||||
|
||||
@@ -1,9 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
@@ -13,7 +10,6 @@ namespace osu.Game.Online.Chat
|
||||
public InfoMessage(string message)
|
||||
: base(null)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now;
|
||||
Content = message;
|
||||
|
||||
Sender = APIUser.SYSTEM_USER;
|
||||
|
||||
@@ -1,8 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class LocalEchoMessage : LocalMessage
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
@@ -13,6 +13,7 @@ namespace osu.Game.Online.Chat
|
||||
protected LocalMessage(long? id)
|
||||
: base(id)
|
||||
{
|
||||
Timestamp = DateTimeOffset.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
@@ -59,19 +60,28 @@ namespace osu.Game.Online.Chat
|
||||
/// <remarks>The <see cref="Link"/>s' <see cref="Link.Index"/> and <see cref="Link.Length"/>s are according to <see cref="DisplayContent"/></remarks>
|
||||
public List<Link> Links;
|
||||
|
||||
private static long constructionOrderStatic;
|
||||
private readonly long constructionOrder;
|
||||
|
||||
public Message(long? id)
|
||||
{
|
||||
Id = id;
|
||||
|
||||
constructionOrder = Interlocked.Increment(ref constructionOrderStatic);
|
||||
}
|
||||
|
||||
public int CompareTo(Message other)
|
||||
{
|
||||
if (!Id.HasValue)
|
||||
return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp);
|
||||
if (!other.Id.HasValue)
|
||||
return -1;
|
||||
if (Id.HasValue && other.Id.HasValue)
|
||||
return Id.Value.CompareTo(other.Id.Value);
|
||||
|
||||
return Id.Value.CompareTo(other.Id.Value);
|
||||
int timestampComparison = Timestamp.CompareTo(other.Timestamp);
|
||||
|
||||
if (timestampComparison != 0)
|
||||
return timestampComparison;
|
||||
|
||||
// Timestamp might not be accurate enough to make a stable sorting decision.
|
||||
return constructionOrder.CompareTo(other.constructionOrder);
|
||||
}
|
||||
|
||||
public virtual bool Equals(Message other)
|
||||
@@ -85,6 +95,6 @@ namespace osu.Game.Online.Chat
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}";
|
||||
public override string ToString() => $"({(Id?.ToString() ?? "null")}) {Timestamp} {Sender}: {Content}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
@@ -15,21 +19,30 @@ namespace osu.Game.Online.Chat
|
||||
public partial class NowPlayingCommand : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IChannelPostTarget channelManager { get; set; }
|
||||
private IChannelPostTarget channelManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> currentBeatmap { get; set; }
|
||||
private Bindable<WorkingBeatmap> currentBeatmap { get; set; } = null!;
|
||||
|
||||
private readonly Channel target;
|
||||
[Resolved]
|
||||
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> currentRuleset { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private LocalisationManager localisation { get; set; } = null!;
|
||||
|
||||
private readonly Channel? target;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="NowPlayingCommand"/> to post the currently-playing beatmap to a parenting <see cref="IChannelPostTarget"/>.
|
||||
/// </summary>
|
||||
/// <param name="target">The target channel to post to. If <c>null</c>, the currently-selected channel will be posted to.</param>
|
||||
public NowPlayingCommand(Channel target = null)
|
||||
public NowPlayingCommand(Channel target)
|
||||
{
|
||||
this.target = target;
|
||||
}
|
||||
@@ -59,10 +72,55 @@ namespace osu.Game.Online.Chat
|
||||
break;
|
||||
}
|
||||
|
||||
string beatmapString = beatmapInfo.OnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString();
|
||||
string[] pieces =
|
||||
{
|
||||
"is",
|
||||
verb,
|
||||
getBeatmapPart(),
|
||||
getRulesetPart(),
|
||||
getModPart(),
|
||||
};
|
||||
|
||||
channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
|
||||
channelManager.PostMessage(string.Join(' ', pieces.Where(p => !string.IsNullOrEmpty(p))), true, target);
|
||||
Expire();
|
||||
|
||||
string getBeatmapPart()
|
||||
{
|
||||
string beatmapInfoString = localisation.GetLocalisedBindableString(beatmapInfo.GetDisplayTitleRomanisable()).Value;
|
||||
|
||||
return beatmapInfo.OnlineID > 0 ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfoString}]" : beatmapInfoString;
|
||||
}
|
||||
|
||||
string getRulesetPart()
|
||||
{
|
||||
if (api.Activity.Value is not UserActivity.InGame) return string.Empty;
|
||||
|
||||
return $"<{currentRuleset.Value.Name}>";
|
||||
}
|
||||
|
||||
string getModPart()
|
||||
{
|
||||
if (api.Activity.Value is not UserActivity.InGame) return string.Empty;
|
||||
|
||||
if (selectedMods.Value.Count == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
StringBuilder modsString = new StringBuilder();
|
||||
|
||||
foreach (var mod in selectedMods.Value.Where(mod => mod.Type == ModType.DifficultyIncrease))
|
||||
{
|
||||
modsString.Append($"+{mod.Acronym} ");
|
||||
}
|
||||
|
||||
foreach (var mod in selectedMods.Value.Where(mod => mod.Type != ModType.DifficultyIncrease))
|
||||
{
|
||||
modsString.Append($"-{mod.Acronym} ");
|
||||
}
|
||||
|
||||
return modsString.ToString().Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
protected partial class StandAloneMessage : ChatLine
|
||||
{
|
||||
protected override float TextSize => 15;
|
||||
protected override float FontSize => 15;
|
||||
protected override float Spacing => 5;
|
||||
protected override float UsernameWidth => 75;
|
||||
|
||||
|
||||
@@ -33,11 +33,11 @@ namespace osu.Game.Online.Notifications
|
||||
|
||||
public override Task ConnectAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
API.Queue(CreateFetchMessagesRequest(0));
|
||||
API.Queue(CreateInitialFetchRequest(0));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected APIRequest CreateFetchMessagesRequest(long? lastMessageId = null)
|
||||
protected APIRequest CreateInitialFetchRequest(long? lastMessageId = null)
|
||||
{
|
||||
var fetchReq = new GetUpdatesRequest(lastMessageId ?? this.lastMessageId);
|
||||
|
||||
@@ -67,8 +67,11 @@ namespace osu.Game.Online.Notifications
|
||||
|
||||
protected void HandleChannelParted(Channel channel) => ChannelParted?.Invoke(channel);
|
||||
|
||||
protected void HandleMessages(List<Message> messages)
|
||||
protected void HandleMessages(List<Message>? messages)
|
||||
{
|
||||
if (messages == null)
|
||||
return;
|
||||
|
||||
NewMessages?.Invoke(messages);
|
||||
lastMessageId = Math.Max(lastMessageId, messages.LastOrDefault()?.Id ?? 0);
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace osu.Game.Online
|
||||
await disconnect(true);
|
||||
|
||||
if (ex != null)
|
||||
await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false);
|
||||
await handleErrorAndDelay(ex, CancellationToken.None).ConfigureAwait(false);
|
||||
else
|
||||
Logger.Log($"{ClientName} disconnected", LoggingTarget.Network);
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
// TODO: Remove disable below after merging https://github.com/ppy/osu-framework/pull/5548 and applying follow-up changes game-side.
|
||||
// ReSharper disable once PartialTypeWithSinglePart
|
||||
public partial class APICreatedRoom : Room
|
||||
{
|
||||
[JsonProperty("error")]
|
||||
|
||||
@@ -16,7 +16,7 @@ using osu.Game.Online.Rooms.RoomStatuses;
|
||||
namespace osu.Game.Online.Rooms
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public partial class Room
|
||||
public partial class Room : IDependencyInjectionCandidate
|
||||
{
|
||||
[Cached]
|
||||
[JsonProperty("id")]
|
||||
|
||||
@@ -15,8 +15,9 @@ namespace osu.Game.Online.Spectator
|
||||
/// <summary>
|
||||
/// Signal the start of a new play session.
|
||||
/// </summary>
|
||||
/// <param name="scoreToken">The score submission token.</param>
|
||||
/// <param name="state">The state of gameplay.</param>
|
||||
Task BeginPlaySession(SpectatorState state);
|
||||
Task BeginPlaySession(long? scoreToken, SpectatorState state);
|
||||
|
||||
/// <summary>
|
||||
/// Send a bundle of frame data for the current play session.
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace osu.Game.Online.Spectator
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task BeginPlayingInternal(SpectatorState state)
|
||||
protected override async Task BeginPlayingInternal(long? scoreToken, SpectatorState state)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return;
|
||||
@@ -56,7 +56,7 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
try
|
||||
{
|
||||
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state);
|
||||
await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), scoreToken, state);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@@ -65,7 +65,7 @@ namespace osu.Game.Online.Spectator
|
||||
Debug.Assert(connector != null);
|
||||
|
||||
await connector.Reconnect();
|
||||
await BeginPlayingInternal(state);
|
||||
await BeginPlayingInternal(scoreToken, state);
|
||||
}
|
||||
|
||||
// Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart.
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
private IBeatmap? currentBeatmap;
|
||||
private Score? currentScore;
|
||||
private long? currentScoreToken;
|
||||
|
||||
private readonly Queue<FrameDataBundle> pendingFrameBundles = new Queue<FrameDataBundle>();
|
||||
|
||||
@@ -108,7 +109,7 @@ namespace osu.Game.Online.Spectator
|
||||
// re-send state in case it wasn't received
|
||||
if (IsPlaying)
|
||||
// TODO: this is likely sent out of order after a reconnect scenario. needs further consideration.
|
||||
BeginPlayingInternal(currentState);
|
||||
BeginPlayingInternal(currentScoreToken, currentState);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -159,7 +160,7 @@ namespace osu.Game.Online.Spectator
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void BeginPlaying(GameplayState state, Score score)
|
||||
public void BeginPlaying(long? scoreToken, GameplayState state, Score score)
|
||||
{
|
||||
// This schedule is only here to match the one below in `EndPlaying`.
|
||||
Schedule(() =>
|
||||
@@ -174,12 +175,13 @@ namespace osu.Game.Online.Spectator
|
||||
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
||||
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
||||
currentState.State = SpectatedUserState.Playing;
|
||||
currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues;
|
||||
currentState.MaximumStatistics = state.ScoreProcessor.MaximumStatistics;
|
||||
|
||||
currentBeatmap = state.Beatmap;
|
||||
currentScore = score;
|
||||
currentScoreToken = scoreToken;
|
||||
|
||||
BeginPlayingInternal(currentState);
|
||||
BeginPlayingInternal(currentScoreToken, currentState);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -264,7 +266,7 @@ namespace osu.Game.Online.Spectator
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract Task BeginPlayingInternal(SpectatorState state);
|
||||
protected abstract Task BeginPlayingInternal(long? scoreToken, SpectatorState state);
|
||||
|
||||
protected abstract Task SendFramesInternal(FrameDataBundle bundle);
|
||||
|
||||
|
||||
@@ -152,12 +152,12 @@ namespace osu.Game.Online.Spectator
|
||||
|
||||
scoreInfo.MaxCombo = frame.Header.MaxCombo;
|
||||
scoreInfo.Statistics = frame.Header.Statistics;
|
||||
scoreInfo.MaximumStatistics = spectatorState.MaximumStatistics;
|
||||
|
||||
Accuracy.Value = frame.Header.Accuracy;
|
||||
Combo.Value = frame.Header.Combo;
|
||||
|
||||
scoreProcessor.ExtractScoringValues(frame.Header, out var currentScoringValues, out _);
|
||||
TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, currentScoringValues, spectatorState.MaximumScoringValues);
|
||||
TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, scoreInfo);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using MessagePack;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Online.Spectator
|
||||
{
|
||||
@@ -31,7 +31,7 @@ namespace osu.Game.Online.Spectator
|
||||
public SpectatedUserState State { get; set; }
|
||||
|
||||
[Key(4)]
|
||||
public ScoringValues MaximumScoringValues { get; set; }
|
||||
public Dictionary<HitResult, int> MaximumStatistics { get; set; } = new Dictionary<HitResult, int>();
|
||||
|
||||
public bool Equals(SpectatorState other)
|
||||
{
|
||||
|
||||
@@ -68,11 +68,15 @@ namespace osu.Game.Overlays.Changelog
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Direction = FillDirection.Vertical,
|
||||
Margin = new MarginPadding { Top = 20 },
|
||||
Children = new Drawable[]
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
new OsuHoverContainer
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Child = new OsuHoverContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -104,27 +103,29 @@ namespace osu.Game.Overlays.Changelog
|
||||
{
|
||||
var fill = base.CreateHeader();
|
||||
|
||||
foreach (var existing in fill.Children.OfType<OsuHoverContainer>())
|
||||
var nestedFill = (FillFlowContainer)fill.Child;
|
||||
|
||||
var buildDisplay = (OsuHoverContainer)nestedFill.Child;
|
||||
|
||||
buildDisplay.Scale = new Vector2(1.25f);
|
||||
buildDisplay.Action = null;
|
||||
|
||||
fill.Add(date = new OsuSpriteText
|
||||
{
|
||||
existing.Scale = new Vector2(1.25f);
|
||||
existing.Action = null;
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14),
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Scale = new Vector2(1.25f),
|
||||
});
|
||||
|
||||
existing.Add(date = new OsuSpriteText
|
||||
{
|
||||
Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14),
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
});
|
||||
}
|
||||
|
||||
fill.Insert(-1, new NavigationIconButton(Build.Versions?.Previous)
|
||||
nestedFill.Insert(-1, new NavigationIconButton(Build.Versions?.Previous)
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronLeft,
|
||||
SelectBuild = b => SelectBuild(b)
|
||||
});
|
||||
fill.Insert(1, new NavigationIconButton(Build.Versions?.Next)
|
||||
nestedFill.Insert(1, new NavigationIconButton(Build.Versions?.Next)
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
SelectBuild = b => SelectBuild(b)
|
||||
|
||||
@@ -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.Linq;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
@@ -9,25 +8,20 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public partial class ChatLine : CompositeDrawable
|
||||
{
|
||||
private Message message = null!;
|
||||
|
||||
public Message Message
|
||||
{
|
||||
get => message;
|
||||
@@ -44,49 +38,35 @@ namespace osu.Game.Overlays.Chat
|
||||
}
|
||||
}
|
||||
|
||||
public LinkFlowContainer ContentFlow { get; private set; } = null!;
|
||||
public IReadOnlyCollection<Drawable> DrawableContentFlow => drawableContentFlow;
|
||||
|
||||
protected virtual float TextSize => 20;
|
||||
protected virtual float FontSize => 20;
|
||||
|
||||
protected virtual float Spacing => 15;
|
||||
|
||||
protected virtual float UsernameWidth => 130;
|
||||
|
||||
private Color4 usernameColour;
|
||||
|
||||
private OsuSpriteText timestamp = null!;
|
||||
|
||||
private Message message = null!;
|
||||
|
||||
private OsuSpriteText username = null!;
|
||||
|
||||
private Container? highlight;
|
||||
|
||||
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
|
||||
|
||||
private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour);
|
||||
|
||||
private bool messageHasColour => Message.IsAction && senderHasColour;
|
||||
|
||||
[Resolved]
|
||||
private ChannelManager? chatManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
private OverlayColourProvider? colourProvider { get; set; }
|
||||
|
||||
private readonly OsuSpriteText drawableTimestamp;
|
||||
|
||||
private readonly DrawableUsername drawableUsername;
|
||||
|
||||
private readonly LinkFlowContainer drawableContentFlow;
|
||||
|
||||
private readonly Bindable<bool> prefer24HourTime = new Bindable<bool>();
|
||||
|
||||
private Container? highlight;
|
||||
|
||||
public ChatLine(Message message)
|
||||
{
|
||||
Message = message;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider? colourProvider, OsuConfigManager configManager)
|
||||
{
|
||||
usernameColour = senderHasColour
|
||||
? Color4Extensions.FromHex(message.Sender.Colour)
|
||||
: username_colours[message.Sender.Id % username_colours.Length];
|
||||
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
@@ -103,30 +83,24 @@ namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
timestamp = new OsuSpriteText
|
||||
drawableTimestamp = new OsuSpriteText
|
||||
{
|
||||
Shadow = false,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
Colour = colourProvider?.Background1 ?? Colour4.White,
|
||||
Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
new MessageSender(message.Sender)
|
||||
drawableUsername = new DrawableUsername(message.Sender)
|
||||
{
|
||||
Width = UsernameWidth,
|
||||
FontSize = FontSize,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
Child = createUsername(),
|
||||
Margin = new MarginPadding { Horizontal = Spacing },
|
||||
},
|
||||
ContentFlow = new LinkFlowContainer(t =>
|
||||
{
|
||||
t.Shadow = false;
|
||||
t.Font = t.Font.With(size: TextSize, italics: Message.IsAction);
|
||||
t.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White;
|
||||
})
|
||||
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -134,18 +108,23 @@ namespace osu.Game.Overlays.Chat
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager configManager)
|
||||
{
|
||||
configManager.BindWith(OsuSetting.Prefer24HourTime, prefer24HourTime);
|
||||
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
drawableTimestamp.Colour = colourProvider?.Background1 ?? Colour4.White;
|
||||
|
||||
updateMessageContent();
|
||||
FinishTransforms(true);
|
||||
|
||||
prefer24HourTime.BindValueChanged(_ => updateTimestamp());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,7 +139,7 @@ namespace osu.Game.Overlays.Chat
|
||||
CornerRadius = 2f,
|
||||
Masking = true,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = usernameColour.Darken(1f),
|
||||
Colour = drawableUsername.AccentColour.Darken(1f),
|
||||
Depth = float.MaxValue,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||
});
|
||||
@@ -170,166 +149,35 @@ namespace osu.Game.Overlays.Chat
|
||||
highlight.Expire();
|
||||
}
|
||||
|
||||
private void styleMessageContent(SpriteText text)
|
||||
{
|
||||
text.Shadow = false;
|
||||
text.Font = text.Font.With(size: FontSize, italics: Message.IsAction);
|
||||
|
||||
bool messageHasColour = Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour);
|
||||
text.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White;
|
||||
}
|
||||
|
||||
private void updateMessageContent()
|
||||
{
|
||||
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
||||
timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
|
||||
drawableTimestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
|
||||
|
||||
updateTimestamp();
|
||||
username.Text = $@"{message.Sender.Username}";
|
||||
drawableUsername.Text = $@"{message.Sender.Username}";
|
||||
|
||||
// remove non-existent channels from the link list
|
||||
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true);
|
||||
|
||||
ContentFlow.Clear();
|
||||
ContentFlow.AddLinks(message.DisplayContent, message.Links);
|
||||
drawableContentFlow.Clear();
|
||||
drawableContentFlow.AddLinks(message.DisplayContent, message.Links);
|
||||
}
|
||||
|
||||
private void updateTimestamp()
|
||||
{
|
||||
timestamp.Text = prefer24HourTime.Value
|
||||
drawableTimestamp.Text = prefer24HourTime.Value
|
||||
? $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"
|
||||
: $@"{message.Timestamp.LocalDateTime:hh:mm:ss tt}";
|
||||
}
|
||||
|
||||
private Drawable createUsername()
|
||||
{
|
||||
username = new OsuSpriteText
|
||||
{
|
||||
Shadow = false,
|
||||
Colour = senderHasColour ? colours.ChatBlue : usernameColour,
|
||||
Truncate = true,
|
||||
EllipsisString = "…",
|
||||
Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
MaxWidth = UsernameWidth,
|
||||
};
|
||||
|
||||
if (!senderHasColour)
|
||||
return username;
|
||||
|
||||
// Background effect
|
||||
return new Container
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 4,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Roundness = 1,
|
||||
Radius = 1,
|
||||
Colour = Color4.Black.Opacity(0.3f),
|
||||
Offset = new Vector2(0, 1),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 4,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = usernameColour,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 },
|
||||
Child = username
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private partial class MessageSender : OsuClickableContainer, IHasContextMenu
|
||||
{
|
||||
private readonly APIUser sender;
|
||||
|
||||
private Action startChatAction = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
public MessageSender(APIUser sender)
|
||||
{
|
||||
this.sender = sender;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(UserProfileOverlay? profile, ChannelManager? chatManager, ChatOverlay? chatOverlay)
|
||||
{
|
||||
Action = () => profile?.ShowUser(sender);
|
||||
startChatAction = () =>
|
||||
{
|
||||
chatManager?.OpenPrivateChannel(sender);
|
||||
chatOverlay?.Show();
|
||||
};
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
if (sender.Equals(APIUser.SYSTEM_USER))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
List<MenuItem> items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action)
|
||||
};
|
||||
|
||||
if (!sender.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Color4[] username_colours =
|
||||
{
|
||||
Color4Extensions.FromHex("588c7e"),
|
||||
Color4Extensions.FromHex("b2a367"),
|
||||
Color4Extensions.FromHex("c98f65"),
|
||||
Color4Extensions.FromHex("bc5151"),
|
||||
Color4Extensions.FromHex("5c8bd6"),
|
||||
Color4Extensions.FromHex("7f6ab7"),
|
||||
Color4Extensions.FromHex("a368ad"),
|
||||
Color4Extensions.FromHex("aa6880"),
|
||||
|
||||
Color4Extensions.FromHex("6fad9b"),
|
||||
Color4Extensions.FromHex("f2e394"),
|
||||
Color4Extensions.FromHex("f2ae72"),
|
||||
Color4Extensions.FromHex("f98f8a"),
|
||||
Color4Extensions.FromHex("7daef4"),
|
||||
Color4Extensions.FromHex("a691f2"),
|
||||
Color4Extensions.FromHex("c894d3"),
|
||||
Color4Extensions.FromHex("d895b0"),
|
||||
|
||||
Color4Extensions.FromHex("53c4a1"),
|
||||
Color4Extensions.FromHex("eace5c"),
|
||||
Color4Extensions.FromHex("ea8c47"),
|
||||
Color4Extensions.FromHex("fc4f4f"),
|
||||
Color4Extensions.FromHex("3d94ea"),
|
||||
Color4Extensions.FromHex("7760ea"),
|
||||
Color4Extensions.FromHex("af52c6"),
|
||||
Color4Extensions.FromHex("e25696"),
|
||||
|
||||
Color4Extensions.FromHex("677c66"),
|
||||
Color4Extensions.FromHex("9b8732"),
|
||||
Color4Extensions.FromHex("8c5129"),
|
||||
Color4Extensions.FromHex("8c3030"),
|
||||
Color4Extensions.FromHex("1f5d91"),
|
||||
Color4Extensions.FromHex("4335a5"),
|
||||
Color4Extensions.FromHex("812a96"),
|
||||
Color4Extensions.FromHex("992861"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public partial class DrawableUsername : OsuClickableContainer, IHasContextMenu
|
||||
{
|
||||
public Color4 AccentColour { get; }
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
colouredDrawable.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
public float FontSize
|
||||
{
|
||||
set => drawableText.Font = OsuFont.GetFont(size: value, weight: FontWeight.Bold, italics: true);
|
||||
}
|
||||
|
||||
public LocalisableString Text
|
||||
{
|
||||
set => drawableText.Text = value;
|
||||
}
|
||||
|
||||
public override float Width
|
||||
{
|
||||
get => base.Width;
|
||||
set => base.Width = drawableText.MaxWidth = value;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private ChannelManager? chatManager { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private ChatOverlay? chatOverlay { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private UserProfileOverlay? profileOverlay { get; set; }
|
||||
|
||||
private readonly APIUser user;
|
||||
private readonly OsuSpriteText drawableText;
|
||||
|
||||
private readonly Drawable colouredDrawable;
|
||||
|
||||
public DrawableUsername(APIUser user)
|
||||
{
|
||||
this.user = user;
|
||||
|
||||
Action = openUserProfile;
|
||||
|
||||
drawableText = new OsuSpriteText
|
||||
{
|
||||
Shadow = false,
|
||||
Truncate = true,
|
||||
EllipsisString = "…",
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(user.Colour))
|
||||
{
|
||||
AccentColour = default_colours[user.Id % default_colours.Length];
|
||||
|
||||
Add(colouredDrawable = drawableText);
|
||||
}
|
||||
else
|
||||
{
|
||||
AccentColour = Color4Extensions.FromHex(user.Colour);
|
||||
|
||||
Add(new Container
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 4,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Roundness = 1,
|
||||
Radius = 1,
|
||||
Colour = Color4.Black.Opacity(0.3f),
|
||||
Offset = new Vector2(0, 1),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
},
|
||||
Child = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 4,
|
||||
Children = new[]
|
||||
{
|
||||
colouredDrawable = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 },
|
||||
Child = drawableText,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
drawableText.Colour = colours.ChatBlue;
|
||||
colouredDrawable.Colour = AccentColour;
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
if (user.Equals(APIUser.SYSTEM_USER))
|
||||
return Array.Empty<MenuItem>();
|
||||
|
||||
List<MenuItem> items = new List<MenuItem>
|
||||
{
|
||||
new OsuMenuItem("View Profile", MenuItemType.Highlighted, openUserProfile)
|
||||
};
|
||||
|
||||
if (!user.Equals(api.LocalUser.Value))
|
||||
items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, openUserChannel));
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void openUserChannel()
|
||||
{
|
||||
chatManager?.OpenPrivateChannel(user);
|
||||
chatOverlay?.Show();
|
||||
}
|
||||
|
||||
private void openUserProfile()
|
||||
{
|
||||
profileOverlay?.ShowUser(user);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
colouredDrawable.FadeColour(AccentColour.Lighten(0.6f), 30, Easing.OutQuint);
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
|
||||
colouredDrawable.FadeColour(AccentColour, 800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private static readonly Color4[] default_colours =
|
||||
{
|
||||
Color4Extensions.FromHex("588c7e"),
|
||||
Color4Extensions.FromHex("b2a367"),
|
||||
Color4Extensions.FromHex("c98f65"),
|
||||
Color4Extensions.FromHex("bc5151"),
|
||||
Color4Extensions.FromHex("5c8bd6"),
|
||||
Color4Extensions.FromHex("7f6ab7"),
|
||||
Color4Extensions.FromHex("a368ad"),
|
||||
Color4Extensions.FromHex("aa6880"),
|
||||
|
||||
Color4Extensions.FromHex("6fad9b"),
|
||||
Color4Extensions.FromHex("f2e394"),
|
||||
Color4Extensions.FromHex("f2ae72"),
|
||||
Color4Extensions.FromHex("f98f8a"),
|
||||
Color4Extensions.FromHex("7daef4"),
|
||||
Color4Extensions.FromHex("a691f2"),
|
||||
Color4Extensions.FromHex("c894d3"),
|
||||
Color4Extensions.FromHex("d895b0"),
|
||||
|
||||
Color4Extensions.FromHex("53c4a1"),
|
||||
Color4Extensions.FromHex("eace5c"),
|
||||
Color4Extensions.FromHex("ea8c47"),
|
||||
Color4Extensions.FromHex("fc4f4f"),
|
||||
Color4Extensions.FromHex("3d94ea"),
|
||||
Color4Extensions.FromHex("7760ea"),
|
||||
Color4Extensions.FromHex("af52c6"),
|
||||
Color4Extensions.FromHex("e25696"),
|
||||
|
||||
Color4Extensions.FromHex("677c66"),
|
||||
Color4Extensions.FromHex("9b8732"),
|
||||
Color4Extensions.FromHex("8c5129"),
|
||||
Color4Extensions.FromHex("8c3030"),
|
||||
Color4Extensions.FromHex("1f5d91"),
|
||||
Color4Extensions.FromHex("4335a5"),
|
||||
Color4Extensions.FromHex("812a96"),
|
||||
Color4Extensions.FromHex("992861"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
loading.Hide();
|
||||
tick.FadeIn(500, Easing.OutQuint);
|
||||
|
||||
Background.FadeColour(colours.Green, 500, Easing.OutQuint);
|
||||
this.TransformTo(nameof(BackgroundColour), colours.Green, 500, Easing.OutQuint);
|
||||
progressBar.FillColour = colours.Green;
|
||||
|
||||
this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint);
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
return parentDependencies.Get(type, info);
|
||||
}
|
||||
|
||||
public void Inject<T>(T instance) where T : class
|
||||
public void Inject<T>(T instance) where T : class, IDependencyInjectionCandidate
|
||||
{
|
||||
parentDependencies.Inject(instance);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps
|
||||
case BeatmapSetType.Guest:
|
||||
return user.GuestBeatmapsetCount;
|
||||
|
||||
case BeatmapSetType.Nominated:
|
||||
return user.NominatedBeatmapsetCount;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace osu.Game.Overlays.Profile.Sections
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, UsersStrings.ShowExtraBeatmapsLovedTitle),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Guest, User, UsersStrings.ShowExtraBeatmapsGuestTitle),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Pending, User, UsersStrings.ShowExtraBeatmapsPendingTitle),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle)
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle),
|
||||
new PaginatedBeatmapContainer(BeatmapSetType.Nominated, User, UsersStrings.ShowExtraBeatmapsNominatedTitle),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,13 +177,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
updateScreenModeWarning();
|
||||
}, true);
|
||||
|
||||
windowModes.BindCollectionChanged((_, _) =>
|
||||
{
|
||||
if (windowModes.Count > 1)
|
||||
windowModeDropdown.Show();
|
||||
else
|
||||
windowModeDropdown.Hide();
|
||||
}, true);
|
||||
windowModes.BindCollectionChanged((_, _) => updateDisplaySettingsVisibility());
|
||||
|
||||
currentDisplay.BindValueChanged(display => Schedule(() =>
|
||||
{
|
||||
@@ -219,7 +213,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint);
|
||||
|
||||
scalingSettings.AutoSizeAxes = scalingMode.Value != ScalingMode.Off ? Axes.Y : Axes.None;
|
||||
scalingSettings.ForEach(s => s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything);
|
||||
scalingSettings.ForEach(s =>
|
||||
{
|
||||
s.TransferValueOnCommit = scalingMode.Value == ScalingMode.Everything;
|
||||
s.CanBeShown.Value = scalingMode.Value != ScalingMode.Off;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,20 +232,10 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||
|
||||
private void updateDisplaySettingsVisibility()
|
||||
{
|
||||
if (resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
||||
resolutionDropdown.Show();
|
||||
else
|
||||
resolutionDropdown.Hide();
|
||||
|
||||
if (displayDropdown.Items.Count() > 1)
|
||||
displayDropdown.Show();
|
||||
else
|
||||
displayDropdown.Hide();
|
||||
|
||||
if (host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero)
|
||||
safeAreaConsiderationsCheckbox.Show();
|
||||
else
|
||||
safeAreaConsiderationsCheckbox.Hide();
|
||||
windowModeDropdown.CanBeShown.Value = windowModes.Count > 1;
|
||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||
}
|
||||
|
||||
private void updateScreenModeWarning()
|
||||
|
||||
@@ -143,6 +143,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
areaOffset.SetDefault();
|
||||
areaSize.SetDefault();
|
||||
},
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsButton
|
||||
{
|
||||
@@ -150,25 +151,29 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
Action = () =>
|
||||
{
|
||||
forceAspectRatio((float)host.Window.ClientSize.Width / host.Window.ClientSize.Height);
|
||||
}
|
||||
},
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = TabletSettingsStrings.XOffset,
|
||||
Current = offsetX
|
||||
Current = offsetX,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = TabletSettingsStrings.YOffset,
|
||||
Current = offsetY
|
||||
Current = offsetY,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = TabletSettingsStrings.Rotation,
|
||||
Current = rotation
|
||||
Current = rotation,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new RotationPresetButtons(tabletHandler)
|
||||
{
|
||||
@@ -181,24 +186,28 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = TabletSettingsStrings.AspectRatio,
|
||||
Current = aspectRatio
|
||||
Current = aspectRatio,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = TabletSettingsStrings.LockAspectRatio,
|
||||
Current = aspectLock
|
||||
Current = aspectLock,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = CommonStrings.Width,
|
||||
Current = sizeX
|
||||
Current = sizeX,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
new SettingsSlider<float>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
LabelText = CommonStrings.Height,
|
||||
Current = sizeY
|
||||
Current = sizeY,
|
||||
CanBeShown = { BindTarget = enabled }
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public partial class SettingsButton : RoundedButton, IHasTooltip
|
||||
public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable
|
||||
{
|
||||
public SettingsButton()
|
||||
{
|
||||
@@ -20,6 +22,9 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
|
||||
public BindableBool CanBeShown { get; } = new BindableBool(true);
|
||||
IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;
|
||||
|
||||
public override IEnumerable<LocalisableString> FilterTerms
|
||||
{
|
||||
get
|
||||
|
||||
@@ -22,7 +22,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public abstract partial class SettingsItem<T> : Container, IFilterable, ISettingsItem, IHasCurrentValue<T>, IHasTooltip
|
||||
public abstract partial class SettingsItem<T> : Container, IConditionalFilterable, ISettingsItem, IHasCurrentValue<T>, IHasTooltip
|
||||
{
|
||||
protected abstract Drawable CreateControl();
|
||||
|
||||
@@ -144,6 +144,9 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
|
||||
public BindableBool CanBeShown { get; } = new BindableBool(true);
|
||||
IBindable<bool> IConditionalFilterable.CanBeShown => CanBeShown;
|
||||
|
||||
public event Action SettingChanged;
|
||||
|
||||
private T classicDefault;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -19,7 +18,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
public abstract partial class SettingsSection : Container, IHasFilterableChildren
|
||||
public abstract partial class SettingsSection : Container, IFilterable
|
||||
{
|
||||
protected FillFlowContainer FlowContent;
|
||||
protected override Container<Drawable> Content => FlowContent;
|
||||
@@ -33,7 +32,6 @@ namespace osu.Game.Overlays.Settings
|
||||
public abstract Drawable CreateIcon();
|
||||
public abstract LocalisableString Header { get; }
|
||||
|
||||
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
|
||||
public virtual IEnumerable<LocalisableString> FilterTerms => new[] { Header };
|
||||
|
||||
public const int ITEM_SPACING = 14;
|
||||
|
||||
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
@@ -17,7 +16,7 @@ using osu.Game.Graphics;
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
[ExcludeFromDynamicCompile]
|
||||
public abstract partial class SettingsSubsection : FillFlowContainer, IHasFilterableChildren
|
||||
public abstract partial class SettingsSubsection : FillFlowContainer, IFilterable
|
||||
{
|
||||
protected override Container<Drawable> Content => FlowContent;
|
||||
|
||||
@@ -25,8 +24,6 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
protected abstract LocalisableString Header { get; }
|
||||
|
||||
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();
|
||||
|
||||
public virtual IEnumerable<LocalisableString> FilterTerms => new[] { Header };
|
||||
|
||||
public bool MatchingFilter
|
||||
|
||||
@@ -205,7 +205,9 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override UserTrackingScrollContainer CreateScrollContainer() => new OverlayScrollContainer();
|
||||
|
||||
protected override FlowContainer<ProfileSection> CreateScrollContentContainer() => new FillFlowContainer<ProfileSection>
|
||||
// Reverse child ID is required so expanding beatmap panels can appear above sections below them.
|
||||
// This can also be done by setting Depth when adding new sections above if using ReverseChildID turns out to have any issues.
|
||||
protected override FlowContainer<ProfileSection> CreateScrollContentContainer() => new ReverseChildIDFillFlowContainer<ProfileSection>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
|
||||
@@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Configuration
|
||||
{
|
||||
var setting = r.All<RealmRulesetSetting>().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString());
|
||||
|
||||
setting.Value = ConfigStore[c].ToString();
|
||||
setting.Value = ConfigStore[c].ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -89,7 +91,7 @@ namespace osu.Game.Rulesets.Configuration
|
||||
setting = new RealmRulesetSetting
|
||||
{
|
||||
Key = lookup.ToString(),
|
||||
Value = bindable.Value.ToString(),
|
||||
Value = bindable.ToString(CultureInfo.InvariantCulture),
|
||||
RulesetName = rulesetName,
|
||||
Variant = variant,
|
||||
};
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
protected IRulesetConfigManager Config { get; private set; }
|
||||
|
||||
protected readonly Ruleset Ruleset;
|
||||
|
||||
// Provides `Playfield`
|
||||
private DependencyContainer dependencies;
|
||||
|
||||
@@ -74,8 +72,8 @@ namespace osu.Game.Rulesets.Edit
|
||||
private IBindable<bool> hasTiming;
|
||||
|
||||
protected HitObjectComposer(Ruleset ruleset)
|
||||
: base(ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||
@@ -419,8 +417,11 @@ namespace osu.Game.Rulesets.Edit
|
||||
[Cached]
|
||||
public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
|
||||
{
|
||||
protected HitObjectComposer()
|
||||
public readonly Ruleset Ruleset;
|
||||
|
||||
protected HitObjectComposer(Ruleset ruleset)
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace osu.Game.Rulesets.Edit
|
||||
{
|
||||
public interface IBeatSnapProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Snaps a duration to the closest beat of a timing point applicable at the reference time.
|
||||
/// Snaps a duration to the closest beat of a timing point applicable at the reference time, factoring in the current <see cref="BeatDivisor"/>.
|
||||
/// </summary>
|
||||
/// <param name="time">The time to snap.</param>
|
||||
/// <param name="referenceTime">An optional reference point to use for timing point lookup.</param>
|
||||
@@ -16,10 +14,10 @@ namespace osu.Game.Rulesets.Edit
|
||||
double SnapTime(double time, double? referenceTime = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get the most appropriate beat length at a given time.
|
||||
/// Get the most appropriate beat length at a given time, pre-divided by <see cref="BeatDivisor"/>.
|
||||
/// </summary>
|
||||
/// <param name="referenceTime">A reference time used for lookup.</param>
|
||||
/// <returns>The most appropriate beat length.</returns>
|
||||
/// <returns>The most appropriate beat length, divided by <see cref="BeatDivisor"/>.</returns>
|
||||
double GetBeatLengthAtTime(double referenceTime);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -118,11 +118,18 @@ namespace osu.Game.Rulesets.Mods
|
||||
if (!(them is DifficultyBindable otherDifficultyBindable))
|
||||
throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}.");
|
||||
|
||||
// ensure that MaxValue and ExtendedMaxValue are copied across first before continuing.
|
||||
// not doing so may cause the value of CurrentNumber to be truncated to 10.
|
||||
otherDifficultyBindable.CopyTo(this);
|
||||
|
||||
// set up mutual binding for ExtendedLimits to correctly set the upper bound of CurrentNumber.
|
||||
ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits;
|
||||
|
||||
// the actual values need to be copied after the max value constraints.
|
||||
// set up mutual binding for CurrentNumber. this must happen after all of the above.
|
||||
CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber;
|
||||
|
||||
// finish up the binding by setting up weak references via the base call.
|
||||
// unfortunately this will call `.CopyTo()` again, but fixing that is problematic and messy.
|
||||
base.BindTo(them);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mods
|
||||
@@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
SpeedChange.SetDefault();
|
||||
|
||||
double firstObjectStart = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0;
|
||||
double lastObjectEnd = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 0;
|
||||
double lastObjectEnd = beatmap.HitObjects.Any() ? beatmap.GetLastObjectTime() : 0;
|
||||
|
||||
beginRampTime = firstObjectStart;
|
||||
finalRateTime = firstObjectStart + FINAL_RATE_PROGRESS * (lastObjectEnd - firstObjectStart);
|
||||
|
||||
@@ -27,11 +27,8 @@ namespace osu.Game.Rulesets.Objects
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return;
|
||||
|
||||
HitObject firstObject = beatmap.HitObjects.First();
|
||||
HitObject lastObject = beatmap.HitObjects.Last();
|
||||
|
||||
double firstHitTime = firstObject.StartTime;
|
||||
double lastHitTime = 1 + lastObject.GetEndTime();
|
||||
double firstHitTime = beatmap.HitObjects.First().StartTime;
|
||||
double lastHitTime = 1 + beatmap.GetLastObjectTime();
|
||||
|
||||
var timingPoints = beatmap.ControlPointInfo.TimingPoints;
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
comboColourBrightness.BindValueChanged(_ => UpdateComboColour());
|
||||
|
||||
// Apply transforms
|
||||
updateState(State.Value, true);
|
||||
updateStateFromResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,12 +266,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
// If not loaded, the state update happens in LoadComplete().
|
||||
if (IsLoaded)
|
||||
{
|
||||
if (Result.IsHit)
|
||||
updateState(ArmedState.Hit, true);
|
||||
else if (Result.HasResult)
|
||||
updateState(ArmedState.Miss, true);
|
||||
else
|
||||
updateState(ArmedState.Idle, true);
|
||||
updateStateFromResult();
|
||||
|
||||
// Combo colour may have been applied via a bindable flow while no object entry was attached.
|
||||
// Update here to ensure we're in a good state.
|
||||
@@ -279,6 +274,16 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStateFromResult()
|
||||
{
|
||||
if (Result.IsHit)
|
||||
updateState(ArmedState.Hit, true);
|
||||
else if (Result.HasResult)
|
||||
updateState(ArmedState.Miss, true);
|
||||
else
|
||||
updateState(ArmedState.Idle, true);
|
||||
}
|
||||
|
||||
protected sealed override void OnFree(HitObjectLifetimeEntry entry)
|
||||
{
|
||||
StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable);
|
||||
|
||||
@@ -10,7 +10,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@@ -90,17 +89,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private readonly double accuracyPortion;
|
||||
private readonly double comboPortion;
|
||||
|
||||
/// <summary>
|
||||
/// Scoring values for a perfect play.
|
||||
/// </summary>
|
||||
public ScoringValues MaximumScoringValues
|
||||
public Dictionary<HitResult, int> MaximumStatistics
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!beatmapApplied)
|
||||
throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}.");
|
||||
throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}.");
|
||||
|
||||
return maximumScoringValues;
|
||||
return new Dictionary<HitResult, int>(maximumResultCounts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +264,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
private void updateScore()
|
||||
{
|
||||
Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1;
|
||||
TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues);
|
||||
TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -285,7 +281,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
// We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap.
|
||||
extractScoringValues(scoreInfo.Statistics, out var current, out var maximum);
|
||||
|
||||
return maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1;
|
||||
return maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -303,9 +299,9 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
|
||||
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
|
||||
|
||||
ExtractScoringValues(scoreInfo, out var current, out var maximum);
|
||||
extractScoringValues(scoreInfo, out var current, out var maximum);
|
||||
|
||||
return ComputeScore(mode, current, maximum);
|
||||
return computeScore(mode, current, maximum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -316,7 +312,7 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// <param name="maximum">The maximum scoring values.</param>
|
||||
/// <returns>The total score computed from the given scoring values.</returns>
|
||||
[Pure]
|
||||
public long ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum)
|
||||
private long computeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum)
|
||||
{
|
||||
double accuracyRatio = maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1;
|
||||
double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1;
|
||||
@@ -474,14 +470,14 @@ namespace osu.Game.Rulesets.Scoring
|
||||
/// Consumers are expected to more accurately fill in the above values through external means.
|
||||
/// <para>
|
||||
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
|
||||
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>.
|
||||
/// <see cref="computeScore(osu.Game.Rulesets.Scoring.ScoringMode,ScoringValues,ScoringValues)"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="scoreInfo">The score to extract scoring values from.</param>
|
||||
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
|
||||
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
|
||||
[Pure]
|
||||
internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum)
|
||||
private void extractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum)
|
||||
{
|
||||
extractScoringValues(scoreInfo.Statistics, out current, out maximum);
|
||||
current.MaxCombo = scoreInfo.MaxCombo;
|
||||
@@ -490,31 +486,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
extractScoringValues(scoreInfo.MaximumStatistics, out _, out maximum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method is useful in a variety of situations, with a few drawbacks that need to be considered:
|
||||
/// <list type="bullet">
|
||||
/// <item>The maximum <see cref="ScoringValues.BonusScore"/> will always be 0.</item>
|
||||
/// <item>The current and maximum <see cref="ScoringValues.CountBasicHitObjects"/> will always be the same value.</item>
|
||||
/// </list>
|
||||
/// Consumers are expected to more accurately fill in the above values through external means.
|
||||
/// <para>
|
||||
/// <b>Ensure</b> to fill in the maximum <see cref="ScoringValues.CountBasicHitObjects"/> for use in
|
||||
/// <see cref="ComputeScore(osu.Game.Rulesets.Scoring.ScoringMode,osu.Game.Scoring.ScoringValues,osu.Game.Scoring.ScoringValues)"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="header">The replay frame header to extract scoring values from.</param>
|
||||
/// <param name="current">The "current" scoring values, representing the hit statistics as they appear.</param>
|
||||
/// <param name="maximum">The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time.</param>
|
||||
[Pure]
|
||||
internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum)
|
||||
{
|
||||
extractScoringValues(header.Statistics, out current, out maximum);
|
||||
current.MaxCombo = header.MaxCombo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies a best-effort extraction of hit statistics into <see cref="ScoringValues"/>.
|
||||
/// </summary>
|
||||
@@ -589,6 +560,32 @@ namespace osu.Game.Rulesets.Scoring
|
||||
base.Dispose(isDisposing);
|
||||
hitEvents.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores the required scoring data that fulfils the minimum requirements for a <see cref="ScoreProcessor"/> to calculate score.
|
||||
/// </summary>
|
||||
private struct ScoringValues
|
||||
{
|
||||
/// <summary>
|
||||
/// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
|
||||
/// </summary>
|
||||
public long BaseScore;
|
||||
|
||||
/// <summary>
|
||||
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
|
||||
/// </summary>
|
||||
public long BonusScore;
|
||||
|
||||
/// <summary>
|
||||
/// The highest achieved combo.
|
||||
/// </summary>
|
||||
public int MaxCombo;
|
||||
|
||||
/// <summary>
|
||||
/// The count of "basic" <see cref="HitObject"/>s. See: <see cref="HitResultExtensions.IsBasic"/>.
|
||||
/// </summary>
|
||||
public int CountBasicHitObjects;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ScoringMode
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// 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.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public partial class DrawableRulesetDependenciesProvidingContainer : Container
|
||||
{
|
||||
private readonly Ruleset ruleset;
|
||||
|
||||
private DrawableRulesetDependencies rulesetDependencies = null!;
|
||||
|
||||
public DrawableRulesetDependenciesProvidingContainer(Ruleset ruleset)
|
||||
{
|
||||
this.ruleset = ruleset;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
return rulesetDependencies = new DrawableRulesetDependencies(ruleset, base.CreateChildDependencies(parent));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (rulesetDependencies.IsNotNull())
|
||||
rulesetDependencies.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,8 @@ namespace osu.Game.Rulesets.UI
|
||||
public readonly BindableBool DisplayJudgements = new BindableBool(true);
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IReadOnlyList<Mod> mods { get; set; }
|
||||
[CanBeNull]
|
||||
protected IReadOnlyList<Mod> Mods { get; private set; }
|
||||
|
||||
private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager();
|
||||
|
||||
@@ -243,9 +244,9 @@ namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!IsNested && mods != null)
|
||||
if (!IsNested && Mods != null)
|
||||
{
|
||||
foreach (var mod in mods)
|
||||
foreach (var mod in Mods)
|
||||
{
|
||||
if (mod is IUpdatableByPlayfield updatable)
|
||||
updatable.Update(this);
|
||||
@@ -374,9 +375,9 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
// If this is the first time this DHO is being used, then apply the DHO mods.
|
||||
// This is done before Apply() so that the state is updated once when the hitobject is applied.
|
||||
if (mods != null)
|
||||
if (Mods != null)
|
||||
{
|
||||
foreach (var m in mods.OfType<IApplicableToDrawableHitObject>())
|
||||
foreach (var m in Mods.OfType<IApplicableToDrawableHitObject>())
|
||||
m.ApplyToDrawableHitObject(dho);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
||||
break;
|
||||
}
|
||||
|
||||
double lastObjectTime = Objects.LastOrDefault()?.GetEndTime() ?? double.MaxValue;
|
||||
double lastObjectTime = Beatmap.HitObjects.Any() ? Beatmap.GetLastObjectTime() : double.MaxValue;
|
||||
double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH;
|
||||
|
||||
if (RelativeScaleBeatLengths)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user