mirror of
https://github.com/ppy/osu.git
synced 2025-03-21 12:57:20 +08:00
Merge branch 'master' into extended-statistics-without-replay
This commit is contained in:
commit
df9d99f5aa
@ -51,7 +51,7 @@
|
||||
<Reference Include="Java.Interop" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.202.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.128.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Transitive Dependencies">
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public override double ScoreMultiplier => 1.12;
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||
{
|
||||
MinValue = 0.5f,
|
||||
MaxValue = 1.5f,
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||
{
|
||||
MinValue = 0.5f,
|
||||
MaxValue = 3f,
|
||||
|
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
Normal file
27
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAimAssist.cs
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModAimAssist : OsuModTestScene
|
||||
{
|
||||
[TestCase(0.1f)]
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(1)]
|
||||
public void TestAimAssist(float strength)
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAimAssist
|
||||
{
|
||||
AssistStrength = { Value = strength },
|
||||
},
|
||||
PassCondition = () => true,
|
||||
Autoplay = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
154
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
Normal file
154
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs
Normal file
@ -0,0 +1,154 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModAlternate : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestInputAtIntro() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAlternate(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(200)),
|
||||
new OsuReplayFrame(1000, new Vector2(100), OsuAction.LeftButton),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputAlternating() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAlternate(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1500,
|
||||
Position = new Vector2(300, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2000,
|
||||
Position = new Vector2(400, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(1001, new Vector2(200, 100)),
|
||||
new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(1501, new Vector2(300, 100)),
|
||||
new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton),
|
||||
new OsuReplayFrame(2001, new Vector2(400, 100)),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputSingular() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAlternate(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(200, 100),
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton),
|
||||
}
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputSingularWithBreak() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAlternate(),
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2,
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(500, 2250),
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 500,
|
||||
Position = new Vector2(100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 2500,
|
||||
Position = new Vector2(100),
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(501, new Vector2(100)),
|
||||
new OsuReplayFrame(2500, new Vector2(100), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(2501, new Vector2(100)),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
83
osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
Normal file
83
osu.Game.Rulesets.Osu/Mods/OsuModAimAssist.cs
Normal file
@ -0,0 +1,83 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
internal class OsuModAimAssist : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Aim Assist";
|
||||
public override string Acronym => "AA";
|
||||
public override IconUsage? Icon => FontAwesome.Solid.MousePointer;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override string Description => "No need to chase the circle – the circle chases you!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) };
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
|
||||
[SettingSource("Assist strength", "How much this mod will assist you.", 0)]
|
||||
public BindableFloat AssistStrength { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.05f,
|
||||
MinValue = 0.05f,
|
||||
MaxValue = 1.0f,
|
||||
};
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
|
||||
// Hide judgment displays and follow points as they won't make any sense.
|
||||
// Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart.
|
||||
drawableRuleset.Playfield.DisplayJudgements.Value = false;
|
||||
(drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide();
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition;
|
||||
|
||||
foreach (var drawable in playfield.HitObjectContainer.AliveObjects)
|
||||
{
|
||||
switch (drawable)
|
||||
{
|
||||
case DrawableHitCircle circle:
|
||||
easeTo(circle, cursorPos);
|
||||
break;
|
||||
|
||||
case DrawableSlider slider:
|
||||
|
||||
if (!slider.HeadCircle.Result.HasResult)
|
||||
easeTo(slider, cursorPos);
|
||||
else
|
||||
easeTo(slider, cursorPos - slider.Ball.DrawPosition);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void easeTo(DrawableHitObject hitObject, Vector2 destination)
|
||||
{
|
||||
double dampLength = Interpolation.Lerp(3000, 40, AssistStrength.Value);
|
||||
|
||||
float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime);
|
||||
float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime);
|
||||
|
||||
hitObject.Position = new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
}
|
106
osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
Normal file
106
osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs
Normal file
@ -0,0 +1,106 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAlternate : Mod, IApplicableToDrawableRuleset<OsuHitObject>, IApplicableToPlayer
|
||||
{
|
||||
public override string Name => @"Alternate";
|
||||
public override string Acronym => @"AL";
|
||||
public override string Description => @"Don't use the same key twice in a row!";
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
public override IconUsage? Icon => FontAwesome.Solid.Keyboard;
|
||||
|
||||
private double firstObjectValidJudgementTime;
|
||||
private IBindable<bool> isBreakTime;
|
||||
private const double flash_duration = 1000;
|
||||
private OsuAction? lastActionPressed;
|
||||
private DrawableRuleset<OsuHitObject> ruleset;
|
||||
|
||||
private IFrameStableClock gameplayClock;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var firstHitObject = ruleset.Objects.FirstOrDefault();
|
||||
firstObjectValidJudgementTime = (firstHitObject?.StartTime ?? 0) - (firstHitObject?.HitWindows.WindowFor(HitResult.Meh) ?? 0);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
isBreakTime = player.IsBreakTime.GetBoundCopy();
|
||||
isBreakTime.ValueChanged += e =>
|
||||
{
|
||||
if (e.NewValue)
|
||||
lastActionPressed = null;
|
||||
};
|
||||
}
|
||||
|
||||
private bool checkCorrectAction(OsuAction action)
|
||||
{
|
||||
if (isBreakTime.Value)
|
||||
return true;
|
||||
|
||||
if (gameplayClock.CurrentTime < firstObjectValidJudgementTime)
|
||||
return true;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case OsuAction.LeftButton:
|
||||
case OsuAction.RightButton:
|
||||
break;
|
||||
|
||||
// Any action which is not left or right button should be ignored.
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lastActionPressed != action)
|
||||
{
|
||||
// User alternated correctly.
|
||||
lastActionPressed = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
private class InputInterceptor : Component, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
private readonly OsuModAlternate mod;
|
||||
|
||||
public InputInterceptor(OsuModAlternate mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Automation;
|
||||
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModAimAssist) };
|
||||
|
||||
public bool PerformFail() => false;
|
||||
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAutoplay : ModAutoplay
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModCinema : ModCinema<OsuHitObject>
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAimAssist), typeof(OsuModAutopilot), typeof(OsuModSpunOut) }).ToArray();
|
||||
|
||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||
{
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
};
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||
{
|
||||
MinValue = 0.5f,
|
||||
MaxValue = 2f,
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override string Description => "Everything rotates. EVERYTHING.";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModAimAssist) };
|
||||
|
||||
private float theta;
|
||||
|
||||
|
@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override string Description => "They just won't stay still...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModAimAssist) };
|
||||
|
||||
private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles
|
||||
private const int wiggle_strength = 10; // Higher = stronger wiggles
|
||||
|
@ -169,6 +169,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModClassic(),
|
||||
new OsuModRandom(),
|
||||
new OsuModMirror(),
|
||||
new OsuModAlternate(),
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
@ -193,6 +194,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModApproachDifferent(),
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
new OsuModAimAssist(),
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -31,7 +31,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
private readonly ProxyContainer approachCircles;
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||
private readonly FollowPointRenderer followPoints;
|
||||
|
||||
public FollowPointRenderer FollowPoints { get; }
|
||||
|
||||
public static readonly Vector2 BASE_SIZE = new Vector2(512, 384);
|
||||
|
||||
@ -50,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
playfieldBorder = new PlayfieldBorder { RelativeSizeAxes = Axes.Both },
|
||||
spinnerProxies = new ProxyContainer { RelativeSizeAxes = Axes.Both },
|
||||
followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||
FollowPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both },
|
||||
judgementLayer = new JudgementContainer<DrawableOsuJudgement> { RelativeSizeAxes = Axes.Both },
|
||||
HitObjectContainer,
|
||||
judgementAboveHitObjectLayer = new Container { RelativeSizeAxes = Axes.Both },
|
||||
@ -131,13 +132,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
protected override void OnHitObjectAdded(HitObject hitObject)
|
||||
{
|
||||
base.OnHitObjectAdded(hitObject);
|
||||
followPoints.AddFollowPoints((OsuHitObject)hitObject);
|
||||
FollowPoints.AddFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
protected override void OnHitObjectRemoved(HitObject hitObject)
|
||||
{
|
||||
base.OnHitObjectRemoved(hitObject);
|
||||
followPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
FollowPoints.RemoveFollowPoints((OsuHitObject)hitObject);
|
||||
}
|
||||
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
public override double ScoreMultiplier => 1.12;
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public override BindableNumber<float> SizeMultiplier { get; } = new BindableNumber<float>
|
||||
public override BindableFloat SizeMultiplier { get; } = new BindableFloat
|
||||
{
|
||||
MinValue = 0.5f,
|
||||
MaxValue = 1.5f,
|
||||
|
@ -15,8 +15,12 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
@ -77,6 +81,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaikoOnlyMod()
|
||||
{
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
SelectedRoom.Value.Playlist.Add(new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo },
|
||||
Ruleset = { Value = new TaikoRuleset().RulesetInfo },
|
||||
AllowedMods = { new TaikoModSwap() }
|
||||
});
|
||||
});
|
||||
|
||||
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
|
||||
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
AddStep("select swap mod", () => Client.ChangeUserMods(API.LocalUser.Value.OnlineID, new[] { new TaikoModSwap() }));
|
||||
AddUntilStep("participant panel has mod", () => this.ChildrenOfType<ParticipantPanel>().Any(p => p.ChildrenOfType<ModIcon>().Any(m => m.Mod is TaikoModSwap)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSettingValidity()
|
||||
{
|
||||
|
@ -128,6 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("choose clear all scores", () => InputManager.Key(Key.Number4));
|
||||
|
||||
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
|
||||
AddUntilStep("wait for dialog", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog != null);
|
||||
AddStep("confirm deletion", () => InputManager.Key(Key.Number1));
|
||||
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
|
||||
@ -172,6 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dialog display", () => Game.Dependencies.Get<DialogOverlay>().IsLoaded);
|
||||
AddUntilStep("wait for dialog", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog != null);
|
||||
AddStep("confirm deletion", () => InputManager.Key(Key.Number1));
|
||||
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<DialogOverlay>().CurrentDialog == null);
|
||||
|
@ -601,7 +601,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
BeatmapSetInfo testMixed = null;
|
||||
|
||||
createCarousel();
|
||||
createCarousel(new List<BeatmapSetInfo>());
|
||||
|
||||
AddStep("add mixed ruleset beatmapset", () =>
|
||||
{
|
||||
@ -765,22 +765,22 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
createCarousel(c =>
|
||||
if (beatmapSets == null)
|
||||
{
|
||||
beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
for (int i = 1; i <= (count ?? set_count); i++)
|
||||
{
|
||||
beatmapSets.Add(randomDifficulties
|
||||
? TestResources.CreateTestBeatmapSetInfo()
|
||||
: TestResources.CreateTestBeatmapSetInfo(3));
|
||||
}
|
||||
}
|
||||
|
||||
createCarousel(beatmapSets, c =>
|
||||
{
|
||||
carouselAdjust?.Invoke(c);
|
||||
|
||||
if (beatmapSets == null)
|
||||
{
|
||||
beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
for (int i = 1; i <= (count ?? set_count); i++)
|
||||
{
|
||||
beatmapSets.Add(randomDifficulties
|
||||
? TestResources.CreateTestBeatmapSetInfo()
|
||||
: TestResources.CreateTestBeatmapSetInfo(3));
|
||||
}
|
||||
}
|
||||
|
||||
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
|
||||
carousel.BeatmapSetsChanged = () => changed = true;
|
||||
carousel.BeatmapSets = beatmapSets;
|
||||
@ -789,7 +789,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("Wait for load", () => changed);
|
||||
}
|
||||
|
||||
private void createCarousel(Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
||||
private void createCarousel(List<BeatmapSetInfo> beatmapSets, Action<BeatmapCarousel> carouselAdjust = null, Container target = null)
|
||||
{
|
||||
AddStep("Create carousel", () =>
|
||||
{
|
||||
@ -803,6 +803,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
carouselAdjust?.Invoke(carousel);
|
||||
|
||||
carousel.BeatmapSets = beatmapSets;
|
||||
|
||||
(target ?? this).Child = carousel;
|
||||
});
|
||||
}
|
||||
|
@ -197,7 +197,24 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModHidden(),
|
||||
new OsuModHardRock(),
|
||||
new OsuModFlashlight
|
||||
{
|
||||
FollowDelay = { Value = 200 },
|
||||
SizeMultiplier = { Value = 5 },
|
||||
},
|
||||
new OsuModDifficultyAdjust
|
||||
{
|
||||
CircleSize = { Value = 11 },
|
||||
ApproachRate = { Value = 10 },
|
||||
OverallDifficulty = { Value = 10 },
|
||||
DrainRate = { Value = 10 },
|
||||
ExtendedLimits = { Value = true }
|
||||
}
|
||||
},
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
BeatmapInfo = beatmapInfo,
|
||||
User = new APIUser
|
||||
@ -217,7 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
User = new APIUser
|
||||
@ -237,7 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -258,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -279,7 +296,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 1,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -300,7 +317,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 0.9826,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -321,7 +338,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 0.9654,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -342,7 +359,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 0.6025,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -363,7 +380,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 0.5140,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
@ -384,7 +401,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Accuracy = 0.4222,
|
||||
MaxCombo = 244,
|
||||
TotalScore = 1707827,
|
||||
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||
BeatmapInfo = beatmapInfo,
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
|
||||
|
@ -30,6 +30,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
Alpha = 0.9f,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new FillFlowContainer
|
||||
|
@ -10,6 +10,8 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -30,13 +32,17 @@ namespace osu.Game.Graphics.Cursor
|
||||
private DragRotationState dragRotationState;
|
||||
private Vector2 positionMouseDown;
|
||||
|
||||
private Sample tapSample;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager)
|
||||
private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio)
|
||||
{
|
||||
cursorRotate = config.GetBindable<bool>(OsuSetting.CursorRotation);
|
||||
|
||||
if (screenshotManager != null)
|
||||
screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility);
|
||||
|
||||
tapSample = audio.Samples.Get(@"UI/cursor-tap");
|
||||
}
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
@ -87,6 +93,8 @@ namespace osu.Game.Graphics.Cursor
|
||||
dragRotationState = DragRotationState.DragStarted;
|
||||
positionMouseDown = e.MousePosition;
|
||||
}
|
||||
|
||||
playTapSample();
|
||||
}
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
@ -104,6 +112,9 @@ namespace osu.Game.Graphics.Cursor
|
||||
activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf);
|
||||
dragRotationState = DragRotationState.NotDragging;
|
||||
}
|
||||
|
||||
if (State.Value == Visibility.Visible)
|
||||
playTapSample(0.8);
|
||||
}
|
||||
|
||||
base.OnMouseUp(e);
|
||||
@ -121,6 +132,18 @@ namespace osu.Game.Graphics.Cursor
|
||||
activeCursor.ScaleTo(0.6f, 250, Easing.In);
|
||||
}
|
||||
|
||||
private void playTapSample(double baseFrequency = 1f)
|
||||
{
|
||||
const float random_range = 0.02f;
|
||||
SampleChannel channel = tapSample.GetChannel();
|
||||
|
||||
// Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird)
|
||||
channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * 0.75;
|
||||
channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range);
|
||||
|
||||
channel.Play();
|
||||
}
|
||||
|
||||
public class Cursor : Container
|
||||
{
|
||||
private Container cursorContainer;
|
||||
|
@ -399,7 +399,10 @@ namespace osu.Game.Online.API
|
||||
lock (queue)
|
||||
{
|
||||
if (state.Value == APIState.Offline)
|
||||
{
|
||||
request.Fail(new WebException(@"User not logged in"));
|
||||
return;
|
||||
}
|
||||
|
||||
queue.Enqueue(request);
|
||||
}
|
||||
@ -416,7 +419,7 @@ namespace osu.Game.Online.API
|
||||
if (failOldRequests)
|
||||
{
|
||||
foreach (var req in oldQueueRequests)
|
||||
req.Fail(new WebException(@"Disconnected from server"));
|
||||
req.Fail(new WebException($@"Request failed from flush operation (state {state.Value})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public class LeaderboardScore : OsuClickableContainer, IHasContextMenu
|
||||
public class LeaderboardScore : OsuClickableContainer, IHasContextMenu, IHasCustomTooltip<ScoreInfo>
|
||||
{
|
||||
public const float HEIGHT = 60;
|
||||
|
||||
@ -70,6 +70,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
[Resolved]
|
||||
private Storage storage { get; set; }
|
||||
|
||||
public ITooltip<ScoreInfo> GetCustomTooltip() => new LeaderboardScoreTooltip();
|
||||
public virtual ScoreInfo TooltipContent => Score;
|
||||
|
||||
public LeaderboardScore(ScoreInfo score, int? rank, bool isOnlineScope = true)
|
||||
{
|
||||
Score = score;
|
||||
@ -183,7 +186,6 @@ namespace osu.Game.Online.Leaderboards
|
||||
Anchor = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10f, 0f),
|
||||
Margin = new MarginPadding { Left = edge_margin },
|
||||
Children = statisticsLabels
|
||||
},
|
||||
@ -228,7 +230,6 @@ namespace osu.Game.Online.Leaderboards
|
||||
Origin = Anchor.BottomRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(1),
|
||||
ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) })
|
||||
},
|
||||
},
|
||||
@ -313,6 +314,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Padding = new MarginPadding { Right = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
|
219
osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs
Normal file
219
osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs
Normal file
@ -0,0 +1,219 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Game.Scoring;
|
||||
using osuTK;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public class LeaderboardScoreTooltip : VisibilityContainer, ITooltip<ScoreInfo>
|
||||
{
|
||||
private OsuSpriteText timestampLabel = null!;
|
||||
private FillFlowContainer<HitResultCell> topScoreStatistics = null!;
|
||||
private FillFlowContainer<HitResultCell> bottomScoreStatistics = null!;
|
||||
private FillFlowContainer<ModCell> modStatistics = null!;
|
||||
|
||||
public LeaderboardScoreTooltip()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
AutoSizeDuration = 200;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.9f,
|
||||
Colour = colours.Gray3,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
Spacing = new Vector2(10),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// Info row
|
||||
timestampLabel = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||
},
|
||||
// Mods row
|
||||
modStatistics = new FillFlowContainer<ModCell>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5, 0),
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// Actual stats rows
|
||||
topScoreStatistics = new FillFlowContainer<HitResultCell>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
},
|
||||
bottomScoreStatistics = new FillFlowContainer<HitResultCell>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10, 0),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ScoreInfo? displayedScore;
|
||||
|
||||
public void SetContent(ScoreInfo score)
|
||||
{
|
||||
if (displayedScore?.Equals(score) == true)
|
||||
return;
|
||||
|
||||
displayedScore = score;
|
||||
|
||||
timestampLabel.Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}";
|
||||
|
||||
modStatistics.Clear();
|
||||
topScoreStatistics.Clear();
|
||||
bottomScoreStatistics.Clear();
|
||||
|
||||
foreach (var mod in score.Mods)
|
||||
{
|
||||
modStatistics.Add(new ModCell(mod));
|
||||
}
|
||||
|
||||
foreach (var result in score.GetStatisticsForDisplay())
|
||||
{
|
||||
if (result.Result > HitResult.Perfect)
|
||||
bottomScoreStatistics.Add(new HitResultCell(result));
|
||||
else
|
||||
topScoreStatistics.Add(new HitResultCell(result));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(20, Easing.OutQuint);
|
||||
protected override void PopOut() => this.FadeOut(80, Easing.OutQuint);
|
||||
|
||||
public void Move(Vector2 pos) => Position = pos;
|
||||
|
||||
private class HitResultCell : CompositeDrawable
|
||||
{
|
||||
private readonly string displayName;
|
||||
private readonly HitResult result;
|
||||
private readonly int count;
|
||||
|
||||
public HitResultCell(HitResultDisplayStatistic stat)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
displayName = stat.DisplayName;
|
||||
result = stat.Result;
|
||||
count = stat.Count;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
Height = 12,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = displayName.ToUpperInvariant(),
|
||||
Colour = colours.ForHitResult(result),
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = count.ToString(),
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class ModCell : CompositeDrawable
|
||||
{
|
||||
private readonly Mod mod;
|
||||
|
||||
public ModCell(Mod mod)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
FillFlowContainer container;
|
||||
InternalChild = container = new FillFlowContainer
|
||||
{
|
||||
Height = 15,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(2f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ModIcon(mod, showTooltip: false).With(icon =>
|
||||
{
|
||||
icon.Origin = Anchor.CentreLeft;
|
||||
icon.Anchor = Anchor.CentreLeft;
|
||||
icon.Scale = new Vector2(15f / icon.Height);
|
||||
}),
|
||||
}
|
||||
};
|
||||
|
||||
string description = mod.SettingDescription;
|
||||
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
container.Add(new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
|
||||
Text = mod.SettingDescription,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Top = 1 },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
{
|
||||
ShowsDefaultIndicator = false,
|
||||
Current = currentNumber,
|
||||
KeyboardStep = 0.1f,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods
|
||||
public override string Description => "Restricted view area.";
|
||||
|
||||
[SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")]
|
||||
public abstract BindableNumber<float> SizeMultiplier { get; }
|
||||
public abstract BindableFloat SizeMultiplier { get; }
|
||||
|
||||
[SettingSource("Change size based on combo", "Decrease the flashlight size as combo increases.")]
|
||||
public abstract BindableBool ComboBasedSize { get; }
|
||||
|
@ -149,6 +149,10 @@ namespace osu.Game.Rulesets
|
||||
var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
|
||||
?? throw new RulesetLoadException(@"Instantiation failure");
|
||||
|
||||
// If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
|
||||
// To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
|
||||
resolvedType.Assembly.GetTypes();
|
||||
|
||||
r.Name = instanceInfo.Name;
|
||||
r.ShortName = instanceInfo.ShortName;
|
||||
r.InstantiationInfo = instanceInfo.InstantiationInfo;
|
||||
|
@ -14,6 +14,8 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
private readonly APIUserScoreAggregate score;
|
||||
|
||||
public override ScoreInfo TooltipContent => null; // match aggregate scores can't show statistics that the custom tooltip displays.
|
||||
|
||||
public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool isOnlineScope = true)
|
||||
: base(score.CreateScoreInfo(), rank, isOnlineScope)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -18,6 +19,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Users;
|
||||
@ -184,8 +186,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
const double fade_time = 50;
|
||||
|
||||
// Todo: Should use the room's selected item to determine ruleset.
|
||||
var ruleset = rulesets.GetRuleset(0)?.CreateInstance();
|
||||
var currentItem = Playlist.GetCurrentItem();
|
||||
Debug.Assert(currentItem != null);
|
||||
|
||||
var ruleset = rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance();
|
||||
|
||||
int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null;
|
||||
userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty;
|
||||
|
@ -17,6 +17,7 @@ using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -83,10 +84,7 @@ namespace osu.Game.Screens.Play
|
||||
Children = new Drawable[]
|
||||
{
|
||||
CreateFailingLayer(),
|
||||
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
mainComponents = new MainComponentsContainer(),
|
||||
topRightElements = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
@ -325,5 +323,29 @@ namespace osu.Game.Screens.Play
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private class MainComponentsContainer : SkinnableTargetContainer
|
||||
{
|
||||
private Bindable<ScoringMode> scoringMode;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
public MainComponentsContainer()
|
||||
: base(SkinnableTarget.MainHUDComponents)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// When the scoring mode changes, relative positions of elements may change (see DefaultSkin.GetDrawableComponent).
|
||||
// This is a best effort implementation for cases where users haven't customised layouts.
|
||||
scoringMode = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode);
|
||||
scoringMode.BindValueChanged(val => Reload());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,12 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -50,18 +54,37 @@ namespace osu.Game.Tests.Visual
|
||||
return CreateModPlayer(ruleset);
|
||||
}
|
||||
|
||||
protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(AllowFail);
|
||||
protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail);
|
||||
|
||||
protected class ModTestPlayer : TestPlayer
|
||||
{
|
||||
private readonly bool allowFail;
|
||||
private readonly ModTestData currentTestData;
|
||||
|
||||
protected override bool CheckModsAllowFailure() => allowFail;
|
||||
|
||||
public ModTestPlayer(bool allowFail)
|
||||
public ModTestPlayer(ModTestData data, bool allowFail)
|
||||
: base(false, false)
|
||||
{
|
||||
this.allowFail = allowFail;
|
||||
currentTestData = data;
|
||||
}
|
||||
|
||||
protected override void PrepareReplay()
|
||||
{
|
||||
if (currentTestData.Autoplay && currentTestData.ReplayFrames?.Count > 0)
|
||||
throw new InvalidOperationException(@$"{nameof(ModTestData.Autoplay)} must be false when {nameof(ModTestData.ReplayFrames)} is specified.");
|
||||
|
||||
if (currentTestData.ReplayFrames != null)
|
||||
{
|
||||
DrawableRuleset?.SetReplayScore(new Score
|
||||
{
|
||||
Replay = new Replay { Frames = currentTestData.ReplayFrames },
|
||||
ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" } },
|
||||
});
|
||||
}
|
||||
|
||||
base.PrepareReplay();
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +95,12 @@ namespace osu.Game.Tests.Visual
|
||||
/// </summary>
|
||||
public bool Autoplay = true;
|
||||
|
||||
/// <summary>
|
||||
/// The frames to use for replay. <see cref="Autoplay"/> must be set to false.
|
||||
/// </summary>
|
||||
[CanBeNull]
|
||||
public List<ReplayFrame> ReplayFrames;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap for this test case.
|
||||
/// </summary>
|
||||
|
@ -37,7 +37,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="10.8.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2022.128.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.202.0" />
|
||||
<PackageReference Include="Sentry" Version="3.13.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||
|
@ -61,7 +61,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.128.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.115.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.202.0" />
|
||||
</ItemGroup>
|
||||
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
|
||||
<PropertyGroup>
|
||||
|
Loading…
x
Reference in New Issue
Block a user